From 0c390631504c406ec369bb5d63a42918b99ef17d Mon Sep 17 00:00:00 2001 From: Evandro Date: Thu, 11 Jun 2026 01:10:36 -0300 Subject: [PATCH] feat: add clipboard paste insertion fallback --- src-tauri/src/injection/mod.rs | 116 ++++++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/injection/mod.rs b/src-tauri/src/injection/mod.rs index 29c969a..3c88078 100644 --- a/src-tauri/src/injection/mod.rs +++ b/src-tauri/src/injection/mod.rs @@ -1,4 +1,7 @@ use serde::{Deserialize, Serialize}; +use std::process::Command; + +use arboard::Clipboard; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -68,35 +71,113 @@ pub struct FallbackInjector; impl TextInjector for FallbackInjector { fn can_insert(&self, target: Option<&FocusTarget>) -> InsertCapability { - match target.map(|target| &target.display_server) { - Some(DisplayServer::Windows | DisplayServer::X11) => InsertCapability::Full, - Some(DisplayServer::Wayland) => InsertCapability::Partial, - _ => InsertCapability::Unsupported, + let display_server = target + .map(|target| target.display_server.clone()) + .unwrap_or_else(current_display_server); + + match display_server { + DisplayServer::Windows | DisplayServer::X11 => InsertCapability::Full, + DisplayServer::Wayland => InsertCapability::Partial, + DisplayServer::Unknown => InsertCapability::Unsupported, } } fn insert_text(&self, request: InsertRequest) -> InsertResult { let capability = self.can_insert(request.target.as_ref()); - let message = match capability { - InsertCapability::Full => { - "Inserção automática preparada; implementação nativa será conectada ao adaptador." - } - InsertCapability::Partial => { - "Wayland pode bloquear automação. Texto mantido para cópia manual com Ctrl+V." - } - InsertCapability::Unsupported => { - "Destino não confiável. Copie o texto manualmente a partir do modal." - } + let clipboard_message = copy_to_clipboard(&request.text); + + let message = match (&request.method, &capability) { + (InsertMethod::ManualCopy, _) => clipboard_message.unwrap_or_else(|error| error), + (_, InsertCapability::Full) => match clipboard_message { + Ok(_) => match trigger_paste() { + Ok(_) => "Texto copiado e Ctrl+V acionado no app em foco.".to_string(), + Err(error) => format!( + "Texto copiado. Não consegui acionar Ctrl+V automaticamente: {error}" + ), + }, + Err(error) => error, + }, + (_, InsertCapability::Partial) => match clipboard_message { + Ok(_) => "Texto copiado. No Wayland, cole manualmente com Ctrl+V.".to_string(), + Err(error) => error, + }, + (_, InsertCapability::Unsupported) => match clipboard_message { + Ok(_) => "Texto copiado. Cole manualmente no aplicativo desejado.".to_string(), + Err(error) => error, + }, }; InsertResult { capability, method_used: request.method, - message: message.to_string(), + message, } } } +fn current_display_server() -> DisplayServer { + #[cfg(target_os = "windows")] + { + return DisplayServer::Windows; + } + + #[cfg(target_os = "linux")] + { + let session_type = std::env::var("XDG_SESSION_TYPE") + .unwrap_or_default() + .to_lowercase(); + if session_type == "wayland" || std::env::var_os("WAYLAND_DISPLAY").is_some() { + return DisplayServer::Wayland; + } + if session_type == "x11" || std::env::var_os("DISPLAY").is_some() { + return DisplayServer::X11; + } + } + + DisplayServer::Unknown +} + +fn copy_to_clipboard(text: &str) -> Result { + let mut clipboard = Clipboard::new().map_err(|error| format!("Falha ao acessar clipboard: {error}"))?; + clipboard + .set_text(text.to_string()) + .map_err(|error| format!("Falha ao copiar texto: {error}"))?; + Ok("Texto copiado para o clipboard.".to_string()) +} + +fn trigger_paste() -> Result<(), String> { + #[cfg(target_os = "windows")] + { + let status = Command::new("powershell") + .args([ + "-NoProfile", + "-Command", + "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('^v')", + ]) + .status() + .map_err(|error| error.to_string())?; + return status + .success() + .then_some(()) + .ok_or_else(|| format!("powershell retornou {status}")); + } + + #[cfg(target_os = "linux")] + { + let status = Command::new("xdotool") + .args(["key", "--clearmodifiers", "ctrl+v"]) + .status() + .map_err(|error| error.to_string())?; + return status + .success() + .then_some(()) + .ok_or_else(|| format!("xdotool retornou {status}")); + } + + #[allow(unreachable_code)] + Err("paste automático não suportado neste sistema".to_string()) +} + #[cfg(test)] mod tests { use super::*; @@ -111,6 +192,9 @@ mod tests { process_name: Some("app".to_string()), }; - assert_eq!(injector.can_insert(Some(&target)), InsertCapability::Partial); + assert_eq!( + injector.can_insert(Some(&target)), + InsertCapability::Partial + ); } }