#181

Cmd (⌘) chords never reach guest as SUPER while cocoa full-grab is on

Opentim-korsoopened 1mo ago

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=0

Guest 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-event meta+C also shows the meta lost before C in guest wev (modifiers 00000000 at 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 / rightCommandGuestDown ivars.
  • In the kVK_Command / kVK_RightCommand flagsChanged cases, send explicit idempotent Super down/up (no toggleKey flips).
  • 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-input entitlements restored.

Activity

2
brentkearney1mo ago

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.

huix952719d ago

I'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.