From 8d68edb32131d5f0432a5ca3c80848fcfc23c9ac Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Thu, 2 Jul 2026 13:41:01 +0700 Subject: [PATCH] daemon: fast grab fallback (300ms) to non-grabbed evdev when grab produces no events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In VM environments, EVIOCGRAB on the AT keyboard device succeeds but produces no events — the kernel/VM routing prevents event delivery to the grabber. Previously the daemon waited 30 seconds then exited. Now: after 3 consecutive 100ms poll timeouts (~300ms) with no events received, the grab is released and the daemon continues in non-grabbed evdev mode. In this mode events reach both X (characters appear on screen) and the daemon simultaneously; the daemon applies backspace corrections via uinput. Also removes the 30-second-exit behavior (which locked the keyboard for 30 seconds unnecessarily) and replaces it with the fast fallback. --- daemon/src/main.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 48d7bcd..a40d120 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1126,7 +1126,7 @@ fn run_with_evdev( ) -> Result<(), Box> { let injector = create_injector(display)?; - let grabbed = if daemon.grab_enabled { + let mut grabbed = if daemon.grab_enabled { match device.grab() { Ok(()) => { log_info("[vietc] Keyboard grabbed — race condition eliminated"); @@ -1157,10 +1157,14 @@ fn run_with_evdev( // (catches in-terminal sudo prompts where window stays the same) let mut password_check_counter: u32 = 0; - // Safety: if grab is active and no events arrive for 30 seconds, - // release the grab so the user isn't locked out. + // Safety: if grab is active and no events arrive for 3 seconds, + // release the grab so the user isn't locked out, and continue in + // non-grabbed mode (events reach both X and the daemon; daemon + // applies backspace corrections via uinput). let mut last_event_time = std::time::Instant::now(); let mut last_key_time = std::time::Instant::now(); + // Track consecutive idle polls for fast grab fallback + let mut idle_polls: u32 = 0; log_info("[vietc] Event loop started"); loop { @@ -1174,13 +1178,17 @@ fn run_with_evdev( return Ok(()); } - // Check for event timeout (grab safety) - if grabbed && last_event_time.elapsed() > std::time::Duration::from_secs(30) { + // Grab safety timeout: if the grabbed device produces no events + // (common in VMs where EVIOCGRAB breaks event delivery), release + // the grab after ~300ms idle and continue in non-grabbed mode + // where events reach both X and the daemon. + if grabbed && idle_polls >= 3 && last_event_time.elapsed() > std::time::Duration::from_millis(200) { log_info( - "[vietc] No events for 30s — releasing grab timeout, releasing grab for safety", + "[vietc] No events received via grab — releasing grab, continuing in non-grabbed evdev mode", ); let _ = device.ungrab(); - return Ok(()); + grabbed = false; + continue; } // Poll evdev fd with 100ms timeout so the loop stays responsive @@ -1204,6 +1212,7 @@ fn run_with_evdev( return Err(err.into()); } if poll_ret == 0 { + idle_polls += 1; // No events available — check for background window changes even // without a keypress (the background thread polls every 250ms). if daemon.config.app_state.enabled { @@ -1215,6 +1224,7 @@ fn run_with_evdev( } continue; } + idle_polls = 0; let caps = is_caps_lock_on(&device); let mut key_state = device