diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6282084ab..e6cf84a7fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444)) - **Breaking:** Removed support for `raw-window-handle` version `0.4` - On Wayland, `RedrawRequested` not emitted during resize. +- On X11, migrate from Xlib to libxcb. - **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface. - Added Orbital support for Redox OS - On X11, added `drag_resize_window` method. diff --git a/Cargo.toml b/Cargo.toml index 874ff842c34..59875de7a30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,14 +36,14 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["x11-dl", "mio", "percent-encoding"] +x11 = ["bytemuck", "x11-dl", "x11rb", "mio", "percent-encoding"] wayland = ["wayland-client", "wayland-protocols", "sctk"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-notitle = ["sctk-adwaita"] -android-native-activity = [ "android-activity/native-activity" ] -android-game-activity = [ "android-activity/game-activity" ] +android-native-activity = ["android-activity/native-activity"] +android-game-activity = ["android-activity/game-activity"] [build-dependencies] cfg_aliases = "0.1.1" @@ -104,6 +104,7 @@ features = [ ] [target.'cfg(all(unix, not(any(target_os = "redox", target_arch = "wasm32", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] +bytemuck = { version = "1.12.3", default-features = false, optional = true, features = ["derive", "extern_crate_alloc"] } libc = "0.2.64" mio = { version = "0.8", features = ["os-ext"], optional = true } percent-encoding = { version = "2.0", optional = true } @@ -112,6 +113,7 @@ sctk-adwaita = { version = "0.5.1", default_features = false, optional = true } wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true } x11-dl = { version = "2.18.5", optional = true } +x11rb = { version = "0.11.0", features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index f6276a2f5cd..b1995a3b2b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,6 +138,7 @@ // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow(clippy::missing_safety_doc)] +#![allow(clippy::uninlined_format_args)] #[allow(unused_imports)] #[macro_use] diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 8ddc9a7a1f5..8208e231907 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -22,7 +22,9 @@ use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; #[cfg(x11_platform)] pub use self::x11::XNotSupported; #[cfg(x11_platform)] -use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; +use self::x11::{ + ffi::XVisualInfo, util::WindowType as XWindowType, PlatformError, XConnection, XError, +}; #[cfg(x11_platform)] use crate::platform::x11::XlibErrorHook; use crate::{ @@ -120,9 +122,9 @@ pub(crate) static X11_BACKEND: Lazy, XNotSupported Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); #[derive(Debug, Clone)] -pub enum OsError { +pub(crate) enum OsError { #[cfg(x11_platform)] - XError(XError), + XError(Arc), #[cfg(x11_platform)] XMisc(&'static str), #[cfg(wayland_platform)] @@ -130,18 +132,39 @@ pub enum OsError { } impl fmt::Display for OsError { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { #[cfg(x11_platform)] - OsError::XError(ref e) => _f.pad(&e.description), + OsError::XError(ref e) => fmt::Display::fmt(e, f), #[cfg(x11_platform)] - OsError::XMisc(e) => _f.pad(e), + OsError::XMisc(e) => f.pad(e), #[cfg(wayland_platform)] - OsError::WaylandMisc(e) => _f.pad(e), + OsError::WaylandMisc(e) => f.pad(e), } } } +#[cfg(x11_platform)] +impl From for OsError { + fn from(value: XError) -> Self { + OsError::XError(Arc::new(value.into())) + } +} + +#[cfg(x11_platform)] +impl From for OsError { + fn from(value: PlatformError) -> Self { + OsError::XError(Arc::new(value)) + } +} + +#[cfg(x11_platform)] +impl From for OsError { + fn from(value: x11rb::errors::ConnectionError) -> Self { + OsError::XError(Arc::new(value.into())) + } +} + pub(crate) enum Window { #[cfg(x11_platform)] X(x11::Window), diff --git a/src/platform_impl/linux/x11/atoms.rs b/src/platform_impl/linux/x11/atoms.rs new file mode 100644 index 00000000000..b772ebf89d5 --- /dev/null +++ b/src/platform_impl/linux/x11/atoms.rs @@ -0,0 +1,158 @@ +//! Atom management. + +use x11rb::{ + atom_manager, cookie, + errors::{ConnectionError, ReplyError}, + protocol::xproto::{self, ConnectionExt as _}, + xcb_ffi::XCBConnection, +}; + +// Note: text/uri-list is a special case and is handled separately. + +macro_rules! make_atom_manager { + ( + $($name: ident),* + ) => { + /// The various atoms used within `Winit`. + #[allow(non_camel_case_types)] + pub(crate) enum AtomType { + TextUriList, + $( + $name, + )* + } + + /// A collection of atoms used within `Winit`. + pub(crate) struct Atoms { + /// The textual atom list. + some_atoms: SomeAtoms, + + /// `text/uri-list`. + text_uri_list: xproto::Atom, + } + + /// The cookie for the `Atoms` structure. + pub(crate) struct AtomsCookie<'a> { + /// The textual atom list. + some_atoms: SomeAtomsCookie<'a, XCBConnection>, + + /// `text/uri-list`. + text_uri_list: cookie::Cookie<'a, XCBConnection, xproto::InternAtomReply>, + } + + impl Atoms { + /// Create a new `Atoms` structure. + pub(crate) fn request(conn: &XCBConnection) -> Result, ConnectionError> { + let some_atoms = SomeAtoms::new(conn)?; + let text_uri_list = conn.intern_atom(true, b"text/uri-list")?; + Ok(AtomsCookie { + some_atoms, + text_uri_list, + }) + } + } + + impl AtomsCookie<'_> { + /// Finish the creation of the `Atoms` structure. + pub(crate) fn reply(self) -> Result { + let some_atoms = self.some_atoms.reply()?; + let text_uri_list = self.text_uri_list.reply()?.atom; + Ok(Atoms { + some_atoms, + text_uri_list, + }) + } + } + + atom_manager! { + /// A collection of atoms used within `Winit`. + SomeAtoms : SomeAtomsCookie { + $( + $name, + )* + } + } + + impl std::ops::Index for Atoms { + type Output = xproto::Atom; + + fn index(&self, atom: AtomType) -> &Self::Output { + match atom { + AtomType::TextUriList => &self.text_uri_list, + $( + AtomType::$name => &self.some_atoms.$name, + )* + } + } + } + }; +} + +make_atom_manager! { + // Window type hints. + _NET_WM_WINDOW_TYPE, + _NET_WM_WINDOW_TYPE_DESKTOP, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_MENU, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_SPLASH, + _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + _NET_WM_WINDOW_TYPE_POPUP_MENU, + _NET_WM_WINDOW_TYPE_TOOLTIP, + _NET_WM_WINDOW_TYPE_NOTIFICATION, + _NET_WM_WINDOW_TYPE_COMBO, + _NET_WM_WINDOW_TYPE_DND, + _NET_WM_WINDOW_TYPE_NORMAL, + + // Other _NET_WM hints + _NET_WM_MOVERESIZE, + _NET_WM_NAME, + _NET_WM_ICON, + _NET_WM_PID, + _NET_WM_PING, + _NET_WM_STATE, + _NET_WM_STATE_ABOVE, + _NET_WM_STATE_BELOW, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_MAXIMIZED_VERT, + + // DND atoms. + XdndAware, + XdndEnter, + XdndLeave, + XdndDrop, + XdndPosition, + XdndStatus, + XdndActionPrivate, + XdndSelection, + XdndFinished, + XdndTypeList, + + // _NET hints. + _NET_ACTIVE_WINDOW, + _NET_FRAME_EXTENTS, + _NET_CLIENT_LIST, + _NET_SUPPORTED, + _NET_SUPPORTING_WM_CHECK, + + // Misc WM hints. + WM_CLIENT_MACHINE, + WM_DELETE_WINDOW, + WM_PROTOCOLS, + WM_STATE, + + // Other misc atoms. + _MOTIF_WM_HINTS, + _GTK_THEME_VARIANT, + CARD32, + UTF8_STRING, + None +} + +pub(crate) use AtomType::*; + +/// Prevent the `None` atom from shadowing `Option::None`. +pub use std::option::Option::None; diff --git a/src/platform_impl/linux/x11/dnd.rs b/src/platform_impl/linux/x11/dnd.rs index 198494e184b..6a4f36f2470 100644 --- a/src/platform_impl/linux/x11/dnd.rs +++ b/src/platform_impl/linux/x11/dnd.rs @@ -5,57 +5,18 @@ use std::{ str::Utf8Error, sync::Arc, }; +use x11rb::{ + connection::Connection, + protocol::xproto::{self, ConnectionExt as _}, +}; use percent_encoding::percent_decode; -use super::{ffi, util, XConnection, XError}; - -#[derive(Debug)] -pub(crate) struct DndAtoms { - pub enter: ffi::Atom, - pub leave: ffi::Atom, - pub drop: ffi::Atom, - pub position: ffi::Atom, - pub status: ffi::Atom, - pub action_private: ffi::Atom, - pub selection: ffi::Atom, - pub finished: ffi::Atom, - pub type_list: ffi::Atom, - pub uri_list: ffi::Atom, - pub none: ffi::Atom, -} - -impl DndAtoms { - pub fn new(xconn: &Arc) -> Result { - let names = [ - b"XdndEnter\0".as_ptr() as *mut c_char, - b"XdndLeave\0".as_ptr() as *mut c_char, - b"XdndDrop\0".as_ptr() as *mut c_char, - b"XdndPosition\0".as_ptr() as *mut c_char, - b"XdndStatus\0".as_ptr() as *mut c_char, - b"XdndActionPrivate\0".as_ptr() as *mut c_char, - b"XdndSelection\0".as_ptr() as *mut c_char, - b"XdndFinished\0".as_ptr() as *mut c_char, - b"XdndTypeList\0".as_ptr() as *mut c_char, - b"text/uri-list\0".as_ptr() as *mut c_char, - b"None\0".as_ptr() as *mut c_char, - ]; - let atoms = unsafe { xconn.get_atoms(&names) }?; - Ok(DndAtoms { - enter: atoms[0], - leave: atoms[1], - drop: atoms[2], - position: atoms[3], - status: atoms[4], - action_private: atoms[5], - selection: atoms[6], - finished: atoms[7], - type_list: atoms[8], - uri_list: atoms[9], - none: atoms[10], - }) - } -} +use super::{ + atoms::*, + util::{self, PlErrorExt}, + PlatformError, XConnection, +}; #[derive(Debug, Clone, Copy)] pub enum DndState { @@ -86,27 +47,25 @@ impl From for DndDataParseError { pub(crate) struct Dnd { xconn: Arc, - pub atoms: DndAtoms, + // Populated by XdndEnter event handler - pub version: Option, - pub type_list: Option>, + pub version: Option, + pub type_list: Option>, // Populated by XdndPosition event handler - pub source_window: Option, + pub source_window: Option, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, } impl Dnd { - pub fn new(xconn: Arc) -> Result { - let atoms = DndAtoms::new(&xconn)?; - Ok(Dnd { + pub fn new(xconn: Arc) -> Self { + Dnd { xconn, - atoms, version: None, type_list: None, source_window: None, result: None, - }) + } } pub fn reset(&mut self) { @@ -116,73 +75,93 @@ impl Dnd { self.result = None; } - pub unsafe fn send_status( + pub fn send_status( &self, - this_window: c_ulong, - target_window: c_ulong, + this_window: xproto::Window, + target_window: xproto::Window, state: DndState, - ) -> Result<(), XError> { + ) -> Result<(), PlatformError> { let (accepted, action) = match state { - DndState::Accepted => (1, self.atoms.action_private as c_long), - DndState::Rejected => (0, self.atoms.none as c_long), + DndState::Accepted => (1, XdndActionPrivate), + DndState::Rejected => (0, AtomType::None), }; self.xconn .send_client_msg( target_window, target_window, - self.atoms.status, + self.xconn.atoms[XdndStatus], None, - [this_window as c_long, accepted, 0, 0, action], - ) - .flush() + 32, + [this_window, accepted, 0, 0, self.xconn.atoms[action]], + )? + .ignore_error(); + + self.xconn.connection.flush().platform() } - pub unsafe fn send_finished( + pub fn send_finished( &self, - this_window: c_ulong, - target_window: c_ulong, + this_window: xproto::Window, + target_window: xproto::Window, state: DndState, - ) -> Result<(), XError> { + ) -> Result<(), PlatformError> { let (accepted, action) = match state { - DndState::Accepted => (1, self.atoms.action_private as c_long), - DndState::Rejected => (0, self.atoms.none as c_long), + DndState::Accepted => (1, XdndActionPrivate), + DndState::Rejected => (0, AtomType::None), }; self.xconn .send_client_msg( target_window, target_window, - self.atoms.finished, + self.xconn.atoms[XdndFinished], None, - [this_window as c_long, accepted, action, 0, 0], - ) - .flush() + 32, + [this_window, accepted, self.xconn.atoms[action], 0, 0], + )? + .ignore_error(); + + self.xconn.connection.flush().platform() } - pub unsafe fn get_type_list( + pub fn get_type_list( &self, - source_window: c_ulong, - ) -> Result, util::GetPropertyError> { - self.xconn - .get_property(source_window, self.atoms.type_list, ffi::XA_ATOM) + source_window: xproto::Window, + ) -> Result, util::GetPropertyError> { + self.xconn.get_property( + source_window, + self.xconn.atoms[XdndTypeList], + xproto::AtomEnum::ATOM.into(), + ) } - pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) { - (self.xconn.xlib.XConvertSelection)( - self.xconn.display, - self.atoms.selection, - self.atoms.uri_list, - self.atoms.selection, - window, - time, - ); + pub fn convert_selection( + &self, + window: xproto::Window, + time: xproto::Timestamp, + ) -> Result<(), PlatformError> { + self.xconn + .connection + .convert_selection( + window, + self.xconn.atoms[XdndSelection], + self.xconn.atoms[TextUriList], + self.xconn.atoms[XdndSelection], + time, + )? + .ignore_error(); + + Ok(()) } - pub unsafe fn read_data( + pub fn read_data( &self, - window: c_ulong, + window: xproto::Window, ) -> Result, util::GetPropertyError> { - self.xconn - .get_property(window, self.atoms.selection, self.atoms.uri_list) + self.xconn.get_property( + window, + self.xconn.atoms[XdndSelection], + self.xconn.atoms[TextUriList], + ) } pub fn parse_data(&self, data: &mut [c_uchar]) -> Result, DndDataParseError> { diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 11c9a6a954d..072ba660a47 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,21 +1,44 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + rc::Rc, + sync::Arc, +}; +use x11rb::{ + connection::Connection, + errors::ConnectionError, + protocol::{ + xinput::{self, ConnectionExt as _}, + xproto::{self, ConnectionExt as _, Mapping, Window}, + Event as X11Event, + }, +}; -use libc::{c_char, c_int, c_long, c_uint, c_ulong}; +use libc::c_uint; use super::{ - events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, - DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, - XExtension, + atoms::*, + events, ffi, fp1616, fp3232, get_xtarget, mkdid, mkwid, monitor, + util::{self, PlErrorExt}, + Device, DeviceId, DeviceInfo, Dnd, DndState, ScrollOrientation, UnownedWindow, WindowId, +}; + +use crate::event::{ + ElementState::{Pressed, Released}, + MouseButton::{Left, Middle, Other, Right}, + MouseScrollDelta::LineDelta, + Touch, + WindowEvent::{ + AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, MouseWheel, + }, }; use util::modifiers::{ModifierKeyState, ModifierKeymap}; -use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{ - DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, TouchPhase, - WindowEvent, + DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent, }, event_loop::EventLoopWindowTarget as RootELW, }; @@ -25,11 +48,9 @@ const KEYCODE_OFFSET: u8 = 8; pub(super) struct EventProcessor { pub(super) dnd: Dnd, - pub(super) ime_receiver: ImeReceiver, - pub(super) ime_event_receiver: ImeEventReceiver, - pub(super) randr_event_offset: c_int, + //pub(super) ime_receiver: ImeReceiver, + //pub(super) ime_event_receiver: ImeEventReceiver, pub(super) devices: RefCell>, - pub(super) xi2ext: XExtension, pub(super) target: Rc>, pub(super) mod_keymap: ModifierKeymap, pub(super) device_mod_state: ModifierKeyState, @@ -37,12 +58,15 @@ pub(super) struct EventProcessor { pub(super) num_touch: u32, pub(super) first_touch: Option, // Currently focused window belonging to this process - pub(super) active_window: Option, + pub(super) active_window: Option, pub(super) is_composing: bool, + + /// A queue containing events that we have read from the X server but have not yet processed. + pub(super) event_queue: VecDeque, } impl EventProcessor { - pub(super) fn init_device(&self, device: c_int) { + pub(super) fn init_device(&self, device: xinput::DeviceId) { let wt = get_xtarget(&self.target); let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&wt.xconn, device) { @@ -52,7 +76,7 @@ impl EventProcessor { } } - fn with_window(&self, window_id: ffi::Window, callback: F) -> Option + fn with_window(&self, window_id: Window, callback: F) -> Option where F: Fn(&Arc) -> Ret, { @@ -76,63 +100,56 @@ impl EventProcessor { result } - fn window_exists(&self, window_id: ffi::Window) -> bool { - self.with_window(window_id, |_| ()).is_some() - } + /// See if there are any pending events in the queue. + /// + /// Returns `true` if there are events in the queue, `false` otherwise. + pub(super) fn poll(&mut self) -> bool { + loop { + // If we have events in the queue, we need to process them. + if !self.event_queue.is_empty() { + return true; + } - pub(super) fn poll(&self) -> bool { - let wt = get_xtarget(&self.target); - let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) }; + // See if the X connection has any events. + let wt = get_xtarget(&self.target); + match wt.xconn.connection.poll_for_event() { + Ok(Some(event)) => { + self.event_queue.push_back(event); + } + Ok(None) => { + // No events in the queue, and no events on the connection. + return false; + } + Err(err) => { + // An error occurred while polling for events. + log::error!("Error while polling for events: {}", err); + return false; + } + } + } + } - result != 0 + fn window_exists(&self, window_id: Window) -> bool { + self.with_window(window_id, |_| ()).is_some() } - pub(super) unsafe fn poll_one_event(&mut self, event_ptr: *mut ffi::XEvent) -> bool { - let wt = get_xtarget(&self.target); - // This function is used to poll and remove a single event - // from the Xlib event queue in a non-blocking, atomic way. - // XCheckIfEvent is non-blocking and removes events from queue. - // XNextEvent can't be used because it blocks while holding the - // global Xlib mutex. - // XPeekEvent does not remove events from the queue. - unsafe extern "C" fn predicate( - _display: *mut ffi::Display, - _event: *mut ffi::XEvent, - _arg: *mut c_char, - ) -> c_int { - // This predicate always returns "true" (1) to accept all events - 1 + pub(super) fn poll_one_event(&mut self) -> Result, ConnectionError> { + // If we previously polled and found an event, return it. + if let Some(event) = self.event_queue.pop_front() { + return Ok(Some(event)); } - let result = (wt.xconn.xlib.XCheckIfEvent)( - wt.xconn.display, - event_ptr, - Some(predicate), - std::ptr::null_mut(), - ); - - result != 0 + let wt = get_xtarget(&self.target); + wt.xconn.connection.poll_for_event() } - pub(super) fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) + pub(super) fn process_event(&mut self, xev: &mut X11Event, mut callback: F) where F: FnMut(Event<'_, T>), { let wt = get_xtarget(&self.target); - // XFilterEvent tells us when an event has been discarded by the input method. - // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, - // along with an extra copy of the KeyRelease events. This also prevents backspace and - // arrow keys from being detected twice. - if ffi::True - == unsafe { - (wt.xconn.xlib.XFilterEvent)(xev, { - let xev: &ffi::XAnyEvent = xev.as_ref(); - xev.window - }) - } - { - return; - } + + // TODO: Filter IME events. // We can't call a `&mut self` method because of the above borrow, // so we use this macro for repeated modifier state updates. @@ -155,65 +172,58 @@ impl EventProcessor { }}; } - let event_type = xev.get_type(); - match event_type { - ffi::MappingNotify => { - let mapping: &ffi::XMappingEvent = xev.as_ref(); + let is_synthetic = xev.sent_event(); - if mapping.request == ffi::MappingModifier - || mapping.request == ffi::MappingKeyboard - { - unsafe { - (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); - } - wt.xconn - .check_errors() - .expect("Failed to call XRefreshKeyboardMapping"); + match xev { + X11Event::MappingNotify(mapping) => { + if matches!(mapping.request, Mapping::MODIFIER | Mapping::KEYBOARD) { + // TODO: IME - self.mod_keymap.reset_from_x_connection(&wt.xconn); + // Update modifier keymap. + self.mod_keymap + .reset_from_x_connection(&wt.xconn) + .expect("Failed to update modifier keymap"); self.device_mod_state.update_keymap(&self.mod_keymap); } } - ffi::ClientMessage => { - let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); - + X11Event::ClientMessage(mut client_msg) => { let window = client_msg.window; let window_id = mkwid(window); - if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window { + let atom = client_msg.data.as_data32()[0] as xproto::Atom; + if atom == wt.xconn.atoms[WM_DELETE_WINDOW] { callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested, }); - } else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping { - let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut(); - response_msg.window = wt.root; + } else if atom == wt.xconn.atoms[_NET_WM_PING] { + client_msg.window = wt.root; wt.xconn + .connection .send_event( + false, wt.root, - Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask), - *response_msg, + xproto::EventMask::SUBSTRUCTURE_NOTIFY + | xproto::EventMask::SUBSTRUCTURE_REDIRECT, + client_msg, ) - .queue(); - } else if client_msg.message_type == self.dnd.atoms.enter { - let source_window = client_msg.data.get_long(0) as c_ulong; - let flags = client_msg.data.get_long(1); + .expect("Failed to send ping event") + .ignore_error(); + } else if client_msg.type_ == wt.xconn.atoms[XdndEnter] { + let longs = client_msg.data.as_data32(); + let source_window = longs[0] as xproto::Window; + let flags = longs[1]; let version = flags >> 24; self.dnd.version = Some(version); - let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; + let has_more_types = flags - (flags & (u32::max_value() - 1)) == 1; if !has_more_types { - let type_list = vec![ - client_msg.data.get_long(2) as c_ulong, - client_msg.data.get_long(3) as c_ulong, - client_msg.data.get_long(4) as c_ulong, - ]; + let type_list = vec![longs[2], longs[3], longs[4]]; self.dnd.type_list = Some(type_list); - } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } - { + } else if let Ok(more_types) = self.dnd.get_type_list(source_window) { self.dnd.type_list = Some(more_types); } - } else if client_msg.message_type == self.dnd.atoms.position { + } else if client_msg.type_ == wt.xconn.atoms[XdndPosition] { // This event occurs every time the mouse moves while a file's being dragged // over our window. We emit HoveredFile in response; while the macOS backend // does that upon a drag entering, XDND doesn't have access to the actual drop @@ -222,7 +232,8 @@ impl EventProcessor { // supply position updates with `HoveredFile` or another event, implementing // that here would be trivial. - let source_window = client_msg.data.get_long(0) as c_ulong; + let longs = client_msg.data.as_data32(); + let source_window = longs[0]; // Equivalent to `(x << shift) | y` // where `shift = mem::size_of::() * 8` @@ -240,37 +251,40 @@ impl EventProcessor { //let action = client_msg.data.get_long(4); let accepted = if let Some(ref type_list) = self.dnd.type_list { - type_list.contains(&self.dnd.atoms.uri_list) + type_list.contains(&wt.xconn.atoms[TextUriList]) } else { false }; if accepted { self.dnd.source_window = Some(source_window); - unsafe { + { if self.dnd.result.is_none() { let time = if version >= 1 { - client_msg.data.get_long(3) as c_ulong + longs[3] } else { // In version 0, time isn't specified - ffi::CurrentTime + 0 }; + // This results in the `SelectionNotify` event below - self.dnd.convert_selection(window, time); + self.dnd + .convert_selection(window, time) + .expect("Failed to convert selection"); } self.dnd .send_status(window, source_window, DndState::Accepted) .expect("Failed to send `XdndStatus` message."); } } else { - unsafe { + { self.dnd .send_status(window, source_window, DndState::Rejected) .expect("Failed to send `XdndStatus` message."); } self.dnd.reset(); } - } else if client_msg.message_type == self.dnd.atoms.drop { + } else if client_msg.type_ == wt.xconn.atoms[XdndDrop] { let (source_window, state) = if let Some(source_window) = self.dnd.source_window { if let Some(Ok(ref path_list)) = self.dnd.result { @@ -285,16 +299,16 @@ impl EventProcessor { } else { // `source_window` won't be part of our DND state if we already rejected the drop in our // `XdndPosition` handler. - let source_window = client_msg.data.get_long(0) as c_ulong; + let source_window = client_msg.data.as_data32()[0]; (source_window, DndState::Rejected) }; - unsafe { + { self.dnd .send_finished(window, source_window, state) .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); - } else if client_msg.message_type == self.dnd.atoms.leave { + } else if client_msg.type_ == wt.xconn.atoms[XdndLeave] { self.dnd.reset(); callback(Event::WindowEvent { window_id, @@ -303,17 +317,15 @@ impl EventProcessor { } } - ffi::SelectionNotify => { - let xsel: &ffi::XSelectionEvent = xev.as_ref(); - + X11Event::SelectionNotify(xsel) => { let window = xsel.requestor; let window_id = mkwid(window); - if xsel.property == self.dnd.atoms.selection { + if xsel.property == wt.xconn.atoms[XdndSelection] { let mut result = None; // This is where we receive data from drag and drop - if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { + if let Ok(mut data) = self.dnd.read_data(window) { let parse_result = self.dnd.parse_data(&mut data); if let Ok(ref path_list) = parse_result { for path in path_list { @@ -330,8 +342,7 @@ impl EventProcessor { } } - ffi::ConfigureNotify => { - let xev: &ffi::XConfigureEvent = xev.as_ref(); + X11Event::ConfigureNotify(xev) => { let xwindow = xev.window; let window_id = mkwid(xwindow); @@ -343,11 +354,10 @@ impl EventProcessor { // We don't want to send `Moved` when this is false, since then every `Resized` // (whether the window moved or not) is accompanied by an extraneous `Moved` event // that has a position relative to the parent window. - let is_synthetic = xev.send_event == ffi::True; // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); - let new_inner_position = (xev.x, xev.y); + let new_inner_position = (xev.x.into(), xev.y.into()); let (mut resized, moved) = { let mut shared_state_lock = window.shared_state_lock(); @@ -477,7 +487,9 @@ impl EventProcessor { // WMs constrain the window size, making the resize fail. This would cause an endless stream of // XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU. if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { - if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) { + if new_inner_size == adjusted_size + || !wt.xconn.wm_name_is_one_of(&["Xfwm4"]) + { // When this finally happens, the event will not be synthetic. shared_state_lock.dpi_adjusted = None; } else { @@ -497,24 +509,22 @@ impl EventProcessor { } } - ffi::ReparentNotify => { - let xev: &ffi::XReparentEvent = xev.as_ref(); - + X11Event::ReparentNotify(xev) => { // This is generally a reliable way to detect when the window manager's been // replaced, though this event is only fired by reparenting window managers // (which is almost all of them). Failing to correctly update WM info doesn't // really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only // effect is that we waste some time trying to query unsupported properties. - wt.xconn.update_cached_wm_info(wt.root); + wt.xconn + .update_cached_wm_info(wt.root) + .expect("Failed to update WM info"); self.with_window(xev.window, |window| { window.invalidate_cached_frame_extents(); }); } - ffi::DestroyNotify => { - let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); - + X11Event::DestroyNotify(xev) => { let window = xev.window; let window_id = mkwid(window); @@ -524,10 +534,12 @@ impl EventProcessor { // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. + /* wt.ime .borrow_mut() - .remove_context(window) + .remove_context(window as _) .expect("Failed to destroy input context"); + */ callback(Event::WindowEvent { window_id, @@ -535,21 +547,18 @@ impl EventProcessor { }); } - ffi::VisibilityNotify => { - let xev: &ffi::XVisibilityEvent = xev.as_ref(); + X11Event::VisibilityNotify(xev) => { let xwindow = xev.window; callback(Event::WindowEvent { window_id: mkwid(xwindow), - event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured), + event: WindowEvent::Occluded(xev.state == xproto::Visibility::FULLY_OBSCURED), }); self.with_window(xwindow, |window| { window.visibility_notify(); }); } - ffi::Expose => { - let xev: &ffi::XExposeEvent = xev.as_ref(); - + X11Event::Expose(xev) => { // Multiple Expose events may be received for subareas of a window. // We issue `RedrawRequested` only for the last event of such a series. if xev.count == 0 { @@ -560,37 +569,33 @@ impl EventProcessor { } } - ffi::KeyPress | ffi::KeyRelease => { - use crate::event::ElementState::{Pressed, Released}; - + X11Event::KeyPress(ref xkev) | X11Event::KeyRelease(ref xkev) => { // Note that in compose/pre-edit sequences, this will always be Released. - let state = if xev.get_type() == ffi::KeyPress { + let state = if matches!(&xev, X11Event::KeyPress(_)) { Pressed } else { Released }; - let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - - let window = xkev.window; + let window = xkev.event; let window_id = mkwid(window); // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable // value, though this should only be an issue under multiseat configurations. let device = util::VIRTUAL_CORE_KEYBOARD; let device_id = mkdid(device); - let keycode = xkev.keycode; + let keycode = xkev.detail; // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // a keycode of 0. if keycode != 0 && !self.is_composing { - let scancode = keycode - KEYCODE_OFFSET as u32; + let scancode = (keycode - KEYCODE_OFFSET) as u32; let keysym = wt.xconn.lookup_keysym(xkev); let virtual_keycode = events::keysym_to_element(keysym as c_uint); update_modifiers!( - ModifiersState::from_x11_mask(xkev.state), - self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) + ModifiersState::from_x11_mask(xkev.state.into()), + self.mod_keymap.get_modifier(xkev.detail as _) ); let modifiers = self.device_mod_state.modifiers(); @@ -611,8 +616,9 @@ impl EventProcessor { }); } + /* if state == Pressed { - let written = if let Some(ic) = wt.ime.borrow().get_context(window) { + let written = if let Some(ic) = wt.ime.borrow().get_context(window as _) { wt.xconn.lookup_utf8(ic, xkev) } else { return; @@ -645,609 +651,604 @@ impl EventProcessor { } } } + */ } - ffi::GenericEvent => { - let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) { - e - } else { - return; - }; - let xev = &guard.cookie; - if self.xi2ext.opcode != xev.extension { + X11Event::XinputButtonPress(ref xbev) | X11Event::XinputButtonRelease(ref xbev) => { + let window_id = mkwid(xbev.event); + let device_id = mkdid(xbev.deviceid); + + // Once psychon/x11rb#768 reaches a release, use BitAnd directly on the flags. + if (u32::from(xbev.flags) & u32::from(xinput::PointerEventFlags::POINTER_EMULATED)) + != 0 + { + // Deliver multi-touch events instead of emulated mouse events. return; } - use crate::event::{ - ElementState::{Pressed, Released}, - MouseButton::{Left, Middle, Other, Right}, - MouseScrollDelta::LineDelta, - Touch, - WindowEvent::{ - AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, - MouseWheel, - }, - }; + let modifiers = ModifiersState::from_x11(&xbev.mods); + update_modifiers!(modifiers, None); - match xev.evtype { - ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); - if (xev.flags & ffi::XIPointerEmulated) != 0 { - // Deliver multi-touch events instead of emulated mouse events. - return; - } - - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); - - let state = if xev.evtype == ffi::XI_ButtonPress { - Pressed - } else { - Released - }; - match xev.detail as u32 { - ffi::Button1 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Left, - modifiers, - }, - }), - ffi::Button2 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Middle, - modifiers, - }, - }), - ffi::Button3 => callback(Event::WindowEvent { + let state = if matches!(&xev, X11Event::XinputButtonPress(_)) { + Pressed + } else { + Released + }; + match xbev.detail { + 1 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Left, + modifiers, + }, + }), + 2 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Middle, + modifiers, + }, + }), + 3 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Right, + modifiers, + }, + }), + + // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. + // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in + // turn) as axis motion, so we don't otherwise special-case these button presses. + 4 | 5 | 6 | 7 => { + if u32::from(xbev.flags) + & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) + == 0 + { + callback(Event::WindowEvent { window_id, - event: MouseInput { + event: MouseWheel { device_id, - state, - button: Right, + delta: match xbev.detail { + 4 => LineDelta(0.0, 1.0), + 5 => LineDelta(0.0, -1.0), + 6 => LineDelta(1.0, 0.0), + 7 => LineDelta(-1.0, 0.0), + _ => unreachable!(), + }, + phase: TouchPhase::Moved, modifiers, }, - }), - - // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. - // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in - // turn) as axis motion, so we don't otherwise special-case these button presses. - 4 | 5 | 6 | 7 => { - if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match xev.detail { - 4 => LineDelta(0.0, 1.0), - 5 => LineDelta(0.0, -1.0), - 6 => LineDelta(1.0, 0.0), - 7 => LineDelta(-1.0, 0.0), - _ => unreachable!(), - }, - phase: TouchPhase::Moved, - modifiers, - }, - }); - } - } + }); + } + } + + x => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Other(x as u16), + modifiers, + }, + }), + } + } + + X11Event::XinputMotion(xev) => { + let device_id = mkdid(xev.deviceid); + let window_id = mkwid(xev.event); + let new_cursor_pos = (fp1616(xev.event_x), fp1616(xev.event_y)); + + let modifiers = ModifiersState::from_x11(&xev.mods); + update_modifiers!(modifiers, None); + + let cursor_moved = self.with_window(xev.event, |window| { + let mut shared_state_lock = window.shared_state_lock(); + util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) + }); + if cursor_moved == Some(true) { + let position = PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } else if cursor_moved.is_none() { + return; + } - x => callback(Event::WindowEvent { + // More gymnastics, for self.devices + let mut events = Vec::new(); + { + let mut devices = self.devices.borrow_mut(); + let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; + + // Iterator over the set bits in the mask. + let bits_iter = xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { + let quantum = std::mem::size_of::(); + (0..quantum) + .filter(move |j| mask & (1 << j) != 0) + .map(move |j| i * quantum + j) + }); + + // Get the iterator over the axises that we want. + let axis_iter = xev + .axisvalues + .iter() + .map(|&frac| fp3232(frac)) + .zip(bits_iter); + + // Iterate and set the axises. + axis_iter.for_each(|(x, i)| { + if let Some(&mut (_, ref mut info)) = physical_device + .scroll_axes + .iter_mut() + .find(|&&mut (axis, _)| axis as usize == i) + { + let delta = (x - info.position) / info.increment; + info.position = x; + events.push(Event::WindowEvent { window_id, - event: MouseInput { + event: MouseWheel { device_id, - state, - button: Other(x as u16), + delta: match info.orientation { + // X11 vertical scroll coordinates are opposite to winit's + ScrollOrientation::Horizontal => { + LineDelta(-delta as f32, 0.0) + } + ScrollOrientation::Vertical => { + LineDelta(0.0, -delta as f32) + } + }, + phase: TouchPhase::Moved, modifiers, }, - }), - } - } - ffi::XI_Motion => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let device_id = mkdid(xev.deviceid); - let window_id = mkwid(xev.event); - let new_cursor_pos = (xev.event_x, xev.event_y); - - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); - - let cursor_moved = self.with_window(xev.event, |window| { - let mut shared_state_lock = window.shared_state_lock(); - util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) - }); - if cursor_moved == Some(true) { - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - callback(Event::WindowEvent { + }); + } else { + events.push(Event::WindowEvent { window_id, - event: CursorMoved { + event: AxisMotion { device_id, - position, - modifiers, + axis: i as u32, + value: x, }, }); - } else if cursor_moved.is_none() { - return; } + }); + } + for event in events { + callback(event); + } + } - // More gymnastics, for self.devices - let mut events = Vec::new(); - { - let mask = unsafe { - slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, - ) - }; - let mut devices = self.devices.borrow_mut(); - let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { - Some(device) => device, - None => return, - }; - - let mut value = xev.valuators.values; - for i in 0..xev.valuators.mask_len * 8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - if let Some(&mut (_, ref mut info)) = physical_device - .scroll_axes - .iter_mut() - .find(|&&mut (axis, _)| axis == i) - { - let delta = (x - info.position) / info.increment; - info.position = x; - events.push(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match info.orientation { - // X11 vertical scroll coordinates are opposite to winit's - ScrollOrientation::Horizontal => { - LineDelta(-delta as f32, 0.0) - } - ScrollOrientation::Vertical => { - LineDelta(0.0, -delta as f32) - } - }, - phase: TouchPhase::Moved, - modifiers, - }, - }); - } else { - events.push(Event::WindowEvent { - window_id, - event: AxisMotion { - device_id, - axis: i as u32, - value: unsafe { *value }, - }, - }); - } - value = unsafe { value.offset(1) }; - } - } - } - for event in events { - callback(event); - } - } - - ffi::XI_Enter => { - let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; - - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); + X11Event::XinputEnter(xev) => { + let window_id = mkwid(xev.event); + let device_id = mkdid(xev.deviceid); - if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) { - let mut devices = self.devices.borrow_mut(); - for device_info in all_info.iter() { - if device_info.deviceid == xev.sourceid + if let Some(all_info) = DeviceInfo::get(&wt.xconn, 0) { + let mut devices = self.devices.borrow_mut(); + for device_info in all_info.iter() { + if device_info.deviceid == xev.sourceid // This is needed for resetting to work correctly on i3, and // presumably some other WMs. On those, `XI_Enter` doesn't include // the physical device ID, so both `sourceid` and `deviceid` are // the virtual device. || device_info.attachment == xev.sourceid - { - let device_id = DeviceId(device_info.deviceid); - if let Some(device) = devices.get_mut(&device_id) { - device.reset_scroll_position(device_info); - } - } + { + let device_id = DeviceId(device_info.deviceid); + if let Some(device) = devices.get_mut(&device_id) { + device.reset_scroll_position(device_info); } } + } + } - if self.window_exists(xev.event) { - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); + if self.window_exists(xev.event) { + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - // The mods field on this event isn't actually populated, so query the - // pointer device. In the future, we can likely remove this round-trip by - // relying on `Xkb` for modifier values. - // - // This needs to only be done after confirming the window still exists, - // since otherwise we risk getting a `BadWindow` error if the window was - // dropped with queued events. - let modifiers = wt - .xconn - .query_pointer(xev.event, xev.deviceid) - .expect("Failed to query pointer device") - .get_modifier_state(); + let position = PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); + + // The mods field on this event isn't actually populated, so query the + // pointer device. In the future, we can likely remove this round-trip by + // relying on `Xkb` for modifier values. + // + // This needs to only be done after confirming the window still exists, + // since otherwise we risk getting a `BadWindow` error if the window was + // dropped with queued events. + let modifiers = wt + .xconn + .connection + .xinput_xi_query_pointer(xev.event, xev.deviceid) + .platform() + .and_then(|r| r.reply().platform()) + .map(|r| ModifiersState::from_x11(&r.mods)) + .expect("Failed to query pointer device"); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, - }); - } - } - ffi::XI_Leave => { - let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } + } - // Leave, FocusIn, and FocusOut can be received by a window that's already - // been destroyed, which the user presumably doesn't want to deal with. - let window_closed = !self.window_exists(xev.event); - if !window_closed { - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - event: CursorLeft { - device_id: mkdid(xev.deviceid), - }, - }); - } - } - ffi::XI_FocusIn => { - let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; + X11Event::XinputLeave(xev) => { + // Leave, FocusIn, and FocusOut can be received by a window that's already + // been destroyed, which the user presumably doesn't want to deal with. + let window_closed = !self.window_exists(xev.event); + if !window_closed { + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: CursorLeft { + device_id: mkdid(xev.deviceid), + }, + }); + } + } - wt.ime - .borrow_mut() - .focus(xev.event) - .expect("Failed to focus input context"); + X11Event::XinputFocusIn(xev) => { + /* + wt.ime + .borrow_mut() + .focus(xev.event as _) + .expect("Failed to focus input context"); + */ - let modifiers = ModifiersState::from_x11(&xev.mods); + let modifiers = ModifiersState::from_x11(&xev.mods); - self.device_mod_state.update_state(&modifiers, None); + self.device_mod_state.update_state(&modifiers, None); - if self.active_window != Some(xev.event) { - self.active_window = Some(xev.event); + if self.active_window != Some(xev.event) { + self.active_window = Some(xev.event); - wt.update_device_event_filter(true); + wt.update_device_event_filter(true) + .expect("Failed to update device event filter"); - let window_id = mkwid(xev.event); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); + let window_id = mkwid(xev.event); + let position = PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); + callback(Event::WindowEvent { + window_id, + event: Focused(true), + }); - if !modifiers.is_empty() { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(modifiers), - }); - } + if !modifiers.is_empty() { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(modifiers), + }); + } - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self - .devices - .borrow() - .get(&DeviceId(xev.deviceid)) - .map(|device| device.attachment) - .unwrap_or(2); + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let pointer_id = self + .devices + .borrow() + .get(&DeviceId(xev.deviceid)) + .map(|device| device.attachment) + .unwrap_or(2); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers, - }, - }); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position, + modifiers, + }, + }); - // Issue key press events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Pressed, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); - } - } - ffi::XI_FocusOut => { - let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - if !self.window_exists(xev.event) { - return; - } + // Issue key press events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Pressed, + &self.mod_keymap, + &mut self.device_mod_state, + &mut callback, + ); + } + } - wt.ime - .borrow_mut() - .unfocus(xev.event) - .expect("Failed to unfocus input context"); + X11Event::XinputFocusOut(xev) => { + if !self.window_exists(xev.event) { + return; + } - if self.active_window.take() == Some(xev.event) { - let window_id = mkwid(xev.event); + /* + wt.ime + .borrow_mut() + .unfocus(xev.event as _) + .expect("Failed to unfocus input context"); + */ - wt.update_device_event_filter(false); + if self.active_window.take() == Some(xev.event) { + let window_id = mkwid(xev.event); - // Issue key release events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Released, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); + wt.update_device_event_filter(false) + .expect("Failed to update device event filter"); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(ModifiersState::empty()), - }); + // Issue key release events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Released, + &self.mod_keymap, + &mut self.device_mod_state, + &mut callback, + ); - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) - } - } + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(ModifiersState::empty()), + }); - ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - let phase = match xev.evtype { - ffi::XI_TouchBegin => TouchPhase::Started, - ffi::XI_TouchUpdate => TouchPhase::Moved, - ffi::XI_TouchEnd => TouchPhase::Ended, - _ => unreachable!(), - }; - if self.window_exists(xev.event) { - let id = xev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); - let location = PhysicalPosition::new(xev.event_x, xev.event_y); - - // Mouse cursor position changes when touch events are received. - // Only the first concurrently active touch ID moves the mouse cursor. - if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) - { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::CursorMoved { - device_id: mkdid(util::VIRTUAL_CORE_POINTER), - position: location.cast(), - modifiers, - }, - }); - } + callback(Event::WindowEvent { + window_id, + event: Focused(false), + }) + } + } - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), - phase, - location, - force: None, // TODO - id, - }), - }) - } - } + X11Event::XinputTouchBegin(ref xtev) + | X11Event::XinputTouchEnd(ref xtev) + | X11Event::XinputTouchUpdate(ref xtev) => { + let window_id = mkwid(xtev.event); + let phase = match xev { + X11Event::XinputTouchBegin(_) => TouchPhase::Started, + X11Event::XinputTouchUpdate(_) => TouchPhase::Moved, + X11Event::XinputTouchEnd(_) => TouchPhase::Ended, + _ => unreachable!(), + }; + if self.window_exists(xtev.event) { + let id = xtev.detail as u64; + let modifiers = self.device_mod_state.modifiers(); + let location = PhysicalPosition::new(xtev.event_x as f64, xtev.event_y as f64); - ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - if xev.flags & ffi::XIPointerEmulated == 0 { - callback(Event::DeviceEvent { - device_id: mkdid(xev.deviceid), - event: DeviceEvent::Button { - button: xev.detail as u32, - state: match xev.evtype { - ffi::XI_RawButtonPress => Pressed, - ffi::XI_RawButtonRelease => Released, - _ => unreachable!(), - }, - }, - }); - } + // Mouse cursor position changes when touch events are received. + // Only the first concurrently active touch ID moves the mouse cursor. + if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::CursorMoved { + device_id: mkdid(util::VIRTUAL_CORE_POINTER), + position: location.cast(), + modifiers, + }, + }); } - ffi::XI_RawMotion => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - let did = mkdid(xev.deviceid); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Touch(Touch { + device_id: mkdid(xtev.deviceid), + phase, + location, + force: None, // TODO + id, + }), + }) + } + } - let mask = unsafe { - slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, - ) - }; - let mut value = xev.raw_values; - let mut mouse_delta = (0.0, 0.0); - let mut scroll_delta = (0.0, 0.0); - for i in 0..xev.valuators.mask_len * 8 { - if ffi::XIMaskIsSet(mask, i) { - let x = unsafe { *value }; - // We assume that every XInput2 device with analog axes is a pointing device emitting - // relative coordinates. - match i { - 0 => mouse_delta.0 = x, - 1 => mouse_delta.1 = x, - 2 => scroll_delta.0 = x as f32, - 3 => scroll_delta.1 = x as f32, - _ => {} - } - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::Motion { - axis: i as u32, - value: x, - }, - }); - value = unsafe { value.offset(1) }; - } - } - if mouse_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseMotion { delta: mouse_delta }, - }); - } - if scroll_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseWheel { - delta: LineDelta(scroll_delta.0, scroll_delta.1), - }, - }); - } - } + X11Event::XinputRawButtonPress(ref xbev) + | X11Event::XinputRawButtonRelease(ref xbev) => { + if u32::from(xbev.flags) & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) + == 0 + { + callback(Event::DeviceEvent { + device_id: mkdid(xbev.deviceid), + event: DeviceEvent::Button { + button: xbev.detail, + state: match xev { + X11Event::XinputRawButtonPress(_) => Pressed, + X11Event::XinputRawButtonRelease(_) => Released, + _ => unreachable!(), + }, + }, + }); + } + } - ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + X11Event::XinputRawMotion(xev) => { + let did = mkdid(xev.deviceid); - let state = match xev.evtype { - ffi::XI_RawKeyPress => Pressed, - ffi::XI_RawKeyRelease => Released, - _ => unreachable!(), - }; + let mut mouse_delta = (0.0, 0.0); + let mut scroll_delta = (0.0, 0.0); + + // Iterate over all bits in the mask. + let bits_iter = xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { + let quantum = std::mem::size_of::(); - let device_id = mkdid(xev.sourceid); - let keycode = xev.detail; - let scancode = keycode - KEYCODE_OFFSET as i32; - if scancode < 0 { - return; + (0..quantum * 8).filter_map(move |j| { + let bit = 1 << j; + if mask & bit != 0 { + Some(i * quantum * 8 + j) + } else { + None } - let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - let modifiers = self.device_mod_state.modifiers(); + }) + }); - #[allow(deprecated)] - callback(Event::DeviceEvent { - device_id, - event: DeviceEvent::Key(KeyboardInput { - scancode: scancode as u32, - virtual_keycode, - state, - modifiers, - }), - }); + // Match those bits to the raw values. + let values_iter = xev.axisvalues_raw.iter().map(|&v| fp3232(v)).zip(bits_iter); + + values_iter.for_each(|(x, i)| { + // We assume that every XInput2 device with analog axes is a pointing device emitting + // relative coordinates. + match i { + 0 => mouse_delta.0 = x, + 1 => mouse_delta.1 = x, + 2 => scroll_delta.0 = x as f32, + 3 => scroll_delta.1 = x as f32, + _ => {} + } + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::Motion { + axis: i as u32, + value: x, + }, + }); + }); + if mouse_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::MouseMotion { delta: mouse_delta }, + }); + } + if scroll_delta != (0.0, 0.0) { + callback(Event::DeviceEvent { + device_id: did, + event: DeviceEvent::MouseWheel { + delta: LineDelta(scroll_delta.0, scroll_delta.1), + }, + }); + } + } - if let Some(modifier) = - self.mod_keymap.get_modifier(keycode as ffi::KeyCode) - { - self.device_mod_state.key_event( - state, - keycode as ffi::KeyCode, - modifier, - ); + X11Event::XinputRawKeyPress(ref xkev) | X11Event::XinputRawKeyRelease(ref xkev) => { + let state = match xev { + X11Event::XinputRawKeyPress(_) => Pressed, + X11Event::XinputRawKeyRelease(_) => Released, + _ => unreachable!(), + }; - let new_modifiers = self.device_mod_state.modifiers(); + let device_id = mkdid(xkev.sourceid); + let keycode = xkev.detail; + let scancode = match keycode.checked_sub(KEYCODE_OFFSET as u32) { + Some(scancode) => scancode, + None => return, + }; + let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + let modifiers = self.device_mod_state.modifiers(); - if modifiers != new_modifiers { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(new_modifiers), - }); - } - } - } - } + #[allow(deprecated)] + callback(Event::DeviceEvent { + device_id, + event: DeviceEvent::Key(KeyboardInput { + scancode, + virtual_keycode, + state, + modifiers, + }), + }); - ffi::XI_HierarchyChanged => { - let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; - for info in - unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } - { - if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid); - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), - event: DeviceEvent::Added, - }); - } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) - { - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), - event: DeviceEvent::Removed, - }); - let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid)); - } + if let Some(modifier) = self.mod_keymap.get_modifier(keycode as ffi::KeyCode) { + self.device_mod_state + .key_event(state, keycode as ffi::KeyCode, modifier); + + let new_modifiers = self.device_mod_state.modifiers(); + + if modifiers != new_modifiers { + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(new_modifiers), + }); } } + } + } - _ => {} + X11Event::XinputHierarchy(xev) => { + for info in xev.infos.iter() { + if 0 != (u32::from(info.flags) + & u32::from( + xinput::HierarchyMask::SLAVE_ADDED + | xinput::HierarchyMask::MASTER_ADDED, + )) + { + self.init_device(info.deviceid); + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid), + event: DeviceEvent::Added, + }); + } else if 0 + != (u32::from(info.flags) + & u32::from( + xinput::HierarchyMask::SLAVE_REMOVED + | xinput::HierarchyMask::MASTER_REMOVED, + )) + { + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid), + event: DeviceEvent::Removed, + }); + let mut devices = self.devices.borrow_mut(); + devices.remove(&DeviceId(info.deviceid)); + } } } - _ => { - if event_type == self.randr_event_offset { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt.xconn.available_monitors(); - for new_monitor in new_list { - // Previous list may be empty, in case of disconnecting and - // reconnecting the only one monitor. We still need to emit events in - // this case. - let maybe_prev_scale_factor = prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| prev_monitor.scale_factor); - if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { - for (window_id, window) in wt.windows.borrow().iter() { - if let Some(window) = window.upgrade() { - // Check if the window is on this monitor - let monitor = window.current_monitor(); - if monitor.name == new_monitor.name { - let (width, height) = window.inner_size_physical(); - let (new_width, new_height) = window.adjust_for_dpi( - // If we couldn't determine the previous scale - // factor (e.g., because all monitors were closed - // before), just pick whatever the current monitor - // has set as a baseline. - maybe_prev_scale_factor - .unwrap_or(monitor.scale_factor), - new_monitor.scale_factor, - width, - height, - &window.shared_state_lock(), - ); - - let window_id = crate::window::WindowId(*window_id); - let old_inner_size = PhysicalSize::new(width, height); - let mut new_inner_size = - PhysicalSize::new(new_width, new_height); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = new_inner_size.into(); - window - .set_inner_size_physical(new_width, new_height); - } + + X11Event::RandrNotify(_) => { + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = monitor::invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = wt.xconn.available_monitors(); + for new_monitor in new_list { + // Previous list may be empty, in case of disconnecting and + // reconnecting the only one monitor. We still need to emit events in + // this case. + let maybe_prev_scale_factor = prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| prev_monitor.scale_factor); + if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { + for (window_id, window) in wt.windows.borrow().iter() { + if let Some(window) = window.upgrade() { + // Check if the window is on this monitor + let monitor = window.current_monitor(); + if monitor.name == new_monitor.name { + let (width, height) = window.inner_size_physical(); + let (new_width, new_height) = window.adjust_for_dpi( + // If we couldn't determine the previous scale + // factor (e.g., because all monitors were closed + // before), just pick whatever the current monitor + // has set as a baseline. + maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), + new_monitor.scale_factor, + width, + height, + &window.shared_state_lock(), + ); + + let window_id = crate::window::WindowId(*window_id); + let old_inner_size = PhysicalSize::new(width, height); + let mut new_inner_size = + PhysicalSize::new(new_width, new_height); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = new_inner_size.into(); + window.set_inner_size_physical(new_width, new_height); } } } @@ -1256,9 +1257,13 @@ impl EventProcessor { } } } + + _ => {} } // Handle IME requests. + + /* if let Ok(request) = self.ime_receiver.try_recv() { let mut ime = wt.ime.borrow_mut(); match request { @@ -1272,7 +1277,7 @@ impl EventProcessor { } let (window, event) = match self.ime_event_receiver.try_recv() { - Ok((window, event)) => (window, event), + Ok((window, event)) => (window as _, event), Err(_) => return, }; @@ -1314,6 +1319,7 @@ impl EventProcessor { }); } } + */ } fn handle_pressed_keys( diff --git a/src/platform_impl/linux/x11/ime/callbacks.rs b/src/platform_impl/linux/x11/ime/callbacks.rs deleted file mode 100644 index 7128376035b..00000000000 --- a/src/platform_impl/linux/x11/ime/callbacks.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc}; - -use super::{ffi, XConnection, XError}; - -use super::{ - context::{ImeContext, ImeContextCreationError}, - inner::{close_im, ImeInner}, - input_method::PotentialInputMethods, -}; - -pub(crate) unsafe fn xim_set_callback( - xconn: &Arc, - xim: ffi::XIM, - field: *const c_char, - callback: *mut ffi::XIMCallback, -) -> Result<(), XError> { - // It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize - // access that isn't type-checked. - (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()); - xconn.check_errors() -} - -// Set a callback for when an input method matching the current locale modifiers becomes -// available. Note that this has nothing to do with what input methods are open or able to be -// opened, and simply uses the modifiers that are set when the callback is set. -// * This is called per locale modifier, not per input method opened with that locale modifier. -// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt -// input contexts would always silently fail to use the input method. -pub(crate) unsafe fn set_instantiate_callback( - xconn: &Arc, - client_data: ffi::XPointer, -) -> Result<(), XError> { - (xconn.xlib.XRegisterIMInstantiateCallback)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - Some(xim_instantiate_callback), - client_data, - ); - xconn.check_errors() -} - -pub(crate) unsafe fn unset_instantiate_callback( - xconn: &Arc, - client_data: ffi::XPointer, -) -> Result<(), XError> { - (xconn.xlib.XUnregisterIMInstantiateCallback)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - Some(xim_instantiate_callback), - client_data, - ); - xconn.check_errors() -} - -pub(crate) unsafe fn set_destroy_callback( - xconn: &Arc, - im: ffi::XIM, - inner: &ImeInner, -) -> Result<(), XError> { - xim_set_callback( - xconn, - im, - ffi::XNDestroyCallback_0.as_ptr() as *const _, - &inner.destroy_callback as *const _ as *mut _, - ) -} - -#[derive(Debug)] -#[allow(clippy::enum_variant_names)] -enum ReplaceImError { - // Boxed to prevent large error type - MethodOpenFailed(Box), - ContextCreationFailed(ImeContextCreationError), - SetDestroyCallbackFailed(XError), -} - -// Attempt to replace current IM (which may or may not be presently valid) with a new one. This -// includes replacing all existing input contexts and free'ing resources as necessary. This only -// modifies existing state if all operations succeed. -unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> { - let xconn = &(*inner).xconn; - - let (new_im, is_fallback) = { - let new_im = (*inner).potential_input_methods.open_im(xconn, None); - let is_fallback = new_im.is_fallback(); - ( - new_im.ok().ok_or_else(|| { - ReplaceImError::MethodOpenFailed(Box::new((*inner).potential_input_methods.clone())) - })?, - is_fallback, - ) - }; - - // It's important to always set a destroy callback, since there's otherwise potential for us - // to try to use or free a resource that's already been destroyed on the server. - { - let result = set_destroy_callback(xconn, new_im.im, &*inner); - if result.is_err() { - let _ = close_im(xconn, new_im.im); - } - result - } - .map_err(ReplaceImError::SetDestroyCallbackFailed)?; - - let mut new_contexts = HashMap::new(); - for (window, old_context) in (*inner).contexts.iter() { - let spot = old_context.as_ref().map(|old_context| old_context.ic_spot); - - // Check if the IME was allowed on that context. - let is_allowed = old_context - .as_ref() - .map(|old_context| old_context.is_allowed()) - .unwrap_or_default(); - - // We can't use the style from the old context here, since it may change on reload, so - // pick style from the new XIM based on the old state. - let style = if is_allowed { - new_im.preedit_style - } else { - new_im.none_style - }; - - let new_context = { - let result = ImeContext::new( - xconn, - new_im.im, - style, - *window, - spot, - (*inner).event_sender.clone(), - ); - if result.is_err() { - let _ = close_im(xconn, new_im.im); - } - result.map_err(ReplaceImError::ContextCreationFailed)? - }; - new_contexts.insert(*window, Some(new_context)); - } - - // If we've made it this far, everything succeeded. - let _ = (*inner).destroy_all_contexts_if_necessary(); - let _ = (*inner).close_im_if_necessary(); - (*inner).im = Some(new_im); - (*inner).contexts = new_contexts; - (*inner).is_destroyed = false; - (*inner).is_fallback = is_fallback; - Ok(()) -} - -pub unsafe extern "C" fn xim_instantiate_callback( - _display: *mut ffi::Display, - client_data: ffi::XPointer, - // This field is unsupplied. - _call_data: ffi::XPointer, -) { - let inner: *mut ImeInner = client_data as _; - if !inner.is_null() { - let xconn = &(*inner).xconn; - match replace_im(inner) { - Ok(()) => { - let _ = unset_instantiate_callback(xconn, client_data); - (*inner).is_fallback = false; - } - Err(err) => { - if (*inner).is_destroyed { - // We have no usable input methods! - panic!("Failed to reopen input method: {:?}", err); - } - } - } - } -} - -// This callback is triggered when the input method is closed on the server end. When this -// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been -// free'd (attempting to do so causes our connection to freeze). -pub unsafe extern "C" fn xim_destroy_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - // This field is unsupplied. - _call_data: ffi::XPointer, -) { - let inner: *mut ImeInner = client_data as _; - if !inner.is_null() { - (*inner).is_destroyed = true; - let xconn = &(*inner).xconn; - if !(*inner).is_fallback { - let _ = set_instantiate_callback(xconn, client_data); - // Attempt to open fallback input method. - match replace_im(inner) { - Ok(()) => (*inner).is_fallback = true, - Err(err) => { - // We have no usable input methods! - panic!("Failed to open fallback input method: {:?}", err); - } - } - } - } -} diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs deleted file mode 100644 index 4531cb804e5..00000000000 --- a/src/platform_impl/linux/x11/ime/context.rs +++ /dev/null @@ -1,376 +0,0 @@ -use std::ffi::CStr; -use std::os::raw::c_short; -use std::sync::Arc; -use std::{mem, ptr}; - -use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; - -use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle}; -use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; - -use super::{ffi, util, XConnection, XError}; - -/// IME creation error. -#[derive(Debug)] -pub enum ImeContextCreationError { - /// Got the error from Xlib. - XError(XError), - - /// Got null pointer from Xlib but without exact reason. - Null, -} - -/// The callback used by XIM preedit functions. -type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer); - -/// Wrapper for creating XIM callbacks. -#[inline] -fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback { - XIMCallback { - client_data, - callback: Some(callback), - } -} - -/// The server started preedit. -extern "C" fn preedit_start_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - _call_data: ffi::XPointer, -) -> i32 { - let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; - - client_data.text.clear(); - client_data.cursor_pos = 0; - client_data - .event_sender - .send((client_data.window, ImeEvent::Start)) - .expect("failed to send preedit start event"); - -1 -} - -/// Done callback is used when the preedit should be hidden. -extern "C" fn preedit_done_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - _call_data: ffi::XPointer, -) { - let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; - - // Drop text buffer and reset cursor position on done. - client_data.text = Vec::new(); - client_data.cursor_pos = 0; - - client_data - .event_sender - .send((client_data.window, ImeEvent::End)) - .expect("failed to send preedit end event"); -} - -fn calc_byte_position(text: &[char], pos: usize) -> usize { - text.iter() - .take(pos) - .fold(0, |byte_pos, text| byte_pos + text.len_utf8()) -} - -/// Preedit text information to be drawn inline by the client. -extern "C" fn preedit_draw_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - call_data: ffi::XPointer, -) { - let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; - let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) }; - client_data.cursor_pos = call_data.caret as usize; - - let chg_range = - call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize; - if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() { - warn!( - "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}", - client_data.text.len(), - call_data.chg_first, - call_data.chg_length - ); - return; - } - - // NULL indicate text deletion - let mut new_chars = if call_data.text.is_null() { - Vec::new() - } else { - let xim_text = unsafe { &mut *(call_data.text) }; - if xim_text.encoding_is_wchar > 0 { - return; - } - - let new_text = unsafe { xim_text.string.multi_byte }; - - if new_text.is_null() { - return; - } - - let new_text = unsafe { CStr::from_ptr(new_text) }; - - String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")) - .chars() - .collect() - }; - let mut old_text_tail = client_data.text.split_off(chg_range.end); - client_data.text.truncate(chg_range.start); - client_data.text.append(&mut new_chars); - client_data.text.append(&mut old_text_tail); - let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); - - client_data - .event_sender - .send(( - client_data.window, - ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), - )) - .expect("failed to send preedit update event"); -} - -/// Handling of cursor movements in preedit text. -extern "C" fn preedit_caret_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - call_data: ffi::XPointer, -) { - let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; - let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) }; - - if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition { - client_data.cursor_pos = call_data.position as usize; - let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); - - client_data - .event_sender - .send(( - client_data.window, - ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), - )) - .expect("failed to send preedit update event"); - } -} - -/// Struct to simplify callback creation and latter passing into Xlib XIM. -struct PreeditCallbacks { - start_callback: ffi::XIMCallback, - done_callback: ffi::XIMCallback, - draw_callback: ffi::XIMCallback, - caret_callback: ffi::XIMCallback, -} - -impl PreeditCallbacks { - pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { - let start_callback = create_xim_callback(client_data, unsafe { - mem::transmute(preedit_start_callback as usize) - }); - let done_callback = create_xim_callback(client_data, preedit_done_callback); - let caret_callback = create_xim_callback(client_data, preedit_caret_callback); - let draw_callback = create_xim_callback(client_data, preedit_draw_callback); - - PreeditCallbacks { - start_callback, - done_callback, - caret_callback, - draw_callback, - } - } -} - -struct ImeContextClientData { - window: ffi::Window, - event_sender: ImeEventSender, - text: Vec, - cursor_pos: usize, -} - -// XXX: this struct doesn't destroy its XIC resource when dropped. -// This is intentional, as it doesn't have enough information to know whether or not the context -// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled -// through `ImeInner`. -pub struct ImeContext { - pub(crate) ic: ffi::XIC, - pub(crate) ic_spot: ffi::XPoint, - pub(crate) style: Style, - // Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from - // there we keep the pointer to automatically deallocate it. - _client_data: Box, -} - -impl ImeContext { - pub(crate) unsafe fn new( - xconn: &Arc, - im: ffi::XIM, - style: Style, - window: ffi::Window, - ic_spot: Option, - event_sender: ImeEventSender, - ) -> Result { - let client_data = Box::into_raw(Box::new(ImeContextClientData { - window, - event_sender, - text: Vec::new(), - cursor_pos: 0, - })); - - let ic = match style as _ { - Style::Preedit(style) => ImeContext::create_preedit_ic( - xconn, - im, - style, - window, - client_data as ffi::XPointer, - ), - Style::Nothing(style) => ImeContext::create_nothing_ic(xconn, im, style, window), - Style::None(style) => ImeContext::create_none_ic(xconn, im, style, window), - } - .ok_or(ImeContextCreationError::Null)?; - - xconn - .check_errors() - .map_err(ImeContextCreationError::XError)?; - - let mut context = ImeContext { - ic, - ic_spot: ffi::XPoint { x: 0, y: 0 }, - style, - _client_data: Box::from_raw(client_data), - }; - - // Set the spot location, if it's present. - if let Some(ic_spot) = ic_spot { - context.set_spot(xconn, ic_spot.x, ic_spot.y) - } - - Ok(context) - } - - unsafe fn create_none_ic( - xconn: &Arc, - im: ffi::XIM, - style: XIMStyle, - window: ffi::Window, - ) -> Option { - let ic = (xconn.xlib.XCreateIC)( - im, - ffi::XNInputStyle_0.as_ptr() as *const _, - style, - ffi::XNClientWindow_0.as_ptr() as *const _, - window, - ptr::null_mut::<()>(), - ); - - (!ic.is_null()).then(|| ic) - } - - unsafe fn create_preedit_ic( - xconn: &Arc, - im: ffi::XIM, - style: XIMStyle, - window: ffi::Window, - client_data: ffi::XPointer, - ) -> Option { - let preedit_callbacks = PreeditCallbacks::new(client_data); - let preedit_attr = util::XSmartPointer::new( - xconn, - (xconn.xlib.XVaCreateNestedList)( - 0, - ffi::XNPreeditStartCallback_0.as_ptr() as *const _, - &(preedit_callbacks.start_callback) as *const _, - ffi::XNPreeditDoneCallback_0.as_ptr() as *const _, - &(preedit_callbacks.done_callback) as *const _, - ffi::XNPreeditCaretCallback_0.as_ptr() as *const _, - &(preedit_callbacks.caret_callback) as *const _, - ffi::XNPreeditDrawCallback_0.as_ptr() as *const _, - &(preedit_callbacks.draw_callback) as *const _, - ptr::null_mut::<()>(), - ), - ) - .expect("XVaCreateNestedList returned NULL"); - - let ic = (xconn.xlib.XCreateIC)( - im, - ffi::XNInputStyle_0.as_ptr() as *const _, - style, - ffi::XNClientWindow_0.as_ptr() as *const _, - window, - ffi::XNPreeditAttributes_0.as_ptr() as *const _, - preedit_attr.ptr, - ptr::null_mut::<()>(), - ); - - (!ic.is_null()).then(|| ic) - } - - unsafe fn create_nothing_ic( - xconn: &Arc, - im: ffi::XIM, - style: XIMStyle, - window: ffi::Window, - ) -> Option { - let ic = (xconn.xlib.XCreateIC)( - im, - ffi::XNInputStyle_0.as_ptr() as *const _, - style, - ffi::XNClientWindow_0.as_ptr() as *const _, - window, - ptr::null_mut::<()>(), - ); - - (!ic.is_null()).then(|| ic) - } - - pub(crate) fn focus(&self, xconn: &Arc) -> Result<(), XError> { - unsafe { - (xconn.xlib.XSetICFocus)(self.ic); - } - xconn.check_errors() - } - - pub(crate) fn unfocus(&self, xconn: &Arc) -> Result<(), XError> { - unsafe { - (xconn.xlib.XUnsetICFocus)(self.ic); - } - xconn.check_errors() - } - - pub fn is_allowed(&self) -> bool { - !matches!(self.style, Style::None(_)) - } - - // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks - // are being used. Certain IMEs do show selection window, but it's placed in bottom left of the - // window and couldn't be changed. - // - // For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580. - pub(crate) fn set_spot(&mut self, xconn: &Arc, x: c_short, y: c_short) { - if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y { - return; - } - - self.ic_spot = ffi::XPoint { x, y }; - - unsafe { - let preedit_attr = util::XSmartPointer::new( - xconn, - (xconn.xlib.XVaCreateNestedList)( - 0, - ffi::XNSpotLocation_0.as_ptr(), - &self.ic_spot, - ptr::null_mut::<()>(), - ), - ) - .expect("XVaCreateNestedList returned NULL"); - - (xconn.xlib.XSetICValues)( - self.ic, - ffi::XNPreeditAttributes_0.as_ptr() as *const _, - preedit_attr.ptr, - ptr::null_mut::<()>(), - ); - } - } -} diff --git a/src/platform_impl/linux/x11/ime/inner.rs b/src/platform_impl/linux/x11/ime/inner.rs deleted file mode 100644 index e6eca9d048f..00000000000 --- a/src/platform_impl/linux/x11/ime/inner.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{collections::HashMap, mem, sync::Arc}; - -use super::{ffi, XConnection, XError}; - -use super::{ - context::ImeContext, - input_method::{InputMethod, PotentialInputMethods}, -}; -use crate::platform_impl::platform::x11::ime::ImeEventSender; - -pub(crate) unsafe fn close_im(xconn: &Arc, im: ffi::XIM) -> Result<(), XError> { - (xconn.xlib.XCloseIM)(im); - xconn.check_errors() -} - -pub(crate) unsafe fn destroy_ic(xconn: &Arc, ic: ffi::XIC) -> Result<(), XError> { - (xconn.xlib.XDestroyIC)(ic); - xconn.check_errors() -} - -pub(crate) struct ImeInner { - pub xconn: Arc, - pub im: Option, - pub potential_input_methods: PotentialInputMethods, - pub contexts: HashMap>, - // WARNING: this is initially zeroed! - pub destroy_callback: ffi::XIMCallback, - pub event_sender: ImeEventSender, - // Indicates whether or not the the input method was destroyed on the server end - // (i.e. if ibus/fcitx/etc. was terminated/restarted) - pub is_destroyed: bool, - pub is_fallback: bool, -} - -impl ImeInner { - pub(crate) fn new( - xconn: Arc, - potential_input_methods: PotentialInputMethods, - event_sender: ImeEventSender, - ) -> Self { - ImeInner { - xconn, - im: None, - potential_input_methods, - contexts: HashMap::new(), - destroy_callback: unsafe { mem::zeroed() }, - event_sender, - is_destroyed: false, - is_fallback: false, - } - } - - pub unsafe fn close_im_if_necessary(&self) -> Result { - if !self.is_destroyed && self.im.is_some() { - close_im(&self.xconn, self.im.as_ref().unwrap().im).map(|_| true) - } else { - Ok(false) - } - } - - pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result { - if !self.is_destroyed { - destroy_ic(&self.xconn, ic).map(|_| true) - } else { - Ok(false) - } - } - - pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result { - for context in self.contexts.values().flatten() { - self.destroy_ic_if_necessary(context.ic)?; - } - Ok(!self.is_destroyed) - } -} diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs deleted file mode 100644 index 0231d669eba..00000000000 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ /dev/null @@ -1,352 +0,0 @@ -use std::{ - env, - ffi::{CStr, CString, IntoStringError}, - fmt, - os::raw::{c_char, c_ulong, c_ushort}, - ptr, - sync::{Arc, Mutex}, -}; - -use once_cell::sync::Lazy; - -use super::{ffi, util, XConnection, XError}; - -static GLOBAL_LOCK: Lazy> = Lazy::new(Default::default); - -unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option { - let _lock = GLOBAL_LOCK.lock(); - - // XSetLocaleModifiers returns... - // * The current locale modifiers if it's given a NULL pointer. - // * The new locale modifiers if we succeeded in setting them. - // * NULL if the locale modifiers string is malformed or if the - // current locale is not supported by Xlib. - (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()); - - let im = (xconn.xlib.XOpenIM)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ); - - if im.is_null() { - None - } else { - Some(im) - } -} - -#[derive(Debug)] -pub struct InputMethod { - pub im: ffi::XIM, - pub preedit_style: Style, - pub none_style: Style, - _name: String, -} - -impl InputMethod { - fn new(xconn: &Arc, im: ffi::XIM, name: String) -> Option { - let mut styles: *mut XIMStyles = std::ptr::null_mut(); - - // Query the styles supported by the XIM. - unsafe { - if !(xconn.xlib.XGetIMValues)( - im, - ffi::XNQueryInputStyle_0.as_ptr() as *const _, - (&mut styles) as *mut _, - std::ptr::null_mut::<()>(), - ) - .is_null() - { - return None; - } - } - - let mut preedit_style = None; - let mut none_style = None; - - unsafe { - std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _) - .iter() - .for_each(|style| match *style { - XIM_PREEDIT_STYLE => { - preedit_style = Some(Style::Preedit(*style)); - } - XIM_NOTHING_STYLE if preedit_style.is_none() => { - preedit_style = Some(Style::Nothing(*style)) - } - XIM_NONE_STYLE => none_style = Some(Style::None(*style)), - _ => (), - }); - - (xconn.xlib.XFree)(styles.cast()); - }; - - if preedit_style.is_none() && none_style.is_none() { - return None; - } - - let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap()); - let none_style = none_style.unwrap_or(preedit_style); - - Some(InputMethod { - im, - _name: name, - preedit_style, - none_style, - }) - } -} - -const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle; -const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle; -const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle; - -/// Style of the IME context. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Style { - /// Preedit callbacks. - Preedit(XIMStyle), - - /// Nothing. - Nothing(XIMStyle), - - /// No IME. - None(XIMStyle), -} - -impl Default for Style { - fn default() -> Self { - Style::None(XIM_NONE_STYLE) - } -} - -#[repr(C)] -#[derive(Debug)] -struct XIMStyles { - count_styles: c_ushort, - supported_styles: *const XIMStyle, -} - -pub(crate) type XIMStyle = c_ulong; - -#[derive(Debug)] -pub enum InputMethodResult { - /// Input method used locale modifier from `XMODIFIERS` environment variable. - XModifiers(InputMethod), - /// Input method used internal fallback locale modifier. - Fallback(InputMethod), - /// Input method could not be opened using any locale modifier tried. - Failure, -} - -impl InputMethodResult { - pub fn is_fallback(&self) -> bool { - matches!(self, InputMethodResult::Fallback(_)) - } - - pub fn ok(self) -> Option { - use self::InputMethodResult::*; - match self { - XModifiers(im) | Fallback(im) => Some(im), - Failure => None, - } - } -} - -#[derive(Debug, Clone)] -enum GetXimServersError { - XError(XError), - GetPropertyError(util::GetPropertyError), - InvalidUtf8(IntoStringError), -} - -// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting -// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named -// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably -// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale -// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set -// XMODIFIERS to `@server=ibus`?!?" -unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXimServersError> { - let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0"); - - let root = (xconn.xlib.XDefaultRootWindow)(xconn.display); - - let mut atoms: Vec = xconn - .get_property(root, servers_atom, ffi::XA_ATOM) - .map_err(GetXimServersError::GetPropertyError)?; - - let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); - (xconn.xlib.XGetAtomNames)( - xconn.display, - atoms.as_mut_ptr(), - atoms.len() as _, - names.as_mut_ptr() as _, - ); - names.set_len(atoms.len()); - - let mut formatted_names = Vec::with_capacity(names.len()); - for name in names { - let string = CStr::from_ptr(name) - .to_owned() - .into_string() - .map_err(GetXimServersError::InvalidUtf8)?; - (xconn.xlib.XFree)(name as _); - formatted_names.push(string.replace("@server=", "@im=")); - } - xconn.check_errors().map_err(GetXimServersError::XError)?; - Ok(formatted_names) -} - -#[derive(Clone)] -struct InputMethodName { - c_string: CString, - string: String, -} - -impl InputMethodName { - pub fn from_string(string: String) -> Self { - let c_string = CString::new(string.clone()) - .expect("String used to construct CString contained null byte"); - InputMethodName { c_string, string } - } - - pub fn from_str(string: &str) -> Self { - let c_string = - CString::new(string).expect("String used to construct CString contained null byte"); - InputMethodName { - c_string, - string: string.to_owned(), - } - } -} - -impl fmt::Debug for InputMethodName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.string.fmt(f) - } -} - -#[derive(Debug, Clone)] -struct PotentialInputMethod { - name: InputMethodName, - successful: Option, -} - -impl PotentialInputMethod { - pub fn from_string(string: String) -> Self { - PotentialInputMethod { - name: InputMethodName::from_string(string), - successful: None, - } - } - - pub fn from_str(string: &str) -> Self { - PotentialInputMethod { - name: InputMethodName::from_str(string), - successful: None, - } - } - - pub fn reset(&mut self) { - self.successful = None; - } - - pub fn open_im(&mut self, xconn: &Arc) -> Option { - let im = unsafe { open_im(xconn, &self.name.c_string) }; - self.successful = Some(im.is_some()); - im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone())) - } -} - -// By logging this struct, you get a sequential listing of every locale modifier tried, where it -// came from, and if it succeeded. -#[derive(Debug, Clone)] -pub(crate) struct PotentialInputMethods { - // On correctly configured systems, the XMODIFIERS environment variable tells us everything we - // need to know. - xmodifiers: Option, - // We have some standard options at our disposal that should ostensibly always work. For users - // who only need compose sequences, this ensures that the program launches without a hitch - // For users who need more sophisticated IME features, this is more or less a silent failure. - // Logging features should be added in the future to allow both audiences to be effectively - // served. - fallbacks: [PotentialInputMethod; 2], - // For diagnostic purposes, we include the list of XIM servers that the server reports as - // being available. - _xim_servers: Result, GetXimServersError>, -} - -impl PotentialInputMethods { - pub fn new(xconn: &Arc) -> Self { - let xmodifiers = env::var("XMODIFIERS") - .ok() - .map(PotentialInputMethod::from_string); - PotentialInputMethods { - // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of - // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply - // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is - // defined in the profile (or parent environment) then that parent XMODIFIERS is used. - // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then - // XSetLocaleModifiers uses the default local input method. Note that defining - // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in - // that case, we get `None` and end up skipping ahead to the next method. - xmodifiers, - fallbacks: [ - // This is a standard input method that supports compose sequences, which should - // always be available. `@im=none` appears to mean the same thing. - PotentialInputMethod::from_str("@im=local"), - // This explicitly specifies to use the implementation-dependent default, though - // that seems to be equivalent to just using the local input method. - PotentialInputMethod::from_str("@im="), - ], - // The XIM_SERVERS property can have surprising values. For instance, when I exited - // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is - // that the fcitx input method could only be successfully opened using "@im=ibus". - // Presumably due to this quirk, it's actually possible to alternate between ibus and - // fcitx in a running application. - _xim_servers: unsafe { get_xim_servers(xconn) }, - } - } - - // This resets the `successful` field of every potential input method, ensuring we have - // accurate information when this struct is re-used by the destruction/instantiation callbacks. - fn reset(&mut self) { - if let Some(ref mut input_method) = self.xmodifiers { - input_method.reset(); - } - - for input_method in &mut self.fallbacks { - input_method.reset(); - } - } - - pub fn open_im( - &mut self, - xconn: &Arc, - callback: Option<&dyn Fn()>, - ) -> InputMethodResult { - use self::InputMethodResult::*; - - self.reset(); - - if let Some(ref mut input_method) = self.xmodifiers { - let im = input_method.open_im(xconn); - if let Some(im) = im { - return XModifiers(im); - } else if let Some(ref callback) = callback { - callback(); - } - } - - for input_method in &mut self.fallbacks { - let im = input_method.open_im(xconn); - if let Some(im) = im { - return Fallback(im); - } - } - - Failure - } -} diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs deleted file mode 100644 index 4c45cdf5d7f..00000000000 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Important: all XIM calls need to happen from the same thread! - -mod callbacks; -mod context; -mod inner; -mod input_method; - -use std::sync::{ - mpsc::{Receiver, Sender}, - Arc, -}; - -use super::{ffi, util, XConnection, XError}; - -pub use self::context::ImeContextCreationError; -use self::{ - callbacks::*, - context::ImeContext, - inner::{close_im, ImeInner}, - input_method::{PotentialInputMethods, Style}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ImeEvent { - Enabled, - Start, - Update(String, usize), - End, - Disabled, -} - -pub type ImeReceiver = Receiver; -pub type ImeSender = Sender; -pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>; -pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>; - -/// Request to control XIM handler from the window. -pub enum ImeRequest { - /// Set IME spot position for given `window_id`. - Position(ffi::Window, i16, i16), - - /// Allow IME input for the given `window_id`. - Allow(ffi::Window, bool), -} - -#[derive(Debug)] -pub(crate) enum ImeCreationError { - // Boxed to prevent large error type - OpenFailure(Box), - SetDestroyCallbackFailed(XError), -} - -pub(crate) struct Ime { - xconn: Arc, - // The actual meat of this struct is boxed away, since it needs to have a fixed location in - // memory so we can pass a pointer to it around. - inner: Box, -} - -impl Ime { - pub fn new( - xconn: Arc, - event_sender: ImeEventSender, - ) -> Result { - let potential_input_methods = PotentialInputMethods::new(&xconn); - - let (mut inner, client_data) = { - let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender)); - let inner_ptr = Box::into_raw(inner); - let client_data = inner_ptr as _; - let destroy_callback = ffi::XIMCallback { - client_data, - callback: Some(xim_destroy_callback), - }; - inner = unsafe { Box::from_raw(inner_ptr) }; - inner.destroy_callback = destroy_callback; - (inner, client_data) - }; - - let xconn = Arc::clone(&inner.xconn); - - let input_method = inner.potential_input_methods.open_im( - &xconn, - Some(&|| { - let _ = unsafe { set_instantiate_callback(&xconn, client_data) }; - }), - ); - - let is_fallback = input_method.is_fallback(); - if let Some(input_method) = input_method.ok() { - inner.is_fallback = is_fallback; - unsafe { - let result = set_destroy_callback(&xconn, input_method.im, &inner) - .map_err(ImeCreationError::SetDestroyCallbackFailed); - if result.is_err() { - let _ = close_im(&xconn, input_method.im); - } - result?; - } - inner.im = Some(input_method); - Ok(Ime { xconn, inner }) - } else { - Err(ImeCreationError::OpenFailure(Box::new( - inner.potential_input_methods, - ))) - } - } - - pub fn is_destroyed(&self) -> bool { - self.inner.is_destroyed - } - - // This pattern is used for various methods here: - // Ok(_) indicates that nothing went wrong internally - // Ok(true) indicates that the action was actually performed - // Ok(false) indicates that the action is not presently applicable - pub fn create_context( - &mut self, - window: ffi::Window, - with_preedit: bool, - ) -> Result { - let context = if self.is_destroyed() { - // Create empty entry in map, so that when IME is rebuilt, this window has a context. - None - } else { - let im = self.inner.im.as_ref().unwrap(); - let style = if with_preedit { - im.preedit_style - } else { - im.none_style - }; - - let context = unsafe { - ImeContext::new( - &self.inner.xconn, - im.im, - style, - window, - None, - self.inner.event_sender.clone(), - )? - }; - - // Check the state on the context, since it could fail to enable or disable preedit. - let event = if matches!(style, Style::None(_)) { - if with_preedit { - debug!("failed to create IME context with preedit support.") - } - ImeEvent::Disabled - } else { - if !with_preedit { - debug!("failed to create IME context without preedit support.") - } - ImeEvent::Enabled - }; - - self.inner - .event_sender - .send((window, event)) - .expect("Failed to send enabled event"); - - Some(context) - }; - - self.inner.contexts.insert(window, context); - Ok(!self.is_destroyed()) - } - - pub fn get_context(&self, window: ffi::Window) -> Option { - if self.is_destroyed() { - return None; - } - if let Some(&Some(ref context)) = self.inner.contexts.get(&window) { - Some(context.ic) - } else { - None - } - } - - pub fn remove_context(&mut self, window: ffi::Window) -> Result { - if let Some(Some(context)) = self.inner.contexts.remove(&window) { - unsafe { - self.inner.destroy_ic_if_necessary(context.ic)?; - } - Ok(true) - } else { - Ok(false) - } - } - - pub fn focus(&mut self, window: ffi::Window) -> Result { - if self.is_destroyed() { - return Ok(false); - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.focus(&self.xconn).map(|_| true) - } else { - Ok(false) - } - } - - pub fn unfocus(&mut self, window: ffi::Window) -> Result { - if self.is_destroyed() { - return Ok(false); - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.unfocus(&self.xconn).map(|_| true) - } else { - Ok(false) - } - } - - pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) { - if self.is_destroyed() { - return; - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.set_spot(&self.xconn, x as _, y as _); - } - } - - pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) { - if self.is_destroyed() { - return; - } - - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - if allowed == context.is_allowed() { - return; - } - } - - // Remove context for that window. - let _ = self.remove_context(window); - - // Create new context supporting IME input. - let _ = self.create_context(window, allowed); - } -} - -impl Drop for Ime { - fn drop(&mut self) { - unsafe { - let _ = self.inner.destroy_all_contexts_if_necessary(); - let _ = self.inner.close_im_if_necessary(); - } - } -} diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 8f96c2dc5f7..2ba86be06d2 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -1,10 +1,10 @@ #![cfg(x11_platform)] +mod atoms; mod dnd; mod event_processor; mod events; pub mod ffi; -mod ime; mod monitor; pub mod util; mod window; @@ -20,20 +20,22 @@ pub use self::xdisplay::{XError, XNotSupported}; use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet}, - ffi::CStr, - mem::{self, MaybeUninit}, + collections::{HashMap, HashSet, VecDeque}, + fmt, ops::Deref, - os::raw::*, - ptr, rc::Rc, - slice, sync::mpsc::{Receiver, Sender, TryRecvError}, - sync::{mpsc, Arc, Weak}, + sync::{Arc, Weak}, time::{Duration, Instant}, }; - -use libc::{self, setlocale, LC_CTYPE}; +use x11rb::{ + errors::{ConnectionError, ReplyError, ReplyOrIdError}, + protocol::{ + xinput::{self, ConnectionExt as _}, + xproto::{self, ConnectionExt as _}, + }, + x11_utils::X11Error, +}; use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker}; use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; @@ -41,7 +43,6 @@ use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, - ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}, util::modifiers::ModifierKeymap, }; use crate::{ @@ -57,6 +58,77 @@ use crate::{ window::WindowAttributes, }; +/// Sum error for X11 errors. +pub(crate) enum PlatformError { + /// An error that can occur during connection operation. + Connection(ConnectionError), + + /// An error that can occur as a result of a protocol. + Protocol(X11Error), + + /// An error that can occur during Xlib operation. + Xlib(XError), +} + +impl fmt::Debug for PlatformError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PlatformError::Connection(e) => fmt::Debug::fmt(e, f), + PlatformError::Protocol(e) => fmt::Debug::fmt(e, f), + PlatformError::Xlib(e) => fmt::Debug::fmt(e, f), + } + } +} + +impl fmt::Display for PlatformError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PlatformError::Connection(e) => fmt::Display::fmt(e, f), + PlatformError::Protocol(_) => f.write_str("Encountered a libX11 error"), + PlatformError::Xlib(e) => fmt::Display::fmt(e, f), + } + } +} + +impl std::error::Error for PlatformError {} + +impl From for PlatformError { + fn from(value: ConnectionError) -> Self { + PlatformError::Connection(value) + } +} + +impl From for PlatformError { + fn from(value: X11Error) -> Self { + PlatformError::Protocol(value) + } +} + +impl From for PlatformError { + fn from(value: XError) -> Self { + PlatformError::Xlib(value) + } +} + +impl From for PlatformError { + fn from(value: ReplyError) -> Self { + match value { + ReplyError::ConnectionError(e) => PlatformError::Connection(e), + ReplyError::X11Error(e) => PlatformError::Protocol(e), + } + } +} + +impl From for PlatformError { + fn from(value: ReplyOrIdError) -> Self { + match value { + ReplyOrIdError::ConnectionError(e) => PlatformError::Connection(e), + ReplyOrIdError::X11Error(e) => PlatformError::Protocol(e), + ReplyOrIdError::IdsExhausted => panic!("XID space exhausted"), + } + } +} + const X_TOKEN: Token = Token(0); const USER_REDRAW_TOKEN: Token = Token(1); @@ -101,11 +173,9 @@ impl PeekableReceiver { pub struct EventLoopWindowTarget { xconn: Arc, - wm_delete_window: ffi::Atom, - net_wm_ping: ffi::Atom, - ime_sender: ImeSender, - root: ffi::Window, - ime: RefCell, + //ime_sender: ImeSender, + root: xproto::Window, + //ime: RefCell, windows: RefCell>>, redraw_sender: WakeSender, device_event_filter: Cell, @@ -138,15 +208,11 @@ impl Clone for EventLoopProxy { impl EventLoop { pub(crate) fn new(xconn: Arc) -> EventLoop { - let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; + let root = xconn.default_screen().root; - let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") }; - - let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\0") }; - - let dnd = Dnd::new(Arc::clone(&xconn)) - .expect("Failed to call XInternAtoms when initializing drag and drop"); + let dnd = Dnd::new(Arc::clone(&xconn)); + /* 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 @@ -179,49 +245,27 @@ impl EventLoop { } result.expect("Failed to set input method destruction callback") }); - - let randr_event_offset = xconn - .select_xrandr_input(root) - .expect("Failed to query XRandR extension"); - - let xi2ext = unsafe { - let mut ext = XExtension::default(); - - let res = (xconn.xlib.XQueryExtension)( - xconn.display, - b"XInputExtension\0".as_ptr() as *const c_char, - &mut ext.opcode, - &mut ext.first_event_id, - &mut ext.first_error_id, - ); - - if res == ffi::False { - panic!("X server missing XInput extension"); - } - - ext - }; - - unsafe { - let mut xinput_major_ver = ffi::XI_2_Major; - let mut xinput_minor_ver = ffi::XI_2_Minor; - if (xconn.xinput2.XIQueryVersion)( - xconn.display, - &mut xinput_major_ver, - &mut xinput_minor_ver, - ) != ffi::Success as libc::c_int - { - panic!( - "X server has XInput extension {}.{} but does not support XInput2", - xinput_major_ver, xinput_minor_ver, - ); - } + */ + + // Check if XInput2 is available. + if xconn + .connection + .xinput_xi_query_version(2, 3) + .unwrap() + .reply() + .is_err() + { + panic!("X server missing XInput2 extension"); } - xconn.update_cached_wm_info(root); + xconn + .update_cached_wm_info(root) + .expect("Failed to update cached WM info"); let mut mod_keymap = ModifierKeymap::new(); - mod_keymap.reset_from_x_connection(&xconn); + mod_keymap + .reset_from_x_connection(&xconn) + .expect("Failed to reset modifier keymap"); let poll = Poll::new().unwrap(); let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); @@ -234,14 +278,10 @@ impl EventLoop { let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); let window_target = EventLoopWindowTarget { - ime, root, windows: Default::default(), _marker: ::std::marker::PhantomData, - ime_sender, xconn, - wm_delete_window, - net_wm_ping, redraw_sender: WakeSender { sender: redraw_sender, // not used again so no clone waker: waker.clone(), @@ -250,7 +290,9 @@ impl EventLoop { }; // Set initial device event filter. - window_target.update_device_event_filter(true); + window_target + .update_device_event_filter(true) + .expect("Failed to update device event filter"); let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(window_target), @@ -261,26 +303,32 @@ impl EventLoop { target: target.clone(), dnd, devices: Default::default(), - randr_event_offset, - ime_receiver, - ime_event_receiver, - xi2ext, mod_keymap, device_mod_state: Default::default(), num_touch: 0, first_touch: None, active_window: None, is_composing: false, + event_queue: VecDeque::new(), }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) + let all_devices = 0; get_xtarget(&target) .xconn - .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) - .queue(); + .connection + .xinput_xi_select_events( + root, + &[xinput::EventMask { + deviceid: all_devices, + mask: vec![xinput::XIEventMask::HIERARCHY], + }], + ) + .expect("Failed to select XI_HIERARCHY events") + .ignore_error(); - event_processor.init_device(ffi::XIAllDevices); + event_processor.init_device(all_devices); EventLoop { poll, @@ -505,11 +553,13 @@ impl EventLoop { F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let target = &self.target; - let mut xev = MaybeUninit::uninit(); let wt = get_xtarget(&self.target); - while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { - let mut xev = unsafe { xev.assume_init() }; + while let Some(mut xev) = self + .event_processor + .poll_one_event() + .expect("Failed to pump X11 events") + { self.event_processor.process_event(&mut xev, |event| { sticky_exit_callback( event, @@ -549,29 +599,36 @@ impl EventLoopWindowTarget { } /// Update the device event filter based on window focus. - pub fn update_device_event_filter(&self, focus: bool) { + pub(crate) fn update_device_event_filter(&self, focus: bool) -> Result<(), PlatformError> { let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never || (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus); - let mut mask = 0; + let mut mask = xinput::XIEventMask::from(0u16); if !filter_events { - mask = ffi::XI_RawMotionMask - | ffi::XI_RawButtonPressMask - | ffi::XI_RawButtonReleaseMask - | ffi::XI_RawKeyPressMask - | ffi::XI_RawKeyReleaseMask; + 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; } + let all_master_devices = 1; + let events = [xinput::EventMask { + deviceid: all_master_devices, + mask: vec![mask], + }]; + self.xconn - .select_xinput_events(self.root, ffi::XIAllMasterDevices, mask) - .queue(); + .connection + .xinput_xi_select_events(self.root, &events)? + .ignore_error(); + Ok(()) } pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { let mut display_handle = XlibDisplayHandle::empty(); - display_handle.display = self.xconn.display as *mut _; - display_handle.screen = - unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) }; + display_handle.display = self.xconn.display.as_ptr() as *mut _; + display_handle.screen = self.xconn.default_screen as _; RawDisplayHandle::Xlib(display_handle) } } @@ -585,48 +642,36 @@ impl EventLoopProxy { } } -struct DeviceInfo<'a> { - xconn: &'a XConnection, - info: *const ffi::XIDeviceInfo, - count: usize, +struct DeviceInfo { + info: Vec, } -impl<'a> DeviceInfo<'a> { - fn get(xconn: &'a XConnection, device: c_int) -> Option { - 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 DeviceInfo { + fn get(xconn: &XConnection, device: xinput::DeviceId) -> Option { + let info = xconn + .connection + .xinput_xi_query_device(device) + .ok()? + .reply() + .ok()?; + + if info.infos.is_empty() { + None + } else { + Some(DeviceInfo { info: info.infos }) } } } -impl<'a> Drop for DeviceInfo<'a> { - fn drop(&mut self) { - assert!(!self.info.is_null()); - unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; - } -} - -impl<'a> Deref for DeviceInfo<'a> { - type Target = [ffi::XIDeviceInfo]; +impl Deref for DeviceInfo { + type Target = [xinput::XIDeviceInfo]; fn deref(&self) -> &Self::Target { - unsafe { slice::from_raw_parts(self.info, self.count) } + &self.info } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(c_int); +pub struct DeviceId(xinput::DeviceId); impl DeviceId { #[allow(unused)] @@ -664,63 +709,28 @@ impl Drop for Window { fn drop(&mut self) { let window = self.deref(); let xconn = &window.xconn; - unsafe { - (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window); - // If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about. - let _ = xconn.check_errors(); - } + xconn + .connection + .destroy_window(window.id().0 as _) + .map(|r| r.ignore_error()) + .ok(); } } -/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to -/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed -struct GenericEventCookie<'a> { - xconn: &'a XConnection, - cookie: ffi::XGenericEventCookie, -} - -impl<'a> GenericEventCookie<'a> { - fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option> { - unsafe { - let mut cookie: ffi::XGenericEventCookie = From::from(event); - if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True { - Some(GenericEventCookie { xconn, cookie }) - } else { - None - } - } - } +fn mkwid(w: xproto::Window) -> crate::window::WindowId { + crate::window::WindowId(crate::platform_impl::platform::WindowId(w as u64)) } - -impl<'a> Drop for GenericEventCookie<'a> { - fn drop(&mut self) { - unsafe { - (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); - } - } -} - -#[derive(Debug, Default, Copy, Clone)] -struct XExtension { - opcode: c_int, - first_event_id: c_int, - first_error_id: c_int, -} - -fn mkwid(w: ffi::Window) -> crate::window::WindowId { - crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) -} -fn mkdid(w: c_int) -> crate::event::DeviceId { +fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] struct Device { _name: String, - scroll_axes: Vec<(i32, ScrollAxis)>, + scroll_axes: Vec<(u16, ScrollAxis)>, // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. - attachment: c_int, + attachment: u16, } #[derive(Debug, Copy, Clone)] @@ -737,25 +747,21 @@ enum ScrollOrientation { } impl Device { - fn new(info: &ffi::XIDeviceInfo) -> Self { - let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; + fn new(info: &xinput::XIDeviceInfo) -> Self { + let name = String::from_utf8_lossy(&info.name).into_owned(); let mut scroll_axes = Vec::new(); if Device::physical_device(info) { // Identify scroll axes - for class_ptr in Device::classes(info) { - let class = unsafe { &**class_ptr }; - if class._type == ffi::XIScrollClass { - let info = unsafe { - mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class) - }; + for class in info.classes.iter() { + if let xinput::DeviceClassData::Scroll(ref info) = class.data { scroll_axes.push(( info.number, ScrollAxis { - increment: info.increment, + increment: fp3232(info.increment), orientation: match info.scroll_type { - ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, - ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, + xinput::ScrollType::HORIZONTAL => ScrollOrientation::Horizontal, + xinput::ScrollType::VERTICAL => ScrollOrientation::Vertical, _ => unreachable!(), }, position: 0.0, @@ -766,7 +772,7 @@ impl Device { } let mut device = Device { - _name: name.into_owned(), + _name: name, scroll_axes, attachment: info.attachment, }; @@ -774,20 +780,16 @@ impl Device { device } - fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) { + fn reset_scroll_position(&mut self, info: &xinput::XIDeviceInfo) { if Device::physical_device(info) { - for class_ptr in Device::classes(info) { - let class = unsafe { &**class_ptr }; - if class._type == ffi::XIValuatorClass { - let info = unsafe { - mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class) - }; + for class in info.classes.iter() { + if let xinput::DeviceClassData::Valuator(ref info) = class.data { if let Some(&mut (_, ref mut axis)) = self .scroll_axes .iter_mut() - .find(|&&mut (axis, _)| axis == info.number) + .find(|&&mut (axis, _)| axis == info.number as _) { - axis.position = info.value; + axis.position = fp3232(info.value); } } } @@ -795,19 +797,25 @@ impl Device { } #[inline] - fn physical_device(info: &ffi::XIDeviceInfo) -> bool { - info._use == ffi::XISlaveKeyboard - || info._use == ffi::XISlavePointer - || info._use == ffi::XIFloatingSlave - } + fn physical_device(info: &xinput::XIDeviceInfo) -> bool { + use xinput::DeviceType; - #[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, - ) - } + matches!( + info.type_, + DeviceType::SLAVE_KEYBOARD | DeviceType::SLAVE_POINTER | DeviceType::FLOATING_SLAVE + ) } } + +#[inline] +fn fp3232(fp: xinput::Fp3232) -> f64 { + // Fixed point fractional representation. + let int = fp.integral as f64; + let frac = fp.frac as f64 / (1u64 << 32) as f64; + int + frac +} + +#[inline] +fn fp1616(fp: xinput::Fp1616) -> f64 { + (fp as f64) / (0x10000 as f64) +} diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 297e3debf5d..4332e723e81 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -1,21 +1,15 @@ -use std::os::raw::*; -use std::slice; use std::sync::Mutex; use once_cell::sync::Lazy; -use super::{ - ffi::{ - RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, - RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources, - }, - util, XConnection, XError, -}; +use super::{util, PlatformError, XConnection}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, }; +use x11rb::protocol::randr::{self, ConnectionExt as _}; + // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; @@ -31,7 +25,7 @@ pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, - pub(crate) native_mode: RRMode, + pub(crate) native_mode: u32, pub(crate) monitor: Option, } @@ -60,7 +54,7 @@ impl VideoMode { #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - pub(crate) id: RRCrtc, + pub(crate) id: u32, /// The name of the monitor pub(crate) name: String, /// The size of the monitor @@ -106,10 +100,10 @@ impl std::hash::Hash for MonitorHandle { } #[inline] -pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option { - if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 { +pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option { + if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 { #[allow(clippy::unnecessary_cast)] - Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32) + Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32) } else { None } @@ -118,28 +112,68 @@ pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option { impl MonitorHandle { fn new( xconn: &XConnection, - resources: *mut XRRScreenResources, - id: RRCrtc, - crtc: *mut XRRCrtcInfo, + resource_modes: &[randr::ModeInfo], + crtc_id: u32, + crtc_info: &randr::GetCrtcInfoReply, primary: bool, - ) -> Option { - let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; - let dimensions = unsafe { ((*crtc).width, (*crtc).height) }; - let position = unsafe { ((*crtc).x, (*crtc).y) }; + ) -> Result { + let (name, scale_factor, video_modes) = { + let output_info = xconn + .connection + .randr_get_output_info(crtc_info.outputs[0], crtc_info.timestamp)? + .reply()?; + + // Get the scale factor for the monitor. + let scale_factor = xconn.dpi_for_monitor(crtc_info, &output_info); + + let randr::GetOutputInfoReply { name, modes, .. } = output_info; + + // Parse the modes of the monitor. + let modes = { + let depth = xconn.default_screen().root_depth; + + resource_modes + .iter() + .filter(|mode| modes.contains(&mode.id)) + .map(|mode| { + VideoMode { + size: (mode.width as _, mode.height as _), + refresh_rate_millihertz: mode_refresh_rate_millihertz(mode) + .unwrap_or(0), + bit_depth: depth.into(), + // This is populated in `MonitorHandle::video_modes` as the + // video mode is returned to the user + native_mode: mode.id as _, + monitor: None, + } + }) + .collect::>() + }; + + ( + String::from_utf8_lossy(&name).into_owned(), + scale_factor, + modes, + ) + }; + + let dimensions = ((crtc_info).width as u32, (crtc_info).height as u32); + let position = ((crtc_info).x as i32, (crtc_info).y as i32); // Get the refresh rate of the current video mode. - let current_mode = unsafe { (*crtc).mode }; - let screen_modes = - unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) }; - let refresh_rate_millihertz = screen_modes - .iter() - .find(|mode| mode.id == current_mode) - .and_then(mode_refresh_rate_millihertz); + let refresh_rate_millihertz = { + let current_mode = (crtc_info).mode; + + resource_modes + .iter() + .find(|mode| mode.id == current_mode) + .and_then(mode_refresh_rate_millihertz) + }; let rect = util::AaRect::new(position, dimensions); - Some(MonitorHandle { - id, + Ok(MonitorHandle { + id: crtc_id, name, refresh_rate_millihertz, scale_factor, @@ -235,57 +269,89 @@ impl XConnection { matched_monitor.to_owned() } - fn query_monitor_list(&self) -> Vec { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + fn query_monitor_list(&self) -> Result, PlatformError> { + let (major, minor) = { + let extension_info = self.connection.randr_query_version(0, 0)?.reply()?; + (extension_info.major_version, extension_info.minor_version) + }; + let root = self.default_screen().root; + + // Start fetching the primary monitor as we fetch the monitor list. + let primary_token = self.connection.randr_get_output_primary(root)?; + + let (crtc_ids, resource_modes) = { + if (major == 1 && minor >= 3) || major > 1 { + let reply = self + .connection + .randr_get_screen_resources_current(root)? + .reply()?; - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) + let randr::GetScreenResourcesCurrentReply { crtcs, modes, .. } = reply; + (crtcs, modes) } else { // WARNING: this function is supposedly very slow, on the order of hundreds of ms. // Upon failure, `resources` will be null. - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; + let reply = self.connection.randr_get_screen_resources(root)?.reply()?; - if resources.is_null() { - panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); + let randr::GetScreenResourcesReply { crtcs, modes, .. } = reply; + (crtcs, modes) } + }; - let mut has_primary = false; - - let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); - let mut available = Vec::with_capacity((*resources).ncrtc as usize); - - for crtc_index in 0..(*resources).ncrtc { - let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; - if is_active { - let is_primary = *(*crtc).outputs.offset(0) == primary; - has_primary |= is_primary; - if let Some(monitor_id) = - MonitorHandle::new(self, resources, crtc_id, crtc, is_primary) - { - available.push(monitor_id) - } - } - (self.xrandr.XRRFreeCrtcInfo)(crtc); - } + // Get the CRTC information for each CRTC. + let crtc_info_tokens = crtc_ids + .into_iter() + .map(|crtc| { + self.connection + .randr_get_crtc_info(crtc, 0) + .map(move |token| (crtc, token)) + }) + .collect::, _>>()?; - // If no monitors were detected as being primary, we just pick one ourselves! - if !has_primary { - if let Some(ref mut fallback) = available.first_mut() { - // Setting this here will come in handy if we ever add an `is_primary` method. - fallback.primary = true; - } - } + let mut has_primary = false; + + let primary = primary_token.reply()?.output; - (self.xrandr.XRRFreeScreenResources)(resources); - available + // Get the monitor information for each CRTC. + let mut available = crtc_info_tokens + .into_iter() + .filter_map(|(crtc_id, cookie)| { + let result = cookie.reply(); + + result + .map(|crtc_info| { + let is_active = crtc_info.width > 0 + && crtc_info.height > 0 + && !crtc_info.outputs.is_empty(); + if is_active { + let is_primary = crtc_info.outputs[0] == primary; + has_primary |= is_primary; + if let Ok(monitor_id) = MonitorHandle::new( + self, + &resource_modes, + crtc_id, + &crtc_info, + is_primary, + ) { + return Some(monitor_id); + } + } + + None + }) + .transpose() + }) + .collect::, _>>()?; + + // If no monitors were detected as being primary, we just pick one ourselves! + if !has_primary { + if let Some(ref mut fallback) = available.first_mut() { + // Setting this here will come in handy if we ever add an `is_primary` method. + fallback.primary = true; + } } + + Ok(available) } pub fn available_monitors(&self) -> Vec { @@ -294,7 +360,10 @@ impl XConnection { .as_ref() .cloned() .or_else(|| { - let monitors = Some(self.query_monitor_list()); + let monitors = Some( + self.query_monitor_list() + .expect("Failed to load monitors list"), + ); if !DISABLE_MONITOR_LIST_CACHING { (*monitors_lock) = monitors.clone(); } @@ -311,31 +380,91 @@ impl XConnection { .unwrap_or_else(MonitorHandle::dummy) } - pub fn select_xrandr_input(&self, root: Window) -> Result { - let has_xrandr = unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) - }; - assert!( - has_xrandr == True, - "[winit] XRandR extension not available." - ); + /// Get the DPI factor for a monitor, considering the environment. + fn dpi_for_monitor( + &self, + crtc: &randr::GetCrtcInfoReply, + monitor: &randr::GetOutputInfoReply, + ) -> f64 { + /// Represents values of `WINIT_HIDPI_FACTOR`. + enum EnvVarDPI { + Randr, + Scale(f64), + NotSet, + } - let mut event_offset = 0; - let mut error_offset = 0; - let status = unsafe { - (self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset) - }; + // Check the environment variable first. + let dpi_env = std::env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else( + || EnvVarDPI::NotSet, + |var| { + if var.to_lowercase() == "randr" { + EnvVarDPI::Randr + } else if let Ok(dpi) = var.parse::() { + EnvVarDPI::Scale(dpi) + } else if var.is_empty() { + EnvVarDPI::NotSet + } else { + panic!( + "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + var + ); + } + }, + ); - if status != True { - self.check_errors()?; - unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); + // Determine the scale factor. + match dpi_env { + EnvVarDPI::Randr => raw_dpi_for_monitor(crtc, monitor), + EnvVarDPI::Scale(dpi_override) => { + if !crate::dpi::validate_scale_factor(dpi_override) { + panic!( + "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + dpi_override, + ); + } + dpi_override + } + EnvVarDPI::NotSet => { + if let Some(dpi) = self.xft_dpi() { + dpi / 96. + } else { + raw_dpi_for_monitor(crtc, monitor) + } + } } + } + + /// Get the DPI property from `Xft.dpi`. + pub fn xft_dpi(&self) -> Option { + self.database.get_value("Xft.dpi", "").ok().flatten() + } +} - let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask; - unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) }; +/// Get the raw DPI factor for a monitor. +fn raw_dpi_for_monitor(crtc: &randr::GetCrtcInfoReply, monitor: &randr::GetOutputInfoReply) -> f64 { + calc_dpi_factor( + (crtc.width as _, crtc.height as _), + (monitor.mm_width as _, monitor.mm_height as _), + ) +} - Ok(event_offset) +pub fn calc_dpi_factor( + (width_px, height_px): (u32, u32), + (width_mm, height_mm): (u64, u64), +) -> f64 { + // See http://xpra.org/trac/ticket/728 for more information. + if width_mm == 0 || height_mm == 0 { + warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); + return 1.0; + } + + let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); + // Quantize 1/12 step size + let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); + assert!(crate::dpi::validate_scale_factor(dpi_factor)); + if dpi_factor <= 20. { + dpi_factor + } else { + 1. } } diff --git a/src/platform_impl/linux/x11/util/atom.rs b/src/platform_impl/linux/x11/util/atom.rs deleted file mode 100644 index 55ebeb609c7..00000000000 --- a/src/platform_impl/linux/x11/util/atom.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{CStr, CString}, - fmt::Debug, - os::raw::*, - sync::Mutex, -}; - -use once_cell::sync::Lazy; - -use super::*; - -type AtomCache = HashMap; - -static ATOM_CACHE: Lazy> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048))); - -impl XConnection { - pub fn get_atom + Debug>(&self, name: T) -> ffi::Atom { - let name = name.as_ref(); - let mut atom_cache_lock = ATOM_CACHE.lock().unwrap(); - let cached_atom = (*atom_cache_lock).get(name).cloned(); - if let Some(atom) = cached_atom { - atom - } else { - let atom = unsafe { - (self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False) - }; - if atom == 0 { - panic!( - "`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}", - name, - self.check_errors(), - ); - } - /*println!( - "XInternAtom name:{:?} atom:{:?}", - name, - atom, - );*/ - (*atom_cache_lock).insert(name.to_owned(), atom); - atom - } - } - - pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom { - debug_assert!(CStr::from_bytes_with_nul(name).is_ok()); - let name = CStr::from_bytes_with_nul_unchecked(name); - self.get_atom(name) - } - - // Note: this doesn't use caching, for the sake of simplicity. - // If you're dealing with this many atoms, you'll usually want to cache them locally anyway. - pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result, XError> { - let mut atoms = Vec::with_capacity(names.len()); - (self.xlib.XInternAtoms)( - self.display, - names.as_ptr() as *mut _, - names.len() as c_int, - ffi::False, - atoms.as_mut_ptr(), - ); - self.check_errors()?; - atoms.set_len(names.len()); - /*println!( - "XInternAtoms atoms:{:?}", - atoms, - );*/ - Ok(atoms) - } -} diff --git a/src/platform_impl/linux/x11/util/client_msg.rs b/src/platform_impl/linux/x11/util/client_msg.rs index 2f2b7edf9c4..eb75d13243b 100644 --- a/src/platform_impl/linux/x11/util/client_msg.rs +++ b/src/platform_impl/linux/x11/util/client_msg.rs @@ -1,46 +1,33 @@ use super::*; - -pub type ClientMsgPayload = [c_long; 5]; +use x11rb::protocol::xproto::{self, ClientMessageData, ConnectionExt}; impl XConnection { - pub fn send_event>( - &self, - target_window: c_ulong, - event_mask: Option, - event: T, - ) -> Flusher<'_> { - let event_mask = event_mask.unwrap_or(ffi::NoEventMask); - unsafe { - (self.xlib.XSendEvent)( - self.display, - target_window, - ffi::False, - event_mask, - &mut event.into(), - ); - } - Flusher::new(self) - } - pub fn send_client_msg( &self, - window: c_ulong, // The window this is "about"; not necessarily this window - target_window: c_ulong, // The window we're sending to - message_type: ffi::Atom, - event_mask: Option, - data: ClientMsgPayload, - ) -> Flusher<'_> { - let event = ffi::XClientMessageEvent { - type_: ffi::ClientMessage, - display: self.display, + window: xproto::Window, // The window this is "about"; not necessarily this window + target_window: xproto::Window, // The window we're sending to + message_type: xproto::Atom, + event_mask: Option, + format: u8, + data: impl Into, + ) -> Result, PlatformError> { + let event = xproto::ClientMessageEvent { + response_type: xproto::CLIENT_MESSAGE_EVENT, + sequence: 0xBEEF, // automatically assigned window, - message_type, - format: c_long::FORMAT as c_int, - data: unsafe { mem::transmute(data) }, - // These fields are ignored by `XSendEvent` - serial: 0, - send_event: 0, + type_: message_type, + format, + data: data.into(), }; - self.send_event(target_window, event_mask, event) + + // Send the event. + self.connection + .send_event( + false, + target_window, + event_mask.unwrap_or(xproto::EventMask::NO_EVENT), + event, + ) + .map_err(PlatformError::from) } } diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index 99b256356f9..dd58f49435b 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -1,9 +1,11 @@ use crate::window::CursorIcon; +use x11rb::protocol::xproto::{self, ConnectionExt as _}; + use super::*; impl XConnection { - pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option) { + pub fn set_cursor_icon(&self, window: xproto::Window, cursor: Option) { let cursor = *self .cursor_cache .lock() @@ -14,12 +16,11 @@ impl XConnection { self.update_cursor(window, cursor); } - fn create_empty_cursor(&self) -> ffi::Cursor { + fn create_empty_cursor(&self) -> xproto::Cursor { let data = 0; let pixmap = unsafe { - let screen = (self.xlib.XDefaultScreen)(self.display); - let window = (self.xlib.XRootWindow)(self.display, screen); - (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) + let window = self.default_screen().root; + (self.xlib.XCreateBitmapFromData)(self.display.as_ptr(), window as _, &data, 1, 1) }; if pixmap == 0 { @@ -31,7 +32,7 @@ impl XConnection { // in the pixmap which are not 0 in the mask. let mut dummy_color = MaybeUninit::uninit(); let cursor = (self.xlib.XCreatePixmapCursor)( - self.display, + self.display.as_ptr(), pixmap, pixmap, dummy_color.as_mut_ptr(), @@ -39,19 +40,22 @@ impl XConnection { 0, 0, ); - (self.xlib.XFreePixmap)(self.display, pixmap); + (self.xlib.XFreePixmap)(self.display.as_ptr(), pixmap); - cursor + cursor as _ } } - fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { + fn load_cursor(&self, name: &[u8]) -> xproto::Cursor { unsafe { - (self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char) + (self.xcursor.XcursorLibraryLoadCursor)( + self.display.as_ptr(), + name.as_ptr() as *const c_char, + ) as xproto::Cursor } } - fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { + fn load_first_existing_cursor(&self, names: &[&[u8]]) -> xproto::Cursor { for name in names.iter() { let xcursor = self.load_cursor(name); if xcursor != 0 { @@ -61,7 +65,7 @@ impl XConnection { 0 } - fn get_cursor(&self, cursor: Option) -> ffi::Cursor { + fn get_cursor(&self, cursor: Option) -> xproto::Cursor { let cursor = match cursor { Some(cursor) => cursor, None => return self.create_empty_cursor(), @@ -120,11 +124,13 @@ impl XConnection { } } - fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) { - unsafe { - (self.xlib.XDefineCursor)(self.display, window, cursor); - - self.flush_requests().expect("Failed to set the cursor"); - } + fn update_cursor(&self, window: xproto::Window, cursor: xproto::Cursor) { + self.connection + .change_window_attributes( + window, + &xproto::ChangeWindowAttributesAux::new().cursor(cursor), + ) + .check() + .expect("Failed to set the cursor"); } } diff --git a/src/platform_impl/linux/x11/util/format.rs b/src/platform_impl/linux/x11/util/format.rs deleted file mode 100644 index 997946b1eee..00000000000 --- a/src/platform_impl/linux/x11/util/format.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{fmt::Debug, mem, os::raw::*}; - -// This isn't actually the number of the bits in the format. -// X11 does a match on this value to determine which type to call sizeof on. -// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64. -// ...if that sounds confusing, then you know why this enum is here. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Format { - Char = 8, - Short = 16, - Long = 32, -} - -impl Format { - pub fn from_format(format: usize) -> Option { - match format { - 8 => Some(Format::Char), - 16 => Some(Format::Short), - 32 => Some(Format::Long), - _ => None, - } - } - - pub fn get_actual_size(&self) -> usize { - match self { - Format::Char => mem::size_of::(), - Format::Short => mem::size_of::(), - Format::Long => mem::size_of::(), - } - } -} - -pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd { - const FORMAT: Format; -} - -// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it. -impl Formattable for c_schar { - const FORMAT: Format = Format::Char; -} -impl Formattable for c_uchar { - const FORMAT: Format = Format::Char; -} -impl Formattable for c_short { - const FORMAT: Format = Format::Short; -} -impl Formattable for c_ushort { - const FORMAT: Format = Format::Short; -} -impl Formattable for c_long { - const FORMAT: Format = Format::Long; -} -impl Formattable for c_ulong { - const FORMAT: Format = Format::Long; -} diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index 7afced804a6..688377aaffa 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -1,6 +1,8 @@ use std::cmp; +use x11rb::protocol::xproto::{self, ConnectionExt as _}; use super::*; +use crate::platform_impl::x11::atoms::*; // Friendly neighborhood axis-aligned rectangle #[derive(Debug, Clone, PartialEq, Eq)] @@ -141,57 +143,10 @@ impl FrameExtentsHeuristic { } impl XConnection { - // This is adequate for inner_position - pub fn translate_coords( - &self, - window: ffi::Window, - root: ffi::Window, - ) -> Result { - let mut coords = TranslatedCoords::default(); - - unsafe { - (self.xlib.XTranslateCoordinates)( - self.display, - window, - root, - 0, - 0, - &mut coords.x_rel_root, - &mut coords.y_rel_root, - &mut coords.child, - ); - } - - self.check_errors()?; - Ok(coords) - } - - // This is adequate for inner_size - pub fn get_geometry(&self, window: ffi::Window) -> Result { - let mut geometry = Geometry::default(); - - let _status = unsafe { - (self.xlib.XGetGeometry)( - self.display, - window, - &mut geometry.root, - &mut geometry.x_rel_parent, - &mut geometry.y_rel_parent, - &mut geometry.width, - &mut geometry.height, - &mut geometry.border, - &mut geometry.depth, - ) - }; + fn get_frame_extents(&self, window: xproto::Window) -> Option { + let extents_atom = self.atoms[_NET_FRAME_EXTENTS]; - self.check_errors()?; - Ok(geometry) - } - - fn get_frame_extents(&self, window: ffi::Window) -> Option { - let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") }; - - if !hint_is_supported(extents_atom) { + if !self.hint_is_supported(extents_atom) { return None; } @@ -199,7 +154,7 @@ impl XConnection { // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to // be unsupported by many smaller WMs. let extents: Option> = self - .get_property(window, extents_atom, ffi::XA_CARDINAL) + .get_property(window, extents_atom, xproto::AtomEnum::CARDINAL.into()) .ok(); extents.and_then(|extents| { @@ -216,52 +171,29 @@ impl XConnection { }) } - pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option { - let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") }; + pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option { + let client_list_atom = self.atoms[_NET_CLIENT_LIST]; - if !hint_is_supported(client_list_atom) { + if !self.hint_is_supported(client_list_atom) { return None; } - let client_list: Option> = self - .get_property(root, client_list_atom, ffi::XA_WINDOW) + let client_list: Option> = self + .get_property(root, client_list_atom, xproto::AtomEnum::WINDOW.into()) .ok(); client_list.map(|client_list| client_list.contains(&window)) } - fn get_parent_window(&self, window: ffi::Window) -> Result { - let parent = unsafe { - let mut root = 0; - let mut parent = 0; - let mut children: *mut ffi::Window = ptr::null_mut(); - let mut nchildren = 0; - - // What's filled into `parent` if `window` is the root window? - let _status = (self.xlib.XQueryTree)( - self.display, - window, - &mut root, - &mut parent, - &mut children, - &mut nchildren, - ); - - // The list of children isn't used - if !children.is_null() { - (self.xlib.XFree)(children as *mut _); - } - - parent - }; - self.check_errors().map(|_| parent) + fn get_parent_window(&self, window: xproto::Window) -> Result { + Ok(self.connection.query_tree(window)?.reply()?.parent) } fn climb_hierarchy( &self, - window: ffi::Window, - root: ffi::Window, - ) -> Result { + window: xproto::Window, + root: xproto::Window, + ) -> Result { let mut outer_window = window; loop { let candidate = self.get_parent_window(outer_window)?; @@ -275,8 +207,8 @@ impl XConnection { pub fn get_frame_extents_heuristic( &self, - window: ffi::Window, - root: ffi::Window, + window: xproto::Window, + root: xproto::Window, ) -> FrameExtentsHeuristic { use self::FrameExtentsHeuristicPath::*; @@ -286,19 +218,25 @@ impl XConnection { // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self - .translate_coords(window, root) + .connection + .translate_coordinates(window, root, 0, 0) + .unwrap() + .reply() .expect("Failed to translate window coordinates"); - (coords.y_rel_root, coords.child) + (coords.dst_y, coords.child) }; let (width, height, border) = { let inner_geometry = self + .connection .get_geometry(window) + .unwrap() + .reply() .expect("Failed to get inner window geometry"); ( inner_geometry.width, inner_geometry.height, - inner_geometry.border, + inner_geometry.border_width, ) }; @@ -350,10 +288,13 @@ impl XConnection { .expect("Failed to climb window hierarchy"); let (outer_y, outer_width, outer_height) = { let outer_geometry = self + .connection .get_geometry(outer_window) + .unwrap() + .reply() .expect("Failed to get outer window geometry"); ( - outer_geometry.y_rel_parent, + outer_geometry.y, outer_geometry.width, outer_geometry.height, ) @@ -363,7 +304,7 @@ impl XConnection { // area, we can figure out what's in between. let diff_x = outer_width.saturating_sub(width); let diff_y = outer_height.saturating_sub(height); - let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint; + let offset_y = inner_y_rel_root.saturating_sub(outer_y) as _; let left = diff_x / 2; let right = left; diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index bc45b235849..fe09e5edb3c 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -1,6 +1,10 @@ -use std::slice; use std::sync::Arc; +use x11rb::protocol::xproto; + +use crate::platform_impl::x11::atoms::*; + +use super::memory::*; use super::*; #[derive(Debug)] @@ -30,36 +34,49 @@ pub enum WindowType { /// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying /// root window clicks. Desktop, + /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows. Dock, + /// Toolbar windows. "Torn off" from the main application. Toolbar, + /// Pinnable menu windows. "Torn off" from the main application. Menu, + /// A small persistent utility window, such as a palette or toolbox. Utility, + /// The window is a splash screen displayed as an application is starting up. Splash, + /// This is a dialog window. Dialog, + /// A dropdown menu that usually appears when the user clicks on an item in a menu bar. /// This property is typically used on override-redirect windows. DropdownMenu, + /// A popup menu that usually appears when the user right clicks on an object. /// This property is typically used on override-redirect windows. PopupMenu, + /// A tooltip window. Usually used to show additional information when hovering over an object with the cursor. /// This property is typically used on override-redirect windows. Tooltip, + /// The window is a notification. /// This property is typically used on override-redirect windows. Notification, + /// This should be used on the windows that are popped up by combo boxes. /// This property is typically used on override-redirect windows. Combo, + /// This indicates the the window is being dragged. /// This property is typically used on override-redirect windows. Dnd, + /// This is a normal, top-level window. Normal, } @@ -71,25 +88,25 @@ impl Default for WindowType { } impl WindowType { - pub(crate) fn as_atom(&self, xconn: &Arc) -> ffi::Atom { + pub(crate) fn as_atom(&self, xconn: &Arc) -> xproto::Atom { use self::WindowType::*; - let atom_name: &[u8] = match *self { - Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0", - Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0", - Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0", - Menu => b"_NET_WM_WINDOW_TYPE_MENU\0", - Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0", - Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0", - Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", - DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0", - PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0", - Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0", - Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0", - Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0", - Dnd => b"_NET_WM_WINDOW_TYPE_DND\0", - Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", + let atom_name = match *self { + Desktop => AtomType::_NET_WM_WINDOW_TYPE_DESKTOP, + Dock => AtomType::_NET_WM_WINDOW_TYPE_DOCK, + Toolbar => AtomType::_NET_WM_WINDOW_TYPE_TOOLBAR, + Menu => AtomType::_NET_WM_WINDOW_TYPE_MENU, + Utility => AtomType::_NET_WM_WINDOW_TYPE_UTILITY, + Splash => AtomType::_NET_WM_WINDOW_TYPE_SPLASH, + Dialog => AtomType::_NET_WM_WINDOW_TYPE_DIALOG, + DropdownMenu => AtomType::_NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + PopupMenu => AtomType::_NET_WM_WINDOW_TYPE_POPUP_MENU, + Tooltip => AtomType::_NET_WM_WINDOW_TYPE_TOOLTIP, + Notification => AtomType::_NET_WM_WINDOW_TYPE_NOTIFICATION, + Combo => AtomType::_NET_WM_WINDOW_TYPE_COMBO, + Dnd => AtomType::_NET_WM_WINDOW_TYPE_DND, + Normal => AtomType::_NET_WM_WINDOW_TYPE_NORMAL, }; - unsafe { xconn.get_atom_unchecked(atom_name) } + xconn.atoms[atom_name] } } @@ -98,6 +115,7 @@ pub struct MotifHints { } #[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct MwmHints { flags: c_ulong, functions: c_ulong, @@ -181,7 +199,7 @@ impl Default for MotifHints { impl MwmHints { fn as_slice(&self) -> &[c_ulong] { - unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) } + bytemuck::cast_slice::(std::slice::from_ref(self)) } } @@ -271,8 +289,8 @@ impl XConnection { pub fn get_wm_hints( &self, window: ffi::Window, - ) -> Result, XError> { - let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) }; + ) -> Result, PlatformError> { + let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display.as_ptr(), window) }; self.check_errors()?; let wm_hints = if wm_hints.is_null() { self.alloc_wm_hints() @@ -288,60 +306,73 @@ impl XConnection { wm_hints: XSmartPointer<'_, ffi::XWMHints>, ) -> Flusher<'_> { unsafe { - (self.xlib.XSetWMHints)(self.display, window, wm_hints.ptr); + (self.xlib.XSetWMHints)(self.display.as_ptr(), window, wm_hints.ptr); } Flusher::new(self) } - pub fn get_normal_hints(&self, window: ffi::Window) -> Result, XError> { + pub fn get_normal_hints( + &self, + window: xproto::Window, + ) -> Result, PlatformError> { let size_hints = self.alloc_size_hints(); let mut supplied_by_user = MaybeUninit::uninit(); unsafe { (self.xlib.XGetWMNormalHints)( - self.display, - window, + self.display.as_ptr(), + window as _, size_hints.ptr, supplied_by_user.as_mut_ptr(), ); } - self.check_errors().map(|_| NormalHints { size_hints }) + self.check_errors() + .map(|_| NormalHints { size_hints }) + .platform() } pub fn set_normal_hints( &self, - window: ffi::Window, + window: xproto::Window, normal_hints: NormalHints<'_>, ) -> Flusher<'_> { unsafe { - (self.xlib.XSetWMNormalHints)(self.display, window, normal_hints.size_hints.ptr); + (self.xlib.XSetWMNormalHints)( + self.display.as_ptr(), + window as _, + normal_hints.size_hints.ptr, + ); } Flusher::new(self) } - pub fn get_motif_hints(&self, window: ffi::Window) -> MotifHints { - let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; + pub fn get_motif_hints(&self, window: xproto::Window) -> Result { + let motif_hints = self.atoms[_MOTIF_WM_HINTS]; let mut hints = MotifHints::new(); - if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { - hints.hints.flags = props.first().cloned().unwrap_or(0); - hints.hints.functions = props.get(1).cloned().unwrap_or(0); - hints.hints.decorations = props.get(2).cloned().unwrap_or(0); - hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long; - hints.hints.status = props.get(4).cloned().unwrap_or(0); + if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { + hints.hints.flags = props.first().cloned().unwrap_or(0) as _; + hints.hints.functions = props.get(1).cloned().unwrap_or(0) as _; + hints.hints.decorations = props.get(2).cloned().unwrap_or(0) as _; + hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as _; + hints.hints.status = props.get(4).cloned().unwrap_or(0) as _; } - hints + Ok(hints) } - pub fn set_motif_hints(&self, window: ffi::Window, hints: &MotifHints) -> Flusher<'_> { - let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; + pub fn set_motif_hints( + &self, + window: xproto::Window, + hints: &MotifHints, + ) -> Result, PlatformError> { + let motif_hints = self.atoms[_MOTIF_WM_HINTS]; self.change_property( window, motif_hints, motif_hints, - PropMode::Replace, + xproto::PropMode::REPLACE, hints.hints.as_slice(), ) } diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index e9f45aee1c9..5492552b6f5 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,18 +1,13 @@ -use std::{slice, str}; - use super::*; use crate::event::ModifiersState; -pub const VIRTUAL_CORE_POINTER: c_int = 2; -pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; +use x11rb::protocol::xinput; -// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to -// re-allocate (and make another round-trip) in the *vast* majority of cases. -// To test if `lookup_utf8` works correctly, set this to 1. -const TEXT_BUFFER_SIZE: usize = 1024; +pub const VIRTUAL_CORE_POINTER: xinput::DeviceId = 2; +pub const VIRTUAL_CORE_KEYBOARD: xinput::DeviceId = 3; impl ModifiersState { - pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self { + pub(crate) fn from_x11(state: &xinput::ModifierInfo) -> Self { ModifiersState::from_x11_mask(state.effective as c_uint) } @@ -26,165 +21,15 @@ impl ModifiersState { } } -// NOTE: Some of these fields are not used, but may be of use in the future. -pub struct PointerState<'a> { - xconn: &'a XConnection, - pub root: ffi::Window, - pub child: ffi::Window, - pub root_x: c_double, - pub root_y: c_double, - pub win_x: c_double, - pub win_y: c_double, - buttons: ffi::XIButtonState, - modifiers: ffi::XIModifierState, - pub group: ffi::XIGroupState, - pub relative_to_window: bool, -} - -impl<'a> PointerState<'a> { - pub fn get_modifier_state(&self) -> ModifiersState { - ModifiersState::from_x11(&self.modifiers) - } -} - -impl<'a> Drop for PointerState<'a> { - fn drop(&mut self) { - if !self.buttons.mask.is_null() { - unsafe { - // This is why you need to read the docs carefully... - (self.xconn.xlib.XFree)(self.buttons.mask as _); - } - } - } -} - impl XConnection { - pub fn select_xinput_events( - &self, - window: c_ulong, - device_id: c_int, - mask: i32, - ) -> Flusher<'_> { - let mut event_mask = ffi::XIEventMask { - deviceid: device_id, - mask: &mask as *const _ as *mut c_uchar, - mask_len: mem::size_of_val(&mask) as c_int, - }; - unsafe { - (self.xinput2.XISelectEvents)( - self.display, - window, - &mut event_mask as *mut ffi::XIEventMask, - 1, // number of masks to read from pointer above - ); - } - Flusher::new(self) - } - #[allow(dead_code)] pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option> { - let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id, mask, mask) }; + let status = + unsafe { (self.xlib.XkbSelectEvents)(self.display.as_ptr(), device_id, mask, mask) }; if status == ffi::True { Some(Flusher::new(self)) } else { None } } - - pub fn query_pointer( - &self, - window: ffi::Window, - device_id: c_int, - ) -> Result, XError> { - unsafe { - let mut root = 0; - let mut child = 0; - let mut root_x = 0.0; - let mut root_y = 0.0; - let mut win_x = 0.0; - let mut win_y = 0.0; - let mut buttons = Default::default(); - let mut modifiers = Default::default(); - let mut group = Default::default(); - - let relative_to_window = (self.xinput2.XIQueryPointer)( - self.display, - device_id, - window, - &mut root, - &mut child, - &mut root_x, - &mut root_y, - &mut win_x, - &mut win_y, - &mut buttons, - &mut modifiers, - &mut group, - ) == ffi::True; - - self.check_errors()?; - - Ok(PointerState { - xconn: self, - root, - child, - root_x, - root_y, - win_x, - win_y, - buttons, - modifiers, - group, - relative_to_window, - }) - } - } - - fn lookup_utf8_inner( - &self, - ic: ffi::XIC, - key_event: &mut ffi::XKeyEvent, - buffer: *mut u8, - size: usize, - ) -> (ffi::KeySym, ffi::Status, c_int) { - let mut keysym: ffi::KeySym = 0; - let mut status: ffi::Status = 0; - let count = unsafe { - (self.xlib.Xutf8LookupString)( - ic, - key_event, - buffer as *mut c_char, - size as c_int, - &mut keysym, - &mut status, - ) - }; - (keysym, status, count) - } - - pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { - // `assume_init` is safe here because the array consists of `MaybeUninit` values, - // which do not require initialization. - let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = - unsafe { MaybeUninit::uninit().assume_init() }; - // If the buffer overflows, we'll make a new one on the heap. - let mut vec; - - let (_, status, count) = - self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); - - let bytes = if status == ffi::XBufferOverflow { - vec = Vec::with_capacity(count as usize); - let (_, _, new_count) = - self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); - debug_assert_eq!(count, new_count); - - unsafe { vec.set_len(count as usize) }; - &vec[..count as usize] - } else { - unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } - }; - - str::from_utf8(bytes).unwrap_or("").to_string() - } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs index e6b6e7a1479..db096a54c9f 100644 --- a/src/platform_impl/linux/x11/util/keys.rs +++ b/src/platform_impl/linux/x11/util/keys.rs @@ -1,4 +1,5 @@ use std::{iter::Enumerate, ptr, slice::Iter}; +use x11rb::protocol::xproto; use super::*; @@ -63,14 +64,17 @@ impl Iterator for KeymapIter<'_> { impl XConnection { pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym { - unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) } + unsafe { (self.xlib.XKeycodeToKeysym)(self.display.as_ptr(), keycode, 0) } } - pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym { + pub fn lookup_keysym(&self, xkev: &xproto::KeyPressEvent) -> ffi::KeySym { let mut keysym = 0; + // TODO: Reimplement this using libxcb. For now, just reconstruct the Xlib event. + let mut xp = self.convert_keyevent(xkev); + unsafe { - (self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); + (self.xlib.XLookupString)(&mut xp, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); } keysym @@ -80,11 +84,31 @@ impl XConnection { let mut keys = [0; 32]; unsafe { - (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); + (self.xlib.XQueryKeymap)(self.display.as_ptr(), keys.as_mut_ptr() as *mut c_char); } Keymap { keys } } + + pub fn convert_keyevent(&self, xkev: &xproto::KeyPressEvent) -> ffi::XKeyEvent { + ffi::XKeyEvent { + type_: (xkev.response_type & 0x7F) as _, + serial: xkev.sequence as _, + send_event: (xkev.response_type & 0x80 != 0) as _, + display: self.display.as_ptr(), + window: xkev.event as _, + root: xkev.root as _, + subwindow: xkev.child as _, + x_root: xkev.root_x as _, + y_root: xkev.root_y as _, + x: xkev.event_x as _, + y: xkev.event_y as _, + time: xkev.time as _, + state: xkev.state.into(), + keycode: xkev.detail as _, + same_screen: xkev.same_screen as _, + } + } } fn first_bit(b: u8) -> u8 { diff --git a/src/platform_impl/linux/x11/util/memory.rs b/src/platform_impl/linux/x11/util/memory.rs index 181fc6a97f0..12b249e01af 100644 --- a/src/platform_impl/linux/x11/util/memory.rs +++ b/src/platform_impl/linux/x11/util/memory.rs @@ -42,11 +42,6 @@ impl<'a, T> Drop for XSmartPointer<'a, T> { } impl XConnection { - pub fn alloc_class_hint(&self) -> XSmartPointer<'_, ffi::XClassHint> { - XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() }) - .expect("`XAllocClassHint` returned null; out of memory") - } - pub fn alloc_size_hints(&self) -> XSmartPointer<'_, ffi::XSizeHints> { XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() }) .expect("`XAllocSizeHints` returned null; out of memory") diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 9b9ec96d63b..e57bca2e998 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -1,10 +1,8 @@ // Welcome to the util module, where we try to keep you from shooting yourself in the foot. // *results may vary -mod atom; mod client_msg; mod cursor; -mod format; mod geometry; mod hint; mod icon; @@ -17,20 +15,54 @@ mod window_property; mod wm; pub use self::{ - atom::*, client_msg::*, format::*, geometry::*, hint::*, icon::*, input::*, randr::*, - window_property::*, wm::*, + client_msg::*, geometry::*, hint::*, icon::*, input::*, randr::*, window_property::*, wm::*, }; -pub(crate) use self::memory::*; - use std::{ mem::{self, MaybeUninit}, ops::BitAnd, os::raw::*, - ptr, }; -use super::{ffi, XConnection, XError}; +use super::{ffi, PlatformError, XConnection}; +use x11rb::{ + connection::Connection, cookie::VoidCookie, protocol::xproto::ConnectionExt, + xcb_ffi::XCBConnection, +}; + +pub(crate) type XcbVoidCookie<'a> = VoidCookie<'a, XCBConnection>; + +// Extension traits that make handling errors a little easier. + +pub(crate) trait PlErrorExt { + fn platform(self) -> Result; +} + +impl> PlErrorExt for Result { + fn platform(self) -> Result { + self.map_err(Into::into) + } +} + +pub(crate) trait XcbVoidCookieExt { + fn check_platform(self) -> Result<(), PlatformError>; +} + +impl XcbVoidCookieExt for XcbVoidCookie<'_> { + fn check_platform(self) -> Result<(), PlatformError> { + self.check().platform() + } +} + +pub(crate) trait VoidResultExt { + fn check(self) -> Result<(), PlatformError>; +} + +impl> VoidResultExt for Result, E> { + fn check(self) -> Result<(), PlatformError> { + self.platform().and_then(|cookie| cookie.check_platform()) + } +} pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); @@ -60,15 +92,10 @@ impl<'a> Flusher<'a> { } // "I want this request sent now!" - pub fn flush(self) -> Result<(), XError> { + pub fn flush(self) -> Result<(), PlatformError> { self.xconn.flush_requests() } - // "I want the response now too!" - pub fn sync(self) -> Result<(), XError> { - self.xconn.sync_with_server() - } - // "I'm aware that this request hasn't been sent, and I'm okay with waiting." pub fn queue(self) {} } @@ -83,17 +110,27 @@ impl XConnection { // 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally. // When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits. // All util functions that abstract an async function will return a `Flusher`. - pub fn flush_requests(&self) -> Result<(), XError> { - unsafe { (self.xlib.XFlush)(self.display) }; - //println!("XFlush"); - // This isn't necessarily a useful time to check for errors (since our request hasn't - // necessarily been processed yet) - self.check_errors() + pub fn flush_requests(&self) -> Result<(), PlatformError> { + // Flush the X11 connection. + self.connection.flush()?; + + // Also flush Xlib's output buffer. + unsafe { (self.xlib.XFlush)(self.display.as_ptr()) }; + + self.check_errors()?; + + Ok(()) } - pub fn sync_with_server(&self) -> Result<(), XError> { - unsafe { (self.xlib.XSync)(self.display, ffi::False) }; - //println!("XSync"); - self.check_errors() + pub fn sync_with_server(&self) -> Result<(), PlatformError> { + // Flush the X11 connection. + self.flush_requests()?; + + // Send and receive a request to sync with the server. + self.connection.get_input_focus()?.reply()?; + + self.check_errors()?; + + Ok(()) } } diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs index 7e317c1b246..3d64a7f49f8 100644 --- a/src/platform_impl/linux/x11/util/modifiers.rs +++ b/src/platform_impl/linux/x11/util/modifiers.rs @@ -1,9 +1,12 @@ -use std::{collections::HashMap, slice}; +use std::collections::HashMap; use super::*; +use x11rb::protocol::xproto::{self, ConnectionExt as _}; use crate::event::{ElementState, ModifiersState}; +type KeyCode = u8; + // Offsets within XModifierKeymap to each set of keycodes. // We are only interested in Shift, Control, Alt, and Logo. // @@ -15,7 +18,6 @@ const SHIFT_OFFSET: usize = 0; const CONTROL_OFFSET: usize = 2; const ALT_OFFSET: usize = 3; const LOGO_OFFSET: usize = 6; -const NUM_MODS: usize = 8; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Modifier { @@ -28,13 +30,13 @@ pub enum Modifier { #[derive(Debug, Default)] pub(crate) struct ModifierKeymap { // Maps keycodes to modifiers - keys: HashMap, + keys: HashMap, } #[derive(Clone, Debug, Default)] pub(crate) struct ModifierKeyState { // Contains currently pressed modifier keys and their corresponding modifiers - keys: HashMap, + keys: HashMap, state: ModifiersState, } @@ -43,30 +45,20 @@ impl ModifierKeymap { ModifierKeymap::default() } - pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { + pub fn get_modifier(&self, keycode: KeyCode) -> Option { self.keys.get(&keycode).cloned() } - pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { - unsafe { - let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); - - if keymap.is_null() { - panic!("failed to allocate XModifierKeymap"); - } - - self.reset_from_x_keymap(&*keymap); - - (xconn.xlib.XFreeModifiermap)(keymap); - } + pub fn reset_from_x_connection(&mut self, xconn: &XConnection) -> Result<(), PlatformError> { + let keymap = xconn.connection.get_modifier_mapping()?.reply()?; + self.reset_from_x_keymap(&keymap); + Ok(()) } - pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { - let keys_per_mod = keymap.max_keypermod as usize; + pub fn reset_from_x_keymap(&mut self, keymap: &xproto::GetModifierMappingReply) { + let keys_per_mod = keymap.keycodes_per_modifier() as usize; - let keys = unsafe { - slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) - }; + let keys = &keymap.keycodes; self.keys.clear(); diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 24f9f631559..00a0fa30958 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,216 +1,65 @@ -use std::{env, slice, str::FromStr}; - -use super::{ - ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, - *, -}; -use crate::platform_impl::platform::x11::monitor; -use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; - -/// Represents values of `WINIT_HIDPI_FACTOR`. -pub enum EnvVarDPI { - Randr, - Scale(f64), - NotSet, -} - -pub fn calc_dpi_factor( - (width_px, height_px): (u32, u32), - (width_mm, height_mm): (u64, u64), -) -> f64 { - // See http://xpra.org/trac/ticket/728 for more information. - if width_mm == 0 || height_mm == 0 { - warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); - return 1.0; - } - - let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); - // Quantize 1/12 step size - let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); - assert!(validate_scale_factor(dpi_factor)); - if dpi_factor <= 20. { - dpi_factor - } else { - 1. - } -} +use super::*; +use x11rb::protocol::randr::{self, ConnectionExt as _}; impl XConnection { - // Retrieve DPI from Xft.dpi property - pub unsafe fn get_xft_dpi(&self) -> Option { - (self.xlib.XrmInitialize)(); - let resource_manager_str = (self.xlib.XResourceManagerString)(self.display); - if resource_manager_str.is_null() { - return None; - } - if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() { - let name: &str = "Xft.dpi:\t"; - for pair in res.split('\n') { - if let Some(stripped) = pair.strip_prefix(name) { - return f64::from_str(stripped).ok(); - } - } - } - None - } - pub unsafe fn get_output_info( - &self, - resources: *mut XRRScreenResources, - crtc: *mut XRRCrtcInfo, - ) -> Option<(String, f64, Vec)> { - let output_info = - (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0)); - if output_info.is_null() { - // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) - // it's possible for it to return null. - // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596 - let _ = self.check_errors(); // discard `BadRROutput` error - return None; - } - - let screen = (self.xlib.XDefaultScreen)(self.display); - let bit_depth = (self.xlib.XDefaultDepth)(self.display, screen); - - let output_modes = - slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize); - let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize); - - let modes = resource_modes - .iter() - // XRROutputInfo contains an array of mode ids that correspond to - // modes in the array in XRRScreenResources - .filter(|x| output_modes.iter().any(|id| x.id == *id)) - .map(|mode| { - VideoMode { - size: (mode.width, mode.height), - refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) - .unwrap_or(0), - bit_depth: bit_depth as u16, - native_mode: mode.id, - // This is populated in `MonitorHandle::video_modes` as the - // video mode is returned to the user - monitor: None, - } - }) - .collect(); - - let name_slice = slice::from_raw_parts( - (*output_info).name as *mut u8, - (*output_info).nameLen as usize, - ); - let name = String::from_utf8_lossy(name_slice).into(); - // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set - let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); - if deprecated_dpi_override.is_some() { - warn!( - "The WINIT_HIDPI_FACTOR environment variable is deprecated; use WINIT_X11_SCALE_FACTOR" - ) - } - let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else( - || EnvVarDPI::NotSet, - |var| { - if var.to_lowercase() == "randr" { - EnvVarDPI::Randr - } else if let Ok(dpi) = f64::from_str(&var) { - EnvVarDPI::Scale(dpi) - } else if var.is_empty() { - EnvVarDPI::NotSet - } else { - panic!( - "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", - var - ); - } - }, - ); - - let scale_factor = match dpi_env { - EnvVarDPI::Randr => calc_dpi_factor( - ((*crtc).width, (*crtc).height), - ((*output_info).mm_width as _, (*output_info).mm_height as _), - ), - EnvVarDPI::Scale(dpi_override) => { - if !validate_scale_factor(dpi_override) { - panic!( - "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", - dpi_override, - ); - } - dpi_override - } - EnvVarDPI::NotSet => { - if let Some(dpi) = self.get_xft_dpi() { - dpi / 96. - } else { - calc_dpi_factor( - ((*crtc).width, (*crtc).height), - ((*output_info).mm_width as _, (*output_info).mm_height as _), - ) - } - } + pub fn set_crtc_config(&self, crtc_id: u32, mode_id: randr::Mode) -> Result<(), PlatformError> { + let root = self.default_screen().root; + let version = self.connection.randr_query_version(0, 0)?.reply()?; + let timestamp = if version.major_version == 1 && version.minor_version >= 3 { + self.connection + .randr_get_screen_resources_current(root)? + .reply()? + .timestamp + } else { + self.connection + .randr_get_screen_resources(root)? + .reply()? + .timestamp }; - (self.xrandr.XRRFreeOutputInfo)(output_info); - Some((name, scale_factor, modes)) - } - - #[must_use] - pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; + let crtc = self + .connection + .randr_get_crtc_info(crtc_id, timestamp)? + .reply()?; - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let status = (self.xrandr.XRRSetCrtcConfig)( - self.display, - resources, + self.connection + .randr_set_crtc_config( crtc_id, - CurrentTime, - (*crtc).x, - (*crtc).y, + timestamp, + x11rb::CURRENT_TIME, + crtc.x, + crtc.y, mode_id, - (*crtc).rotation, - (*crtc).outputs.offset(0), - 1, - ); - - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); + crtc.rotation, + &crtc.outputs, + )? + .reply()?; - if status == Success as i32 { - Some(()) - } else { - None - } - } + Ok(()) } - pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; + pub fn get_crtc_mode(&self, crtc_id: u32) -> Result { + let root = self.default_screen().root; + let version = self.connection.randr_query_version(0, 0)?.reply()?; + + // Get the timestamp to use. + let timestamp = if version.major_version == 1 && version.minor_version >= 3 { + self.connection + .randr_get_screen_resources_current(root)? + .reply()? + .timestamp + } else { + self.connection + .randr_get_screen_resources(root)? + .reply()? + .timestamp + }; - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let mode = (*crtc).mode; - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); - mode - } + // Fetch the CRTC version. + let crtc = self + .connection + .randr_get_crtc_info(crtc_id, timestamp)? + .reply()?; + Ok(crtc.mode) } } diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index e93d5bb072c..b31d7778866 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -1,18 +1,39 @@ use super::*; +use std::sync::Arc; +use x11rb::errors::{ConnectionError, ReplyError}; +use x11rb::protocol::xproto::{self, ConnectionExt as _}; + pub type Cardinal = c_long; pub const CARDINAL_SIZE: usize = mem::size_of::(); #[derive(Debug, Clone)] -pub enum GetPropertyError { - XError(XError), - TypeMismatch(ffi::Atom), - FormatMismatch(c_int), - NothingAllocated, +pub(crate) enum GetPropertyError { + XError(Arc), + TypeMismatch(xproto::Atom), + FormatMismatch(u8), +} + +impl From for GetPropertyError { + fn from(value: PlatformError) -> Self { + GetPropertyError::XError(Arc::new(value)) + } +} + +impl From for GetPropertyError { + fn from(value: ConnectionError) -> Self { + PlatformError::from(value).into() + } +} + +impl From for GetPropertyError { + fn from(value: ReplyError) -> Self { + PlatformError::from(value).into() + } } impl GetPropertyError { - pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool { + pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool { if let GetPropertyError::TypeMismatch(actual_type) = *self { actual_type == t } else { @@ -23,120 +44,102 @@ impl GetPropertyError { // Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. // To test if `get_property` works correctly, set this to 1. -const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone! - -#[derive(Debug)] -#[allow(dead_code)] -pub enum PropMode { - Replace = ffi::PropModeReplace as isize, - Prepend = ffi::PropModePrepend as isize, - Append = ffi::PropModeAppend as isize, -} +const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone! impl XConnection { - pub fn get_property( + pub fn get_property( &self, - window: c_ulong, - property: ffi::Atom, - property_type: ffi::Atom, + window: xproto::Window, + property: xproto::Atom, + property_type: xproto::Atom, ) -> Result, GetPropertyError> { let mut data = Vec::new(); let mut offset = 0; - let mut done = false; - let mut actual_type = 0; - let mut actual_format = 0; - let mut quantity_returned = 0; - let mut bytes_after = 0; - let mut buf: *mut c_uchar = ptr::null_mut(); - - while !done { - unsafe { - (self.xlib.XGetWindowProperty)( - self.display, + loop { + // Fetch the next chunk of data. + let property_reply = self + .connection + .get_property( + false, window, property, - // This offset is in terms of 32-bit chunks. + property_type, offset, - // This is the quantity of 32-bit chunks to receive at once. PROPERTY_BUFFER_SIZE, - ffi::False, - property_type, - &mut actual_type, - &mut actual_format, - // This is the quantity of items we retrieved in our format, NOT of 32-bit chunks! - &mut quantity_returned, - // ...and this is a quantity of bytes. So, this function deals in 3 different units. - &mut bytes_after, - &mut buf, - ); - - if let Err(e) = self.check_errors() { - return Err(GetPropertyError::XError(e)); - } - - if actual_type != property_type { - return Err(GetPropertyError::TypeMismatch(actual_type)); - } - - let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT); - if format_mismatch { - return Err(GetPropertyError::FormatMismatch(actual_format)); - } - - if !buf.is_null() { - offset += PROPERTY_BUFFER_SIZE; - let new_data = - std::slice::from_raw_parts(buf as *mut T, quantity_returned as usize); - /*println!( - "XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}", - property, - mem::size_of::() * 8, - data.len(), - offset, - quantity_returned, - new_data, - );*/ - data.extend_from_slice(new_data); - // Fun fact: XGetWindowProperty allocates one extra byte at the end. - (self.xlib.XFree)(buf as _); // Don't try to access new_data after this. - } else { - return Err(GetPropertyError::NothingAllocated); - } - - done = bytes_after == 0; + )? + .reply()?; + + // Ensure that the property type matches. + if property_reply.type_ != property_type { + return Err(GetPropertyError::TypeMismatch(property_reply.type_)); } - } - Ok(data) + // Ensure that the format is right. + if property_reply.format as usize != mem::size_of::() * 8 { + return Err(GetPropertyError::FormatMismatch(property_reply.format)); + } + + // Append the data to the output. + let bytes_after = property_reply.bytes_after; + append_byte_vector(&mut data, property_reply.value); + + // If there is no more data, we're done. + if bytes_after == 0 { + return Ok(data); + } + + // Add to the offset and go again. + offset += PROPERTY_BUFFER_SIZE; + } } - pub fn change_property<'a, T: Formattable>( + pub fn change_property<'a, T: bytemuck::NoUninit>( &'a self, - window: c_ulong, - property: ffi::Atom, - property_type: ffi::Atom, - mode: PropMode, + window: xproto::Window, + property: xproto::Atom, + property_type: xproto::Atom, + mode: xproto::PropMode, new_value: &[T], - ) -> Flusher<'a> { - debug_assert_eq!(mem::size_of::(), T::FORMAT.get_actual_size()); - unsafe { - (self.xlib.XChangeProperty)( - self.display, - window, - property, - property_type, - T::FORMAT as c_int, - mode as c_int, - new_value.as_ptr() as *const c_uchar, - new_value.len() as c_int, - ); - } - /*println!( - "XChangeProperty prop:{:?} val:{:?}", + ) -> Result, PlatformError> { + // Preform the property change. + let cookie = self.connection.change_property( + mode, + window, property, - new_value, - );*/ - Flusher::new(self) + property_type, + (mem::size_of::() * 8) as u8, + new_value.len() as _, + bytemuck::cast_slice::(new_value), + )?; + + Ok(cookie) + } +} + +/// Append a byte vector to a vector of real elements. +fn append_byte_vector(real: &mut Vec, bytes: Vec) { + // If the type is equivalent to a byte, this cast will succeed no matter what. + if mem::size_of::() == 1 && mem::align_of::() == 1 { + let mut bytes_casted = bytemuck::allocation::cast_vec::(bytes); + real.append(&mut bytes_casted); + return; } + + // Add enough buffer space to hold the new data. + debug_assert!( + bytes.len() % mem::size_of::() == 0, + "Byte vector is not a multiple of the element size" + ); + let additional_space = bytes.len() / mem::size_of::(); + let new_len = real.len() + additional_space; + let former_len = real.len(); + real.resize(new_len, T::zeroed()); + + // Get a handle to the new space in the vector. + let new_space = &mut real[former_len..]; + + // Copy the data into the new space. + let new_bytes = bytemuck::cast_slice_mut::(new_space); + new_bytes.copy_from_slice(&bytes); } diff --git a/src/platform_impl/linux/x11/util/wm.rs b/src/platform_impl/linux/x11/util/wm.rs index 03890facc8d..f1307b5aed4 100644 --- a/src/platform_impl/linux/x11/util/wm.rs +++ b/src/platform_impl/linux/x11/util/wm.rs @@ -1,8 +1,9 @@ -use std::sync::Mutex; +use std::sync::Arc; -use once_cell::sync::Lazy; +use x11rb::protocol::xproto::{self, ConnectionExt as _}; use super::*; +use crate::platform_impl::x11::atoms::*; // https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248 pub const MOVERESIZE_TOPLEFT: isize = 0; @@ -15,38 +16,46 @@ pub const MOVERESIZE_BOTTOMLEFT: isize = 6; pub const MOVERESIZE_LEFT: isize = 7; pub const MOVERESIZE_MOVE: isize = 8; -// This info is global to the window manager. -static SUPPORTED_HINTS: Lazy>> = - Lazy::new(|| Mutex::new(Vec::with_capacity(0))); -static WM_NAME: Lazy>> = Lazy::new(|| Mutex::new(None)); - -pub fn hint_is_supported(hint: ffi::Atom) -> bool { - (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint) -} +impl XConnection { + pub fn hint_is_supported(&self, hint: xproto::Atom) -> bool { + self.supported_hints.lock().unwrap().contains(&hint) + } -pub fn wm_name_is_one_of(names: &[&str]) -> bool { - if let Some(ref name) = *WM_NAME.lock().unwrap() { - names.contains(&name.as_str()) - } else { - false + pub fn wm_name_is_one_of(&self, names: &[&str]) -> bool { + if let Some(ref name) = *self.wm_name.lock().unwrap() { + names.contains(&name.as_str()) + } else { + false + } } -} -impl XConnection { - pub fn update_cached_wm_info(&self, root: ffi::Window) { - *SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root); - *WM_NAME.lock().unwrap() = self.get_wm_name(root); + pub fn update_cached_wm_info(&self, root: xproto::Window) -> Result<(), PlatformError> { + *self.supported_hints.lock().unwrap() = self.get_supported_hints(root)?; + *self.wm_name.lock().unwrap() = self.get_wm_name(root)?; + + Ok(()) } - fn get_supported_hints(&self, root: ffi::Window) -> Vec { - let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") }; - self.get_property(root, supported_atom, ffi::XA_ATOM) - .unwrap_or_else(|_| Vec::with_capacity(0)) + fn get_supported_hints( + &self, + root: xproto::Window, + ) -> Result, PlatformError> { + match self.get_property( + root, + self.atoms[_NET_SUPPORTED], + xproto::AtomEnum::ATOM.into(), + ) { + Ok(prop) => Ok(prop), + Err(GetPropertyError::XError(e)) => Err(Arc::try_unwrap(e).unwrap()), + _ => Ok(Vec::new()), + } } - fn get_wm_name(&self, root: ffi::Window) -> Option { - let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") }; - let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") }; + fn get_wm_name(&self, root: xproto::Window) -> Result, PlatformError> { + let (check_atom, wm_name_atom) = ( + self.atoms[_NET_SUPPORTING_WM_CHECK], + self.atoms[_NET_WM_NAME], + ); // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite // it working and being supported. This has been reported upstream, but due to the @@ -70,31 +79,45 @@ impl XConnection { // Querying this property on the root window will give us the ID of a child window created by // the WM. let root_window_wm_check = { - let result = self.get_property(root, check_atom, ffi::XA_WINDOW); + let result = self.get_property(root, check_atom, xproto::AtomEnum::WINDOW.into()); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); - wm_check? + match wm_check { + Some(wm_check) => wm_check, + None => return Ok(None), + } }; // Querying the same property on the child window we were given, we should get this child // window's ID again. let child_window_wm_check = { - let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW); + let result = self.get_property( + root_window_wm_check, + check_atom, + xproto::AtomEnum::WINDOW.into(), + ); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); - wm_check? + match wm_check { + Some(wm_check) => wm_check, + None => return Ok(None), + } }; // These values should be the same. if root_window_wm_check != child_window_wm_check { - return None; + return Ok(None); } // All of that work gives us a window ID that we can get the WM name from. let wm_name = { - let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") }; + let utf8_string_atom = self + .connection + .intern_atom(false, b"UTF8_STRING")? + .reply()? + .atom; let result = self.get_property(root_window_wm_check, wm_name_atom, utf8_string_atom); @@ -105,19 +128,23 @@ impl XConnection { // The unofficial 1.4 fork of IceWM still includes the extra details, but properly // returns a UTF8 string that isn't null-terminated. let no_utf8 = if let Err(ref err) = result { - err.is_actual_property_type(ffi::XA_STRING) + err.is_actual_property_type(xproto::AtomEnum::STRING.into()) } else { false }; if no_utf8 { - self.get_property(root_window_wm_check, wm_name_atom, ffi::XA_STRING) + self.get_property( + root_window_wm_check, + wm_name_atom, + xproto::AtomEnum::STRING.into(), + ) } else { result } } .ok(); - wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok()) + Ok(wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())) } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f0a8bf46853..71713a31979 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,7 +1,6 @@ use std::{ cmp, env, - ffi::CString, - mem::{self, replace, MaybeUninit}, + mem::{replace, MaybeUninit}, os::raw::*, path::Path, ptr, slice, @@ -10,15 +9,22 @@ use std::{ use libc; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle}; -use x11_dl::xlib::{TrueColor, XID}; +use x11rb::{ + connection::Connection, + protocol::{ + randr, + xinput::{self, ConnectionExt as _}, + xkb::{self, ConnectionExt as _}, + xproto::{self, ConnectionExt as _}, + }, +}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, platform_impl::{ - x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, - Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, - PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, + x11::MonitorHandle as X11MonitorHandle, Fullscreen, MonitorHandle as PlatformMonitorHandle, + OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, window::{ CursorGrabMode, CursorIcon, Icon, ResizeDirection, Theme, UserAttentionType, @@ -27,9 +33,12 @@ use crate::{ }; use super::{ - ffi, util, EventLoopWindowTarget, ImeRequest, ImeSender, WakeSender, WindowId, XConnection, - XError, + atoms::*, + ffi, + util::{self, PlErrorExt, VoidResultExt, XcbVoidCookie}, + EventLoopWindowTarget, PlatformError, WakeSender, WindowId, XConnection, }; +use std::option::Option::None; #[derive(Debug)] pub struct SharedState { @@ -48,7 +57,7 @@ pub struct SharedState { // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, // Used to restore video mode after exiting fullscreen - pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>, + pub desktop_video_mode: Option<(u32, randr::Mode)>, pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, @@ -103,14 +112,13 @@ unsafe impl Sync for UnownedWindow {} pub(crate) struct UnownedWindow { pub(crate) xconn: Arc, // never changes - xwindow: ffi::Window, // never changes - root: ffi::Window, // never changes - screen_id: i32, // never changes + xwindow: xproto::Window, // never changes + root: xproto::Window, // never changes + screen_id: usize, // never changes cursor: Mutex, cursor_grabbed_mode: Mutex, #[allow(clippy::mutex_atomic)] cursor_visible: Mutex, - ime_sender: Mutex, pub shared_state: Mutex, redraw_sender: WakeSender, } @@ -121,10 +129,19 @@ impl UnownedWindow { window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { + macro_rules! unwrap_os { + ($e:expr) => {{ + match $e { + Ok(v) => v, + Err(e) => return Err(os_error!(OsError::from(PlatformError::from(e)))), + } + }}; + } + let xconn = &event_loop.xconn; let root = match window_attrs.parent_window { - Some(RawWindowHandle::Xlib(handle)) => handle.window, - Some(RawWindowHandle::Xcb(handle)) => handle.window as XID, + Some(RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window, + Some(RawWindowHandle::Xcb(handle)) => handle.window, Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"), None => event_loop.root, }; @@ -134,7 +151,10 @@ impl UnownedWindow { X11MonitorHandle::dummy() } else { xconn - .query_pointer(root, util::VIRTUAL_CORE_POINTER) + .connection + .xinput_xi_query_pointer(root, util::VIRTUAL_CORE_POINTER) + .platform() + .and_then(|r| r.reply().platform()) .ok() .and_then(|pointer_state| { let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64); @@ -189,92 +209,103 @@ impl UnownedWindow { }; let screen_id = match pl_attribs.screen_id { - Some(id) => id, - None => unsafe { (xconn.xlib.XDefaultScreen)(xconn.display) }, + Some(id) => id as _, + None => xconn.default_screen, }; // creating - let (visual, depth, require_colormap) = match pl_attribs.visual_infos { - Some(vi) => (vi.visual, vi.depth, false), + let (visual_id, depth, require_colormap) = match pl_attribs.visual_infos { + Some(vi) => (vi.visualid as _, vi.depth as u8, false), None if window_attrs.transparent => { - // Find a suitable visual - let mut vinfo = MaybeUninit::uninit(); - let vinfo_initialized = unsafe { - (xconn.xlib.XMatchVisualInfo)( - xconn.display, - screen_id, - 32, - TrueColor, - vinfo.as_mut_ptr(), - ) != 0 - }; - if vinfo_initialized { - let vinfo = unsafe { vinfo.assume_init() }; - (vinfo.visual, vinfo.depth, true) + // Find a suitable visual. + let desired_root = &xconn.connection.setup().roots[screen_id]; + let desired_visual = desired_root + .allowed_depths + .iter() + .filter(|depth| depth.depth == 32) + .flat_map(|depth| depth.visuals.iter().map(move |visual| (depth, visual))) + .find(|(_, visual)| visual.class == xproto::VisualClass::TRUE_COLOR); + + if let Some((depth, visual)) = desired_visual { + (visual.visual_id, depth.depth, true) } else { debug!("Could not set transparency, because XMatchVisualInfo returned zero for the required parameters"); - ( - ffi::CopyFromParent as *mut ffi::Visual, - ffi::CopyFromParent, - false, - ) + (x11rb::COPY_FROM_PARENT, x11rb::COPY_FROM_PARENT as _, false) } } - _ => ( - ffi::CopyFromParent as *mut ffi::Visual, - ffi::CopyFromParent, - false, - ), + _ => (x11rb::COPY_FROM_PARENT, x11rb::COPY_FROM_PARENT as _, false), }; - let mut set_win_attr = { - let mut swa: ffi::XSetWindowAttributes = unsafe { mem::zeroed() }; - swa.colormap = if let Some(vi) = pl_attribs.visual_infos { - unsafe { - let visual = vi.visual; - (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) + let set_win_attr = { + let mut swa = xproto::CreateWindowAux::new(); + swa = swa.colormap({ + // See if we should use a direct visual or a colormap. + let mut visual = pl_attribs.visual_infos.map(|vi| vi.visualid as _); + + if require_colormap { + visual = visual.or(Some(visual_id)); } - } else if require_colormap { - unsafe { (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) } - } else { - 0 - }; - swa.event_mask = ffi::ExposureMask - | ffi::StructureNotifyMask - | ffi::VisibilityChangeMask - | ffi::KeyPressMask - | ffi::KeyReleaseMask - | ffi::KeymapStateMask - | ffi::ButtonPressMask - | ffi::ButtonReleaseMask - | ffi::PointerMotionMask; - swa.border_pixel = 0; - swa.override_redirect = pl_attribs.override_redirect as c_int; + + if let Some(visual_id) = visual { + let result = xconn.connection.generate_id().platform().and_then(|id| { + // Create a colormap. + xconn + .connection + .create_colormap(xproto::ColormapAlloc::NONE, id, root, visual_id) + .platform() + .map(|tok| { + tok.ignore_error(); + id + }) + }); + + unwrap_os!(result) + } else { + 0 + } + }); + + swa = swa.event_mask( + xproto::EventMask::EXPOSURE + | xproto::EventMask::STRUCTURE_NOTIFY + | xproto::EventMask::PROPERTY_CHANGE + | xproto::EventMask::KEY_PRESS + | xproto::EventMask::KEY_RELEASE + | xproto::EventMask::KEYMAP_STATE + | xproto::EventMask::BUTTON_PRESS + | xproto::EventMask::BUTTON_RELEASE + | xproto::EventMask::POINTER_MOTION, + ); + swa = swa.border_pixel(0); + swa = swa.override_redirect(u32::from(pl_attribs.override_redirect)); swa }; - let mut window_attributes = ffi::CWBorderPixel | ffi::CWColormap | ffi::CWEventMask; - - if pl_attribs.override_redirect { - window_attributes |= ffi::CWOverrideRedirect; - } - - // finally creating the window - let xwindow = unsafe { - (xconn.xlib.XCreateWindow)( - xconn.display, - root, - position.map_or(0, |p: PhysicalPosition| p.x as c_int), - position.map_or(0, |p: PhysicalPosition| p.y as c_int), - dimensions.0 as c_uint, - dimensions.1 as c_uint, - 0, - depth, - ffi::InputOutput as c_uint, - visual, - window_attributes, - &mut set_win_attr, - ) + // Create the window. + let xwindow = match xconn.connection.generate_id().platform().and_then(|wid| { + xconn + .connection + .create_window( + depth, + wid, + root, + position.map_or(0, |p: PhysicalPosition| p.x as _), + position.map_or(0, |p: PhysicalPosition| p.y as _), + dimensions.0 as _, + dimensions.1 as _, + 0, + xproto::WindowClass::INPUT_OUTPUT, + visual_id, + &set_win_attr, + ) + .platform() + .map(|tok| { + tok.ignore_error(); + wid + }) + }) { + Ok(wid) => wid, + Err(err) => return Err(os_error!(err.into())), }; #[allow(clippy::mutex_atomic)] @@ -286,7 +317,6 @@ impl UnownedWindow { cursor: Default::default(), cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), cursor_visible: Mutex::new(true), - ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, &window_attrs), redraw_sender: WakeSender { waker: event_loop.redraw_sender.waker.clone(), @@ -297,40 +327,34 @@ impl UnownedWindow { // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window // title to determine placement/etc., so doing this after mapping would cause the WM to // act on the wrong title state. - window.set_title_inner(&window_attrs.title).queue(); - window - .set_decorations_inner(window_attrs.decorations) - .queue(); + unwrap_os!(window.set_title_inner(&window_attrs.title)).ignore_error(); + unwrap_os!(window.set_decorations_inner(window_attrs.decorations)).ignore_error(); if let Some(theme) = window_attrs.preferred_theme { - window.set_theme_inner(Some(theme)).queue(); + unwrap_os!(window.set_theme_inner(Some(theme))).ignore_error(); } { // Enable drag and drop (TODO: extend API to make this toggleable) - unsafe { - let dnd_aware_atom = xconn.get_atom_unchecked(b"XdndAware\0"); + { + let dnd_aware_atom = xconn.atoms[XdndAware]; let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 - xconn.change_property( + unwrap_os!(xconn.change_property( window.xwindow, dnd_aware_atom, - ffi::XA_ATOM, - util::PropMode::Replace, + xproto::AtomEnum::ATOM.into(), + xproto::PropMode::REPLACE, version, - ) + )) + .ignore_error(); } - .queue(); // WM_CLASS must be set *before* mapping the window, as per ICCCM! { let (class, instance) = if let Some(name) = pl_attribs.name { - let instance = CString::new(name.instance.as_str()) - .expect("`WM_CLASS` instance contained null byte"); - let class = CString::new(name.general.as_str()) - .expect("`WM_CLASS` class contained null byte"); - (instance, class) + (name.instance, name.general) } else { - let class = env::args() + let class = env::args_os() .next() .as_ref() // Default to the name of the binary (via argv[0]) @@ -338,31 +362,33 @@ impl UnownedWindow { .and_then(|bin_name| bin_name.to_str()) .map(|bin_name| bin_name.to_owned()) .or_else(|| Some(window_attrs.title.clone())) - .and_then(|string| CString::new(string.as_str()).ok()) - .expect("Default `WM_CLASS` class contained null byte"); + .unwrap(); // This environment variable is extraordinarily unlikely to actually be used... let instance = env::var("RESOURCE_NAME") .ok() - .and_then(|instance| CString::new(instance.as_str()).ok()) .or_else(|| Some(class.clone())) - .expect("Default `WM_CLASS` instance contained null byte"); + .unwrap(); (instance, class) }; - let mut class_hint = xconn.alloc_class_hint(); - class_hint.res_name = class.as_ptr() as *mut c_char; - class_hint.res_class = instance.as_ptr() as *mut c_char; + // Create the class hint and set it. + let class_hint = format!("{}\0{}\0", class, instance); - unsafe { - (xconn.xlib.XSetClassHint)(xconn.display, window.xwindow, class_hint.ptr); - } //.queue(); + unwrap_os!(xconn.change_property( + window.xwindow, + xproto::AtomEnum::WM_CLASS.into(), + xproto::AtomEnum::ATOM.into(), + xproto::PropMode::REPLACE, + class_hint.as_bytes(), + )) + .ignore_error(); } - if let Some(flusher) = window.set_pid() { - flusher.queue() + if let Some(flusher) = unwrap_os!(window.set_pid()) { + flusher.ignore_error(); } - window.set_window_types(pl_attribs.x11_window_types).queue(); + unwrap_os!(window.set_window_types(pl_attribs.x11_window_types)).ignore_error(); // set size hints { @@ -374,7 +400,7 @@ impl UnownedWindow { .map(|size| size.to_physical::(scale_factor)); if !window_attrs.resizable { - if util::wm_name_is_one_of(&["Xfwm4"]) { + if xconn.wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); } else { max_inner_size = Some(dimensions.into()); @@ -408,83 +434,95 @@ impl UnownedWindow { // Set window icons if let Some(icon) = window_attrs.window_icon { - window.set_icon_inner(icon).queue(); + unwrap_os!(window.set_icon_inner(icon)).ignore_error(); } // Opt into handling window close - unsafe { - (xconn.xlib.XSetWMProtocols)( - xconn.display, + { + unwrap_os!(xconn.change_property( window.xwindow, - &[event_loop.wm_delete_window, event_loop.net_wm_ping] as *const ffi::Atom - as *mut ffi::Atom, - 2, - ); - } //.queue(); + xconn.atoms[WM_PROTOCOLS], + xproto::AtomEnum::ATOM.into(), + xproto::PropMode::REPLACE, + &[xconn.atoms[WM_DELETE_WINDOW], xconn.atoms[_NET_WM_PING]], + )) + .ignore_error(); + } // Set visibility (map window) if window_attrs.visible { - unsafe { - (xconn.xlib.XMapRaised)(xconn.display, window.xwindow); - } //.queue(); + unwrap_os!(window.map_raised()).ignore_error(); } // Attempt to make keyboard input repeat detectable - unsafe { - let mut supported_ptr = ffi::False; - (xconn.xlib.XkbSetDetectableAutoRepeat)( - xconn.display, - ffi::True, - &mut supported_ptr, - ); - if supported_ptr == ffi::False { + { + let pcf = unwrap_os!(unwrap_os!(xconn.connection.xkb_per_client_flags( + xkb::ID::USE_CORE_KBD.into(), + xkb::PerClientFlag::DETECTABLE_AUTO_REPEAT, + xkb::PerClientFlag::DETECTABLE_AUTO_REPEAT, + xkb::BoolCtrl::from(0u32), + xkb::BoolCtrl::from(0u32), + xkb::BoolCtrl::from(0u32), + )) + .reply()); + + if u32::from(pcf.supported) & u32::from(xkb::PerClientFlag::DETECTABLE_AUTO_REPEAT) + == 0 + { return Err(os_error!(OsError::XMisc( - "`XkbSetDetectableAutoRepeat` failed" + "`XkbSetDetectableAutoRepeat` not supported" ))); } } // Select XInput2 events - let mask = ffi::XI_MotionMask - | ffi::XI_ButtonPressMask - | ffi::XI_ButtonReleaseMask - //| ffi::XI_KeyPressMask - //| ffi::XI_KeyReleaseMask - | ffi::XI_EnterMask - | ffi::XI_LeaveMask - | ffi::XI_FocusInMask - | ffi::XI_FocusOutMask - | ffi::XI_TouchBeginMask - | ffi::XI_TouchUpdateMask - | ffi::XI_TouchEndMask; - xconn - .select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask) - .queue(); - - { + let mask = xinput::XIEventMask::MOTION + | xinput::XIEventMask::BUTTON_PRESS + | xinput::XIEventMask::BUTTON_RELEASE + | xinput::XIEventMask::ENTER + | xinput::XIEventMask::LEAVE + | xinput::XIEventMask::FOCUS_IN + | xinput::XIEventMask::FOCUS_OUT + | xinput::XIEventMask::TOUCH_BEGIN + | xinput::XIEventMask::TOUCH_UPDATE + | xinput::XIEventMask::TOUCH_END; + + unwrap_os!(xconn.connection.xinput_xi_select_events( + window.xwindow, + &[xinput::EventMask { + deviceid: ffi::XIAllMasterDevices as _, + mask: vec![mask] + }] + )) + .ignore_error(); + + /*{ let result = event_loop .ime .borrow_mut() - .create_context(window.xwindow, false); + .create_context(window.xwindow as _, false); if let Err(err) = result { let e = match err { - ImeContextCreationError::XError(err) => OsError::XError(err), + ImeContextCreationError::XError(err) => { + OsError::XError(PlatformError::from(err).into()) + } ImeContextCreationError::Null => { OsError::XMisc("IME Context creation failed") } }; return Err(os_error!(e)); } - } + }*/ // These properties must be set after mapping if window_attrs.maximized { - window.set_maximized_inner(window_attrs.maximized).queue(); + unwrap_os!(window.set_maximized_inner(window_attrs.maximized)).ignore_error(); } if window_attrs.fullscreen.is_some() { - if let Some(flusher) = window.set_fullscreen_inner(window_attrs.fullscreen.clone()) + if let Some(flusher) = + unwrap_os!(window.set_fullscreen_inner(window_attrs.fullscreen.clone())) { - flusher.queue() + flusher.ignore_error(); } if let Some(PhysicalPosition { x, y }) = position { @@ -494,64 +532,71 @@ impl UnownedWindow { } } - window - .set_window_level_inner(window_attrs.window_level) - .queue(); + unwrap_os!(window.set_window_level_inner(window_attrs.window_level)).ignore_error(); } // We never want to give the user a broken window, since by then, it's too late to handle. xconn .sync_with_server() .map(|_| window) - .map_err(|x_err| os_error!(OsError::XError(x_err))) + .map_err(|x_err| os_error!(OsError::XError(x_err.into()))) } pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> { self.shared_state.lock().unwrap() } - fn set_pid(&self) -> Option> { - let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; - let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; - unsafe { - // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is - // the limit defined by OpenBSD. - const MAXHOSTNAMELEN: usize = 256; - // `assume_init` is safe here because the array consists of `MaybeUninit` values, - // which do not require initialization. - let mut buffer: [MaybeUninit; MAXHOSTNAMELEN] = - MaybeUninit::uninit().assume_init(); + fn set_pid(&self) -> Result>, PlatformError> { + let pid_atom = self.xconn.atoms[_NET_WM_PID]; + let client_machine_atom = self.xconn.atoms[WM_CLIENT_MACHINE]; + + // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is + // the limit defined by OpenBSD. + const MAXHOSTNAMELEN: usize = 256; + // `assume_init` is safe here because the array consists of `MaybeUninit` values, + // which do not require initialization. + let mut buffer: [MaybeUninit; MAXHOSTNAMELEN] = + unsafe { MaybeUninit::uninit().assume_init() }; + + // Get the hostname. + let hostname = unsafe { let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()); if status != 0 { - return None; + return Ok(None); } ptr::write(buffer[MAXHOSTNAMELEN - 1].as_mut_ptr() as *mut u8, b'\0'); // a little extra safety let hostname_length = libc::strlen(buffer.as_ptr() as *const c_char); - let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length); + slice::from_raw_parts(buffer.as_ptr() as *const u8, hostname_length) + }; - self.xconn - .change_property( - self.xwindow, - pid_atom, - ffi::XA_CARDINAL, - util::PropMode::Replace, - &[libc::getpid() as util::Cardinal], - ) - .queue(); - let flusher = self.xconn.change_property( + let pid = unsafe { libc::getpid() as util::Cardinal }; + + self.xconn + .change_property( + self.xwindow, + pid_atom, + xproto::AtomEnum::CARDINAL.into(), + xproto::PropMode::REPLACE, + &[pid], + )? + .ignore_error(); + self.xconn + .change_property( self.xwindow, client_machine_atom, - ffi::XA_STRING, - util::PropMode::Replace, - &hostname[0..hostname_length], - ); - Some(flusher) - } + xproto::AtomEnum::STRING.into(), + xproto::PropMode::REPLACE, + hostname, + ) + .map(Some) } - fn set_window_types(&self, window_types: Vec) -> util::Flusher<'_> { - let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; + fn set_window_types( + &self, + window_types: Vec, + ) -> Result, PlatformError> { + let hint_atom = self.xconn.atoms[_NET_WM_WINDOW_TYPE]; let atoms: Vec<_> = window_types .iter() .map(|t| t.as_atom(&self.xconn)) @@ -560,26 +605,28 @@ impl UnownedWindow { self.xconn.change_property( self.xwindow, hint_atom, - ffi::XA_ATOM, - util::PropMode::Replace, + xproto::AtomEnum::ATOM.into(), + xproto::PropMode::REPLACE, &atoms, ) } - pub fn set_theme_inner(&self, theme: Option) -> util::Flusher<'_> { - let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_GTK_THEME_VARIANT\0") }; - let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; + pub fn set_theme_inner( + &self, + theme: Option, + ) -> Result, PlatformError> { + let hint_atom = self.xconn.atoms[_GTK_THEME_VARIANT]; + let utf8_atom = self.xconn.atoms[UTF8_STRING]; let variant = match theme { Some(Theme::Dark) => "dark", Some(Theme::Light) => "light", None => "dark", }; - let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte"); self.xconn.change_property( self.xwindow, hint_atom, utf8_atom, - util::PropMode::Replace, + xproto::PropMode::REPLACE, variant.as_bytes(), ) } @@ -587,23 +634,24 @@ impl UnownedWindow { #[inline] pub fn set_theme(&self, theme: Option) { self.set_theme_inner(theme) - .flush() + .check() .expect("Failed to change window theme") } fn set_netwm( &self, operation: util::StateOperation, - properties: (c_long, c_long, c_long, c_long), - ) -> util::Flusher<'_> { - let state_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE\0") }; + properties: (u32, u32, u32, u32), + ) -> Result, PlatformError> { + let state_atom = self.xconn.atoms[_NET_WM_STATE]; self.xconn.send_client_msg( self.xwindow, self.root, state_atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY), + 32, [ - operation as c_long, + operation as u32, properties.0, properties.1, properties.2, @@ -612,42 +660,44 @@ impl UnownedWindow { ) } - fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> { - let fullscreen_atom = - unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") }; - let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)); + fn set_fullscreen_hint(&self, fullscreen: bool) -> Result, PlatformError> { + let fullscreen_atom = self.xconn.atoms[_NET_WM_STATE_FULLSCREEN]; + let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0))?; if fullscreen { // Ensure that the fullscreen window receives input focus to prevent // locking up the user's display. - unsafe { - (self.xconn.xlib.XSetInputFocus)( - self.xconn.display, + self.xconn + .connection + .set_input_focus( + xproto::InputFocus::PARENT, self.xwindow, - ffi::RevertToParent, - ffi::CurrentTime, - ); - } + xproto::Time::CURRENT_TIME, + )? + .ignore_error(); } - flusher + Ok(flusher) } - fn set_fullscreen_inner(&self, fullscreen: Option) -> Option> { + fn set_fullscreen_inner( + &self, + fullscreen: Option, + ) -> Result>, PlatformError> { let mut shared_state_lock = self.shared_state_lock(); match shared_state_lock.visibility { // Setting fullscreen on a window that is not visible will generate an error. Visibility::No | Visibility::YesWait => { shared_state_lock.desired_fullscreen = Some(fullscreen); - return None; + return Ok(None); } Visibility::Yes => (), } let old_fullscreen = shared_state_lock.fullscreen.clone(); if old_fullscreen == fullscreen { - return None; + return Ok(None); } shared_state_lock.fullscreen = fullscreen.clone(); @@ -663,7 +713,7 @@ impl UnownedWindow { ) => { let monitor = video_mode.monitor.as_ref().unwrap(); shared_state_lock.desktop_video_mode = - Some((monitor.id, self.xconn.get_crtc_mode(monitor.id))); + Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)?)); } // Restore desktop video mode upon exiting exclusive fullscreen (&Some(Fullscreen::Exclusive(_)), &None) @@ -680,13 +730,14 @@ impl UnownedWindow { match fullscreen { None => { - let flusher = self.set_fullscreen_hint(false); + let flusher = self.set_fullscreen_hint(false)?; let mut shared_state_lock = self.shared_state_lock(); if let Some(position) = shared_state_lock.restore_position.take() { drop(shared_state_lock); - self.set_position_inner(position.0, position.1).queue(); + self.set_position_inner(position.0, position.1)? + .ignore_error(); } - Some(flusher) + Ok(Some(flusher)) } Some(fullscreen) => { let (video_mode, monitor) = match fullscreen { @@ -703,7 +754,7 @@ impl UnownedWindow { // Don't set fullscreen on an invalid dummy monitor handle if monitor.is_dummy() { - return None; + return Ok(None); } if let Some(video_mode) = video_mode { @@ -740,9 +791,9 @@ impl UnownedWindow { let window_position = self.outer_position_physical(); self.shared_state_lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); - self.set_position_inner(monitor_origin.0, monitor_origin.1) - .queue(); - Some(self.set_fullscreen_hint(true)) + self.set_position_inner(monitor_origin.0, monitor_origin.1)? + .ignore_error(); + Ok(Some(self.set_fullscreen_hint(true)?)) } } } @@ -759,9 +810,9 @@ impl UnownedWindow { #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { - if let Some(flusher) = self.set_fullscreen_inner(fullscreen) { + if let Ok(Some(flusher)) = self.set_fullscreen_inner(fullscreen) { flusher - .sync() + .check() .expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } @@ -772,9 +823,13 @@ impl UnownedWindow { let mut shared_state = self.shared_state_lock(); match shared_state.visibility { - Visibility::No => unsafe { - (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); - }, + Visibility::No => { + self.xconn + .connection + .unmap_window(self.xwindow) + .expect("Failed to unmap window") + .ignore_error(); + } Visibility::Yes => (), Visibility::YesWait => { shared_state.visibility = Visibility::Yes; @@ -800,112 +855,101 @@ impl UnownedWindow { self.xconn.primary_monitor() } - fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> { - unsafe { - if minimized { - let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display); - - (self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen); + fn set_minimized_inner(&self, minimized: bool) -> Result, PlatformError> { + if minimized { + let wm_change_state = self.xconn.atoms[_NET_WM_STATE]; - util::Flusher::new(&self.xconn) - } else { - let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0"); + self.xconn.send_client_msg( + self.xwindow, + self.root, + wm_change_state, + Some( + xproto::EventMask::SUBSTRUCTURE_REDIRECT + | xproto::EventMask::SUBSTRUCTURE_NOTIFY, + ), + 32, + [x11rb::properties::WmHintsState::Iconic as u32, 0, 0, 0, 0], + ) + } else { + let atom = self.xconn.atoms[_NET_ACTIVE_WINDOW]; - self.xconn.send_client_msg( - self.xwindow, - self.root, - atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), - [1, ffi::CurrentTime as c_long, 0, 0, 0], - ) - } + self.xconn.send_client_msg( + self.xwindow, + self.root, + atom, + Some( + xproto::EventMask::SUBSTRUCTURE_REDIRECT + | xproto::EventMask::SUBSTRUCTURE_NOTIFY, + ), + 32, + [1, x11rb::CURRENT_TIME, 0, 0, 0], + ) } } #[inline] pub fn set_minimized(&self, minimized: bool) { self.set_minimized_inner(minimized) - .flush() + .expect("Failed to change window minimization") + .check() .expect("Failed to change window minimization"); } #[inline] pub fn is_maximized(&self) -> bool { - let state_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE\0") }; - let state = self - .xconn - .get_property(self.xwindow, state_atom, ffi::XA_ATOM); - let horz_atom = unsafe { + let state_atom = self.xconn.atoms[_NET_WM_STATE]; + let state = self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0") - }; - let vert_atom = unsafe { - self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0") - }; + .get_property(self.xwindow, state_atom, xproto::AtomEnum::ATOM.into()); + let horz_atom = self.xconn.atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; + let vert_atom = self.xconn.atoms[_NET_WM_STATE_MAXIMIZED_VERT]; match state { Ok(atoms) => { - let horz_maximized = atoms.iter().any(|atom: &ffi::Atom| *atom == horz_atom); - let vert_maximized = atoms.iter().any(|atom: &ffi::Atom| *atom == vert_atom); + let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom); + let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom); horz_maximized && vert_maximized } _ => false, } } - fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> { - let horz_atom = unsafe { - self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0") - }; - let vert_atom = unsafe { - self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0") - }; - self.set_netwm( - maximized.into(), - (horz_atom as c_long, vert_atom as c_long, 0, 0), - ) + fn set_maximized_inner(&self, maximized: bool) -> Result, PlatformError> { + let horz_atom = self.xconn.atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; + let vert_atom = self.xconn.atoms[_NET_WM_STATE_MAXIMIZED_VERT]; + self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0)) } #[inline] pub fn set_maximized(&self, maximized: bool) { self.set_maximized_inner(maximized) - .flush() + .check() .expect("Failed to change window maximization"); self.invalidate_cached_frame_extents(); } - fn set_title_inner(&self, title: &str) -> util::Flusher<'_> { - let wm_name_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_NAME\0") }; - let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; - let title = CString::new(title).expect("Window title contained null byte"); - unsafe { - (self.xconn.xlib.XStoreName)( - self.xconn.display, - self.xwindow, - title.as_ptr() as *const c_char, - ); - self.xconn.change_property( - self.xwindow, - wm_name_atom, - utf8_atom, - util::PropMode::Replace, - title.as_bytes(), - ) - } + fn set_title_inner(&self, title: &str) -> Result, PlatformError> { + let wm_name_atom = self.xconn.atoms[_NET_WM_NAME]; + let utf8_atom = self.xconn.atoms[UTF8_STRING]; + + self.xconn.change_property( + self.xwindow, + wm_name_atom, + utf8_atom, + xproto::PropMode::REPLACE, + title.as_bytes(), + ) } #[inline] pub fn set_title(&self, title: &str) { self.set_title_inner(title) - .flush() + .check() .expect("Failed to set window title"); } - fn set_decorations_inner(&self, decorations: bool) -> util::Flusher<'_> { + fn set_decorations_inner(&self, decorations: bool) -> Result, PlatformError> { self.shared_state_lock().is_decorated = decorations; - let mut hints = self.xconn.get_motif_hints(self.xwindow); + let mut hints = self.xconn.get_motif_hints(self.xwindow)?; hints.set_decorations(decorations); @@ -915,7 +959,7 @@ impl UnownedWindow { #[inline] pub fn set_decorations(&self, decorations: bool) { self.set_decorations_inner(decorations) - .flush() + .check() .expect("Failed to set decoration state"); self.invalidate_cached_frame_extents(); } @@ -925,55 +969,59 @@ impl UnownedWindow { self.shared_state_lock().is_decorated } - fn set_maximizable_inner(&self, maximizable: bool) -> util::Flusher<'_> { - let mut hints = self.xconn.get_motif_hints(self.xwindow); + fn set_maximizable_inner(&self, maximizable: bool) -> Result, PlatformError> { + let mut hints = self.xconn.get_motif_hints(self.xwindow)?; hints.set_maximizable(maximizable); self.xconn.set_motif_hints(self.xwindow, &hints) } - fn toggle_atom(&self, atom_bytes: &[u8], enable: bool) -> util::Flusher<'_> { - let atom = unsafe { self.xconn.get_atom_unchecked(atom_bytes) }; - self.set_netwm(enable.into(), (atom as c_long, 0, 0, 0)) + fn toggle_atom( + &self, + atom_name: AtomType, + enable: bool, + ) -> Result, PlatformError> { + let atom = self.xconn.atoms[atom_name]; + self.set_netwm(enable.into(), (atom, 0, 0, 0)) } - fn set_window_level_inner(&self, level: WindowLevel) -> util::Flusher<'_> { - self.toggle_atom(b"_NET_WM_STATE_ABOVE\0", level == WindowLevel::AlwaysOnTop) - .queue(); - self.toggle_atom( - b"_NET_WM_STATE_BELOW\0", - level == WindowLevel::AlwaysOnBottom, - ) + fn set_window_level_inner( + &self, + level: WindowLevel, + ) -> Result, PlatformError> { + self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)? + .ignore_error(); + self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { self.set_window_level_inner(level) - .flush() + .check() .expect("Failed to set window-level state"); } - fn set_icon_inner(&self, icon: Icon) -> util::Flusher<'_> { - let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; + fn set_icon_inner(&self, icon: Icon) -> Result, PlatformError> { + let icon_atom = self.xconn.atoms[_NET_WM_ICON]; let data = icon.to_cardinals(); self.xconn.change_property( self.xwindow, icon_atom, - ffi::XA_CARDINAL, - util::PropMode::Replace, + xproto::AtomEnum::CARDINAL.into(), + xproto::PropMode::REPLACE, data.as_slice(), ) } - fn unset_icon_inner(&self) -> util::Flusher<'_> { - let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; + fn unset_icon_inner(&self) -> Result, PlatformError> { + let icon_atom = self.xconn.atoms[_NET_WM_ICON]; let empty_data: [util::Cardinal; 0] = []; self.xconn.change_property( self.xwindow, icon_atom, - ffi::XA_CARDINAL, - util::PropMode::Replace, + xproto::AtomEnum::CARDINAL.into(), + xproto::PropMode::REPLACE, &empty_data, ) } @@ -984,7 +1032,7 @@ impl UnownedWindow { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), } - .flush() + .check() .expect("Failed to set icons"); } @@ -1000,19 +1048,15 @@ impl UnownedWindow { } if visible { - unsafe { - (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); - } - self.xconn - .flush_requests() + self.map_raised() + .check() .expect("Failed to call XMapRaised"); shared_state.visibility = Visibility::YesWait; } else { - unsafe { - (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); - } self.xconn - .flush_requests() + .connection + .unmap_window(self.xwindow) + .check() .expect("Failed to call XUnmapWindow"); shared_state.visibility = Visibility::No; } @@ -1037,7 +1081,9 @@ impl UnownedWindow { pub(crate) fn outer_position_physical(&self) -> (i32, i32) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { - let (x, y) = self.inner_position_physical(); + let (x, y) = self + .inner_position_physical() + .expect("Failed to get inner position"); extents.inner_pos_to_outer(x, y) } else { self.update_cached_frame_extents(); @@ -1049,7 +1095,9 @@ impl UnownedWindow { pub fn outer_position(&self) -> Result, NotSupportedError> { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { - let (x, y) = self.inner_position_physical(); + let (x, y) = self + .inner_position_physical() + .expect("Failed to get inner position"); Ok(extents.inner_pos_to_outer(x, y).into()) } else { self.update_cached_frame_extents(); @@ -1057,24 +1105,33 @@ impl UnownedWindow { } } - pub(crate) fn inner_position_physical(&self) -> (i32, i32) { + pub(crate) fn inner_position_physical(&self) -> Result<(i32, i32), PlatformError> { // This should be okay to unwrap since the only error XTranslateCoordinates can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn - .translate_coords(self.xwindow, self.root) - .map(|coords| (coords.x_rel_root, coords.y_rel_root)) - .unwrap() + .connection + .translate_coordinates(self.xwindow, self.root, 0, 0) + .platform() + .and_then(|c| c.reply().platform()) + .map(|coords| (coords.dst_x.into(), coords.dst_y.into())) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { - Ok(self.inner_position_physical().into()) + Ok(self + .inner_position_physical() + .expect("Failed to get physical position") + .into()) } - pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher<'_> { + pub(crate) fn set_position_inner( + &self, + mut x: i32, + mut y: i32, + ) -> Result, PlatformError> { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. - if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { + if self.xconn.wm_name_is_one_of(&["Enlightenment", "FVWM"]) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { x += extents.frame_extents.left as i32; @@ -1084,15 +1141,16 @@ impl UnownedWindow { return self.set_position_inner(x, y); } } - unsafe { - (self.xconn.xlib.XMoveWindow)(self.xconn.display, self.xwindow, x as c_int, y as c_int); - } - util::Flusher::new(&self.xconn) + + self.xconn + .connection + .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y)) + .platform() } pub(crate) fn set_position_physical(&self, x: i32, y: i32) { self.set_position_inner(x, y) - .flush() + .check() .expect("Failed to call `XMoveWindow`"); } @@ -1106,8 +1164,11 @@ impl UnownedWindow { // This should be okay to unwrap since the only error XGetGeometry can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn + .connection .get_geometry(self.xwindow) - .map(|geo| (geo.width, geo.height)) + .platform() + .and_then(|c| c.reply().platform()) + .map(|geo| (geo.width.into(), geo.height.into())) .unwrap() } @@ -1129,16 +1190,16 @@ impl UnownedWindow { } pub(crate) fn set_inner_size_physical(&self, width: u32, height: u32) { - unsafe { - (self.xconn.xlib.XResizeWindow)( - self.xconn.display, + self.xconn + .connection + .configure_window( self.xwindow, - width as c_uint, - height as c_uint, - ); - self.xconn.flush_requests() - } - .expect("Failed to call `XResizeWindow`"); + &xproto::ConfigureWindowAux::new() + .width(width) + .height(height), + ) + .check() + .expect("Failed to call `XResizeWindow`"); } #[inline] @@ -1155,7 +1216,7 @@ impl UnownedWindow { self.set_inner_size_physical(size.0, size.1); } - fn update_normal_hints(&self, callback: F) -> Result<(), XError> + fn update_normal_hints(&self, callback: F) -> Result<(), PlatformError> where F: FnOnce(&mut util::NormalHints<'_>), { @@ -1240,7 +1301,7 @@ impl UnownedWindow { } pub fn set_resizable(&self, resizable: bool) { - if util::wm_name_is_one_of(&["Xfwm4"]) { + if self.xconn.wm_name_is_one_of(&["Xfwm4"]) { // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected. // This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose // the lesser of two evils and do nothing. @@ -1260,7 +1321,9 @@ impl UnownedWindow { }; self.shared_state_lock().is_resizable = resizable; - self.set_maximizable_inner(resizable).queue(); + self.set_maximizable_inner(resizable) + .check() + .expect("Failed to set maximizable"); let scale_factor = self.scale_factor(); let min_inner_size = min_size @@ -1291,22 +1354,22 @@ impl UnownedWindow { #[inline] pub fn xlib_display(&self) -> *mut c_void { - self.xconn.display as _ + self.xconn.display.as_ptr() as _ } #[inline] pub fn xlib_screen_id(&self) -> c_int { - self.screen_id + self.screen_id as _ } #[inline] pub fn xlib_window(&self) -> c_ulong { - self.xwindow + self.xwindow as _ } #[inline] pub fn xcb_connection(&self) -> *mut c_void { - unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } + self.xconn.connection.get_raw_xcb_connection() } #[inline] @@ -1325,54 +1388,63 @@ impl UnownedWindow { return Ok(()); } - unsafe { + { // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. // Therefore, this is common to both codepaths. - (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); + self.xconn + .connection + .ungrab_pointer(xproto::Time::CURRENT_TIME) + .check() + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; } let result = match mode { CursorGrabMode::None => self .xconn .flush_requests() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))), + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))), CursorGrabMode::Confined => { - let result = unsafe { - (self.xconn.xlib.XGrabPointer)( - self.xconn.display, + let reply = self + .xconn + .connection + .grab_pointer( + true, self.xwindow, - ffi::True, - (ffi::ButtonPressMask - | ffi::ButtonReleaseMask - | ffi::EnterWindowMask - | ffi::LeaveWindowMask - | ffi::PointerMotionMask - | ffi::PointerMotionHintMask - | ffi::Button1MotionMask - | ffi::Button2MotionMask - | ffi::Button3MotionMask - | ffi::Button4MotionMask - | ffi::Button5MotionMask - | ffi::ButtonMotionMask - | ffi::KeymapStateMask) as c_uint, - ffi::GrabModeAsync, - ffi::GrabModeAsync, + xproto::EventMask::BUTTON_PRESS + | xproto::EventMask::BUTTON_RELEASE + | xproto::EventMask::ENTER_WINDOW + | xproto::EventMask::LEAVE_WINDOW + | xproto::EventMask::POINTER_MOTION + | xproto::EventMask::POINTER_MOTION_HINT + | xproto::EventMask::BUTTON1_MOTION + | xproto::EventMask::BUTTON2_MOTION + | xproto::EventMask::BUTTON3_MOTION + | xproto::EventMask::BUTTON4_MOTION + | xproto::EventMask::BUTTON5_MOTION + | xproto::EventMask::BUTTON_MOTION + | xproto::EventMask::KEYMAP_STATE, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, self.xwindow, - 0, - ffi::CurrentTime, + 0u32, + xproto::Time::CURRENT_TIME, ) - }; + .platform() + .and_then(|r| r.reply().platform()) + .expect("Failed to call `XGrabPointer`"); - match result { - ffi::GrabSuccess => Ok(()), - ffi::AlreadyGrabbed => { + match reply.status { + xproto::GrabStatus::SUCCESS => Ok(()), + xproto::GrabStatus::ALREADY_GRABBED => { Err("Cursor could not be confined: already confined by another client") } - ffi::GrabInvalidTime => Err("Cursor could not be confined: invalid time"), - ffi::GrabNotViewable => { + xproto::GrabStatus::INVALID_TIME => { + Err("Cursor could not be confined: invalid time") + } + xproto::GrabStatus::NOT_VIEWABLE => { Err("Cursor could not be confined: confine location not viewable") } - ffi::GrabFrozen => { + xproto::GrabStatus::FROZEN => { Err("Cursor could not be confined: frozen by another client") } _ => unreachable!(), @@ -1414,12 +1486,11 @@ impl UnownedWindow { } pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { - unsafe { - (self.xconn.xlib.XWarpPointer)(self.xconn.display, 0, self.xwindow, 0, 0, 0, 0, x, y); - self.xconn - .flush_requests() - .map_err(|e| ExternalError::Os(os_error!(OsError::XError(e)))) - } + self.xconn + .connection + .warp_pointer(self.xwindow, 0u32, 0, 0, 0, 0, x as i16, y as i16) + .check() + .map_err(|e| ExternalError::Os(os_error!(OsError::XError(e.into())))) } #[inline] @@ -1454,24 +1525,27 @@ impl UnownedWindow { /// Initiates a drag operation while the left mouse button is pressed. fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> { - let pointer = self + let (win_x, win_y) = self .xconn - .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?; + .connection + .xinput_xi_query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) + .platform() + .and_then(|r| r.reply().platform()) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))) + .map(|reply| (reply.win_x, reply.win_y))?; let window = self.inner_position().map_err(ExternalError::NotSupported)?; - let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") }; + let message = self.xconn.atoms[_NET_WM_MOVERESIZE]; // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` // if the cursor isn't currently grabbed let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); - unsafe { - (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); - } self.xconn - .flush_requests() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?; + .connection + .ungrab_pointer(xproto::Time::CURRENT_TIME) + .check() + .map_err(|e| ExternalError::Os(os_error!(OsError::XError(e.into()))))?; *grabbed_lock = CursorGrabMode::None; // we keep the lock until we are done @@ -1480,42 +1554,52 @@ impl UnownedWindow { self.xwindow, self.root, message, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + Some( + xproto::EventMask::SUBSTRUCTURE_NOTIFY + | xproto::EventMask::SUBSTRUCTURE_REDIRECT, + ), + 32, [ - (window.x as c_long + pointer.win_x as c_long), - (window.y as c_long + pointer.win_y as c_long), + (window.x as u32 + win_x as u32), + (window.y as u32 + win_y as u32), action.try_into().unwrap(), - ffi::Button1 as c_long, + 1, 1, ], ) - .flush() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))) + .check() + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))) } #[inline] pub fn set_ime_position(&self, spot: Position) { + let _ = spot; + /* let (x, y) = spot.to_physical::(self.scale_factor()).into(); let _ = self .ime_sender .lock() .unwrap() - .send(ImeRequest::Position(self.xwindow, x, y)); + .send(ImeRequest::Position(self.xwindow as _, x, y)); + */ } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { + let _ = allowed; + /* let _ = self .ime_sender .lock() .unwrap() - .send(ImeRequest::Allow(self.xwindow, allowed)); + .send(ImeRequest::Allow(self.xwindow as _, allowed)); + */ } #[inline] pub fn focus_window(&self) { - let state_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_STATE\0") }; - let state_type_atom = unsafe { self.xconn.get_atom_unchecked(b"CARD32\0") }; + let state_atom = self.xconn.atoms[WM_STATE]; + let state_type_atom = self.xconn.atoms[CARD32]; let is_minimized = if let Ok(state) = self.xconn .get_property(self.xwindow, state_atom, state_type_atom) @@ -1530,15 +1614,19 @@ impl UnownedWindow { }; if is_visible && !is_minimized { - let atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0") }; + let atom = self.xconn.atoms[_NET_ACTIVE_WINDOW]; let flusher = self.xconn.send_client_msg( self.xwindow, self.root, atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), - [1, ffi::CurrentTime as c_long, 0, 0, 0], + Some( + xproto::EventMask::SUBSTRUCTURE_REDIRECT + | xproto::EventMask::SUBSTRUCTURE_NOTIFY, + ), + 32, + [1u32, xproto::Time::CURRENT_TIME.into(), 0, 0, 0], ); - if let Err(e) = flusher.flush() { + if let Err(e) = flusher.check() { log::error!( "`flush` returned an error when focusing the window. Error was: {}", e @@ -1551,7 +1639,7 @@ impl UnownedWindow { pub fn request_user_attention(&self, request_type: Option) { let mut wm_hints = self .xconn - .get_wm_hints(self.xwindow) + .get_wm_hints(self.xwindow as _) .expect("`XGetWMHints` failed"); if request_type.is_some() { wm_hints.flags |= ffi::XUrgencyHint; @@ -1559,7 +1647,7 @@ impl UnownedWindow { wm_hints.flags &= !ffi::XUrgencyHint; } self.xconn - .set_wm_hints(self.xwindow, wm_hints) + .set_wm_hints(self.xwindow as _, wm_hints) .flush() .expect("Failed to set urgency hint"); } @@ -1589,7 +1677,7 @@ impl UnownedWindow { pub fn raw_display_handle(&self) -> RawDisplayHandle { let mut display_handle = XlibDisplayHandle::empty(); display_handle.display = self.xlib_display(); - display_handle.screen = self.screen_id; + display_handle.screen = self.screen_id as _; RawDisplayHandle::Xlib(display_handle) } @@ -1602,4 +1690,16 @@ impl UnownedWindow { pub fn title(&self) -> String { String::new() } + + fn map_raised(&self) -> Result, PlatformError> { + self.xconn + .connection + .configure_window( + self.xwindow, + &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE), + )? + .ignore_error(); + + self.xconn.connection.map_window(self.xwindow).platform() + } } diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index e3d7f4ffdc5..bf840f0c07a 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,21 +1,62 @@ -use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr, sync::Mutex}; +use std::{ + collections::HashMap, + error::Error, + fmt, + mem::ManuallyDrop, + os::raw::c_int, + ptr::{self, NonNull}, + sync::Mutex, +}; + +use x11rb::resource_manager; +use x11rb::xcb_ffi::XCBConnection; +use x11rb::{connection::Connection, protocol::xproto}; use crate::window::CursorIcon; +use super::atoms::*; use super::ffi; /// A connection to an X server. pub(crate) struct XConnection { + /// Core Xlib shared library. pub xlib: ffi::Xlib, - /// Exposes XRandR functions from version < 1.5 - pub xrandr: ffi::Xrandr_2_2_0, + + /// X11 cursor shared library. pub xcursor: ffi::Xcursor, - pub xinput2: ffi::XInput2, - pub xlib_xcb: ffi::Xlib_xcb, - pub display: *mut ffi::Display, + + /// Pointer to the Xlib display. + pub display: NonNull, + + /// A wrapper around the XCB connection. + /// + /// We have to wrap the connection in a `ManuallyDrop` because the `XCBConnection` type + /// needs to be dropped before the Xlib `Display` is dropped. + pub connection: ManuallyDrop, + + /// The X11 XRM database. + pub database: resource_manager::Database, + + /// The default screen number. + pub default_screen: usize, + + /// The file descriptor associated with the X11 connection. pub x11_fd: c_int, + + /// The atoms used by the program. + pub(crate) atoms: Atoms, + + /// The latest X11 error used for error handling. pub latest_error: Mutex>, - pub cursor_cache: Mutex, ffi::Cursor>>, + + /// Cache of X11 cursors. + pub cursor_cache: Mutex, xproto::Cursor>>, + + /// The window manager hints that we support. + pub supported_hints: Mutex>, + + /// The name of the window manager. + pub wm_name: Mutex>, } unsafe impl Send for XConnection {} @@ -29,8 +70,6 @@ impl XConnection { // opening the libraries let xlib = ffi::Xlib::open()?; let xcursor = ffi::Xcursor::open()?; - let xrandr = ffi::Xrandr_2_2_0::open()?; - let xinput2 = ffi::XInput2::open()?; let xlib_xcb = ffi::Xlib_xcb::open()?; unsafe { (xlib.XInitThreads)() }; @@ -39,25 +78,59 @@ impl XConnection { // calling XOpenDisplay let display = unsafe { let display = (xlib.XOpenDisplay)(ptr::null()); - if display.is_null() { - return Err(XNotSupported::XOpenDisplayFailed); + match NonNull::new(display) { + Some(display) => display, + None => return Err(XNotSupported::XOpenDisplayFailed), } - display }; // Get X11 socket file descriptor - let fd = unsafe { (xlib.XConnectionNumber)(display) }; + let fd = unsafe { (xlib.XConnectionNumber)(display.as_ptr()) }; + + // Default screen number. + let default_screen = unsafe { (xlib.XDefaultScreen)(display.as_ptr()) } as usize; + + // Create a new wrapper around the XCB connection. + let connection = { + let raw_conn = unsafe { (xlib_xcb.XGetXCBConnection)(display.as_ptr()) }; + debug_assert!(!raw_conn.is_null()); + + // Switch the event queue owner so XCB can be used to process events. + unsafe { + (xlib_xcb.XSetEventQueueOwner)( + display.as_ptr(), + ffi::XEventQueueOwner::XCBOwnsEventQueue, + ); + } + + // Create the x11rb wrapper. + unsafe { XCBConnection::from_raw_xcb_connection(raw_conn, false) } + .expect("Failed to create x11rb connection") + }; + + // Begin loading the atoms. + let atom_cookie = Atoms::request(&connection).expect("Failed to load atoms"); + + // Load the resource manager database. + let database = resource_manager::new_from_default(&connection) + .expect("Failed to load resource manager database"); + + // Finish loading the atoms. + let atoms = atom_cookie.reply().expect("Failed to load atoms"); Ok(XConnection { xlib, - xrandr, xcursor, - xinput2, - xlib_xcb, display, + connection: ManuallyDrop::new(connection), + database, + default_screen, x11_fd: fd, + atoms, latest_error: Mutex::new(None), cursor_cache: Default::default(), + supported_hints: Mutex::new(Vec::new()), + wm_name: Mutex::new(None), }) } @@ -71,6 +144,16 @@ impl XConnection { Ok(()) } } + + /// Get the default screen for this connection. + #[inline] + pub fn default_screen(&self) -> &x11rb::protocol::xproto::Screen { + self.connection + .setup() + .roots + .get(self.default_screen) + .unwrap() + } } impl fmt::Debug for XConnection { @@ -82,7 +165,11 @@ impl fmt::Debug for XConnection { impl Drop for XConnection { #[inline] fn drop(&mut self) { - unsafe { (self.xlib.XCloseDisplay)(self.display) }; + // Make sure that the XCB connection is dropped before the Xlib connection. + unsafe { + ManuallyDrop::drop(&mut self.connection); + (self.xlib.XCloseDisplay)(self.display.as_ptr()); + } } } @@ -112,6 +199,7 @@ impl fmt::Display for XError { pub enum XNotSupported { /// Failed to load one or several shared libraries. LibraryOpenError(ffi::OpenError), + /// Connecting to the X server with `XOpenDisplay` failed. XOpenDisplayFailed, // TODO: add better message }