From 9477514ad50f7e8b09253f7b08217f16a1ca1bf7 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 29 Sep 2023 20:11:29 -0700 Subject: [PATCH] Use x11rb for event handling Signed-off-by: John Nunley --- src/platform_impl/linux/x11/dnd.rs | 2 +- .../linux/x11/event_processor.rs | 1349 ++++++++--------- src/platform_impl/linux/x11/ime.rs | 52 +- src/platform_impl/linux/x11/mod.rs | 81 +- src/platform_impl/linux/x11/util/mod.rs | 8 - 5 files changed, 710 insertions(+), 782 deletions(-) diff --git a/src/platform_impl/linux/x11/dnd.rs b/src/platform_impl/linux/x11/dnd.rs index 618a4cde9..52aaf8435 100644 --- a/src/platform_impl/linux/x11/dnd.rs +++ b/src/platform_impl/linux/x11/dnd.rs @@ -44,7 +44,7 @@ impl From for DndDataParseError { pub(crate) struct Dnd { xconn: Arc, // Populated by XdndEnter event handler - pub version: Option, + pub version: Option, pub type_list: Option>, // Populated by XdndPosition event handler pub source_window: Option, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 981da8647..1a7743571 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,27 +1,33 @@ use std::{ cell::RefCell, - collections::HashMap, - os::raw::{c_char, c_int, c_long, c_ulong}, + collections::{HashMap, VecDeque}, rc::Rc, - slice, sync::{mpsc, Arc, Mutex}, }; -use x11rb::x11_utils::Serialize; -use x11rb::{ - protocol::{ - xinput, - xproto::{self, ConnectionExt as _}, - }, - x11_utils::ExtensionInformation, +use x11rb::protocol::{ + xinput, xkb, + xproto::{self, ConnectionExt as _}, + Event as X11Event, }; +use x11rb::{connection::Connection, x11_utils::Serialize}; use super::{ - atoms::*, ffi, get_xtarget, ime, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, - DeviceInfo, Dnd, DndState, GenericEventCookie, ScrollOrientation, UnownedWindow, WindowId, + atoms::*, get_xtarget, ime, mkdid, mkwid, util, xinput_fp1616_to_float, xinput_fp3232_to_float, + CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState, ScrollOrientation, UnownedWindow, + WindowId, }; use crate::event::InnerSizeWriter; +use crate::event::{ + ElementState::{Pressed, Released}, + MouseButton::{Back, Forward, Left, Middle, Other, Right}, + MouseScrollDelta::LineDelta, + Touch, + WindowEvent::{ + AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, MouseWheel, + }, +}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, ElementState, Event, Ime, RawKeyEvent, TouchPhase, WindowEvent}, @@ -34,13 +40,13 @@ use crate::{ const KEYCODE_OFFSET: u8 = 8; pub(super) struct EventProcessor { + /// Queue of unprocessed events. + pub(super) event_queue: Rc>>, + pub(super) dnd: Dnd, /// Requests from other threads for IME. pub(super) ime_requests: mpsc::Receiver, - pub(super) randr_event_offset: u8, pub(super) devices: RefCell>, - pub(super) xi2ext: ExtensionInformation, - pub(super) xkbext: ExtensionInformation, pub(super) target: Rc>, pub(super) kb_state: KbdState, // Number of touch events currently in progress @@ -96,90 +102,57 @@ impl EventProcessor { } pub(super) fn poll(&self) -> bool { - let wt = get_xtarget(&self.target); - let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) }; - - result != 0 - } - - pub(super) unsafe fn poll_one_event(&mut self, event_ptr: *mut ffi::XEvent) -> bool { - let wt = get_xtarget(&self.target); - // This function is used to poll and remove a single event - // from the Xlib event queue in a non-blocking, atomic way. - // XCheckIfEvent is non-blocking and removes events from queue. - // XNextEvent can't be used because it blocks while holding the - // global Xlib mutex. - // XPeekEvent does not remove events from the queue. - unsafe extern "C" fn predicate( - _display: *mut ffi::Display, - _event: *mut ffi::XEvent, - _arg: *mut c_char, - ) -> c_int { - // This predicate always returns "true" (1) to accept all events - 1 + // See if we have a cached event. + if !self.event_queue.borrow().is_empty() { + return true; } - let result = unsafe { - (wt.xconn.xlib.XCheckIfEvent)( - wt.xconn.display, - event_ptr, - Some(predicate), - std::ptr::null_mut(), - ) - }; - - result != 0 + // If not, try to poll for one, + match get_xtarget(&self.target) + .x_connection() + .xcb_connection() + .poll_for_event() + .expect("Error while polling for X11 events") + { + None => false, + Some(event) => { + self.event_queue.borrow_mut().push_back(event); + true + } + } } - pub(super) fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) + pub(super) fn poll_one_event(&mut self) -> Option { + if let Some(event) = self.event_queue.borrow_mut().pop_front() { + return Some(event); + } + + get_xtarget(&self.target) + .x_connection() + .xcb_connection() + .poll_for_event() + .expect("Error while polling for X11 events") + } + + pub(super) fn process_event(&mut self, event: X11Event, mut callback: F) where F: FnMut(Event), { let wt = get_xtarget(&self.target); let atoms = wt.x_connection().atoms(); - // XFilterEvent tells us when an event has been discarded by the input method. - // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, - // along with an extra copy of the KeyRelease events. This also prevents backspace and - // arrow keys from being detected twice. - if ffi::True - == unsafe { - (wt.xconn.xlib.XFilterEvent)(xev, { - let xev: &ffi::XAnyEvent = xev.as_ref(); - xev.window - }) - } - { - return; - } - - let event_type = xev.get_type(); - match event_type { - ffi::ClientMessage => { - let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); + match &event { + X11Event::ClientMessage(client_msg) => { let window = client_msg.window as xproto::Window; let window_id = mkwid(window); - if client_msg.data.get_long(0) as xproto::Atom == wt.wm_delete_window { + let data = client_msg.data.as_data32(); + if data[0] == wt.wm_delete_window { callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested, }); - } else if client_msg.data.get_long(0) as xproto::Atom == wt.net_wm_ping { - let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut(); - let client_msg = xproto::ClientMessageEvent { - response_type: xproto::CLIENT_MESSAGE_EVENT, - format: response_msg.format as _, - sequence: response_msg.serial as _, - window: wt.root, - type_: response_msg.message_type as _, - data: xproto::ClientMessageData::from({ - let [a, b, c, d, e]: [c_long; 5] = - response_msg.data.as_longs().try_into().unwrap(); - [a as u32, b as u32, c as u32, d as u32, e as u32] - }), - }; - + } else if data[0] == wt.net_wm_ping { wt.xconn .xcb_connection() .send_event( @@ -190,24 +163,20 @@ impl EventProcessor { client_msg.serialize(), ) .expect_then_ignore_error("Failed to send `ClientMessage` event."); - } else if client_msg.message_type == atoms[XdndEnter] as c_ulong { - let source_window = client_msg.data.get_long(0) as xproto::Window; - let flags = client_msg.data.get_long(1); + } else if client_msg.type_ == atoms[XdndEnter] { + let source_window = data[0]; + let flags = data[1]; let version = flags >> 24; self.dnd.version = Some(version); - let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; + let has_more_types = flags - (flags & (u32::max_value() - 1)) == 1; if !has_more_types { - let type_list = vec![ - client_msg.data.get_long(2) as xproto::Atom, - client_msg.data.get_long(3) as xproto::Atom, - client_msg.data.get_long(4) as xproto::Atom, - ]; + let type_list = vec![data[2], data[3], data[4]]; self.dnd.type_list = Some(type_list); } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } { self.dnd.type_list = Some(more_types); } - } else if client_msg.message_type == atoms[XdndPosition] as c_ulong { + } else if client_msg.type_ == atoms[XdndPosition] { // This event occurs every time the mouse moves while a file's being dragged // over our window. We emit HoveredFile in response; while the macOS backend // does that upon a drag entering, XDND doesn't have access to the actual drop @@ -216,7 +185,7 @@ impl EventProcessor { // supply position updates with `HoveredFile` or another event, implementing // that here would be trivial. - let source_window = client_msg.data.get_long(0) as xproto::Window; + let source_window = data[0]; // Equivalent to `(x << shift) | y` // where `shift = mem::size_of::() * 8` @@ -244,7 +213,7 @@ impl EventProcessor { unsafe { if self.dnd.result.is_none() { let time = if version >= 1 { - client_msg.data.get_long(3) as xproto::Timestamp + data[3] as xproto::Timestamp } else { // In version 0, time isn't specified x11rb::CURRENT_TIME @@ -268,7 +237,7 @@ impl EventProcessor { } self.dnd.reset(); } - } else if client_msg.message_type == atoms[XdndDrop] as c_ulong { + } else if client_msg.type_ == atoms[XdndDrop] { let (source_window, state) = if let Some(source_window) = self.dnd.source_window { if let Some(Ok(ref path_list)) = self.dnd.result { @@ -283,7 +252,7 @@ impl EventProcessor { } else { // `source_window` won't be part of our DND state if we already rejected the drop in our // `XdndPosition` handler. - let source_window = client_msg.data.get_long(0) as xproto::Window; + let source_window = data[0]; (source_window, DndState::Rejected) }; unsafe { @@ -292,7 +261,7 @@ impl EventProcessor { .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); - } else if client_msg.message_type == atoms[XdndLeave] as c_ulong { + } else if client_msg.type_ == atoms[XdndLeave] { self.dnd.reset(); callback(Event::WindowEvent { window_id, @@ -301,16 +270,14 @@ impl EventProcessor { } } - ffi::SelectionNotify => { - let xsel: &ffi::XSelectionEvent = xev.as_ref(); - - let window = xsel.requestor as xproto::Window; + X11Event::SelectionNotify(xsel) => { + let window = xsel.requestor; let window_id = mkwid(window); // Set the timestamp. wt.xconn.set_timestamp(xsel.time as xproto::Timestamp); - if xsel.property == atoms[XdndSelection] as c_ulong { + if xsel.property == atoms[XdndSelection] { let mut result = None; // This is where we receive data from drag and drop @@ -331,9 +298,8 @@ impl EventProcessor { } } - ffi::ConfigureNotify => { - let xev: &ffi::XConfigureEvent = xev.as_ref(); - let xwindow = xev.window as xproto::Window; + X11Event::ConfigureNotify(xev) => { + let xwindow = xev.window; let window_id = mkwid(xwindow); if let Some(window) = self.with_window(xwindow, Arc::clone) { @@ -344,11 +310,11 @@ impl EventProcessor { // We don't want to send `Moved` when this is false, since then every `Resized` // (whether the window moved or not) is accompanied by an extraneous `Moved` event // that has a position relative to the parent window. - let is_synthetic = xev.send_event == ffi::True; + let is_synthetic = xev.response_type & 0x80 == 0; // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); - let new_inner_position = (xev.x, xev.y); + let new_inner_position = (xev.x as i32, xev.y as i32); let (mut resized, moved) = { let mut shared_state_lock = window.shared_state_lock(); @@ -513,9 +479,7 @@ impl EventProcessor { } } - ffi::ReparentNotify => { - let xev: &ffi::XReparentEvent = xev.as_ref(); - + X11Event::ReparentNotify(xev) => { // This is generally a reliable way to detect when the window manager's been // replaced, though this event is only fired by reparenting window managers // (which is almost all of them). Failing to correctly update WM info doesn't @@ -527,8 +491,7 @@ impl EventProcessor { window.invalidate_cached_frame_extents(); }); } - ffi::MapNotify => { - let xev: &ffi::XMapEvent = xev.as_ref(); + X11Event::MapNotify(xev) => { let window = xev.window as xproto::Window; let window_id = mkwid(window); @@ -545,9 +508,7 @@ impl EventProcessor { event: WindowEvent::Focused(focus), }); } - ffi::DestroyNotify => { - let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); - + X11Event::DestroyNotify(xev) => { let window = xev.window as xproto::Window; let window_id = mkwid(window); @@ -577,21 +538,18 @@ impl EventProcessor { } } - ffi::VisibilityNotify => { - let xev: &ffi::XVisibilityEvent = xev.as_ref(); + X11Event::VisibilityNotify(xev) => { let xwindow = xev.window as xproto::Window; callback(Event::WindowEvent { window_id: mkwid(xwindow), - event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured), + event: WindowEvent::Occluded(xev.state == xproto::Visibility::FULLY_OBSCURED), }); self.with_window(xwindow, |window| { window.visibility_notify(); }); } - ffi::Expose => { - let xev: &ffi::XExposeEvent = xev.as_ref(); - + X11Event::Expose(xev) => { // Multiple Expose events may be received for subareas of a window. // We issue `RedrawRequested` only for the last event of such a series. if xev.count == 0 { @@ -606,9 +564,7 @@ impl EventProcessor { } // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events - ty @ ffi::KeyPress | ty @ ffi::KeyRelease => { - let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - + X11Event::KeyPress(xkev) | X11Event::KeyRelease(xkev) => { // Set the timestamp. wt.xconn.set_timestamp(xkev.time as xproto::Timestamp); @@ -620,7 +576,7 @@ impl EventProcessor { let window_id = mkwid(window); let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - let keycode = xkev.keycode as _; + let keycode = xkev.detail.into(); // Update state to track key repeats and determine whether this key was a repeat. // @@ -635,7 +591,7 @@ impl EventProcessor { let repeat = if self.kb_state.key_repeats(keycode) { let is_latest_held = self.held_key_press == Some(keycode); - if ty == ffi::KeyPress { + if matches!(event, X11Event::KeyPress(_)) { self.held_key_press = Some(keycode); is_latest_held } else { @@ -651,7 +607,7 @@ impl EventProcessor { false }; - let state = if ty == ffi::KeyPress { + let state = if matches!(event, X11Event::KeyPress(_)) { ElementState::Pressed } else { ElementState::Released @@ -670,635 +626,606 @@ impl EventProcessor { } } - ffi::GenericEvent => { - let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) { - e - } else { - return; - }; - let xev = &guard.cookie; - if self.xi2ext.major_opcode != xev.extension as u8 { + X11Event::XinputButtonPress(xev) | X11Event::XinputButtonRelease(xev) => { + let window_id = mkwid(xev.event as xproto::Window); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); + + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + if xev + .flags + .contains(xinput::PointerEventFlags::POINTER_EMULATED) + { + // Deliver multi-touch events instead of emulated mouse events. return; } - use crate::event::{ - ElementState::{Pressed, Released}, - MouseButton::{Back, Forward, Left, Middle, Other, Right}, - MouseScrollDelta::LineDelta, - Touch, - WindowEvent::{ - AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, - MouseWheel, - }, + let state = if matches!(event, X11Event::XinputButtonPress(_)) { + Pressed + } else { + Released }; + match xev.detail { + 1 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Left, + }, + }), + 2 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Middle, + }, + }), + 3 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Right, + }, + }), - match xev.evtype { - ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event as xproto::Window); - let device_id = mkdid(xev.deviceid as xinput::DeviceId); - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - if (xev.flags & ffi::XIPointerEmulated) != 0 { - // Deliver multi-touch events instead of emulated mouse events. - return; - } - - let state = if xev.evtype == ffi::XI_ButtonPress { - Pressed - } else { - Released - }; - match xev.detail as u32 { - ffi::Button1 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Left, - }, - }), - ffi::Button2 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Middle, - }, - }), - ffi::Button3 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Right, - }, - }), - - // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. - // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in - // turn) as axis motion, so we don't otherwise special-case these button presses. - 4..=7 => { - if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match xev.detail { - 4 => LineDelta(0.0, 1.0), - 5 => LineDelta(0.0, -1.0), - 6 => LineDelta(1.0, 0.0), - 7 => LineDelta(-1.0, 0.0), - _ => unreachable!(), - }, - phase: TouchPhase::Moved, - }, - }); - } - } - - 8 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Back, - }, - }), - 9 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Forward, - }, - }), - - x => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Other(x as u16), - }, - }), - } - } - ffi::XI_Motion => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - let device_id = mkdid(xev.deviceid as xinput::DeviceId); - let window = xev.event as xproto::Window; - let window_id = mkwid(window); - let new_cursor_pos = (xev.event_x, xev.event_y); - - let cursor_moved = self.with_window(window, |window| { - let mut shared_state_lock = window.shared_state_lock(); - util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) - }); - if cursor_moved == Some(true) { - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - + // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. + // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in + // turn) as axis motion, so we don't otherwise special-case these button presses. + 4..=7 => { + if xev + .flags + .contains(xinput::PointerEventFlags::POINTER_EMULATED) + { callback(Event::WindowEvent { window_id, - event: CursorMoved { + event: MouseWheel { device_id, - position, + delta: match xev.detail { + 4 => LineDelta(0.0, 1.0), + 5 => LineDelta(0.0, -1.0), + 6 => LineDelta(1.0, 0.0), + 7 => LineDelta(-1.0, 0.0), + _ => unreachable!(), + }, + phase: TouchPhase::Moved, }, }); - } else if cursor_moved.is_none() { - return; - } - - // More gymnastics, for self.devices - let mut events = Vec::new(); - { - let mask = unsafe { - slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, - ) - }; - let mut devices = self.devices.borrow_mut(); - let physical_device = match devices - .get_mut(&DeviceId(xev.sourceid as xinput::DeviceId)) - { - Some(device) => device, - None => return, - }; - - let mut value = xev.valuators.values; - for i in 0..xev.valuators.mask_len * 8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - if let Some(&mut (_, ref mut info)) = physical_device - .scroll_axes - .iter_mut() - .find(|&&mut (axis, _)| axis == i as _) - { - let delta = (x - info.position) / info.increment; - info.position = x; - events.push(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match info.orientation { - // X11 vertical scroll coordinates are opposite to winit's - ScrollOrientation::Horizontal => { - LineDelta(-delta as f32, 0.0) - } - ScrollOrientation::Vertical => { - LineDelta(0.0, -delta as f32) - } - }, - phase: TouchPhase::Moved, - }, - }); - } else { - events.push(Event::WindowEvent { - window_id, - event: AxisMotion { - device_id, - axis: i as u32, - value: unsafe { *value }, - }, - }); - } - value = unsafe { value.offset(1) }; - } - } - } - for event in events { - callback(event); } } - ffi::XI_Enter => { - let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; + 8 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Back, + }, + }), + 9 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Forward, + }, + }), - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + x => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Other(x as u16), + }, + }), + } + } + X11Event::XinputMotion(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - let window = xev.event as xproto::Window; - let window_id = mkwid(window); - let device_id = mkdid(xev.deviceid as xinput::DeviceId); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); + let window = xev.event as xproto::Window; + let window_id = mkwid(window); + let new_cursor_pos = ( + xinput_fp1616_to_float(xev.event_x), + xinput_fp1616_to_float(xev.event_y), + ); - if let Some(all_info) = - DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into()) + let cursor_moved = self.with_window(window, |window| { + let mut shared_state_lock = window.shared_state_lock(); + util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) + }); + if cursor_moved == Some(true) { + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position: new_cursor_pos.into(), + }, + }); + } else if cursor_moved.is_none() { + return; + } + + // More gymnastics, for self.devices + let mut events = Vec::new(); + { + let mut devices = self.devices.borrow_mut(); + let physical_device = + match devices.get_mut(&DeviceId(xev.sourceid as xinput::DeviceId)) { + Some(device) => device, + None => return, + }; + + let mask = bytemuck::cast_slice::(&xev.valuator_mask); + let mut values = &*xev.axisvalues; + + for i in 0..mask.len() * 8 { + let byte_index = i / 8; + let bit_index = i % 8; + + if mask[byte_index] & (1 << bit_index) == 0 { + continue; + } + + // This mask is set, get the value. + let x = { + let (value, rest) = values.split_first().unwrap(); + values = rest; + xinput_fp3232_to_float(*value) + }; + + if let Some(&mut (_, ref mut info)) = physical_device + .scroll_axes + .iter_mut() + .find(|&&mut (axis, _)| axis == i as _) { - let mut devices = self.devices.borrow_mut(); - for device_info in all_info.iter() { - if device_info.deviceid == xev.sourceid + let delta = (x - info.position) / info.increment; + info.position = x; + events.push(Event::WindowEvent { + window_id, + event: MouseWheel { + device_id, + delta: match info.orientation { + // X11 vertical scroll coordinates are opposite to winit's + ScrollOrientation::Horizontal => { + LineDelta(-delta as f32, 0.0) + } + ScrollOrientation::Vertical => { + LineDelta(0.0, -delta as f32) + } + }, + phase: TouchPhase::Moved, + }, + }); + } else { + events.push(Event::WindowEvent { + window_id, + event: AxisMotion { + device_id, + axis: i as u32, + value: x, + }, + }); + } + } + } + for event in events { + callback(event); + } + } + + X11Event::XinputEnter(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let window = xev.event as xproto::Window; + let window_id = mkwid(window); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); + + if let Some(all_info) = DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into()) { + let mut devices = self.devices.borrow_mut(); + for device_info in all_info.iter() { + if device_info.deviceid == xev.sourceid as _ // This is needed for resetting to work correctly on i3, and // presumably some other WMs. On those, `XI_Enter` doesn't include // the physical device ID, so both `sourceid` and `deviceid` are // the virtual device. - || device_info.attachment == xev.sourceid - { - let device_id = DeviceId(device_info.deviceid as _); - if let Some(device) = devices.get_mut(&device_id) { - device.reset_scroll_position(device_info); - } - } + || device_info.attachment == xev.sourceid as _ + { + let device_id = DeviceId(device_info.deviceid as _); + if let Some(device) = devices.get_mut(&device_id) { + device.reset_scroll_position(device_info); } } - - if self.window_exists(window) { - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); - - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - }, - }); - } } - ffi::XI_Leave => { - let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; - let window = xev.event as xproto::Window; + } - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + if self.window_exists(window) { + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); - // Leave, FocusIn, and FocusOut can be received by a window that's already - // been destroyed, which the user presumably doesn't want to deal with. - let window_closed = !self.window_exists(window); - if !window_closed { - callback(Event::WindowEvent { - window_id: mkwid(window), - event: CursorLeft { - device_id: mkdid(xev.deviceid as xinput::DeviceId), - }, - }); - } - } - ffi::XI_FocusIn => { - let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - let window = xev.event as xproto::Window; + let position = PhysicalPosition::new( + xinput_fp1616_to_float(xev.event_x), + xinput_fp1616_to_float(xev.event_y), + ); - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - if let Some(ime) = wt.ime.as_ref() { - ime.borrow_mut() - .focus_window(window) - .expect("Failed to focus input context"); - } - - if self.active_window != Some(window) { - self.active_window = Some(window); - - wt.update_listen_device_events(true); - - let window_id = mkwid(window); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - if let Some(window) = self.with_window(window, Arc::clone) { - window.shared_state_lock().has_focus = true; - } - - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); - - let modifiers: crate::keyboard::ModifiersState = - self.kb_state.mods_state().into(); - if !modifiers.is_empty() { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(modifiers.into()), - }); - } - - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self - .devices - .borrow() - .get(&DeviceId(xev.deviceid as xinput::DeviceId)) - .map(|device| device.attachment) - .unwrap_or(2); - - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id as _), - position, - }, - }); - - // Issue key press events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Pressed, - &mut self.kb_state, - &mut callback, - ); - } - } - ffi::XI_FocusOut => { - let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - let window = xev.event as xproto::Window; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - if !self.window_exists(window) { - return; - } - - if let Some(ime) = wt.ime.as_ref() { - ime.borrow_mut() - .unfocus_window(window) - .expect("Failed to focus input context"); - } - - if self.active_window.take() == Some(window) { - let window_id = mkwid(window); - - wt.update_listen_device_events(false); - - // Issue key release events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Released, - &mut self.kb_state, - &mut callback, - ); - // Clear this so detecting key repeats is consistently handled when the - // window regains focus. - self.held_key_press = None; - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged( - ModifiersState::empty().into(), - ), - }); - - if let Some(window) = self.with_window(window, Arc::clone) { - window.shared_state_lock().has_focus = false; - } - - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) - } - } - - ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - let window = xev.event as xproto::Window; - let window_id = mkwid(window); - let phase = match xev.evtype { - ffi::XI_TouchBegin => TouchPhase::Started, - ffi::XI_TouchUpdate => TouchPhase::Moved, - ffi::XI_TouchEnd => TouchPhase::Ended, - _ => unreachable!(), - }; - if self.window_exists(window) { - let id = xev.detail as u64; - let location = PhysicalPosition::new(xev.event_x, xev.event_y); - - // Mouse cursor position changes when touch events are received. - // Only the first concurrently active touch ID moves the mouse cursor. - if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) - { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::CursorMoved { - device_id: mkdid(util::VIRTUAL_CORE_POINTER), - position: location.cast(), - }, - }); - } - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid as xinput::DeviceId), - phase, - location, - force: None, // TODO - id, - }), - }) - } - } - - ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::DeviceEvent { - device_id: mkdid(xev.deviceid as xinput::DeviceId), - event: DeviceEvent::Button { - button: xev.detail as u32, - state: match xev.evtype { - ffi::XI_RawButtonPress => Pressed, - ffi::XI_RawButtonRelease => Released, - _ => unreachable!(), - }, - }, - }); - } - } - - ffi::XI_RawMotion => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - let did = mkdid(xev.deviceid as xinput::DeviceId); - - let mask = unsafe { - slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, - ) - }; - let mut value = xev.raw_values; - let mut mouse_delta = (0.0, 0.0); - let mut scroll_delta = (0.0, 0.0); - for i in 0..xev.valuators.mask_len * 8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - // We assume that every XInput2 device with analog axes is a pointing device emitting - // relative coordinates. - match i { - 0 => mouse_delta.0 = x, - 1 => mouse_delta.1 = x, - 2 => scroll_delta.0 = x as f32, - 3 => scroll_delta.1 = x as f32, - _ => {} - } - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::Motion { - axis: i as u32, - value: x, - }, - }); - value = unsafe { value.offset(1) }; - } - } - if mouse_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseMotion { delta: mouse_delta }, - }); - } - if scroll_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseWheel { - delta: LineDelta(scroll_delta.0, scroll_delta.1), - }, - }); - } - } - ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - - let state = match xev.evtype { - ffi::XI_RawKeyPress => Pressed, - ffi::XI_RawKeyRelease => Released, - _ => unreachable!(), - }; - - let device_id = mkdid(xev.sourceid as xinput::DeviceId); - let keycode = xev.detail as u32; - if keycode < KEYCODE_OFFSET as u32 { - return; - } - let physical_key = keymap::raw_keycode_to_physicalkey(keycode); - - callback(Event::DeviceEvent { + callback(Event::WindowEvent { + window_id, + event: CursorMoved { device_id, - event: DeviceEvent::Key(RawKeyEvent { - physical_key, - state, - }), + position, + }, + }); + } + } + X11Event::XinputLeave(xev) => { + let window = xev.event as xproto::Window; + + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + // Leave, FocusIn, and FocusOut can be received by a window that's already + // been destroyed, which the user presumably doesn't want to deal with. + let window_closed = !self.window_exists(window); + if !window_closed { + callback(Event::WindowEvent { + window_id: mkwid(window), + event: CursorLeft { + device_id: mkdid(xev.deviceid as xinput::DeviceId), + }, + }); + } + } + X11Event::XinputFocusIn(xev) => { + let window = xev.event as xproto::Window; + + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + if let Some(ime) = wt.ime.as_ref() { + ime.borrow_mut() + .focus_window(window) + .expect("Failed to focus input context"); + } + + if self.active_window != Some(window) { + self.active_window = Some(window); + + wt.update_listen_device_events(true); + + let window_id = mkwid(window); + let position = PhysicalPosition::new( + xinput_fp1616_to_float(xev.event_x), + xinput_fp1616_to_float(xev.event_y), + ); + + if let Some(window) = self.with_window(window, Arc::clone) { + window.shared_state_lock().has_focus = true; + } + + callback(Event::WindowEvent { + window_id, + event: Focused(true), + }); + + let modifiers: crate::keyboard::ModifiersState = + self.kb_state.mods_state().into(); + if !modifiers.is_empty() { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(modifiers.into()), }); } - ffi::XI_HierarchyChanged => { - let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let pointer_id = self + .devices + .borrow() + .get(&DeviceId(xev.deviceid as xinput::DeviceId)) + .map(|device| device.attachment) + .unwrap_or(2); - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id as _), + position, + }, + }); - for info in - unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } - { - if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid as xinput::DeviceId); - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid as xinput::DeviceId), - event: DeviceEvent::Added, - }); - } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) - { - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid as xinput::DeviceId), - event: DeviceEvent::Removed, - }); - let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid as xinput::DeviceId)); - } - } - } - - _ => {} + // Issue key press events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Pressed, + &mut self.kb_state, + &mut callback, + ); } } - _ => { - if event_type == self.xkbext.first_event as _ { - let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) }; - match xev.xkb_type { - ffi::XkbNewKeyboardNotify => { - let xev = unsafe { - &*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent) - }; + X11Event::XinputFocusOut(xev) => { + let window = xev.event as xproto::Window; - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - let keycodes_changed_flag = 0x1; - let geometry_changed_flag = 0x1 << 1; + if !self.window_exists(window) { + return; + } - let keycodes_changed = - util::has_flag(xev.changed, keycodes_changed_flag); - let geometry_changed = - util::has_flag(xev.changed, geometry_changed_flag); + if let Some(ime) = wt.ime.as_ref() { + ime.borrow_mut() + .unfocus_window(window) + .expect("Failed to focus input context"); + } - if xev.device == self.kb_state.core_keyboard_id - && (keycodes_changed || geometry_changed) - { - unsafe { self.kb_state.init_with_x11_keymap() }; - } - } - ffi::XkbStateNotify => { - let xev = - unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) }; + if self.active_window.take() == Some(window) { + let window_id = mkwid(window); - // Set the timestamp. - wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + wt.update_listen_device_events(false); - let prev_mods = self.kb_state.mods_state(); - self.kb_state.update_modifiers( - xev.base_mods, - xev.latched_mods, - xev.locked_mods, - xev.base_group as u32, - xev.latched_group as u32, - xev.locked_group as u32, - ); - let new_mods = self.kb_state.mods_state(); - if prev_mods != new_mods { - if let Some(window) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window), - event: WindowEvent::ModifiersChanged( - Into::::into(new_mods).into(), - ), - }); - } - } - } + // Issue key release events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Released, + &mut self.kb_state, + &mut callback, + ); + // Clear this so detecting key repeats is consistently handled when the + // window regains focus. + self.held_key_press = None; + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()), + }); + + if let Some(window) = self.with_window(window, Arc::clone) { + window.shared_state_lock().has_focus = false; + } + + callback(Event::WindowEvent { + window_id, + event: Focused(false), + }) + } + } + + X11Event::XinputTouchBegin(xev) + | X11Event::XinputTouchUpdate(xev) + | X11Event::XinputTouchEnd(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let window = xev.event as xproto::Window; + let window_id = mkwid(window); + let phase = match event { + X11Event::XinputTouchBegin(_) => TouchPhase::Started, + X11Event::XinputTouchUpdate(_) => TouchPhase::Moved, + X11Event::XinputTouchEnd(_) => TouchPhase::Ended, + _ => unreachable!(), + }; + if self.window_exists(window) { + let id = xev.detail as u64; + let location = PhysicalPosition::new( + xinput_fp1616_to_float(xev.event_x), + xinput_fp1616_to_float(xev.event_y), + ); + + // Mouse cursor position changes when touch events are received. + // Only the first concurrently active touch ID moves the mouse cursor. + if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::CursorMoved { + device_id: mkdid(util::VIRTUAL_CORE_POINTER), + position: location.cast(), + }, + }); + } + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Touch(Touch { + device_id: mkdid(xev.deviceid as xinput::DeviceId), + phase, + location, + force: None, // TODO + id, + }), + }) + } + } + + X11Event::XinputRawButtonPress(xev) | X11Event::XinputRawButtonRelease(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + if xev + .flags + .contains(xinput::PointerEventFlags::POINTER_EMULATED) + { + callback(Event::DeviceEvent { + device_id: mkdid(xev.deviceid as xinput::DeviceId), + event: DeviceEvent::Button { + button: xev.detail, + state: match event { + X11Event::XinputRawButtonPress(_) => Pressed, + X11Event::XinputRawButtonRelease(_) => Released, + _ => unreachable!(), + }, + }, + }); + } + } + + X11Event::XinputRawMotion(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let did = mkdid(xev.deviceid as xinput::DeviceId); + + let mut mouse_delta = (0.0, 0.0); + let mut scroll_delta = (0.0, 0.0); + + let mask = bytemuck::cast_slice::(&xev.valuator_mask); + let mut values = &*xev.axisvalues; + + for i in 0..mask.len() * 8 { + let byte_index = i / 8; + let bit_index = i % 8; + + if mask[byte_index] & (1 << bit_index) == 0 { + continue; + } + + // This mask is set, get the value. + let x = { + let (value, rest) = values.split_first().unwrap(); + values = rest; + xinput_fp3232_to_float(*value) + }; + + // We assume that every XInput2 device with analog axes is a pointing device emitting + // relative coordinates. + match i { + 0 => mouse_delta.0 = x, + 1 => mouse_delta.1 = x, + 2 => scroll_delta.0 = x as f32, + 3 => scroll_delta.1 = x as f32, _ => {} } + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::Motion { + axis: i as u32, + value: x, + }, + }); } - if event_type == self.randr_event_offset as c_int { - self.process_dpi_change(&mut callback); + + if mouse_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::MouseMotion { delta: mouse_delta }, + }); } + if scroll_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::MouseWheel { + delta: LineDelta(scroll_delta.0, scroll_delta.1), + }, + }); + } + } + X11Event::XinputRawKeyPress(xev) | X11Event::XinputRawKeyRelease(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let state = match event { + X11Event::XinputRawKeyPress(_) => Pressed, + X11Event::XinputRawKeyRelease(_) => Released, + _ => unreachable!(), + }; + + let device_id = mkdid(xev.sourceid as xinput::DeviceId); + let keycode = xev.detail; + if keycode < KEYCODE_OFFSET as u32 { + return; + } + let physical_key = keymap::raw_keycode_to_keycode(keycode); + + callback(Event::DeviceEvent { + device_id, + event: DeviceEvent::Key(RawKeyEvent { + physical_key, + state, + }), + }); + } + + X11Event::XinputHierarchy(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + for info in &xev.infos { + if info.flags.contains( + xinput::HierarchyMask::MASTER_ADDED | xinput::HierarchyMask::MASTER_REMOVED, + ) { + self.init_device(info.deviceid as xinput::DeviceId); + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid as xinput::DeviceId), + event: DeviceEvent::Added, + }); + } else if info.flags.contains( + xinput::HierarchyMask::SLAVE_ADDED | xinput::HierarchyMask::SLAVE_REMOVED, + ) { + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid as xinput::DeviceId), + event: DeviceEvent::Removed, + }); + let mut devices = self.devices.borrow_mut(); + devices.remove(&DeviceId(info.deviceid as xinput::DeviceId)); + } + } + } + + X11Event::XkbNewKeyboardNotify(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let keycodes_changed = xev.changed.contains(xkb::NKNDetail::KEYCODES); + let geometry_changed = xev.changed.contains(xkb::NKNDetail::GEOMETRY); + + if xev.device_id as i32 == self.kb_state.core_keyboard_id + && (keycodes_changed || geometry_changed) + { + unsafe { self.kb_state.init_with_x11_keymap() }; + } + } + X11Event::XkbStateNotify(xev) => { + // Set the timestamp. + wt.xconn.set_timestamp(xev.time as xproto::Timestamp); + + let prev_mods = self.kb_state.mods_state(); + self.kb_state.update_modifiers( + xev.base_mods.into(), + xev.latched_mods.into(), + xev.locked_mods.into(), + xev.base_group as u32, + xev.latched_group as u32, + xev.locked_group.into(), + ); + let new_mods = self.kb_state.mods_state(); + if prev_mods != new_mods { + if let Some(window) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::ModifiersChanged( + Into::::into(new_mods).into(), + ), + }); + } + } + } + + X11Event::RandrNotify(_) => { + self.process_dpi_change(&mut callback); + } + + _ => { + // Don't care, lol } } diff --git a/src/platform_impl/linux/x11/ime.rs b/src/platform_impl/linux/x11/ime.rs index a59f8c267..902e8cec3 100644 --- a/src/platform_impl/linux/x11/ime.rs +++ b/src/platform_impl/linux/x11/ime.rs @@ -9,8 +9,10 @@ use x11rb::protocol::Event; use xim::x11rb::{HasConnection, X11rbClient}; use xim::{AttributeName, Client as _, ClientError, ClientHandler, InputStyle, Point}; +use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; use std::fmt; +use std::rc::Rc; use std::sync::Arc; impl HasConnection for XConnection { @@ -78,6 +80,9 @@ pub(super) struct ImeData { /// Inner IME handler. struct ImeHandler { + /// Handle to the event queue. + event_queue: Rc>>, + /// Whether IME is currently disconnected. disconnected: bool, @@ -139,7 +144,11 @@ enum Style { impl ImeData { /// Creates the IME data for the display. - pub(super) fn new(conn: &Arc, screen: usize) -> Result { + pub(super) fn new( + conn: &Arc, + screen: usize, + event_queue: &Rc>>, + ) -> Result { // IM servers to try, in order: // - None, which defaults to the environment variable `XMODIFIERS` in xim's impl. // - "local", which is the default for most IMEs. @@ -154,6 +163,7 @@ impl ImeData { return Ok(Self { client, handler: ImeHandler { + event_queue: event_queue.clone(), disconnected: true, ime_events: VecDeque::new(), pending_windows: VecDeque::new(), @@ -429,8 +439,44 @@ impl ImeData { let mut last_event = self.conn().poll_for_event()?; loop { - if let Some(last_event) = last_event { - if self.filter_event(&last_event)? { + if let Some(last_event) = last_event.as_ref() { + if self.filter_event(last_event)? { + return Ok(()); + } + } + + { + // Check the event queue for events. + let event_queue = self.handler.event_queue.clone(); + let mut event_queue = event_queue.borrow_mut(); + + // Check the event queue for events we can use. + let mut found_event = false; + let mut last_err = None; + event_queue.retain(|event| match self.filter_event(event) { + Ok(false) => { + found_event = true; + true + } + Ok(true) => false, + Err(err) => { + last_err = Some(err); + true + } + }); + + // Push our own event to the queue. + if let Some(last_event) = last_event.take() { + event_queue.push_back(last_event); + } + + // Check for errors. + if let Some(err) = last_err { + return Err(err); + } + + // If we found an event, then we're done. + if found_event { return Ok(()); } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 52c78eeed..be4a59dcc 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -27,8 +27,7 @@ use std::{ cell::{Cell, RefCell}, collections::{HashMap, HashSet}, ffi::CStr, - fmt, - mem::MaybeUninit, + fmt, mem, ops::Deref, os::{ raw::*, @@ -43,15 +42,12 @@ use std::{ use atoms::*; -use x11rb::x11_utils::X11Error as LogicalError; -use x11rb::{ - connection::RequestConnection, - protocol::{ - xinput::{self, ConnectionExt as _}, - xkb, - xproto::{self, ConnectionExt as _}, - }, +use x11rb::protocol::{ + xinput::{self, ConnectionExt as _}, + xkb, + xproto::{self, ConnectionExt as _}, }; +use x11rb::x11_utils::X11Error as LogicalError; use x11rb::{ errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}, xcb_ffi::ReplyOrIdError, @@ -200,12 +196,15 @@ impl EventLoop { let wm_delete_window = atoms[WM_DELETE_WINDOW]; let net_wm_ping = atoms[_NET_WM_PING]; + // Create an event queue. + let event_queue = Rc::new(RefCell::new(std::collections::VecDeque::with_capacity(4))); + let dnd = Dnd::new(Arc::clone(&xconn)) .expect("Failed to call XInternAtoms when initializing drag and drop"); let (ime_sender, ime_receiver) = mpsc::channel(); - let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index()) { + let ime = match ime::ImeData::new(&xconn, xconn.default_screen_index(), &event_queue) { Ok(ime) => Some(ime), Err(e) => { log::error!("Failed to open IME: {e}"); @@ -213,21 +212,10 @@ impl EventLoop { } }; - let randr_event_offset = xconn + xconn .select_xrandr_input(root) .expect("Failed to query XRandR extension"); - let xi2ext = xconn - .xcb_connection() - .extension_information(xinput::X11_EXTENSION_NAME) - .expect("Failed to query XInput extension") - .expect("X server missing XInput extension"); - let xkbext = xconn - .xcb_connection() - .extension_information(xkb::X11_EXTENSION_NAME) - .expect("Failed to query XKB extension") - .expect("X server missing XKB extension"); - // Check for XInput2 support. xconn .xcb_connection() @@ -309,13 +297,11 @@ impl EventLoop { }); let event_processor = EventProcessor { + event_queue, target: target.clone(), dnd, devices: Default::default(), - randr_event_offset, ime_requests: ime_receiver, - xi2ext, - xkbext, kb_state, num_touch: 0, held_key_press: None, @@ -598,12 +584,10 @@ impl EventLoop { F: FnMut(Event, &RootELW), { let target = &self.target; - let mut xev = MaybeUninit::uninit(); let wt = get_xtarget(&self.target); - while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { - let mut xev = unsafe { xev.assume_init() }; - self.event_processor.process_event(&mut xev, |event| { + while let Some(event) = self.event_processor.poll_one_event() { + self.event_processor.process_event(event, |event| { if let Event::WindowEvent { window_id: crate::window::WindowId(wid), event: WindowEvent::RedrawRequested, @@ -983,34 +967,6 @@ impl<'a, E: fmt::Debug> CookieResultExt for Result, E> { } } -/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to -/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed -struct GenericEventCookie<'a> { - xconn: &'a XConnection, - cookie: ffi::XGenericEventCookie, -} - -impl<'a> GenericEventCookie<'a> { - fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option> { - unsafe { - let mut cookie: ffi::XGenericEventCookie = From::from(event); - if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True { - Some(GenericEventCookie { xconn, cookie }) - } else { - None - } - } - } -} - -impl<'a> Drop for GenericEventCookie<'a> { - fn drop(&mut self) { - unsafe { - (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); - } - } -} - fn mkwid(w: xproto::Window) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) } @@ -1112,8 +1068,15 @@ impl Device { } } -/// Convert the raw X11 representation for a 32-bit floating point to a double. +/// Convert the raw X11 representation for a 32-bit fixed point to a double. #[inline] fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 { (fp as f64) / ((1 << 16) as f64) } + +/// Conver the raw X11 representation for a 64-bit fixed point number to a double. +#[inline] +fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 { + let xinput::Fp3232 { integral, frac } = fp; + integral as f64 + (frac as f64 / (1u64 << 32) as f64) +} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 1bff92eb0..7e53cb792 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -17,7 +17,6 @@ pub use self::{cursor::*, geometry::*, hint::*, input::*, window_property::*, wm use std::{ mem::{self, MaybeUninit}, - ops::BitAnd, os::raw::*, }; @@ -34,13 +33,6 @@ pub fn maybe_change(field: &mut Option, value: T) -> bool { } } -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} - impl XConnection { // This is impoartant, so pay attention! // Xlib has an output buffer, and tries to hide the async nature of X from you.