Skip to content

fix(core): fix keydown/up handling for LDML keyboards #️⃣#15609

Open
ermshiperete wants to merge 3 commits intorefactor/core/cleanupfrom
fix/core/15569_ldml
Open

fix(core): fix keydown/up handling for LDML keyboards #️⃣#15609
ermshiperete wants to merge 3 commits intorefactor/core/cleanupfrom
fix/core/15569_ldml

Conversation

@ermshiperete
Copy link
Contributor

@ermshiperete ermshiperete commented Feb 20, 2026

For LDML keyboards this change fixes the emit_keystroke flag so that it always has the same value for the KeyDown and the KeyUp event. This fixes some problems with stuck keys. Previously we would set emit_keystroke=TRUE on KeyDown but emit_keystroke=FALSE on KeyUp for frame keys. This caused Linux to never see the KeyUp event, resulting in a stuck key.

Also add unit tests that verifies that the actions that we get after calling km_core_process_event are what we expect.

Follows: #15656
Fixes: #15569
Fixes: #15550

User Testing

Test on Linux

SUITE_BASICTEST

  • GROUP_X11 Test with Gnome Shell and X11

  • GROUP_WAYLAND Test with Gnome Shell and Wayland

  • TEST_ONEKEY_COMPLIANT:

    Install ldmlonekey.kmp.zip keyboard (rename to ldmlonekey.kmp). This defines a keyboard where only 1 will output anything.

    Open the text editor or gedit and type different keys with and without Shift, Control, and Alt/RAlt. Only the 1 key (without any modifier) should produce an output, all other (regular) keys shouldn't output anything. Enter should output a new line, and Backspace should work as well. The usual shortcut keys with Ctrl should also work: Ctrl-C copy, Ctrl-V paste, Ctrl-Backspace delete word, Ctrl-a select all text.

  • TEST_ONEKEY_NONCOMPLIANT: Try the same in Chromium, e.g. in the textbox of https://keymanweb.com/.

SUITE_BUGSFIXED

@github-project-automation github-project-automation bot moved this to Todo in Keyman Feb 20, 2026
@github-actions github-actions bot added core/ Keyman Core fix labels Feb 20, 2026
@keymanapp-test-bot keymanapp-test-bot bot added the user-test-missing User tests have not yet been defined for the PR label Feb 20, 2026
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Feb 20, 2026

User Test Results

Test specification and instructions

✅ SUITE_BASICTEST:

4 tests in 2 groups PASSED
  • ✅ GROUP_X11: Test with Gnome Shell and X11

    2 tests PASSED
  • ✅ GROUP_WAYLAND: Test with Gnome Shell and Wayland

    2 tests PASSED

🟥 SUITE_BUGSFIXED:

Retesting Template
Test-bot: retest SUITE_BUGSFIXED  TEST_15550

Test Artifacts

@ermshiperete ermshiperete changed the title fix(core): fix keydown/up handling for LDML keyboards fix(core): fix keydown/up handling for LDML keyboards #️⃣ Feb 20, 2026
@keymanapp-test-bot keymanapp-test-bot bot added this to the A19S23 milestone Feb 20, 2026
@ermshiperete ermshiperete force-pushed the fix/core/15569_ldml branch 2 times, most recently from f67f988 to c0b6426 Compare February 23, 2026 08:12
Base automatically changed from test/core/minkbd to master February 24, 2026 16:40
@keymanapp-test-bot keymanapp-test-bot bot added has-user-test user-test-required User tests have not been completed and removed user-test-missing User tests have not yet been defined for the PR labels Feb 24, 2026
@ermshiperete ermshiperete marked this pull request as ready for review February 24, 2026 17:02
ermshiperete added a commit that referenced this pull request Feb 25, 2026
This documents the state of `km_core_actions.emit_keystroke` for
different keys pressed. Also some cleanup in other docs.

This documents the state after merging #15609 (for ldml keyboards) and
NN (for kmn keyboards).

