From 41ecc48b0a1eb7bb1eca06243aabf5021f75848d Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Thu, 2 Jul 2026 13:32:54 +0700 Subject: [PATCH] daemon: use poll() with 100ms timeout for evdev reads instead of blocking fetch_events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original fetch_events() call blocked indefinitely on the evdev device. In VM environments, the grabbed keyboard device may not deliver events after the initial batch, causing the 30-second safety timeout to trigger silently — the daemon exits, the grab is released, and subsequent keystrokes bypass the IME entirely. Replace with libc::poll() with a 100ms timeout so the event loop stays responsive. When poll returns 0 (timeout), the loop checks signals, the 30-second grab-safety timeout, and also polls for background window changes. This ensures the safety timeout actually fires as expected, and the daemon correctly detects and handles the no-event condition. Also check for background window class changes during idle periods (no keypress events) so app detection works consistently. --- daemon/src/main.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 5128be7..00f8333 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use std::collections::HashSet; use std::fs; +use std::os::unix::io::AsRawFd; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -1169,6 +1170,39 @@ fn run_with_evdev( return Ok(()); } + // Poll evdev fd with 100ms timeout so the loop stays responsive + // even when no keyboard events arrive (e.g. VM doesn't route input + // through the grabbed device). + let mut pfd = libc::pollfd { + fd: device.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }; + let poll_ret = unsafe { libc::poll(&mut pfd, 1, 100) }; + if poll_ret < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + log_info(&format!( + "[vietc] poll error on evdev fd: {:?} — exiting", + err + )); + return Err(err.into()); + } + if poll_ret == 0 { + // No events available — check for background window changes even + // without a keypress (the background thread polls every 250ms). + if daemon.config.app_state.enabled { + let class = shared_window_class.lock().unwrap().clone(); + if !class.is_empty() && class != last_window_class { + last_window_class = class.clone(); + daemon.check_app_change_with(last_window_class.clone()); + } + } + continue; + } + let caps = is_caps_lock_on(&device); let mut key_state = device .get_key_state()