Files
nethack/win/curses/cursmisc.c
nhmall 0792e5fe9e expand implicit fallthrough detection to non-gcc compilers
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.
2024-11-30 14:16:27 -05:00

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, &current_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*/