From 669f85024f15a52c92c8c4df0485d703d5f2360e Mon Sep 17 00:00:00 2001 From: Kyle Buxton <110138165+kylebuxton@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:31:17 -0600 Subject: [PATCH] fix(macos): set Command flag on Cmd-down event so Electron apps paste (#952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS auto-paste sequence in `send_paste` posted the Cmd-down CGEvent with flags = 0, setting the Command flag only on the V events. On real hardware the Cmd keyDown (a flagsChanged event) already carries kCGEventFlagMaskCommand, and Chromium/Electron builds its tracked modifier state from that flag. With flags = 0 the tracker stays at "Command up", so the following V matches neither the Cmd+V accelerator (tracker says no modifier) nor plain-text insertion (the V event's own flags say Command is held) — Electron drops it silently, producing no paste and no stray "v". AppKit reads the V event's own modifier flags and pastes regardless, which is why native apps (Notes, TextEdit, Warp) worked while Electron targets (Slack, VS Code, VS Code Insiders) silently no-op'd. Setting kCGEventFlagMaskCommand on the Cmd-down event makes the flagsChanged event well-formed; Chromium then registers Command=down and Cmd+V matches. Likely fixes #762 and #643. --- tauri/src-tauri/src/synthetic_keys.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tauri/src-tauri/src/synthetic_keys.rs b/tauri/src-tauri/src/synthetic_keys.rs index 9c9fbe97..1e33dfa1 100644 --- a/tauri/src-tauri/src/synthetic_keys.rs +++ b/tauri/src-tauri/src/synthetic_keys.rs @@ -4,10 +4,14 @@ //! pipeline so the focused app performs its native paste action against //! whatever the clipboard module has just staged. //! -//! - **macOS** — Cmd down, V down with Cmd flag, V up with Cmd flag, Cmd -//! up via `CGEventPost` at `kCGHIDEventTap`. Accessibility permission is -//! load-bearing: without it the system swallows the events silently, so -//! callers must gate on [`crate::accessibility::is_trusted`]. +//! - **macOS** — Cmd down with Cmd flag, V down with Cmd flag, V up with +//! Cmd flag, Cmd up via `CGEventPost` at `kCGHIDEventTap`. The Cmd-down +//! event carries the Command flag so its `flagsChanged` representation +//! matches hardware — Electron/Chromium tracks modifier state from that +//! flag and drops the paste otherwise (see the note on the event table). +//! Accessibility permission is load-bearing: without it the system +//! swallows the events silently, so callers must gate on +//! [`crate::accessibility::is_trusted`]. //! - **Windows** — Ctrl down, V down, V up, Ctrl up via `SendInput`. No //! permission gate, but UAC/UIPI blocks delivery into elevated target //! windows when we run non-elevated — nothing we can do short of also @@ -101,7 +105,18 @@ pub fn send_paste() -> Result<(), String> { let _source_guard = scopeguard::guard(source, |s| CFRelease(s as *const c_void)); let events = [ - (KEYCODE_LEFT_CMD, true, 0), + // The Cmd-down event must carry the Command flag itself. On real + // hardware the Cmd keyDown is a flagsChanged event whose flags + // already include Command; Chromium/Electron builds its tracked + // modifier state from that flag. Posting Cmd-down with flags = 0 + // leaves that tracker showing "Command up", so the following V — + // even though its own flags carry Command — matches neither the + // Cmd+V accelerator (tracker says no modifier) nor plain-text + // insertion (event flags say Command), and Electron drops it + // silently. AppKit reads the V event's own flags and pastes + // regardless, which is why native apps worked but Electron + // targets (Slack, VS Code) silently no-op'd. + (KEYCODE_LEFT_CMD, true, K_CG_EVENT_FLAG_MASK_COMMAND), (v_keycode, true, K_CG_EVENT_FLAG_MASK_COMMAND), (v_keycode, false, K_CG_EVENT_FLAG_MASK_COMMAND), (KEYCODE_LEFT_CMD, false, 0),