/* NetHack 3.6 winX.c $NHDT-Date: 1457079197 2016/03/04 08:13:17 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.41 $ */ /* Copyright (c) Dean Luick, 1992 */ /* NetHack may be freely redistributed. See license for details. */ /* * "Main" file for the X window-port. This contains most of the interface * routines. Please see doc/window.doc for an description of the window * interface. */ #ifndef SYSV #define PRESERVE_NO_SYSV /* X11 include files may define SYSV */ #endif #ifdef MSDOS /* from compiler */ #define SHORT_FILENAMES #endif #include #include #include #include #include #include #include #include #include #include #include /* for color support */ #ifdef SHORT_FILENAMES #include #else #include #endif #ifdef PRESERVE_NO_SYSV #ifdef SYSV #undef SYSV #endif #undef PRESERVE_NO_SYSV #endif #ifdef SHORT_FILENAMES #undef SHORT_FILENAMES /* hack.h will reset via global.h if necessary */ #endif #include "hack.h" #include "winX.h" #include "dlb.h" #ifndef NO_SIGNAL #include #endif /* Should be defined in but you never know */ #ifndef XtSpecificationRelease #define XtSpecificationRelease 0 #endif /* * Icons. */ #include "../win/X11/nh72icon" #include "../win/X11/nh56icon" #include "../win/X11/nh32icon" static struct icon_info { const char *name; unsigned char *bits; unsigned width, height; } icon_data[] = { { "nh72", nh72icon_bits, nh72icon_width, nh72icon_height }, { "nh56", nh56icon_bits, nh56icon_width, nh56icon_height }, { "nh32", nh32icon_bits, nh32icon_width, nh32icon_height }, { (const char *) 0, (unsigned char *) 0, 0, 0 } }; /* * Private global variables (shared among the window port files). */ struct xwindow window_list[MAX_WINDOWS]; AppResources appResources; void FDECL((*input_func), (Widget, XEvent *, String *, Cardinal *)); int click_x, click_y, click_button; /* Click position on a map window */ /* (filled by set_button_values()). */ int updated_inventory; #if !defined(NO_SIGNAL) && defined(SAFERHANGUP) #if XtSpecificationRelease >= 6 #define X11_HANGUP_SIGNAL static XtSignalId X11_sig_id; #endif #endif /* this is only needed until X11_status_* routines are written */ extern NEARDATA winid WIN_STATUS; /* Interface definition, for windows.c */ struct window_procs X11_procs = { "X11", WC_COLOR | WC_HILITE_PET | WC_TILED_MAP, 0L, X11_init_nhwindows, X11_player_selection, X11_askname, X11_get_nh_event, X11_exit_nhwindows, X11_suspend_nhwindows, X11_resume_nhwindows, X11_create_nhwindow, X11_clear_nhwindow, X11_display_nhwindow, X11_destroy_nhwindow, X11_curs, X11_putstr, genl_putmixed, X11_display_file, X11_start_menu, X11_add_menu, X11_end_menu, X11_select_menu, genl_message_menu, /* no need for X-specific handling */ X11_update_inventory, X11_mark_synch, X11_wait_synch, #ifdef CLIPPING X11_cliparound, #endif #ifdef POSITIONBAR donull, #endif X11_print_glyph, X11_raw_print, X11_raw_print_bold, X11_nhgetch, X11_nh_poskey, X11_nhbell, X11_doprev_message, X11_yn_function, X11_getlin, X11_get_ext_cmd, X11_number_pad, X11_delay_output, #ifdef CHANGE_COLOR /* only a Mac option currently */ donull, donull, #endif /* other defs that really should go away (they're tty specific) */ X11_start_screen, X11_end_screen, #ifdef GRAPHIC_TOMBSTONE X11_outrip, #else genl_outrip, #endif X11_preference_update, genl_getmsghistory, genl_putmsghistory, #ifdef STATUS_VIA_WINDOWPORT genl_status_init, genl_status_finish, genl_status_enablefield, genl_status_update, #ifdef STATUS_HILITES genl_status_threshold, #endif #endif genl_can_suspend_no, /* XXX may not always be correct */ }; /* * Local functions. */ static winid NDECL(find_free_window); static void FDECL(nhFreePixel, (XtAppContext, XrmValuePtr, XtPointer, XrmValuePtr, Cardinal *)); static void NDECL(load_default_resources); static void NDECL(release_default_resources); #ifdef X11_HANGUP_SIGNAL static void FDECL(X11_sig, (int)); static void FDECL(X11_sig_cb, (XtPointer, XtSignalId *)); #endif static void FDECL(d_timeout, (XtPointer, XtIntervalId *)); static void FDECL(X11_hangup, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(askname_delete, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(askname_done, (Widget, XtPointer, XtPointer)); static void FDECL(done_button, (Widget, XtPointer, XtPointer)); static void FDECL(getline_delete, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(abort_button, (Widget, XtPointer, XtPointer)); static void NDECL(release_getline_widgets); static void FDECL(delete_file, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(dismiss_file, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(yn_delete, (Widget, XEvent *, String *, Cardinal *)); static void FDECL(yn_key, (Widget, XEvent *, String *, Cardinal *)); static void NDECL(release_yn_widgets); static int FDECL(input_event, (int)); static void FDECL(win_visible, (Widget, XtPointer, XEvent *, Boolean *)); static void NDECL(init_standard_windows); /* * Local variables. */ static boolean x_inited = FALSE; /* TRUE if window system is set up. */ static winid message_win = WIN_ERR, /* These are the winids of the message, */ map_win = WIN_ERR, /* map, and status windows, when they */ status_win = WIN_ERR; /* are created in init_windows(). */ static Pixmap icon_pixmap = None; /* Pixmap for icon. */ /* * Find the window structure that corresponds to the given widget. Note * that this is not the popup widget, nor the viewport, but the child. */ struct xwindow * find_widget(w) Widget w; { int windex; struct xwindow *wp; /* * Search to find the corresponding window. Look at the main widget, * popup, the parent of the main widget, then parent of the widget. */ for (windex = 0, wp = window_list; windex < MAX_WINDOWS; windex++, wp++) if (wp->type != NHW_NONE && (wp->w == w || wp->popup == w || (wp->w && (XtParent(wp->w)) == w) || (wp->popup == XtParent(w)))) break; if (windex == MAX_WINDOWS) panic("find_widget: can't match widget"); return wp; } /* * Find a free window slot for use. */ static winid find_free_window() { int windex; struct xwindow *wp; for (windex = 0, wp = &window_list[0]; windex < MAX_WINDOWS; windex++, wp++) if (wp->type == NHW_NONE) break; if (windex == MAX_WINDOWS) panic("find_free_window: no free windows!"); return (winid) windex; } /* * Color conversion. The default X11 color converters don't try very * hard to find matching colors in PseudoColor visuals. If they can't * allocate the exact color, they puke and give you something stupid. * This is an attempt to find some close readonly cell and use it. */ XtConvertArgRec const nhcolorConvertArgs[] = { { XtWidgetBaseOffset, (XtPointer) (ptrdiff_t) XtOffset(Widget, core.screen), sizeof (Screen *) }, { XtWidgetBaseOffset, (XtPointer) (ptrdiff_t) XtOffset(Widget, core.colormap), sizeof (Colormap) } }; #define done(type, value) \ { \ if (toVal->addr != 0) { \ if (toVal->size < sizeof(type)) { \ toVal->size = sizeof(type); \ return False; \ } \ *(type *)(toVal->addr) = (value); \ } else { \ static type static_val; \ static_val = (value); \ toVal->addr = (genericptr_t) &static_val; \ } \ toVal->size = sizeof(type); \ return True; \ } /* * Find a color that approximates the color named in "str". * The "str" color may be a color name ("red") or number ("#7f0000"). * If str is Null, then "color" is assumed to contain the RGB color wanted. * The approximate color found is returned in color as well. * Return True if something close was found. */ Boolean nhApproxColor(screen, colormap, str, color) Screen *screen; /* screen to use */ Colormap colormap; /* the colormap to use */ char *str; /* color name */ XColor *color; /* the X color structure; changed only if successful */ { int ncells; long cdiff = 16777216; /* 2^24; hopefully our map is smaller */ XColor tmp; static XColor *table = 0; register int i, j; register long tdiff; /* if the screen doesn't have a big colormap, don't waste our time or if it's huge, and _some_ match should have been possible */ if ((ncells = CellsOfScreen(screen)) < 256 || ncells > 4096) return False; if (str != (char *) 0) { if (!XParseColor(DisplayOfScreen(screen), colormap, str, &tmp)) return False; } else { tmp = *color; tmp.flags = 7; /* force to use all 3 of RGB */ } if (!table) { table = (XColor *) XtCalloc(ncells, sizeof(XColor)); for (i = 0; i < ncells; i++) table[i].pixel = i; XQueryColors(DisplayOfScreen(screen), colormap, table, ncells); } /* go thru cells and look for the one with smallest diff */ /* diff is calculated abs(reddiff)+abs(greendiff)+abs(bluediff) */ /* a more knowledgeable color person might improve this -dlc */ try_again: for (i = 0; i < ncells; i++) { if (table[i].flags == tmp.flags) { j = (int) table[i].red - (int) tmp.red; if (j < 0) j = -j; tdiff = j; j = (int) table[i].green - (int) tmp.green; if (j < 0) j = -j; tdiff += j; j = (int) table[i].blue - (int) tmp.blue; if (j < 0) j = -j; tdiff += j; if (tdiff < cdiff) { cdiff = tdiff; tmp.pixel = i; /* table[i].pixel == i */ } } } if (cdiff == 16777216) return False; /* nothing found?! */ /* * Found something. Return it and mark this color as used to avoid * reuse. Reuse causes major contrast problems :-) */ *color = table[tmp.pixel]; table[tmp.pixel].flags = 0; /* try to alloc the color, so no one else can change it */ if (!XAllocColor(DisplayOfScreen(screen), colormap, color)) { cdiff = 16777216; goto try_again; } return True; } Boolean nhCvtStringToPixel(dpy, args, num_args, fromVal, toVal, closure_ret) Display *dpy; XrmValuePtr args; Cardinal *num_args; XrmValuePtr fromVal; XrmValuePtr toVal; XtPointer *closure_ret; { String str = (String) fromVal->addr; XColor screenColor; XColor exactColor; Screen *screen; XtAppContext app = XtDisplayToApplicationContext(dpy); Colormap colormap; Status status; String params[1]; Cardinal num_params = 1; if (*num_args != 2) { XtAppWarningMsg(app, "wrongParameters", "cvtStringToPixel", "XtToolkitError", "String to pixel conversion needs screen and colormap arguments", (String *) 0, (Cardinal *) 0); return False; } screen = *((Screen **) args[0].addr); colormap = *((Colormap *) args[1].addr); /* If Xt colors, use the Xt routine and hope for the best */ #if (XtSpecificationRelease >= 5) if ((strcmpi(str, XtDefaultBackground) == 0) || (strcmpi(str, XtDefaultForeground) == 0)) { return XtCvtStringToPixel(dpy, args, num_args, fromVal, toVal, closure_ret); } #else if (strcmpi(str, XtDefaultBackground) == 0) { *closure_ret = (char *) False; done(Pixel, WhitePixelOfScreen(screen)); } if (strcmpi(str, XtDefaultForeground) == 0) { *closure_ret = (char *) False; done(Pixel, BlackPixelOfScreen(screen)); } #endif status = XAllocNamedColor(DisplayOfScreen(screen), colormap, (char *) str, &screenColor, &exactColor); if (status == 0) { String msg, type; /* some versions of XAllocNamedColor don't allow #xxyyzz names */ if (str[0] == '#' && XParseColor(DisplayOfScreen(screen), colormap, str, &exactColor) && XAllocColor(DisplayOfScreen(screen), colormap, &exactColor)) { *closure_ret = (char *) True; done(Pixel, exactColor.pixel); } params[0] = str; /* Server returns a specific error code but Xlib discards it. Ugh */ if (XLookupColor(DisplayOfScreen(screen), colormap, (char *) str, &exactColor, &screenColor)) { /* try to find another color that will do */ if (nhApproxColor(screen, colormap, (char *) str, &screenColor)) { *closure_ret = (char *) True; done(Pixel, screenColor.pixel); } type = nhStr("noColormap"); msg = nhStr("Cannot allocate colormap entry for \"%s\""); } else { /* some versions of XLookupColor also don't allow #xxyyzz names */ if (str[0] == '#' && (nhApproxColor(screen, colormap, (char *) str, &screenColor))) { *closure_ret = (char *) True; done(Pixel, screenColor.pixel); } type = nhStr("badValue"); msg = nhStr("Color name \"%s\" is not defined"); } XtAppWarningMsg(app, type, "cvtStringToPixel", "XtToolkitError", msg, params, &num_params); *closure_ret = False; return False; } else { *closure_ret = (char *) True; done(Pixel, screenColor.pixel); } } /* ARGSUSED */ static void nhFreePixel(app, toVal, closure, args, num_args) XtAppContext app; XrmValuePtr toVal; XtPointer closure; XrmValuePtr args; Cardinal *num_args; { Screen *screen; Colormap colormap; if (*num_args != 2) { XtAppWarningMsg(app, "wrongParameters", "freePixel", "XtToolkitError", "Freeing a pixel requires screen and colormap arguments", (String *) 0, (Cardinal *) 0); return; } screen = *((Screen **) args[0].addr); colormap = *((Colormap *) args[1].addr); if (closure) { XFreeColors(DisplayOfScreen(screen), colormap, (unsigned long *) toVal->addr, 1, (unsigned long) 0); } } /* [ALI] Utility function to ask Xaw for font height, since the previous * assumption of ascent + descent is not always valid. */ Dimension nhFontHeight(w) Widget w; { #ifdef _XawTextSink_h Widget sink; XawTextPosition pos = 0; int resWidth, resHeight; Arg args[1]; XtSetArg(args[0], XtNtextSink, &sink); XtGetValues(w, args, 1); XawTextSinkFindPosition(sink, pos, 0, 0, 0, &pos, &resWidth, &resHeight); return resHeight; #else XFontStruct *fs; Arg args[1]; XtSetArg(args[0], XtNfont, &fs); XtGetValues(w, args, 1); /* Assume font height is ascent + descent. */ return = fs->ascent + fs->descent; #endif } static String *default_resource_data = 0; /* NULL-terminated array */ /* read the template NetHack.ad into default_resource_data[] to supply fallback resources to XtAppInitialize() */ static void load_default_resources() { FILE *fp; String inbuf; unsigned insiz, linelen, longlen, numlines; boolean comment = FALSE; /* lint suppression */ /* * Running nethack via the shell script adds $HACKDIR to the path used * by X to find resources, but running it directly doesn't. So, if we * can find the template file for NetHack.ad in the current directory, * load its contents into memory so that the application startup call * in X11_init_nhwindows() can use them as fallback resources. * * No attempt to support the 'include' directive has been made. */ fp = fopen("./NetHack.ad", "r"); if (!fp) return; /* measure the file without retaining its contents */ insiz = BUFSIZ; /* stdio BUFSIZ, not nethack BUFSZ */ inbuf = (String) alloc(insiz); linelen = longlen = 0; numlines = 0; while (fgets(inbuf, insiz, fp)) { if (!linelen) /* inbuf has start of record; treat empty as comment */ comment = (*inbuf == '!' || *inbuf == '\n'); linelen += strlen(inbuf); if (!index(inbuf, '\n')) continue; if (linelen > longlen) longlen = linelen; linelen = 0; if (!comment) ++numlines; } free((genericptr_t) inbuf); insiz = longlen + 1; inbuf = (String) alloc(insiz); ++numlines; /* room for terminator */ default_resource_data = (String *) alloc(numlines * sizeof (String)); /* now read the file into the array */ (void) rewind(fp); numlines = 0; while (fgets(inbuf, insiz, fp)) { if (*inbuf != '!' && *inbuf != '\n') default_resource_data[numlines++] = dupstr(inbuf); } default_resource_data[numlines] = (String) 0; (void) fclose(fp); free((genericptr_t) inbuf); } static void release_default_resources() { if (default_resource_data) { unsigned idx; for (idx = 0; default_resource_data[idx]; idx++) free((genericptr_t) default_resource_data[idx]); free((genericptr_t) default_resource_data), default_resource_data = 0; } } /* Global Functions ======================================================= */ void X11_raw_print(str) const char *str; { (void) puts(str); } void X11_raw_print_bold(str) const char *str; { (void) puts(str); } void X11_curs(window, x, y) winid window; int x, y; { check_winid(window); if (x < 0 || x >= COLNO) { impossible("curs: bad x value [%d]", x); x = 0; } if (y < 0 || y >= ROWNO) { impossible("curs: bad y value [%d]", y); y = 0; } window_list[window].cursx = x; window_list[window].cursy = y; } void X11_putstr(window, attr, str) winid window; int attr; const char *str; { winid new_win; struct xwindow *wp; check_winid(window); wp = &window_list[window]; switch (wp->type) { case NHW_MESSAGE: (void) strncpy(toplines, str, TBUFSZ); /* for Norep(). */ toplines[TBUFSZ - 1] = 0; append_message(wp, str); break; case NHW_STATUS: adjust_status(wp, str); break; case NHW_MAP: impossible("putstr: called on map window \"%s\"", str); break; case NHW_MENU: if (wp->menu_information->is_menu) { impossible("putstr: called on a menu window, \"%s\" discarded", str); break; } /* * Change this menu window into a text window by creating a * new text window, then copying it to this winid. */ new_win = X11_create_nhwindow(NHW_TEXT); X11_destroy_nhwindow(window); *wp = window_list[new_win]; window_list[new_win].type = NHW_NONE; /* allow re-use */ /* fall though to add text */ case NHW_TEXT: add_to_text_window(wp, attr, str); break; default: impossible("putstr: unknown window type [%d] \"%s\"", wp->type, str); } } /* We do event processing as a callback, so this is a null routine. */ void X11_get_nh_event() { return; } int X11_nhgetch() { return input_event(EXIT_ON_KEY_PRESS); } int X11_nh_poskey(x, y, mod) int *x, *y, *mod; { int val = input_event(EXIT_ON_KEY_OR_BUTTON_PRESS); if (val == 0) { /* user clicked on a map window */ *x = click_x; *y = click_y; *mod = click_button; } return val; } winid X11_create_nhwindow(type) int type; { winid window; struct xwindow *wp; if (!x_inited) panic("create_nhwindow: windows not initialized"); #ifdef X11_HANGUP_SIGNAL /* set up our own signal handlers on the first call. Can't do this in * X11_init_nhwindows because unixmain sets its handler after calling * all the init routines. */ if (X11_sig_id == 0) { X11_sig_id = XtAppAddSignal(app_context, X11_sig_cb, (XtPointer) 0); #ifdef SA_RESTART { struct sigaction sact; (void) memset((char *) &sact, 0, sizeof(struct sigaction)); sact.sa_handler = (SIG_RET_TYPE) X11_sig; (void) sigaction(SIGHUP, &sact, (struct sigaction *) 0); #ifdef SIGXCPU (void) sigaction(SIGXCPU, &sact, (struct sigaction *) 0); #endif } #else (void) signal(SIGHUP, (SIG_RET_TYPE) X11_sig); #ifdef SIGXCPU (void) signal(SIGXCPU, (SIG_RET_TYPE) X11_sig); #endif #endif } #endif /* * We have already created the standard message, map, and status * windows in the window init routine. The first window of that * type to be created becomes the standard. * * A better way to do this would be to say that init_nhwindows() * has already defined these three windows. */ if (type == NHW_MAP && map_win != WIN_ERR) { window = map_win; map_win = WIN_ERR; return window; } if (type == NHW_MESSAGE && message_win != WIN_ERR) { window = message_win; message_win = WIN_ERR; return window; } if (type == NHW_STATUS && status_win != WIN_ERR) { window = status_win; status_win = WIN_ERR; return window; } window = find_free_window(); wp = &window_list[window]; /* The create routines will set type, popup, w, and Win_info. */ wp->prevx = wp->prevy = wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0; wp->keep_window = FALSE; switch (type) { case NHW_MAP: create_map_window(wp, TRUE, (Widget) 0); break; case NHW_MESSAGE: create_message_window(wp, TRUE, (Widget) 0); break; case NHW_STATUS: create_status_window(wp, TRUE, (Widget) 0); break; case NHW_MENU: create_menu_window(wp); break; case NHW_TEXT: create_text_window(wp); break; default: panic("create_nhwindow: unknown type [%d]", type); break; } return window; } void X11_clear_nhwindow(window) winid window; { struct xwindow *wp; check_winid(window); wp = &window_list[window]; switch (wp->type) { case NHW_MAP: clear_map_window(wp); break; case NHW_TEXT: clear_text_window(wp); break; case NHW_STATUS: case NHW_MENU: case NHW_MESSAGE: /* do nothing for these window types */ break; default: panic("clear_nhwindow: unknown window type [%d]", wp->type); break; } } void X11_display_nhwindow(window, blocking) winid window; boolean blocking; { struct xwindow *wp; check_winid(window); wp = &window_list[window]; switch (wp->type) { case NHW_MAP: if (wp->popup) nh_XtPopup(wp->popup, (int) XtGrabNone, wp->w); display_map_window(wp); /* flush map */ /* * We need to flush the message window here due to the way the tty * port is set up. To flush a window, you need to call this * routine. However, the tty port _pauses_ with a --more-- if we * do a display_nhwindow(WIN_MESSAGE, FALSE). Thus, we can't call * display_nhwindow(WIN_MESSAGE,FALSE) in parse() because then we * get a --more-- after every line. * * Perhaps the window document should mention that when the map * is flushed, everything on the three main windows should be * flushed. Note: we don't need to flush the status window * because we don't buffer changes. */ if (WIN_MESSAGE != WIN_ERR) display_message_window(&window_list[WIN_MESSAGE]); if (blocking) (void) x_event(EXIT_ON_KEY_OR_BUTTON_PRESS); break; case NHW_MESSAGE: if (wp->popup) nh_XtPopup(wp->popup, (int) XtGrabNone, wp->w); display_message_window(wp); /* flush messages */ break; case NHW_STATUS: if (wp->popup) nh_XtPopup(wp->popup, (int) XtGrabNone, wp->w); break; /* no flushing necessary */ case NHW_MENU: { int n; menu_item *selected; /* pop up menu */ n = X11_select_menu(window, PICK_NONE, &selected); if (n) { impossible("perminvent: %d selected??", n); free((genericptr_t) selected); } break; } case NHW_TEXT: display_text_window(wp, blocking); /* pop up text window */ break; default: panic("display_nhwindow: unknown window type [%d]", wp->type); break; } } void X11_destroy_nhwindow(window) winid window; { struct xwindow *wp; check_winid(window); wp = &window_list[window]; /* * "Zap" known windows, but don't destroy them. We need to keep the * toplevel widget popped up so that later windows (e.g. tombstone) * are visible on DECWindow systems. This is due to the virtual * roots that the DECWindow wm creates. */ if (window == WIN_MESSAGE) { wp->keep_window = TRUE; WIN_MESSAGE = WIN_ERR; iflags.window_inited = 0; } else if (window == WIN_MAP) { wp->keep_window = TRUE; WIN_MAP = WIN_ERR; } else if (window == WIN_STATUS) { wp->keep_window = TRUE; WIN_STATUS = WIN_ERR; } else if (window == WIN_INVEN) { /* don't need to keep this one */ WIN_INVEN = WIN_ERR; } switch (wp->type) { case NHW_MAP: destroy_map_window(wp); break; case NHW_MENU: destroy_menu_window(wp); break; case NHW_TEXT: destroy_text_window(wp); break; case NHW_STATUS: destroy_status_window(wp); break; case NHW_MESSAGE: destroy_message_window(wp); break; default: panic("destroy_nhwindow: unknown window type [%d]", wp->type); break; } } void X11_update_inventory() { if (x_inited && window_list[WIN_INVEN].menu_information->is_up) { updated_inventory = 1; /* hack to avoid mapping&raising window */ (void) display_inventory((char *) 0, FALSE); updated_inventory = 0; } } /* The current implementation has all of the saved lines on the screen. */ int X11_doprev_message() { return 0; } void X11_nhbell() { /* We can't use XBell until toplevel has been initialized. */ if (x_inited) XBell(XtDisplay(toplevel), 0); /* else print ^G ?? */ } void X11_mark_synch() { if (x_inited) { /* * The window document is unclear about the status of text * that has been pline()d but not displayed w/display_nhwindow(). * Both the main and tty code assume that a pline() followed * by mark_synch() results in the text being seen, even if * display_nhwindow() wasn't called. Duplicate this behavior. */ if (WIN_MESSAGE != WIN_ERR) display_message_window(&window_list[WIN_MESSAGE]); XSync(XtDisplay(toplevel), False); } } void X11_wait_synch() { if (x_inited) XFlush(XtDisplay(toplevel)); } /* Both resume_ and suspend_ are called from ioctl.c and unixunix.c. */ void X11_resume_nhwindows() { return; } /* ARGSUSED */ void X11_suspend_nhwindows(str) const char *str; { nhUse(str); return; } /* Under X, we don't need to initialize the number pad. */ /* ARGSUSED */ void X11_number_pad(state) /* called from options.c */ int state; { nhUse(state); return; } /* called from setftty() in unixtty.c */ void X11_start_screen() { return; } /* called from settty() in unixtty.c */ void X11_end_screen() { return; } #ifdef GRAPHIC_TOMBSTONE void X11_outrip(window, how, when) winid window; int how; time_t when; { struct xwindow *wp; check_winid(window); wp = &window_list[window]; if (wp->type == NHW_TEXT) { wp->text_information->is_rip = TRUE; } else { panic("ripout on non-text window (window type [%d])", wp->type); } calculate_rip_text(how, when); } #endif /* init and exit nhwindows ------------------------------------------------ */ XtAppContext app_context; /* context of application */ Widget toplevel = (Widget) 0; /* toplevel widget */ Atom wm_delete_window; /* pop down windows */ static XtActionsRec actions[] = { { nhStr("dismiss_file"), dismiss_file }, /* file viewing widget */ { nhStr("delete_file"), delete_file }, /* file delete-window */ { nhStr("dismiss_text"), dismiss_text }, /* text widget button action */ { nhStr("delete_text"), delete_text }, /* text widget delete action */ { nhStr("key_dismiss_text"), key_dismiss_text }, /* text key action */ #ifdef GRAPHIC_TOMBSTONE { nhStr("rip_dismiss_text"), rip_dismiss_text }, /* rip in text widget */ #endif { nhStr("menu_key"), menu_key }, /* menu accelerators */ { nhStr("yn_key"), yn_key }, /* yn accelerators */ { nhStr("yn_delete"), yn_delete }, /* yn delete-window */ { nhStr("askname_delete"), askname_delete }, /* askname delete-window */ { nhStr("getline_delete"), getline_delete }, /* getline delete-window */ { nhStr("menu_delete"), menu_delete }, /* menu delete-window */ { nhStr("ec_key"), ec_key }, /* extended commands */ { nhStr("ec_delete"), ec_delete }, /* ext-com menu delete */ { nhStr("ps_key"), ps_key }, /* player selection */ { nhStr("race_key"), race_key }, /* race selection */ { nhStr("gend_key"), gend_key }, /* gender selection */ { nhStr("algn_key"), algn_key }, /* alignment selection */ { nhStr("X11_hangup"), X11_hangup }, /* delete of top-level */ { nhStr("input"), map_input }, /* key input */ { nhStr("scroll"), nh_keyscroll }, /* scrolling by keys */ }; static XtResource resources[] = { { nhStr("slow"), nhStr("Slow"), XtRBoolean, sizeof(Boolean), XtOffset(AppResources *, slow), XtRString, nhStr("True") }, { nhStr("autofocus"), nhStr("AutoFocus"), XtRBoolean, sizeof(Boolean), XtOffset(AppResources *, autofocus), XtRString, nhStr("False") }, { nhStr("message_line"), nhStr("Message_line"), XtRBoolean, sizeof(Boolean), XtOffset(AppResources *, message_line), XtRString, nhStr("False") }, { nhStr("highlight_prompt"), nhStr("Highlight_prompt"), XtRBoolean, sizeof(Boolean), XtOffset(AppResources *, highlight_prompt), XtRString, nhStr("True") }, { nhStr("double_tile_size"), nhStr("Double_tile_size"), XtRBoolean, sizeof(Boolean), XtOffset(AppResources *, double_tile_size), XtRString, nhStr("False") }, { nhStr("tile_file"), nhStr("Tile_file"), XtRString, sizeof(String), XtOffset(AppResources *, tile_file), XtRString, nhStr("x11tiles") }, { nhStr("icon"), nhStr("Icon"), XtRString, sizeof(String), XtOffset(AppResources *, icon), XtRString, nhStr("nh72") }, { nhStr("message_lines"), nhStr("Message_lines"), XtRInt, sizeof(int), XtOffset(AppResources *, message_lines), XtRString, nhStr("12") }, { nhStr("extcmd_height_delta"), nhStr("Extcmd_height_delta"), XtRInt, sizeof (int), XtOffset(AppResources *, extcmd_height_delta), XtRString, nhStr("0") }, { nhStr("pet_mark_bitmap"), nhStr("Pet_mark_bitmap"), XtRString, sizeof(String), XtOffset(AppResources *, pet_mark_bitmap), XtRString, nhStr("pet_mark.xbm") }, { nhStr("pet_mark_color"), nhStr("Pet_mark_color"), XtRPixel, sizeof(XtRPixel), XtOffset(AppResources *, pet_mark_color), XtRString, nhStr("Red") }, { nhStr("pilemark_bitmap"), nhStr("Pilemark_bitmap"), XtRString, sizeof(String), XtOffset(AppResources *, pilemark_bitmap), XtRString, nhStr("pilemark.xbm") }, { nhStr("pilemark_color"), nhStr("Pilemark_color"), XtRPixel, sizeof(XtRPixel), XtOffset(AppResources *, pilemark_color), XtRString, nhStr("Green") }, #ifdef GRAPHIC_TOMBSTONE { nhStr("tombstone"), "Tombstone", XtRString, sizeof(String), XtOffset(AppResources *, tombstone), XtRString, "rip.xpm" }, { nhStr("tombtext_x"), "Tombtext_x", XtRInt, sizeof(int), XtOffset(AppResources *, tombtext_x), XtRString, "155" }, { nhStr("tombtext_y"), "Tombtext_y", XtRInt, sizeof(int), XtOffset(AppResources *, tombtext_y), XtRString, "78" }, { nhStr("tombtext_dx"), "Tombtext_dx", XtRInt, sizeof(int), XtOffset(AppResources *, tombtext_dx), XtRString, "0" }, { nhStr("tombtext_dy"), "Tombtext_dy", XtRInt, sizeof(int), XtOffset(AppResources *, tombtext_dy), XtRString, "13" }, #endif }; void X11_init_nhwindows(argcp, argv) int *argcp; char **argv; { int i; Cardinal num_args; Arg args[4]; uid_t savuid; /* Init windows to nothing. */ for (i = 0; i < MAX_WINDOWS; i++) window_list[i].type = NHW_NONE; /* add another option that can be set */ set_wc_option_mod_status(WC_TILED_MAP, SET_IN_GAME); load_default_resources(); /* create default_resource_data[] */ /* * setuid hack: make sure that if nethack is setuid, to use real uid * when opening X11 connections, in case the user is using xauth, since * the "games" or whatever user probably doesn't have permission to open * a window on the user's display. This code is harmless if the binary * is not installed setuid. See include/system.h on compilation failures. */ savuid = geteuid(); (void) seteuid(getuid()); XSetIOErrorHandler((XIOErrorHandler) hangup); num_args = 0; XtSetArg(args[num_args], XtNallowShellResize, True); num_args++; toplevel = XtAppInitialize(&app_context, "NetHack", /* application */ (XrmOptionDescList) 0, 0, /* options list */ argcp, (String *) argv, /* command line */ default_resource_data, /* fallback resources */ (ArgList) args, num_args); XtOverrideTranslations(toplevel, XtParseTranslationTable("WM_PROTOCOLS: X11_hangup()")); /* We don't need to realize the top level widget. */ #ifdef TEXTCOLOR /* add new color converter to deal with overused colormaps */ XtSetTypeConverter(XtRString, XtRPixel, nhCvtStringToPixel, (XtConvertArgList) nhcolorConvertArgs, XtNumber(nhcolorConvertArgs), XtCacheByDisplay, nhFreePixel); #endif /* TEXTCOLOR */ /* Register the actions mentioned in "actions". */ XtAppAddActions(app_context, actions, XtNumber(actions)); /* Get application-wide resources */ XtGetApplicationResources(toplevel, (XtPointer) &appResources, resources, XtNumber(resources), (ArgList) 0, ZERO); /* Initialize other things. */ init_standard_windows(); /* Give the window manager an icon to use; toplevel must be realized. */ if (appResources.icon && *appResources.icon) { struct icon_info *ip; for (ip = icon_data; ip->name; ip++) if (!strcmp(appResources.icon, ip->name)) { icon_pixmap = XCreateBitmapFromData( XtDisplay(toplevel), XtWindow(toplevel), (genericptr_t) ip->bits, ip->width, ip->height); if (icon_pixmap != None) { XWMHints hints; (void) memset((genericptr_t) &hints, 0, sizeof(XWMHints)); hints.flags = IconPixmapHint; hints.icon_pixmap = icon_pixmap; XSetWMHints(XtDisplay(toplevel), XtWindow(toplevel), &hints); } break; } } /* end of setuid hack: reset uid back to the "games" uid */ (void) seteuid(savuid); x_inited = TRUE; /* X is now initialized */ release_default_resources(); /* Display the startup banner in the message window. */ for (i = 1; i <= 4 + 2; ++i) /* (values beyond 4 yield blank lines) */ X11_putstr(WIN_MESSAGE, 0, copyright_banner_line(i)); } /* * All done. */ /* ARGSUSED */ void X11_exit_nhwindows(dummy) const char *dummy; { extern Pixmap tile_pixmap; /* from winmap.c */ nhUse(dummy); /* explicitly free the icon and tile pixmaps */ if (icon_pixmap != None) { XFreePixmap(XtDisplay(toplevel), icon_pixmap); icon_pixmap = None; } if (tile_pixmap != None) { XFreePixmap(XtDisplay(toplevel), tile_pixmap); tile_pixmap = None; } if (WIN_INVEN != WIN_ERR) X11_destroy_nhwindow(WIN_INVEN); if (WIN_STATUS != WIN_ERR) X11_destroy_nhwindow(WIN_STATUS); if (WIN_MAP != WIN_ERR) X11_destroy_nhwindow(WIN_MAP); if (WIN_MESSAGE != WIN_ERR) X11_destroy_nhwindow(WIN_MESSAGE); release_getline_widgets(); release_yn_widgets(); } #ifdef X11_HANGUP_SIGNAL static void X11_sig(sig) /* Unix signal handler */ int sig; { XtNoticeSignal(X11_sig_id); hangup(sig); } static void X11_sig_cb(not_used, id) XtPointer not_used; XtSignalId *id; { XEvent event; XClientMessageEvent *mesg; nhUse(not_used); nhUse(id); /* Set up a fake message to the event handler. */ mesg = (XClientMessageEvent *) &event; mesg->type = ClientMessage; mesg->message_type = XA_STRING; mesg->format = 8; XSendEvent(XtDisplay(window_list[WIN_MAP].w), XtWindow(window_list[WIN_MAP].w), False, NoEventMask, (XEvent *) mesg); } #endif /* delay_output ----------------------------------------------------------- */ /* * Timeout callback for delay_output(). Send a fake message to the map * window. */ /* ARGSUSED */ static void d_timeout(client_data, id) XtPointer client_data; XtIntervalId *id; { XEvent event; XClientMessageEvent *mesg; nhUse(client_data); nhUse(id); /* Set up a fake message to the event handler. */ mesg = (XClientMessageEvent *) &event; mesg->type = ClientMessage; mesg->message_type = XA_STRING; mesg->format = 8; XSendEvent(XtDisplay(window_list[WIN_MAP].w), XtWindow(window_list[WIN_MAP].w), False, NoEventMask, (XEvent *) mesg); } /* * Delay for 50ms. This is not implemented asynch. Maybe later. * Start the timeout, then wait in the event loop. The timeout * function will send an event to the map window which will be waiting * for a sent event. */ void X11_delay_output() { if (!x_inited) return; (void) XtAppAddTimeOut(app_context, 30L, d_timeout, (XtPointer) 0); /* The timeout function will enable the event loop exit. */ (void) x_event(EXIT_ON_SENT_EVENT); } /* X11_hangup ------------------------------------------------------------- */ /* ARGSUSED */ static void X11_hangup(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { nhUse(w); nhUse(event); nhUse(params); nhUse(num_params); hangup(1); /* 1 is commonly SIGHUP, but ignored anyway */ exit_x_event = TRUE; } /* askname ---------------------------------------------------------------- */ /* ARGSUSED */ static void askname_delete(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { nhUse(event); nhUse(params); nhUse(num_params); nh_XtPopdown(w); (void) strcpy(plname, "Mumbles"); /* give them a name... ;-) */ exit_x_event = TRUE; } /* Callback for askname dialog widget. */ /* ARGSUSED */ static void askname_done(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { unsigned len; char *s; Widget dialog = (Widget) client_data; nhUse(w); nhUse(call_data); s = (char *) GetDialogResponse(dialog); len = strlen(s); if (len == 0) { X11_nhbell(); return; } /* Truncate name if necessary */ if (len >= sizeof plname - 1) len = sizeof plname - 1; (void) strncpy(plname, s, len); plname[len] = '\0'; XtFree(s); nh_XtPopdown(XtParent(dialog)); exit_x_event = TRUE; } /* ask player for character's name to replace generic name "player" (or other values; see config.h) after 'nethack -u player' or OPTIONS=name:player */ void X11_askname() { Widget popup, dialog; Arg args[1]; XtSetArg(args[0], XtNallowShellResize, True); popup = XtCreatePopupShell("askname", transientShellWidgetClass, toplevel, args, ONE); XtOverrideTranslations(popup, XtParseTranslationTable("WM_PROTOCOLS: askname_delete()")); dialog = CreateDialog(popup, nhStr("dialog"), askname_done, (XtCallbackProc) 0); SetDialogPrompt(dialog, nhStr("What is your name?")); /* set prompt */ SetDialogResponse(dialog, nhStr(""), PL_NSIZ); /* set default answer */ XtRealizeWidget(popup); positionpopup(popup, TRUE); /* center,bottom */ nh_XtPopup(popup, (int) XtGrabExclusive, dialog); /* The callback will enable the event loop exit. */ (void) x_event(EXIT_ON_EXIT); XtDestroyWidget(dialog); XtDestroyWidget(popup); } /* getline ---------------------------------------------------------------- */ /* This uses Tim Theisen's dialog widget set (from GhostView). */ static Widget getline_popup, getline_dialog; #define CANCEL_STR "\033" static char *getline_input; /* Callback for getline dialog widget. */ /* ARGSUSED */ static void done_button(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { int len; char *s; Widget dialog = (Widget) client_data; nhUse(w); nhUse(call_data); s = (char *) GetDialogResponse(dialog); len = strlen(s); /* Truncate input if necessary */ if (len >= BUFSZ) len = BUFSZ - 1; (void) strncpy(getline_input, s, len); getline_input[len] = '\0'; XtFree(s); nh_XtPopdown(XtParent(dialog)); exit_x_event = TRUE; } /* ARGSUSED */ static void getline_delete(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { nhUse(event); nhUse(params); nhUse(num_params); Strcpy(getline_input, CANCEL_STR); nh_XtPopdown(w); exit_x_event = TRUE; } /* Callback for getline dialog widget. */ /* ARGSUSED */ static void abort_button(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { Widget dialog = (Widget) client_data; nhUse(w); nhUse(call_data); Strcpy(getline_input, CANCEL_STR); nh_XtPopdown(XtParent(dialog)); exit_x_event = TRUE; } static void release_getline_widgets() { if (getline_dialog) XtDestroyWidget(getline_dialog), getline_dialog = (Widget) 0; if (getline_popup) XtDestroyWidget(getline_popup), getline_popup = (Widget) 0; } void X11_getlin(question, input) const char *question; char *input; { getline_input = input; flush_screen(1); if (!getline_popup) { Arg args[1]; XtSetArg(args[0], XtNallowShellResize, True); getline_popup = XtCreatePopupShell("getline", transientShellWidgetClass, toplevel, args, ONE); XtOverrideTranslations(getline_popup, XtParseTranslationTable( "WM_PROTOCOLS: getline_delete()")); getline_dialog = CreateDialog(getline_popup, nhStr("dialog"), done_button, abort_button); XtRealizeWidget(getline_popup); XSetWMProtocols(XtDisplay(getline_popup), XtWindow(getline_popup), &wm_delete_window, 1); } SetDialogPrompt(getline_dialog, (String) question); /* set prompt */ /* 60: make the answer widget be wide enough to hold 60 characters, or the length of the prompt string, whichever is bigger. User's response can be longer--when limit is reached, value-so-far will slide left hiding some chars at the beginning of the response but making room to type more. [Prior to 3.6.1, width wasn't specifiable and answer box always got sized to match the width of the prompt.] */ SetDialogResponse(getline_dialog, nhStr(""), 60); /* set default answer */ positionpopup(getline_popup, TRUE); /* center,bottom */ nh_XtPopup(getline_popup, (int) XtGrabExclusive, getline_dialog); /* The callback will enable the event loop exit. */ (void) x_event(EXIT_ON_EXIT); } /* Display file ----------------------------------------------------------- */ static const char display_translations[] = "#override\n\ q: dismiss_file()\n\ Escape: dismiss_file()\n\ : dismiss_file()"; /* WM_DELETE_WINDOW callback for file dismissal. */ /*ARGSUSED*/ static void delete_file(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { nhUse(event); nhUse(params); nhUse(num_params); nh_XtPopdown(w); XtDestroyWidget(w); } /* Callback for file dismissal. */ /*ARGSUSED*/ static void dismiss_file(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { Widget popup = XtParent(w); nhUse(event); nhUse(params); nhUse(num_params); nh_XtPopdown(popup); XtDestroyWidget(popup); } void X11_display_file(str, complain) const char *str; boolean complain; { dlb *fp; Arg args[12]; Cardinal num_args; Widget popup, dispfile; Position top_margin, bottom_margin, left_margin, right_margin; XFontStruct *fs; int new_width, new_height; #define LLEN 128 char line[LLEN]; int num_lines; char *textlines, *bp; int charcount; /* Use the port-independent file opener to see if the file exists. */ fp = dlb_fopen(str, RDTMODE); if (!fp) { if (complain) pline("Cannot open %s. Sorry.", str); return; /* it doesn't exist, ignore */ } /* * Count the number of lines and characters in the file. */ num_lines = 0; charcount = 1; while (dlb_fgets(line, LLEN, fp)) { num_lines++; charcount += strlen(line); } (void) dlb_fclose(fp); /* Ignore empty files */ if (num_lines == 0) return; /* If over the max window size, truncate the window size to the max */ if (num_lines >= DISPLAY_FILE_SIZE) num_lines = DISPLAY_FILE_SIZE; /* * Re-open the file and read the data into a buffer. Cannot use * the XawAsciiFile type of widget, because that is not DLB-aware. */ textlines = (char *) alloc((unsigned int) charcount); textlines[0] = '\0'; fp = dlb_fopen(str, RDTMODE); bp = textlines; while (dlb_fgets(line, LLEN, fp)) { Strcpy((bp = eos(bp)), line); } (void) dlb_fclose(fp); num_args = 0; XtSetArg(args[num_args], nhStr(XtNtitle), str); num_args++; popup = XtCreatePopupShell("display_file", topLevelShellWidgetClass, toplevel, args, num_args); XtOverrideTranslations(popup, XtParseTranslationTable("WM_PROTOCOLS: delete_file()")); num_args = 0; XtSetArg(args[num_args], nhStr(XtNscrollHorizontal), XawtextScrollWhenNeeded); num_args++; XtSetArg(args[num_args], nhStr(XtNscrollVertical), XawtextScrollAlways); num_args++; XtSetArg(args[num_args], nhStr(XtNtype), XawAsciiString); num_args++; XtSetArg(args[num_args], nhStr(XtNstring), textlines); num_args++; XtSetArg(args[num_args], nhStr(XtNdisplayCaret), False); num_args++; XtSetArg(args[num_args], nhStr(XtNtranslations), XtParseTranslationTable(display_translations)); num_args++; dispfile = XtCreateManagedWidget("text", /* name */ asciiTextWidgetClass, popup, /* parent */ args, num_args); /* Get font and border information. */ num_args = 0; XtSetArg(args[num_args], nhStr(XtNfont), &fs); num_args++; XtSetArg(args[num_args], nhStr(XtNtopMargin), &top_margin); num_args++; XtSetArg(args[num_args], nhStr(XtNbottomMargin), &bottom_margin); num_args++; XtSetArg(args[num_args], nhStr(XtNleftMargin), &left_margin); num_args++; XtSetArg(args[num_args], nhStr(XtNrightMargin), &right_margin); num_args++; XtGetValues(dispfile, args, num_args); /* * The data files are currently set up assuming an 80 char wide window * and a fixed width font. Soo.. */ new_height = num_lines * nhFontHeight(dispfile) + top_margin + bottom_margin; new_width = 80 * fs->max_bounds.width + left_margin + right_margin; /* Set the new width and height. */ num_args = 0; XtSetArg(args[num_args], XtNwidth, new_width); num_args++; XtSetArg(args[num_args], XtNheight, new_height); num_args++; XtSetValues(dispfile, args, num_args); nh_XtPopup(popup, (int) XtGrabNone, (Widget) 0); free(textlines); } /* yn_function ------------------------------------------------------------ */ /* (not threaded) */ static const char *yn_quitchars = " \n\r"; static const char *yn_choices; /* string of acceptable input */ static char yn_def; static char yn_return; /* return value */ static char yn_esc_map; /* ESC maps to this char. */ static Widget yn_popup; /* popup for the yn fuction (created once) */ static Widget yn_label; /* label for yn function (created once) */ static boolean yn_getting_num; /* TRUE if accepting digits */ static boolean yn_preserve_case; /* default is to force yn to lower case */ static int yn_ndigits; /* digit count */ static long yn_val; /* accumulated value */ static const char yn_translations[] = "#override\n\ : yn_key()"; /* * Convert the given key event into a character. If the key maps to * more than one character only the first is returned. If there is * no conversion (i.e. just the CTRL key hit) a NUL is returned. */ char key_event_to_char(key) XKeyEvent *key; { char keystring[MAX_KEY_STRING]; int nbytes; boolean meta = !!(key->state & Mod1Mask); nbytes = XLookupString(key, keystring, MAX_KEY_STRING, (KeySym *) 0, (XComposeStatus *) 0); /* Modifier keys return a zero lengh string when pressed. */ if (nbytes == 0) return '\0'; return (char) (((int) keystring[0]) + (meta ? 0x80 : 0)); } /* * Called when we get a WM_DELETE_WINDOW event on a yn window. */ /* ARGSUSED */ static void yn_delete(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { nhUse(w); nhUse(event); nhUse(params); nhUse(num_params); yn_getting_num = FALSE; /* Only use yn_esc_map if we have choices. Otherwise, return ESC. */ yn_return = yn_choices ? yn_esc_map : '\033'; exit_x_event = TRUE; /* exit our event handler */ } /* * Called when we get a key press event on a yn window. */ /* ARGSUSED */ static void yn_key(w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal *num_params; { char ch; if (appResources.slow && !input_func) map_input(w, event, params, num_params); ch = key_event_to_char((XKeyEvent *) event); if (ch == '\0') { /* don't accept nul char or modifier event */ /* no bell */ return; } if (!yn_choices) { /* accept any input */ yn_return = ch; } else { if (!yn_preserve_case) ch = lowc(ch); /* move to lower case */ if (ch == '\033') { yn_getting_num = FALSE; yn_return = yn_esc_map; } else if (index(yn_quitchars, ch)) { yn_return = yn_def; } else if (index(yn_choices, ch)) { if (ch == '#') { if (yn_getting_num) { /* don't select again */ X11_nhbell(); return; } yn_getting_num = TRUE; yn_ndigits = 0; yn_val = 0; return; /* wait for more input */ } yn_return = ch; if (ch != 'y') yn_getting_num = FALSE; } else { if (yn_getting_num) { if (digit(ch)) { yn_ndigits++; yn_val = (yn_val * 10) + (long) (ch - '0'); return; /* wait for more input */ } if (yn_ndigits && (ch == '\b' || ch == 127 /*DEL*/)) { yn_ndigits--; yn_val = yn_val / 10; return; /* wait for more input */ } } X11_nhbell(); /* no match */ return; } if (yn_getting_num) { yn_return = '#'; if (yn_val < 0) yn_val = 0; yn_number = yn_val; /* assign global */ } } exit_x_event = TRUE; /* exit our event handler */ } /* called at exit time */ static void release_yn_widgets() { if (yn_label) XtDestroyWidget(yn_label), yn_label = (Widget) 0; if (yn_popup) XtDestroyWidget(yn_popup), yn_popup = (Widget) 0; } /* X11-specific edition of yn_function(), the routine called by the core to show a prompt and get a single keystroke answer, often 'y' vs 'n' */ char X11_yn_function(ques, choices, def) const char *ques; const char *choices; /* string of possible response chars; any char if Null */ char def; /* default response if user hits or */ { char buf[BUFSZ]; Arg args[4]; Cardinal num_args; yn_choices = choices; /* set up globals for callback to use */ yn_def = def; yn_preserve_case = !choices; /* preserve case when an arbitrary response is allowed */ /* * This is sort of a kludge. There are quite a few places in the main * nethack code where a pline containing information is followed by a * call to yn_function(). There is no flush of the message window * (it is implicit in the tty window port), so the line never shows * up for us! Solution: do our own flush. */ if (WIN_MESSAGE != WIN_ERR) display_message_window(&window_list[WIN_MESSAGE]); if (choices) { char *cb, choicebuf[QBUFSZ]; Strcpy(choicebuf, choices); /* anything beyond is hidden */ /* default when choices are present is to force yn answer to lowercase unless one or more choices are explicitly uppercase; check this before stripping the hidden choices */ for (cb = choicebuf; *cb; ++cb) if ('A' <= *cb && *cb <= 'Z') { yn_preserve_case = TRUE; break; } if ((cb = index(choicebuf, '\033')) != 0) *cb = '\0'; /* ques [choices] (def) */ if ((int) (1 + strlen(ques) + 2 + strlen(choicebuf) + 4) >= BUFSZ) panic("X11_yn_function: question too long"); (void) strncpy(buf, ques, QBUFSZ - 1); buf[QBUFSZ - 1] = '\0'; Sprintf(eos(buf), " [%s]", choicebuf); if (def) Sprintf(eos(buf), " (%c)", def); Strcat(buf, " "); /* escape maps to 'q' or 'n' or default, in that order */ yn_esc_map = (index(choices, 'q') ? 'q' : index(choices, 'n') ? 'n' : def); } else { if ((int) (1 + strlen(ques) + 1) >= BUFSZ) panic("X11_yn_function: question too long"); Strcpy(buf, ques); Strcat(buf, " "); } /* * The 'slow' resource is misleadingly named. When it is True, we * re-use the same one-line widget above the map for all yn prompt * responses instead of re-using a popup widget for each one. It's * crucial for client/server configs where the server is slow putting * up a popup or requires click-to-focus to respond to the popup, but * is also useful for players who just want to be able to look at the * same location to read questions they're being asked to answer. */ if (appResources.slow) { /* * 'slow': the yn_label widget was created when the map and * status widgets were, and is positioned between them. It * will persist until end of game. All we need to do for * yn_function is direct keystroke input to the yn response * handler and reset its label to be the prompt text (below). */ input_func = yn_key; highlight_yn(FALSE); /* expose yn_label as separate from map */ } else if (!yn_label) { /* * Not 'slow'; create a persistent widget that will be popped up * as needed, then down again, and last until end of game. The * associated yn_label widget is used to track whether it exists. */ XtSetArg(args[0], XtNallowShellResize, True); yn_popup = XtCreatePopupShell("query", transientShellWidgetClass, toplevel, args, ONE); XtOverrideTranslations(yn_popup, XtParseTranslationTable("WM_PROTOCOLS: yn_delete()")); num_args = 0; XtSetArg(args[num_args], XtNtranslations, XtParseTranslationTable(yn_translations)); num_args++; yn_label = XtCreateManagedWidget("yn_label", labelWidgetClass, yn_popup, args, num_args); XtRealizeWidget(yn_popup); XSetWMProtocols(XtDisplay(yn_popup), XtWindow(yn_popup), &wm_delete_window, 1); } /* set the label of the yn widget to be the prompt text */ num_args = 0; XtSetArg(args[num_args], XtNlabel, buf); num_args++; XtSetValues(yn_label, args, num_args); if (!appResources.slow) { /* * Due to some kind of weird bug in the X11R4 and X11R5 shell, we * need to set the label twice to get the size to change. */ num_args = 0; XtSetArg(args[num_args], XtNlabel, buf); num_args++; XtSetValues(yn_label, args, num_args); positionpopup(yn_popup, TRUE); nh_XtPopup(yn_popup, (int) XtGrabExclusive, yn_label); } yn_getting_num = FALSE; (void) x_event(EXIT_ON_EXIT); /* get keystroke(s) */ if (appResources.slow) { /* keystrokes now belong to the map */ input_func = 0; /* erase the prompt */ num_args = 0; XtSetArg(args[num_args], XtNlabel, " "); num_args++; XtSetValues(yn_label, args, num_args); highlight_yn(FALSE); /* disguise yn_label as part of map */ } else { nh_XtPopdown(yn_popup); /* this removes the event grab */ } return yn_return; } /* used when processing window-capability-specific run-time options; we support toggling tiles on and off via iflags.wc_tiled_map */ void X11_preference_update(pref) const char *pref; { if (!strcmp(pref, "tiled_map")) { if (WIN_MAP != WIN_ERR) display_map_window(&window_list[WIN_MAP]); } } /* End global functions =================================================== */ /* * Before we wait for input via nhgetch() and nh_poskey(), we need to * do some pre-processing. */ static int input_event(exit_condition) int exit_condition; { if (WIN_STATUS != WIN_ERR) /* hilighting on the fancy status window */ check_turn_events(); if (WIN_MAP != WIN_ERR) /* make sure cursor is not clipped */ check_cursor_visibility(&window_list[WIN_MAP]); if (WIN_MESSAGE != WIN_ERR) /* reset pause line */ set_last_pause(&window_list[WIN_MESSAGE]); return x_event(exit_condition); } /*ARGSUSED*/ void msgkey(w, data, event) Widget w; XtPointer data; XEvent *event; { Cardinal num = 0; nhUse(w); nhUse(data); map_input(window_list[WIN_MAP].w, event, (String *) 0, &num); } /* only called for autofocus */ /*ARGSUSED*/ static void win_visible(w, data, event, flag) Widget w; XtPointer data; /* client_data not used */ XEvent *event; Boolean *flag; /* continue_to_dispatch flag not used */ { XVisibilityEvent *vis_event = (XVisibilityEvent *) event; nhUse(data); nhUse(flag); if (vis_event->state != VisibilityFullyObscured) { /* one-time operation; cancel ourself */ XtRemoveEventHandler(toplevel, VisibilityChangeMask, False, win_visible, (XtPointer) 0); /* grab initial input focus */ XSetInputFocus(XtDisplay(w), XtWindow(w), RevertToNone, CurrentTime); } } /* if 'slow' and 'highlight_prompt', set the yn_label widget to look like part of the map when idle or to invert background and foreground when a prompt is active */ void highlight_yn(init) boolean init; { struct xwindow *xmap; if (!appResources.slow || !appResources.highlight_prompt) return; /* first time through, WIN_MAP isn't fully initiialized yet */ xmap = ((map_win != WIN_ERR) ? &window_list[map_win] : (WIN_MAP != WIN_ERR) ? &window_list[WIN_MAP] : 0); if (init && xmap) { Arg args[2]; XGCValues vals; unsigned long fg_bg = (GCForeground | GCBackground); GC gc = (xmap->map_information->is_tile ? xmap->map_information->tile_map.white_gc : xmap->map_information->text_map.copy_gc); (void) memset((genericptr_t) &vals, 0, sizeof vals); if (XGetGCValues(XtDisplay(xmap->w), gc, fg_bg, &vals)) { XtSetArg(args[0], XtNforeground, vals.foreground); XtSetArg(args[1], XtNbackground, vals.background); XtSetValues(yn_label, args, TWO); } } else swap_fg_bg(yn_label); } /* * Set up the playing console. This has three major parts: the * message window, the map, and the status window. * * For configs specifying the 'slow' resource, the yn_label widget * is placed above the map and below the message window. Prompts * requiring a single character response are displayed there rather * than using a popup. */ static void init_standard_windows() { Widget form, message_viewport, map_viewport, status; Arg args[8]; Cardinal num_args; Dimension message_vp_width, map_vp_width, status_width, max_width; int map_vp_hd, status_hd; struct xwindow *wp; num_args = 0; XtSetArg(args[num_args], XtNallowShellResize, True); num_args++; form = XtCreateManagedWidget("nethack", panedWidgetClass, toplevel, args, num_args); XtAddEventHandler(form, KeyPressMask, False, (XtEventHandler) msgkey, (XtPointer) 0); if (appResources.autofocus) XtAddEventHandler(toplevel, VisibilityChangeMask, False, win_visible, (XtPointer) 0); /* * Create message window. */ WIN_MESSAGE = message_win = find_free_window(); wp = &window_list[message_win]; wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0; wp->popup = (Widget) 0; create_message_window(wp, FALSE, form); message_viewport = XtParent(wp->w); /* Tell the form that contains it that resizes are OK. */ num_args = 0; XtSetArg(args[num_args], nhStr(XtNresizable), True); num_args++; XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++; XtSetArg(args[num_args], nhStr(XtNtop), XtChainTop); num_args++; XtSetValues(message_viewport, args, num_args); if (appResources.slow) { num_args = 0; XtSetArg(args[num_args], XtNtranslations, XtParseTranslationTable(yn_translations)); num_args++; yn_label = XtCreateManagedWidget("yn_label", labelWidgetClass, form, args, num_args); num_args = 0; XtSetArg(args[num_args], nhStr(XtNfromVert), message_viewport); num_args++; XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft); num_args++; XtSetArg(args[num_args], nhStr(XtNresizable), True); num_args++; XtSetArg(args[num_args], nhStr(XtNlabel), " "); num_args++; XtSetValues(yn_label, args, num_args); } /* * Create the map window & viewport and chain the viewport beneath the * message_viewport. */ map_win = find_free_window(); wp = &window_list[map_win]; wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0; wp->popup = (Widget) 0; create_map_window(wp, FALSE, form); map_viewport = XtParent(wp->w); /* Chain beneath message_viewport or yn window. */ num_args = 0; if (appResources.slow) { XtSetArg(args[num_args], nhStr(XtNfromVert), yn_label); num_args++; } else { XtSetArg(args[num_args], nhStr(XtNfromVert), message_viewport); num_args++; } XtSetArg(args[num_args], nhStr(XtNbottom), XtChainBottom); num_args++; XtSetValues(map_viewport, args, num_args); /* Create the status window, with the form as it's parent. */ status_win = find_free_window(); wp = &window_list[status_win]; wp->cursx = wp->cursy = wp->pixel_width = wp->pixel_height = 0; wp->popup = (Widget) 0; create_status_window(wp, FALSE, form); status = wp->w; /* * Chain the status window beneath the viewport. Mark the left and right * edges so that they stay a fixed distance from the left edge of the * parent, as well as the top and bottom edges so that they stay a fixed * distance from the bottom of the parent. We do this so that the status * will never expand or contract. */ num_args = 0; XtSetArg(args[num_args], nhStr(XtNfromVert), map_viewport); num_args++; XtSetArg(args[num_args], nhStr(XtNleft), XtChainLeft); num_args++; XtSetArg(args[num_args], nhStr(XtNright), XtChainLeft); num_args++; XtSetArg(args[num_args], nhStr(XtNtop), XtChainBottom); num_args++; XtSetArg(args[num_args], nhStr(XtNbottom), XtChainBottom); num_args++; XtSetValues(status, args, num_args); /* * Realize the popup so that the status widget knows it's size. * * If we unset MappedWhenManaged then the DECwindow driver doesn't * attach the nethack toplevel to the highest virtual root window. * So don't do it. */ /* XtSetMappedWhenManaged(toplevel, False); */ XtRealizeWidget(toplevel); wm_delete_window = XInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW", False); XSetWMProtocols(XtDisplay(toplevel), XtWindow(toplevel), &wm_delete_window, 1); /* * Resize to at most full-screen. */ { #define TITLEBAR_SPACE 18 /* Leave SOME screen for window decorations */ int screen_width = WidthOfScreen(XtScreen(wp->w)); int screen_height = HeightOfScreen(XtScreen(wp->w)) - TITLEBAR_SPACE; Dimension form_width, form_height; XtSetArg(args[0], XtNwidth, &form_width); XtSetArg(args[1], XtNheight, &form_height); XtGetValues(toplevel, args, TWO); if (form_width > screen_width || form_height > screen_height) { XtSetArg(args[0], XtNwidth, min(form_width, screen_width)); XtSetArg(args[1], XtNheight, min(form_height, screen_height)); XtSetValues(toplevel, args, TWO); XMoveWindow(XtDisplay(toplevel), XtWindow(toplevel), 0, TITLEBAR_SPACE); } #undef TITLEBAR_SPACE } post_process_tiles(); /* after toplevel is realized */ /* * Now get the default widths of the windows. */ XtSetArg(args[0], nhStr(XtNwidth), &message_vp_width); XtGetValues(message_viewport, args, ONE); XtSetArg(args[0], nhStr(XtNwidth), &map_vp_width); XtSetArg(args[1], nhStr(XtNhorizDistance), &map_vp_hd); XtGetValues(map_viewport, args, TWO); XtSetArg(args[0], nhStr(XtNwidth), &status_width); XtSetArg(args[1], nhStr(XtNhorizDistance), &status_hd); XtGetValues(status, args, TWO); /* * Adjust positions and sizes. The message viewport widens out to the * widest width. Both the map and status are centered by adjusting * their horizDistance. */ if (map_vp_width < status_width || map_vp_width < message_vp_width) { if (status_width > message_vp_width) { XtSetArg(args[0], nhStr(XtNwidth), status_width); XtSetValues(message_viewport, args, ONE); max_width = status_width; } else { max_width = message_vp_width; } XtSetArg(args[0], nhStr(XtNhorizDistance), map_vp_hd + ((int) (max_width - map_vp_width) / 2)); XtSetValues(map_viewport, args, ONE); } else { /* map is widest */ XtSetArg(args[0], nhStr(XtNwidth), map_vp_width); XtSetValues(message_viewport, args, ONE); } /* * Clear all data values on the fancy status widget so that the values * used for spacing don't appear. This needs to be called some time * after the fancy status widget is realized (above, with the game popup), * but before it is popped up. */ null_out_status(); /* * Set the map size to its standard size. As with the message window * above, the map window needs to be set to its constrained size until * its parent (the viewport widget) was realized. * * Move the message window's slider to the bottom. */ set_map_size(&window_list[map_win], COLNO, ROWNO); set_message_slider(&window_list[message_win]); /* attempt to catch fatal X11 errors before the program quits */ (void) XtAppSetErrorHandler(app_context, (XtErrorHandler) hangup); highlight_yn(TRUE); /* switch foreground and background */ /* We can now print to the message window. */ iflags.window_inited = 1; } void nh_XtPopup(w, g, childwid) Widget w; /* widget */ int g; /* type of grab */ Widget childwid; /* child to recieve focus (can be None) */ { XtPopup(w, (XtGrabKind) g); XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_delete_window, 1); if (appResources.autofocus) XtSetKeyboardFocus(toplevel, childwid); } void nh_XtPopdown(w) Widget w; { XtPopdown(w); if (appResources.autofocus) XtSetKeyboardFocus(toplevel, None); } void win_X11_init(dir) int dir; { if (dir != WININIT) return; #ifdef OPENWINBUG /* With the OpenWindows 3.0 libraries and the SunOS 4.1.2 ld, these * two routines will not be found when linking. An apparently correct * executable is produced, along with nasty messages and a failure code * returned to make. The routines are in the static libXmu.a and * libXmu.sa.4.0, but not in libXmu.so.4.0. Rather than fiddle with * static linking, we do this. */ if (rn2(2) > 2) { /* i.e., FALSE that an optimizer probably can't find */ get_wmShellWidgetClass(); get_applicationShellWidgetClass(); } #endif return; } /* Callback * Scroll a viewport, using standard NH 1,2,3,4,6,7,8,9 directions. */ /*ARGSUSED*/ void nh_keyscroll(viewport, event, params, num_params) Widget viewport; XEvent *event; String *params; Cardinal *num_params; { Arg arg[2]; Widget horiz_sb, vert_sb; float top, shown; Boolean do_call; int direction; Cardinal in_nparams = (num_params ? *num_params : 0); nhUse(event); if (in_nparams != 1) return; /* bad translation */ direction = atoi(params[0]); horiz_sb = XtNameToWidget(viewport, "*horizontal"); vert_sb = XtNameToWidget(viewport, "*vertical"); if (!horiz_sb && !vert_sb) { /* Perhaps the widget enclosing this has scrollbars (could use while) */ Widget parent = XtParent(viewport); if (parent) { horiz_sb = XtNameToWidget(parent, "horizontal"); vert_sb = XtNameToWidget(parent, "vertical"); } } #define H_DELTA 0.25 /* distance of horiz shift */ /* vert shift is half of curr distance */ /* The V_DELTA is 1/2 the value of shown. */ if (horiz_sb) { XtSetArg(arg[0], nhStr(XtNshown), &shown); XtSetArg(arg[1], nhStr(XtNtopOfThumb), &top); XtGetValues(horiz_sb, arg, TWO); do_call = True; switch (direction) { case 1: case 4: case 7: top -= H_DELTA; if (top < 0.0) top = 0.0; break; case 3: case 6: case 9: top += H_DELTA; if (top + shown > 1.0) top = 1.0 - shown; break; default: do_call = False; } if (do_call) { XtCallCallbacks(horiz_sb, XtNjumpProc, &top); } } if (vert_sb) { XtSetArg(arg[0], nhStr(XtNshown), &shown); XtSetArg(arg[1], nhStr(XtNtopOfThumb), &top); XtGetValues(vert_sb, arg, TWO); do_call = True; switch (direction) { case 7: case 8: case 9: top -= shown / 2.0; if (top < 0.0) top = 0; break; case 1: case 2: case 3: top += shown / 2.0; if (top + shown > 1.0) top = 1.0 - shown; break; default: do_call = False; } if (do_call) { XtCallCallbacks(vert_sb, XtNjumpProc, &top); } } } /*winX.c*/