diff --git a/src-tauri/src/clipboard.rs b/src-tauri/src/clipboard.rs index 634dd90..f5ebf92 100644 --- a/src-tauri/src/clipboard.rs +++ b/src-tauri/src/clipboard.rs @@ -7,7 +7,7 @@ use tauri::{AppHandle, Manager}; use tauri_plugin_clipboard_manager::ClipboardExt; #[cfg(target_os = "linux")] -use crate::utils::is_wayland; +use crate::utils::{is_kde_wayland, is_wayland}; #[cfg(target_os = "linux")] use std::process::Command; @@ -23,9 +23,23 @@ fn paste_via_clipboard( let clipboard_content = clipboard.read_text().unwrap_or_default(); // Write text to clipboard first - clipboard + // On Wayland, prefer wl-copy for better compatibility (especially with umlauts) + #[cfg(target_os = "linux")] + let write_result = if is_wayland() && is_wl_copy_available() { + info!("Using wl-copy for clipboard write on Wayland"); + write_clipboard_via_wl_copy(text) + } else { + clipboard + .write_text(text) + .map_err(|e| format!("Failed to write to clipboard: {}", e)) + }; + + #[cfg(not(target_os = "linux"))] + let write_result = clipboard .write_text(text) - .map_err(|e| format!("Failed to write to clipboard: {}", e))?; + .map_err(|e| format!("Failed to write to clipboard: {}", e)); + + write_result?; std::thread::sleep(Duration::from_millis(paste_delay_ms)); @@ -49,9 +63,16 @@ fn paste_via_clipboard( std::thread::sleep(std::time::Duration::from_millis(50)); // Restore original clipboard content - clipboard - .write_text(&clipboard_content) - .map_err(|e| format!("Failed to restore clipboard: {}", e))?; + // On Wayland, prefer wl-copy for better compatibility + #[cfg(target_os = "linux")] + if is_wayland() && is_wl_copy_available() { + let _ = write_clipboard_via_wl_copy(&clipboard_content); + } else { + let _ = clipboard.write_text(&clipboard_content); + } + + #[cfg(not(target_os = "linux"))] + let _ = clipboard.write_text(&clipboard_content); Ok(()) } @@ -61,8 +82,9 @@ 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, then ydotool - if is_wtype_available() { + // Wayland: prefer wtype (but not on KDE), then dotool, then ydotool + // Note: wtype doesn't work on KDE (no zwp_virtual_keyboard_manager_v1 support) + if !is_kde_wayland() && is_wtype_available() { info!("Using wtype for key combo"); send_key_combo_via_wtype(paste_method)?; return Ok(true); @@ -99,8 +121,15 @@ 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() { + // KDE Wayland: prefer kwtype (uses KDE Fake Input protocol, supports umlauts) + if is_kde_wayland() && is_kwtype_available() { + info!("Using kwtype for direct text input on KDE Wayland"); + type_text_via_kwtype(text)?; + return Ok(true); + } // Wayland: prefer wtype, then dotool, then ydotool - if is_wtype_available() { + // Note: wtype doesn't work on KDE (no zwp_virtual_keyboard_manager_v1 support) + if !is_kde_wayland() && is_wtype_available() { info!("Using wtype for direct text input"); type_text_via_wtype(text)?; return Ok(true); @@ -171,6 +200,26 @@ fn is_xdotool_available() -> bool { .unwrap_or(false) } +/// Check if kwtype is available (KDE Wayland virtual keyboard input tool) +#[cfg(target_os = "linux")] +fn is_kwtype_available() -> bool { + Command::new("which") + .arg("kwtype") + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + +/// Check if wl-copy is available (Wayland clipboard tool) +#[cfg(target_os = "linux")] +fn is_wl_copy_available() -> bool { + Command::new("which") + .arg("wl-copy") + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + /// Type text directly via wtype on Wayland. #[cfg(target_os = "linux")] fn type_text_via_wtype(text: &str) -> Result<(), String> { @@ -254,6 +303,40 @@ fn type_text_via_ydotool(text: &str) -> Result<(), String> { Ok(()) } +/// Type text directly via kwtype (KDE Wayland virtual keyboard, uses KDE Fake Input protocol). +#[cfg(target_os = "linux")] +fn type_text_via_kwtype(text: &str) -> Result<(), String> { + let output = Command::new("kwtype") + .arg("--") + .arg(text) + .output() + .map_err(|e| format!("Failed to execute kwtype: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("kwtype failed: {}", stderr)); + } + + Ok(()) +} + +/// Write text to clipboard via wl-copy (Wayland clipboard tool). +#[cfg(target_os = "linux")] +fn write_clipboard_via_wl_copy(text: &str) -> Result<(), String> { + let output = Command::new("wl-copy") + .arg("--") + .arg(text) + .output() + .map_err(|e| format!("Failed to execute wl-copy: {}", e))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("wl-copy 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> { diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 29d8704..10a4a6c 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -52,3 +52,18 @@ pub fn is_wayland() -> bool { .map(|v| v.to_lowercase() == "wayland") .unwrap_or(false) } + +/// Check if running on KDE Plasma desktop environment +#[cfg(target_os = "linux")] +pub fn is_kde_plasma() -> bool { + std::env::var("XDG_CURRENT_DESKTOP") + .map(|v| v.to_uppercase().contains("KDE")) + .unwrap_or(false) + || std::env::var("KDE_SESSION_VERSION").is_ok() +} + +/// Check if running on KDE Plasma with Wayland +#[cfg(target_os = "linux")] +pub fn is_kde_wayland() -> bool { + is_wayland() && is_kde_plasma() +}