Add ctrl+shift+v as option on windows and linux (#430)
* ctrl+shift+v * clean up some clipboard code * format
This commit is contained in:
parent
81bccdea0b
commit
aaade62f9d
5 changed files with 73 additions and 51 deletions
|
|
@ -48,6 +48,48 @@ fn send_paste_ctrl_v() -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Ctrl+Shift+V paste command.
|
||||
/// This is commonly used in terminal applications on Linux to paste without formatting.
|
||||
fn send_paste_ctrl_shift_v() -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if try_wayland_send_paste(&PasteMethod::CtrlShiftV)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Platform-specific key definitions
|
||||
#[cfg(target_os = "macos")]
|
||||
let (modifier_key, v_key_code) = (Key::Meta, Key::Other(9)); // Cmd+Shift+V on macOS
|
||||
#[cfg(target_os = "windows")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Other(0x56)); // VK_V
|
||||
#[cfg(target_os = "linux")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Unicode('v'));
|
||||
|
||||
let mut enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
|
||||
// Press Ctrl/Cmd + Shift + V
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press modifier key: {}", e))?;
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(v_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click V key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release modifier key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Shift+Insert paste command (Windows and Linux only).
|
||||
/// This is more universal for terminal applications and legacy software.
|
||||
fn send_paste_shift_insert() -> Result<(), String> {
|
||||
|
|
@ -94,53 +136,23 @@ fn paste_via_direct_input(text: &str) -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Pastes text using the clipboard method with Ctrl+V/Cmd+V.
|
||||
/// Saves the current clipboard, writes the text, sends paste command, then restores the clipboard.
|
||||
fn paste_via_clipboard_ctrl_v(text: &str, app_handle: &AppHandle) -> Result<(), String> {
|
||||
/// Pastes text using the clipboard: saves current content, writes text, sends paste keystroke, restores clipboard.
|
||||
fn paste_via_clipboard(
|
||||
text: &str,
|
||||
app_handle: &AppHandle,
|
||||
send_paste: fn() -> Result<(), String>,
|
||||
) -> Result<(), String> {
|
||||
let clipboard = app_handle.clipboard();
|
||||
|
||||
// get the current clipboard content
|
||||
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
||||
|
||||
clipboard
|
||||
.write_text(text)
|
||||
.map_err(|e| format!("Failed to write to clipboard: {}", e))?;
|
||||
|
||||
// small delay to ensure the clipboard content has been written to
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
send_paste()?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
send_paste_ctrl_v()?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
// restore the clipboard
|
||||
clipboard
|
||||
.write_text(&clipboard_content)
|
||||
.map_err(|e| format!("Failed to restore clipboard: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pastes text using the clipboard method with Shift+Insert (Windows/Linux only).
|
||||
/// Saves the current clipboard, writes the text, sends paste command, then restores the clipboard.
|
||||
fn paste_via_clipboard_shift_insert(text: &str, app_handle: &AppHandle) -> Result<(), String> {
|
||||
let clipboard = app_handle.clipboard();
|
||||
|
||||
// get the current clipboard content
|
||||
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
||||
|
||||
clipboard
|
||||
.write_text(text)
|
||||
.map_err(|e| format!("Failed to write to clipboard: {}", e))?;
|
||||
|
||||
// small delay to ensure the clipboard content has been written to
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
send_paste_shift_insert()?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
// restore the clipboard
|
||||
clipboard
|
||||
.write_text(&clipboard_content)
|
||||
.map_err(|e| format!("Failed to restore clipboard: {}", e))?;
|
||||
|
|
@ -192,6 +204,7 @@ fn send_paste_via_wtype(paste_method: &PasteMethod) -> Result<(), String> {
|
|||
let args: Vec<&str> = match paste_method {
|
||||
PasteMethod::CtrlV => vec!["-M", "ctrl", "-k", "v"],
|
||||
PasteMethod::ShiftInsert => vec!["-M", "shift", "-k", "Insert"],
|
||||
PasteMethod::CtrlShiftV => vec!["-M", "ctrl", "-M", "shift", "-k", "v"],
|
||||
_ => return Err("Unsupported paste method".into()),
|
||||
};
|
||||
|
||||
|
|
@ -215,6 +228,7 @@ fn send_paste_via_dotool(paste_method: &PasteMethod) -> Result<(), String> {
|
|||
match paste_method {
|
||||
PasteMethod::CtrlV => command = "echo key ctrl+v | dotool",
|
||||
PasteMethod::ShiftInsert => command = "echo key shift+insert | dotool",
|
||||
PasteMethod::CtrlShiftV => command = "echo key ctrl+shift+v | dotool",
|
||||
_ => return Err("Unsupported paste method".into()),
|
||||
}
|
||||
let output = Command::new("sh")
|
||||
|
|
@ -246,12 +260,16 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
|
|||
// Perform the paste operation
|
||||
match paste_method {
|
||||
PasteMethod::None => {
|
||||
// Intentionally do not perform any paste action; history/clipboard update
|
||||
info!("PasteMethod::None selected - skipping paste action");
|
||||
}
|
||||
PasteMethod::CtrlV => paste_via_clipboard_ctrl_v(&text, &app_handle)?,
|
||||
PasteMethod::Direct => paste_via_direct_input(&text)?,
|
||||
PasteMethod::ShiftInsert => paste_via_clipboard_shift_insert(&text, &app_handle)?,
|
||||
PasteMethod::CtrlV => paste_via_clipboard(&text, &app_handle, send_paste_ctrl_v)?,
|
||||
PasteMethod::CtrlShiftV => {
|
||||
paste_via_clipboard(&text, &app_handle, send_paste_ctrl_shift_v)?
|
||||
}
|
||||
PasteMethod::ShiftInsert => {
|
||||
paste_via_clipboard(&text, &app_handle, send_paste_shift_insert)?
|
||||
}
|
||||
}
|
||||
|
||||
// After pasting, optionally copy to clipboard based on settings
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ pub enum PasteMethod {
|
|||
Direct,
|
||||
None,
|
||||
ShiftInsert,
|
||||
CtrlShiftV,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Type)]
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(),
|
|||
"direct" => PasteMethod::Direct,
|
||||
"none" => PasteMethod::None,
|
||||
"shift_insert" => PasteMethod::ShiftInsert,
|
||||
"ctrl_shift_v" => PasteMethod::CtrlShiftV,
|
||||
other => {
|
||||
warn!("Invalid paste method '{}', defaulting to ctrl_v", other);
|
||||
PasteMethod::CtrlV
|
||||
|
|
|
|||
|
|
@ -623,7 +623,7 @@ export type ModelInfo = { id: string; name: string; description: string; filenam
|
|||
export type ModelLoadStatus = { is_loaded: boolean; current_model: string | null }
|
||||
export type ModelUnloadTimeout = "never" | "immediately" | "min_2" | "min_5" | "min_10" | "min_15" | "hour_1" | "sec_5"
|
||||
export type OverlayPosition = "none" | "top" | "bottom"
|
||||
export type PasteMethod = "ctrl_v" | "direct" | "none" | "shift_insert"
|
||||
export type PasteMethod = "ctrl_v" | "direct" | "none" | "shift_insert" | "ctrl_shift_v"
|
||||
export type PostProcessProvider = { id: string; label: string; base_url: string; allow_base_url_edit?: boolean; models_endpoint?: string | null }
|
||||
export type RecordingRetentionPeriod = "never" | "preserve_limit" | "days_3" | "weeks_2" | "months_3"
|
||||
export type ShortcutBinding = { id: string; name: string; description: string; default_binding: string; current_binding: string }
|
||||
|
|
|
|||
|
|
@ -11,21 +11,23 @@ interface PasteMethodProps {
|
|||
}
|
||||
|
||||
const getPasteMethodOptions = (osType: string) => {
|
||||
const baseOptions = [
|
||||
{ value: "ctrl_v", label: "Clipboard (Ctrl+V)" },
|
||||
const mod = osType === "macos" ? "Cmd" : "Ctrl";
|
||||
|
||||
const options = [
|
||||
{ value: "ctrl_v", label: `Clipboard (${mod}+V)` },
|
||||
{ value: "direct", label: "Direct" },
|
||||
{ value: "none", label: "None" },
|
||||
];
|
||||
|
||||
// Add Shift+Insert option for Windows and Linux only
|
||||
// Add Shift+Insert and Ctrl+Shift+V options for Windows and Linux only
|
||||
if (osType === "windows" || osType === "linux") {
|
||||
baseOptions.push({
|
||||
value: "shift_insert",
|
||||
label: "Clipboard (Shift+Insert)",
|
||||
});
|
||||
options.push(
|
||||
{ value: "ctrl_shift_v", label: "Clipboard (Ctrl+Shift+V)" },
|
||||
{ value: "shift_insert", label: "Clipboard (Shift+Insert)" },
|
||||
);
|
||||
}
|
||||
|
||||
return baseOptions;
|
||||
return options;
|
||||
};
|
||||
|
||||
export const PasteMethodSetting: React.FC<PasteMethodProps> = React.memo(
|
||||
|
|
@ -45,7 +47,7 @@ export const PasteMethodSetting: React.FC<PasteMethodProps> = React.memo(
|
|||
return (
|
||||
<SettingContainer
|
||||
title="Paste Method"
|
||||
description="Ctrl+V: 'pastes' via ctrl+v. Direct: type text directly. Shift+Insert: pastes via shift+insert. None: skip paste; just update history/clipboard."
|
||||
description="Choose how text is inserted. Direct: simulates typing via system input. None: skips paste, only updates history/clipboard."
|
||||
descriptionMode={descriptionMode}
|
||||
grouped={grouped}
|
||||
tooltipPosition="bottom"
|
||||
|
|
|
|||
Loading…
Reference in a new issue