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
This commit is contained in:
vndangkhoa 2026-06-24 17:41:04 +07:00
parent 96c8006070
commit 0f1f08e79f
2 changed files with 64 additions and 16 deletions

View file

@ -130,6 +130,7 @@ impl Daemon {
let mut commands = Vec::new(); let mut commands = Vec::new();
if let Some(event) = self.engine.process_key(ch) { if let Some(event) = self.engine.process_key(ch) {
eprintln!("[vietc] key='{}' buf='{}' -> {:?}", ch, self.engine.buffer(), event);
match event { match event {
EngineEvent::Flush(text) => { EngineEvent::Flush(text) => {
commands.push(OutputCommand::Type(text)); commands.push(OutputCommand::Type(text));
@ -151,6 +152,8 @@ impl Daemon {
commands.push(OutputCommand::Type(restored)); commands.push(OutputCommand::Type(restored));
} }
} }
} else {
eprintln!("[vietc] key='{}' -> (no event, buf='{}')", ch, self.engine.buffer());
} }
commands commands
@ -576,20 +579,19 @@ fn execute_commands(injector: &dyn vietc_protocol::KeyInjector, commands: &[Outp
for cmd in commands { for cmd in commands {
match cmd { match cmd {
OutputCommand::Backspace(count) => { 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 }; let adjusted = if grabbed { count.saturating_sub(1) } else { *count };
eprintln!("[vietc] cmd: Backspace({}) -> adjusted={}", count, adjusted);
pending_backspaces += adjusted; pending_backspaces += adjusted;
} }
OutputCommand::Type(text) => { OutputCommand::Type(text) => {
eprintln!("[vietc] cmd: Type(\"{}\")", text);
pending_text.push_str(text); pending_text.push_str(text);
} }
} }
} }
if pending_backspaces > 0 || !pending_text.is_empty() { 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.inject_replacement(pending_backspaces, &pending_text);
} }
injector.flush(); injector.flush();

View file

@ -24,7 +24,23 @@ unsafe impl Send for UinputInjector {}
unsafe impl Sync for UinputInjector {} unsafe impl Sync for UinputInjector {}
impl 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, Box<dyn std::error::Error>> { pub fn new(name: &str) -> Result<Self, Box<dyn std::error::Error>> {
Self::start_ydotoold();
let file = OpenOptions::new() let file = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
@ -332,7 +348,30 @@ impl UinputInjector {
{ {
return true; return true;
} }
// Try xclip (X11) // 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") std::process::Command::new("xclip")
.args(["-selection", "clipboard"]) .args(["-selection", "clipboard"])
.stdin(std::process::Stdio::piped()) .stdin(std::process::Stdio::piped())
@ -344,6 +383,13 @@ impl UinputInjector {
}) })
.map(|status| status.success()) .map(|status| status.success())
.unwrap_or(false) .unwrap_or(false)
};
if xclip_result {
return true;
}
false
} }
/// Send Ctrl+V through our uinput device. /// Send Ctrl+V through our uinput device.