gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
1103 lines
32 KiB
C
1103 lines
32 KiB
C
/* vim:set cin ft=c sw=4 sts=4 ts=8 et ai cino=Ls\:0t0(0 : -*- mode:c;fill-column:80;tab-width:8;c-basic-offset:4;indent-tabs-mode:nil;c-file-style:"k&r" -*-*/
|
|
/* NetHack 3.7 cursmisc.c */
|
|
/* Copyright (c) Karl Garrison, 2010. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "curses.h"
|
|
#include "hack.h"
|
|
#include "wincurs.h"
|
|
#include "cursmisc.h"
|
|
#include "func_tab.h"
|
|
#include "dlb.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
/* Misc. curses interface functions */
|
|
|
|
/* Private declarations */
|
|
|
|
static int curs_x = -1;
|
|
static int curs_y = -1;
|
|
|
|
#if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT)
|
|
static unsigned long last_getch_modifiers = 0L;
|
|
static boolean modifiers_available = TRUE;
|
|
#else
|
|
static boolean modifiers_available = FALSE;
|
|
#endif
|
|
|
|
static int modified(int ch);
|
|
static void update_modifiers(void);
|
|
static int parse_escape_sequence(int, boolean *);
|
|
|
|
#define SS3 M(C('O')) /* 8-bit escape sequence initiator for VT number pad */
|
|
|
|
int
|
|
curses_getch(void)
|
|
{
|
|
int ch;
|
|
|
|
if (iflags.debug_fuzzer)
|
|
ch = randomkey();
|
|
else
|
|
ch = getch();
|
|
return ch;
|
|
}
|
|
|
|
/* Read a character of input from the user */
|
|
|
|
int
|
|
curses_read_char(void)
|
|
{
|
|
int ch;
|
|
|
|
/* cancel message suppression; all messages have had a chance to be read */
|
|
curses_got_input();
|
|
|
|
ch = curses_getch();
|
|
ch = curses_convert_keys(ch);
|
|
|
|
if (ch == 0) {
|
|
ch = '\033'; /* map NUL to ESC since nethack doesn't expect NUL */
|
|
}
|
|
|
|
if (counting && !isdigit(ch)) { /* dismiss count window if necessary */
|
|
curses_count_window(NULL);
|
|
curses_refresh_nethack_windows();
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
/* Turn on or off the specified color and / or attribute */
|
|
|
|
void
|
|
curses_toggle_color_attr(WINDOW *win, int color, int attr, int onoff)
|
|
{
|
|
if (color == NO_COLOR)
|
|
color = NONE;
|
|
|
|
int curses_color;
|
|
boolean use_bold = FALSE;
|
|
|
|
/* if color is disabled, just show attribute */
|
|
if ((win == mapwin) ? !iflags.wc_color
|
|
/* statuswin is for #if STATUS_HILITES
|
|
but doesn't need to be conditional */
|
|
: !(iflags.wc2_guicolor || win == statuswin)) {
|
|
if (attr != NONE) {
|
|
if (onoff == ON)
|
|
wattron(win, attr);
|
|
else
|
|
wattroff(win, attr);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (color == CLR_BLACK) { /* make black fg visible */
|
|
# ifdef USE_DARKGRAY
|
|
if (iflags.wc2_darkgray) {
|
|
if (COLORS > 16) {
|
|
/* colorpair for black is already darkgray */
|
|
} else { /* Use bold for a bright black */
|
|
wattron(win, A_BOLD);
|
|
}
|
|
} else
|
|
# endif/* USE_DARKGRAY */
|
|
color = CLR_BLUE;
|
|
}
|
|
|
|
if (COLORS < 16) {
|
|
/* convert NetHack's 16 colors to 8 colors + BOLD */
|
|
int fg = color % 16;
|
|
int bg = color / 16;
|
|
|
|
if (fg > 7)
|
|
use_bold = TRUE;
|
|
|
|
curses_color = (8 * (bg % 8)) + (fg % 8) + 1;
|
|
} else {
|
|
curses_color = color + 1;
|
|
}
|
|
|
|
if (onoff == ON) { /* Turn on color/attributes */
|
|
if (color != NONE) {
|
|
if (use_bold) {
|
|
wattron(win, A_BOLD);
|
|
}
|
|
wattron(win, COLOR_PAIR(curses_color));
|
|
}
|
|
|
|
if (attr != NONE) {
|
|
wattron(win, attr);
|
|
}
|
|
} else { /* Turn off color/attributes */
|
|
|
|
if (color != NONE) {
|
|
if (use_bold) {
|
|
wattroff(win, A_BOLD);
|
|
}
|
|
# ifdef USE_DARKGRAY
|
|
if ((color == 0) && (COLORS <= 16)) {
|
|
wattroff(win, A_BOLD);
|
|
}
|
|
# else
|
|
if (iflags.use_inverse) {
|
|
wattroff(win, A_REVERSE);
|
|
}
|
|
# endif/* DARKGRAY */
|
|
wattroff(win, COLOR_PAIR(curses_color));
|
|
}
|
|
|
|
if (attr != NONE) {
|
|
wattroff(win, attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* call curses_toggle_color_attr() with 'menucolors' instead of 'guicolor'
|
|
as the control flag */
|
|
|
|
void
|
|
curses_menu_color_attr(WINDOW *win, int color, int attr, int onoff)
|
|
{
|
|
boolean save_guicolor = iflags.wc2_guicolor;
|
|
|
|
/* curses_toggle_color_attr() uses 'guicolor' to decide whether to
|
|
honor specified color, but menu windows have their own
|
|
more-specific control, 'menucolors', so override with that here */
|
|
/* iflags.wc2_guicolor = iflags.use_menu_color; */
|
|
iflags.wc2_guicolor = (color != NONE);
|
|
curses_toggle_color_attr(win, color, attr, onoff);
|
|
iflags.wc2_guicolor = save_guicolor;
|
|
}
|
|
|
|
|
|
/* clean up and quit - taken from tty port */
|
|
|
|
void
|
|
curses_bail(const char *mesg)
|
|
{
|
|
clearlocks();
|
|
curses_exit_nhwindows(mesg);
|
|
nh_terminate(EXIT_SUCCESS);
|
|
}
|
|
|
|
|
|
/* Return a winid for a new window of the given type */
|
|
|
|
winid
|
|
curses_get_wid(int type)
|
|
{
|
|
static winid menu_wid = 20; /* Always even */
|
|
static winid text_wid = 21; /* Always odd */
|
|
winid ret;
|
|
|
|
switch (type) {
|
|
case NHW_MESSAGE:
|
|
return MESSAGE_WIN;
|
|
case NHW_MAP:
|
|
return MAP_WIN;
|
|
case NHW_STATUS:
|
|
return STATUS_WIN;
|
|
case NHW_MENU:
|
|
ret = menu_wid;
|
|
break;
|
|
case NHW_TEXT:
|
|
ret = text_wid;
|
|
break;
|
|
default:
|
|
impossible("curses_get_wid: unsupported window type");
|
|
ret = -1;
|
|
}
|
|
|
|
while (curses_window_exists(ret)) {
|
|
ret += 2;
|
|
if ((ret + 2) > 10000) { /* Avoid "wid2k" problem */
|
|
ret -= 9900;
|
|
}
|
|
}
|
|
|
|
if (type == NHW_MENU) {
|
|
menu_wid += 2;
|
|
} else {
|
|
text_wid += 2;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate a copy of the given string. If null, return a string of
|
|
* zero length.
|
|
*
|
|
* This is taken from copy_of() in tty/wintty.c.
|
|
*/
|
|
|
|
char *
|
|
curses_copy_of(const char *s)
|
|
{
|
|
if (!s)
|
|
s = "";
|
|
return dupstr(s);
|
|
}
|
|
|
|
|
|
/* Determine the number of lines needed for a string for a dialog window
|
|
of the given width */
|
|
|
|
int
|
|
curses_num_lines(const char *str, int width)
|
|
{
|
|
int last_space, count;
|
|
int curline = 1;
|
|
char substr[BUFSZ];
|
|
char tmpstr[BUFSZ];
|
|
|
|
strncpy(substr, str, BUFSZ-1);
|
|
substr[BUFSZ-1] = '\0';
|
|
|
|
while (strlen(substr) > (size_t) width) {
|
|
last_space = 0;
|
|
|
|
for (count = 0; count <= width; count++) {
|
|
if (substr[count] == ' ')
|
|
last_space = count;
|
|
|
|
}
|
|
if (last_space == 0) { /* No spaces found */
|
|
last_space = count - 1;
|
|
}
|
|
for (count = (last_space + 1); count < (int) strlen(substr); count++) {
|
|
tmpstr[count - (last_space + 1)] = substr[count];
|
|
}
|
|
tmpstr[count - (last_space + 1)] = '\0';
|
|
strcpy(substr, tmpstr);
|
|
curline++;
|
|
}
|
|
|
|
return curline;
|
|
}
|
|
|
|
|
|
/* Break string into smaller lines to fit into a dialog window of the
|
|
given width */
|
|
|
|
char *
|
|
curses_break_str(const char *str, int width, int line_num)
|
|
{
|
|
int last_space, count;
|
|
char *retstr;
|
|
int curline = 0;
|
|
int strsize = (int) strlen(str) + 1;
|
|
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) && !defined(_MSC_VER)
|
|
char substr[strsize];
|
|
char curstr[strsize];
|
|
char tmpstr[strsize];
|
|
|
|
strcpy(substr, str);
|
|
#else
|
|
#ifndef BUFSZ
|
|
#define BUFSZ 256
|
|
#endif
|
|
char substr[BUFSZ * 2];
|
|
char curstr[BUFSZ * 2];
|
|
char tmpstr[BUFSZ * 2];
|
|
|
|
if (strsize > (BUFSZ * 2) - 1) {
|
|
paniclog("curses", "curses_break_str() string too long.");
|
|
strncpy(substr, str, (BUFSZ * 2) - 2);
|
|
substr[(BUFSZ * 2) - 1] = '\0';
|
|
} else
|
|
strcpy(substr, str);
|
|
#endif
|
|
|
|
while (curline < line_num) {
|
|
if (strlen(substr) == 0) {
|
|
break;
|
|
}
|
|
curline++;
|
|
last_space = 0;
|
|
for (count = 0; count <= width; count++) {
|
|
if (substr[count] == ' ') {
|
|
last_space = count;
|
|
} else if (substr[count] == '\0') {
|
|
last_space = count;
|
|
break;
|
|
}
|
|
}
|
|
if (last_space == 0) { /* No spaces found */
|
|
last_space = count - 1;
|
|
}
|
|
for (count = 0; count < last_space; count++) {
|
|
curstr[count] = substr[count];
|
|
}
|
|
curstr[count] = '\0';
|
|
if (substr[count] == '\0') {
|
|
break;
|
|
}
|
|
for (count = (last_space + 1); count < (int) strlen(substr); count++) {
|
|
tmpstr[count - (last_space + 1)] = substr[count];
|
|
}
|
|
tmpstr[count - (last_space + 1)] = '\0';
|
|
strcpy(substr, tmpstr);
|
|
}
|
|
|
|
if (curline < line_num) {
|
|
return NULL;
|
|
}
|
|
|
|
retstr = curses_copy_of(curstr);
|
|
|
|
return retstr;
|
|
}
|
|
|
|
|
|
/* Return the remaining portion of a string after hacking-off line_num lines */
|
|
|
|
char *
|
|
curses_str_remainder(const char *str, int width, int line_num)
|
|
{
|
|
int last_space, count;
|
|
char *retstr;
|
|
int curline = 0;
|
|
int strsize = strlen(str) + 1;
|
|
#if (__STDC_VERSION__ >= 199901L) && !defined(_MSC_VER)
|
|
char substr[strsize];
|
|
char tmpstr[strsize];
|
|
|
|
strcpy(substr, str);
|
|
#else
|
|
#ifndef BUFSZ
|
|
#define BUFSZ 256
|
|
#endif
|
|
char substr[BUFSZ * 2];
|
|
char tmpstr[BUFSZ * 2];
|
|
|
|
if (strsize > (BUFSZ * 2) - 1) {
|
|
paniclog("curses", "curses_str_remainder() string too long.");
|
|
strncpy(substr, str, (BUFSZ * 2) - 2);
|
|
substr[(BUFSZ * 2) - 1] = '\0';
|
|
} else
|
|
strcpy(substr, str);
|
|
#endif
|
|
|
|
while (curline < line_num) {
|
|
if (strlen(substr) == 0) {
|
|
break;
|
|
}
|
|
curline++;
|
|
last_space = 0;
|
|
for (count = 0; count <= width; count++) {
|
|
if (substr[count] == ' ') {
|
|
last_space = count;
|
|
} else if (substr[count] == '\0') {
|
|
last_space = count;
|
|
break;
|
|
}
|
|
}
|
|
if (last_space == 0) { /* No spaces found */
|
|
last_space = count - 1;
|
|
}
|
|
assert(IndexOk(last_space, substr));
|
|
if (substr[last_space] == '\0') {
|
|
break;
|
|
}
|
|
for (count = (last_space + 1); count < (int) strlen(substr); count++) {
|
|
tmpstr[count - (last_space + 1)] = substr[count];
|
|
}
|
|
tmpstr[count - (last_space + 1)] = '\0';
|
|
strcpy(substr, tmpstr);
|
|
}
|
|
|
|
if (curline < line_num) {
|
|
return NULL;
|
|
}
|
|
|
|
retstr = curses_copy_of(substr);
|
|
|
|
return retstr;
|
|
}
|
|
|
|
|
|
/* Determine if the given NetHack winid is a menu window */
|
|
|
|
boolean
|
|
curses_is_menu(winid wid)
|
|
{
|
|
if ((wid > 19) && !(wid % 2)) { /* Even number */
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/* Determine if the given NetHack winid is a text window */
|
|
|
|
boolean
|
|
curses_is_text(winid wid)
|
|
{
|
|
if ((wid > 19) && (wid % 2)) { /* Odd number */
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* convert nethack's DECgraphics encoding into curses' ACS encoding */
|
|
int
|
|
curses_convert_glyph(int ch, int glyph)
|
|
{
|
|
/* The DEC line drawing characters use 0x5f through 0x7e instead
|
|
of the much more straightforward 0x60 through 0x7f, possibly
|
|
because 0x7f is effectively a control character (Rubout);
|
|
nethack ORs 0x80 to flag line drawing--that's stripped below */
|
|
static int decchars[33]; /* for chars 0x5f through 0x7f (95..127) */
|
|
|
|
ch &= 0xff; /* 0..255 only */
|
|
if (!(ch & 0x80))
|
|
return ch; /* no conversion needed */
|
|
|
|
/* this conversion routine is only called for SYMHANDLING(H_DEC) and
|
|
we decline to support special graphics symbols on the rogue level */
|
|
if (Is_rogue_level(&u.uz)) {
|
|
/* attempting to use line drawing characters will end up being
|
|
rendered as lowercase gibberish */
|
|
ch &= ~0x80;
|
|
return ch;
|
|
}
|
|
|
|
/*
|
|
* Curses has complete access to all characters that DECgraphics uses.
|
|
* However, their character value isn't consistent between terminals
|
|
* and implementations. For actual DEC terminals and faithful emulators,
|
|
* line-drawing characters are specified as lowercase letters (mostly)
|
|
* and a control code is sent to the terminal telling it to switch
|
|
* character sets (that's how the tty interface handles them).
|
|
* Curses remaps the characters instead.
|
|
*/
|
|
|
|
/* one-time initialization; some ACS_x aren't compile-time constant */
|
|
if (!decchars[0]) {
|
|
/* [0] is non-breakable space; irrelevant to nethack */
|
|
decchars[0x5f - 0x5f] = ' '; /* NBSP */
|
|
decchars[0x60 - 0x5f] = ACS_DIAMOND; /* [1] solid diamond */
|
|
decchars[0x61 - 0x5f] = ACS_CKBOARD; /* [2] checkerboard */
|
|
/* several "line drawing" characters are two-letter glyphs
|
|
which could be substituted for invisible control codes;
|
|
nethack's DECgraphics doesn't use any of them so we're
|
|
satisfied with conversion to a simple letter;
|
|
[3] "HT" as one char, with small raised upper case H over
|
|
and/or preceding small lowered upper case T */
|
|
decchars[0x62 - 0x5f] = 'H'; /* "HT" (horizontal tab) */
|
|
decchars[0x63 - 0x5f] = 'F'; /* "FF" as one char (form feed) */
|
|
decchars[0x64 - 0x5f] = 'C'; /* "CR" as one (carriage return) */
|
|
decchars[0x65 - 0x5f] = 'L'; /* [6] "LF" as one (line feed) */
|
|
decchars[0x66 - 0x5f] = ACS_DEGREE; /* small raised circle */
|
|
/* [8] plus or minus sign, '+' with horizontal line below */
|
|
decchars[0x67 - 0x5f] = ACS_PLMINUS;
|
|
decchars[0x68 - 0x5f] = 'N'; /* [9] "NL" as one char (new line) */
|
|
decchars[0x69 - 0x5f] = 'V'; /* [10] "VT" as one (vertical tab) */
|
|
decchars[0x6a - 0x5f] = ACS_LRCORNER; /* lower right corner */
|
|
decchars[0x6b - 0x5f] = ACS_URCORNER; /* upper right corner, 7-ish */
|
|
decchars[0x6c - 0x5f] = ACS_ULCORNER; /* upper left corner */
|
|
decchars[0x6d - 0x5f] = ACS_LLCORNER; /* lower left corner, 'L' */
|
|
/* [15] center cross, like big '+' sign */
|
|
decchars[0x6e - 0x5f] = ACS_PLUS;
|
|
decchars[0x6f - 0x5f] = ACS_S1; /* very high horizontal line */
|
|
decchars[0x70 - 0x5f] = ACS_S3; /* medium high horizontal line */
|
|
decchars[0x71 - 0x5f] = ACS_HLINE; /* centered horizontal line */
|
|
decchars[0x72 - 0x5f] = ACS_S7; /* medium low horizontal line */
|
|
decchars[0x73 - 0x5f] = ACS_S9; /* very low horizontal line */
|
|
/* [21] left tee, 'H' with right-hand vertical stroke removed;
|
|
note on left vs right: the ACS name (also DEC's terminal
|
|
documentation) refers to vertical bar rather than cross stroke,
|
|
nethack's left/right refers to direction of the cross stroke */
|
|
decchars[0x74 - 0x5f] = ACS_LTEE; /* ACS left tee, NH right tee */
|
|
/* [22] right tee, 'H' with left-hand vertical stroke removed */
|
|
decchars[0x75 - 0x5f] = ACS_RTEE; /* ACS right tee, NH left tee */
|
|
/* [23] bottom tee, '+' with lower half of vertical stroke
|
|
removed and remaining stroke pointed up (unside-down 'T');
|
|
nethack is inconsistent here--unlike with left/right, its
|
|
bottom/top directions agree with ACS */
|
|
decchars[0x76 - 0x5f] = ACS_BTEE; /* bottom tee, stroke up */
|
|
/* [24] top tee, '+' with upper half of vertical stroke removed */
|
|
decchars[0x77 - 0x5f] = ACS_TTEE; /* top tee, stroke down, 'T' */
|
|
decchars[0x78 - 0x5f] = ACS_VLINE; /* centered vertical line */
|
|
decchars[0x79 - 0x5f] = ACS_LEQUAL; /* less than or equal to */
|
|
/* [27] greater than or equal to, '>' with underscore */
|
|
decchars[0x7a - 0x5f] = ACS_GEQUAL;
|
|
/* [28] Greek pi ('n'-like; case is ambiguous: small size
|
|
suggests lower case but flat top suggests upper case) */
|
|
decchars[0x7b - 0x5f] = ACS_PI;
|
|
/* [29] not equal sign, combination of '=' and '/' */
|
|
decchars[0x7c - 0x5f] = ACS_NEQUAL;
|
|
/* [30] British pound sign (curly 'L' with embellishments) */
|
|
decchars[0x7d - 0x5f] = ACS_STERLING;
|
|
decchars[0x7e - 0x5f] = ACS_BULLET; /* [31] centered dot */
|
|
/* [32] is not used for DEC line drawing but is a potential
|
|
value for someone who assumes that 0x60..0x7f is the valid
|
|
range, so we're prepared to accept--and sanitize--it */
|
|
decchars[0x7f - 0x5f] = '?';
|
|
}
|
|
|
|
/* high bit set means special handling */
|
|
if (ch & 0x80) {
|
|
int convindx, symbol;
|
|
|
|
ch &= ~0x80; /* force plain ASCII for last resort */
|
|
convindx = ch - 0x5f;
|
|
/* if it's in the lower case block of ASCII (which includes
|
|
a few punctuation characters), use the conversion table */
|
|
if (convindx >= 0 && convindx < SIZE(decchars)) {
|
|
ch = decchars[convindx];
|
|
/* in case ACS_foo maps to 0 when current terminal is unable
|
|
to handle a particular character; if so, revert to default
|
|
rather than using DECgr value with high bit stripped */
|
|
if (!ch) {
|
|
symbol = glyph_to_cmap(glyph);
|
|
ch = (int) defsyms[symbol].sym;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
|
|
/* Move text cursor to specified coordinates in the given NetHack window */
|
|
|
|
void
|
|
curses_move_cursor(winid wid, int x, int y)
|
|
{
|
|
int sx, sy, ex, ey;
|
|
int xadj = 0;
|
|
int yadj = 0;
|
|
|
|
#ifndef PDCURSES
|
|
WINDOW *win = curses_get_nhwin(MAP_WIN);
|
|
#endif
|
|
|
|
if (wid != MAP_WIN) {
|
|
return;
|
|
}
|
|
#ifdef PDCURSES
|
|
/* PDCurses seems to not handle wmove correctly, so we use move and
|
|
physical screen coordinates instead */
|
|
curses_get_window_xy(wid, &xadj, &yadj);
|
|
#endif
|
|
curs_x = x + xadj;
|
|
curs_y = y + yadj;
|
|
curses_map_borders(&sx, &sy, &ex, &ey, x, y);
|
|
|
|
if (curses_window_has_border(wid)) {
|
|
curs_x++;
|
|
curs_y++;
|
|
}
|
|
|
|
if (x >= sx && x <= ex && y >= sy && y <= ey) {
|
|
/* map column #0 isn't used; shift column #1 to first screen column */
|
|
curs_x -= (sx + 1);
|
|
curs_y -= sy;
|
|
#ifdef PDCURSES
|
|
move(curs_y, curs_x);
|
|
#else
|
|
wmove(win, curs_y, curs_x);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* update the ncurses stdscr cursor to where the cursor in our map is */
|
|
void
|
|
curses_update_stdscr_cursor(void)
|
|
{
|
|
#ifndef PDCURSES
|
|
int xadj = 0, yadj = 0;
|
|
|
|
curses_get_window_xy(MAP_WIN, &xadj, &yadj);
|
|
move(curs_y + yadj, curs_x + xadj);
|
|
#endif
|
|
}
|
|
|
|
/* Perform actions that should be done every turn before nhgetch() */
|
|
|
|
void
|
|
curses_prehousekeeping(void)
|
|
{
|
|
#ifndef PDCURSES
|
|
WINDOW *win = curses_get_nhwin(MAP_WIN);
|
|
#endif /* PDCURSES */
|
|
|
|
if ((curs_x > -1) && (curs_y > -1)) {
|
|
curs_set(1);
|
|
#ifdef PDCURSES
|
|
/* PDCurses seems to not handle wmove correctly, so we use move
|
|
and physical screen coordinates instead */
|
|
move(curs_y, curs_x);
|
|
#else
|
|
wmove(win, curs_y, curs_x);
|
|
#endif /* PDCURSES */
|
|
curses_refresh_nhwin(MAP_WIN);
|
|
}
|
|
}
|
|
|
|
|
|
/* Perform actions that should be done every turn after nhgetch() */
|
|
|
|
void
|
|
curses_posthousekeeping(void)
|
|
{
|
|
curs_set(0);
|
|
/* curses_decrement_highlights(FALSE); */
|
|
curses_clear_unhighlight_message_window();
|
|
}
|
|
|
|
|
|
void
|
|
curses_view_file(const char *filename, boolean must_exist)
|
|
{
|
|
winid wid;
|
|
anything Id;
|
|
char buf[BUFSZ];
|
|
menu_item *selected = NULL;
|
|
dlb *fp = dlb_fopen(filename, "r");
|
|
int clr = NO_COLOR;
|
|
|
|
if (fp == NULL) {
|
|
if (must_exist)
|
|
pline("Cannot open \"%s\" for reading!", filename);
|
|
return;
|
|
}
|
|
|
|
wid = curses_get_wid(NHW_MENU);
|
|
curses_create_nhmenu(wid, 0UL);
|
|
Id = cg.zeroany;
|
|
|
|
while (dlb_fgets(buf, BUFSZ, fp) != NULL) {
|
|
curses_add_menu(wid, &nul_glyphinfo, &Id, 0, 0,
|
|
A_NORMAL, clr, buf, FALSE);
|
|
}
|
|
|
|
dlb_fclose(fp);
|
|
curses_end_menu(wid, "");
|
|
curses_select_menu(wid, PICK_NONE, &selected);
|
|
curses_del_wid(wid);
|
|
}
|
|
|
|
|
|
void
|
|
curses_rtrim(char *str)
|
|
{
|
|
char *s;
|
|
|
|
for (s = str; *s != '\0'; ++s);
|
|
if (s > str)
|
|
for (--s; isspace(*s) && s > str; --s);
|
|
if (s == str)
|
|
*s = '\0';
|
|
else
|
|
*(++s) = '\0';
|
|
}
|
|
|
|
|
|
/* Read numbers until non-digit is encountered, and return number
|
|
in int form. */
|
|
|
|
long
|
|
curses_get_count(int first_digit)
|
|
{
|
|
int current_char;
|
|
long current_count = 0L;
|
|
|
|
/* use core's count routine; we have the first digit; if any more
|
|
are typed, get_count() will send "Count:123" to the message window;
|
|
curses's message window will display that in count window instead */
|
|
current_char = get_count(NULL, (char) first_digit,
|
|
/* 0L => no limit on value unless it wraps
|
|
* to negative */
|
|
0L, ¤t_count,
|
|
/* don't put into message history, echo full
|
|
* number rather than waiting until 2nd digit */
|
|
GC_ECHOFIRST);
|
|
|
|
ungetch(current_char);
|
|
if (current_char == '\033') { /* Cancelled with escape */
|
|
current_count = -1;
|
|
}
|
|
return current_count;
|
|
}
|
|
|
|
|
|
/* Convert the given NetHack text attributes into the format curses
|
|
understands, and return that format mask. */
|
|
|
|
attr_t
|
|
curses_convert_attr(int attr)
|
|
{
|
|
attr_t curses_attr;
|
|
|
|
/* first, strip off control flags masked onto the display attributes
|
|
(caller should have already done this...) */
|
|
attr &= ~(ATR_URGENT | ATR_NOHISTORY);
|
|
|
|
switch (attr) {
|
|
case ATR_NONE:
|
|
curses_attr = A_NORMAL;
|
|
break;
|
|
case ATR_ULINE:
|
|
curses_attr = A_UNDERLINE;
|
|
break;
|
|
case ATR_BOLD:
|
|
curses_attr = A_BOLD;
|
|
break;
|
|
case ATR_DIM:
|
|
curses_attr = A_DIM;
|
|
break;
|
|
case ATR_BLINK:
|
|
curses_attr = A_BLINK;
|
|
break;
|
|
case ATR_INVERSE:
|
|
curses_attr = A_REVERSE;
|
|
break;
|
|
case ATR_ITALIC:
|
|
curses_attr = A_ITALIC;
|
|
break;
|
|
default:
|
|
curses_attr = A_NORMAL;
|
|
}
|
|
|
|
return curses_attr;
|
|
}
|
|
|
|
/* Convert special keys into values that NetHack can understand.
|
|
Currently this is limited to arrow keys, but this may be expanded. */
|
|
int
|
|
curses_convert_keys(int key)
|
|
{
|
|
boolean reject = (program_state.input_state == otherInp),
|
|
as_is = FALSE, numpad_esc = FALSE;
|
|
int ret = key;
|
|
|
|
if (modifiers_available)
|
|
update_modifiers();
|
|
|
|
/* Handle arrow and keypad keys, but only when getting a command
|
|
(or a command-like keystroke for getpos() or getdir()). */
|
|
switch (key) {
|
|
case SS3: /* M-^O, 8-bit version of ESC 'O' c for keypad key */
|
|
case '\033': /* ESC or ^[ */
|
|
/* changes ESC c to M-c or number pad key to corresponding digit
|
|
(but we only get here via key==ESC if curses' getch() didn't
|
|
change the latter to KEY_xyz) */
|
|
ret = parse_escape_sequence(key, &numpad_esc);
|
|
reject = ((uchar) ret < 1 || ret > 255);
|
|
as_is = !numpad_esc; /* don't perform phonepad inversion */
|
|
break;
|
|
case KEY_BACKSPACE:
|
|
/* we can't distinguish between a separate backspace key and
|
|
explicit Ctrl+H intended to rush to the left; without this,
|
|
a value for ^H greater than 255 is passed back to core's
|
|
readchar() and stripping the value down to 0..255 yields ^G! */
|
|
ret = C('H');
|
|
FALLTHROUGH;
|
|
/*FALLTHRU*/
|
|
default:
|
|
if (modifiers_available)
|
|
ret = modified(ret);
|
|
#if defined(ALT_A) && defined(ALT_Z)
|
|
/* for PDcurses, but doesn't handle Alt+X for upper case X;
|
|
ncurses doesn't have ALT_x definitions so we achieve a similar
|
|
effect via parse_escape_sequence(), and that works for upper
|
|
case and other non-letter, non-digit keys */
|
|
if (ret >= ALT_A && ret <= ALT_Z) {
|
|
ret = (ret - ALT_A) + 'a';
|
|
ret = M(ret);
|
|
}
|
|
#endif
|
|
#if defined(ALT_0) && defined(ALT_9)
|
|
if (ret >= ALT_0 && ret <= ALT_9) {
|
|
ret = (ret - ALT_0) + '0';
|
|
ret = M(ret);
|
|
}
|
|
#endif
|
|
/* use key as-is unless it's out of normal char range */
|
|
reject = ((uchar) ret < 1 || ret > 255);
|
|
as_is = TRUE;
|
|
break;
|
|
#ifdef KEY_B1
|
|
case KEY_B1:
|
|
#endif
|
|
case KEY_LEFT:
|
|
ret = iflags.num_pad ? '4' : 'h';
|
|
break;
|
|
#ifdef KEY_B3
|
|
case KEY_B3:
|
|
#endif
|
|
case KEY_RIGHT:
|
|
ret = iflags.num_pad ? '6' : 'l';
|
|
break;
|
|
#ifdef KEY_A2
|
|
case KEY_A2:
|
|
#endif
|
|
case KEY_UP:
|
|
ret = iflags.num_pad ? '8' : 'k';
|
|
break;
|
|
#ifdef KEY_C2
|
|
case KEY_C2:
|
|
#endif
|
|
case KEY_DOWN:
|
|
ret = iflags.num_pad ? '2' : 'j';
|
|
break;
|
|
#ifdef KEY_A1
|
|
case KEY_A1:
|
|
#endif
|
|
case KEY_HOME:
|
|
ret = iflags.num_pad ? '7' : (!gc.Cmd.swap_yz ? 'y' : 'z');
|
|
break;
|
|
#ifdef KEY_A3
|
|
case KEY_A3:
|
|
#endif
|
|
case KEY_PPAGE:
|
|
ret = iflags.num_pad ? '9' : 'u';
|
|
break;
|
|
#ifdef KEY_C1
|
|
case KEY_C1:
|
|
#endif
|
|
case KEY_END:
|
|
ret = iflags.num_pad ? '1' : 'b';
|
|
break;
|
|
#ifdef KEY_C3
|
|
case KEY_C3:
|
|
#endif
|
|
case KEY_NPAGE:
|
|
ret = iflags.num_pad ? '3' : 'n';
|
|
break;
|
|
#ifdef KEY_B2
|
|
case KEY_B2:
|
|
ret = iflags.num_pad ? '5' : 'g';
|
|
break;
|
|
#endif /* KEY_B2 */
|
|
#ifdef KEY_RESIZE
|
|
case KEY_RESIZE:
|
|
/* actual resize is handled elsewhere; just avoid beep/bell here */
|
|
ret = '\033'; /* was C('R'); -- nethack's redraw command */
|
|
reject = FALSE;
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
/* phone layout is inverted, 123 on top and 789 on bottom; if player has
|
|
set num_pad to deal with that, we need to invert here too but only
|
|
when some key has been converted into a digit, not for actual digit */
|
|
if (iflags.num_pad && (iflags.num_pad_mode & 2) != 0 && !as_is) {
|
|
if (ret >= '1' && ret <= '3')
|
|
ret += 6; /* 1,2,3 -> 7,8,9 */
|
|
else if (ret >= '7' && ret <= '9')
|
|
ret -= 6; /* 7,8,9 -> 1,2,3 */
|
|
}
|
|
|
|
if (reject) {
|
|
/* an arrow or function key has been pressed during text entry */
|
|
curses_nhbell(); /* calls beep() which might cause unwanted screen
|
|
* refresh if terminal is set for 'visible bell' */
|
|
ret = '\033'; /* ESC */
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* We treat buttons 2 and 3 as equivalent so that it doesn't matter which
|
|
* one is for right-click and which for middle-click. The core uses CLICK_2
|
|
* for right-click ("not left"-click) even though 2 might be middle button.
|
|
*
|
|
* BUTTON_CTRL was enabled at one point but was not working as intended.
|
|
* Ctrl+left_click was generating pairs of duplicated events with Ctrl and
|
|
* Report_mouse_position bits set (even though Report_mouse_position wasn't
|
|
* enabled) but no button click bit set. (It sort of worked because Ctrl+
|
|
* Report_mouse_position wasn't a left click so passed along CLICK_2, but
|
|
* the duplication made that too annoying to use. Attempting to immediately
|
|
* drain the second one wasn't working as intended either.)
|
|
*/
|
|
#define MOUSEBUTTONS (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)
|
|
|
|
/* Process mouse events. Mouse movement is processed until no further
|
|
mouse movement events are available. Returns 0 for a mouse click
|
|
event, or the first non-mouse key event in the case of mouse
|
|
movement. */
|
|
|
|
int
|
|
curses_get_mouse(coordxy *mousex, coordxy *mousey, int *mod)
|
|
{
|
|
int key = '\033';
|
|
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
MEVENT event;
|
|
|
|
if (getmouse(&event) == OK) { /* True if user has clicked */
|
|
if ((event.bstate & MOUSEBUTTONS) != 0) {
|
|
/*
|
|
* The ncurses man page documents wmouse_trafo() incorrectly.
|
|
* It says that last argument 'TRUE' translates from screen
|
|
* to window and 'FALSE' translates from window to screen,
|
|
* but those are backwards. The mouse_trafo() macro calls
|
|
* last argument 'to_screen', suggesting that the backwards
|
|
* implementation is the intended behavior and the man page
|
|
* is describing it wrong.
|
|
*/
|
|
/* See if coords are in map window & convert coords */
|
|
if (wmouse_trafo(mapwin, &event.y, &event.x, FALSE)) {
|
|
key = '\0'; /* core uses this to detect a mouse click */
|
|
*mousex = event.x + 1; /* +1: screen 0..78 is map 1..79 */
|
|
*mousey = event.y;
|
|
|
|
if (curses_window_has_border(MAP_WIN)) {
|
|
(*mousex)--;
|
|
(*mousey)--;
|
|
}
|
|
|
|
*mod = ((event.bstate & (BUTTON1_CLICKED | BUTTON_CTRL))
|
|
== BUTTON1_CLICKED) ? CLICK_1 : CLICK_2;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
nhUse(mousex);
|
|
nhUse(mousey);
|
|
nhUse(mod);
|
|
#endif /* NCURSES_MOUSE_VERSION */
|
|
|
|
return key;
|
|
}
|
|
|
|
void
|
|
curses_mouse_support(int mode) /* 0: off, 1: on, 2: alternate on */
|
|
{
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
mmask_t result, oldmask, newmask;
|
|
|
|
if (!mode)
|
|
newmask = 0;
|
|
else
|
|
newmask = MOUSEBUTTONS; /* buttons 1, 2, and 3 */
|
|
|
|
result = mousemask(newmask, &oldmask);
|
|
nhUse(result);
|
|
#else
|
|
nhUse(mode);
|
|
#endif
|
|
}
|
|
|
|
/* caller just got an input character of ESC or M-^O;
|
|
note: curses converts a lot of escape sequences to single values greater
|
|
than 255 and those won't look like ESC to caller so won't get here */
|
|
static int
|
|
parse_escape_sequence(int key, boolean *keypadnum)
|
|
{
|
|
#ifndef PDCURSES
|
|
int ret, keypadother = 0;
|
|
|
|
*keypadnum = FALSE;
|
|
|
|
timeout(10);
|
|
ret = getch();
|
|
|
|
if (ret == 'O' || key == SS3) { /* handle numeric keypad */
|
|
/*
|
|
* ESC O <pending> or M-^O <something|nothing>.
|
|
*
|
|
* For the former, we don't have the next char yet so get it now.
|
|
* If there isn't one, treat ESC O as if user typed M-O (which
|
|
* is probably the case, via alt+shift+O combo sending two char
|
|
* "ESC O").
|
|
*
|
|
* For the latter, it there wasn't another char then 'ret' will
|
|
* be ERR and we'll treat the result as M-^O. However, if there
|
|
* is another char and it is O meant as two characters "M-^O O"
|
|
* we'll be fooled, but that's not a valid escape sequence so
|
|
* don't worry about those two characters arriving together.
|
|
*/
|
|
if (key == '\033')
|
|
ret = getch();
|
|
|
|
if (ret == ERR) {
|
|
/* there was no additional char; treat as M-O or M-^O below */
|
|
ret = (key == '\033') ? 'O' : C('O');
|
|
} else if (ret >= 112 && ret <= 121) { /* 'p'..'y' */
|
|
*keypadnum = TRUE; /* convert 'p'..'y' to '0'..'9' below */
|
|
} else if (ret >= 108 && ret <= 110) { /* 'l'..'n' */
|
|
keypadother = 1; /* convert 'l','m','n' to ',','.','-' below */
|
|
} else if (ret == 'M') {
|
|
keypadother = 2; /* convert "ESC O M" or "SS3 M" to ^M */
|
|
}
|
|
}
|
|
|
|
timeout(-1); /* reset to 'wait unlimited time for next input' */
|
|
|
|
if (*keypadnum) {
|
|
/* 'p' -> '0', ..., 'y' -> '9' */
|
|
ret -= ('p' - '0'); /* Convert c from 'ESC O c' to digit */
|
|
} else if (keypadother > 0) {
|
|
/* conversion for VT keypad keys (no plus; ignore PF1 through PF4)
|
|
[typical PC keyboard has period and plus, no comma or minus] */
|
|
if (keypadother == 1)
|
|
ret -= ('l' - ','); /* keypad comma, period, or minus */
|
|
else
|
|
ret = C('M'); /* keypad <enter> */
|
|
} else if (ret != ERR && ret <= 255) {
|
|
/* ESC <something>; effectively 'altmeta' behind player's back */
|
|
ret = M(ret); /* Meta key support for most terminals */
|
|
} else {
|
|
ret = '\033'; /* Just an escape character */
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
nhUse(key);
|
|
nhUse(keypadnum);
|
|
return '\033';
|
|
#endif /* !PDCURSES */
|
|
}
|
|
|
|
#undef SS3
|
|
|
|
/* update_modifiers() and modified() will never be
|
|
called if modifiers_available is FALSE */
|
|
|
|
static void
|
|
update_modifiers(void)
|
|
{
|
|
#if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT)
|
|
last_getch_modifiers = PDC_get_key_modifiers();
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
modified(int ch)
|
|
{
|
|
int ret_ch = ch;
|
|
|
|
#if defined(PDCURSES) && defined(PDC_KEY_MODIFIER_ALT)
|
|
/* PDCurses key modifier masks:
|
|
* PDC_KEY_MODIFIER_SHIFT = 1
|
|
* PDC_KEY_MODIFIER_CONTROL = 2
|
|
* PDC_KEY_MODIFIER_ALT = 4
|
|
* PDC_KEY_MODIFIER_NUMLOCK = 8
|
|
* PDC_KEY_MODIFIER_REPEAT = 16
|
|
* ALT + 'a' through ALT + 'z' returns ALT_A through ALT_Z
|
|
* and those are out of the normal character range and
|
|
* code in curses_convert_keys() handles those.
|
|
* ALT + 'A' through ALT + 'Z' return normal 'A' through 'Z'
|
|
* so we check the modifier here.
|
|
*/
|
|
if (((last_getch_modifiers & PDC_KEY_MODIFIER_ALT) == PDC_KEY_MODIFIER_ALT)
|
|
&& (ch >= 'A' && ch <= 'Z'))
|
|
ret_ch = M(ch);
|
|
#endif
|
|
return ret_ch;
|
|
}
|
|
|
|
/*cursmisc.c*/
|