winit/winit-x11/src/event_loop.rs
Kirill Chibisov f046e778aa
winit-core:winit: add tablet input support
The API is integrated into the `WindowEvent::Pointer*` API and is
present in form of `TabletTool` variant on corresponding data entries.

For now implemented for Web, Windows, and with limitations for Wayland.

Fixes #99.

Co-authored-by: daxpedda <daxpedda@gmail.com>
2025-10-07 21:42:36 +09:00

1131 lines
36 KiB
Rust

use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::os::raw::*;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, LazyLock, Mutex, Weak};
use std::time::{Duration, Instant};
use std::{fmt, mem, ptr, slice, str};
use calloop::generic::Generic;
use calloop::ping::Ping;
use calloop::{EventLoop as Loop, Readiness};
use libc::{setlocale, LC_CTYPE};
use tracing::warn;
use winit_common::xkb::Context;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
use winit_core::event::{DeviceId, StartCause, WindowEvent};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow, WindowAttributes, WindowId};
use x11rb::connection::RequestConnection;
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use x11rb::protocol::{xkb, xproto};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use crate::atoms::*;
use crate::dnd::Dnd;
use crate::event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN};
use crate::ime::{self, Ime, ImeCreationError, ImeSender};
use crate::util::{self, CustomCursor};
use crate::window::{UnownedWindow, Window};
use crate::xdisplay::{XConnection, XError, XNotSupported};
use crate::{ffi, xsettings, XlibErrorHook};
// Xinput constants not defined in x11rb
pub(crate) const ALL_DEVICES: u16 = 0;
pub(crate) const ALL_MASTER_DEVICES: u16 = 1;
pub(crate) const ICONIC_STATE: u32 = 3;
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
type X11Source = Generic<BorrowedFd<'static>>;
pub(crate) static X11_BACKEND: LazyLock<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
LazyLock::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
/// Hooks for X11 errors.
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
unsafe extern "C" fn x_error_callback(
display: *mut ffi::Display,
event: *mut ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
// which do not require initialization.
let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
unsafe {
(xconn.xlib.XGetErrorText)(
display,
(*event).error_code as c_int,
buf.as_mut_ptr() as *mut c_char,
buf.len() as c_int,
)
};
let description =
unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
let error = unsafe {
XError {
description: description.into_owned(),
error_code: (*event).error_code,
request_code: (*event).request_code,
minor_code: (*event).minor_code,
}
};
// Don't log error.
if !error_handled {
tracing::error!("X11 error: {:#?}", error);
// XXX only update the error, if it wasn't handled by any of the hooks.
*xconn.latest_error.lock().unwrap() = Some(error);
}
}
// Fun fact: this return value is completely ignored.
0
}
#[derive(Debug)]
pub(crate) struct WakeSender<T> {
sender: Sender<T>,
waker: Ping,
}
impl<T> Clone for WakeSender<T> {
fn clone(&self) -> Self {
Self { sender: self.sender.clone(), waker: self.waker.clone() }
}
}
impl<T> WakeSender<T> {
pub fn send(&self, t: T) {
let res = self.sender.send(t);
if res.is_ok() {
self.waker.ping();
}
}
}
#[derive(Debug)]
struct PeekableReceiver<T> {
recv: Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
},
Err(TryRecvError::Empty) => false,
Err(TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
},
}
}
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
}
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(crate) xconn: Arc<XConnection>,
pub(crate) wm_delete_window: xproto::Atom,
pub(crate) net_wm_ping: xproto::Atom,
pub(crate) net_wm_sync_request: xproto::Atom,
pub(crate) ime_sender: ImeSender,
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
pub(crate) root: xproto::Window,
pub(crate) ime: Option<RefCell<Ime>>,
pub(crate) windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
pub(crate) redraw_sender: WakeSender<WindowId>,
pub(crate) activation_sender: WakeSender<ActivationItem>,
event_loop_proxy: CoreEventLoopProxy,
device_events: Cell<DeviceEvents>,
}
#[derive(Debug)]
pub struct EventLoop {
loop_running: bool,
event_loop: Loop<'static, EventLoopState>,
event_processor: EventProcessor,
redraw_receiver: PeekableReceiver<WindowId>,
activation_receiver: PeekableReceiver<ActivationItem>,
/// The current state of the event loop.
state: EventLoopState,
}
pub(crate) type ActivationItem = (WindowId, winit_core::event_loop::AsyncRequestSerial);
#[derive(Debug)]
struct EventLoopState {
/// The latest readiness state for the x11 file descriptor
x11_readiness: Readiness,
/// User requested a wake up.
proxy_wake_up: bool,
}
impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// Required?
return Err(EventLoopError::RecreationAttempt);
}
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => return Err(os_error!(err.clone()).into()),
};
let root = xconn.default_root().root;
let atoms = xconn.atoms();
let wm_delete_window = atoms[WM_DELETE_WINDOW];
let net_wm_ping = atoms[_NET_WM_PING];
let net_wm_sync_request = atoms[_NET_WM_SYNC_REQUEST];
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_event_sender, ime_event_receiver) = mpsc::channel();
// Input methods will open successfully without setting the locale, but it won't be
// possible to actually commit pre-edit sequences.
unsafe {
// Remember default locale to restore it if target locale is unsupported
// by Xlib
let default_locale = setlocale(LC_CTYPE, ptr::null());
setlocale(LC_CTYPE, c"".as_ptr() as *const _);
// Check if set locale is supported by Xlib.
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
// will fail.
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
if !locale_supported {
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
warn!(
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
CStr::from_ptr(unsupported_locale).to_string_lossy(),
CStr::from_ptr(default_locale).to_string_lossy()
);
// Restore default locale
setlocale(LC_CTYPE, default_locale);
}
}
let ime = Ime::new(Arc::clone(&xconn), ime_event_sender);
if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() {
warn!("Failed to open input method: {state:#?}");
} else if let Err(err) = ime.as_ref() {
warn!("Failed to set input method destruction callback: {err:?}");
}
let ime = ime.ok().map(RefCell::new);
let randr_event_offset =
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()
.xinput_xi_query_version(2, 3)
.expect("Failed to send XInput2 query version request")
.reply()
.expect("Error while checking for XInput2 query version reply");
xconn.update_cached_wm_info(root);
// Create an event loop.
let event_loop =
Loop::<EventLoopState>::try_new().expect("Failed to initialize the event loop");
let handle = event_loop.handle();
// Create the X11 event dispatcher.
let source = X11Source::new(
// SAFETY: xcb owns the FD and outlives the source.
unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) },
calloop::Interest::READ,
calloop::Mode::Level,
);
handle
.insert_source(source, |readiness, _, state| {
state.x11_readiness = readiness;
Ok(calloop::PostAction::Continue)
})
.expect("Failed to register the X11 event dispatcher");
let (waker, waker_source) =
calloop::ping::make_ping().expect("Failed to create event loop waker");
event_loop
.handle()
.insert_source(waker_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
})
.expect("Failed to register the event loop waker source");
// Create a channel for handling redraw requests.
let (redraw_sender, redraw_channel) = mpsc::channel();
// Create a channel for sending activation tokens.
let (activation_token_sender, activation_token_channel) = mpsc::channel();
// Create a channel for sending user events.
let (user_waker, user_waker_source) =
calloop::ping::make_ping().expect("Failed to create user event loop waker.");
event_loop
.handle()
.insert_source(user_waker_source, move |_, _, state| {
// No extra handling is required, we just need to wake-up.
state.proxy_wake_up = true;
})
.expect("Failed to register the event loop waker source");
let event_loop_proxy = EventLoopProxy::new(user_waker);
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
let mut xmodmap = util::ModifierKeymap::new();
xmodmap.reload_from_x_connection(&xconn);
let window_target = ActiveEventLoop {
ime,
root,
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
windows: Default::default(),
ime_sender,
xconn,
wm_delete_window,
net_wm_ping,
net_wm_sync_request,
redraw_sender: WakeSender {
sender: redraw_sender, // not used again so no clone
waker: waker.clone(),
},
activation_sender: WakeSender {
sender: activation_token_sender, // not used again so no clone
waker: waker.clone(),
},
event_loop_proxy: event_loop_proxy.into(),
device_events: Default::default(),
};
// Set initial device event filter.
window_target.update_listen_device_events(true);
let event_processor = EventProcessor {
target: window_target,
dnd,
devices: Default::default(),
randr_event_offset,
ime_receiver,
ime_event_receiver,
xi2ext,
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
xmodmap,
xkbext,
xkb_context,
num_touch: 0,
held_key_press: None,
first_touch: None,
active_window: None,
modifiers: Default::default(),
is_composing: false,
};
// Register for device hotplug events
// (The request buffer is flushed during `init_device`)
event_processor
.target
.xconn
.select_xinput_events(
root,
ALL_DEVICES,
x11rb::protocol::xinput::XIEventMask::HIERARCHY,
)
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
event_processor
.target
.xconn
.select_xkb_events(
0x100, // Use the "core keyboard device"
xkb::EventType::NEW_KEYBOARD_NOTIFY
| xkb::EventType::MAP_NOTIFY
| xkb::EventType::STATE_NOTIFY,
)
.unwrap();
event_processor.init_device(ALL_DEVICES);
let event_loop = EventLoop {
loop_running: false,
event_loop,
event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
};
Ok(event_loop)
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.event_processor.target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,
) -> Result<(), EventLoopError> {
self.event_processor.target.clear_exit();
let exit = loop {
match self.pump_app_events(None, &mut app) {
PumpStatus::Exit(0) => {
break Ok(());
},
PumpStatus::Exit(code) => {
break Err(EventLoopError::ExitFailure(code));
},
_ => {
continue;
},
}
};
// Applications aren't allowed to carry windows between separate
// `run_on_demand` calls but if they have only just dropped their
// windows we need to make sure those last requests are sent to the
// X Server.
self.event_processor
.target
.x_connection()
.sync_with_server()
.map_err(|x_err| EventLoopError::Os(os_error!(X11Error::Xlib(x_err))))?;
exit
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
mut app: A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
// run the initial loop iteration
self.single_iteration(&mut app, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut app);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
PumpStatus::Exit(code)
} else {
PumpStatus::Continue
}
}
fn has_pending(&mut self) -> bool {
self.event_processor.poll()
|| self.state.proxy_wake_up
|| self.redraw_receiver.has_incoming()
}
fn poll_events_with_timeout<A: ApplicationHandler>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
let has_pending = self.has_pending();
timeout = if has_pending {
// If we already have work to do then we don't want to block on the next poll.
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
};
self.state.x11_readiness = Readiness::EMPTY;
if let Err(error) =
self.event_loop.dispatch(timeout, &mut self.state).map_err(std::io::Error::from)
{
tracing::error!("Failed to poll for events: {error:?}");
let exit_code = error.raw_os_error().unwrap_or(1);
self.set_exit_code(exit_code);
return;
}
// NB: `StartCause::Init` is handled as a special case and doesn't need
// to be considered here
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(deadline) => {
if Instant::now() < deadline {
StartCause::WaitCancelled { start, requested_resume: Some(deadline) }
} else {
StartCause::ResumeTimeReached { start, requested_resume: deadline }
}
},
};
// False positive / spurious wake ups could lead to us spamming
// redundant iterations of the event loop with no new events to
// dispatch.
//
// If there's no readable event source then we just double check if we
// have any pending `_receiver` events and if not we return without
// running a loop iteration.
// If we don't have any pending `_receiver`
if !self.has_pending()
&& !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll)
&& timeout.is_none()
{
return;
}
self.single_iteration(app, cause);
}
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
// NB: For consistency all platforms must call `can_create_surfaces` even though X11
// applications don't themselves have a formal surface destroy/create lifecycle.
if cause == StartCause::Init {
app.can_create_surfaces(&self.event_processor.target)
}
// Process all pending events
self.drain_events(app);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
let token = self
.event_processor
.with_window(window_id.into_raw() as xproto::Window, |window| {
window.generate_activation_token()
});
match token {
Some(Ok(token)) => {
let event = WindowEvent::ActivationTokenDone {
serial,
token: winit_core::window::ActivationToken::from_raw(token),
};
app.window_event(&self.event_processor.target, window_id, event);
},
Some(Err(e)) => {
tracing::error!("Failed to get activation token: {}", e);
},
None => {},
}
}
// Empty the user event buffer
if mem::take(&mut self.state.proxy_wake_up) {
app.proxy_wake_up(&self.event_processor.target);
}
// Empty the redraw requests
{
let mut windows = HashSet::new();
while let Ok(window_id) = self.redraw_receiver.try_recv() {
windows.insert(window_id);
}
for window_id in windows {
app.window_event(
&self.event_processor.target,
window_id,
WindowEvent::RedrawRequested,
);
}
}
// This is always the last event we dispatch before poll again
app.about_to_wait(&self.event_processor.target);
}
fn drain_events<A: ApplicationHandler>(&mut self, app: &mut A) {
let mut xev = MaybeUninit::uninit();
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, app);
}
}
fn control_flow(&self) -> ControlFlow {
self.event_processor.target.control_flow()
}
fn exiting(&self) -> bool {
self.event_processor.target.exiting()
}
fn set_exit_code(&self, code: i32) {
self.event_processor.target.set_exit_code(code);
}
fn exit_code(&self) -> Option<i32> {
self.event_processor.target.exit_code()
}
}
impl AsFd for EventLoop {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl AsRawFd for EventLoop {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
impl ActiveEventLoop {
/// Returns the `XConnection` of this events loop.
#[inline]
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
&self.xconn
}
/// Update the device event based on window focus.
pub fn update_listen_device_events(&self, focus: bool) {
let device_events = self.device_events.get() == DeviceEvents::Always
|| (focus && self.device_events.get() == DeviceEvents::WhenFocused);
let mut mask = xinput::XIEventMask::from(0u32);
if device_events {
mask = xinput::XIEventMask::RAW_MOTION
| xinput::XIEventMask::RAW_BUTTON_PRESS
| xinput::XIEventMask::RAW_BUTTON_RELEASE
| xinput::XIEventMask::RAW_KEY_PRESS
| xinput::XIEventMask::RAW_KEY_RELEASE;
}
self.xconn
.select_xinput_events(self.root, ALL_MASTER_DEVICES, mask)
.expect_then_ignore_error("Failed to update device event filter");
}
pub(crate) fn clear_exit(&self) {
self.exit.set(None)
}
pub(crate) fn set_exit_code(&self, code: i32) {
self.exit.set(Some(code))
}
pub(crate) fn exit_code(&self) -> Option<i32> {
self.exit.get()
}
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
self.event_loop_proxy.clone()
}
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, custom_cursor)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
self.xconn
.available_monitors()
.into_iter()
.flatten()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.xconn.primary_monitor().ok().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
}
fn system_theme(&self) -> Option<Theme> {
None
}
fn listen_device_events(&self, allowed: DeviceEvents) {
self.device_events.set(allowed);
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow)
}
fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
fn exit(&self) {
self.exit.set(Some(0))
}
fn exiting(&self) -> bool {
self.exit.get().is_some()
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(self.x_connection().clone())
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
self.xconn.display_handle()
}
}
pub(crate) struct DeviceInfo<'a> {
xconn: &'a XConnection,
info: *const ffi::XIDeviceInfo,
count: usize,
}
impl<'a> DeviceInfo<'a> {
pub(crate) fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
unsafe {
let mut count = 0;
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
xconn.check_errors().ok()?;
if info.is_null() || count == 0 {
None
} else {
Some(DeviceInfo { xconn, info, count: count as usize })
}
}
}
}
impl Drop for DeviceInfo<'_> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl Deref for DeviceInfo<'_> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.info, self.count) }
}
}
#[derive(Clone, Debug)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.ping.ping();
}
}
impl EventLoopProxy {
fn new(ping: Ping) -> Self {
Self { ping }
}
}
impl From<EventLoopProxy> for CoreEventLoopProxy {
fn from(value: EventLoopProxy) -> Self {
CoreEventLoopProxy::new(Arc::new(value))
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {
/// An error from the Xlib library.
Xlib(XError),
/// An error that occurred while trying to connect to the X server.
Connect(ConnectError),
/// An error that occurred over the connection medium.
Connection(ConnectionError),
/// An error that occurred logically on the X11 end.
X11(LogicalError),
/// The XID range has been exhausted.
XidsExhausted(IdsExhausted),
/// Got `null` from an Xlib function without a reason.
UnexpectedNull(&'static str),
/// Got an invalid activation token.
InvalidActivationToken(Vec<u8>),
/// An extension that we rely on is not available.
MissingExtension(&'static str),
/// Could not find a matching X11 visual for this visualid
NoSuchVisual(xproto::Visualid),
/// Unable to parse xsettings.
XsettingsParse(xsettings::ParserError),
/// Failed to get property.
GetProperty(util::GetPropertyError),
/// Could not find an ARGB32 pict format.
NoArgb32Format,
}
impl fmt::Display for X11Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X11Error::Xlib(e) => write!(f, "Xlib error: {e}"),
X11Error::Connect(e) => write!(f, "X11 connection error: {e}"),
X11Error::Connection(e) => write!(f, "X11 connection error: {e}"),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"),
X11Error::X11(e) => write!(f, "X11 error: {e:?}"),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"),
X11Error::InvalidActivationToken(s) => write!(
f,
"Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
X11Error::NoSuchVisual(visualid) => {
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
},
X11Error::XsettingsParse(err) => {
write!(f, "Failed to parse xsettings: {err:?}")
},
X11Error::NoArgb32Format => {
f.write_str("winit only supports X11 displays with ARGB32 picture formats")
},
}
}
}
impl std::error::Error for X11Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
X11Error::Xlib(e) => Some(e),
X11Error::Connect(e) => Some(e),
X11Error::Connection(e) => Some(e),
X11Error::XidsExhausted(e) => Some(e),
_ => None,
}
}
}
impl From<XError> for X11Error {
fn from(e: XError) -> Self {
X11Error::Xlib(e)
}
}
impl From<ConnectError> for X11Error {
fn from(e: ConnectError) -> Self {
X11Error::Connect(e)
}
}
impl From<ConnectionError> for X11Error {
fn from(e: ConnectionError) -> Self {
X11Error::Connection(e)
}
}
impl From<LogicalError> for X11Error {
fn from(e: LogicalError) -> Self {
X11Error::X11(e)
}
}
impl From<ReplyError> for X11Error {
fn from(value: ReplyError) -> Self {
match value {
ReplyError::ConnectionError(e) => e.into(),
ReplyError::X11Error(e) => e.into(),
}
}
}
impl From<ime::ImeContextCreationError> for X11Error {
fn from(value: ime::ImeContextCreationError) -> Self {
match value {
ime::ImeContextCreationError::XError(e) => e.into(),
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
}
}
}
impl From<ReplyOrIdError> for X11Error {
fn from(value: ReplyOrIdError) -> Self {
match value {
ReplyOrIdError::ConnectionError(e) => e.into(),
ReplyOrIdError::X11Error(e) => e.into(),
ReplyOrIdError::IdsExhausted => Self::XidsExhausted(IdsExhausted),
}
}
}
impl From<xsettings::ParserError> for X11Error {
fn from(value: xsettings::ParserError) -> Self {
Self::XsettingsParse(value)
}
}
impl From<util::GetPropertyError> for X11Error {
fn from(value: util::GetPropertyError) -> Self {
Self::GetProperty(value)
}
}
/// Type alias for a void cookie.
pub(crate) type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
/// Extension trait for `Result<VoidCookie, E>`.
pub(crate) trait CookieResultExt {
/// Unwrap the send error and ignore the result.
fn expect_then_ignore_error(self, msg: &str);
}
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}
}
pub(crate) fn mkwid(w: xproto::Window) -> winit_core::window::WindowId {
winit_core::window::WindowId::from_raw(w as _)
}
pub(crate) fn mkdid(w: xinput::DeviceId) -> DeviceId {
DeviceId::from_raw(w as i64)
}
#[derive(Debug)]
pub struct Device {
_name: String,
pub(crate) scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).
// For slave devices, this is the master.
pub(crate) attachment: c_int,
pub(crate) r#type: DeviceType,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum DeviceType {
Mouse,
Touch,
Pen,
Eraser,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct ScrollAxis {
pub(crate) increment: f64,
pub(crate) orientation: ScrollOrientation,
pub(crate) position: f64,
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum ScrollOrientation {
Vertical,
Horizontal,
}
impl Device {
pub(crate) fn new(info: &ffi::XIDeviceInfo, atoms: &Atoms) -> Self {
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
let mut scroll_axes = Vec::new();
let mut r#type = None;
if Device::physical_device(info) {
// Identify scroll axes
for &class_ptr in Device::classes(info) {
let ty = unsafe { (*class_ptr)._type };
if ty == ffi::XIScrollClass {
let info = unsafe { &*(class_ptr as *const ffi::XIScrollClassInfo) };
scroll_axes.push((info.number, ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
position: 0.0,
}));
} else if ty == ffi::XITouchClass {
r#type = Some(DeviceType::Touch);
} else if r#type.is_none() && ty == ffi::XIValuatorClass {
let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) };
let atom = info.label as xproto::Atom;
if atom == atoms[ABS_X]
|| atom == atoms[ABS_Y]
|| atom == atoms[ABS_PRESSURE]
|| atom == atoms[ABS_TILT_X]
|| atom == atoms[ABS_TILT_Y]
{
if name.contains("eraser") {
r#type = Some(DeviceType::Eraser);
} else {
r#type = Some(DeviceType::Pen);
}
}
}
}
}
let mut device = Device {
_name: name.into_owned(),
scroll_axes,
attachment: info.attachment,
r#type: r#type.unwrap_or(DeviceType::Mouse),
};
device.reset_scroll_position(info);
device
}
pub(crate) fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
if Device::physical_device(info) {
for &class_ptr in Device::classes(info) {
let ty = unsafe { (*class_ptr)._type };
if ty == ffi::XIValuatorClass {
let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) };
if let Some(&mut (_, ref mut axis)) =
self.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number)
{
axis.position = info.value;
}
}
}
}
}
#[inline]
fn physical_device(info: &ffi::XIDeviceInfo) -> bool {
info._use == ffi::XISlaveKeyboard
|| info._use == ffi::XISlavePointer
|| info._use == ffi::XIFloatingSlave
}
#[inline]
fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] {
unsafe {
slice::from_raw_parts(
info.classes as *const *const ffi::XIAnyClassInfo,
info.num_classes as usize,
)
}
}
}
/// Convert the raw X11 representation for a 32-bit floating point to a double.
#[inline]
pub(crate) fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
(fp as f64) / ((1 << 16) as f64)
}
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}