feat: add clipboard paste insertion fallback

This commit is contained in:
Evandro committed 2026-06-11 01:10:36 -03:00
1 parent 7b726af951
commit 0c39063150
1 file changed
+100 -16
+100 -16
View File
@@ -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<String, String> {
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
);
}
}