Skip to content

Keyboard: don't auto-toggle CODE/KANA lock while that key is held down - #2158

Merged
MBilderbeek merged 1 commit into
openMSX:masterfrom
sndpl:keyboard-code-kana-lock-desync
Jul 25, 2026
Merged

MBilderbeek merged 1 commit into
openMSX:masterfrom
sndpl:keyboard-code-kana-lock-desync

Conversation

@sndpl

@sndpl sndpl commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Addresses the stuck CODE/KANA lock reported in #1331. That ticket bundles
several distinct problems that happen to share the same trigger; this PR fixes
the one that makes the lock impossible to switch off again, which is what
@Colpocorto describes there. See "Not covered here" at the bottom for the parts
it deliberately does not touch, so please don't auto-close the issue on merge.

On a machine where CODE/KANA locks (e.g. a Japanese MSX), the lock can get
permanently out of sync with what openMSX thinks it is, after which it can no
longer be switched off:

  • tap the host CODE/KANA key -> the KANA LED goes off, but
  • the very next ordinary keystroke turns it straight back on again.

The only way out is restarting openMSX; a machine reset does not help, because
locksOn is never reset while the MSX does clear its own state. This is easy
to run into on host layouts where AltGr has to be held down to type common
characters such as @, # or ~, because the host CODE/KANA key defaults to
right-Alt.

Cause

Keyboard::pressUnicodeByUser() auto-toggles the CODE/KANA lock by flipping
locksOn and calling pressKeyMatrixEvent() for the CODE key. That call is a
no-op when the key is already down (it deliberately bails out when pressing
would not change the matrix).

So while the user physically holds the host CODE/KANA key, the MSX never sees
a new key-press edge and never toggles its lock, but openMSX flips locksOn
anyway. From then on the two are inverted, and because needsLockToggle() now
reports the wrong direction, every following keystroke auto-toggles the real
lock the wrong way.

Fix

Only take the auto-toggle path when the CODE key is currently released, so the
press really produces an edge the MSX can act on. When the user is holding the
key, just type the character with CODE/KANA held down, which is what a real
MSX does.

The "already pressed" test is extracted from pressKeyMatrixEvent() into
isKeyMatrixPressed() so both call sites share it. When the CODE key is not
held - the common case - behaviour is unchanged.

How it was verified

Driven against a real emulator run (-control stdio) with synthetic host key
events posted through the OS, observing $led_kana, i.e. the actual KANA LED
as driven by the emulated MSX via PSG register 15. Every injected press and
release is confirmed against debug read keymatrix <row> before the next step,
so a dropped host event cannot silently produce a wrong trace.

Note: on macOS right-Alt is Option, which mangles the unicode of the following
key, so the character never reaches the unicode mapping path. Binding
kbd_code_kana_host_key to END hits exactly the same code path with an
unmangled character, which is what AltGr+3=# does on a Windows/Spanish
keyboard.

Machine Canon_V-20_JP, kbd_code_kana_host_key END,
kbd_auto_toggle_code_kana_lock on. Steps:

1 tap END               6 tap END
2 tap END               7 type 'a'
3 type 'a'              8 type 'a'
4 hold END, 'a', release 9 type 'a'
5 type 'a'

KANA LED after each step, for all three mapping modes:

                init   1    2    3    4    5    6    7    8    9
before
  CHARACTER     off    on   off  off  on   on   off  on   on   on
  KEY           off    on   off  off  on   on   off  off  off  off
  POSITIONAL    off    on   off  off  on   on   off  off  off  off
after
  CHARACTER     off    on   off  off  on   off  on   off  off  off
  KEY           off    on   off  off  on   on   off  off  off  off
  POSITIONAL    off    on   off  off  on   on   off  off  off  off

