Summary
Host Cmd (⌘) combinations are delivered to the guest as Super↓ Super↑ <key>↓, so SUPER bindings (⌘C copy, ⌘V paste, ⌘W close, etc.) never fire while the QEMU Cocoa display is in full-grab mode. Even holding ⌘ down for 2+ seconds and then pressing C produces the same premature Super release.
Environment
- Try Omarchy macOS app, Apple Silicon (arm64)
- QEMU 11.1.1 (
c3d48b7d1e89604920e5b81b91140c2ad39a1943) - Display args:
-display cocoa,gl=es,show-cursor=on,zoom-to-fit=on,full-screen=on,full-grab=on,immersive=on,swap-opt-cmd=off - Guest: ARCH Linux / Hyprland, virtio-keyboard + virtio-tablet
- Repro app: Chromium (select text then ⌘C), also Foot terminal
What happened
Pressing ⌘C with full-grab on produces, in the guest evdev stream (/dev/input/event0):
KEY code=125 val=1 (Super down)
KEY code=125 val=0 (Super up — premature!)
KEY code=46 val=1 (C down, now unmodified)
KEY code=46 val=0Guest Hyprland hyprctl binds shows the expected modmask 64+SUPER+C entries, and non-modified keys arrive fine, but no SUPER chord is ever formed. Holding ⌘ for 2s first does not help: Super still goes up before C arrives. Clipboard bridging also does not help because the guest never sees a real copy event.
Root cause (in ui/cocoa.m)
The full-grab HID event tap swallows Cmd flagsChanged before AppKit records the modifier. A later C keyDown still carries no Command bit in modifierFlags, so the modifier-sync block at the top of handleEventLocked: (if modifierFlags lacks Command → qkbd_state_key_event(kbd, KEY_LEFTMETA, false)) emits a guest Super up immediately after the tap's Super down, before the chord key arrives.
Verified layers:
- QMP
display-update {"full-grab": false}doesn't change the broken sequence — the tap's destructive path isn't governed by that switch. - QMP
input-send-eventmeta+C also shows the meta lost before C in guestwev(modifiers00000000at C). - Host keyboard remappers (Karabiner/skhd/yabai/BTT) are not involved; killing them doesn't change the sequence.
Suggested fix (we have a working patch)
Track Command state inside cocoa.m itself instead of trusting AppKit modifierFlags while the keyboard is captured:
- Keep explicit
leftCommandGuestDown/rightCommandGuestDownivars. - In the
kVK_Command/kVK_RightCommandflagsChangedcases, send explicit idempotent Super down/up (notoggleKeyflips). - Make the "no Command modifier → release meta" sync a no-op while keyboard is captured (
isKeyboardCaptured). - Reset the ivars in
raiseAllKeys.
With this patch, rebuilding QEMU and replacing the runtime binary changes the guest evdev sequence to:
KEY code=125 val=1 (Super down, held)
KEY code=46 val=1 (C down while Super held)
KEY code=46 val=0
KEY code=125 val=0 (Super up after Cmd release)⌘C/⌘V and ⌘W-style SUPER bindings then work end-to-end; host/guest clipboard sync works.
We can open a PR with macos/patches/qemu-cocoa-command-chord-hold.patch + build-script wiring if maintainers want it.
Notes
- Original QEMU binary backed up; test was done by replacing the app runtime binary and re-signing ad-hoc with
com.apple.security.hypervisor+audio-inputentitlements restored.
Activity
2I'm experiencing the same issue in UTM as well. Keystrokes for the left Control and Command keys aren't reaching the guest OS / virtual machine.
I'm not a maintainer, but would be interested in this. IMO you should post the PR. I will try it, and leave a comment on my experience. If the maintainers want it they'll merge it, or not.