Skip to content

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

Description

@tim-korso

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions