2024-02-09 05:52:09 +04:00
|
|
|
use std::cell::{Cell, RefCell};
|
2024-02-26 12:59:41 +04:00
|
|
|
use std::collections::{HashMap, HashSet, VecDeque};
|
2024-02-09 05:52:09 +04:00
|
|
|
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::mpsc::{self, Receiver, Sender, TryRecvError};
|
2025-05-05 21:55:12 +09:00
|
|
|
use std::sync::{Arc, LazyLock, Mutex, Weak};
|
2024-02-09 05:52:09 +04:00
|
|
|
use std::time::{Duration, Instant};
|
2024-06-24 13:04:55 +03:00
|
|
|
use std::{fmt, mem, ptr, slice, str};
|
2019-06-21 11:33:15 -04:00
|
|
|
|
2023-05-31 09:44:42 -07:00
|
|
|
use calloop::generic::Generic;
|
2023-06-18 12:40:03 +01:00
|
|
|
use calloop::ping::Ping;
|
|
|
|
|
use calloop::{EventLoop as Loop, Readiness};
|
2024-02-19 08:34:39 +04:00
|
|
|
use libc::{setlocale, LC_CTYPE};
|
2024-02-25 19:20:39 -08:00
|
|
|
use tracing::warn;
|
2025-05-17 04:26:09 +02:00
|
|
|
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};
|
2025-05-20 16:56:53 +02:00
|
|
|
use winit_core::event_loop::pump_events::PumpStatus;
|
2025-05-17 04:26:09 +02:00
|
|
|
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};
|
2024-02-09 05:52:09 +04:00
|
|
|
use x11rb::connection::RequestConnection;
|
|
|
|
|
use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError};
|
|
|
|
|
use x11rb::protocol::xinput::{self, ConnectionExt as _};
|
2024-08-23 23:40:27 +03:00
|
|
|
use x11rb::protocol::{xkb, xproto};
|
2023-07-12 00:59:12 -07:00
|
|
|
use x11rb::x11_utils::X11Error as LogicalError;
|
2024-02-09 05:52:09 +04:00
|
|
|
use x11rb::xcb_ffi::ReplyOrIdError;
|
2023-07-12 00:59:12 -07:00
|
|
|
|
2025-03-08 10:14:08 +03:00
|
|
|
use crate::platform::x11::XlibErrorHook;
|
2024-02-03 07:27:17 +04:00
|
|
|
use crate::platform_impl::common::xkb::Context;
|
2024-10-08 15:29:40 +02:00
|
|
|
use crate::platform_impl::platform::min_timeout;
|
2024-08-23 23:40:27 +03:00
|
|
|
use crate::platform_impl::x11::window::Window;
|
2017-03-03 21:41:51 +01:00
|
|
|
|
2024-02-09 05:52:09 +04:00
|
|
|
mod activation;
|
|
|
|
|
mod atoms;
|
|
|
|
|
mod dnd;
|
|
|
|
|
mod event_processor;
|
|
|
|
|
pub mod ffi;
|
|
|
|
|
mod ime;
|
|
|
|
|
mod monitor;
|
|
|
|
|
mod util;
|
2024-08-23 23:40:27 +03:00
|
|
|
pub(crate) mod window;
|
2024-02-09 05:52:09 +04:00
|
|
|
mod xdisplay;
|
|
|
|
|
mod xsettings;
|
|
|
|
|
|
|
|
|
|
use atoms::*;
|
|
|
|
|
use dnd::{Dnd, DndState};
|
2024-02-26 12:59:41 +04:00
|
|
|
use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN};
|
2024-02-09 05:52:09 +04:00
|
|
|
use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender};
|
|
|
|
|
pub(crate) use monitor::{MonitorHandle, VideoModeHandle};
|
2024-02-26 12:59:41 +04:00
|
|
|
pub use util::CustomCursor;
|
2024-02-09 05:52:09 +04:00
|
|
|
use window::UnownedWindow;
|
|
|
|
|
pub(crate) use xdisplay::{XConnection, XError, XNotSupported};
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
// Xinput constants not defined in x11rb
|
|
|
|
|
const ALL_DEVICES: u16 = 0;
|
|
|
|
|
const ALL_MASTER_DEVICES: u16 = 1;
|
2023-11-28 16:20:36 -08:00
|
|
|
const ICONIC_STATE: u32 = 3;
|
2023-08-29 14:01:25 -07:00
|
|
|
|
2024-03-01 13:40:20 +04:00
|
|
|
/// The underlying x11rb connection that we are using.
|
|
|
|
|
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
|
|
|
|
|
|
2023-10-15 06:49:57 +04:00
|
|
|
type X11Source = Generic<BorrowedFd<'static>>;
|
2022-02-04 12:13:04 +01:00
|
|
|
|
2025-03-08 10:14:08 +03:00
|
|
|
#[cfg(x11_platform)]
|
2025-05-05 21:55:12 +09:00
|
|
|
pub(crate) static X11_BACKEND: LazyLock<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
|
|
|
|
|
LazyLock::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
|
2025-03-08 10:14:08 +03:00
|
|
|
|
|
|
|
|
/// Hooks for X11 errors.
|
|
|
|
|
#[cfg(x11_platform)]
|
|
|
|
|
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
|
|
|
|
|
|
|
|
|
|
#[cfg(x11_platform)]
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Debug)]
|
2023-06-18 12:40:03 +01:00
|
|
|
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> {
|
2024-06-24 13:04:55 +03:00
|
|
|
pub fn send(&self, t: T) {
|
|
|
|
|
let res = self.sender.send(t);
|
2023-06-18 12:40:03 +01:00
|
|
|
if res.is_ok() {
|
|
|
|
|
self.waker.ping();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Debug)]
|
2023-06-18 12:40:03 +01:00
|
|
|
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 }
|
|
|
|
|
}
|
2024-04-26 19:11:44 +04:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
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
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-26 19:11:44 +04:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
|
|
|
|
if let Some(first) = self.first.take() {
|
|
|
|
|
return Ok(first);
|
|
|
|
|
}
|
|
|
|
|
self.recv.try_recv()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Debug)]
|
2024-01-31 17:29:59 +04:00
|
|
|
pub struct ActiveEventLoop {
|
2018-05-29 07:48:47 -04:00
|
|
|
xconn: Arc<XConnection>,
|
2023-07-12 00:59:12 -07:00
|
|
|
wm_delete_window: xproto::Atom,
|
|
|
|
|
net_wm_ping: xproto::Atom,
|
2024-07-21 18:39:43 +02:00
|
|
|
net_wm_sync_request: xproto::Atom,
|
2018-04-10 22:18:30 -04:00
|
|
|
ime_sender: ImeSender,
|
2023-09-07 08:25:04 +02:00
|
|
|
control_flow: Cell<ControlFlow>,
|
|
|
|
|
exit: Cell<Option<i32>>,
|
2023-07-12 00:59:12 -07:00
|
|
|
root: xproto::Window,
|
2024-02-13 07:49:58 +04:00
|
|
|
ime: Option<RefCell<Ime>>,
|
2018-05-29 07:48:47 -04:00
|
|
|
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
2023-06-18 12:40:03 +01:00
|
|
|
redraw_sender: WakeSender<WindowId>,
|
|
|
|
|
activation_sender: WakeSender<ActivationToken>,
|
2024-11-12 10:56:20 +03:00
|
|
|
event_loop_proxy: CoreEventLoopProxy,
|
2023-05-30 21:32:31 +02:00
|
|
|
device_events: Cell<DeviceEvents>,
|
2019-04-27 18:06:51 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Debug)]
|
2024-06-24 13:04:55 +03:00
|
|
|
pub struct EventLoop {
|
2023-06-18 12:40:03 +01:00
|
|
|
loop_running: bool,
|
|
|
|
|
event_loop: Loop<'static, EventLoopState>,
|
2024-01-13 21:36:53 +01:00
|
|
|
event_processor: EventProcessor,
|
2023-06-18 12:40:03 +01:00
|
|
|
redraw_receiver: PeekableReceiver<WindowId>,
|
|
|
|
|
activation_receiver: PeekableReceiver<ActivationToken>,
|
2023-05-31 09:44:42 -07:00
|
|
|
|
|
|
|
|
/// The current state of the event loop.
|
2023-06-18 12:40:03 +01:00
|
|
|
state: EventLoopState,
|
2023-05-31 09:44:42 -07:00
|
|
|
}
|
|
|
|
|
|
2025-05-17 04:26:09 +02:00
|
|
|
type ActivationToken = (WindowId, winit_core::event_loop::AsyncRequestSerial);
|
2023-07-20 13:16:51 +00:00
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Debug)]
|
2023-06-18 12:40:03 +01:00
|
|
|
struct EventLoopState {
|
|
|
|
|
/// The latest readiness state for the x11 file descriptor
|
|
|
|
|
x11_readiness: Readiness,
|
2017-05-25 23:19:13 +10:00
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
/// User requested a wake up.
|
|
|
|
|
proxy_wake_up: bool,
|
2019-08-06 05:51:42 +09:00
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
impl EventLoop {
|
2025-03-08 10:14:08 +03:00
|
|
|
pub(crate) fn new() -> Result<EventLoop, EventLoopError> {
|
|
|
|
|
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()),
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
let root = xconn.default_root().root;
|
|
|
|
|
let atoms = xconn.atoms();
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
let wm_delete_window = atoms[WM_DELETE_WINDOW];
|
|
|
|
|
let net_wm_ping = atoms[_NET_WM_PING];
|
2024-07-21 18:39:43 +02:00
|
|
|
let net_wm_sync_request = atoms[_NET_WM_SYNC_REQUEST];
|
2019-06-28 01:40:27 +01:00
|
|
|
|
2018-05-29 07:48:47 -04:00
|
|
|
let dnd = Dnd::new(Arc::clone(&xconn))
|
2017-12-13 06:22:03 -05:00
|
|
|
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
|
|
|
|
|
2018-04-10 22:18:30 -04:00
|
|
|
let (ime_sender, ime_receiver) = mpsc::channel();
|
2022-05-07 05:29:25 +03:00
|
|
|
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
2018-04-10 22:18:30 -04:00
|
|
|
// Input methods will open successfully without setting the locale, but it won't be
|
|
|
|
|
// possible to actually commit pre-edit sequences.
|
2019-06-21 11:33:15 -04:00
|
|
|
unsafe {
|
2020-02-10 06:37:06 +01:00
|
|
|
// Remember default locale to restore it if target locale is unsupported
|
|
|
|
|
// by Xlib
|
|
|
|
|
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
2025-05-05 21:55:12 +09:00
|
|
|
setlocale(LC_CTYPE, c"".as_ptr() as *const _);
|
2020-02-10 06:37:06 +01:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
2019-06-21 11:33:15 -04:00
|
|
|
}
|
2024-02-13 07:49:58 +04:00
|
|
|
|
|
|
|
|
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);
|
2018-04-05 14:58:10 -04:00
|
|
|
|
2019-06-21 11:33:15 -04:00
|
|
|
let randr_event_offset =
|
2018-05-14 08:14:57 -04:00
|
|
|
xconn.select_xrandr_input(root).expect("Failed to query XRandR extension");
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
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");
|
2023-05-28 20:02:59 +02:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
// 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");
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2018-05-29 07:48:47 -04:00
|
|
|
xconn.update_cached_wm_info(root);
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2023-05-31 09:44:42 -07:00
|
|
|
// Create an event loop.
|
|
|
|
|
let event_loop =
|
2023-06-18 12:40:03 +01:00
|
|
|
Loop::<EventLoopState>::try_new().expect("Failed to initialize the event loop");
|
2023-05-31 09:44:42 -07:00
|
|
|
let handle = event_loop.handle();
|
|
|
|
|
|
|
|
|
|
// Create the X11 event dispatcher.
|
2023-07-12 00:59:12 -07:00
|
|
|
let source = X11Source::new(
|
2023-10-15 06:49:57 +04:00
|
|
|
// SAFETY: xcb owns the FD and outlives the source.
|
|
|
|
|
unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) },
|
2023-07-12 00:59:12 -07:00
|
|
|
calloop::Interest::READ,
|
|
|
|
|
calloop::Mode::Level,
|
|
|
|
|
);
|
2023-05-31 09:44:42 -07:00
|
|
|
handle
|
2023-06-18 12:40:03 +01:00
|
|
|
.insert_source(source, |readiness, _, state| {
|
|
|
|
|
state.x11_readiness = readiness;
|
|
|
|
|
Ok(calloop::PostAction::Continue)
|
|
|
|
|
})
|
2023-05-31 09:44:42 -07:00
|
|
|
.expect("Failed to register the X11 event dispatcher");
|
|
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
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.
|
2023-05-31 09:44:42 -07:00
|
|
|
})
|
2023-06-18 12:40:03 +01:00
|
|
|
.expect("Failed to register the event loop waker source");
|
2023-05-31 09:44:42 -07:00
|
|
|
|
|
|
|
|
// Create a channel for handling redraw requests.
|
2023-06-18 12:40:03 +01:00
|
|
|
let (redraw_sender, redraw_channel) = mpsc::channel();
|
2023-05-31 09:44:42 -07:00
|
|
|
|
2023-07-20 13:16:51 +00:00
|
|
|
// Create a channel for sending activation tokens.
|
2023-06-18 12:40:03 +01:00
|
|
|
let (activation_token_sender, activation_token_channel) = mpsc::channel();
|
|
|
|
|
|
|
|
|
|
// Create a channel for sending user events.
|
2024-06-24 13:04:55 +03:00
|
|
|
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);
|
2023-07-20 13:16:51 +00:00
|
|
|
|
2024-02-18 01:39:42 +04:00
|
|
|
let xkb_context =
|
|
|
|
|
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
2023-05-28 20:02:59 +02:00
|
|
|
|
2024-02-26 12:59:41 +04:00
|
|
|
let mut xmodmap = util::ModifierKeymap::new();
|
|
|
|
|
xmodmap.reload_from_x_connection(&xconn);
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
let window_target = ActiveEventLoop {
|
2022-06-07 23:17:45 +02:00
|
|
|
ime,
|
|
|
|
|
root,
|
2023-09-07 08:25:04 +02:00
|
|
|
control_flow: Cell::new(ControlFlow::default()),
|
|
|
|
|
exit: Cell::new(None),
|
2022-06-07 23:17:45 +02:00
|
|
|
windows: Default::default(),
|
|
|
|
|
ime_sender,
|
|
|
|
|
xconn,
|
|
|
|
|
wm_delete_window,
|
|
|
|
|
net_wm_ping,
|
2024-07-21 18:39:43 +02:00
|
|
|
net_wm_sync_request,
|
2023-06-18 12:40:03 +01:00
|
|
|
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(),
|
|
|
|
|
},
|
2024-11-12 10:56:20 +03:00
|
|
|
event_loop_proxy: event_loop_proxy.into(),
|
2023-05-30 21:32:31 +02:00
|
|
|
device_events: Default::default(),
|
2022-06-07 23:17:45 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set initial device event filter.
|
2023-05-30 21:32:31 +02:00
|
|
|
window_target.update_listen_device_events(true);
|
2022-06-07 23:17:45 +02:00
|
|
|
|
2020-01-04 01:31:23 -05:00
|
|
|
let event_processor = EventProcessor {
|
2024-08-06 21:02:53 +03:00
|
|
|
target: window_target,
|
2017-12-13 06:22:03 -05:00
|
|
|
dnd,
|
2018-05-29 07:48:47 -04:00
|
|
|
devices: Default::default(),
|
2019-04-27 18:06:51 +02:00
|
|
|
randr_event_offset,
|
|
|
|
|
ime_receiver,
|
2022-05-07 05:29:25 +03:00
|
|
|
ime_event_receiver,
|
2017-12-13 06:22:03 -05:00
|
|
|
xi2ext,
|
2024-02-26 12:59:41 +04:00
|
|
|
xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN),
|
|
|
|
|
xmodmap,
|
2023-05-28 20:02:59 +02:00
|
|
|
xkbext,
|
2024-02-18 01:39:42 +04:00
|
|
|
xkb_context,
|
2019-12-11 17:23:55 -07:00
|
|
|
num_touch: 0,
|
2023-07-09 13:05:49 -04:00
|
|
|
held_key_press: None,
|
2019-12-11 17:23:55 -07:00
|
|
|
first_touch: None,
|
Move `ModifiersChanged` variant to `WindowEvent` (#1381)
* Move `ModifiersChanged` variant to `WindowEvent`
* macos: Fix flags_changed for ModifiersChanged variant move
I haven't look too deep at what this does internally, but at least
cargo-check is fully happy now. :)
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Fire a ModifiersChanged event on window_did_resign_key
From debugging, I determined that macOS' emission of a flagsChanged
around window switching is inconsistent. It is fair to assume, I think,
that when the user switches windows, they do not expect their former
modifiers state to remain effective; so I think it's best to clear that
state by sending a ModifiersChanged(ModifiersState::empty()).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Fix build
I don't know enough about the code to implement the fix as it is done on
this branch, but this commit at least fixes the build.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS
Very similar to the changes made in [1], as focus is lost, send an event
to the window indicating that the modifiers have been released.
It's unclear to me (without a Windows device to test this on) whether
this is necessary, but it certainly ensures that unfocused windows will
have at least received this event, which is an improvement.
[1]: f79f21641a31da3e4039d41be89047cdcc6028f7
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* macos: Add a hook to update stale modifiers
Sometimes, `ViewState` and `event` might have different values for their
stored `modifiers` flags. These are internally stored as a bitmask in
the latter and an enum in the former.
We can check to see if they differ, and if they do, automatically
dispatch an event to update consumers of modifier state as well as the
stored `state.modifiers`. That's what the hook does.
This hook is then called in the key_down, mouse_entered, mouse_exited,
mouse_click, scroll_wheel, and pressure_change_with_event callbacks,
which each will contain updated modifiers.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Only call event_mods once when determining whether to update state
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* flags_changed: Memoize window_id collection
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_did_resign_key: Remove synthetic ModifiersChanged event
We no longer need to emit this event, since we are checking the state of
our modifiers before emitting most other events.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Add a call to update_potentially_stale_modifiers
Now, cover all events (that I can think of, at least) where stale
modifiers might affect how user programs behave. Effectively, every
human-interface event (keypress, mouse click, keydown, etc.) will cause
a ModifiersChanged event to be fired if something has changed.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* key_up: Add a call to update_potentially_stale_modifiers
We also want to make sure modifiers state is synchronized here, too.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* mouse_motion: Remove update_potentially_stale_modifiers invocation
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Retry CI
* ViewState: Promote visibility of modifiers to the macos impl
This is so that we can interact with the ViewState directly from the
WindowDelegate.
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* window_delegate: Synthetically set modifiers state to empty on resignKey
This logic is implemented similarly on other platforms, so we wish to
regain parity here. Originally this behavior was implemented to always
fire an event with ModifiersState::empty(), but that was not the best as
it was not necessarily correct and could be a duplicate event.
This solution is perhaps the most elegant possible to implement the
desired behavior of sending a synthetic empty modifiers event when a
window loses focus, trading some safety for interoperation between the
NSWindowDelegate and the NSView (as the objc runtime must now be
consulted in order to acquire access to the ViewState which is "owned"
by the NSView).
Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com>
* Check for modifiers change in window events
* Fix modifier changed on macOS
Since the `mouse_entered` function was generating a mouse motion, which
updates the modifier state, a modifiers changed event was incorrectly
generated.
The updating of the modifier state has also been changed to make sure it
consistently happens before events that have a modifier state attached
to it, without happening on any other event.
This of course means that no `CursorMoved` event is generated anymore
when the user enters the window without it being focused, however I'd
say that is consistent with how winit should behave.
* Fix unused variable warning
* Move changelog entry into `Unreleased` section
Co-authored-by: Freya Gentz <zegentzy@protonmail.com>
Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com>
Co-authored-by: Christian Duerr <contact@christianduerr.com>
2020-03-06 15:43:55 -07:00
|
|
|
active_window: None,
|
2023-12-30 09:05:03 +04:00
|
|
|
modifiers: Default::default(),
|
2022-05-07 05:29:25 +03:00
|
|
|
is_composing: false,
|
2017-04-22 13:52:35 -07:00
|
|
|
};
|
|
|
|
|
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
// Register for device hotplug events
|
2018-05-27 08:49:35 -04:00
|
|
|
// (The request buffer is flushed during `init_device`)
|
2024-08-06 21:02:53 +03:00
|
|
|
event_processor
|
|
|
|
|
.target
|
|
|
|
|
.xconn
|
2023-07-12 00:59:12 -07:00
|
|
|
.select_xinput_events(
|
|
|
|
|
root,
|
2023-08-29 14:01:25 -07:00
|
|
|
ALL_DEVICES,
|
2023-07-12 00:59:12 -07:00
|
|
|
x11rb::protocol::xinput::XIEventMask::HIERARCHY,
|
|
|
|
|
)
|
|
|
|
|
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
event_processor
|
|
|
|
|
.target
|
|
|
|
|
.xconn
|
2023-05-28 20:02:59 +02:00
|
|
|
.select_xkb_events(
|
|
|
|
|
0x100, // Use the "core keyboard device"
|
2023-12-30 01:10:38 +04:00
|
|
|
xkb::EventType::NEW_KEYBOARD_NOTIFY
|
|
|
|
|
| xkb::EventType::MAP_NOTIFY
|
|
|
|
|
| xkb::EventType::STATE_NOTIFY,
|
2023-05-28 20:02:59 +02:00
|
|
|
)
|
2023-07-12 00:59:12 -07:00
|
|
|
.unwrap();
|
2023-05-28 20:02:59 +02:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
event_processor.init_device(ALL_DEVICES);
|
2019-04-27 18:06:51 +02:00
|
|
|
|
2025-03-08 10:14:08 +03:00
|
|
|
let event_loop = EventLoop {
|
2023-06-18 12:40:03 +01:00
|
|
|
loop_running: false,
|
2023-05-31 09:44:42 -07:00
|
|
|
event_loop,
|
2021-08-24 03:38:56 -07:00
|
|
|
event_processor,
|
2023-06-18 12:40:03 +01:00
|
|
|
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
|
|
|
|
|
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
|
2024-06-24 13:04:55 +03:00
|
|
|
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
|
2025-03-08 10:14:08 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(event_loop)
|
2017-03-03 21:41:51 +01:00
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop {
|
2024-02-09 05:52:09 +04:00
|
|
|
&self.event_processor.target
|
2019-02-25 01:02:55 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-11 15:38:09 +02:00
|
|
|
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
|
2024-05-20 20:27:36 +04:00
|
|
|
self.run_app_on_demand(app)
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
pub fn run_app_on_demand<A: ApplicationHandler>(
|
2024-05-20 20:27:36 +04:00
|
|
|
&mut self,
|
2024-07-11 15:38:09 +02:00
|
|
|
mut app: A,
|
2024-05-20 20:27:36 +04:00
|
|
|
) -> Result<(), EventLoopError> {
|
2024-08-06 21:02:53 +03:00
|
|
|
self.event_processor.target.clear_exit();
|
2023-07-04 22:53:17 +01:00
|
|
|
let exit = loop {
|
2024-07-11 15:38:09 +02:00
|
|
|
match self.pump_app_events(None, &mut app) {
|
2023-06-18 12:40:03 +01:00
|
|
|
PumpStatus::Exit(0) => {
|
|
|
|
|
break Ok(());
|
2023-07-20 13:16:51 +00:00
|
|
|
},
|
2023-06-18 12:40:03 +01:00
|
|
|
PumpStatus::Exit(code) => {
|
2023-08-13 23:20:09 +04:00
|
|
|
break Err(EventLoopError::ExitFailure(code));
|
2023-06-18 12:40:03 +01:00
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
continue;
|
2017-04-22 13:52:35 -07:00
|
|
|
},
|
|
|
|
|
}
|
2023-07-04 22:53:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Applications aren't allowed to carry windows between separate
|
2023-10-03 23:24:42 +02:00
|
|
|
// `run_on_demand` calls but if they have only just dropped their
|
2023-07-04 22:53:17 +01:00
|
|
|
// windows we need to make sure those last requests are sent to the
|
|
|
|
|
// X Server.
|
2024-09-06 17:20:11 +03:00
|
|
|
self.event_processor
|
|
|
|
|
.target
|
|
|
|
|
.x_connection()
|
|
|
|
|
.sync_with_server()
|
|
|
|
|
.map_err(|x_err| EventLoopError::Os(os_error!(X11Error::Xlib(x_err))))?;
|
2023-07-04 22:53:17 +01:00
|
|
|
|
|
|
|
|
exit
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2023-05-31 09:44:42 -07:00
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
pub fn pump_app_events<A: ApplicationHandler>(
|
2024-05-20 20:27:36 +04:00
|
|
|
&mut self,
|
|
|
|
|
timeout: Option<Duration>,
|
2024-07-11 15:38:09 +02:00
|
|
|
mut app: A,
|
2024-05-20 20:27:36 +04:00
|
|
|
) -> PumpStatus {
|
2023-06-18 12:40:03 +01:00
|
|
|
if !self.loop_running {
|
|
|
|
|
self.loop_running = true;
|
2020-11-07 11:46:37 -07:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
// run the initial loop iteration
|
2024-07-11 15:38:09 +02:00
|
|
|
self.single_iteration(&mut app, StartCause::Init);
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2020-11-07 11:46:37 -07:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
// Consider the possibility that the `StartCause::Init` iteration could
|
|
|
|
|
// request to Exit.
|
2023-09-07 08:25:04 +02:00
|
|
|
if !self.exiting() {
|
2024-07-11 15:38:09 +02:00
|
|
|
self.poll_events_with_timeout(timeout, &mut app);
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2023-09-07 08:25:04 +02:00
|
|
|
if let Some(code) = self.exit_code() {
|
2023-06-18 12:40:03 +01:00
|
|
|
self.loop_running = false;
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
PumpStatus::Exit(code)
|
|
|
|
|
} else {
|
|
|
|
|
PumpStatus::Continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn has_pending(&mut self) -> bool {
|
|
|
|
|
self.event_processor.poll()
|
2024-06-24 13:04:55 +03:00
|
|
|
|| self.state.proxy_wake_up
|
2023-06-18 12:40:03 +01:00
|
|
|
|| self.redraw_receiver.has_incoming()
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 15:38:09 +02:00
|
|
|
fn poll_events_with_timeout<A: ApplicationHandler>(
|
2024-05-20 20:27:36 +04:00
|
|
|
&mut self,
|
|
|
|
|
mut timeout: Option<Duration>,
|
|
|
|
|
app: &mut A,
|
|
|
|
|
) {
|
2023-06-18 12:40:03 +01:00
|
|
|
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 {
|
2023-09-07 08:25:04 +02:00
|
|
|
let control_flow_timeout = match self.control_flow() {
|
2023-06-18 12:40:03 +01:00
|
|
|
ControlFlow::Wait => None,
|
|
|
|
|
ControlFlow::Poll => Some(Duration::ZERO),
|
|
|
|
|
ControlFlow::WaitUntil(wait_deadline) => {
|
|
|
|
|
Some(wait_deadline.saturating_duration_since(start))
|
2019-06-24 12:14:55 -04:00
|
|
|
},
|
2023-06-18 12:40:03 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2024-02-25 19:20:39 -08:00
|
|
|
tracing::error!("Failed to poll for events: {error:?}");
|
2023-06-18 12:40:03 +01:00
|
|
|
let exit_code = error.raw_os_error().unwrap_or(1);
|
2023-09-07 08:25:04 +02:00
|
|
|
self.set_exit_code(exit_code);
|
2023-06-18 12:40:03 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NB: `StartCause::Init` is handled as a special case and doesn't need
|
|
|
|
|
// to be considered here
|
2023-09-07 08:25:04 +02:00
|
|
|
let cause = match self.control_flow() {
|
2023-06-18 12:40:03 +01:00
|
|
|
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 }
|
2019-06-24 12:14:55 -04:00
|
|
|
}
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
},
|
2023-06-18 12:40:03 +01:00
|
|
|
};
|
2022-06-10 13:43:33 +03:00
|
|
|
|
2023-09-20 12:15:28 +01:00
|
|
|
// 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)
|
2025-03-17 13:20:17 +03:00
|
|
|
&& timeout.is_none()
|
2023-09-20 12:15:28 +01:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-20 20:27:36 +04:00
|
|
|
self.single_iteration(app, cause);
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2019-07-17 11:09:02 -07:00
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
|
2024-05-20 20:27:36 +04:00
|
|
|
app.new_events(&self.event_processor.target, cause);
|
2022-02-04 12:13:04 +01:00
|
|
|
|
2024-06-30 00:41:57 +02:00
|
|
|
// NB: For consistency all platforms must call `can_create_surfaces` even though X11
|
|
|
|
|
// applications don't themselves have a formal surface destroy/create lifecycle.
|
2023-06-18 12:40:03 +01:00
|
|
|
if cause == StartCause::Init {
|
2024-06-30 00:41:57 +02:00
|
|
|
app.can_create_surfaces(&self.event_processor.target)
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2022-02-04 12:13:04 +01:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
// Process all pending events
|
2024-05-20 20:27:36 +04:00
|
|
|
self.drain_events(app);
|
2023-06-18 12:40:03 +01:00
|
|
|
|
|
|
|
|
// Empty activation tokens.
|
|
|
|
|
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
|
2024-10-08 15:29:40 +02:00
|
|
|
let token = self
|
|
|
|
|
.event_processor
|
|
|
|
|
.with_window(window_id.into_raw() as xproto::Window, |window| {
|
|
|
|
|
window.generate_activation_token()
|
|
|
|
|
});
|
2023-06-18 12:40:03 +01:00
|
|
|
|
|
|
|
|
match token {
|
2024-02-09 05:52:09 +04:00
|
|
|
Some(Ok(token)) => {
|
2024-05-20 20:27:36 +04:00
|
|
|
let event = WindowEvent::ActivationTokenDone {
|
|
|
|
|
serial,
|
2025-05-17 04:26:09 +02:00
|
|
|
token: winit_core::window::ActivationToken::from_raw(token),
|
2024-02-09 05:52:09 +04:00
|
|
|
};
|
2024-05-20 20:27:36 +04:00
|
|
|
app.window_event(&self.event_processor.target, window_id, event);
|
2024-02-09 05:52:09 +04:00
|
|
|
},
|
2023-06-18 12:40:03 +01:00
|
|
|
Some(Err(e)) => {
|
2024-02-25 19:20:39 -08:00
|
|
|
tracing::error!("Failed to get activation token: {}", e);
|
2022-02-04 12:13:04 +01:00
|
|
|
},
|
2023-06-18 12:40:03 +01:00
|
|
|
None => {},
|
2020-03-11 21:54:23 -07:00
|
|
|
}
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2019-11-10 11:24:43 -07:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
// Empty the user event buffer
|
2024-06-24 13:04:55 +03:00
|
|
|
if mem::take(&mut self.state.proxy_wake_up) {
|
|
|
|
|
app.proxy_wake_up(&self.event_processor.target);
|
2023-06-18 12:40:03 +01:00
|
|
|
}
|
2023-07-28 17:37:56 +01:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
// Empty the redraw requests
|
|
|
|
|
{
|
|
|
|
|
let mut windows = HashSet::new();
|
2022-02-04 12:13:04 +01:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
while let Ok(window_id) = self.redraw_receiver.try_recv() {
|
|
|
|
|
windows.insert(window_id);
|
|
|
|
|
}
|
2018-05-29 07:48:47 -04:00
|
|
|
|
2023-06-18 12:40:03 +01:00
|
|
|
for window_id in windows {
|
2024-05-20 20:27:36 +04:00
|
|
|
app.window_event(
|
2024-02-09 05:52:09 +04:00
|
|
|
&self.event_processor.target,
|
2024-05-20 20:27:36 +04:00
|
|
|
window_id,
|
|
|
|
|
WindowEvent::RedrawRequested,
|
2023-06-18 12:40:03 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-28 17:37:56 +01:00
|
|
|
|
|
|
|
|
// This is always the last event we dispatch before poll again
|
2024-05-20 20:27:36 +04:00
|
|
|
app.about_to_wait(&self.event_processor.target);
|
2018-05-29 07:48:47 -04:00
|
|
|
}
|
2019-07-17 11:09:02 -07:00
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
fn drain_events<A: ApplicationHandler>(&mut self, app: &mut A) {
|
2020-01-04 01:31:23 -05:00
|
|
|
let mut xev = MaybeUninit::uninit();
|
2019-07-17 11:09:02 -07:00
|
|
|
|
2020-01-04 01:31:23 -05:00
|
|
|
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
|
|
|
|
let mut xev = unsafe { xev.assume_init() };
|
2025-02-24 09:13:25 +01:00
|
|
|
self.event_processor.process_event(&mut xev, app);
|
2019-07-30 23:31:12 -07:00
|
|
|
}
|
2019-07-17 11:09:02 -07:00
|
|
|
}
|
2023-09-07 08:25:04 +02:00
|
|
|
|
|
|
|
|
fn control_flow(&self) -> ControlFlow {
|
2024-08-06 21:02:53 +03:00
|
|
|
self.event_processor.target.control_flow()
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn exiting(&self) -> bool {
|
2024-08-06 21:02:53 +03:00
|
|
|
self.event_processor.target.exiting()
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_exit_code(&self, code: i32) {
|
2024-08-06 21:02:53 +03:00
|
|
|
self.event_processor.target.set_exit_code(code);
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn exit_code(&self) -> Option<i32> {
|
2024-08-06 21:02:53 +03:00
|
|
|
self.event_processor.target.exit_code()
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
impl AsFd for EventLoop {
|
2023-10-15 20:31:29 +04:00
|
|
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
|
|
|
self.event_loop.as_fd()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
impl AsRawFd for EventLoop {
|
2023-10-15 20:31:29 +04:00
|
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
|
|
|
self.event_loop.as_raw_fd()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 17:29:59 +04:00
|
|
|
impl ActiveEventLoop {
|
2019-08-08 14:50:22 -07:00
|
|
|
/// Returns the `XConnection` of this events loop.
|
|
|
|
|
#[inline]
|
2023-01-10 08:46:48 +00:00
|
|
|
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
|
2019-08-08 14:50:22 -07:00
|
|
|
&self.xconn
|
2019-06-21 11:33:15 -04:00
|
|
|
}
|
2022-06-07 23:17:45 +02:00
|
|
|
|
2023-05-30 21:32:31 +02:00
|
|
|
/// 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);
|
2022-06-07 23:17:45 +02:00
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
let mut mask = xinput::XIEventMask::from(0u32);
|
2023-05-30 21:32:31 +02:00
|
|
|
if device_events {
|
2023-07-12 00:59:12 -07:00
|
|
|
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;
|
2022-06-07 23:17:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.xconn
|
2023-08-29 14:01:25 -07:00
|
|
|
.select_xinput_events(self.root, ALL_MASTER_DEVICES, mask)
|
2023-07-12 00:59:12 -07:00
|
|
|
.expect_then_ignore_error("Failed to update device event filter");
|
2022-06-07 23:17:45 +02:00
|
|
|
}
|
2022-07-21 22:22:36 +03:00
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
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 {
|
2024-11-12 10:56:20 +03:00
|
|
|
fn create_proxy(&self) -> CoreEventLoopProxy {
|
|
|
|
|
self.event_loop_proxy.clone()
|
2024-08-06 21:02:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_window(
|
|
|
|
|
&self,
|
|
|
|
|
window_attributes: WindowAttributes,
|
2024-09-06 17:20:11 +03:00
|
|
|
) -> Result<Box<dyn CoreWindow>, RequestError> {
|
2024-08-23 23:40:27 +03:00
|
|
|
Ok(Box::new(Window::new(self, window_attributes)?))
|
2024-08-06 21:02:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_custom_cursor(
|
|
|
|
|
&self,
|
|
|
|
|
custom_cursor: CustomCursorSource,
|
2025-03-13 17:18:37 +03:00
|
|
|
) -> Result<CoreCustomCursor, RequestError> {
|
|
|
|
|
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(self, custom_cursor)?)))
|
2024-08-06 21:02:53 +03:00
|
|
|
}
|
|
|
|
|
|
2024-09-21 20:27:12 +03:00
|
|
|
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
2024-08-06 21:02:53 +03:00
|
|
|
Box::new(
|
|
|
|
|
self.xconn
|
|
|
|
|
.available_monitors()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.flatten()
|
2024-09-21 20:27:12 +03:00
|
|
|
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
|
2024-08-06 21:02:53 +03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-21 20:27:12 +03:00
|
|
|
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
|
|
|
|
self.xconn.primary_monitor().ok().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
2024-08-06 21:02:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2023-09-07 08:25:04 +02:00
|
|
|
self.control_flow.set(control_flow)
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn control_flow(&self) -> ControlFlow {
|
2023-09-07 08:25:04 +02:00
|
|
|
self.control_flow.get()
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn exit(&self) {
|
2023-09-07 08:25:04 +02:00
|
|
|
self.exit.set(Some(0))
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
fn exiting(&self) -> bool {
|
|
|
|
|
self.exit.get().is_some()
|
2023-12-22 20:00:20 +04:00
|
|
|
}
|
|
|
|
|
|
2024-11-13 15:29:05 +03:00
|
|
|
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
|
|
|
|
|
CoreOwnedDisplayHandle::new(self.x_connection().clone())
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-06 21:02:53 +03:00
|
|
|
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> {
|
2024-11-13 15:29:05 +03:00
|
|
|
self.xconn.display_handle()
|
2023-09-07 08:25:04 +02:00
|
|
|
}
|
2019-04-27 18:06:51 +02:00
|
|
|
}
|
2017-05-25 23:19:13 +10:00
|
|
|
|
2017-04-22 13:52:35 -07:00
|
|
|
struct DeviceInfo<'a> {
|
2018-05-29 07:48:47 -04:00
|
|
|
xconn: &'a XConnection,
|
2017-04-22 13:52:35 -07:00
|
|
|
info: *const ffi::XIDeviceInfo,
|
|
|
|
|
count: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> DeviceInfo<'a> {
|
2018-05-29 07:48:47 -04:00
|
|
|
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
2017-04-22 13:52:35 -07:00
|
|
|
unsafe {
|
2019-07-11 09:34:32 -07:00
|
|
|
let mut count = 0;
|
2018-05-29 07:48:47 -04:00
|
|
|
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
2019-07-11 09:34:32 -07:00
|
|
|
xconn.check_errors().ok()?;
|
|
|
|
|
|
|
|
|
|
if info.is_null() || count == 0 {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(DeviceInfo { xconn, info, count: count as usize })
|
|
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 12:51:26 +01:00
|
|
|
impl Drop for DeviceInfo<'_> {
|
2017-04-22 13:52:35 -07:00
|
|
|
fn drop(&mut self) {
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
assert!(!self.info.is_null());
|
2018-05-29 07:48:47 -04:00
|
|
|
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 12:51:26 +01:00
|
|
|
impl Deref for DeviceInfo<'_> {
|
2017-04-22 13:52:35 -07:00
|
|
|
type Target = [ffi::XIDeviceInfo];
|
2024-04-26 19:11:44 +04:00
|
|
|
|
2017-04-22 13:52:35 -07:00
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
unsafe { slice::from_raw_parts(self.info, self.count) }
|
|
|
|
|
}
|
2017-03-03 21:41:51 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-03 08:40:04 +01:00
|
|
|
#[derive(Clone, Debug)]
|
2024-06-24 13:04:55 +03:00
|
|
|
pub struct EventLoopProxy {
|
|
|
|
|
ping: Ping,
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 10:56:20 +03:00
|
|
|
impl EventLoopProxyProvider for EventLoopProxy {
|
|
|
|
|
fn wake_up(&self) {
|
|
|
|
|
self.ping.ping();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 13:04:55 +03:00
|
|
|
impl EventLoopProxy {
|
|
|
|
|
fn new(ping: Ping) -> Self {
|
|
|
|
|
Self { ping }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 10:56:20 +03:00
|
|
|
impl From<EventLoopProxy> for CoreEventLoopProxy {
|
|
|
|
|
fn from(value: EventLoopProxy) -> Self {
|
|
|
|
|
CoreEventLoopProxy::new(Arc::new(value))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
/// 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),
|
2023-07-20 13:16:51 +00:00
|
|
|
|
|
|
|
|
/// Got an invalid activation token.
|
|
|
|
|
InvalidActivationToken(Vec<u8>),
|
2023-08-05 14:58:23 -07:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
/// An extension that we rely on is not available.
|
|
|
|
|
MissingExtension(&'static str),
|
|
|
|
|
|
2023-08-05 14:58:23 -07:00
|
|
|
/// Could not find a matching X11 visual for this visualid
|
|
|
|
|
NoSuchVisual(xproto::Visualid),
|
2024-01-30 04:52:29 -08:00
|
|
|
|
|
|
|
|
/// Unable to parse xsettings.
|
|
|
|
|
XsettingsParse(xsettings::ParserError),
|
2024-03-07 22:14:33 +04:00
|
|
|
|
|
|
|
|
/// Failed to get property.
|
|
|
|
|
GetProperty(util::GetPropertyError),
|
2024-08-22 19:25:20 -07:00
|
|
|
|
|
|
|
|
/// Could not find an ARGB32 pict format.
|
|
|
|
|
NoArgb32Format,
|
2023-07-12 00:59:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for X11Error {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
match self {
|
2024-11-22 13:14:11 -08:00
|
|
|
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}"),
|
2023-07-20 13:16:51 +00:00
|
|
|
X11Error::InvalidActivationToken(s) => write!(
|
|
|
|
|
f,
|
|
|
|
|
"Invalid activation token: {}",
|
|
|
|
|
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
|
|
|
|
|
),
|
2024-11-22 13:14:11 -08:00
|
|
|
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
|
2023-08-05 14:58:23 -07:00
|
|
|
X11Error::NoSuchVisual(visualid) => {
|
2024-11-22 13:14:11 -08:00
|
|
|
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
|
2023-08-05 14:58:23 -07:00
|
|
|
},
|
2024-01-30 04:52:29 -08:00
|
|
|
X11Error::XsettingsParse(err) => {
|
2024-11-22 13:14:11 -08:00
|
|
|
write!(f, "Failed to parse xsettings: {err:?}")
|
2024-01-30 04:52:29 -08:00
|
|
|
},
|
2024-08-22 19:25:20 -07:00
|
|
|
X11Error::NoArgb32Format => {
|
|
|
|
|
f.write_str("winit only supports X11 displays with ARGB32 picture formats")
|
|
|
|
|
},
|
2023-07-12 00:59:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(),
|
2017-03-03 21:41:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-30 04:52:29 -08:00
|
|
|
impl From<xsettings::ParserError> for X11Error {
|
|
|
|
|
fn from(value: xsettings::ParserError) -> Self {
|
|
|
|
|
Self::XsettingsParse(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 22:14:33 +04:00
|
|
|
impl From<util::GetPropertyError> for X11Error {
|
|
|
|
|
fn from(value: util::GetPropertyError) -> Self {
|
|
|
|
|
Self::GetProperty(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
/// Type alias for a void cookie.
|
|
|
|
|
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
|
|
|
|
|
|
|
|
|
|
/// Extension trait for `Result<VoidCookie, E>`.
|
|
|
|
|
trait CookieResultExt {
|
|
|
|
|
/// Unwrap the send error and ignore the result.
|
|
|
|
|
fn expect_then_ignore_error(self, msg: &str);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 12:51:26 +01:00
|
|
|
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
|
2023-07-12 00:59:12 -07:00
|
|
|
fn expect_then_ignore_error(self, msg: &str) {
|
|
|
|
|
self.expect(msg).ignore_error()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-17 04:26:09 +02:00
|
|
|
fn mkwid(w: xproto::Window) -> winit_core::window::WindowId {
|
|
|
|
|
winit_core::window::WindowId::from_raw(w as _)
|
2019-06-21 11:33:15 -04:00
|
|
|
}
|
2024-10-11 11:15:54 +03:00
|
|
|
fn mkdid(w: xinput::DeviceId) -> DeviceId {
|
|
|
|
|
DeviceId::from_raw(w as i64)
|
2019-06-21 11:33:15 -04:00
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2024-02-09 06:32:03 +04:00
|
|
|
pub struct Device {
|
2021-12-11 03:02:48 +01:00
|
|
|
_name: String,
|
2017-04-22 13:52:35 -07:00
|
|
|
scroll_axes: Vec<(i32, ScrollAxis)>,
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
// For master devices, this is the paired device (pointer <-> keyboard).
|
|
|
|
|
// For slave devices, this is the master.
|
|
|
|
|
attachment: c_int,
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
struct ScrollAxis {
|
|
|
|
|
increment: f64,
|
|
|
|
|
orientation: ScrollOrientation,
|
|
|
|
|
position: f64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
enum ScrollOrientation {
|
|
|
|
|
Vertical,
|
|
|
|
|
Horizontal,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Device {
|
2022-06-07 23:17:45 +02:00
|
|
|
fn new(info: &ffi::XIDeviceInfo) -> Self {
|
2017-04-22 13:52:35 -07:00
|
|
|
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
2017-07-09 17:47:52 +10:00
|
|
|
let mut scroll_axes = Vec::new();
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2017-07-09 17:47:52 +10:00
|
|
|
if Device::physical_device(info) {
|
2017-04-22 13:52:35 -07:00
|
|
|
// Identify scroll axes
|
2023-10-14 20:09:10 -07:00
|
|
|
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) };
|
2022-01-01 13:00:11 +11:00
|
|
|
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,
|
|
|
|
|
}));
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-07-09 17:47:52 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut device =
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment };
|
2017-07-09 17:47:52 +10:00
|
|
|
device.reset_scroll_position(info);
|
|
|
|
|
device
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
|
|
|
|
|
if Device::physical_device(info) {
|
2023-10-14 20:09:10 -07:00
|
|
|
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) };
|
2022-01-01 13:00:11 +11:00
|
|
|
if let Some(&mut (_, ref mut axis)) =
|
|
|
|
|
self.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number)
|
|
|
|
|
{
|
|
|
|
|
axis.position = info.value;
|
2019-06-24 12:14:55 -04:00
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-07-09 17:47:52 +10:00
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
|
2017-07-09 17:47:52 +10:00
|
|
|
#[inline]
|
|
|
|
|
fn physical_device(info: &ffi::XIDeviceInfo) -> bool {
|
2019-06-21 11:33:15 -04:00
|
|
|
info._use == ffi::XISlaveKeyboard
|
|
|
|
|
|| info._use == ffi::XISlavePointer
|
|
|
|
|
|| info._use == ffi::XIFloatingSlave
|
2017-07-09 17:47:52 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] {
|
2019-06-21 11:33:15 -04:00
|
|
|
unsafe {
|
|
|
|
|
slice::from_raw_parts(
|
|
|
|
|
info.classes as *const *const ffi::XIAnyClassInfo,
|
|
|
|
|
info.num_classes as usize,
|
|
|
|
|
)
|
|
|
|
|
}
|
2017-04-22 13:52:35 -07:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-28 15:08:14 -08:00
|
|
|
|
|
|
|
|
/// Convert the raw X11 representation for a 32-bit floating point to a double.
|
|
|
|
|
#[inline]
|
|
|
|
|
fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 {
|
|
|
|
|
(fp as f64) / ((1 << 16) as f64)
|
|
|
|
|
}
|