From d1a5f366069f2edf844d1341b4409b7d4001dfab Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Fri, 26 Jun 2026 10:13:27 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20remove=20XGrabKeyboard=20from=20XRecord?= =?UTF-8?q?=20path=20=E2=80=94=20it=20blocks=20event=20delivery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XGrabKeyboard on the same display as XRecord breaks event delivery. XRecord captures events globally without any grab needed. Also: use XPending() before select() to check Xlib internal buffer, and add XFlush before XRecordProcessReplies after select(). --- daemon/src/main.rs | 33 +++++++++++++++++++-------------- protocol/src/x11_capture.rs | 30 ++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index ff3c669..1bb4282 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -577,19 +577,17 @@ fn main() -> Result<(), Box> { #[cfg(feature = "x11")] if display != display::DisplayServer::Wayland { if let Some(mut capture) = X11Capture::new() { - if capture.grab_keyboard() { - log_info("[vietc] X11 keyboard grabbed — using X11 capture/injection"); - return run_with_x11( - capture, - &mut daemon, - shared_active_window, - config_changed, - status_changed, - engine_enabled, - ); - } else { - log_info("[vietc] X11 grab failed, falling back to evdev"); - } + // XRecord captures events globally — no grab needed for capture. + // XGrabKeyboard on the same display as XRecord breaks event delivery. + log_info("[vietc] X11 XRecord capture active — using X11 capture/injection"); + return run_with_x11( + capture, + &mut daemon, + shared_active_window, + config_changed, + status_changed, + engine_enabled, + ); } else { log_info("[vietc] X11 not available, falling back to evdev"); } @@ -710,6 +708,8 @@ fn run_with_x11( // press+release immediately, breaking held-key combos (Ctrl+C, Alt+Tab…). let mut pressed_keys: HashSet = HashSet::new(); + eprintln!("[vietc] X11 event loop starting"); + loop { if status_changed.load(Ordering::SeqCst) { daemon.sync_status_file(); @@ -747,8 +747,13 @@ fn run_with_x11( let got_data = capture.wait_for_event(100); let evt = capture.next_event(); if evt.is_none() { + static mut LOOP_COUNT: u64 = 0; + unsafe { LOOP_COUNT += 1; } if got_data { - eprintln!("[vietc] DEBUG: select said data but no event in queue"); + eprintln!("[vietc] DEBUG: select said data but no event in queue (loop={})", unsafe { LOOP_COUNT }); + } + if unsafe { LOOP_COUNT } <= 3 || unsafe { LOOP_COUNT } % 50 == 0 { + eprintln!("[vietc] DEBUG: no event, grabbed={}, got_data={}", capture.is_grabbed(), got_data); } if !capture.is_grabbed() { eprintln!("[vietc] Keyboard grab lost — re-grabbing"); diff --git a/protocol/src/x11_capture.rs b/protocol/src/x11_capture.rs index 3a70499..e30717d 100644 --- a/protocol/src/x11_capture.rs +++ b/protocol/src/x11_capture.rs @@ -35,6 +35,7 @@ struct X11Lib { x_utf8_lookup_string: Option c_int>, x_flush: unsafe extern "C" fn(*mut Display) -> c_int, x_connection_number: unsafe extern "C" fn(*mut Display) -> c_int, + x_pending: unsafe extern "C" fn(*mut Display) -> c_int, // XRecord x_record_query_version: unsafe extern "C" fn(*mut Display, *mut c_int, *mut c_int) -> i32, x_record_alloc_range: unsafe extern "C" fn() -> *mut XRecordRange, @@ -153,6 +154,7 @@ impl X11Lib { }; let x_flush = sym!("XFlush"); let x_connection_number = sym!("XConnectionNumber"); + let x_pending = sym!("XPending"); if xtst_handle.is_null() { return Err("Failed to load libXtst.so.6 — install libxtst6".into()); @@ -184,6 +186,7 @@ impl X11Lib { x_utf8_lookup_string, x_flush, x_connection_number, + x_pending, x_record_query_version, x_record_alloc_range, x_record_create_context, @@ -416,11 +419,21 @@ impl X11Capture { self.grabbed } - /// Wait for XRecord data to arrive on the X11 connection fd, with timeout. + /// Wait for XRecord data to arrive, with timeout. + /// Uses XPending() first (checks Xlib internal buffer), then select() on fd. pub fn wait_for_event(&mut self, timeout_ms: u64) -> bool { unsafe { (self.lib.x_flush)(self.display); + // First check: XPending reads from Xlib's internal buffer. + // XRecord data may already be buffered there by a previous read. + let pending = (self.lib.x_pending)(self.display); + if pending > 0 { + (self.lib.x_record_process_replies)(self.display); + return true; + } + + // Second check: select() on the X11 socket fd let fd = (self.lib.x_connection_number)(self.display); let mut readfds: FdSet = std::mem::zeroed(); fd_zero(&mut readfds); @@ -431,7 +444,8 @@ impl X11Capture { }; let n = select(fd + 1, &mut readfds, std::ptr::null_mut(), std::ptr::null_mut(), &mut timeout); if n > 0 && fd_isset(fd, &readfds) { - // Process XRecord replies — this fires the callback + // Flush to move data from socket into Xlib buffer + (self.lib.x_flush)(self.display); (self.lib.x_record_process_replies)(self.display); true } else { @@ -470,10 +484,14 @@ impl X11Capture { where F: FnOnce() -> T, { - self.ungrab_keyboard(); - let result = f(); - self.grab_keyboard(); - result + if self.grabbed { + self.ungrab_keyboard(); + let result = f(); + self.grab_keyboard(); + result + } else { + f() + } } pub fn lookup_keycode(&self, keycode: u32, state: c_int) -> Option {