From db00ff240f8f745f1b23a4888f2f43d413b34d14 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Fri, 17 Oct 2025 16:25:35 -0700 Subject: [PATCH] Add Setting to Copy to Clipboard (#220) * update transcribe-rs * add copy to clipboard feat --- src-tauri/Cargo.lock | 4 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/clipboard.rs | 17 ++++++-- src-tauri/src/lib.rs | 1 + src-tauri/src/settings.rs | 16 +++++++ src-tauri/src/shortcut.rs | 18 +++++++- src/components/settings/ClipboardHandling.tsx | 43 +++++++++++++++++++ src/components/settings/DebugSettings.tsx | 2 + src/lib/types.ts | 4 ++ src/stores/settingsStore.ts | 2 + 10 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 src/components/settings/ClipboardHandling.tsx diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e88466c..7dd4b66 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6924,9 +6924,9 @@ dependencies = [ [[package]] name = "transcribe-rs" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77dbb8e1048585b7f0bc216d87025953e2f7b04a3e4f4489948f18ab847486a9" +checksum = "856646076d3d9739998ba693ebfff3afe95c0cdb71d4fead32e761b11496094f" dependencies = [ "async-openai", "async-trait", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 966b700..a267368 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -64,7 +64,7 @@ rusqlite = { version = "0.32", features = ["bundled"] } tauri-plugin-sql = { version = "2", features = ["sqlite"] } tar = "0.4.44" flate2 = "1.0" -transcribe-rs = "0.1.3" +transcribe-rs = "0.1.4" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-autostart = "2" diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index a8ec264..07b5210 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -1,4 +1,4 @@ -use crate::settings::{get_settings, PasteMethod}; +use crate::settings::{get_settings, ClipboardHandling, PasteMethod}; use enigo::Enigo; use enigo::Key; use enigo::Keyboard; @@ -83,8 +83,19 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> { println!("Using paste method: {:?}", paste_method); + // Perform the paste operation match paste_method { - PasteMethod::CtrlV => paste_via_clipboard(&text, &app_handle), - PasteMethod::Direct => paste_via_direct_input(&text), + PasteMethod::CtrlV => paste_via_clipboard(&text, &app_handle)?, + PasteMethod::Direct => paste_via_direct_input(&text)?, } + + // After pasting, optionally copy to clipboard based on settings + if settings.clipboard_handling == ClipboardHandling::CopyToClipboard { + let clipboard = app_handle.clipboard(); + clipboard + .write_text(&text) + .map_err(|e| format!("Failed to copy to clipboard: {}", e))?; + } + + Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3ff518b..e2e1bbb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -233,6 +233,7 @@ pub fn run() { shortcut::change_debug_mode_setting, shortcut::change_word_correction_threshold_setting, shortcut::change_paste_method_setting, + shortcut::change_clipboard_handling_setting, shortcut::update_custom_words, shortcut::suspend_binding, shortcut::resume_binding, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 934aad3..af290d8 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -40,6 +40,13 @@ pub enum PasteMethod { Direct, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ClipboardHandling { + DontModify, + CopyToClipboard, +} + impl Default for ModelUnloadTimeout { fn default() -> Self { ModelUnloadTimeout::Never @@ -56,6 +63,12 @@ impl Default for PasteMethod { } } +impl Default for ClipboardHandling { + fn default() -> Self { + ClipboardHandling::DontModify + } +} + impl ModelUnloadTimeout { pub fn to_minutes(self) -> Option { match self { @@ -146,6 +159,8 @@ pub struct AppSettings { pub history_limit: usize, #[serde(default)] pub paste_method: PasteMethod, + #[serde(default)] + pub clipboard_handling: ClipboardHandling, } fn default_model() -> String { @@ -244,6 +259,7 @@ pub fn get_default_settings() -> AppSettings { word_correction_threshold: default_word_correction_threshold(), history_limit: default_history_limit(), paste_method: PasteMethod::default(), + clipboard_handling: ClipboardHandling::default(), } } diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index a0576a1..c9cf0b3 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -5,7 +5,7 @@ use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState}; use crate::actions::ACTION_MAP; use crate::settings::ShortcutBinding; -use crate::settings::{self, get_settings, OverlayPosition, PasteMethod, SoundTheme}; +use crate::settings::{self, get_settings, ClipboardHandling, OverlayPosition, PasteMethod, SoundTheme}; use crate::ManagedToggleState; pub fn init_shortcuts(app: &AppHandle) { @@ -276,6 +276,22 @@ pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(), Ok(()) } +#[tauri::command] +pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Result<(), String> { + let mut settings = settings::get_settings(&app); + let parsed = match handling.as_str() { + "dont_modify" => ClipboardHandling::DontModify, + "copy_to_clipboard" => ClipboardHandling::CopyToClipboard, + other => { + eprintln!("Invalid clipboard handling '{}', defaulting to dont_modify", other); + ClipboardHandling::DontModify + } + }; + settings.clipboard_handling = parsed; + settings::write_settings(&app, settings); + Ok(()) +} + /// Determine whether a shortcut string contains at least one non-modifier key. /// We allow single non-modifier keys (e.g. "f5" or "space") but disallow /// modifier-only combos (e.g. "ctrl" or "ctrl+shift"). diff --git a/src/components/settings/ClipboardHandling.tsx b/src/components/settings/ClipboardHandling.tsx new file mode 100644 index 0000000..2f7fbf7 --- /dev/null +++ b/src/components/settings/ClipboardHandling.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { Dropdown } from "../ui/Dropdown"; +import { SettingContainer } from "../ui/SettingContainer"; +import { useSettings } from "../../hooks/useSettings"; +import type { ClipboardHandling } from "../../lib/types"; + +interface ClipboardHandlingProps { + descriptionMode?: "inline" | "tooltip"; + grouped?: boolean; +} + +const clipboardHandlingOptions = [ + { value: "dont_modify", label: "Don't Modify Clipboard" }, + { value: "copy_to_clipboard", label: "Copy to Clipboard" }, +]; + +export const ClipboardHandlingSetting: React.FC = React.memo(({ + descriptionMode = "tooltip", + grouped = false, +}) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); + + const selectedHandling = (getSetting("clipboard_handling") || + "dont_modify") as ClipboardHandling; + + return ( + + + updateSetting("clipboard_handling", value as ClipboardHandling) + } + disabled={isUpdating("clipboard_handling")} + /> + + ); +}); diff --git a/src/components/settings/DebugSettings.tsx b/src/components/settings/DebugSettings.tsx index fc1ca27..05a5aaf 100644 --- a/src/components/settings/DebugSettings.tsx +++ b/src/components/settings/DebugSettings.tsx @@ -4,6 +4,7 @@ import { AppDataDirectory } from "./AppDataDirectory"; import { SettingsGroup } from "../ui/SettingsGroup"; import { HistoryLimit } from "./HistoryLimit"; import { PasteMethodSetting } from "./PasteMethod"; +import { ClipboardHandlingSetting } from "./ClipboardHandling"; import { AlwaysOnMicrophone } from "./AlwaysOnMicrophone"; import { SoundPicker } from "./SoundPicker"; @@ -12,6 +13,7 @@ export const DebugSettings: React.FC = () => {
+ ; export const PasteMethodSchema = z.enum(["ctrl_v", "direct"]); export type PasteMethod = z.infer; +export const ClipboardHandlingSchema = z.enum(["dont_modify", "copy_to_clipboard"]); +export type ClipboardHandling = z.infer; + export const SettingsSchema = z.object({ bindings: ShortcutBindingsMapSchema, push_to_talk: z.boolean(), @@ -61,6 +64,7 @@ export const SettingsSchema = z.object({ word_correction_threshold: z.number().optional().default(0.18), history_limit: z.number().optional().default(5), paste_method: PasteMethodSchema.optional().default("ctrl_v"), + clipboard_handling: ClipboardHandlingSchema.optional().default("dont_modify"), }); export const BindingResponseSchema = z.object({ diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index 86bc00c..c5e7745 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -98,6 +98,8 @@ const settingUpdaters: { invoke("change_word_correction_threshold_setting", { threshold: value }), paste_method: (value) => invoke("change_paste_method_setting", { method: value }), + clipboard_handling: (value) => + invoke("change_clipboard_handling_setting", { handling: value }), history_limit: (value) => invoke("update_history_limit", { limit: value }), };