diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index dafda45..85de50a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3098,6 +3098,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -3742,6 +3752,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9d29ac6..d92b391 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,7 +26,7 @@ base64 = "0.22" log = "0.4" png = "0.17" dirs = "5" -tokio = { version = "1", features = ["time", "macros"] } +tokio = { version = "1", features = ["time", "macros", "process"] } [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.24" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index f330907..96c904b 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -74,16 +74,33 @@ pub async fn paste_and_refocus(app: tauri::AppHandle) -> Result<(), String> { } // macOS needs time to refocus the previous application - tokio::time::sleep(std::time::Duration::from_millis(250)).await; + tokio::time::sleep(std::time::Duration::from_millis(300)).await; #[cfg(target_os = "macos")] { - // .output() blocks until osascript finishes, ensuring the keystroke lands - std::process::Command::new("osascript") + // Activate the frontmost app explicitly, then send Cmd+V. + // Using tokio::process::Command to avoid blocking the async runtime. + let script = r#" +tell application "System Events" + set frontApp to name of first application process whose frontmost is true +end tell +tell application frontApp to activate +delay 0.1 +tell application "System Events" + keystroke "v" using command down +end tell +"#; + let output = tokio::process::Command::new("osascript") .arg("-e") - .arg(r#"tell application "System Events" to keystroke "v" using command down"#) + .arg(script) .output() - .map_err(|e| format!("osascript failed: {}", e))?; + .await + .map_err(|e| format!("osascript spawn failed: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + log::warn!("osascript paste failed: {}", stderr); + } } Ok(()) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 84cd53e..b8ad61d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -21,7 +21,7 @@ "minWidth": 320, "minHeight": 300, "decorations": true, - "titleBarStyle": "overlay", + "titleBarStyle": "Overlay", "hiddenTitle": true, "visible": false, "alwaysOnTop": true, diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index c1dc315..b749c22 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useState, useCallback } from "react"; import { invoke } from "@tauri-apps/api/core"; import type { Settings } from "../types"; @@ -19,11 +19,17 @@ const POSITION_OPTIONS: { value: string; label: string }[] = [ { value: "bottom-left", label: "Bottom left" }, ]; -export default function SettingsPanel({ settings, onClose, onUpdate }: Props) { +export default function SettingsPanel({ settings: initialSettings, onClose, onUpdate }: Props) { + const [settings, setSettings] = useState(initialSettings); + const updateSetting = useCallback( async (key: string, value: string) => { + // Optimistically update local state for instant visual feedback + setSettings((prev) => ({ ...prev, [key]: parseSettingValue(key, value) })); + await invoke("set_setting", { key, value }); const updated: Settings = await invoke("get_settings"); + setSettings(updated); onUpdate(updated); }, [onUpdate] @@ -122,6 +128,13 @@ export default function SettingsPanel({ settings, onClose, onUpdate }: Props) { ); } +function parseSettingValue(key: string, value: string): unknown { + if (key === "launch_at_login" || key === "show_images") return value === "true"; + if (key === "max_history" || key === "window_width" || key === "window_height") + return parseInt(value, 10); + return value; +} + function ToggleRow({ label, checked,