/* SCCS Id: @(#)amiwind.c 3.2 2000/01/12 /* Copyright (c) Olaf Seibert (KosmoSoft), 1989, 1992 */ /* Copyright (c) Kenneth Lorber, Bethesda, Maryland 1993,1996 */ /* NetHack may be freely redistributed. See license for details. */ #include "NH:sys/amiga/windefs.h" #include "NH:sys/amiga/winext.h" #include "NH:sys/amiga/winproto.h" /* Have to undef CLOSE as display.h and intuition.h both use it */ #undef CLOSE #ifdef AMII_GRAPHICS /* too early in the file? too late? */ #ifdef AMIFLUSH static struct Message *FDECL(GetFMsg,(struct MsgPort *)); #endif static int BufferGetchar(void); static void ProcessMessage( register struct IntuiMessage *message ); #define BufferQueueChar(ch) (KbdBuffer[KbdBuffered++] = (ch)) struct Library *ConsoleDevice; #include "NH:sys/amiga/amimenu.c" /* Now our own variables */ struct IntuitionBase *IntuitionBase; struct Screen *HackScreen; struct Window *pr_WindowPtr; struct MsgPort *HackPort; struct IOStdReq ConsoleIO; struct Menu *MenuStrip; APTR *VisualInfo; char Initialized = 0; WEVENT lastevent; #ifdef HACKFONT struct GfxBase *GfxBase; struct Library *DiskfontBase; #endif #define KBDBUFFER 10 static unsigned char KbdBuffer[KBDBUFFER]; unsigned char KbdBuffered; #ifdef HACKFONT struct TextFont *TextsFont = NULL; struct TextFont *HackFont = NULL; struct TextFont *RogueFont = NULL; UBYTE FontName[] = "NetHack:hack.font"; /* # chars in "NetHack:": */ #define SIZEOF_DISKNAME 8 #endif struct TextAttr Hack80 = { #ifdef HACKFONT &FontName[SIZEOF_DISKNAME], #else (UBYTE *) "topaz.font", #endif 8, FS_NORMAL, FPF_DISKFONT | FPF_DESIGNED | FPF_ROMFONT }; struct TextAttr TextsFont13 = { (UBYTE *) "courier.font", 13, FS_NORMAL, FPF_DISKFONT | FPF_DESIGNED #ifndef HACKFONT | FPF_ROMFONT #endif }; /* Avoid doing a ReplyMsg through a window that no longer exists. */ static enum {NoAction, CloseOver} delayed_key_action = NoAction; /* * Open a window that shares the HackPort IDCMP. Use CloseShWindow() * to close. */ struct Window *OpenShWindow(nw) struct NewWindow *nw; { register struct Window *win; register ULONG idcmpflags; if (!HackPort) /* Sanity check */ return (struct Window *) 0; idcmpflags = nw->IDCMPFlags; nw->IDCMPFlags = 0; if (!(win = OpenWindow((void *)nw))) { nw->IDCMPFlags = idcmpflags; return (struct Window *) 0; } nw->IDCMPFlags = idcmpflags; win->UserPort = HackPort; ModifyIDCMP(win, idcmpflags); return win; } /* * Close a window that shared the HackPort IDCMP port. */ void FDECL(CloseShWindow, (struct Window *)); void CloseShWindow(win) struct Window *win; { register struct IntuiMessage *msg; if( !HackPort ) panic("HackPort NULL in CloseShWindow" ); if (!win) return; Forbid(); /* Flush all messages for all windows to avoid typeahead and other * similar problems... */ while( msg = (struct IntuiMessage *)GetMsg( win->UserPort ) ) ReplyMsg( (struct Message *) msg ); KbdBuffered = 0; win->UserPort = (struct MsgPort *) 0; ModifyIDCMP(win, 0L); Permit(); CloseWindow(win); } static int BufferGetchar() { register int c; if (KbdBuffered > 0) { c = KbdBuffer[0]; KbdBuffered--; /* Move the remaining characters */ if( KbdBuffered < sizeof( KbdBuffer ) ) memcpy( KbdBuffer, KbdBuffer+1, KbdBuffered ); return c; } return NO_CHAR; } /* * This should remind you remotely of DeadKeyConvert, but we are cheating * a bit. We want complete control over the numeric keypad, and no dead * keys... (they are assumed to be on Alted keys). * * Also assumed is that the IntuiMessage is of type RAWKEY. For some * reason, IECODE_UP_PREFIX events seem to be lost when they occur while * our console window is inactive. This is particulary troublesome with * qualifier keys... Is this because I never RawKeyConvert those events??? */ int ConvertKey(message) register struct IntuiMessage *message; { static struct InputEvent theEvent; static char numpad[] = "bjnh.lyku"; static char ctrl_numpad[] = "\x02\x0A\x0E\x08.\x0C\x19\x0B\x15"; static char shift_numpad[] = "BJNH.LYKU"; unsigned char buffer[10]; struct Window *w = message->IDCMPWindow; register int length; register ULONG qualifier; char numeric_pad, shift, control, alt; if( amii_wins[ WIN_MAP ] ) w = amii_wins[ WIN_MAP ]->win; qualifier = message->Qualifier; control = (qualifier & IEQUALIFIER_CONTROL) != 0; shift = (qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)) != 0; alt = (qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT )) != 0; /* Allow ALT to function as a META key ... */ /* But make it switchable - alt is needed for some non-US keymaps */ if(flags.altmeta) qualifier &= ~(IEQUALIFIER_LALT | IEQUALIFIER_RALT); numeric_pad = (qualifier & IEQUALIFIER_NUMERICPAD) != 0; /* * Shortcut for HELP and arrow keys. I suppose this is allowed. * The defines are in intuition/intuition.h, and the keys don't * serve 'text' input, normally. Also, parsing their escape * sequences is such a mess... */ switch (message->Code) { case RAWHELP: if( alt ) { EditColor(); return( -1 ); } #ifdef CLIPPING else if( WINVERS_AMIV && control ) { EditClipping(); CO = ( w->Width - w->BorderLeft - w->BorderRight ) / mxsize; LI = ( w->Height - w->BorderTop - w->BorderBottom ) / mysize; clipxmax = CO + clipx; clipymax = LI + clipy; if( CO < COLNO || LI < ROWNO ) { clipping = TRUE; amii_cliparound( u.ux, u.uy ); } else { clipping = FALSE; clipx = clipy = 0; } BufferQueueChar( 'R'-64 ); return(-1); } #endif else if( WINVERS_AMIV && shift ) { if( WIN_OVER == WIN_ERR ) { WIN_OVER = amii_create_nhwindow( NHW_OVER ); BufferQueueChar( 'R'-64 ); } else { delayed_key_action = CloseOver; } return( -1 ); } return( '?' ); break; case CURSORLEFT: length = '4'; numeric_pad = 1; goto arrow; case CURSORDOWN: length = '2'; numeric_pad = 1; goto arrow; case CURSORUP: length = '8'; numeric_pad = 1; goto arrow; case CURSORRIGHT: length = '6'; numeric_pad = 1; goto arrow; } theEvent.ie_Class = IECLASS_RAWKEY; theEvent.ie_Code = message->Code; theEvent.ie_Qualifier = numeric_pad ? IEQUALIFIER_NUMERICPAD : qualifier; theEvent.ie_EventAddress = (APTR) (message->IAddress); length = RawKeyConvert(&theEvent, (char *)buffer, (long) sizeof(buffer), NULL); if (length == 1) { /* Plain ASCII character */ length = buffer[0]; /* * If iflags.num_pad is set, movement is by 4286. * If not set, translate 4286 into hjkl. * This way, the numeric pad can /always/ be used * for moving, though best results are when it is off. */ arrow: if (!iflags.num_pad && numeric_pad && length >= '1' && length <= '9') { length -= '1'; if (control) { length = ctrl_numpad[length]; } else if (shift) { length = shift_numpad[length]; } else { length = numpad[length]; } } /* Kludge to allow altmeta on eg. scandinavian keymap (# == shift+alt+3) and prevent it from interfering with # command (M-#) */ if (length == ('#'|0x80)) return '#'; if (alt && flags.altmeta) length |= 0x80; return(length); } /* else shift, ctrl, alt, amiga, F-key, shift-tab, etc */ else if( length > 1 ) { int i; if( length == 3 && buffer[ 0 ] == 155 && buffer[ 2 ] == 126 ) { int got = 1; switch( buffer[ 1 ] ) { case 53: mxsize = mysize = 8; break; case 54: mxsize = mysize = 16; break; case 55: mxsize = mysize = 24; break; case 56: mxsize = mysize = 32; break; case 57: mxsize = mysize = 48; break; default: got = 0; break; } #ifdef OPT_DISPMAP dispmap_sanity(); #endif if( got ) { CO = (w->Width-w->BorderLeft-w->BorderRight)/mxsize; LI = (w->Height-w->BorderTop-w->BorderBottom)/mysize; clipxmax = CO + clipx; clipymax = LI + clipy; if( CO < COLNO || LI < ROWNO ) { amii_cliparound( u.ux, u.uy ); } else { CO = COLNO; LI = ROWNO; } reclip = 1; doredraw(); flush_screen( 1 ); reclip = 0; /*BufferQueueChar( 'R'-64 );*/ return( -1 ); } } printf( "Unrecognized key: %d ", (int)buffer[0]); for( i = 1; i < length; ++i ) printf( "%d ", (int)buffer[i]); printf( "\n" ); } return( -1 ); } /* * Process an incoming IntuiMessage. * It would certainly look nicer if this could be done using a * PA_SOFTINT message port, but we cannot call RawKeyConvert() * during a software interrupt. * Anyway, amikbhit()/kbhit() is called often enough, and usually gets * ahead of input demands, when the user types ahead. */ static void ProcessMessage(message) register struct IntuiMessage *message; { int c; int cnt; menu_item *mip; static int skip_mouse=0; /* need to ignore next mouse event on * a window activation */ struct Window *w = message->IDCMPWindow; switch(message->Class) { case ACTIVEWINDOW: if( alwaysinvent && WIN_INVEN != WIN_ERR && w == amii_wins[ WIN_INVEN ]->win ) { cnt = DoMenuScroll( WIN_INVEN, 0, PICK_NONE, &mip ); } else if( scrollmsg && WIN_MESSAGE != WIN_ERR && w == amii_wins[ WIN_MESSAGE ]->win ) { cnt = DoMenuScroll( WIN_MESSAGE, 0, PICK_NONE, &mip ); } else { skip_mouse=1; } break; case MOUSEBUTTONS: { if( skip_mouse ) { skip_mouse=0; break; } if( !amii_wins[ WIN_MAP ] || w != amii_wins[ WIN_MAP ]->win ) break; if( message->Code == SELECTDOWN ) { lastevent.type = WEMOUSE; lastevent.un.mouse.x = message->MouseX; lastevent.un.mouse.y = message->MouseY; /* With shift equals RUN */ lastevent.un.mouse.qual = (message->Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) != 0; } } break; case MENUPICK: { USHORT thismenu; struct MenuItem *item; thismenu = message->Code; while (thismenu != MENUNULL) { item = ItemAddress(MenuStrip, (ULONG) thismenu); if (KbdBuffered < KBDBUFFER) BufferQueueChar((char)(GTMENUITEM_USERDATA(item))); thismenu = item->NextSelect; } } break; case REFRESHWINDOW: { if( scrollmsg && amii_wins[ WIN_MESSAGE ] && w == amii_wins[ WIN_MESSAGE ]->win ){ cnt = DoMenuScroll( WIN_MESSAGE, 0, PICK_NONE, &mip ); } } break; case CLOSEWINDOW: if( WIN_INVEN != WIN_ERR && w == amii_wins[ WIN_INVEN ]->win ) { dismiss_nhwindow( WIN_INVEN ); } if( WINVERS_AMIV && ( WIN_OVER != WIN_ERR && w == amii_wins[ WIN_OVER ]->win ) ){ destroy_nhwindow( WIN_OVER ); WIN_OVER = WIN_ERR; } break; case RAWKEY: if (!(message->Code & IECODE_UP_PREFIX)){ /* May queue multiple characters * but doesn't do that yet... */ if( ( c = ConvertKey(message) ) > 0 ) BufferQueueChar( c ); } break; case GADGETDOWN: if( WIN_MESSAGE != WIN_ERR && w == amii_wins[ WIN_MESSAGE ]->win ) { cnt = DoMenuScroll( WIN_MESSAGE, 0, PICK_NONE, &mip ); } else if( WIN_INVEN != WIN_ERR && w == amii_wins[ WIN_INVEN ]->win ) { cnt = DoMenuScroll( WIN_INVEN, 0, PICK_NONE, &mip ); } break; case NEWSIZE: if( WIN_MESSAGE != WIN_ERR && w == amii_wins[ WIN_MESSAGE ]->win ) { if( WINVERS_AMIV ) { /* Make sure that new size is honored for good. */ SetAPen( w->RPort, amii_msgBPen ); SetBPen( w->RPort, amii_msgBPen ); SetDrMd( w->RPort, JAM2 ); RectFill( w->RPort, w->BorderLeft, w->BorderTop, w->Width - w->BorderRight-1, w->Height - w->BorderBottom-1 ); } ReDisplayData( WIN_MESSAGE ); } else if( WIN_INVEN != WIN_ERR && w == amii_wins[ WIN_INVEN ]->win ) { ReDisplayData( WIN_INVEN ); } else if( WINVERS_AMIV && ( WIN_OVER != WIN_ERR && w == amii_wins[ WIN_OVER ]->win ) ){ BufferQueueChar( 'R'-64 ); } else if( WIN_MAP != WIN_ERR && w == amii_wins[ WIN_MAP ]->win ) { #ifdef CLIPPING CO = (w->Width-w->BorderLeft-w->BorderRight)/mxsize; LI = (w->Height-w->BorderTop-w->BorderBottom)/mysize; clipxmax = CO + clipx; clipymax = LI + clipy; if( CO < COLNO || LI < ROWNO ) { amii_cliparound( u.ux, u.uy ); } else { clipping = FALSE; clipx = clipy = 0; } BufferQueueChar( 'R'-64 ); #endif } break; } ReplyMsg((struct Message *) message); switch(delayed_key_action){ case CloseOver: amii_destroy_nhwindow( WIN_OVER ); WIN_OVER = WIN_ERR; delayed_key_action = NoAction; case NoAction: ; /* null */ } } #endif /* AMII_GRAPHICS */ /* * Get all incoming messages and fill up the keyboard buffer, * thus allowing Intuition to (maybe) free up the IntuiMessages. * Return when no more messages left, or keyboard buffer half full. * We need to do this since there is no one-to-one correspondence * between characters and incoming messages. */ #if defined(TTY_GRAPHICS) && !defined(AMII_GRAPHICS) int kbhit(){ return 0; } #else int kbhit() { int c; # ifdef TTY_GRAPHICS /* a kludge to defuse the mess in allmain.c */ /* I hope this is the right approach */ if(windowprocs.win_init_nhwindows==amii_procs.win_init_nhwindows)return 0; # endif c = amikbhit(); if( c <= 0 ) return( 0 ); return( c ); } #endif #ifdef AMII_GRAPHICS int amikbhit() { register struct IntuiMessage *message; while( KbdBuffered < KBDBUFFER / 2 ) { #ifdef AMIFLUSH message = (struct IntuiMessage *) GetFMsg(HackPort); #else message = (struct IntuiMessage *) GetMsg(HackPort); #endif if(message) { ProcessMessage(message); if( lastevent.type != WEUNK && lastevent.type != WEKEY ) break; } else break; } return ( lastevent.type == WEUNK ) ? KbdBuffered : -1; } /* * Get a character from the keyboard buffer, waiting if not available. * Ignore other kinds of events that happen in the mean time. */ int WindowGetchar( ) { while ((lastevent.type = WEUNK), amikbhit() <= 0) { WaitPort(HackPort); } return BufferGetchar(); } WETYPE WindowGetevent() { lastevent.type = WEUNK; while (amikbhit() == 0) { WaitPort(HackPort); } if( KbdBuffered ) { lastevent.type = WEKEY; lastevent.un.key = BufferGetchar(); } return( lastevent.type ); } /* * Clean up everything. But before we do, ask the user to hit return * when there is something that s/he should read. */ void amii_cleanup() { register struct IntuiMessage *msg; /* Close things up */ if( HackPort ) { amii_raw_print(""); amii_getret(); } if (ConsoleIO.io_Device) CloseDevice( (struct IORequest *)&ConsoleIO ); ConsoleIO.io_Device = 0; if( ConsoleIO.io_Message.mn_ReplyPort ) DeleteMsgPort( ConsoleIO.io_Message.mn_ReplyPort ); ConsoleIO.io_Message.mn_ReplyPort = 0; /* Strip messages before deleting the port */ if( HackPort ) { Forbid(); while (msg = (struct IntuiMessage *) GetMsg(HackPort)) ReplyMsg((struct Message *) msg); kill_nhwindows( 1 ); DeleteMsgPort( HackPort ); HackPort = NULL; Permit(); } /* Close the screen, under v37 or greater it is a pub screen and there may * be visitors, so check close status and wait till everyone is gone. */ if( HackScreen ) { #ifdef INTUI_NEW_LOOK if( IntuitionBase->LibNode.lib_Version >= 37 ) { if (MenuStrip) FreeMenus(MenuStrip); if (VisualInfo) FreeVisualInfo(VisualInfo); while( CloseScreen( HackScreen ) == FALSE ) { struct EasyStruct easy = { sizeof( struct EasyStruct ), 0, "Nethack Problem", "Can't Close Screen, Close Visiting Windows", "Okay" }; EasyRequest( NULL, &easy, NULL, NULL ); } } else #endif { CloseScreen(HackScreen); } HackScreen = NULL; } #ifdef HACKFONT if (HackFont) { CloseFont(HackFont); HackFont = NULL; } if( TextsFont ) { CloseFont( TextsFont ); TextsFont = NULL; } if( RogueFont ) { CloseFont( RogueFont ); RogueFont = NULL; } if( DiskfontBase ) { CloseLibrary(DiskfontBase); DiskfontBase = NULL; } #endif if (GadToolsBase) { CloseLibrary((struct Library *)GadToolsBase); GadToolsBase=NULL; } if (LayersBase) { CloseLibrary((struct Library *)LayersBase); LayersBase = NULL; } if (GfxBase) { CloseLibrary((struct Library *)GfxBase); GfxBase = NULL; } if (IntuitionBase) { CloseLibrary((struct Library *)IntuitionBase); IntuitionBase = NULL; } #ifdef SHAREDLIB if (DOSBase) { CloseLibrary((struct Library *)DOSBase); DOSBase = NULL; } #endif ((struct Process *) FindTask(NULL))->pr_WindowPtr = (APTR) pr_WindowPtr; Initialized = 0; } #endif /* AMII_GRAPHICS */ #ifndef SHAREDLIB void Abort(rc) long rc; { int fault = 1; #ifdef CHDIR extern char orgdir[]; chdir(orgdir); #endif #ifdef AMII_GRAPHICS if (Initialized && ConsoleDevice && windowprocs.win_init_nhwindows==amii_procs.win_init_nhwindows) { printf("\n\nAbort with alert code %08lx...\n", rc); amii_getret(); } else #endif printf("\n\nAbort with alert code %08lx...\n",rc); /* Alert(rc); this is too severe */ #ifdef __SASC # ifdef INTUI_NEW_LOOK if( IntuitionBase->LibNode.lib_Version >= 37 ) { struct EasyStruct es = { sizeof( struct EasyStruct ), 0, "NetHack Panic Request", "NetHack is Aborting with code == 0x%08lx", "Continue Abort|Return to Program|Clean up and exit", }; fault = EasyRequest( NULL, &es, NULL, (long)rc ); if( fault == 2 ) return; } # endif if( fault == 1 ) { /* __emit(0x4afc); */ /* illegal instruction */ __emit(0x40fc); /* divide by */ __emit(0x0000); /* #0 */ /* NOTE: don't move amii_cleanup() above here - */ /* it is too likely to kill the system */ /* before it can get the SnapShot out, if */ /* there is something really wrong. */ } #endif #ifdef AMII_GRAPHICS if(windowprocs.win_init_nhwindows==amii_procs.win_init_nhwindows) amii_cleanup(); #endif #undef exit #ifdef AZTEC_C _abort(); #endif exit((int) rc); } void CleanUp() { amii_cleanup(); } #endif #ifdef AMII_GRAPHICS #ifdef AMIFLUSH /* This routine adapted from AmigaMail IV-37 by Michael Sinz */ static struct Message * GetFMsg(port) struct MsgPort *port; { struct IntuiMessage *msg,*succ,*succ1; if(msg=(struct IntuiMessage *)GetMsg(port)){ if(!flags.amiflush)return((struct Message *)msg); if(msg->Class==RAWKEY){ Forbid(); succ=(struct IntuiMessage *)(port->mp_MsgList.lh_Head); while(succ1=(struct IntuiMessage *) (succ->ExecMessage.mn_Node.ln_Succ)){ if(succ->Class==RAWKEY){ Remove((struct Node *)succ); ReplyMsg((struct Message *)succ); } succ=succ1; } Permit(); } } return((struct Message *)msg); } #endif struct NewWindow * DupNewWindow( win ) struct NewWindow *win; { struct NewWindow *nwin; struct Gadget *ngd, *gd, *pgd = NULL; struct PropInfo *pip; struct StringInfo *sip; /* Copy the (Ext)NewWindow structure */ nwin = (struct NewWindow *)alloc( sizeof( struct NewWindow ) ); *nwin = *win; /* Now do the gadget list */ nwin->FirstGadget = NULL; for( gd = win->FirstGadget; gd; gd = gd->NextGadget ) { ngd = (struct Gadget *)alloc( sizeof( struct Gadget ) ); *ngd = *gd; if( gd->GadgetType == STRGADGET ) { sip = (struct StringInfo *)alloc( sizeof( struct StringInfo ) ); *sip = *((struct StringInfo *)gd->SpecialInfo); sip->Buffer = (UBYTE *) alloc( sip->MaxChars ); *sip->Buffer = 0; ngd->SpecialInfo = (APTR)sip; } else if( gd->GadgetType == PROPGADGET ) { pip = (struct PropInfo *)alloc( sizeof( struct PropInfo ) ); *pip = *((struct PropInfo *)gd->SpecialInfo); ngd->SpecialInfo = (APTR)pip; } if( pgd ) pgd->NextGadget = ngd; else nwin->FirstGadget = ngd; pgd = ngd; ngd->NextGadget = NULL; ngd->UserData = (APTR) 0x45f35c3d; // magic cookie for FreeNewWindow() } return( nwin ); } void FreeNewWindow( win ) struct NewWindow *win; { register struct Gadget *gd, *pgd; register struct StringInfo *sip; for( gd = win->FirstGadget; gd; gd = pgd ) { pgd = gd->NextGadget; if ((ULONG)gd->UserData == 0x45f35c3d) { if( gd->GadgetType == STRGADGET ) { sip = (struct StringInfo *)gd->SpecialInfo; free( sip->Buffer ); free( sip ); } else if( gd->GadgetType == PROPGADGET ) { free( (struct PropInfo *)gd->SpecialInfo ); } free( gd ); } } free( win ); } void bell() { if (flags.silent) return; DisplayBeep(NULL); } void amii_delay_output() { /* delay 50 ms */ Delay(2L); } void amii_number_pad(state) int state; { } #endif /* AMII_GRAPHICS */ #ifndef SHAREDLIB void amiv_loadlib( void ) { } void amii_loadlib( void ) { } /* fatal error */ /*VARARGS1*/ void error VA_DECL(const char *, s) VA_START(s); VA_INIT(s, char *); putchar('\n'); vprintf(s, VA_ARGS); putchar('\n'); VA_END(); Abort(0L); } #endif