fix: XEvent was a struct but X11 defines it as a union — all fields at offset 0

CRITICAL BUG: XEvent was { _type, _pad[24], data } (data at offset 28)
but in X11 it's a union where ALL variants start at offset 0. This meant
event.data.key.state/keycode read from completely wrong offsets — every
keystroke produced garbage characters.

Fixed by replacing XEvent with a raw [u8; 192] byte buffer and using
event_type() and key() accessor methods that cast from offset 0.
Also fixed same bug in x11_inject.rs.
This commit is contained in:
Khoa Vo 2026-06-26 09:38:25 +07:00
parent cca68004ab
commit f87e37ebff
2 changed files with 34 additions and 14 deletions

View file

@ -171,15 +171,20 @@ struct XKeyEvent {
} }
#[repr(C)] #[repr(C)]
union XEventData { struct XEvent {
key: XKeyEvent, // XEvent is a union — all variants share offset 0
// sizeof(XEvent) = 192 on x86_64 (long pad[24])
_bytes: [u8; 192],
} }
#[repr(C)] impl XEvent {
struct XEvent { fn event_type(&self) -> c_int {
_type: c_int, unsafe { std::ptr::read_unaligned(self._bytes.as_ptr() as *const c_int) }
_pad: [u8; 24], }
data: XEventData,
fn key(&self) -> &XKeyEvent {
unsafe { &*(self._bytes.as_ptr() as *const XKeyEvent) }
}
} }
type KeySym = u64; type KeySym = u64;
@ -308,7 +313,18 @@ impl X11Capture {
tv_usec: ((timeout_ms % 1000) * 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); let n = select(fd + 1, &mut readfds, std::ptr::null_mut(), std::ptr::null_mut(), &mut timeout);
n > 0 && fd_isset(fd, &readfds) if n > 0 && fd_isset(fd, &readfds) {
true
} else {
// Log on first timeout to diagnose
static mut LOGGED: bool = false;
if !LOGGED {
eprintln!("[vietc] X11 select timeout (fd={}, n={}, timeout={}ms) — no events arriving", fd, n, timeout_ms);
eprintln!("[vietc] Keyboard grab may not be working. Check if another app grabbed the keyboard.");
LOGGED = true;
}
false
}
} }
} }
@ -327,7 +343,7 @@ impl X11Capture {
(self.lib.x_next_event)(self.display, &mut event); (self.lib.x_next_event)(self.display, &mut event);
} }
let _type = event._type; let _type = event.event_type();
// Handle FocusIn/FocusOut — reset engine state when focus changes // Handle FocusIn/FocusOut — reset engine state when focus changes
if _type == FOCUS_OUT { if _type == FOCUS_OUT {
@ -343,7 +359,7 @@ impl X11Capture {
return self.next_event(); return self.next_event();
} }
let key_event = unsafe { &event.data.key }; let key_event = event.key();
let ch = self.lookup_key(key_event); let ch = self.lookup_key(key_event);
Some(X11KeyEvent { Some(X11KeyEvent {
keycode: key_event.keycode, keycode: key_event.keycode,

View file

@ -201,9 +201,13 @@ struct XSelectionRequestEvent {
#[repr(C)] #[repr(C)]
struct XEvent { struct XEvent {
_type: c_int, _bytes: [u8; 192],
_pad: [u8; 24], }
data: [u64; 6],
impl XEvent {
fn event_type(&self) -> c_int {
unsafe { std::ptr::read_unaligned(self._bytes.as_ptr() as *const c_int) }
}
} }
pub struct X11Injector { pub struct X11Injector {
@ -298,7 +302,7 @@ impl X11Injector {
while (self.lib.x_pending)(self.display) > 0 { while (self.lib.x_pending)(self.display) > 0 {
let mut event: XEvent = std::mem::zeroed(); let mut event: XEvent = std::mem::zeroed();
(self.lib.x_next_event)(self.display, &mut event); (self.lib.x_next_event)(self.display, &mut event);
if event._type == SELECTION_REQUEST { if event.event_type() == SELECTION_REQUEST {
let req = &*(&event as *const XEvent as *const XSelectionRequestEvent); let req = &*(&event as *const XEvent as *const XSelectionRequestEvent);
self.handle_selection_request(req); self.handle_selection_request(req);
} }