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.
This commit is contained in:
parent
d6ded6e706
commit
cca68004ab
2 changed files with 84 additions and 9 deletions
|
|
@ -743,6 +743,17 @@ fn run_with_x11(
|
||||||
capture.focus_lost = false;
|
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() {
|
while let Some(event) = capture.next_event() {
|
||||||
if event.pressed {
|
if event.pressed {
|
||||||
// Skip autorepeat
|
// 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,45 @@ struct X11Lib {
|
||||||
x_flush: unsafe extern "C" fn(*mut Display) -> c_int,
|
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_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_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 {
|
impl X11Lib {
|
||||||
|
|
@ -81,6 +120,7 @@ impl X11Lib {
|
||||||
let x_flush = sym!("XFlush");
|
let x_flush = sym!("XFlush");
|
||||||
let x_select_input = sym!("XSelectInput");
|
let x_select_input = sym!("XSelectInput");
|
||||||
let x_sync = sym!("XSync");
|
let x_sync = sym!("XSync");
|
||||||
|
let x_connection_number = sym!("XConnectionNumber");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
handle,
|
handle,
|
||||||
|
|
@ -96,6 +136,7 @@ impl X11Lib {
|
||||||
x_flush,
|
x_flush,
|
||||||
x_select_input,
|
x_select_input,
|
||||||
x_sync,
|
x_sync,
|
||||||
|
x_connection_number,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,13 +274,44 @@ impl X11Capture {
|
||||||
if !self.grabbed {
|
if !self.grabbed {
|
||||||
return false;
|
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 {
|
pub fn is_grabbed(&self) -> bool {
|
||||||
self.grabbed
|
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<X11KeyEvent> {
|
pub fn next_event(&mut self) -> Option<X11KeyEvent> {
|
||||||
if !self.grabbed {
|
if !self.grabbed {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue