fix: remove XGrabKeyboard from XRecord path — it blocks event delivery

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().
This commit is contained in:
Khoa Vo 2026-06-26 10:13:27 +07:00
parent 7898768141
commit d1a5f36606
2 changed files with 43 additions and 20 deletions

View file

@ -577,19 +577,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
if display != display::DisplayServer::Wayland { if display != display::DisplayServer::Wayland {
if let Some(mut capture) = X11Capture::new() { if let Some(mut capture) = X11Capture::new() {
if capture.grab_keyboard() { // XRecord captures events globally — no grab needed for capture.
log_info("[vietc] X11 keyboard grabbed — using X11 capture/injection"); // XGrabKeyboard on the same display as XRecord breaks event delivery.
return run_with_x11( log_info("[vietc] X11 XRecord capture active — using X11 capture/injection");
capture, return run_with_x11(
&mut daemon, capture,
shared_active_window, &mut daemon,
config_changed, shared_active_window,
status_changed, config_changed,
engine_enabled, status_changed,
); engine_enabled,
} else { );
log_info("[vietc] X11 grab failed, falling back to evdev");
}
} else { } else {
log_info("[vietc] X11 not available, falling back to evdev"); 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…). // press+release immediately, breaking held-key combos (Ctrl+C, Alt+Tab…).
let mut pressed_keys: HashSet<u32> = HashSet::new(); let mut pressed_keys: HashSet<u32> = HashSet::new();
eprintln!("[vietc] X11 event loop starting");
loop { loop {
if status_changed.load(Ordering::SeqCst) { if status_changed.load(Ordering::SeqCst) {
daemon.sync_status_file(); daemon.sync_status_file();
@ -747,8 +747,13 @@ fn run_with_x11(
let got_data = capture.wait_for_event(100); let got_data = capture.wait_for_event(100);
let evt = capture.next_event(); let evt = capture.next_event();
if evt.is_none() { if evt.is_none() {
static mut LOOP_COUNT: u64 = 0;
unsafe { LOOP_COUNT += 1; }
if got_data { 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() { if !capture.is_grabbed() {
eprintln!("[vietc] Keyboard grab lost — re-grabbing"); eprintln!("[vietc] Keyboard grab lost — re-grabbing");

View file

@ -35,6 +35,7 @@ struct X11Lib {
x_utf8_lookup_string: Option<unsafe extern "C" fn(*mut XKeyEvent, *mut c_char, c_int, *mut KeySym, *mut c_int) -> c_int>, x_utf8_lookup_string: Option<unsafe extern "C" fn(*mut XKeyEvent, *mut c_char, c_int, *mut KeySym, *mut c_int) -> c_int>,
x_flush: unsafe extern "C" fn(*mut Display) -> c_int, x_flush: unsafe extern "C" fn(*mut Display) -> c_int,
x_connection_number: 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 // XRecord
x_record_query_version: unsafe extern "C" fn(*mut Display, *mut c_int, *mut c_int) -> i32, 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, x_record_alloc_range: unsafe extern "C" fn() -> *mut XRecordRange,
@ -153,6 +154,7 @@ impl X11Lib {
}; };
let x_flush = sym!("XFlush"); let x_flush = sym!("XFlush");
let x_connection_number = sym!("XConnectionNumber"); let x_connection_number = sym!("XConnectionNumber");
let x_pending = sym!("XPending");
if xtst_handle.is_null() { if xtst_handle.is_null() {
return Err("Failed to load libXtst.so.6 — install libxtst6".into()); return Err("Failed to load libXtst.so.6 — install libxtst6".into());
@ -184,6 +186,7 @@ impl X11Lib {
x_utf8_lookup_string, x_utf8_lookup_string,
x_flush, x_flush,
x_connection_number, x_connection_number,
x_pending,
x_record_query_version, x_record_query_version,
x_record_alloc_range, x_record_alloc_range,
x_record_create_context, x_record_create_context,
@ -416,11 +419,21 @@ impl X11Capture {
self.grabbed 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 { pub fn wait_for_event(&mut self, timeout_ms: u64) -> bool {
unsafe { unsafe {
(self.lib.x_flush)(self.display); (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 fd = (self.lib.x_connection_number)(self.display);
let mut readfds: FdSet = std::mem::zeroed(); let mut readfds: FdSet = std::mem::zeroed();
fd_zero(&mut readfds); 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); let n = select(fd + 1, &mut readfds, std::ptr::null_mut(), std::ptr::null_mut(), &mut timeout);
if n > 0 && fd_isset(fd, &readfds) { 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); (self.lib.x_record_process_replies)(self.display);
true true
} else { } else {
@ -470,10 +484,14 @@ impl X11Capture {
where where
F: FnOnce() -> T, F: FnOnce() -> T,
{ {
self.ungrab_keyboard(); if self.grabbed {
let result = f(); self.ungrab_keyboard();
self.grab_keyboard(); let result = f();
result self.grab_keyboard();
result
} else {
f()
}
} }
pub fn lookup_keycode(&self, keycode: u32, state: c_int) -> Option<char> { pub fn lookup_keycode(&self, keycode: u32, state: c_int) -> Option<char> {