address github issue #987 - curses: arrow keys

Issue reported by jeremyhetzler:  left and right arrows produced
unexpected characters when trying to use them to edit text that is
being entered.

The curses interface converts arrow keys and function keys related
to the keypad into movement keys (hjkl or 4286 depending on the
number_pad setting).  But it was doing that all the time, not just
when nethack wanted movement keys.  This extends the existing
program_state.getting_a_command flag to getdir() so that it can be
used for controlling that in addition to 'altmeta' support.

Typing an arrow when interacting with the map (actual command or
getpos, now getdir too) will still work.  Typing one when making a
wish or naming a pet will issue a beep and be treated as if '\0' had
been typed, and that normally gets treated as if ESC had been typed.
[Possible room for improvement there.  Losing the whole text when
trying to back up a character feels a bit harsh.]

Treating left arrow as escape rather than as h or 4 will probably be
enough to train players not to try to edit text with it after they
get burned by that a time or two.

Bonus fix:  curses' keystroke conversion only supported traditional
number_pad behavior, not the inverted phone number pad layout.

Closes #987
This commit is contained in:
PatR
2023-02-23 14:02:28 -08:00
parent 4ce149b2cf
commit d75beae272
3 changed files with 47 additions and 54 deletions

View File

@@ -409,7 +409,6 @@ spells that require a target spot rather than a direction (like skilled
prevent wish request "death wand" from matching Death monster and producing a
random wand instead of a wand of death
grammar bit: "you hear a [AEF] note squeak in the distance" (should be "an")
curses interface failed to honor menu_xxx option settings for menu interaction
during engraving, spaces were counted instead of non-spaces [later: affected
code is gone, removed when engraving was converted into an occupation]
when an explosion scatters objects, make any that fly over sinks stop there
@@ -1586,6 +1585,7 @@ curses: sometimes entering a count during menu selection caused the menu to
more digits typed followed by non-digit); in-out menu was still active
but no longer displayed
curses: support backspace/delete when entering a count during menu selection
curses: was failing to honor menu_xxx option settings for menu interaction
curses: make extended command prompt behave more sensibly
curses: if a menu of objects contains at least one iron ball, and player is
not already in the midst of entering a count, recognize '0' as a
@@ -1593,6 +1593,9 @@ curses: if a menu of objects contains at least one iron ball, and player is
curses: obey timed_delay option
curses: support tty-style extended role/race/&c selection at start of new game
curses: implement mark_synch() and wait_synch()
curses: only convert arrow keys to hjkl or 4286 if nethack wants a direction
curses: conversion of arrows and other numpad-related function keys to digit
ignored phone-layout setting (inverted up/down) of the num_pad option
macOS: Xcode project was failing to build if the path to the NetHack source
tree contained a space; the issue was within some shell script code
contained within the project

View File

@@ -5169,6 +5169,7 @@ getdir(const char *s)
}
retry:
gp.program_state.getting_a_command = 1; /* arrow key support for curses */
if (gi.in_doagain || *readchar_queue)
dirsym = readchar();
else
@@ -6199,7 +6200,7 @@ get_count(
inkey = '\0';
} else {
gp.program_state.getting_a_command = 1; /* readchar altmeta
* compatibility */
* compatibility */
key = readchar();
}
@@ -6260,8 +6261,8 @@ parse(void)
flush_screen(1); /* Flush screen buffer. Put the cursor on the hero. */
gp.program_state.getting_a_command = 1; /* affects readchar() behavior for
* ESC iff 'altmeta' option is On;
* reset to 0 by readchar() */
* ESC iff 'altmeta' option is On;
* reset to 0 by readchar() */
if (!gc.Cmd.num_pad || (foo = readchar()) == gc.Cmd.spkeys[NHKF_COUNT]) {
foo = get_count((char *) 0, '\0', LARGEST_INT,
&gc.command_count, GC_NOFLAGS);
@@ -6405,8 +6406,8 @@ readchar_core(coordxy *x, coordxy *y, int *mod)
click_to_cmd(*x, *y, *mod);
}
gp.program_state.getting_a_command = 0; /* next readchar() will be for an
* ordinary char unless parse()
* sets this back to 1 */
* ordinary char unless parse()
* sets this back to 1 */
return (char) sym;
}

