[PR #459] [MERGED] Fix modifier keys being instantly released when clients send side-specific VK codes #481

Closed
opened 2026-10-15 13:00:54 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/wolf/pull/459
Author: @LorenzoMorelli
Created: 7/20/2026
Status: ✅ Merged
Merged: 9/7/2026
Merged by: @ABeltramo

Base: stable ← Head: stable


📝 Commits (1)

  • 579e72a fix(input): stop instantly releasing modifier keys held by the client

📊 Changes

3 files changed (+58 additions, -12 deletions)

View changed files

📝 src/moonlight-protocol/moonlight/control.hpp (+8 -1)
📝 src/moonlight-server/control/input_handler.cpp (+44 -11)
📝 src/moonlight-server/events/events.hpp (+6 -0)

📄 Description

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.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/wolf/pull/459 **Author:** [@LorenzoMorelli](https://github.com/LorenzoMorelli) **Created:** 7/20/2026 **Status:** ✅ Merged **Merged:** 9/7/2026 **Merged by:** [@ABeltramo](https://github.com/ABeltramo) **Base:** `stable` ← **Head:** `stable` --- ### 📝 Commits (1) - [`579e72a`](https://github.com/games-on-whales/wolf/commit/579e72adc88b593c1c5e3805a7baffeb14942e29) fix(input): stop instantly releasing modifier keys held by the client ### 📊 Changes **3 files changed** (+58 additions, -12 deletions) <details> <summary>View changed files</summary> 📝 `src/moonlight-protocol/moonlight/control.hpp` (+8 -1) 📝 `src/moonlight-server/control/input_handler.cpp` (+44 -11) 📝 `src/moonlight-server/events/events.hpp` (+6 -0) </details> ### 📄 Description ## 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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 13:00:54 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/wolf#481
No description provided.