Skip to content

fix(input): track/release client keycode for modifiers - #5558

Open
sethdmoore wants to merge 2 commits into
LizardByte:masterfrom
sethdmoore:fix/input-bugs
Open

fix(input): track/release client keycode for modifiers#5558
sethdmoore wants to merge 2 commits into
LizardByte:masterfrom
sethdmoore:fix/input-bugs

Conversation

@sethdmoore

@sethdmoore sethdmoore commented Aug 26, 2026

Copy link
Copy Markdown

Description

Fix two related bugs in the keybindings path. Also add tests.

Please let me know if any of the changes are unacceptable. This change set fixes bugs I
encountered while streaming OSX Taho with Sunshine to Linux (via Moonlight).

Tested and working after these patches. Wayland Arch client (moonlight) -> OSX host (sunshine).

Both bugs stem from the same issue: key_press and shortcutFlags were stored on
the client's unmapped virtual-key code. Key presses were emitted through
map_keycode(), so the host holds a different key than Sunshine's perspective.

1. Synthetic modifier fix (src/input.cpp, passthrough)

shortcutFlags is compared against packet->modifiers, a client-side bitmask. It was
updated from the remapped host keycode.

Any keybindings entry that moves Alt off
VKEY_MENU/VKEY_LMENU/VKEY_RMENU caused the ALT bit to clear while the client still
reported MODIFIER_ALT. send_key_and_modifiers() wrapped every following key in a real
VKEY_MENU press/release.

keybindings = [0x5B, 0xA4, 0x5C, 0xA5, 0xA4, 0x5B, 0xA5, 0x5C]

Before the fix, Alt+Space from a client (linux) arrived as Cmd+Option+Space on Sunshine host (OSX).
This would cause annoyances like: "Search This Mac" appearing when attempting to spawn Spotlight.
It also made simple things like such as option-as-alt in terminals to be unusable.

The bug affected every key pressed while the remapped Alt was held, not just Space.

The synthetic-modifier path exists for clients that report modifiers in the bitmask without
sending discrete modifier key events, so both sides of that comparison should be client-side.
apply_shortcut() in the combo check already receives the unmapped keycode, so this also
makes the two halves of that check agree.

2. Remapped key left latched on disconnect (src/input.cpp, reset_keyboard_keys)

The reset path pulled the unmapped VK back out of key_press and released those unmapped keys.
The remapped key that was never released. With the keybindings config above, a disconnect
released Option and left Command held.

This bug also affects the shippedkey_rightalt_to_key_win option, which follows the same remap / unmap flow.

3. Tests

platf::keyboard_update is a per-platform free function, and libvirtualhid's FakeKeyboard
discards submit(), so ordered keyboard output was not observable. The nine emit sites now go
through one emit_keyboard_update() forwarder whose sink check is inside
#ifdef SUNSHINE_TESTS; non-test builds compile to a plain forwarder.

18 tests: 45 held-modifier combinations (3 side variants x 15 non-empty subsets of
Shift/Ctrl/Alt/Meta), all 7 synthetic-injection subsets, 91 keys forwarded verbatim, a
self-verifying table proving all 95 printable ASCII characters are reachable and exercised
plain and shifted, flags propagation, press/release dedup, the shortcut-swallow path, and
regression coverage for both fixes. One test clears the recorder and asserts through
lvh::Keyboard::submit_count() so a broken delivery path can't hide behind the sink.

Both fixes were verified by reverting them individually and confirming the tests fail:
fix 1 -> 3 failures (expected {+0x20, -0x20}, got {+0x12, +0x20, -0x12, -0x20});
fix 2 -> 2 failures (released -0xA4 instead of -0x5B). A no-keybindings control test
passes either way, showing fix 2 doesn't over-map.

Native VK -> native keycode translation is deliberately not tested here. It moved to
libvirtualhid, whose backend tables live in anonymous namespaces in translation units that
only compile on their own platform, and which are already covered by TranslatesKeyboardKeys
in that project's per-platform test jobs. Nothing here is platform-gated, so every CI job runs
all 18.

Screenshot

N/A - not a UI change.

Issues Fixed or Closed

None open that I could find. Fix 1 is the same root cause as #4531, which #5318 fixed for the
key_rightalt_to_key_win path only. That special is likely redundant. Unmapped
tracking VKEY_RMENU applies the ALT bit on its own.

Roadmap Issues

None.

Type of Change

  • feat: New feature (non-breaking change which adds functionality)
  • fix: Bug fix (non-breaking change which fixes an issue)
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit
  • BREAKING CHANGE: Introduces a breaking change (can be combined with any type above)

Checklist

  • Code follows the style guidelines of this project
  • Code has been self-reviewed
  • Code has been commented, particularly in hard-to-understand areas
  • Code docstring/documentation-blocks for new or existing methods/components have been added or updated
  • Unit tests have been added or updated for any new or modified functionality

AI Usage

See our AI usage policy.

  • None: No AI tools were used in creating this PR
  • Light: AI provided minor assistance (formatting, simple suggestions)
  • Moderate: AI helped with code generation or debugging specific parts
  • Heavy: AI generated most or all of the code changes

Fixed, tested and working on my branch. Added tests to prevent
regressions.

shortcutFlags is compared against packet->modifiers, a client-side
bitmask, but was updated from the host keycode from map_keycode().

- A keybindings entry moving Alt off VKEY_*MENU left the ALT bit clear,
  so send_key_and_modifiers() wrapped every following key in a real
  VKEY_MENU press
  - Alt->Meta on a macOS host turned Cmd+Space into Cmd+Option+Space
- apply_shortcut() already gets the unmapped keycode, so both halves of
  the combo check now agree
- Generalizes the key_rightalt_to_key_win case from LizardByte#5318

fix(input): release the remapped key when the client disconnects

reset_keyboard_keys() emitted an unmapped virtual-key code. Keypresses
go are sent via map_keycode(). Remapped keys stay pressed on the
host after a disconnect.

- With keybindings 0xA4 -> 0x5B, Option was released while Command was
  left held down
- Also affected key_rightalt_to_key_win option

test(input): cover keyboard passthrough with a recording sink

platf::keyboard_update is a per-platform function and lvh's
FakeKeyboard discards submit(), so ordered keyboard output could not be
observed from a test.

- Route the nine emit sites through emit_keyboard_update(), whose sink
  check sits inside #ifdef SUNSHINE_TESTS
- Cover 45 held-modifier combinations, all 7 synthetic-injection
  subsets, and every printable ASCII character plain and shifted
- Pin the spurious VKEY_MENU injection fixed in the previous commit
- Native VK translation stays in libvirtualhid, which covers each
  backend table in its own platform's CI job
- Return the test keyboard recorder from an accessor, so the file adds no
  non-const variable at namespace scope
- Build assertion tokens and the stream name with std::format instead of
  output manipulators and manual concatenation
- Hold modifier masks in unsigned, and cast once where the packet helper
  needs a byte
- Move the modifier selection loop into select_modifiers(), which drops
  the combination test to three levels of nesting
- Make the fixture stream private, add using enum for side_e, and use
  std::ranges::sort with class template argument deduction
@sonarqubecloud

Copy link
Copy Markdown

@Kishi85

Kishi85 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

I'll test this as I've seen modifiers getting stuck sometimes myself on Linux host + Linux client. Will report back.

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