From cca68004ab92cacc217f62bf63b9281e43b66c30 Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Fri, 26 Jun 2026 09:32:47 +0700 Subject: [PATCH] fix: use select() on X11 fd for reliable event detection XPending returned 0 even with active keyboard grab. Using select() on the X11 connection file descriptor with 100ms timeout to reliably detect when the X server sends events. Also adds XSelectInput on root window and XConnectionNumber for the fd. --- daemon/src/main.rs | 19 ++++++---- protocol/src/x11_capture.rs | 74 ++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index a2c420f..50a2f03 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -743,6 +743,17 @@ fn run_with_x11( capture.focus_lost = false; } + // Wait for events with 100ms timeout, then re-grab if needed + if !capture.wait_for_event(100) { + // No events — check if grab is still held + if !capture.is_grabbed() { + eprintln!("[vietc] Keyboard grab lost — re-grabbing"); + capture.grab_keyboard(); + } + continue; + } + + // Drain all available events while let Some(event) = capture.next_event() { if event.pressed { // Skip autorepeat @@ -813,14 +824,6 @@ fn run_with_x11( } } } - - // Re-grab if the grab was silently lost (tray started, WM took focus, etc.) - if !capture.is_grabbed() { - eprintln!("[vietc] Keyboard grab lost — re-grabbing"); - capture.grab_keyboard(); - } - - thread::sleep(Duration::from_millis(10)); } } diff --git a/protocol/src/x11_capture.rs b/protocol/src/x11_capture.rs index eb214e2..3ae749a 100644 --- a/protocol/src/x11_capture.rs +++ b/protocol/src/x11_capture.rs @@ -38,6 +38,45 @@ struct X11Lib { x_flush: unsafe extern "C" fn(*mut Display) -> c_int, x_select_input: unsafe extern "C" fn(*mut Display, Window, c_long) -> c_int, x_sync: unsafe extern "C" fn(*mut Display, c_int) -> c_int, + x_connection_number: unsafe extern "C" fn(*mut Display) -> c_int, +} + +// select() timeout struct +#[repr(C)] +struct Timeval { + tv_sec: i64, + tv_usec: i64, +} + +#[repr(C)] +struct FdSet { + fds_bits: [u64; 16], // 1024 bits +} + +extern "C" { + fn select(nfds: c_int, readfds: *mut FdSet, writefds: *mut FdSet, exceptfds: *mut FdSet, timeout: *mut Timeval) -> c_int; +} + +fn fd_zero(set: &mut FdSet) { + set.fds_bits = [0u64; 16]; +} + +fn fd_set(fd: c_int, set: &mut FdSet) { + let idx = fd as usize / 64; + let bit = fd as usize % 64; + if idx < set.fds_bits.len() { + set.fds_bits[idx] |= 1u64 << bit; + } +} + +fn fd_isset(fd: c_int, set: &FdSet) -> bool { + let idx = fd as usize / 64; + let bit = fd as usize % 64; + if idx < set.fds_bits.len() { + (set.fds_bits[idx] & (1u64 << bit)) != 0 + } else { + false + } } impl X11Lib { @@ -81,6 +120,7 @@ impl X11Lib { let x_flush = sym!("XFlush"); let x_select_input = sym!("XSelectInput"); let x_sync = sym!("XSync"); + let x_connection_number = sym!("XConnectionNumber"); Ok(Self { handle, @@ -96,6 +136,7 @@ impl X11Lib { x_flush, x_select_input, x_sync, + x_connection_number, }) } } @@ -233,13 +274,44 @@ impl X11Capture { if !self.grabbed { return false; } - unsafe { (self.lib.x_pending)(self.display) > 0 } + unsafe { + let fd = (self.lib.x_connection_number)(self.display); + let mut readfds: FdSet = std::mem::zeroed(); + fd_zero(&mut readfds); + fd_set(fd, &mut readfds); + let mut timeout = Timeval { tv_sec: 0, tv_usec: 0 }; + let n = select(fd + 1, &mut readfds, std::ptr::null_mut(), std::ptr::null_mut(), &mut timeout); + n > 0 && fd_isset(fd, &readfds) + } } pub fn is_grabbed(&self) -> bool { self.grabbed } + /// Block until an event arrives, with a timeout in milliseconds. + /// Returns true if an event is available, false on timeout. + pub fn wait_for_event(&mut self, timeout_ms: u64) -> bool { + if !self.grabbed { + return false; + } + unsafe { + // Flush pending output first + (self.lib.x_flush)(self.display); + + let fd = (self.lib.x_connection_number)(self.display); + let mut readfds: FdSet = std::mem::zeroed(); + fd_zero(&mut readfds); + fd_set(fd, &mut readfds); + let mut timeout = Timeval { + tv_sec: (timeout_ms / 1000) as i64, + tv_usec: ((timeout_ms % 1000) * 1000) as i64, + }; + let n = select(fd + 1, &mut readfds, std::ptr::null_mut(), std::ptr::null_mut(), &mut timeout); + n > 0 && fd_isset(fd, &readfds) + } + } + pub fn next_event(&mut self) -> Option { if !self.grabbed { return None;