Add a paste delay setting (#694)

* Add a paste delay setting.

* format
This commit is contained in:
CJ Pais 2026-02-01 20:58:02 +08:00 committed by GitHub
parent 90bfa73377
commit e9a6108559
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 65 additions and 4 deletions

View file

@ -2,6 +2,7 @@ use crate::input::{self, EnigoState};
use crate::settings::{get_settings, ClipboardHandling, PasteMethod}; use crate::settings::{get_settings, ClipboardHandling, PasteMethod};
use enigo::Enigo; use enigo::Enigo;
use log::info; use log::info;
use std::time::Duration;
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
@ -16,6 +17,7 @@ fn paste_via_clipboard(
text: &str, text: &str,
app_handle: &AppHandle, app_handle: &AppHandle,
paste_method: &PasteMethod, paste_method: &PasteMethod,
paste_delay_ms: u64,
) -> Result<(), String> { ) -> Result<(), String> {
let clipboard = app_handle.clipboard(); let clipboard = app_handle.clipboard();
let clipboard_content = clipboard.read_text().unwrap_or_default(); let clipboard_content = clipboard.read_text().unwrap_or_default();
@ -25,7 +27,7 @@ fn paste_via_clipboard(
.write_text(text) .write_text(text)
.map_err(|e| format!("Failed to write to clipboard: {}", e))?; .map_err(|e| format!("Failed to write to clipboard: {}", e))?;
std::thread::sleep(std::time::Duration::from_millis(50)); std::thread::sleep(Duration::from_millis(paste_delay_ms));
// Send paste key combo // Send paste key combo
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -364,6 +366,7 @@ fn paste_direct(enigo: &mut Enigo, text: &str) -> Result<(), String> {
pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> { pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
let settings = get_settings(&app_handle); let settings = get_settings(&app_handle);
let paste_method = settings.paste_method; let paste_method = settings.paste_method;
let paste_delay_ms = settings.paste_delay_ms;
// Append trailing space if setting is enabled // Append trailing space if setting is enabled
let text = if settings.append_trailing_space { let text = if settings.append_trailing_space {
@ -372,7 +375,10 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
text text
}; };
info!("Using paste method: {:?}", paste_method); info!(
"Using paste method: {:?}, delay: {}ms",
paste_method, paste_delay_ms
);
// Get the managed Enigo instance // Get the managed Enigo instance
let enigo_state = app_handle let enigo_state = app_handle
@ -392,7 +398,13 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
paste_direct(&mut enigo, &text)?; paste_direct(&mut enigo, &text)?;
} }
PasteMethod::CtrlV | PasteMethod::CtrlShiftV | PasteMethod::ShiftInsert => { PasteMethod::CtrlV | PasteMethod::CtrlShiftV | PasteMethod::ShiftInsert => {
paste_via_clipboard(&mut enigo, &text, &app_handle, &paste_method)? paste_via_clipboard(
&mut enigo,
&text,
&app_handle,
&paste_method,
paste_delay_ms,
)?
} }
} }

View file

@ -315,6 +315,8 @@ pub struct AppSettings {
pub experimental_enabled: bool, pub experimental_enabled: bool,
#[serde(default)] #[serde(default)]
pub keyboard_implementation: KeyboardImplementation, pub keyboard_implementation: KeyboardImplementation,
#[serde(default = "default_paste_delay_ms")]
pub paste_delay_ms: u64,
} }
fn default_model() -> String { fn default_model() -> String {
@ -364,6 +366,10 @@ fn default_word_correction_threshold() -> f64 {
0.18 0.18
} }
fn default_paste_delay_ms() -> u64 {
60
}
fn default_history_limit() -> usize { fn default_history_limit() -> usize {
5 5
} }
@ -605,6 +611,7 @@ pub fn get_default_settings() -> AppSettings {
app_language: default_app_language(), app_language: default_app_language(),
experimental_enabled: false, experimental_enabled: false,
keyboard_implementation: KeyboardImplementation::default(), keyboard_implementation: KeyboardImplementation::default(),
paste_delay_ms: default_paste_delay_ms(),
} }
} }

View file

@ -703,7 +703,7 @@ async isLaptop() : Promise<Result<boolean, string>> {
/** user-defined types **/ /** user-defined types **/
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation } export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation; paste_delay_ms?: number }
export type AudioDevice = { index: string; name: string; is_default: boolean } export type AudioDevice = { index: string; name: string; is_default: boolean }
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null } export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard" export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"

View file

@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { type } from "@tauri-apps/plugin-os"; import { type } from "@tauri-apps/plugin-os";
import { WordCorrectionThreshold } from "./WordCorrectionThreshold"; import { WordCorrectionThreshold } from "./WordCorrectionThreshold";
import { LogLevelSelector } from "./LogLevelSelector"; import { LogLevelSelector } from "./LogLevelSelector";
import { PasteDelay } from "./PasteDelay";
import { SettingsGroup } from "../../ui/SettingsGroup"; import { SettingsGroup } from "../../ui/SettingsGroup";
import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone"; import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone";
import { SoundPicker } from "../SoundPicker"; import { SoundPicker } from "../SoundPicker";
@ -27,6 +28,7 @@ export const DebugSettings: React.FC = () => {
description={t("settings.debug.soundTheme.description")} description={t("settings.debug.soundTheme.description")}
/> />
<WordCorrectionThreshold descriptionMode="tooltip" grouped={true} /> <WordCorrectionThreshold descriptionMode="tooltip" grouped={true} />
<PasteDelay descriptionMode="tooltip" grouped={true} />
<AlwaysOnMicrophone descriptionMode="tooltip" grouped={true} /> <AlwaysOnMicrophone descriptionMode="tooltip" grouped={true} />
<ClamshellMicrophoneSelector descriptionMode="tooltip" grouped={true} /> <ClamshellMicrophoneSelector descriptionMode="tooltip" grouped={true} />
{/* Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration */} {/* Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration */}

View file

@ -0,0 +1,36 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Slider } from "../../ui/Slider";
import { useSettings } from "../../../hooks/useSettings";
interface PasteDelayProps {
descriptionMode?: "tooltip" | "inline";
grouped?: boolean;
}
export const PasteDelay: React.FC<PasteDelayProps> = ({
descriptionMode = "tooltip",
grouped = false,
}) => {
const { t } = useTranslation();
const { settings, updateSetting } = useSettings();
const handleDelayChange = (value: number) => {
updateSetting("paste_delay_ms", value);
};
return (
<Slider
value={settings?.paste_delay_ms ?? 60}
onChange={handleDelayChange}
min={10}
max={200}
step={10}
label={t("settings.debug.pasteDelay.title")}
description={t("settings.debug.pasteDelay.description")}
descriptionMode={descriptionMode}
grouped={grouped}
formatValue={(v) => `${v}ms`}
/>
);
};

View file

@ -370,6 +370,10 @@
"description": "Choose the keyboard shortcut backend.", "description": "Choose the keyboard shortcut backend.",
"bindingsReset": "Keyboard shortcuts were incompatible and reset to defaults" "bindingsReset": "Keyboard shortcuts were incompatible and reset to defaults"
}, },
"pasteDelay": {
"title": "Paste Delay",
"description": "Delay before sending paste keystroke (in milliseconds). Increase if wrong text is being pasted."
},
"paths": { "paths": {
"appData": "App Data:", "appData": "App Data:",
"models": "Models:", "models": "Models:",