Follows: #15609
Build-bot: skip
Test-bot: skip
ermshiperete added a commit that referenced this pull request Feb 25, 2026
This documents the state of `km_core_actions.emit_keystroke` for
different keys pressed. Also some cleanup in other docs.

This documents the state after merging #15609 (for ldml keyboards) and
NN (for kmn keyboards).

Follows: #15609
Build-bot: skip
Test-bot: skip
@Meng-Heng Meng-Heng self-assigned this Feb 26, 2026

if (!ldml_state.is_key_down()) {
if (!ldml_state.get_state()->backspace_handled_internally()) {
ldml_state.emit_passthrough_keystroke();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to imagine if there was a scenario where we got a key-up for a Backspace without first getting a key-down. This would be considered bad input anyway. Passing the backspace to the app would probably be acceptable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this is needed only for LDML and not for KMN keyboards.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need it for KMN keyboards as well. However, I didn't get to that yet. I thought I'd fix LDML keyboards first in this PR since we get crashes with LDML, and then in the next PR tackle KMN keyboards.

@Meng-Heng
Copy link
Contributor

Meng-Heng commented Feb 26, 2026

Test Results

SUITE_BASICTEST:

Test Specs

  1. Ubuntu Noble 24.04.4 LTS
  2. GNOME Version 46
  3. Keyman v19.0.204-alpha 15609-6795

GROUP_X11: Test with Gnome Shell and X11

  • TEST_ONEKEY_COMPLIANT (PASSED):
  1. Launch Text Editor
  2. Result: Work as expected
    • Key 1 produce 1 on the default layer
    • Able to use SHIFT, CTRL, TAB, BACKSPACE, ENTER, and ARROW KEYS
    • Typing with SHIFT, CAPS LOCK produces the uppercase alphabet letters, symbols, and signs.
    • CTRL shortcut keys work
  • TEST_ONEKEY_NONCOMPLIANT (PASSED):
  1. Launch Chrome -> keymanweb.com
  2. Type in textarea
  3. Result: Work as expected (details the same as above).

GROUP_WAYLAND: Test with Gnome Shell and Wayland

Test Prerequisites

  1. Enable Wayland with sudo nano /etc/gdm3/custom.conf
  • TEST_ONEKEY_COMPLIANT (PASSED):
  1. Launch Text Editor
  2. Result: Work as expected
    • 1 produce on the default layer
    • Able to use SHIFT, CTRL, TAB, BACKSPACE, ENTER, and ARROW KEYS
    • Typing with SHIFT, CAPS LOCK produce the uppercase alphabet letters
    • CTRL shortcut keys work
  • TEST_ONEKEY_NONCOMPLIANT (PASSED):
  1. Launch Chrome -> keymanweb.com
  2. Type in textarea
  3. Result: Work as expected (details above).

SUITE_BUGSFIXED:

Test Specs

  1. Ubuntu Noble 24.04.4 LTS
  2. GNOME Version 46
  3. Keyman v19.0.204-alpha 15609-6795
  4. Non-compliant app: Chrome -> Keymanweb.com
  5. Compliant app: Text Editor
  • TEST_15569 (PASSED):
  1. With the LDMLOneKey and Khmer Angkor keyboard
  2. In the Wayland Windowing system, the Enter key stops producing an infinite number of newlines in non-compliant and compliant apps.
  • TEST_15550 (FAILED):
  1. Non-compliant app --> keymanweb.com --> click in textarea to type
  2. Use the Test_BKSP keyboard
  3. Type abc     d/, hit bksp
  4. Verified: Backspace does not stick.
  5. Result: A backspace deletes the character with its combining acute.
  • TEST_ADHOC (PASSED):
  1. With Khmer Angkor and Test BKSP keyboard
  2. Typing work as expected in compliant and non-compliant apps.

@keymanapp-test-bot keymanapp-test-bot bot added user-test-failed and removed user-test-required User tests have not been completed labels Feb 26, 2026
@Meng-Heng Meng-Heng removed their assignment Feb 26, 2026
rc-swag
rc-swag previously approved these changes Feb 27, 2026
Copy link
Contributor

@rc-swag rc-swag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will need to continue review on this next week but had some questions to start with.


if (!ldml_state.is_key_down()) {
if (!ldml_state.get_state()->backspace_handled_internally()) {
ldml_state.emit_passthrough_keystroke();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this is needed only for LDML and not for KMN keyboards.

Comment on lines +213 to +219
// Look up the key
bool found = false;
const std::u16string key_str = keys.lookup(ldml_state.get_vk(), ldml_state.get_modifier_state(), found);

if (!found) {
ldml_state.emit_passthrough_keystroke();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this match the logic in key_down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that part is exactly the same.

Comment on lines +535 to +557
this->vk = v;
this->modifier_state = m;
this->is_key_down = i;
this->event_flags = e;
this->_vk = v;
this->_modifier_state = m;
this->_is_key_down = i;
this->_event_flags = e;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming like this should be in a separate refactor PR please -- it makes it harder to follow the logical changes here and introduces complexity for patching into other versions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, sorry. Do you want me to still split this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please can you split the refactor/rename into a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#15656

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, doing that required a force-push for this PR.

this->keyboard = nullptr;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to keep whitespace-only changes out of a change like this. OK if the whitespace is in an affected area but otherwise it doesn't belong and unnecessarily expands the scope of patches.

input: kbd_src,
command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@', '--out-file', kbd_obj]
)
test('imx_list', imx_e, depends: kbd_log, args: [kbd_obj] )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on whitespace in this file

Comment on lines +179 to +181
// This is used to track whether the backspace key was handled internally
// during the keydown event. This is needed so that we can return the same
// value from the keyup event. Only used when processing KM_CORE_VKEY_BKSP.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a javadoc style comment. I still don't really understand why it needs to be handled separately in Core.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backspace is the only key that we sometimes handle internally (if we have enough context) and sometimes not. By the time we get the keyup event the context already got updated and so we have no way of knowing whether or not the keydown handled it internally. Therefore this flag.

Note that things do work without knowing because for backspace we always returned the right value for emit_keystroke, but it will cause the emit_keystroke values for keydown and keyup to be out of sync.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha thanks! Perhaps we can capture some of that detail in the comment?

@keyman-server keyman-server modified the milestones: A19S23, A19S24 Feb 27, 2026
@ermshiperete ermshiperete requested a review from mcdurdin March 2, 2026 10:25
@ermshiperete ermshiperete dismissed stale reviews from mcdurdin and rc-swag March 2, 2026 10:26

Comments addressed. Do you still want me to split out the whitespace and refactoring changes to a separate PR?

ermshiperete and others added 2 commits March 2, 2026 17:27
For LDML keyboards this change fixes the `emit_key` flag so that it has
the same value for the KeyDown and the KeyUp event. This fixes some
problems with stuck keys. Previously we would set `emit_key=TRUE` on
KeyDown but `emit_key=FALSE` on KeyUp for frame keys. This caused Linux
to never see the KeyUp event, resulting in a stuck key.

Also add unit tests that verifies that the actions that we get after
calling `km_core_process_event` are what we expect.

Fixes: #15569
Fixes: #15550
Co-authored-by: Marc Durdin <marc@durdin.net>
@ermshiperete ermshiperete changed the base branch from master to refactor/core/cleanup March 2, 2026 16:34
@ermshiperete ermshiperete force-pushed the fix/core/15569_ldml branch from 7ec8297 to 9836070 Compare March 2, 2026 16:43
@ermshiperete ermshiperete requested a review from rc-swag March 2, 2026 16:48
ermshiperete added a commit that referenced this pull request Mar 2, 2026
docs(core): add keyhandling doc

This documents the state of `km_core_actions.emit_keystroke` for different keys pressed. Also some cleanup in other docs.

This documents the state after merging #15609 (for ldml keyboards) and NN (for kmn keyboards).

Follows: #15609
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

5 participants