fix(apple): resolve macOS modifiers by keyCode — Control was silently dropped

Modifier keys arrive only as flagsChanged, and the direction was recovered
by diffing the device-dependent L/R bits (NX_DEVICE*KEYMASK) alone. Those
bits are undocumented and some keyboards omit them (only the class bit,
e.g. NX_CONTROLMASK, is set), so the diff saw no transition and the key
never reached the host — no Ctrl shortcuts. SDL/Moonlight key off the
event's keyCode for exactly this reason; do the same: keyCode names the
changed key, the class bit says up, the device bits (when present) pick
the side, and a tracked-held-state flip covers keyboards without them.

PUNKTFUNK_INPUT_DEBUG=1 now also logs every flagsChanged (keyCode + raw
flags) so a field report is diagnosable from client logs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 18:29:33 +02:00
parent 6f47abab8c
commit 7f29796e81
4 changed files with 154 additions and 48 deletions
@@ -346,10 +346,13 @@ public final class StreamLayerView: NSView {
super.keyUp(with: event)
}
/// Modifier keys (shift/control/option/command) arrive ONLY as flagsChanged on macOS,
/// never keyDown/keyUp InputCapture diffs the raw flags to recover each L/R down/up.
/// never keyDown/keyUp the changed key is `event.keyCode`; InputCapture resolves the
/// down-vs-up direction from the flags (diffing the device-dependent flag bits alone
/// proved unreliable some keyboards omit them, which silently dropped Control).
public override func flagsChanged(with event: NSEvent) {
if captured, let inputCapture {
inputCapture.handleFlagsChanged(UInt(event.modifierFlags.rawValue))
inputCapture.handleFlagsChanged(
keyCode: event.keyCode, rawFlags: UInt(event.modifierFlags.rawValue))
return
}
super.flagsChanged(with: event)