Skip to content

Fix modifier keys being instantly released when clients send side-specific VK codes - #459

Open
LorenzoMorelli wants to merge 1 commit into
games-on-whales:stablefrom
LorenzoMorelli:stable
Open

Fix modifier keys being instantly released when clients send side-specific VK codes#459
LorenzoMorelli wants to merge 1 commit into
games-on-whales:stablefrom
LorenzoMorelli:stable

Conversation

@LorenzoMorelli

Copy link
Copy Markdown

Problem

keyboard_key() synthesizes "virtual modifiers" around every KEY_PRESS: it presses the modifiers reported in the packet flags, presses the actual key, then immediately releases those modifiers. The guards that skip this dance when the pressed key is the modifier itself only compare against the generic Windows VK codes (M_SHIFT = 0x10, M_CTRL = 0x11), but clients (e.g. moonlight-qt) send the side-specific codes (VK_LSHIFT = 0xA0, VK_LCONTROL = 0xA2, ...).

As a result:

  • Pressing and holding Shift/Ctrl produces a key press immediately followed by a synthetic release (a ~0 ms pulse), so applications never see the key as held.
  • While a modifier is physically held, every other keypress carries the modifier flag and re-triggers the press/release dance, releasing a key the client is still holding.

Typical symptoms: hold-modifier actions in games don't work (e.g. Shift to sprint) and key-binding screens don't detect Shift/Ctrl, while text capitalization keeps working (the modifier state bit is briefly set around each letter). Alt is unaffected only by luck, since M_ALT happens to be defined as the side-specific code (0xA4). This is likely the root cause of games-on-whales/gow#299.

Fix

  • Add the side-specific modifier VK codes to MOONLIGHT_MODIFIERS and recognize both families through a modifier_bit() helper.
  • Track per session which modifiers the client is physically holding, from the modifier keys' own press/release packets.
  • Synthesize the virtual modifier press/release only for modifiers reported in the packet flags that the client is not physically holding. This preserves the original purpose of the dance (state drift, e.g. a modifier already held before the stream started) while never interfering with keys the client actually holds.

Note: the bug can go unnoticed on sessions using uinput virtual devices, because inputtino periodically re-presses the keys it still tracks as held (cur_press_keys), so the wrongly released modifier comes back down after a few milliseconds. Sessions using the Wayland virtual keyboard have no such keep-alive, so the modifier stays released.

@winiciuscota

Copy link
Copy Markdown

Confirming this fixes the issue I reported in games-on-whales/gow#299 — I was seeing the same symptom (Shift/Ctrl registering fine for menus/binding but not during actual gameplay) with a Wine + DirectInput game (DDO), not just Minecraft/LWJGL. Tested with this fix applied and modifier keys now hold correctly during play. Thanks for tracking down the side-specific VK code issue.

@ABeltramo ABeltramo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for contributing and sorry it took me this long to review. I think the fix is good and valid, I'd just like for it to be implemented differently so that it's more idiomatic with our codebase.

Comment thread src/moonlight-server/control/input_handler.cpp Outdated
@ABeltramo ABeltramo added the review done PR has been reviewed, waiting for contributor to address it label Aug 8, 2026
Refactored to match Wolf's codebase style: track held modifiers as a
per-session immer::atom<char> on StreamSession instead of global state.
@LorenzoMorelli

Copy link
Copy Markdown
Author

Hi and thanks for reviewing!
I have updated and synchronized my fork.

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

Labels

review done PR has been reviewed, waiting for contributor to address it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants