diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index bcf89df2..a65bdb17 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -84,6 +84,26 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -614,6 +634,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.58" @@ -748,6 +777,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -1161,6 +1196,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "event-listener" version = "5.4.1" @@ -1188,6 +1229,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + [[package]] name = "fdeflate" version = "0.3.7" @@ -1747,6 +1794,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2071,6 +2129,7 @@ dependencies = [ "moxcms", "num-traits", "png 0.18.1", + "tiff", ] [[package]] @@ -2637,6 +2696,7 @@ dependencies = [ "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.2", ] @@ -2890,6 +2950,7 @@ dependencies = [ name = "omnivoice-studio" version = "0.3.5" dependencies = [ + "arboard", "dirs-next", "enigo", "fs4", @@ -3340,6 +3401,12 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.39.2" @@ -4932,6 +4999,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.47" @@ -5732,6 +5813,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -6757,6 +6844,21 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "5.10.1" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index a73746d6..27a73885 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -51,6 +51,10 @@ walkdir = "2" fs4 = "0.13" # Cross-platform home/config directories for pill autostart registration dirs-next = "2" +# Native (OS-side) clipboard write for dictation auto-paste: the widget window +# is unfocused on macOS so the simulated ⌘V reaches the target app, which makes +# the WebView clipboard APIs fail silently there (#287) +arboard = "3" [target.'cfg(windows)'.dependencies] zip = { version = "2", default-features = false, features = ["deflate"] } diff --git a/frontend/src-tauri/src/commands.rs b/frontend/src-tauri/src/commands.rs index 0a975143..6379c7c8 100644 --- a/frontend/src-tauri/src/commands.rs +++ b/frontend/src-tauri/src/commands.rs @@ -258,7 +258,19 @@ fn hf_hub_cache_dir() -> PathBuf { use enigo::{Direction, Enigo, Key, Keyboard, Settings as EnigoSettings}; #[tauri::command] -pub fn simulate_paste() -> Result<(), String> { +pub fn simulate_paste(text: Option) -> Result<(), String> { + // Write the transcript to the clipboard natively first: the widget window + // is intentionally unfocused on macOS (so the simulated ⌘V reaches the + // target app), which makes the WebView clipboard APIs (navigator.clipboard + // / execCommand('copy')) fail silently there (#287). `text` is optional so + // call sites that already populated the clipboard keep working. + if let Some(t) = text { + let mut cb = arboard::Clipboard::new() + .map_err(|e| format!("clipboard init failed: {e}"))?; + cb.set_text(t) + .map_err(|e| format!("clipboard write failed: {e}"))?; + } + std::thread::sleep(Duration::from_millis(80)); let mut enigo = Enigo::new(&EnigoSettings::default()) diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 3d6fca72..eeca5330 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -219,6 +219,11 @@ pub fn run() { let _ = win.center(); } let _ = win.show(); + // Don't steal focus on macOS: the simulated ⌘V from + // simulate_paste() must land in the app the user is + // dictating into — focusing the widget would swallow + // it (#287). + #[cfg(not(target_os = "macos"))] let _ = win.set_focus(); } let _ = app_handle.emit("tray-dictate", ()); diff --git a/frontend/src/components/CaptureWidget.jsx b/frontend/src/components/CaptureWidget.jsx index 481a4574..1bad1472 100644 --- a/frontend/src/components/CaptureWidget.jsx +++ b/frontend/src/components/CaptureWidget.jsx @@ -120,10 +120,14 @@ export default function CaptureWidget({ onDismiss }) { if (data.text) { try { + // Best-effort WebView copy (works in browser mode). In Tauri the + // widget window is unfocused on macOS, where WebView clipboard APIs + // fail silently — so pass the transcript to simulate_paste, which + // writes the clipboard natively (OS-side) before sending ⌘V (#287). await copyText(data.text); try { const { invoke } = await import('@tauri-apps/api/core'); - await invoke('simulate_paste'); + await invoke('simulate_paste', { text: data.text }); } catch { /* not in Tauri */ } } catch { /* clipboard API may fail */ }