fix: non-grab mode uses event sourcing (replay_and_inject) to avoid double-letter race conditions
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions

This commit is contained in:
Khoa Vo 2026-07-01 13:42:02 +07:00
parent 82d0796059
commit 3612939643

View file

@ -1205,35 +1205,27 @@ fn run_with_evdev(
} }
if !grabbed { if !grabbed {
// Legacy mode: only forward to engine on press events. // Non-grabbing mode: raw keystrokes reach the application
// Raw keystrokes reach the application BEFORE the daemon // directly. Use Event Sourcing (replay_and_inject) which
// can correct them, so VNI/Telex control keys (1-9, f,s,r,...) // tracks what's actually on screen and computes corrections
// appear on screen as literal characters. We must account // based on the screen state — not the engine's internal
// for this by adding one extra backspace for control keys. // buffer. This avoids race conditions where a correction
if value != 1 { // backspace removes the wrong characters because the user
continue; // typed more while the clipboard paste was in flight.
} if value == 1 {
if is_modifier_pressed(&key_state) { if is_modifier_pressed(&key_state) {
continue; continue;
} }
if let Some(ch) = key_to_char(key) { if let Some(ch) = key_to_char(key) {
let mut commands = daemon.process_key(ch); if ch == '\x08' {
// In non-grabbing mode, VNI/Telex control keys (1-9, f,s,r,x,j) // Backspace: let the raw key through, but also
// reach the application as literal characters before the daemon // update the event sourcing state.
// can inject corrections. Add one extra backspace to remove daemon.replay_backspace();
// the control character from the screen. } else {
if !commands.is_empty() let commands = daemon.replay_and_inject(ch);
&& is_vn_control_key(&daemon.config.input_method, ch) execute_commands(&*injector, &commands, false);
{
// Find and increment the first Backspace command
for cmd in &mut commands {
if let OutputCommand::Backspace(ref mut n) = cmd {
*n += 1;
break;
}
} }
} }
execute_commands(&*injector, &commands, false);
} }
} else { } else {
// Grabbing mode: all output goes through uinput only. // Grabbing mode: all output goes through uinput only.