From 3bd2ad841d4a18b1ad92b422260b3b9ef99f2441 Mon Sep 17 00:00:00 2001 From: Mani Mohan Date: Fri, 9 Jan 2026 19:29:21 -0800 Subject: [PATCH] feat(linux): add ydotool support for text input (#557) Add ydotool as a fallback option for direct text input and key combinations on Linux, after wtype/dotool (Wayland) and xdotool (X11). ydotool uses uinput and works on both Wayland and X11, making it a useful alternative when other tools are unavailable. Requirements: - ydotool installed - ydotoold daemon running - User in input group with uinput permissions --- src-tauri/src/clipboard.rs | 81 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index 61f56d9..6fd8f10 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -59,7 +59,7 @@ fn paste_via_clipboard( #[cfg(target_os = "linux")] fn try_send_key_combo_linux(paste_method: &PasteMethod) -> Result { if is_wayland() { - // Wayland: prefer wtype, then dotool + // Wayland: prefer wtype, then dotool, then ydotool if is_wtype_available() { info!("Using wtype for key combo"); send_key_combo_via_wtype(paste_method)?; @@ -70,13 +70,23 @@ fn try_send_key_combo_linux(paste_method: &PasteMethod) -> Result send_key_combo_via_dotool(paste_method)?; return Ok(true); } + if is_ydotool_available() { + info!("Using ydotool for key combo"); + send_key_combo_via_ydotool(paste_method)?; + return Ok(true); + } } else { - // X11: prefer xdotool + // X11: prefer xdotool, then ydotool if is_xdotool_available() { info!("Using xdotool for key combo"); send_key_combo_via_xdotool(paste_method)?; return Ok(true); } + if is_ydotool_available() { + info!("Using ydotool for key combo"); + send_key_combo_via_ydotool(paste_method)?; + return Ok(true); + } } Ok(false) @@ -87,7 +97,7 @@ fn try_send_key_combo_linux(paste_method: &PasteMethod) -> Result #[cfg(target_os = "linux")] fn try_direct_typing_linux(text: &str) -> Result { if is_wayland() { - // Wayland: prefer wtype, then dotool + // Wayland: prefer wtype, then dotool, then ydotool if is_wtype_available() { info!("Using wtype for direct text input"); type_text_via_wtype(text)?; @@ -98,13 +108,23 @@ fn try_direct_typing_linux(text: &str) -> Result { type_text_via_dotool(text)?; return Ok(true); } + if is_ydotool_available() { + info!("Using ydotool for direct text input"); + type_text_via_ydotool(text)?; + return Ok(true); + } } else { - // X11: prefer xdotool + // X11: prefer xdotool, then ydotool if is_xdotool_available() { info!("Using xdotool for direct text input"); type_text_via_xdotool(text)?; return Ok(true); } + if is_ydotool_available() { + info!("Using ydotool for direct text input"); + type_text_via_ydotool(text)?; + return Ok(true); + } } Ok(false) @@ -130,6 +150,16 @@ fn is_dotool_available() -> bool { .unwrap_or(false) } +/// Check if ydotool is available (uinput-based, works on both Wayland and X11) +#[cfg(target_os = "linux")] +fn is_ydotool_available() -> bool { + Command::new("which") + .arg("ydotool") + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + #[cfg(target_os = "linux")] fn is_xdotool_available() -> bool { Command::new("which") @@ -204,6 +234,24 @@ fn type_text_via_dotool(text: &str) -> Result<(), String> { Ok(()) } +/// Type text directly via ydotool (uinput-based, requires ydotoold daemon). +#[cfg(target_os = "linux")] +fn type_text_via_ydotool(text: &str) -> Result<(), String> { + let output = Command::new("ydotool") + .arg("type") + .arg("--") + .arg(text) + .output() + .map_err(|e| format!("Failed to execute ydotool: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("ydotool failed: {}", stderr)); + } + + Ok(()) +} + /// Send a key combination (e.g., Ctrl+V) via wtype on Wayland. #[cfg(target_os = "linux")] fn send_key_combo_via_wtype(paste_method: &PasteMethod) -> Result<(), String> { @@ -250,6 +298,31 @@ fn send_key_combo_via_dotool(paste_method: &PasteMethod) -> Result<(), String> { Ok(()) } +/// Send a key combination (e.g., Ctrl+V) via ydotool (requires ydotoold daemon). +#[cfg(target_os = "linux")] +fn send_key_combo_via_ydotool(paste_method: &PasteMethod) -> Result<(), String> { + // ydotool uses Linux input event keycodes with format : + // where pressed is 1 for down, 0 for up. Keycodes: ctrl=29, shift=42, v=47, insert=110 + let args: Vec<&str> = match paste_method { + PasteMethod::CtrlV => vec!["key", "29:1", "47:1", "47:0", "29:0"], + PasteMethod::ShiftInsert => vec!["key", "42:1", "110:1", "110:0", "42:0"], + PasteMethod::CtrlShiftV => vec!["key", "29:1", "42:1", "47:1", "47:0", "42:0", "29:0"], + _ => return Err("Unsupported paste method".into()), + }; + + let output = Command::new("ydotool") + .args(&args) + .output() + .map_err(|e| format!("Failed to execute ydotool: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("ydotool failed: {}", stderr)); + } + + Ok(()) +} + /// Send a key combination (e.g., Ctrl+V) via xdotool on X11. #[cfg(target_os = "linux")] fn send_key_combo_via_xdotool(paste_method: &PasteMethod) -> Result<(), String> {