//! Auto-insert-at-cursor. //! //! `copy_to_clipboard` puts the transcript on the user's clipboard. That is //! fine when the user wants to choose where it lands. It is friction when //! they were about to paste into the already-focused window — which is //! almost always, for a dictation tool. //! //! This module adds the follow-on step: send the platform's Ctrl+V / Cmd+V //! keystroke to the focused window so the transcript lands where the cursor //! already is. Each platform uses a different primitive (wtype / xdotool / //! ydotool / osascript / SendKeys), so we probe and fall back. //! //! NOTE: focus must already be on the target window when the keystroke //! fires. The global hotkey flow preserves this naturally; clicking Lumotia's //! window does not. The frontend surfaces this caveat next to the toggle. use std::process::Command; use std::time::Duration; use arboard::Clipboard; use serde::Serialize; use tauri::Manager; use crate::commands::security::{ensure_main_window, ensure_window_in_set}; /// Windows allowed to invoke `paste_text_replacing`. The /// `transcription-preview` window's paste-to-foreground flow legitimately /// uses this command; mirror the secondary-windows capability grant in /// `src-tauri/capabilities/secondary-windows.json`. The mirror invariant /// is pinned by /// `commands::security::tests_capability_mirror::allowlists_match_capability_jsons`. pub(crate) const PASTE_REPLACING_ALLOWED_WINDOWS: &[&str] = &["main", "transcription-preview"]; /// Refuse-to-paste limit. Matches `commands::clipboard::MAX_CLIPBOARD_BYTES` /// (1 MiB) so paste and copy surfaces share a single rejection rule. /// Synthesising a 50 MiB Ctrl+V keystroke into the foreground app would /// also be a denial-of-service against the target (LibreOffice / Notes /// can hang or crash on multi-megabyte paste payloads). pub(crate) const MAX_PASTE_BYTES: usize = crate::commands::clipboard::MAX_CLIPBOARD_BYTES; /// Window after the paste keystroke at which we restore the user's /// prior clipboard content. 300 ms is enough for even a slow Wayland /// compositor to have fully delivered the synthesised Ctrl+V to the /// focused app; longer risks the user manually pasting again and /// stomping themselves. See Handy #921. const CLIPBOARD_RESTORE_MS: u64 = 300; /// Compositor settle time after hiding the preview overlay before firing /// the paste keystroke. Empirically ~80ms is enough on KWin + Mutter /// Wayland for focus to return to the previously-focused app; shorter /// risks the keystroke still landing on the (now-invisible) overlay. const PREVIEW_HIDE_SETTLE_MS: u64 = 80; /// Gap between the undo keystroke and the follow-up paste in the /// replace flow (brief item #17). Most GUI apps process undo /// instantly, but a short beat keeps the paste from racing the undo /// on slower-to-redraw targets (Electron apps, LibreOffice). const UNDO_PASTE_GAP_MS: u64 = 60; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PasteOutcome { /// The backend that executed the paste, if any (`"wtype"`, `"xdotool"`, /// `"ydotool"`, `"osascript"`, `"sendkeys"`). pub backend: Option, pub pasted: bool, pub copied: bool, /// Diagnostic message when either step failed. Present even on success /// if the paste fell back to copy-only so the frontend can toast it. pub message: Option, } /// Copy `text` to the clipboard, then trigger a paste keystroke in the /// focused window. Returns a structured result so the frontend can surface /// partial success (clipboard set, paste failed). /// /// Wayland compositor quirk: if Lumotia's always-on-top preview overlay is /// visible when the keystroke fires, KWin / Mutter may resolve the keystroke /// against the overlay (even with `focused: false` set at build time) and /// the paste lands inside Lumotia instead of the previously-focused app. We hide /// the preview window and give the compositor a beat to re-focus the real /// target before dispatching. Matches OpenWhispr's PR #246 fix on GNOME. #[tauri::command] pub async fn paste_text( window: tauri::WebviewWindow, app: tauri::AppHandle, text: String, ) -> Result { ensure_main_window(&window)?; if text.len() > MAX_PASTE_BYTES { return Err(format!( "Paste payload too large ({} bytes; limit {} bytes).", text.len(), MAX_PASTE_BYTES )); } let mut outcome = PasteOutcome { backend: None, pasted: false, copied: false, message: None, }; let prior_clipboard = snapshot_clipboard_text(); match Clipboard::new().and_then(|mut cb| cb.set_text(&text)) { Ok(()) => outcome.copied = true, Err(err) => { outcome.message = Some(format!("clipboard: {err}")); return Ok(outcome); } } // Brief item #10: if the focused window is a known terminal // emulator, skip the synthesised Ctrl+V — it duplicates the // keystroke through the PTY, which Kitty / Alacritty / Windows // Terminal all demonstrably mishandle (Handy #692). Clipboard-only // paste with a "Terminal detected" message lets the user finish // with a manual right-click paste or Ctrl+Shift+V. if let Some(term) = detect_focused_terminal() { outcome.message = Some(format!( "Terminal detected ({term}) — transcript is on your clipboard. \ Use Ctrl+Shift+V (or right-click → paste) to insert." )); // Prior clipboard snapshot is intentionally NOT restored here: // the user's path to recover the transcript is their own paste, // which needs the transcript on the clipboard. The restore // task below only runs when `outcome.pasted` is true. return Ok(outcome); } hide_preview_overlay_for_paste(&app).await; match trigger_paste_keystroke() { Ok(backend) => { outcome.backend = Some(backend); outcome.pasted = true; } Err(err) => outcome.message = Some(err), } if outcome.pasted { schedule_clipboard_restore(prior_clipboard, text); } Ok(outcome) } /// Snapshot the clipboard's current text, or `None` when the clipboard /// holds non-text content (images, files, empty). Shared between /// `paste_text` and `paste_text_replacing` so both halves of the /// dictation flow obey the "never silently clobber the user's /// clipboard" contract from Handy #921 (brief item #3). fn snapshot_clipboard_text() -> Option { match Clipboard::new() { Ok(mut cb) => cb.get_text().ok(), Err(_) => None, } } /// Fire-and-forget: restore `prior` to the clipboard after /// `CLIPBOARD_RESTORE_MS`, but only if the clipboard still holds the /// `transcript` we set (i.e., the user hasn't copied something new in /// the window). No-op when `prior` is `None` — nothing safe to /// restore. fn schedule_clipboard_restore(prior: Option, transcript: String) { let Some(prior) = prior else { return; }; tokio::spawn(async move { tokio::time::sleep(Duration::from_millis(CLIPBOARD_RESTORE_MS)).await; restore_prior_clipboard(&prior, &transcript); }); } /// Write `prior` back to the clipboard only if the current clipboard /// content is still the transcript we just set — i.e. the user hasn't /// already copied something new themselves in the last 300 ms. This /// avoids stomping a deliberate Cmd+C that happened right after the /// paste. fn restore_prior_clipboard(prior: &str, transcript: &str) { let mut cb = match Clipboard::new() { Ok(cb) => cb, Err(_) => return, }; if should_restore(cb.get_text().ok().as_deref(), transcript) { let _ = cb.set_text(prior.to_string()); } } /// Pure decision function: should we restore the prior clipboard? /// /// - Some(current) == transcript → yes, the clipboard still holds /// what we wrote, so restore is safe. /// - Some(current) != transcript → no, user has already copied /// something else in the 300 ms window; respect it. /// - None → couldn't read the clipboard (image, no access, etc.); /// conservative no-op. fn should_restore(current: Option<&str>, transcript: &str) -> bool { matches!(current, Some(text) if text == transcript) } /// Replace-flow paste (brief item #17): send the platform's undo /// keystroke to the previously-focused app, wait a beat so the undo /// takes effect, then paste `text`. The caller (preview overlay's /// "Replace with raw" button) is responsible for only invoking this /// when a paste actually happened moments earlier — we don't probe to /// verify there's something to undo. /// /// Returns the same PasteOutcome shape as `paste_text` so the frontend /// can surface identical partial-success messaging. #[tauri::command] pub async fn paste_text_replacing( window: tauri::WebviewWindow, app: tauri::AppHandle, text: String, ) -> Result { ensure_window_in_set(&window, PASTE_REPLACING_ALLOWED_WINDOWS)?; if text.len() > MAX_PASTE_BYTES { return Err(format!( "Paste payload too large ({} bytes; limit {} bytes).", text.len(), MAX_PASTE_BYTES )); } let mut outcome = PasteOutcome { backend: None, pasted: false, copied: false, message: None, }; // Same clipboard-preservation contract as `paste_text`: snapshot // the user's prior clipboard before we write the raw transcript, // restore it in the background after the paste keystroke fires. // Without this, a successful revert leaves the raw transcript on // the clipboard permanently — stomping whatever the user had // copied before invoking replace-with-raw. let prior_clipboard = snapshot_clipboard_text(); match Clipboard::new().and_then(|mut cb| cb.set_text(&text)) { Ok(()) => outcome.copied = true, Err(err) => { outcome.message = Some(format!("clipboard: {err}")); return Ok(outcome); } } hide_preview_overlay_for_paste(&app).await; if let Err(err) = trigger_undo_keystroke() { outcome.message = Some(format!("undo: {err}")); return Ok(outcome); } tokio::time::sleep(Duration::from_millis(UNDO_PASTE_GAP_MS)).await; match trigger_paste_keystroke() { Ok(backend) => { outcome.backend = Some(backend); outcome.pasted = true; } Err(err) => outcome.message = Some(err), } if outcome.pasted { schedule_clipboard_restore(prior_clipboard, text); } Ok(outcome) } /// Hide the transcription-preview window if it's currently visible, then /// sleep a short beat so the compositor can recompute focus. No-ops when /// the window isn't registered yet (user never enabled the overlay) or /// isn't currently shown. async fn hide_preview_overlay_for_paste(app: &tauri::AppHandle) { let Some(window) = app.get_webview_window("transcription-preview") else { return; }; let visible = window.is_visible().unwrap_or(false); if !visible { return; } if window.hide().is_err() { return; } tokio::time::sleep(Duration::from_millis(PREVIEW_HIDE_SETTLE_MS)).await; } /// Report which paste backends the OS has available right now. Pure probe — /// does not paste. Used by Settings to tell the user "install wtype" when /// nothing is available on their session. #[tauri::command] pub fn detect_paste_backends() -> Vec { let mut available = Vec::new(); #[cfg(target_os = "linux")] { for tool in ["wtype", "xdotool", "ydotool"] { if which_on_path(tool) { available.push(tool.to_string()); } } } #[cfg(target_os = "macos")] { available.push("osascript".to_string()); } #[cfg(target_os = "windows")] { available.push("sendkeys".to_string()); } available } /// Well-known terminal-emulator window classes / process names. Each /// entry is matched case-insensitively against whatever the /// focused-window probe returns on each platform. The list is /// deliberately conservative — users can always copy + paste manually, /// so false negatives are cheap; false positives silently break /// auto-paste on a legitimate GUI, which is expensive. /// Longest / most specific matches come first so that `classify_terminal` /// returns the specific name when two needles would both hit /// (e.g. "windowsterminal" contains "terminal"). Substring match is /// otherwise blunt enough that a `foot`/`pwsh` match on a non-terminal /// window would be vanishingly unlikely. const KNOWN_TERMINAL_CLASSES: &[&str] = &[ // Windows — specific names first so they win over plain "terminal". "windowsterminal", "powershell", "conhost", "console", "pwsh", "cmd", // macOS "iterm2", "iterm", "terminal", // Linux "gnome-terminal-server", "alacritty", "konsole", "wezterm", "kitty", "tilix", "xterm", "urxvt", "st-256color", "hyper", "foot", ]; /// Return `Some(class)` when the focused window matches a known /// terminal emulator, `None` otherwise. Pure by design: the actual /// platform probe is isolated in `detect_focused_window_class` so the /// classification rule is unit-testable. fn detect_focused_terminal() -> Option { let raw = detect_focused_window_class()?; classify_terminal(&raw) } /// Case-insensitive substring match of the focused window class / /// process name against `KNOWN_TERMINAL_CLASSES`. Returns the matched /// needle (for user-facing copy: "Terminal detected (kitty)") when /// positive. fn classify_terminal(raw: &str) -> Option { let haystack = raw.to_ascii_lowercase(); for needle in KNOWN_TERMINAL_CLASSES { if haystack.contains(needle) { return Some((*needle).to_string()); } } None } /// Probe the focused window's class name / process name. Returns /// `None` when the probe tool is missing or fails — we fall back to /// the default auto-paste path rather than guessing wrong and /// breaking a GUI user's workflow. fn detect_focused_window_class() -> Option { #[cfg(target_os = "linux")] { detect_focused_window_class_linux() } #[cfg(target_os = "macos")] { return detect_focused_window_class_macos(); } #[cfg(target_os = "windows")] { return detect_focused_window_class_windows(); } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] { return None; } } #[cfg(target_os = "linux")] fn detect_focused_window_class_linux() -> Option { // X11: xdotool getactivewindow getwindowclassname if let Ok(output) = Command::new("xdotool") .args(["getactivewindow", "getwindowclassname"]) .output() { if output.status.success() { let class = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !class.is_empty() { return Some(class); } } } // Wayland: no reliable focused-window probe from an unprivileged // client (by design). Return None and let the normal paste path // run; users on Kitty-under-Wayland already lean on Ctrl+Shift+V // themselves. None } #[cfg(target_os = "macos")] fn detect_focused_window_class_macos() -> Option { // `osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true'` let output = Command::new("osascript") .args([ "-e", "tell application \"System Events\" to get name of first application process whose frontmost is true", ]) .output() .ok()?; if !output.status.success() { return None; } let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); if name.is_empty() { None } else { Some(name) } } #[cfg(target_os = "windows")] fn detect_focused_window_class_windows() -> Option { // PowerShell one-liner: (Get-Process -Id (Get-Process | Where-Object ... // Keeping it simple here — GetForegroundWindow + GetWindowThreadProcessId // via PowerShell works without a `windows` crate dep. let script = r#" $w = Add-Type -MemberDefinition ' [DllImport("user32.dll")] public static extern System.IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern int GetWindowThreadProcessId(System.IntPtr hWnd, out int pid); ' -Name W -PassThru $pid = 0 [void]$w::GetWindowThreadProcessId($w::GetForegroundWindow(), [ref]$pid) (Get-Process -Id $pid).ProcessName "#; let output = Command::new("powershell") .args(["-NoProfile", "-Command", script]) .output() .ok()?; if !output.status.success() { return None; } let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); if name.is_empty() { None } else { Some(name) } } fn trigger_paste_keystroke() -> Result { #[cfg(target_os = "linux")] { linux_paste( std::env::var("XDG_SESSION_TYPE").ok().as_deref(), std::env::var_os("WAYLAND_DISPLAY").is_some(), ) } #[cfg(target_os = "macos")] { macos_paste() } #[cfg(target_os = "windows")] { windows_paste() } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] { Err("auto-paste not implemented on this platform".into()) } } /// Send Ctrl+Z / Cmd+Z to the focused window. Mirrors the shape of /// `trigger_paste_keystroke`: reuse the same per-OS backend ordering, /// just swap the keycode. Used by `paste_text_replacing` for the /// raw-revert flow (brief item #17). fn trigger_undo_keystroke() -> Result { #[cfg(target_os = "linux")] { linux_undo( std::env::var("XDG_SESSION_TYPE").ok().as_deref(), std::env::var_os("WAYLAND_DISPLAY").is_some(), ) } #[cfg(target_os = "macos")] { macos_undo() } #[cfg(target_os = "windows")] { windows_undo() } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] { Err("undo-keystroke not implemented on this platform".into()) } } #[cfg(target_os = "linux")] fn linux_paste( xdg_session_type: Option<&str>, wayland_display_set: bool, ) -> Result { for tool in pick_linux_backend_order(xdg_session_type, wayland_display_set) { match run_linux_tool(tool) { Ok(()) => return Ok(tool.to_string()), Err(_) => continue, } } Err( "No paste backend available. Install wtype (Wayland) or xdotool (X11) to enable \ auto-insert-at-cursor." .into(), ) } #[cfg(target_os = "linux")] fn pick_linux_backend_order( xdg_session_type: Option<&str>, wayland_display_set: bool, ) -> &'static [&'static str] { let is_wayland = xdg_session_type .map(|value| value.eq_ignore_ascii_case("wayland")) .unwrap_or(false) || wayland_display_set; if is_wayland { &["wtype", "ydotool", "xdotool"] } else { &["xdotool", "ydotool", "wtype"] } } #[cfg(target_os = "linux")] fn run_linux_tool(tool: &str) -> Result<(), String> { let output = match tool { // wtype -M ctrl v -m ctrl (press ctrl, tap v, release ctrl) "wtype" => Command::new("wtype") .args(["-M", "ctrl", "v", "-m", "ctrl"]) .output(), "xdotool" => Command::new("xdotool").args(["key", "ctrl+v"]).output(), // ydotool linux input keycodes: 29=LEFTCTRL, 47=V. Format is // `code:state` pairs. Requires ydotoold running with access to // /dev/uinput. "ydotool" => Command::new("ydotool") .args(["key", "29:1", "47:1", "47:0", "29:0"]) .output(), other => return Err(format!("unknown backend: {other}")), } .map_err(|e| format!("{tool} unavailable: {e}"))?; if output.status.success() { Ok(()) } else { Err(format!( "{tool} exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(target_os = "linux")] fn linux_undo(xdg_session_type: Option<&str>, wayland_display_set: bool) -> Result { for tool in pick_linux_backend_order(xdg_session_type, wayland_display_set) { match run_linux_undo_tool(tool) { Ok(()) => return Ok(tool.to_string()), Err(_) => continue, } } Err("No undo backend available. Install wtype (Wayland) or xdotool (X11).".into()) } #[cfg(target_os = "linux")] fn run_linux_undo_tool(tool: &str) -> Result<(), String> { let output = match tool { // wtype -M ctrl z -m ctrl (press ctrl, tap z, release ctrl) "wtype" => Command::new("wtype") .args(["-M", "ctrl", "z", "-m", "ctrl"]) .output(), "xdotool" => Command::new("xdotool").args(["key", "ctrl+z"]).output(), // 29 = LEFTCTRL, 44 = Z in linux input keycodes. "ydotool" => Command::new("ydotool") .args(["key", "29:1", "44:1", "44:0", "29:0"]) .output(), other => return Err(format!("unknown backend: {other}")), } .map_err(|e| format!("{tool} unavailable: {e}"))?; if output.status.success() { Ok(()) } else { Err(format!( "{tool} exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(target_os = "linux")] fn which_on_path(tool: &str) -> bool { Command::new("sh") .args(["-c", &format!("command -v {tool}")]) .output() .map(|output| output.status.success()) .unwrap_or(false) } #[cfg(target_os = "macos")] fn macos_paste() -> Result { let output = Command::new("osascript") .args([ "-e", "tell application \"System Events\" to keystroke \"v\" using command down", ]) .output() .map_err(|e| format!("osascript: {e}"))?; if output.status.success() { Ok("osascript".into()) } else { Err(format!( "osascript exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(target_os = "macos")] fn macos_undo() -> Result { let output = Command::new("osascript") .args([ "-e", "tell application \"System Events\" to keystroke \"z\" using command down", ]) .output() .map_err(|e| format!("osascript: {e}"))?; if output.status.success() { Ok("osascript".into()) } else { Err(format!( "osascript exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(target_os = "windows")] fn windows_paste() -> Result { // SendKeys("^v") simulates Ctrl+V in the foreground window. Requires // no extra permissions on Windows. A native SendInput call would skip // the PowerShell spawn but pulls in the windows crate; cost not worth // the complexity until this hot path actually shows up. let output = Command::new("powershell") .args([ "-NoProfile", "-Command", "(New-Object -ComObject WScript.Shell).SendKeys('^v')", ]) .output() .map_err(|e| format!("powershell: {e}"))?; if output.status.success() { Ok("sendkeys".into()) } else { Err(format!( "powershell exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(target_os = "windows")] fn windows_undo() -> Result { let output = Command::new("powershell") .args([ "-NoProfile", "-Command", "(New-Object -ComObject WScript.Shell).SendKeys('^z')", ]) .output() .map_err(|e| format!("powershell: {e}"))?; if output.status.success() { Ok("sendkeys".into()) } else { Err(format!( "powershell exit {}: {}", output.status, String::from_utf8_lossy(&output.stderr).trim() )) } } #[cfg(all(test, target_os = "linux"))] mod tests_linux { use super::pick_linux_backend_order; #[test] fn wayland_session_prefers_wtype_then_ydotool() { assert_eq!( pick_linux_backend_order(Some("wayland"), true), &["wtype", "ydotool", "xdotool"] ); } #[test] fn x11_session_prefers_xdotool() { assert_eq!( pick_linux_backend_order(Some("x11"), false), &["xdotool", "ydotool", "wtype"] ); } #[test] fn wayland_display_env_var_alone_is_enough() { assert_eq!( pick_linux_backend_order(None, true), &["wtype", "ydotool", "xdotool"] ); } #[test] fn uppercase_wayland_token_still_detects_wayland() { assert_eq!( pick_linux_backend_order(Some("WAYLAND"), false), &["wtype", "ydotool", "xdotool"] ); } } #[cfg(test)] mod tests_clipboard_restore { use super::should_restore; #[test] fn restores_when_clipboard_still_holds_transcript() { assert!(should_restore(Some("dictated text"), "dictated text")); } #[test] fn does_not_restore_when_user_copied_something_else() { assert!(!should_restore( Some("user copied this just now"), "dictated text" )); } #[test] fn does_not_restore_when_clipboard_unreadable() { assert!(!should_restore(None, "dictated text")); } #[test] fn does_not_restore_on_empty_mismatch() { assert!(!should_restore(Some(""), "dictated text")); } } #[cfg(test)] mod tests_terminal_classification { use super::classify_terminal; #[test] fn kitty_class_is_a_terminal() { assert_eq!(classify_terminal("kitty"), Some("kitty".into())); } #[test] fn alacritty_matches_case_insensitively() { assert_eq!(classify_terminal("Alacritty"), Some("alacritty".into())); } #[test] fn windows_terminal_matches_collapsed_name() { // Windows reports the process name sans space: "WindowsTerminal" assert_eq!( classify_terminal("WindowsTerminal"), Some("windowsterminal".into()), ); } #[test] fn iterm_matches_iterm2_as_well() { assert_eq!(classify_terminal("iTerm2"), Some("iterm2".into())); } #[test] fn firefox_is_not_a_terminal() { assert_eq!(classify_terminal("firefox"), None); } #[test] fn empty_class_is_not_a_terminal() { assert_eq!(classify_terminal(""), None); } #[test] fn substring_match_survives_xdotool_decorations() { // Some X11 window managers report "Alacritty.Alacritty" assert_eq!( classify_terminal("Alacritty.Alacritty"), Some("alacritty".into()), ); } } #[cfg(test)] mod tests_paste_size_cap { use super::MAX_PASTE_BYTES; /// Pure mirror of the size-cap branch in `paste_text` / /// `paste_text_replacing`. Wiring is tested through the command /// surface; this asserts the rule itself. fn size_check(text: &str) -> Result<(), String> { if text.len() > MAX_PASTE_BYTES { Err(format!( "Paste payload too large ({} bytes; limit {} bytes).", text.len(), MAX_PASTE_BYTES )) } else { Ok(()) } } #[test] fn accepts_typical_dictation_payload() { // ~500 chars of dictated text: comfortably under the cap. let text = "a".repeat(500); assert!(size_check(&text).is_ok()); } #[test] fn rejects_payload_above_cap() { let big = "x".repeat(MAX_PASTE_BYTES + 1); let err = size_check(&big).expect_err("size-cap should reject"); assert!(err.contains("too large")); } #[test] fn paste_cap_matches_clipboard_cap() { // Both surfaces must refuse identically — drift between the // two would let an attacker copy a 1 MiB-plus payload via one // command and synthesise it via the other. assert_eq!( MAX_PASTE_BYTES, crate::commands::clipboard::MAX_CLIPBOARD_BYTES ); } }