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
This commit is contained in:
Mani Mohan 2026-01-09 19:29:21 -08:00 committed by GitHub
parent 1b580d54e8
commit 3bd2ad841d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -59,7 +59,7 @@ fn paste_via_clipboard(
#[cfg(target_os = "linux")]
fn try_send_key_combo_linux(paste_method: &PasteMethod) -> Result<bool, String> {
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<bool, String>
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<bool, String>
#[cfg(target_os = "linux")]
fn try_direct_typing_linux(text: &str) -> Result<bool, String> {
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<bool, String> {
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 <keycode>:<pressed>
// 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> {