From 0f1f08e79f5c5ed0a433003bfdbf48c8c145c2b0 Mon Sep 17 00:00:00 2001 From: vndangkhoa Date: Wed, 24 Jun 2026 17:41:04 +0700 Subject: [PATCH] Fix Unicode injection: ydotoold + xclip fallback + input logging - Start ydotoold automatically so ydotool can handle Vietnamese chars - Fix xclip clipboard to run as SUDO_USER when daemon is root - Add detailed input logging for easier debugging --- daemon/src/main.rs | 10 +++-- protocol/src/uinput_monitor.rs | 70 ++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 7a1a620..40144f5 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -130,6 +130,7 @@ impl Daemon { let mut commands = Vec::new(); if let Some(event) = self.engine.process_key(ch) { + eprintln!("[vietc] key='{}' buf='{}' -> {:?}", ch, self.engine.buffer(), event); match event { EngineEvent::Flush(text) => { commands.push(OutputCommand::Type(text)); @@ -151,6 +152,8 @@ impl Daemon { commands.push(OutputCommand::Type(restored)); } } + } else { + eprintln!("[vietc] key='{}' -> (no event, buf='{}')", ch, self.engine.buffer()); } commands @@ -576,20 +579,19 @@ fn execute_commands(injector: &dyn vietc_protocol::KeyInjector, commands: &[Outp for cmd in commands { match cmd { OutputCommand::Backspace(count) => { - // The engine adds +1 to account for the current character key. - // With grabbing that key was never forwarded to the app, so - // we subtract 1. Without grab, the key WAS forwarded, so we - // use the full count. let adjusted = if grabbed { count.saturating_sub(1) } else { *count }; + eprintln!("[vietc] cmd: Backspace({}) -> adjusted={}", count, adjusted); pending_backspaces += adjusted; } OutputCommand::Type(text) => { + eprintln!("[vietc] cmd: Type(\"{}\")", text); pending_text.push_str(text); } } } if pending_backspaces > 0 || !pending_text.is_empty() { + eprintln!("[vietc] inject: BS={} text=\"{}\"", pending_backspaces, pending_text); injector.inject_replacement(pending_backspaces, &pending_text); } injector.flush(); diff --git a/protocol/src/uinput_monitor.rs b/protocol/src/uinput_monitor.rs index 7ddf5fd..a6ae56d 100644 --- a/protocol/src/uinput_monitor.rs +++ b/protocol/src/uinput_monitor.rs @@ -24,7 +24,23 @@ unsafe impl Send for UinputInjector {} unsafe impl Sync for UinputInjector {} impl UinputInjector { + fn start_ydotoold() { + // ydotoold must be running for ydotool to handle Unicode characters. + // ydotool in direct mode crashes with "no matching keycode" for + // non-ASCII chars. Start it once; ignore failure (daemon may already + // exist). + let _ = std::process::Command::new("ydotoold") + .arg("--fork") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn(); + // Give it a moment to start + std::thread::sleep(std::time::Duration::from_millis(200)); + } + pub fn new(name: &str) -> Result> { + Self::start_ydotoold(); + let file = OpenOptions::new() .read(true) .write(true) @@ -332,18 +348,48 @@ impl UinputInjector { { return true; } - // Try xclip (X11) - std::process::Command::new("xclip") - .args(["-selection", "clipboard"]) - .stdin(std::process::Stdio::piped()) - .spawn() - .and_then(|mut child| { - use std::io::Write; - child.stdin.take().unwrap().write_all(s.as_bytes())?; - child.wait() - }) - .map(|status| status.success()) - .unwrap_or(false) + // Try xclip (X11). When root, run as SUDO_USER so it can connect to X. + let xclip_result = if is_root { + if let Ok(sudo_user) = std::env::var("SUDO_USER") { + let display = std::env::var("DISPLAY").unwrap_or_default(); + let mut cmd = std::process::Command::new("sudo"); + cmd.args(["-u", &sudo_user, "env"]); + if !display.is_empty() { + cmd.arg(format!("DISPLAY={}", display)); + } + cmd.arg("xclip"); + cmd.args(["-selection", "clipboard"]); + cmd.stdin(std::process::Stdio::piped()) + .spawn() + .and_then(|mut child| { + use std::io::Write; + child.stdin.take().unwrap().write_all(s.as_bytes())?; + child.wait() + }) + .map(|status| status.success()) + .unwrap_or(false) + } else { + false + } + } else { + std::process::Command::new("xclip") + .args(["-selection", "clipboard"]) + .stdin(std::process::Stdio::piped()) + .spawn() + .and_then(|mut child| { + use std::io::Write; + child.stdin.take().unwrap().write_all(s.as_bytes())?; + child.wait() + }) + .map(|status| status.success()) + .unwrap_or(false) + }; + + if xclip_result { + return true; + } + + false } /// Send Ctrl+V through our uinput device.