use crcbl_core::input::{ButtonState, Keysym, Modifiers, Scancode};
use super::{
Conn, DisplayMode, KEYBOARD_DEVICE, KeyCode, LOCK_RECENTRE_FRACTION, POINTER_DEVICE,
PhysicalPoint, PhysicalSize, PointerMode, ShellEvent, WindowConfiguration, WindowId, X11Shell,
ffi, keys, read_wire, xkb,
};
const NOTIFY_GRAB: u8 = 1;
const NOTIFY_UNGRAB: u8 = 2;
type Raw = Vec<u8>;
impl X11Shell {
fn collect_events(&mut self) -> Vec<Raw> {
let mut batch = Vec::new();
loop {
let event = unsafe { (self.conn.lib.poll_for_event)(self.conn.raw()) };
if event.is_null() {
break;
}
let bytes = unsafe {
let header = *event;
let size = if header.response_type & 0x7f == ffi::event::GE_GENERIC {
36 + (header.pad[0] as usize).saturating_mul(4)
} else {
32
};
core::slice::from_raw_parts(event.cast::<u8>(), size).to_vec()
};
unsafe { ffi::free_reply(event) };
batch.push(bytes);
if batch.len() >= MAX_EVENTS_PER_PUMP {
self.undrained_events = true;
break;
}
}
batch
}
pub(super) fn drain(&mut self) {
self.undrained_events = false;
let batch = self.collect_events();
let mut index = 0;
while index < batch.len() {
let event = &batch[index];
if !self.conn.detectable_auto_repeat && is_repeat_pair(event, batch.get(index + 1)) {
self.handle_key(&batch[index + 1], true);
index += 2;
continue;
}
self.handle_event(event);
index += 1;
}
if let Some(detail) = self.conn.has_error()
&& self.lost.is_none()
{
crcbl_core::log::error!("x11 connection lost: {detail}");
self.lost = Some(detail);
}
}
fn handle_event(&mut self, raw: &Raw) {
let response = raw[0] & 0x7f;
if let Some(base) = self.conn.randr_event_base
&& response >= base
&& response <= base.saturating_add(1)
{
self.handle_monitors_changed();
return;
}
match response {
ffi::event::KEY_PRESS => self.handle_key(raw, false),
ffi::event::KEY_RELEASE => self.handle_key(raw, false),
ffi::event::BUTTON_PRESS => self.handle_button(raw, true),
ffi::event::BUTTON_RELEASE => self.handle_button(raw, false),
ffi::event::MOTION_NOTIFY => self.handle_motion(raw),
ffi::event::ENTER_NOTIFY => self.handle_crossing(raw, true),
ffi::event::LEAVE_NOTIFY => self.handle_crossing(raw, false),
ffi::event::FOCUS_IN => self.handle_focus(raw, true),
ffi::event::FOCUS_OUT => self.handle_focus(raw, false),
ffi::event::CONFIGURE_NOTIFY => self.handle_configure(raw),
ffi::event::MAP_NOTIFY => self.handle_map(raw, true),
ffi::event::UNMAP_NOTIFY => self.handle_map(raw, false),
ffi::event::DESTROY_NOTIFY => self.handle_destroy(raw),
ffi::event::CLIENT_MESSAGE => self.handle_client_message(raw),
ffi::event::PROPERTY_NOTIFY => self.handle_property_notify(raw),
ffi::event::SELECTION_CLEAR => self.handle_selection_clear(raw),
ffi::event::SELECTION_REQUEST => self.handle_selection_request(raw),
ffi::event::SELECTION_NOTIFY => self.handle_selection_notify(raw),
ffi::event::MAPPING_NOTIFY => self.handle_mapping_notify(raw),
ffi::event::GE_GENERIC => self.handle_generic(raw),
_ => {}
}
}
fn handle_key(&mut self, raw: &Raw, paired: bool) {
let Some(event) = read_wire::<ffi::InputEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.event) else {
return;
};
let pressed = event.response_type & 0x7f == ffi::event::KEY_PRESS;
let repeat = paired || (pressed && self.held.contains(&event.detail));
self.last_server_time = event.time;
let time = self.time.event_time(event.time);
let Some(scancode) = keys::scancode(event.detail) else {
return;
};
let keysym = self
.keymap
.as_ref()
.map_or(Keysym::NONE, |keymap| keymap.keysym(scancode));
let text = if pressed {
self.keymap
.as_mut()
.and_then(|keymap| keymap.text(scancode))
} else {
None
};
let modifiers = self.modifiers();
if let Some(keymap) = self.keymap.as_mut() {
keymap.update_key(u32::from(event.detail), pressed);
}
if pressed {
if !self.held.contains(&event.detail) {
self.held.push(event.detail);
}
} else {
self.held.retain(|held| *held != event.detail);
}
self.queue.push_back(ShellEvent::Key {
window,
device: KEYBOARD_DEVICE,
time,
scancode: Scancode(scancode),
key_code: keys::key_code(event.detail),
keysym,
state: if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
repeat,
modifiers,
});
if let Some(text) = text {
self.queue
.push_back(ShellEvent::TextCommit { window, time, text });
}
}
fn handle_button(&mut self, raw: &Raw, pressed: bool) {
let Some(event) = read_wire::<ffi::InputEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.event) else {
return;
};
self.last_server_time = event.time;
let time = self.time.event_time(event.time);
let modifiers = self.modifiers();
let position = self.position_of(window, &event);
match keys::button(event.detail, pressed) {
keys::Button::Press(button) => self.queue.push_back(ShellEvent::Button {
window,
device: POINTER_DEVICE,
time,
button,
state: if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
position,
modifiers,
}),
keys::Button::Scroll(delta) => self.queue.push_back(ShellEvent::Wheel {
window,
device: POINTER_DEVICE,
time,
delta,
position,
modifiers,
}),
keys::Button::ScrollRelease => {}
}
}
fn handle_motion(&mut self, raw: &Raw) {
let Some(event) = read_wire::<ffi::InputEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.event) else {
return;
};
self.last_server_time = event.time;
let time = self.time.event_time(event.time);
let locked = self
.window(window)
.is_ok_and(|state| state.pointer_mode == PointerMode::Locked);
if locked {
self.recentre_if_near_edge(window, &event);
return;
}
let position = self.position_of(window, &event);
self.queue.push_back(ShellEvent::PointerMotion {
window,
device: POINTER_DEVICE,
time,
abs: position,
raw_delta: None,
});
}
fn handle_crossing(&mut self, raw: &Raw, entered: bool) {
let Some(event) = read_wire::<ffi::InputEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.event) else {
return;
};
self.last_server_time = event.time;
let time = self.time.event_time(event.time);
let position = if entered {
self.position_of(window, &event)
} else {
None
};
if let Ok(state) = self.window_mut(window) {
state.pointer_inside = entered;
}
self.queue.push_back(ShellEvent::PointerFocus {
window,
device: POINTER_DEVICE,
time,
entered,
position,
});
}
fn handle_focus(&mut self, raw: &Raw, focused: bool) {
let Some(event) = read_wire::<ffi::FocusEvent>(raw) else {
return;
};
if event.mode == NOTIFY_GRAB || event.mode == NOTIFY_UNGRAB {
return;
}
let Some(window) = self.window_by_xid(event.event) else {
return;
};
if let Ok(state) = self.window_mut(window) {
if state.focused == focused {
return;
}
state.focused = focused;
}
if focused {
self.held.clear();
self.resync_modifiers();
} else {
self.held.clear();
}
if let Some(keymap) = self.keymap.as_mut() {
keymap.reset_compose();
}
#[cfg(feature = "accesskit")]
self.access.focus(window, focused);
self.queue.push_back(ShellEvent::Focus { window, focused });
}
fn handle_configure(&mut self, raw: &Raw) {
let Some(event) = read_wire::<ffi::ConfigureNotifyEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.window) else {
return;
};
let size = PhysicalSize::new(u32::from(event.width), u32::from(event.height));
if size.is_empty() {
return;
}
let scale_factor = self.scale_factor;
let Ok(state) = self.window_mut(window) else {
return;
};
if state.last_size == Some(size) && state.configuration.is_some() {
return;
}
state.last_size = Some(size);
let config = WindowConfiguration {
size,
scale_factor,
mode: DisplayMode::Windowed,
};
state.pending = Some(config);
}
fn handle_map(&mut self, raw: &Raw, mapped: bool) {
let Some(event) = read_wire::<ffi::WindowNotifyEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.window) else {
return;
};
if let Ok(state) = self.window_mut(window) {
state.mapped = mapped;
state.visible = mapped;
}
if mapped {
self.refresh_effective_mode(window);
}
}
fn handle_destroy(&mut self, raw: &Raw) {
let Some(event) = read_wire::<ffi::WindowNotifyEvent>(raw) else {
return;
};
let Some(window) = self.window_by_xid(event.window) else {
return;
};
self.windows.remove(window.cast());
self.forget_selection_state(window, event.window);
#[cfg(feature = "accesskit")]
self.access.close(window);
self.queue.push_back(ShellEvent::WindowDestroyed { window });
}
fn handle_client_message(&mut self, raw: &Raw) {
let Some(event) = read_wire::<ffi::ClientMessageEvent>(raw) else {
return;
};
if self.handle_xdnd_message(&event) {
return;
}
let Some(window) = self.window_by_xid(event.window) else {
return;
};
if event.type_ != self.conn.atoms.wm_protocols
|| event.data[0] != self.conn.atoms.wm_delete_window
{
return;
}
if let Ok(state) = self.window_mut(window) {
if state.close_pending {
return;
}
state.close_pending = true;
}
self.queue.push_back(ShellEvent::CloseRequested { window });
}
fn handle_mapping_notify(&mut self, raw: &Raw) {
let Some(event) = read_wire::<ffi::MappingNotifyEvent>(raw) else {
return;
};
if event.request == 2 {
return;
}
self.keymap =
unsafe { xkb::Keymap::from_x11_device(self.conn.raw().cast::<core::ffi::c_void>()) };
self.resync_modifiers();
}
fn handle_generic(&mut self, raw: &Raw) {
let Some(opcode) = self.conn.xi_opcode else {
return;
};
let Some(header) = read_wire::<ffi::XiRawEvent>(raw) else {
return;
};
if header.extension != opcode || header.event_type != ffi::value::XI_RAW_MOTION {
return;
}
let Some((dx, dy)) = raw_motion_delta(raw, &header) else {
return;
};
if dx == 0.0 && dy == 0.0 {
return;
}
let Some(window) = self
.windows
.iter()
.find(|(_, state)| state.focused)
.map(|(handle, _)| handle.cast())
else {
return;
};
self.last_server_time = header.time;
let time = self.time.event_time(header.time);
let locked = self
.window(window)
.is_ok_and(|state| state.pointer_mode == PointerMode::Locked);
let abs = if locked {
None
} else {
self.pointer_position(window)
};
self.queue.push_back(ShellEvent::PointerMotion {
window,
device: POINTER_DEVICE,
time,
abs,
raw_delta: Some((dx, dy)),
});
}
fn handle_monitors_changed(&mut self) {
let monitors = self.enumerate_monitors();
if monitors == self.monitors {
return;
}
crate::monitor::queue_removed(&self.monitors, &monitors, &mut self.queue);
self.monitors = monitors;
self.queue.push_back(ShellEvent::MonitorsChanged);
}
fn modifiers(&self) -> Modifiers {
match self.keymap.as_ref() {
Some(keymap) => keymap.modifiers(),
None => {
let held: Vec<KeyCode> = self
.held
.iter()
.filter_map(|code| keys::key_code(*code))
.collect();
xkb::modifiers_from_held(&held)
}
}
}
fn resync_modifiers(&mut self) {
let Some(mask) = self.conn.query_pointer_mask() else {
return;
};
if let Some(keymap) = self.keymap.as_mut() {
keymap.resync_from_x11_state(mask);
}
}
fn position_of(&self, window: WindowId, event: &ffi::InputEvent) -> Option<PhysicalPoint> {
if self
.window(window)
.is_ok_and(|state| state.pointer_mode == PointerMode::Locked)
{
return None;
}
Some(PhysicalPoint::new(
f64::from(event.event_x),
f64::from(event.event_y),
))
}
fn pointer_position(&self, window: WindowId) -> Option<PhysicalPoint> {
let xid = self.window(window).ok()?.id;
let (x, y, _) = self.conn.query_pointer(xid)?;
Some(PhysicalPoint::new(f64::from(x), f64::from(y)))
}
fn recentre_if_near_edge(&mut self, window: WindowId, event: &ffi::InputEvent) {
let Ok(state) = self.window(window) else {
return;
};
let Some(size) = state.configuration.map(|config| config.size) else {
return;
};
if size.is_empty() {
return;
}
let xid = state.id;
let centre_x = i32::try_from(size.width / 2).unwrap_or(0);
let centre_y = i32::try_from(size.height / 2).unwrap_or(0);
let margin_x = (f64::from(size.width) * LOCK_RECENTRE_FRACTION) as i32;
let margin_y = (f64::from(size.height) * LOCK_RECENTRE_FRACTION) as i32;
let x = i32::from(event.event_x);
let y = i32::from(event.event_y);
if (x - centre_x).abs() < margin_x && (y - centre_y).abs() < margin_y {
return;
}
self.conn.warp_to(xid, centre_x, centre_y);
}
}
pub(super) const MAX_EVENTS_PER_PUMP: usize = 4096;
fn is_repeat_pair(first: &Raw, second: Option<&Raw>) -> bool {
let Some(second) = second else {
return false;
};
if first[0] & 0x7f != ffi::event::KEY_RELEASE || second[0] & 0x7f != ffi::event::KEY_PRESS {
return false;
}
let (Some(release), Some(press)) = (
read_wire::<ffi::InputEvent>(first),
read_wire::<ffi::InputEvent>(second),
) else {
return false;
};
release.detail == press.detail && release.time == press.time
}
fn fp3232(integral: i32, frac: u32) -> f64 {
const SCALE: f64 = 4_294_967_296.0;
f64::from(integral) + f64::from(frac) / SCALE
}
fn raw_motion_delta(raw: &Raw, header: &ffi::XiRawEvent) -> Option<(f64, f64)> {
let mask_words = usize::from(header.valuators_len);
let mask_start = size_of::<ffi::XiRawEvent>();
let mask_end = mask_start.checked_add(mask_words.checked_mul(4)?)?;
let mask = raw.get(mask_start..mask_end)?;
let set: u32 = mask
.as_chunks::<4>()
.0
.iter()
.map(|word| u32::from_ne_bytes([word[0], word[1], word[2], word[3]]).count_ones())
.sum();
let set = set as usize;
let raw_start = mask_end.checked_add(set.checked_mul(8)?)?;
let mut delta = (0.0, 0.0);
let mut slot = 0usize;
for (word_index, word) in mask.as_chunks::<4>().0.iter().enumerate() {
let bits = u32::from_ne_bytes([word[0], word[1], word[2], word[3]]);
for bit in 0..32u32 {
if bits & (1 << bit) == 0 {
continue;
}
let axis = word_index * 32 + bit as usize;
let offset = raw_start.checked_add(slot.checked_mul(8)?)?;
let value = raw.get(offset..offset.checked_add(8)?)?;
let integral = i32::from_ne_bytes([value[0], value[1], value[2], value[3]]);
let frac = u32::from_ne_bytes([value[4], value[5], value[6], value[7]]);
match axis {
0 => delta.0 = fp3232(integral, frac),
1 => delta.1 = fp3232(integral, frac),
_ => {}
}
slot += 1;
}
}
Some(delta)
}
impl Conn {
pub(super) fn query_pointer(&self, xid: u32) -> Option<(i16, i16, u16)> {
let reply = unsafe {
let cookie = (self.lib.query_pointer)(self.raw(), xid);
(self.lib.query_pointer_reply)(self.raw(), cookie, core::ptr::null_mut())
};
if reply.is_null() {
return None;
}
let answer = unsafe { ((*reply).win_x, (*reply).win_y, (*reply).mask) };
unsafe { ffi::free_reply(reply) };
Some(answer)
}
fn query_pointer_mask(&self) -> Option<u16> {
self.query_pointer(self.root).map(|(_, _, mask)| mask)
}
pub(super) fn warp_to(&self, xid: u32, x: i32, y: i32) {
unsafe {
(self.lib.warp_pointer)(
self.raw(),
ffi::value::NONE,
xid,
0,
0,
0,
0,
i16::try_from(x).unwrap_or(0),
i16::try_from(y).unwrap_or(0),
);
}
self.flush();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn key_event(response_type: u8, detail: u8, time: u32) -> Raw {
let event = ffi::InputEvent {
response_type,
detail,
sequence: 0,
time,
root: 1,
event: 2,
child: 0,
root_x: 0,
root_y: 0,
event_x: 0,
event_y: 0,
state: 0,
same_screen: 1,
pad0: 0,
};
unsafe {
core::slice::from_raw_parts(core::ptr::from_ref(&event).cast::<u8>(), 32).to_vec()
}
}
#[test]
fn an_auto_repeat_pair_is_a_release_and_press_at_the_same_millisecond() {
let release = key_event(ffi::event::KEY_RELEASE, 38, 1_000);
let press = key_event(ffi::event::KEY_PRESS, 38, 1_000);
assert!(is_repeat_pair(&release, Some(&press)));
let later = key_event(ffi::event::KEY_PRESS, 38, 1_001);
assert!(!is_repeat_pair(&release, Some(&later)));
let other = key_event(ffi::event::KEY_PRESS, 39, 1_000);
assert!(!is_repeat_pair(&release, Some(&other)));
assert!(!is_repeat_pair(&release, None));
assert!(!is_repeat_pair(&press, Some(&press)));
}
#[test]
fn fp3232_adds_the_fraction_even_when_the_integral_part_is_negative() {
assert_eq!(fp3232(3, 0), 3.0);
assert_eq!(fp3232(0, 1 << 31), 0.5);
assert_eq!(fp3232(-1, 1 << 31), -0.5);
assert_eq!(fp3232(-3, 0), -3.0);
assert_eq!(fp3232(0, u32::MAX), 1.0 - 1.0 / 4_294_967_296.0);
assert!(fp3232(0, u32::MAX) < 1.0);
}
#[test]
fn raw_motion_reads_the_unaccelerated_array_and_not_the_first_one() {
let header = ffi::XiRawEvent {
response_type: ffi::event::GE_GENERIC,
extension: 131,
sequence: 0,
length: 0,
event_type: ffi::value::XI_RAW_MOTION,
deviceid: 2,
time: 0,
detail: 0,
sourceid: 9,
valuators_len: 1,
flags: 0,
pad0: [0; 4],
full_sequence: 0,
};
let mut raw = Vec::new();
raw.extend_from_slice(unsafe {
core::slice::from_raw_parts(core::ptr::from_ref(&header).cast::<u8>(), 36)
});
raw.extend_from_slice(&0b11u32.to_ne_bytes());
raw.extend_from_slice(&99i32.to_ne_bytes());
raw.extend_from_slice(&0u32.to_ne_bytes());
raw.extend_from_slice(&99i32.to_ne_bytes());
raw.extend_from_slice(&0u32.to_ne_bytes());
raw.extend_from_slice(&7i32.to_ne_bytes());
raw.extend_from_slice(&0u32.to_ne_bytes());
raw.extend_from_slice(&(-4i32).to_ne_bytes());
raw.extend_from_slice(&(u32::MAX / 2).to_ne_bytes());
let (dx, dy) = raw_motion_delta(&raw, &header).expect("well-formed");
assert!((dx - 7.0).abs() < 1e-6, "{dx}");
assert!((dy + 3.5).abs() < 1e-6, "{dy}");
}
#[test]
fn a_truncated_raw_event_answers_none_rather_than_reading_past_the_end() {
let header = ffi::XiRawEvent {
response_type: ffi::event::GE_GENERIC,
extension: 131,
sequence: 0,
length: 0,
event_type: ffi::value::XI_RAW_MOTION,
deviceid: 2,
time: 0,
detail: 0,
sourceid: 9,
valuators_len: 4,
flags: 0,
pad0: [0; 4],
full_sequence: 0,
};
assert_eq!(raw_motion_delta(&vec![0u8; 36], &header), None);
assert_eq!(raw_motion_delta(&vec![0u8; 48], &header), None);
}
#[test]
fn a_grab_is_not_a_focus_change() {
assert_ne!(NOTIFY_GRAB, NOTIFY_UNGRAB);
assert_eq!((NOTIFY_GRAB, NOTIFY_UNGRAB), (1, 2));
}
}