View File

@@ -863,13 +863,16 @@ Currently this is limited to arrow keys, but this may be expanded. */
int
curses_convert_keys(int key)
{
boolean reject = !gp.program_state.getting_a_command,
as_is = FALSE;
int ret = key;
if (ret == '\033') {
ret = parse_escape_sequence();
}
/* Handle arrow keys */
/* Handle arrow and keypad keys, but only when getting a command
(or a command-like keystroke for getpos() or getdir()). */
switch (key) {
case KEY_BACKSPACE:
/* we can't distinguish between a separate backspace key and
@@ -877,98 +880,84 @@ curses_convert_keys(int key)
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');
/*FALLTHRU*/
default:
/* 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:
if (iflags.num_pad) {
ret = '4';
} else {
ret = 'h';
}
ret = iflags.num_pad ? '4' : 'h';
break;
#ifdef KEY_B3
case KEY_B3:
#endif
case KEY_RIGHT:
if (iflags.num_pad) {
ret = '6';
} else {
ret = 'l';
}
ret = iflags.num_pad ? '6' : 'l';
break;
#ifdef KEY_A2
case KEY_A2:
#endif
case KEY_UP:
if (iflags.num_pad) {
ret = '8';
} else {
ret = 'k';
}
ret = iflags.num_pad ? '8' : 'k';
break;
#ifdef KEY_C2
case KEY_C2:
#endif
case KEY_DOWN:
if (iflags.num_pad) {
ret = '2';
} else {
ret = 'j';
}
ret = iflags.num_pad ? '2' : 'j';
break;
#ifdef KEY_A1
case KEY_A1:
#endif
case KEY_HOME:
if (iflags.num_pad) {
ret = '7';
} else {
ret = !gc.Cmd.swap_yz ? 'y' : 'z';
}
ret = iflags.num_pad ? '7' : (!gc.Cmd.swap_yz ? 'y' : 'z');
break;
#ifdef KEY_A3
case KEY_A3:
#endif
case KEY_PPAGE:
if (iflags.num_pad) {
ret = '9';
} else {
ret = 'u';
}
ret = iflags.num_pad ? '9' : 'u';
break;
#ifdef KEY_C1
case KEY_C1:
#endif
case KEY_END:
if (iflags.num_pad) {
ret = '1';
} else {
ret = 'b';
}
ret = iflags.num_pad ? '1' : 'b';
break;
#ifdef KEY_C3
case KEY_C3:
#endif
case KEY_NPAGE:
if (iflags.num_pad) {
ret = '3';
} else {
ret = 'n';
}
ret = iflags.num_pad ? '3' : 'n';
break;
#ifdef KEY_B2
case KEY_B2:
if (iflags.num_pad) {
ret = '5';
} else {
ret = 'g';
}
ret = iflags.num_pad ? '5' : 'g';
break;
#endif /* KEY_B2 */
}
/* 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 */
beep(); /* not curses_nhbell() because it might end up getting
* changed to deliver a sound effect */
ret = 0; /* this ends up behaving like <escape> */
}
return ret;
}
@@ -1065,11 +1054,11 @@ parse_escape_sequence(void)
ret = getch();
if (ret != ERR) { /* Likely an escape sequence */
if (((ret >= 'a') && (ret <= 'z')) || ((ret >= '0') && (ret <= '9'))) {
if ((ret >= 'a' && ret <= 'z') || (ret >= '0' && ret <= '9')) {
ret |= 0x80; /* Meta key support for most terminals */
} else if (ret == 'O') { /* Numeric keypad */
ret = getch();
if ((ret != ERR) && (ret >= 112) && (ret <= 121)) {
if (ret != ERR && ret >= 112 && ret <= 121) {
ret = ret - 112 + '0'; /* Convert to number */
} else {
ret = '\033'; /* Escape */