Skip to content

Fix Unix key input: no echo, full arrow keys, case-insensitive keys - #12

Merged
su8 merged 1 commit into
su8:mainfrom
lasers:fix/key-input
Oct 23, 2026
Merged

su8 merged 1 commit into
su8:mainfrom
lasers:fix/key-input

Conversation

@lasers

@lasers lasers commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Problem

  • Keys echo: readKey() turned off ICANON but not ECHO, so every key press was printed (seen on macOS).
  • One arrow press filled three key slots: an arrow key sends 3 bytes (ESC [ C), but readKey() read 1. In setupKeys(), pressing → set RIGHT=ESC, UP=[, LEFT=C. During play, the leftover bytes were read as extra moves.
  • Upper-case and arrow keys never matched: move was a lowercased signed char, while the stored keys were unsigned char and not lowercased.
  • Endless loop at end of input: when stdin was closed, read() returned 0, and the main loop spun forever.

Fix

readKey() turns off ECHO, reads the rest of an arrow sequence while still in raw mode, and returns -1 at end of input:

raw.c_lflag &= static_cast<tcflag_t>(~(ICANON | ECHO));
// ...
// Read the rest of an arrow key while still raw: going back to canonical mode first makes macOS echo "[C"
if (result == 27) {
  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 1; // wait up to 0.1 s
  // ...
  if (read(STDIN_FILENO, seq, 2) == 2 && (seq[0] == '[' || seq[0] == 'O') && seq[1] >= 'A' && seq[1] <= 'D') { result = 1001 + (seq[1] - 'A'); }
}

setupKeys() stores letters lowercased:

int key = readKey();
if (key < 0) { std::exit(EXIT_SUCCESS); } // stdin closed
keys[x++] = (key < 256) ? std::tolower(key) : key;

Main loop: move is now an int compared against keys[] directly. The unsigned char copies are kept only for printing the hint line. This also lets Windows arrow codes (224<<8 | x) match.

This also clears the -Wconversion and -Wmissing-field-initializers warnings from the old code.

Testing

Tested on Linux in a pseudo-terminal:

  • arrow keys at setup fill one slot each, and move the hero during play
  • an upper-case key at setup (I) works when pressed as i
  • nothing echoes
  • a closed stdin exits cleanly

Not tested on macOS or Windows. The Windows readKey() is unchanged.

Known leftover

When arrow keys are used, the "Move (…)" hint line still prints junk characters, because it prints the keys as single characters. That's left for a follow-up.

Fixes #5

readKey() now turns off ECHO and reads an arrow key's escape sequence
while still in raw mode, so one arrow press is one key and macOS no
longer echoes "[C". Keys are compared as ints and lowercased at setup,
so upper-case and arrow keys match. A closed stdin exits instead of
looping forever.
@su8
su8 merged commit e6480df into su8:main Oct 23, 2026
3 checks passed
@su8

su8 commented Oct 23, 2026

Copy link
Copy Markdown
Owner

You have merged this code already in #13

Will keep it open, but you can close it at any time :)

Cheers mate

@lasers
lasers deleted the fix/key-input branch October 23, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unix readKey(): typed keys echo, arrow keys fill 3 key slots, upper-case keys never match

2 participants