In CHARACTER mode before the fix, steps 7-9 are the bug: a plain letter
switches the KANA lock back on, and it stays stuck that way. Step 5 shows the
same desync from the other side - the lock is on and typing a plain letter
should auto-toggle it off, but openMSX already believes it is off so nothing
happens. After the fix step 5 auto-toggles correctly and steps 7-9 leave the
lock off.

KEY and POSITIONAL are byte-identical before and after, as expected:
processKeyEvent() forces unicode = 0 for those two modes, so
pressUnicodeByUser() - and with it the changed branch - is unreachable there.
Those traces do exercise the isKeyMatrixPressed() extraction heavily though,
since every keystroke in those modes goes through pressKeyMatrixEvent() via
processSdlKey().

Also checked to be unchanged:

  • default right-Alt binding behaves exactly as before;
  • the auto-toggle still does nothing when kbd_auto_toggle_code_kana_lock
    is off;
  • plain, shifted and CODE-held typing on screen: helo, HELO, and kana
    glyphs while CODE/KANA is held (which is correct MSX behaviour).

Not covered here

(Revised after merge: the Windows claim in the original version of this
section was wrong, see below.)

The ticket also mentions right-Alt behaving like CTRL on Windows (right-Alt+G
beeping). That one looks like it is already fixed, on the SDL side rather than
here. Windows generates a synthetic left-Ctrl alongside AltGr, and SDL2 started
filtering it in 2.30.4: skip_bad_lcrtl() in
src/video/windows/SDL_windowsevents.c peeks the message queue on WM_KEYDOWN
and drops a non-extended VK_CONTROL when the next queued message is an extended
VK_MENU down with the same timestamp. It is absent in 2.30.3 and every earlier
release, and was renamed to SkipAltGrLeftControl() in the 2.32 line. openMSX
has pinned SDL 2.30.7 since ae1dfac (2024-09-30) and both Windows build paths
use that pin, so every 21.0 build should have it. The original report is from
2021, when we still bundled SDL 2.0.x.

I have no Windows machine, so that is source archaeology rather than an
observation - someone on a non-US layout pressing right-Alt+G on a current
build would settle it in ten seconds.

One gap does remain, and it is in SDL rather than here: the same helper is also
called on WM_KEYUP, but it only matches when the next queued message is a
VK_MENU key-down, which cannot happen on release. So the fake left-Ctrl
release is still delivered. For openMSX that is normally harmless - releasing
a key that was never pressed is a no-op - but if you hold real left-Ctrl and
tap AltGr, the MSX sees CTRL released early. That also wants confirming on
Windows before it is worth reporting upstream.

The macOS half of the ticket is untouched as well: Option acts as a
dead-key/compose modifier there, so the character following it is altered by
the host before openMSX sees it. That is why the macOS symptoms in the report
read as "sometimes the character appears, sometimes nothing" rather than as a
lock you cannot clear.

On a machine where CODE/KANA locks, pressUnicodeByUser() can auto-toggle the
lock by flipping 'locksOn' and pressing the CODE key in the matrix. But
pressKeyMatrixEvent() is a no-op when the key is already down, so when the
user is physically holding the host CODE/KANA key (by default right-Alt,
which on many host layouts is AltGr and must be held to type '@', '#', ...)
the MSX never sees a new key-press edge and never toggles its lock, while
openMSX did flip 'locksOn'.

From that moment on the two are inverted and stay inverted: tapping the
host key appears to switch the lock off, but the next ordinary keystroke
auto-toggles it straight back on, and there is no way to get out of it
short of restarting openMSX. A reset does not help, since 'locksOn' is
not reset while the MSX does clear its own state.

Only take the auto-toggle path when the CODE key is currently released, so
that the press really produces an edge for the MSX. When the user is holding
the key, just type the character with CODE/KANA held, like a real MSX does.

Extract the "already pressed" test from pressKeyMatrixEvent() into
isKeyMatrixPressed() so both places share it.
@MBilderbeek
MBilderbeek merged commit 69db790 into openMSX:master Jul 25, 2026
7 of 8 checks passed
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.

2 participants