Pull request from mkuoppal: avoid integer overflow when user types digits and they're combined into a number by successively multiplying intermediate value by 10 and adding new digit. Needed to avoid triggering undefined behavior if the value overflows the largest signed integer (actually long int). This is a much more general fix than the code in the pull request, which imposed an arbitrary limit for one aspect of tty input. I'm not convinced that integer.h was the right place to add the new AppendLongDigit() macro. I may not have caught all the places where it is needed. files.c accumulates a value from digits but uses unsigned int, so overflow won't trigger undefined behavior (although it presumably ends up with a different value than what was intended). options.c and coloratt.c accumulate smaller integers and have a limit on the number of digits they'll use, so can't overflow. Fixes #1254
2930 lines
92 KiB
C
2930 lines
92 KiB
C
/* NetHack 3.7 winX.c $NHDT-Date: 1717967337 2024/06/09 21:08:57 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.136 $ */
|
|
/* 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.txt 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
|
|
|
|
/* Can't use #ifdef LINUX because no header files have been processed yet.
|
|
* This is needed to ensure the prototype for seteuid() is picked up when
|
|
* the header files are processed.
|
|
*/
|
|
#ifdef __linux__
|
|
#define _POSIX_C_SOURCE 200809
|
|
#endif
|
|
|
|
#include <X11/Intrinsic.h>
|
|
#include <X11/StringDefs.h>
|
|
#include <X11/Shell.h>
|
|
#include <X11/Xaw/AsciiText.h>
|
|
#include <X11/Xaw/Label.h>
|
|
#include <X11/Xaw/Form.h>
|
|
#include <X11/Xaw/Scrollbar.h>
|
|
#include <X11/Xaw/Paned.h>
|
|
#include <X11/Xaw/Cardinals.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xos.h>
|
|
|
|
/* for color support */
|
|
#ifdef SHORT_FILENAMES
|
|
#include <X11/IntrinsP.h>
|
|
#else
|
|
#include <X11/IntrinsicP.h>
|
|
#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
|
|
|
|
#define X11_BUILD
|
|
#include "hack.h"
|
|
#undef X11_BUILD
|
|
|
|
#include "winX.h"
|
|
#include "dlb.h"
|
|
#include "xwindow.h"
|
|
|
|
#ifndef NO_SIGNAL
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
/* Should be defined in <X11/Intrinsic.h> 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 (*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; /* used to indicate perm_invent updating */
|
|
color_attr X11_menu_promptstyle = { NO_COLOR, ATR_NONE };
|
|
|
|
static void X11_error_handler(String) NORETURN;
|
|
static int X11_io_error_handler(Display *);
|
|
|
|
static int (*old_error_handler)(Display *, XErrorEvent *);
|
|
|
|
#if defined(HANGUPHANDLING)
|
|
#if !defined(NO_SIGNAL) && defined(SAFERHANGUP)
|
|
#if XtSpecificationRelease >= 6
|
|
#define X11_HANGUP_SIGNAL
|
|
static XtSignalId X11_sig_id;
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
/* Interface definition, for windows.c */
|
|
struct window_procs X11_procs = {
|
|
WPID(X11),
|
|
( WC_COLOR | WC_INVERSE | WC_HILITE_PET | WC_ASCII_MAP | WC_TILED_MAP
|
|
| WC_PLAYER_SELECTION | WC_PERM_INVENT | WC_MOUSE_SUPPORT ),
|
|
/* status requires VIA_WINDOWPORT(); WC2_FLUSH_STATUS ensures that */
|
|
( WC2_FLUSH_STATUS | WC2_SELECTSAVED
|
|
#ifdef STATUS_HILITES
|
|
| WC2_RESET_STATUS | WC2_HILITE_STATUS
|
|
#endif
|
|
| WC2_MENU_SHIFT ),
|
|
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
|
|
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_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, X11_getmsghistory, X11_putmsghistory,
|
|
X11_status_init, X11_status_finish, X11_status_enablefield,
|
|
X11_status_update,
|
|
genl_can_suspend_no, /* XXX may not always be correct */
|
|
X11_update_inventory,
|
|
X11_ctrl_nhwindow,
|
|
};
|
|
|
|
/*
|
|
* Local functions.
|
|
*/
|
|
static winid find_free_window(void);
|
|
static void nhFreePixel(XtAppContext, XrmValuePtr, XtPointer, XrmValuePtr,
|
|
Cardinal *);
|
|
static boolean new_resource_macro(String, unsigned);
|
|
static void load_default_resources(void);
|
|
static void release_default_resources(void);
|
|
static int panic_on_error(Display *, XErrorEvent *);
|
|
#ifdef X11_HANGUP_SIGNAL
|
|
static void X11_sig(int);
|
|
static void X11_sig_cb(XtPointer, XtSignalId *);
|
|
#endif
|
|
static void d_timeout(XtPointer, XtIntervalId *);
|
|
static void X11_hangup(Widget, XEvent *, String *, Cardinal *);
|
|
static void X11_bail(const char *) NORETURN;
|
|
static void askname_delete(Widget, XEvent *, String *, Cardinal *);
|
|
static void askname_done(Widget, XtPointer, XtPointer);
|
|
static void done_button(Widget, XtPointer, XtPointer);
|
|
static void getline_delete(Widget, XEvent *, String *, Cardinal *);
|
|
static void abort_button(Widget, XtPointer, XtPointer);
|
|
static void release_getline_widgets(void);
|
|
static void yn_delete(Widget, XEvent *, String *, Cardinal *);
|
|
static void yn_key(Widget, XEvent *, String *, Cardinal *);
|
|
static void release_yn_widgets(void);
|
|
static int input_event(int);
|
|
static void win_visible(Widget, XtPointer, XEvent *, Boolean *);
|
|
static void init_standard_windows(void);
|
|
|
|
/*
|
|
* 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. */
|
|
|
|
void
|
|
X11_putmsghistory(const char *msg, boolean is_restoring)
|
|
{
|
|
if (WIN_MESSAGE != WIN_ERR) {
|
|
struct xwindow *wp = &window_list[WIN_MESSAGE];
|
|
debugpline2("X11_putmsghistory('%s',%i)", msg, is_restoring);
|
|
if (msg)
|
|
append_message(wp, msg);
|
|
}
|
|
}
|
|
|
|
char *
|
|
X11_getmsghistory(boolean init)
|
|
{
|
|
if (WIN_MESSAGE != WIN_ERR) {
|
|
static struct line_element *curr = (struct line_element *) 0;
|
|
static int numlines = 0;
|
|
struct xwindow *wp = &window_list[WIN_MESSAGE];
|
|
|
|
if (init)
|
|
curr = (struct line_element *) 0;
|
|
|
|
if (!curr) {
|
|
curr = wp->mesg_information->head;
|
|
numlines = 0;
|
|
}
|
|
|
|
if (numlines < wp->mesg_information->num_lines) {
|
|
curr = curr->next;
|
|
numlines++;
|
|
debugpline2("X11_getmsghistory(%i)='%s'", init, curr->line);
|
|
return curr->line;
|
|
}
|
|
}
|
|
return (char *) 0;
|
|
}
|
|
|
|
/*
|
|
* 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(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(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
XColor
|
|
get_nhcolor(struct xwindow *wp, int clr)
|
|
{
|
|
init_menu_nhcolors(wp);
|
|
/* FIXME: init_menu_nhcolors may fail */
|
|
|
|
if (clr >= 0 && clr < CLR_MAX)
|
|
return wp->nh_colors[clr];
|
|
|
|
return wp->nh_colors[0];
|
|
}
|
|
|
|
void
|
|
init_menu_nhcolors(struct xwindow *wp)
|
|
{
|
|
static const char *mapCLR_to_res[CLR_MAX] = {
|
|
XtNblack,
|
|
XtNred,
|
|
XtNgreen,
|
|
XtNbrown,
|
|
XtNblue,
|
|
XtNmagenta,
|
|
XtNcyan,
|
|
XtNgray,
|
|
XtNforeground,
|
|
XtNorange,
|
|
XtNbright_green,
|
|
XtNyellow,
|
|
XtNbright_blue,
|
|
XtNbright_magenta,
|
|
XtNbright_cyan,
|
|
XtNwhite,
|
|
};
|
|
static const char *wintypenames[NHW_TEXT] = {
|
|
"message", "status", "map", "menu", "text"
|
|
};
|
|
Display *dpy;
|
|
Colormap screen_colormap;
|
|
XrmDatabase rDB;
|
|
XrmValue value;
|
|
Status rc;
|
|
int color;
|
|
char *ret_type[32];
|
|
char wtn_up[20], clr_name[BUFSZ], clrclass[BUFSZ];
|
|
const char *wtn;
|
|
|
|
if (wp->nh_colors_inited || !wp->type)
|
|
return;
|
|
|
|
wtn = wintypenames[wp->type - 1];
|
|
Strcpy(wtn_up, wtn);
|
|
(void) upstart(wtn_up);
|
|
|
|
dpy = XtDisplay(wp->w);
|
|
screen_colormap = DefaultColormap(dpy, DefaultScreen(dpy));
|
|
rDB = XrmGetDatabase(dpy);
|
|
|
|
for (color = 0; color < CLR_MAX; color++) {
|
|
Sprintf(clr_name, "nethack.%s.%s", wtn, mapCLR_to_res[color]);
|
|
Sprintf(clrclass, "NetHack.%s.%s", wtn_up, mapCLR_to_res[color]);
|
|
|
|
if (!XrmGetResource(rDB, clr_name, clrclass, ret_type, &value)) {
|
|
Sprintf(clr_name, "nethack.map.%s", mapCLR_to_res[color]);
|
|
Sprintf(clrclass, "NetHack.Map.%s", mapCLR_to_res[color]);
|
|
}
|
|
|
|
if (!XrmGetResource(rDB, clr_name, clrclass, ret_type, &value)) {
|
|
impossible("XrmGetResource error (%s)", clr_name);
|
|
} else if (!strcmp(ret_type[0], "String")) {
|
|
char tmpbuf[256];
|
|
|
|
if (value.size >= sizeof tmpbuf)
|
|
value.size = sizeof tmpbuf - 1;
|
|
(void) strncpy(tmpbuf, (char *) value.addr, (int) value.size);
|
|
tmpbuf[value.size] = '\0';
|
|
/* tmpbuf now contains the color name from the named resource */
|
|
|
|
rc = XAllocNamedColor(dpy, screen_colormap, tmpbuf,
|
|
&wp->nh_colors[color],
|
|
&wp->nh_colors[color]);
|
|
if (rc == 0) {
|
|
impossible("XAllocNamedColor failed for color %i (%s)",
|
|
color, clr_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
wp->nh_colors_inited = TRUE;
|
|
}
|
|
|
|
/*
|
|
* 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 *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;
|
|
int i, j;
|
|
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(
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* Ask the WM for window frame size */
|
|
void
|
|
get_window_frame_extents(
|
|
Widget w,
|
|
long *top, long *bottom,
|
|
long *left, long *right)
|
|
{
|
|
XEvent event;
|
|
Display *dpy = XtDisplay(w);
|
|
Window win = XtWindow(w);
|
|
Atom prop, retprop;
|
|
int retfmt;
|
|
unsigned long nitems;
|
|
unsigned long nbytes;
|
|
unsigned char *data = 0;
|
|
long *extents;
|
|
|
|
*top = *bottom = *left = *right = 0L;
|
|
|
|
prop = XInternAtom(dpy, "_NET_FRAME_EXTENTS", True);
|
|
if (prop == None) {
|
|
/*
|
|
* FIXME!
|
|
*/
|
|
#ifdef MACOS
|
|
/*
|
|
* Default window manager doesn't support _NET_FRAME_EXTENTS.
|
|
* Without this position tweak, the persistent inventory window
|
|
* creeps downward by approximately the height of its title bar
|
|
* and also a smaller amount to the left every time it gets
|
|
* updated. Caveat: amount determined by trial and error and
|
|
* could change depending upon monitor resolution....
|
|
*
|
|
* This hack could be improved by implementing the anti-creep
|
|
* value as an X resource so that player can adjust it.
|
|
*/
|
|
*top = 22L;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
while (XGetWindowProperty(dpy, win, prop,
|
|
0, 4, False, AnyPropertyType,
|
|
&retprop, &retfmt,
|
|
&nitems, &nbytes, &data) != Success
|
|
|| nitems != 4 || nbytes != 0) {
|
|
XNextEvent(dpy, &event);
|
|
}
|
|
|
|
extents = (long *) data;
|
|
if (extents) {
|
|
*left = extents[0];
|
|
*right = extents[1];
|
|
*top = extents[2];
|
|
*bottom = extents[3];
|
|
}
|
|
}
|
|
|
|
void
|
|
get_widget_window_geometry(
|
|
Widget w,
|
|
int *x, int *y,
|
|
int *width, int *height)
|
|
{
|
|
long top, bottom, left, right;
|
|
Arg args[5];
|
|
|
|
XtSetArg(args[0], nhStr(XtNx), x);
|
|
XtSetArg(args[1], nhStr(XtNy), y);
|
|
XtSetArg(args[2], nhStr(XtNwidth), width);
|
|
XtSetArg(args[3], nhStr(XtNheight), height);
|
|
XtGetValues(w, args, 4);
|
|
get_window_frame_extents(w, &top, &bottom, &left, &right);
|
|
*x -= left;
|
|
*y -= top;
|
|
}
|
|
|
|
/* Change the full font name string so the weight is "bold" */
|
|
char *
|
|
fontname_boldify(const char *fontname)
|
|
{
|
|
static char buf[BUFSZ];
|
|
char *bufp = buf;
|
|
int idx = 0;
|
|
|
|
while (*fontname) {
|
|
if (*fontname == '-')
|
|
idx++;
|
|
*bufp = *fontname;
|
|
if (idx == 3) {
|
|
strcat(buf, "bold");
|
|
bufp += 5;
|
|
do {
|
|
fontname++;
|
|
} while (*fontname && *fontname != '-');
|
|
} else {
|
|
bufp++;
|
|
fontname++;
|
|
}
|
|
}
|
|
*bufp = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
load_boldfont(struct xwindow *wp, Widget w)
|
|
{
|
|
Arg args[1];
|
|
XFontStruct *fs;
|
|
unsigned long ret;
|
|
char *fontname;
|
|
Display *dpy;
|
|
|
|
if (wp->boldfs)
|
|
return;
|
|
|
|
XtSetArg(args[0], nhStr(XtNfont), &fs);
|
|
XtGetValues(w, args, 1);
|
|
|
|
if (!XGetFontProperty(fs, XA_FONT, &ret))
|
|
return;
|
|
|
|
wp->boldfs_dpy = dpy = XtDisplay(w);
|
|
fontname = fontname_boldify(XGetAtomName(dpy, (Atom)ret));
|
|
wp->boldfs = XLoadQueryFont(dpy, fontname);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static void
|
|
nhFreePixel(
|
|
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(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 arrays */
|
|
*def_rsrc_macr = 0, /* macro names */
|
|
*def_rsrc_valu = 0; /* macro values */
|
|
|
|
/* caller found "#define"; parse into macro name and its expansion value */
|
|
static boolean
|
|
new_resource_macro(
|
|
String inbuf, /* points past '#define' rather than to start of buffer */
|
|
unsigned numdefs) /* array slot to fill */
|
|
{
|
|
String p, q;
|
|
|
|
/* we expect inbuf to be terminated by newline; get rid of it */
|
|
q = eos(inbuf);
|
|
if (q > inbuf && q[-1] == '\n')
|
|
q[-1] = '\0';
|
|
|
|
/* figure out macro's name */
|
|
for (p = inbuf; *p == ' ' || *p == '\t'; ++p)
|
|
continue; /* skip whitespace */
|
|
for (q = p; *q && *q != ' ' && *q != '\t'; ++q)
|
|
continue; /* token consists of non-whitespace */
|
|
Strcat(q, " "); /* guarantee something beyond '#define FOO' */
|
|
*q++ = '\0'; /* p..(q-1) contains macro name */
|
|
if (!*p) /* invalid definition: '#define' followed by nothing */
|
|
return FALSE;
|
|
def_rsrc_macr[numdefs] = dupstr(p);
|
|
|
|
/* figure out macro's value; empty value is supported but not expected */
|
|
while (*q == ' ' || *q == '\t')
|
|
++q; /* skip whitespace between name and value */
|
|
for (p = eos(q); --p > q && (*p == ' ' || *p == '\t'); )
|
|
continue; /* discard trailing whitespace */
|
|
*++p = '\0'; /* q..p contains macro value */
|
|
def_rsrc_valu[numdefs] = dupstr(q);
|
|
return TRUE;
|
|
}
|
|
|
|
/* read the template NetHack.ad into default_resource_data[] to supply
|
|
fallback resources to XtAppInitialize() */
|
|
static void
|
|
load_default_resources(void)
|
|
{
|
|
FILE *fp;
|
|
String inbuf;
|
|
unsigned insiz, linelen, longlen, numlines, numdefs, midx;
|
|
boolean comment, isdef;
|
|
|
|
/*
|
|
* 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, nor
|
|
* backslash+newline continuation lines. Macro expansion (at most
|
|
* one substitution per line) is supported. '#define' to introduce
|
|
* a macro must be at start of line (no whitespace before or after
|
|
* the '#' character).
|
|
*/
|
|
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 = numdefs = 0;
|
|
comment = isdef = FALSE; /* lint suppression */
|
|
while (fgets(inbuf, insiz, fp)) {
|
|
if (!linelen) {
|
|
/* !linelen: inbuf has start of record; treat empty as comment */
|
|
comment = (*inbuf == '!' || *inbuf == '\n');
|
|
isdef = !strncmp(inbuf, "#define", 7);
|
|
++numdefs;
|
|
}
|
|
linelen += strlen(inbuf);
|
|
if (!strchr(inbuf, '\n'))
|
|
continue;
|
|
if (linelen > longlen)
|
|
longlen = linelen;
|
|
linelen = 0;
|
|
if (!comment && !isdef)
|
|
++numlines;
|
|
}
|
|
insiz = longlen + 1;
|
|
if (numdefs) { /* don't alloc if 0; no need for any terminator */
|
|
def_rsrc_macr = (String *) alloc(numdefs * sizeof (String));
|
|
def_rsrc_valu = (String *) alloc(numdefs * sizeof (String));
|
|
insiz += BUFSIZ; /* include room for macro expansion within buffer */
|
|
}
|
|
if (insiz > BUFSIZ) {
|
|
free((genericptr_t) inbuf);
|
|
inbuf = (String) alloc(insiz);
|
|
}
|
|
++numlines; /* room for terminator */
|
|
default_resource_data = (String *) alloc(numlines * sizeof (String));
|
|
|
|
/* now re-read the file, storing its contents into the allocated array
|
|
after performing macro substitutions */
|
|
(void) rewind(fp);
|
|
numlines = numdefs = 0;
|
|
while (fgets(inbuf, insiz, fp)) {
|
|
if (!strncmp(inbuf, "#define", 7)) {
|
|
if (new_resource_macro(&inbuf[7], numdefs))
|
|
++numdefs;
|
|
} else if (*inbuf != '!' && *inbuf != '\n') {
|
|
if (numdefs) {
|
|
/*
|
|
* Macro expansion: we assume at most one substitution
|
|
* per line. That's all that our sample NetHack.ad uses.
|
|
*
|
|
* If we ever need more, this will have to become a lot
|
|
* more sophisticated. It will need to find the first
|
|
* instance within inbuf[] rather than first macro which
|
|
* appears, and to avoid finding names within substituted
|
|
* expansion values.
|
|
*
|
|
* Any substitution which would exceed the buffer size is
|
|
* skipped. A sophisticated implementation would need to
|
|
* be prepared to allocate a bigger buffer when needed.
|
|
*/
|
|
linelen = strlen(inbuf);
|
|
for (midx = 0; midx < numdefs; ++midx) {
|
|
if ((linelen + strlen(def_rsrc_valu[midx])
|
|
< insiz - strlen(def_rsrc_macr[midx]))
|
|
&& strNsubst(inbuf, def_rsrc_macr[midx],
|
|
def_rsrc_valu[midx], 1))
|
|
break;
|
|
}
|
|
}
|
|
default_resource_data[numlines++] = dupstr(inbuf);
|
|
}
|
|
}
|
|
default_resource_data[numlines] = (String) 0;
|
|
(void) fclose(fp);
|
|
free((genericptr_t) inbuf);
|
|
if (def_rsrc_macr) { /* implies def_rsrc_valu is non-Null too */
|
|
for (midx = 0; midx < numdefs; ++midx) {
|
|
free((genericptr_t) def_rsrc_macr[midx]);
|
|
free((genericptr_t) def_rsrc_valu[midx]);
|
|
}
|
|
free((genericptr_t) def_rsrc_macr), def_rsrc_macr = 0;
|
|
free((genericptr_t) def_rsrc_valu), def_rsrc_valu = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
release_default_resources(void)
|
|
{
|
|
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;
|
|
}
|
|
/* def_rsrc_macr[] and def_rsrc_valu[] have already been released */
|
|
}
|
|
|
|
/* Global Functions ======================================================= */
|
|
void
|
|
X11_raw_print(const char *str)
|
|
{
|
|
(void) puts(str);
|
|
}
|
|
|
|
void
|
|
X11_raw_print_bold(const char *str)
|
|
{
|
|
(void) puts(str);
|
|
}
|
|
|
|
void
|
|
X11_curs(winid window, int x, int 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(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(gt.toplines, str, TBUFSZ); /* for Norep(). */
|
|
gt.toplines[TBUFSZ - 1] = 0;
|
|
append_message(wp, str);
|
|
break;
|
|
#ifndef STATUS_HILITES
|
|
case NHW_STATUS:
|
|
adjust_status(wp, str);
|
|
break;
|
|
#endif
|
|
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 through */
|
|
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(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int
|
|
X11_nhgetch(void)
|
|
{
|
|
return input_event(EXIT_ON_KEY_PRESS);
|
|
}
|
|
|
|
int
|
|
X11_nh_poskey(coordxy *x, coordxy *y, int *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(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;
|
|
wp->nh_colors_inited = FALSE;
|
|
wp->boldfs = (XFontStruct *) 0;
|
|
wp->boldfs_dpy = (Display *) 0;
|
|
wp->title = (char *) 0;
|
|
|
|
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(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(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(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;
|
|
}
|
|
|
|
if (wp->boldfs) {
|
|
XFreeFont(wp->boldfs_dpy, wp->boldfs);
|
|
wp->boldfs = (XFontStruct *) 0;
|
|
wp->boldfs_dpy = (Display *) 0;
|
|
}
|
|
|
|
if (wp->title) {
|
|
free(wp->title);
|
|
wp->title = (char *) 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* display persistent inventory in its own window */
|
|
void
|
|
X11_update_inventory(int arg)
|
|
{
|
|
struct xwindow *wp = 0;
|
|
|
|
if (!x_inited)
|
|
return;
|
|
|
|
if (iflags.perm_invent) {
|
|
/* skip any calls to update_inventory() before in_moveloop starts */
|
|
if (gp.program_state.in_moveloop || gp.program_state.gameover) {
|
|
updated_inventory = 1; /* hack to avoid mapping&raising window */
|
|
if (!arg) {
|
|
(void) display_inventory((char *) 0, FALSE);
|
|
} else {
|
|
x11_scroll_perminv(arg);
|
|
}
|
|
updated_inventory = 0;
|
|
}
|
|
} else if ((wp = &window_list[WIN_INVEN]) != 0
|
|
&& wp->type == NHW_MENU && wp->menu_information->is_up) {
|
|
/* persistent inventory is up but perm_invent is off, take it down */
|
|
x11_no_perminv(wp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
win_request_info *
|
|
X11_ctrl_nhwindow(
|
|
winid window UNUSED,
|
|
int request UNUSED,
|
|
win_request_info *wri UNUSED)
|
|
{
|
|
if (!wri)
|
|
return (win_request_info *) 0;
|
|
|
|
switch(request) {
|
|
case set_mode:
|
|
case request_settings:
|
|
break;
|
|
case set_menu_promptstyle:
|
|
X11_menu_promptstyle = wri->fromcore.menu_promptstyle;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return wri;
|
|
}
|
|
|
|
/* The current implementation has all of the saved lines on the screen. */
|
|
int
|
|
X11_doprev_message(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* issue a beep to alert the user about something */
|
|
void
|
|
X11_nhbell(void)
|
|
{
|
|
if (!flags.silent) {
|
|
/* We can't use XBell until toplevel has been initialized. */
|
|
if (x_inited) {
|
|
XBell(XtDisplay(toplevel), 0);
|
|
#if 0
|
|
} else {
|
|
/* raw_print() uses puts() so appends a newline */
|
|
X11_raw_print("\a"); /* '\a' == '\007' (^G), ascii BEL */
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
X11_mark_synch(void)
|
|
{
|
|
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(void)
|
|
{
|
|
if (x_inited) {
|
|
X11_mark_synch();
|
|
XFlush(XtDisplay(toplevel));
|
|
}
|
|
}
|
|
|
|
/* Both resume_ and suspend_ are called from ioctl.c and unixunix.c. */
|
|
void
|
|
X11_resume_nhwindows(void)
|
|
{
|
|
return;
|
|
}
|
|
/* ARGSUSED */
|
|
void
|
|
X11_suspend_nhwindows(const char *str)
|
|
{
|
|
nhUse(str);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Under X, we don't need to initialize the number pad. */
|
|
/* ARGSUSED */
|
|
void
|
|
X11_number_pad(int state) /* called from options.c */
|
|
{
|
|
nhUse(state);
|
|
|
|
return;
|
|
}
|
|
|
|
/* called from setftty() in unixtty.c */
|
|
void
|
|
X11_start_screen(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* called from settty() in unixtty.c */
|
|
void
|
|
X11_end_screen(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef GRAPHIC_TOMBSTONE
|
|
void
|
|
X11_outrip(winid window, int how, time_t when)
|
|
{
|
|
struct xwindow *wp;
|
|
FILE *rip_fp = 0;
|
|
|
|
check_winid(window);
|
|
wp = &window_list[window];
|
|
|
|
/* make sure the graphical tombstone is available; it's not easy to
|
|
revert to the ASCII-art text tombstone once we're past this point */
|
|
if (appResources.tombstone && *appResources.tombstone)
|
|
rip_fp = fopen(appResources.tombstone, "r"); /* "rip.xpm" */
|
|
if (!rip_fp) {
|
|
genl_outrip(window, how, when);
|
|
return;
|
|
}
|
|
(void) fclose(rip_fp);
|
|
|
|
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_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("plsel_quit"), plsel_quit }, /* player selection dialog */
|
|
{ nhStr("plsel_play"), plsel_play }, /* player selection dialog */
|
|
{ nhStr("plsel_rnd"), plsel_randomize }, /* player selection dialog */
|
|
{ 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("fancy_status"), nhStr("Fancy_status"),
|
|
XtRBoolean, sizeof(Boolean),
|
|
XtOffset(AppResources *, fancy_status), 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"), nhStr("Tombstone"), XtRString, sizeof(String),
|
|
XtOffset(AppResources *, tombstone), XtRString, nhStr("rip.xpm") },
|
|
{ nhStr("tombtext_x"), nhStr("Tombtext_x"), XtRInt, sizeof(int),
|
|
XtOffset(AppResources *, tombtext_x), XtRString, nhStr("155") },
|
|
{ nhStr("tombtext_y"), nhStr("Tombtext_y"), XtRInt, sizeof(int),
|
|
XtOffset(AppResources *, tombtext_y), XtRString, nhStr("78") },
|
|
{ nhStr("tombtext_dx"), nhStr("Tombtext_dx"), XtRInt, sizeof(int),
|
|
XtOffset(AppResources *, tombtext_dx), XtRString, nhStr("0") },
|
|
{ nhStr("tombtext_dy"), nhStr("Tombtext_dy"), XtRInt, sizeof(int),
|
|
XtOffset(AppResources *, tombtext_dy), XtRString, nhStr("13") },
|
|
#endif
|
|
};
|
|
|
|
static int
|
|
panic_on_error(Display *display, XErrorEvent *error)
|
|
{
|
|
char buf[BUFSZ];
|
|
XGetErrorText(display, error->error_code, buf, BUFSZ);
|
|
fprintf(stderr,
|
|
"X Error: code %i (%s), request %i, minor %i, serial %lu\n",
|
|
error->error_code, buf,
|
|
error->request_code, error->minor_code, error->serial);
|
|
panic("X Error");
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
X11_error_handler(String str)
|
|
{
|
|
nhUse(str);
|
|
#if defined(HANGUPHANDLING)
|
|
hangup(1);
|
|
#ifdef SAFERHANGUP /* keeps going after hangup() for '#if SAFERHANGUP' */
|
|
end_of_input();
|
|
#endif
|
|
#endif
|
|
/*NOTREACHED*/
|
|
nh_terminate(EXIT_FAILURE);
|
|
}
|
|
|
|
static int
|
|
X11_io_error_handler(Display *display)
|
|
{
|
|
nhUse(display);
|
|
#if defined(HANGUPHANDLING)
|
|
hangup(1);
|
|
#ifdef SAFERHANGUP /* keeps going after hangup() for '#if SAFERHANGUP' */
|
|
end_of_input();
|
|
#endif
|
|
#endif
|
|
/*NOREACHED*/ /* but not declared NORETURN */
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
X11_init_nhwindows(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;
|
|
|
|
/* force high scores display to be shown in a window, and don't allow
|
|
that to be toggled off via 'O' (note: 'nethack -s' won't reach here;
|
|
its output goes to stdout and could be redirected into a file) */
|
|
iflags.toptenwin = TRUE;
|
|
set_option_mod_status("toptenwin", set_in_config);
|
|
|
|
/* add another option that can be set */
|
|
set_wc_option_mod_status(WC_TILED_MAP, set_in_game);
|
|
set_option_mod_status("mouse_support", 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.
|
|
*/
|
|
savuid = geteuid();
|
|
(void) seteuid(getuid());
|
|
|
|
XSetIOErrorHandler((XIOErrorHandler) X11_io_error_handler);
|
|
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], XtNallowShellResize, True); num_args++;
|
|
XtSetArg(args[num_args], XtNtitle, "NetHack"); 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("<Message>WM_PROTOCOLS: X11_hangup()"));
|
|
|
|
/* We don't need to realize the top level widget. */
|
|
|
|
old_error_handler = XSetErrorHandler(panic_on_error);
|
|
|
|
/* add new color converter to deal with overused colormaps */
|
|
XtSetTypeConverter(XtRString, XtRPixel, nhCvtStringToPixel,
|
|
(XtConvertArgList) nhcolorConvertArgs,
|
|
XtNumber(nhcolorConvertArgs), XtCacheByDisplay,
|
|
nhFreePixel);
|
|
|
|
/* 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 hints);
|
|
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 */
|
|
plsel_ask_name = FALSE;
|
|
|
|
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(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();
|
|
|
|
x_inited = FALSE;
|
|
}
|
|
|
|
#ifdef X11_HANGUP_SIGNAL
|
|
static void
|
|
X11_sig(int sig) /* Unix signal handler */
|
|
{
|
|
XtNoticeSignal(X11_sig_id);
|
|
hangup(sig);
|
|
}
|
|
|
|
static void
|
|
X11_sig_cb(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
|
|
|
|
/* X11_delay_output ------------------------------------------------------- */
|
|
|
|
/*
|
|
* Timeout callback for X11_delay_output(). Send a fake message to the map
|
|
* window.
|
|
*/
|
|
/* ARGSUSED */
|
|
static void
|
|
d_timeout(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(void)
|
|
{
|
|
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(Widget w, XEvent *event, String *params, Cardinal *num_params)
|
|
{
|
|
nhUse(w);
|
|
nhUse(event);
|
|
nhUse(params);
|
|
nhUse(num_params);
|
|
|
|
#if defined(HANGUPHANDLING)
|
|
hangup(1); /* 1 is commonly SIGHUP, but ignored anyway */
|
|
#endif
|
|
exit_x_event = TRUE;
|
|
}
|
|
|
|
/* X11_bail --------------------------------------------------------------- */
|
|
/* clean up and quit */
|
|
static void
|
|
X11_bail(const char *mesg)
|
|
{
|
|
gp.program_state.something_worth_saving = 0;
|
|
clearlocks();
|
|
X11_exit_nhwindows(mesg);
|
|
nh_terminate(EXIT_SUCCESS);
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/* askname ---------------------------------------------------------------- */
|
|
/* ARGSUSED */
|
|
static void
|
|
askname_delete(Widget w, XEvent *event, String *params, Cardinal *num_params)
|
|
{
|
|
nhUse(event);
|
|
nhUse(params);
|
|
nhUse(num_params);
|
|
|
|
nh_XtPopdown(w);
|
|
(void) strcpy(gp.plname, "Mumbles"); /* give them a name... ;-) */
|
|
exit_x_event = TRUE;
|
|
}
|
|
|
|
/* Callback for askname dialog widget. */
|
|
/* ARGSUSED */
|
|
static void
|
|
askname_done(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 gp.plname - 1)
|
|
len = sizeof gp.plname - 1;
|
|
|
|
(void) strncpy(gp.plname, s, len);
|
|
gp.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(void)
|
|
{
|
|
Widget popup, dialog;
|
|
Arg args[1];
|
|
|
|
#ifdef SELECTSAVED
|
|
if (iflags.wc2_selectsaved && !iflags.renameinprogress)
|
|
switch (restore_menu(WIN_MAP)) {
|
|
case -1: /* quit */
|
|
X11_bail("Until next time then...");
|
|
/*NOTREACHED*/
|
|
case 0: /* no game chosen; start new game */
|
|
break;
|
|
case 1: /* save game selected, plname[] has been set */
|
|
return;
|
|
}
|
|
#else
|
|
nhUse(X11_bail);
|
|
#endif /* SELECTSAVED */
|
|
|
|
if (iflags.wc_player_selection == VIA_DIALOG) {
|
|
/* X11_player_selection_dialog() handles name query */
|
|
plsel_ask_name = TRUE;
|
|
iflags.defer_plname = TRUE;
|
|
return;
|
|
} /* else iflags.wc_player_selection == VIA_PROMPTS */
|
|
|
|
XtSetArg(args[0], XtNallowShellResize, True);
|
|
|
|
popup = XtCreatePopupShell("askname", transientShellWidgetClass, toplevel,
|
|
args, ONE);
|
|
XtOverrideTranslations(popup,
|
|
XtParseTranslationTable("<Message>WM_PROTOCOLS: askname_delete()"));
|
|
|
|
dialog = CreateDialog(popup, nhStr("dialog"), askname_done,
|
|
(XtCallbackProc) 0);
|
|
|
|
SetDialogPrompt(dialog, nhStr("What is your name?")); /* set prompt */
|
|
SetDialogResponse(dialog, gp.plname, 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);
|
|
|
|
/* tty's character selection uses this; we might someday;
|
|
since we let user pick an arbitrary name now, he/she can
|
|
pick another one during role selection */
|
|
iflags.renameallowed = TRUE;
|
|
|
|
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; /* buffer to hold user input; getline's output */
|
|
|
|
/* Callback for getline dialog widget. */
|
|
/* ARGSUSED */
|
|
static void
|
|
done_button(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(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(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(void)
|
|
{
|
|
if (getline_dialog)
|
|
XtDestroyWidget(getline_dialog), getline_dialog = (Widget) 0;
|
|
if (getline_popup)
|
|
XtDestroyWidget(getline_popup), getline_popup = (Widget) 0;
|
|
}
|
|
|
|
/* ask user for a line of text */
|
|
void
|
|
X11_getlin(
|
|
const char *question, /* prompt */
|
|
char *input) /* user's input, getlin's _output_ buffer */
|
|
{
|
|
unsigned upromptlen;
|
|
|
|
getline_input = input; /* used by popup actions */
|
|
|
|
flush_screen(1); /* tell core to make sure that map is up to date */
|
|
if (!getline_popup) {
|
|
Arg args[1];
|
|
|
|
XtSetArg(args[0], XtNallowShellResize, True);
|
|
|
|
getline_popup = XtCreatePopupShell("getline",
|
|
transientShellWidgetClass,
|
|
toplevel, args, ONE);
|
|
XtOverrideTranslations(getline_popup,
|
|
XtParseTranslationTable(
|
|
"<Message>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);
|
|
}
|
|
|
|
/* 64: make the answer widget be wide enough to hold 64 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.] */
|
|
upromptlen = (unsigned) strlen(question);
|
|
if (upromptlen < 64)
|
|
upromptlen = 64;
|
|
#ifdef EDIT_GETLIN
|
|
/* set default answer */
|
|
SetDialogResponse(getline_dialog, input, upromptlen);
|
|
#else
|
|
/* no default answer */
|
|
SetDialogResponse(getline_dialog, nhStr(""), upromptlen);
|
|
#endif
|
|
SetDialogPrompt(getline_dialog, (String) question); /* set prompt */
|
|
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);
|
|
|
|
/* we get here after the popup has exited;
|
|
put prompt and response into the message window (and into
|
|
core's dumplog history) unless play hasn't started yet */
|
|
if (gp.program_state.in_moveloop || gp.program_state.gameover) {
|
|
/* single space has meaning (to remove a previously applied name) so
|
|
show it clearly; don't care about legibility of multiple spaces */
|
|
const char *visanswer = !input[0] ? "<empty>"
|
|
: (input[0] == ' ' && !input[1]) ? "<space>"
|
|
: (input[0] == '\033') ? "<esc>"
|
|
: input;
|
|
int promptlen = (int) strlen(question),
|
|
answerlen = (int) strlen(visanswer);
|
|
|
|
/* prompt should be limited to QBUFSZ-1 by caller; enforce that
|
|
here for the echoed value; answer could be up to BUFSZ-1 long;
|
|
pline() will truncate the whole message to that amount so we
|
|
truncate the answer for display; this only affects the echoed
|
|
response, not the actual response being returned to the core */
|
|
if (promptlen >= QBUFSZ)
|
|
promptlen = QBUFSZ - 1;
|
|
if (answerlen + 1 + promptlen >= BUFSZ) /* +1: separating space */
|
|
answerlen = BUFSZ - (1 + promptlen) - 1;
|
|
pline("%.*s %.*s", promptlen, question, answerlen, visanswer);
|
|
}
|
|
|
|
/* clear static pointer that's about to go stale */
|
|
getline_input = 0;
|
|
}
|
|
|
|
/* Display file ----------------------------------------------------------- */
|
|
|
|
/* uses a menu (with no selectors specified) rather than a text window
|
|
to allow previous_page and first_menu actions to move backwards */
|
|
void
|
|
X11_display_file(const char *str, boolean complain)
|
|
{
|
|
dlb *fp;
|
|
winid newwin;
|
|
struct xwindow *wp;
|
|
anything any;
|
|
menu_item *menu_list;
|
|
#define LLEN 128
|
|
char line[LLEN];
|
|
int clr = 0;
|
|
|
|
/* 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 */
|
|
}
|
|
|
|
newwin = X11_create_nhwindow(NHW_MENU);
|
|
wp = &window_list[newwin];
|
|
X11_start_menu(newwin, MENU_BEHAVE_STANDARD);
|
|
|
|
any = cg.zeroany;
|
|
while (dlb_fgets(line, LLEN, fp)) {
|
|
X11_add_menu(newwin, &nul_glyphinfo, &any, 0, 0, ATR_NONE,
|
|
clr, line, MENU_ITEMFLAGS_NONE);
|
|
}
|
|
(void) dlb_fclose(fp);
|
|
|
|
/* show file name as the window title */
|
|
if (str)
|
|
wp->title = dupstr(str);
|
|
|
|
wp->menu_information->permi = FALSE;
|
|
wp->menu_information->disable_mcolors = TRUE;
|
|
(void) X11_select_menu(newwin, PICK_NONE, &menu_list);
|
|
X11_destroy_nhwindow(newwin);
|
|
}
|
|
|
|
/* 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 function (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 boolean yn_no_default; /* don't convert ESC or quitchars to def */
|
|
static int yn_ndigits; /* digit count */
|
|
static long yn_val; /* accumulated value */
|
|
|
|
static const char yn_translations[] = "#override\n\
|
|
<Key>: 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(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 length 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(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(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_no_default && (ch == '\033' || strchr(yn_quitchars, ch)))) {
|
|
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 (strchr(yn_quitchars, ch)) {
|
|
yn_return = yn_def;
|
|
} else if (strchr(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)) {
|
|
long dgt = (long) (ch - '0');
|
|
|
|
yn_ndigits++;
|
|
/* yn_val = (10 * yn_val) + (ch - '0'); */
|
|
yn_val = AppendLongDigit(yn_val, dgt);
|
|
if (yn_val < 0L) {
|
|
yn_ndigits = 0;
|
|
yn_val = 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(void)
|
|
{
|
|
if (yn_label)
|
|
XtDestroyWidget(yn_label), yn_label = (Widget) 0;
|
|
if (yn_popup)
|
|
XtDestroyWidget(yn_popup), yn_popup = (Widget) 0;
|
|
}
|
|
|
|
/* guts of the X11_yn_function(), not to be confused with core code;
|
|
requires an extra argument: ynflags */
|
|
char
|
|
X11_yn_function_core(
|
|
const char *ques, /* prompt text */
|
|
const char *choices, /* allowed response chars; any char if Null */
|
|
char def, /* default if user hits <space> or <return> */
|
|
unsigned ynflags) /* flags; currently just log-it or not */
|
|
{
|
|
static XFontStruct *yn_font = 0;
|
|
static Dimension yn_minwidth = 0;
|
|
char buf[BUFSZ], buf2[BUFSZ];
|
|
Arg args[4];
|
|
Cardinal num_args;
|
|
boolean suppress_logging = (ynflags & YN_NO_LOGMESG) != 0U,
|
|
no_default_cnvrt = (ynflags & YN_NO_DEFAULT) != 0U;
|
|
|
|
yn_choices = choices; /* set up globals for callback to use */
|
|
yn_def = def;
|
|
yn_no_default = no_default_cnvrt;
|
|
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 <esc> 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 = strchr(choicebuf, '\033')) != 0)
|
|
*cb = '\0';
|
|
/* ques [choices] (def) */
|
|
int ln = ((int) strlen(ques) /* prompt text */
|
|
+ 3 /* " []" */
|
|
+ (int) strlen(choicebuf) /* choices within "[]" */
|
|
+ 4 /* " (c)" */
|
|
+ 1 /* a trailing space */
|
|
+ 1); /* \0 terminator */
|
|
if (ln >= BUFSZ)
|
|
panic("X11_yn_function: question too long (%d)", ln);
|
|
Snprintf(buf, sizeof buf, "%.*s [%s]", QBUFSZ - 1, ques, choicebuf);
|
|
if (def)
|
|
Sprintf(eos(buf), " (%c)", def);
|
|
Strcat(buf, " ");
|
|
|
|
/* escape maps to 'q' or 'n' or default, in that order */
|
|
yn_esc_map = (strchr(choices, 'q') ? 'q'
|
|
: strchr(choices, 'n') ? 'n'
|
|
: def);
|
|
} else {
|
|
int ln = ((int) strlen(ques) /* prompt text */
|
|
+ 1 /* a trailing space */
|
|
+ 1); /* \0 terminator */
|
|
if (ln >= BUFSZ)
|
|
panic("X11_yn_function: question too long (%d)", ln);
|
|
Strcpy(buf, ques);
|
|
Strcat(buf, " ");
|
|
}
|
|
/* for popup-style, add some extra elbow room to the prompt to
|
|
enhance its visibility; there's no cursor shown, just the text */
|
|
if (!appResources.slow) {
|
|
/* insert one leading space and two extra trailing spaces */
|
|
Strcpy(buf2, buf);
|
|
Snprintf(buf, sizeof buf, " %s ", buf2);
|
|
}
|
|
|
|
/*
|
|
* 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' is True: 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 {
|
|
/*
|
|
* 'slow' is False; 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.
|
|
*/
|
|
if (!yn_label) {
|
|
XtSetArg(args[0], XtNallowShellResize, True);
|
|
yn_popup = XtCreatePopupShell("query", transientShellWidgetClass,
|
|
toplevel, args, ONE);
|
|
XtOverrideTranslations(yn_popup,
|
|
XtParseTranslationTable("<Message>WM_PROTOCOLS: yn_delete()"));
|
|
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], nhStr(XtNjustify), XtJustifyLeft);
|
|
num_args++;
|
|
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);
|
|
|
|
/* get font that will be used; we'll need it to measure text */
|
|
(void) memset((genericptr_t) args, 0, sizeof args);
|
|
XtSetArg(args[0], nhStr(XtNfont), &yn_font);
|
|
XtGetValues(yn_label, args, ONE);
|
|
|
|
/* set up minimum yn_label width; we don't actually set
|
|
the XtNminWidth attribute */
|
|
(void) memset(buf2, 'X', 25), buf2[25] = '\0'; /* 25 'X's */
|
|
yn_minwidth = (Dimension) XTextWidth(yn_font, buf2,
|
|
(int) strlen(buf2));
|
|
}
|
|
}
|
|
|
|
/* set the label of the yn widget to be the prompt text */
|
|
(void) memset((genericptr_t) args, 0, sizeof args);
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], XtNlabel, buf); num_args++;
|
|
XtSetValues(yn_label, args, num_args);
|
|
|
|
/* for !slow, pop up the prompt+response widget */
|
|
if (!appResources.slow) {
|
|
/*
|
|
* Setting the text doesn't always perform a resize when it
|
|
* should. We used to just set the text a second time, but that
|
|
* wasn't a reliable workaround, at least on OSX with XQuartz.
|
|
* This seems to work reliably.
|
|
*
|
|
* It also enforces a minimum prompt width, which wasn't being
|
|
* done before, so that really short prompts are more noticeable
|
|
* if they pop up where the pointer is parked and it happens to
|
|
* be sitting somewhere the player isn't looking.
|
|
*/
|
|
Dimension promptwidth, labelwidth = 0;
|
|
|
|
(void) memset((genericptr_t) args, 0, sizeof args);
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], XtNwidth, &labelwidth); num_args++;
|
|
XtGetValues(yn_label, args, num_args);
|
|
|
|
promptwidth = (Dimension) XTextWidth(yn_font, buf, (int) strlen(buf));
|
|
if (labelwidth != promptwidth || labelwidth < yn_minwidth) {
|
|
labelwidth = max(promptwidth, yn_minwidth);
|
|
(void) memset((genericptr_t) args, 0, sizeof args);
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], XtNwidth, labelwidth); 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) */
|
|
|
|
/* erase or remove the prompt */
|
|
if (appResources.slow) {
|
|
(void) memset((genericptr_t) args, 0, sizeof args);
|
|
num_args = 0;
|
|
XtSetArg(args[num_args], XtNlabel, " "); num_args++;
|
|
XtSetValues(yn_label, args, num_args);
|
|
|
|
input_func = 0; /* keystrokes now belong to the map */
|
|
highlight_yn(FALSE); /* disguise yn_label as part of map */
|
|
} else {
|
|
nh_XtPopdown(yn_popup); /* this removes the event grab */
|
|
}
|
|
|
|
if (!suppress_logging) {
|
|
char *p = trimspaces(buf); /* remove !slow's extra whitespace */
|
|
|
|
pline("%s %s", p, (yn_return == '\033') ? "ESC" : visctrl(yn_return));
|
|
}
|
|
|
|
return yn_return;
|
|
}
|
|
|
|
/* X11-specific edition of yn_function(), the routine called by the core
|
|
to show a prompt and get a single key answer, often 'y' vs 'n' */
|
|
char
|
|
X11_yn_function(
|
|
const char *ques, /* prompt text */
|
|
const char *choices, /* allowed response chars; any char if Null */
|
|
char def) /* default if user hits <space> or <return> */
|
|
{
|
|
char res = X11_yn_function_core(ques, choices, def, YN_NORMAL);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* 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(const char *pref)
|
|
{
|
|
if (!strcmp(pref, "tiled_map")) {
|
|
if (WIN_MAP != WIN_ERR)
|
|
display_map_window(&window_list[WIN_MAP]);
|
|
} else if (!strcmp(pref, "perm_invent")) {
|
|
; /* TODO... */
|
|
}
|
|
}
|
|
|
|
/* End global functions =================================================== */
|
|
|
|
/*
|
|
* Before we wait for input via nhgetch() and nh_poskey(), we need to
|
|
* do some pre-processing.
|
|
*/
|
|
static int
|
|
input_event(int exit_condition)
|
|
{
|
|
if (appResources.fancy_status && WIN_STATUS != WIN_ERR)
|
|
check_turn_events(); /* hilighting on the fancy status window */
|
|
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(Widget w, XtPointer data, XEvent *event, Boolean *continue_to_dispatch)
|
|
{
|
|
Cardinal num = 0;
|
|
|
|
nhUse(w);
|
|
nhUse(data);
|
|
nhUse(continue_to_dispatch);
|
|
|
|
map_input(window_list[WIN_MAP].w, event, (String *) 0, &num);
|
|
}
|
|
|
|
/* only called for autofocus */
|
|
/*ARGSUSED*/
|
|
static void
|
|
win_visible(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(boolean init)
|
|
{
|
|
struct xwindow *xmap;
|
|
|
|
if (!appResources.slow || !appResources.highlight_prompt)
|
|
return;
|
|
|
|
/* first time through, WIN_MAP isn't fully initialized 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 ggc = (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), ggc, 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(void)
|
|
{
|
|
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 its 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 its 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.
|
|
*/
|
|
if (appResources.fancy_status)
|
|
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, X11_error_handler);
|
|
|
|
highlight_yn(TRUE); /* switch foreground and background */
|
|
|
|
/* We can now print to the message window. */
|
|
iflags.window_inited = 1;
|
|
}
|
|
|
|
void
|
|
nh_XtPopup(Widget w, /* widget */
|
|
int grb, /* type of grab */
|
|
Widget childwid) /* child to receive focus (can be None) */
|
|
{
|
|
XtPopup(w, (XtGrabKind) grb);
|
|
XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_delete_window, 1);
|
|
if (appResources.autofocus)
|
|
XtSetKeyboardFocus(toplevel, childwid);
|
|
}
|
|
|
|
void
|
|
nh_XtPopdown(Widget w)
|
|
{
|
|
XtPopdown(w);
|
|
if (appResources.autofocus)
|
|
XtSetKeyboardFocus(toplevel, None);
|
|
}
|
|
|
|
void
|
|
win_X11_init(int dir)
|
|
{
|
|
if (dir != WININIT)
|
|
return;
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
find_scrollbars(
|
|
Widget w, /* widget of interest; scroll bars are probably attached
|
|
to its parent or grandparent */
|
|
Widget last_w, /* if non-zero, don't search ancestry beyond this point */
|
|
Widget *horiz, /* output: horizontal scrollbar */
|
|
Widget *vert) /* output: vertical scrollbar */
|
|
{
|
|
*horiz = *vert = (Widget) 0;
|
|
/* for 3.6 this looked for an ancestor with both scrollbars but
|
|
menus might have only vertical */
|
|
if (w) {
|
|
do {
|
|
*horiz = XtNameToWidget(w, "*horizontal");
|
|
*vert = XtNameToWidget(w, "*vertical");
|
|
if (*horiz || *vert)
|
|
break;
|
|
w = XtParent(w);
|
|
} while (w && (!last_w || w != last_w));
|
|
}
|
|
}
|
|
|
|
/* Callback
|
|
* Scroll a viewport, using standard NH 1,2,3,4,6,7,8,9 directions.
|
|
*/
|
|
/*ARGSUSED*/
|
|
void
|
|
nh_keyscroll(Widget viewport, XEvent *event, String *params,
|
|
Cardinal *num_params)
|
|
{
|
|
Arg arg[2];
|
|
Widget horiz_sb = (Widget) 0, vert_sb = (Widget) 0;
|
|
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]);
|
|
|
|
find_scrollbars(viewport, (Widget) 0, &horiz_sb, &vert_sb);
|
|
|
|
#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*/
|