Replace parts of the Xlib backend with x11-rb

This commit is contained in:
John Nunley 2023-07-12 00:59:12 -07:00 committed by GitHub
parent 5379d60e4d
commit d7ec899d69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1550 additions and 1395 deletions

View file

@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "percent-encoding", "xkbcommon-dl/x11"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
@ -113,6 +113,7 @@ features = [
]
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
bytemuck = { version = "1.13.1", default-features = false, optional = true }
libc = "0.2.64"
percent-encoding = { version = "2.0", optional = true }
fnv = { version = "1.0.3", optional = true }
@ -123,6 +124,7 @@ wayland-backend = { version = "0.1.0", default_features = false, features = ["cl
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
x11-dl = { version = "2.18.5", optional = true }
x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true }
xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true }

View file

@ -23,7 +23,7 @@ use smol_str::SmolStr;
#[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, X11Error, XConnection, XError};
#[cfg(x11_platform)]
use crate::platform::x11::XlibErrorHook;
use crate::{
@ -124,7 +124,7 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
#[derive(Debug, Clone)]
pub enum OsError {
#[cfg(x11_platform)]
XError(XError),
XError(Arc<X11Error>),
#[cfg(x11_platform)]
XMisc(&'static str),
#[cfg(wayland_platform)]
@ -135,7 +135,7 @@ impl fmt::Display for OsError {
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),
#[cfg(wayland_platform)]

View file

@ -0,0 +1,110 @@
//! Collects every atom used by the platform implementation.
use core::ops::Index;
macro_rules! atom_manager {
($($name:ident $(:$lit:literal)?),*) => {
x11rb::atom_manager! {
/// The atoms used by `winit`
pub(crate) Atoms: AtomsCookie {
$($name $(:$lit)?,)*
}
}
/// Indices into the `Atoms` struct.
#[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub(crate) enum AtomName {
$($name,)*
}
impl AtomName {
pub(crate) fn atom_from(
self,
atoms: &Atoms
) -> &x11rb::protocol::xproto::Atom {
match self {
$(AtomName::$name => &atoms.$name,)*
}
}
}
};
}
atom_manager! {
// General Use Atoms
CARD32,
UTF8_STRING,
WM_CHANGE_STATE,
WM_CLIENT_MACHINE,
WM_DELETE_WINDOW,
WM_PROTOCOLS,
WM_STATE,
XIM_SERVERS,
// Assorted ICCCM Atoms
_NET_WM_ICON,
_NET_WM_MOVERESIZE,
_NET_WM_NAME,
_NET_WM_PID,
_NET_WM_PING,
_NET_WM_STATE,
_NET_WM_STATE_ABOVE,
_NET_WM_STATE_BELOW,
_NET_WM_STATE_FULLSCREEN,
_NET_WM_STATE_HIDDEN,
_NET_WM_STATE_MAXIMIZED_HORZ,
_NET_WM_STATE_MAXIMIZED_VERT,
_NET_WM_WINDOW_TYPE,
// WM window types.
_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,
// Drag-N-Drop Atoms
XdndAware,
XdndEnter,
XdndLeave,
XdndDrop,
XdndPosition,
XdndStatus,
XdndActionPrivate,
XdndSelection,
XdndFinished,
XdndTypeList,
TextUriList: b"text/uri-list",
None: b"None",
// Miscellaneous Atoms
_GTK_THEME_VARIANT,
_MOTIF_WM_HINTS,
_NET_ACTIVE_WINDOW,
_NET_CLIENT_LIST,
_NET_FRAME_EXTENTS,
_NET_SUPPORTED,
_NET_SUPPORTING_WM_CHECK
}
impl Index<AtomName> for Atoms {
type Output = x11rb::protocol::xproto::Atom;
fn index(&self, index: AtomName) -> &Self::Output {
index.atom_from(self)
}
}
pub(crate) use AtomName::*;
// Make sure `None` is still defined.
pub(crate) use core::option::Option::None;

View file

@ -7,55 +7,12 @@ use std::{
};
use percent_encoding::percent_decode;
use x11rb::protocol::xproto::{self, ConnectionExt};
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<XConnection>) -> Result<Self, XError> {
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::{AtomName::None as DndNone, *},
util, CookieResultExt, X11Error, XConnection,
};
#[derive(Debug, Clone, Copy)]
pub enum DndState {
@ -86,22 +43,19 @@ impl From<io::Error> for DndDataParseError {
pub(crate) struct Dnd {
xconn: Arc<XConnection>,
pub atoms: DndAtoms,
// Populated by XdndEnter event handler
pub version: Option<c_long>,
pub type_list: Option<Vec<c_ulong>>,
pub type_list: Option<Vec<xproto::Atom>>,
// Populated by XdndPosition event handler
pub source_window: Option<c_ulong>,
pub source_window: Option<xproto::Window>,
// Populated by SelectionNotify event handler (triggered by XdndPosition event handler)
pub result: Option<Result<Vec<PathBuf>, DndDataParseError>>,
}
impl Dnd {
pub fn new(xconn: Arc<XConnection>) -> Result<Self, XError> {
let atoms = DndAtoms::new(&xconn)?;
pub fn new(xconn: Arc<XConnection>) -> Result<Self, X11Error> {
Ok(Dnd {
xconn,
atoms,
version: None,
type_list: None,
source_window: None,
@ -118,71 +72,85 @@ impl Dnd {
pub unsafe 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<(), X11Error> {
let atoms = self.xconn.atoms();
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, atoms[XdndActionPrivate]),
DndState::Rejected => (0, atoms[DndNone]),
};
self.xconn
.send_client_msg(
target_window,
target_window,
self.atoms.status,
atoms[XdndStatus] as _,
None,
[this_window as c_long, accepted, 0, 0, action],
)
.flush()
[this_window, accepted, 0, 0, action as _],
)?
.ignore_error();
Ok(())
}
pub unsafe 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<(), X11Error> {
let atoms = self.xconn.atoms();
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, atoms[XdndActionPrivate]),
DndState::Rejected => (0, atoms[DndNone]),
};
self.xconn
.send_client_msg(
target_window,
target_window,
self.atoms.finished,
atoms[XdndFinished] as _,
None,
[this_window as c_long, accepted, action, 0, 0],
)
.flush()
[this_window, accepted, action as _, 0, 0],
)?
.ignore_error();
Ok(())
}
pub unsafe fn get_type_list(
&self,
source_window: c_ulong,
) -> Result<Vec<ffi::Atom>, util::GetPropertyError> {
self.xconn
.get_property(source_window, self.atoms.type_list, ffi::XA_ATOM)
source_window: xproto::Window,
) -> Result<Vec<xproto::Atom>, util::GetPropertyError> {
let atoms = self.xconn.atoms();
self.xconn.get_property(
source_window,
atoms[XdndTypeList],
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
}
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 unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) {
let atoms = self.xconn.atoms();
self.xconn
.xcb_connection()
.convert_selection(
window,
atoms[XdndSelection],
atoms[TextUriList],
atoms[XdndSelection],
time,
)
.expect_then_ignore_error("Failed to send XdndSelection event")
}
pub unsafe fn read_data(
&self,
window: c_ulong,
window: xproto::Window,
) -> Result<Vec<c_uchar>, util::GetPropertyError> {
let atoms = self.xconn.atoms();
self.xconn
.get_property(window, self.atoms.selection, self.atoms.uri_list)
.get_property(window, atoms[XdndSelection], atoms[TextUriList])
}
pub fn parse_data(&self, data: &mut [c_uchar]) -> Result<Vec<PathBuf>, DndDataParseError> {

View file

@ -2,9 +2,13 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc};
use libc::{c_char, c_int, c_long, c_ulong};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::Serialize;
use super::{
ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState,
GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension,
atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId,
DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow,
WindowId, XExtension,
};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
@ -38,7 +42,7 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) held_key_press: Option<u32>,
pub(super) first_touch: Option<u64>,
// Currently focused window belonging to this process
pub(super) active_window: Option<ffi::Window>,
pub(super) active_window: Option<xproto::Window>,
pub(super) is_composing: bool,
}
@ -53,7 +57,7 @@ impl<T: 'static> EventProcessor<T> {
}
}
fn with_window<F, Ret>(&self, window_id: ffi::Window, callback: F) -> Option<Ret>
fn with_window<F, Ret>(&self, window_id: xproto::Window, callback: F) -> Option<Ret>
where
F: Fn(&Arc<UnownedWindow>) -> Ret,
{
@ -77,7 +81,7 @@ impl<T: 'static> EventProcessor<T> {
result
}
fn window_exists(&self, window_id: ffi::Window) -> bool {
fn window_exists(&self, window_id: xproto::Window) -> bool {
self.with_window(window_id, |_| ()).is_some()
}
@ -120,6 +124,7 @@ impl<T: 'static> EventProcessor<T> {
F: FnMut(Event<'_, T>),
{
let wt = get_xtarget(&self.target);
let atoms = wt.x_connection().atoms();
// XFilterEvent tells us when an event has been discarded by the input method.
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
// along with an extra copy of the KeyRelease events. This also prevents backspace and
@ -140,42 +145,57 @@ impl<T: 'static> EventProcessor<T> {
ffi::ClientMessage => {
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
let window = client_msg.window;
let window = client_msg.window as xproto::Window;
let window_id = mkwid(window);
if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window {
if client_msg.data.get_long(0) as xproto::Atom == wt.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 {
} else if client_msg.data.get_long(0) as xproto::Atom == wt.net_wm_ping {
let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut();
response_msg.window = wt.root;
let client_msg = xproto::ClientMessageEvent {
response_type: xproto::CLIENT_MESSAGE_EVENT,
format: response_msg.format as _,
sequence: response_msg.serial as _,
window: wt.root,
type_: response_msg.message_type as _,
data: xproto::ClientMessageData::from({
let [a, b, c, d, e]: [c_long; 5] =
response_msg.data.as_longs().try_into().unwrap();
[a as u32, b as u32, c as u32, d as u32, e as u32]
}),
};
wt.xconn
.xcb_connection()
.send_event(
false,
wt.root,
Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask),
*response_msg,
xproto::EventMask::SUBSTRUCTURE_NOTIFY
| xproto::EventMask::SUBSTRUCTURE_REDIRECT,
client_msg.serialize(),
)
.queue();
} else if client_msg.message_type == self.dnd.atoms.enter {
let source_window = client_msg.data.get_long(0) as c_ulong;
.expect_then_ignore_error("Failed to send `ClientMessage` event.");
} else if client_msg.message_type == atoms[XdndEnter] as c_ulong {
let source_window = client_msg.data.get_long(0) as xproto::Window;
let flags = client_msg.data.get_long(1);
let version = flags >> 24;
self.dnd.version = Some(version);
let has_more_types = flags - (flags & (c_long::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,
client_msg.data.get_long(2) as xproto::Atom,
client_msg.data.get_long(3) as xproto::Atom,
client_msg.data.get_long(4) as xproto::Atom,
];
self.dnd.type_list = Some(type_list);
} else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) }
{
self.dnd.type_list = Some(more_types);
}
} else if client_msg.message_type == self.dnd.atoms.position {
} else if client_msg.message_type == atoms[XdndPosition] as c_ulong {
// 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
@ -184,7 +204,7 @@ impl<T: 'static> EventProcessor<T> {
// 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 source_window = client_msg.data.get_long(0) as xproto::Window;
// Equivalent to `(x << shift) | y`
// where `shift = mem::size_of::<c_short>() * 8`
@ -202,7 +222,7 @@ impl<T: 'static> EventProcessor<T> {
//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(&atoms[TextUriList])
} else {
false
};
@ -212,10 +232,10 @@ impl<T: 'static> EventProcessor<T> {
unsafe {
if self.dnd.result.is_none() {
let time = if version >= 1 {
client_msg.data.get_long(3) as c_ulong
client_msg.data.get_long(3) as xproto::Timestamp
} else {
// In version 0, time isn't specified
ffi::CurrentTime
x11rb::CURRENT_TIME
};
// This results in the `SelectionNotify` event below
self.dnd.convert_selection(window, time);
@ -232,7 +252,7 @@ impl<T: 'static> EventProcessor<T> {
}
self.dnd.reset();
}
} else if client_msg.message_type == self.dnd.atoms.drop {
} else if client_msg.message_type == atoms[XdndDrop] as c_ulong {
let (source_window, state) = if let Some(source_window) = self.dnd.source_window
{
if let Some(Ok(ref path_list)) = self.dnd.result {
@ -247,7 +267,7 @@ impl<T: 'static> EventProcessor<T> {
} 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.get_long(0) as xproto::Window;
(source_window, DndState::Rejected)
};
unsafe {
@ -256,7 +276,7 @@ impl<T: 'static> EventProcessor<T> {
.expect("Failed to send `XdndFinished` message.");
}
self.dnd.reset();
} else if client_msg.message_type == self.dnd.atoms.leave {
} else if client_msg.message_type == atoms[XdndLeave] as c_ulong {
self.dnd.reset();
callback(Event::WindowEvent {
window_id,
@ -268,10 +288,10 @@ impl<T: 'static> EventProcessor<T> {
ffi::SelectionNotify => {
let xsel: &ffi::XSelectionEvent = xev.as_ref();
let window = xsel.requestor;
let window = xsel.requestor as xproto::Window;
let window_id = mkwid(window);
if xsel.property == self.dnd.atoms.selection {
if xsel.property == atoms[XdndSelection] as c_ulong {
let mut result = None;
// This is where we receive data from drag and drop
@ -294,7 +314,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::ConfigureNotify => {
let xev: &ffi::XConfigureEvent = xev.as_ref();
let xwindow = xev.window;
let xwindow = xev.window as xproto::Window;
let window_id = mkwid(xwindow);
if let Some(window) = self.with_window(xwindow, Arc::clone) {
@ -469,13 +489,13 @@ impl<T: 'static> EventProcessor<T> {
// effect is that we waste some time trying to query unsupported properties.
wt.xconn.update_cached_wm_info(wt.root);
self.with_window(xev.window, |window| {
self.with_window(xev.window as xproto::Window, |window| {
window.invalidate_cached_frame_extents();
});
}
ffi::MapNotify => {
let xev: &ffi::XMapEvent = xev.as_ref();
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
// XXX re-issue the focus state when mapping the window.
@ -494,7 +514,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::DestroyNotify => {
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
// In the event that the window's been destroyed without being dropped first, we
@ -505,7 +525,7 @@ impl<T: 'static> EventProcessor<T> {
// context here instead of when dropping the window.
wt.ime
.borrow_mut()
.remove_context(window)
.remove_context(window as ffi::Window)
.expect("Failed to destroy input context");
callback(Event::WindowEvent {
@ -516,7 +536,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::VisibilityNotify => {
let xev: &ffi::XVisibilityEvent = xev.as_ref();
let xwindow = xev.window;
let xwindow = xev.window as xproto::Window;
callback(Event::WindowEvent {
window_id: mkwid(xwindow),
event: WindowEvent::Occluded(xev.state == ffi::VisibilityFullyObscured),
@ -532,7 +552,7 @@ impl<T: 'static> EventProcessor<T> {
// 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 {
let window = xev.window;
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
callback(Event::RedrawRequested(window_id));
@ -548,7 +568,7 @@ impl<T: 'static> EventProcessor<T> {
};
let window_id = mkwid(window);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
let keycode = xkev.keycode as _;
@ -597,7 +617,7 @@ impl<T: 'static> EventProcessor<T> {
is_synthetic: false,
},
});
} else if let Some(ic) = wt.ime.borrow().get_context(window) {
} else if let Some(ic) = wt.ime.borrow().get_context(window as ffi::Window) {
let written = wt.xconn.lookup_utf8(ic, xkev);
if !written.is_empty() {
let event = Event::WindowEvent {
@ -642,7 +662,7 @@ impl<T: 'static> EventProcessor<T> {
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 window_id = mkwid(xev.event as xproto::Window);
let device_id = mkdid(xev.deviceid);
if (xev.flags & ffi::XIPointerEmulated) != 0 {
// Deliver multi-touch events instead of emulated mouse events.
@ -732,10 +752,11 @@ impl<T: 'static> EventProcessor<T> {
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 window = xev.event as xproto::Window;
let window_id = mkwid(window);
let new_cursor_pos = (xev.event_x, xev.event_y);
let cursor_moved = self.with_window(xev.event, |window| {
let cursor_moved = self.with_window(window, |window| {
let mut shared_state_lock = window.shared_state_lock();
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
});
@ -817,7 +838,8 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_Enter => {
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
let window = xev.event as xproto::Window;
let window_id = mkwid(window);
let device_id = mkdid(xev.deviceid);
if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) {
@ -838,7 +860,7 @@ impl<T: 'static> EventProcessor<T> {
}
}
if self.window_exists(xev.event) {
if self.window_exists(window) {
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
@ -857,13 +879,14 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_Leave => {
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
let window = xev.event as xproto::Window;
// 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);
let window_closed = !self.window_exists(window);
if !window_closed {
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
window_id: mkwid(window),
event: CursorLeft {
device_id: mkdid(xev.deviceid),
},
@ -872,21 +895,22 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let window = xev.event as xproto::Window;
wt.ime
.borrow_mut()
.focus(xev.event)
.expect("Failed to focus input context");
if self.active_window != Some(xev.event) {
self.active_window = Some(xev.event);
if self.active_window != Some(window) {
self.active_window = Some(window);
wt.update_listen_device_events(true);
let window_id = mkwid(xev.event);
let window_id = mkwid(window);
let position = PhysicalPosition::new(xev.event_x, xev.event_y);
if let Some(window) = self.with_window(xev.event, Arc::clone) {
if let Some(window) = self.with_window(window, Arc::clone) {
window.shared_state_lock().has_focus = true;
}
@ -933,7 +957,8 @@ impl<T: 'static> EventProcessor<T> {
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
if !self.window_exists(xev.event) {
let window = xev.event as xproto::Window;
if !self.window_exists(window) {
return;
}
@ -942,8 +967,8 @@ impl<T: 'static> EventProcessor<T> {
.unfocus(xev.event)
.expect("Failed to unfocus input context");
if self.active_window.take() == Some(xev.event) {
let window_id = mkwid(xev.event);
if self.active_window.take() == Some(window) {
let window_id = mkwid(window);
wt.update_listen_device_events(false);
@ -966,7 +991,7 @@ impl<T: 'static> EventProcessor<T> {
),
});
if let Some(window) = self.with_window(xev.event, Arc::clone) {
if let Some(window) = self.with_window(window, Arc::clone) {
window.shared_state_lock().has_focus = false;
}
@ -979,14 +1004,15 @@ impl<T: 'static> EventProcessor<T> {
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 window = xev.event as xproto::Window;
let window_id = mkwid(window);
let phase = match xev.evtype {
ffi::XI_TouchBegin => TouchPhase::Started,
ffi::XI_TouchUpdate => TouchPhase::Moved,
ffi::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!(),
};
if self.window_exists(xev.event) {
if self.window_exists(window) {
let id = xev.detail as u64;
let location = PhysicalPosition::new(xev.event_x, xev.event_y);
@ -997,7 +1023,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()),
position: location.cast(),
},
});
@ -1260,7 +1286,7 @@ impl<T: 'static> EventProcessor<T> {
}
let (window, event) = match self.ime_event_receiver.try_recv() {
Ok((window, event)) => (window, event),
Ok((window, event)) => (window as xproto::Window, event),
Err(_) => return,
};
@ -1313,7 +1339,7 @@ impl<T: 'static> EventProcessor<T> {
) where
F: FnMut(Event<'_, T>),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into());
// Update modifiers state and emit key events based on which keys are currently pressed.
for keycode in wt

View file

@ -274,7 +274,7 @@ impl ImeContext {
client_data: ffi::XPointer,
) -> Option<ffi::XIC> {
let preedit_callbacks = PreeditCallbacks::new(client_data);
let preedit_attr = util::XSmartPointer::new(
let preedit_attr = util::memory::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,
@ -354,7 +354,7 @@ impl ImeContext {
self.ic_spot = ffi::XPoint { x, y };
unsafe {
let preedit_attr = util::XSmartPointer::new(
let preedit_attr = util::memory::XSmartPointer::new(
xconn,
(xconn.xlib.XVaCreateNestedList)(
0,

View file

@ -7,9 +7,9 @@ use std::{
sync::{Arc, Mutex},
};
use super::{super::atoms::*, ffi, util, XConnection, XError};
use once_cell::sync::Lazy;
use super::{ffi, util, XConnection, XError};
use x11rb::protocol::xproto;
static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
@ -162,6 +162,12 @@ enum GetXimServersError {
InvalidUtf8(IntoStringError),
}
impl From<util::GetPropertyError> for GetXimServersError {
fn from(error: util::GetPropertyError) -> Self {
GetXimServersError::GetPropertyError(error)
}
}
// 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
@ -169,13 +175,21 @@ enum GetXimServersError {
// 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<XConnection>) -> Result<Vec<String>, GetXimServersError> {
let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0");
let atoms = xconn.atoms();
let servers_atom = atoms[XIM_SERVERS];
let root = (xconn.xlib.XDefaultRootWindow)(xconn.display);
let mut atoms: Vec<ffi::Atom> = xconn
.get_property(root, servers_atom, ffi::XA_ATOM)
.map_err(GetXimServersError::GetPropertyError)?;
.get_property::<xproto::Atom>(
root as xproto::Window,
servers_atom,
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(ffi::Atom::from)
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
(xconn.xlib.XGetAtomNames)(

View file

@ -1,5 +1,6 @@
#![cfg(x11_platform)]
mod atoms;
mod dnd;
mod event_processor;
pub mod ffi;
@ -25,10 +26,13 @@ use std::{
cell::{Cell, RefCell},
collections::{HashMap, HashSet, VecDeque},
ffi::CStr,
fmt,
mem::{self, MaybeUninit},
ops::Deref,
os::raw::*,
os::unix::io::RawFd,
os::{
raw::*,
unix::io::{AsRawFd, RawFd},
},
ptr,
rc::Rc,
slice,
@ -37,8 +41,20 @@ use std::{
};
use libc::{self, setlocale, LC_CTYPE};
use atoms::*;
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
use x11rb::protocol::{
xinput,
xproto::{self, ConnectionExt},
};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError,
};
use self::{
dnd::{Dnd, DndState},
event_processor::EventProcessor,
@ -60,10 +76,10 @@ type X11Source = Generic<RawFd>;
pub struct EventLoopWindowTarget<T> {
xconn: Arc<XConnection>,
wm_delete_window: ffi::Atom,
net_wm_ping: ffi::Atom,
wm_delete_window: xproto::Atom,
net_wm_ping: xproto::Atom,
ime_sender: ImeSender,
root: ffi::Window,
root: xproto::Window,
ime: RefCell<Ime>,
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
redraw_sender: Sender<WindowId>,
@ -106,11 +122,11 @@ impl<T: 'static> Clone for EventLoopProxy<T> {
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
let root = xconn.default_root().root;
let atoms = xconn.atoms();
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 wm_delete_window = atoms[WM_DELETE_WINDOW];
let net_wm_ping = atoms[_NET_WM_PING];
let dnd = Dnd::new(Arc::clone(&xconn))
.expect("Failed to call XInternAtoms when initializing drag and drop");
@ -149,7 +165,7 @@ impl<T: 'static> EventLoop<T> {
});
let randr_event_offset = xconn
.select_xrandr_input(root)
.select_xrandr_input(root as ffi::Window)
.expect("Failed to query XRandR extension");
let xi2ext = unsafe {
@ -223,7 +239,11 @@ impl<T: 'static> EventLoop<T> {
let handle = event_loop.handle();
// Create the X11 event dispatcher.
let source = X11Source::new(xconn.x11_fd, calloop::Interest::READ, calloop::Mode::Level);
let source = X11Source::new(
xconn.xcb_connection().as_raw_fd(),
calloop::Interest::READ,
calloop::Mode::Level,
);
handle
.insert_source(source, |_, _, _| Ok(calloop::PostAction::Continue))
.expect("Failed to register the X11 event dispatcher");
@ -254,8 +274,7 @@ impl<T: 'static> EventLoop<T> {
.expect("Failed to register the redraw event channel with the event loop");
let kb_state =
KbdState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) })
.unwrap();
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
let window_target = EventLoopWindowTarget {
ime,
@ -299,8 +318,12 @@ impl<T: 'static> EventLoop<T> {
// (The request buffer is flushed during `init_device`)
get_xtarget(&target)
.xconn
.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask)
.queue();
.select_xinput_events(
root,
ffi::XIAllDevices as _,
x11rb::protocol::xinput::XIEventMask::HIERARCHY,
)
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
get_xtarget(&target)
.xconn
@ -308,8 +331,7 @@ impl<T: 'static> EventLoop<T> {
0x100, // Use the "core keyboard device"
ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask,
)
.unwrap()
.queue();
.unwrap();
event_processor.init_device(ffi::XIAllDevices);
@ -592,25 +614,24 @@ impl<T> EventLoopWindowTarget<T> {
let device_events = self.device_events.get() == DeviceEvents::Always
|| (focus && self.device_events.get() == DeviceEvents::WhenFocused);
let mut mask = 0;
let mut mask = xinput::XIEventMask::from(0u32);
if device_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;
}
self.xconn
.select_xinput_events(self.root, ffi::XIAllMasterDevices, mask)
.queue();
.select_xinput_events(self.root, ffi::XIAllMasterDevices as _, mask)
.expect_then_ignore_error("Failed to update device event filter");
}
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.screen = self.xconn.default_screen_index() as c_int;
RawDisplayHandle::Xlib(display_handle)
}
}
@ -702,14 +723,133 @@ 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();
if let Ok(c) = xconn
.xcb_connection()
.destroy_window(window.id().0 as xproto::Window)
{
c.ignore_error();
}
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {
/// An error from the Xlib library.
Xlib(XError),
/// An error that occurred while trying to connect to the X server.
Connect(ConnectError),
/// An error that occurred over the connection medium.
Connection(ConnectionError),
/// An error that occurred logically on the X11 end.
X11(LogicalError),
/// The XID range has been exhausted.
XidsExhausted(IdsExhausted),
/// Got `null` from an Xlib function without a reason.
UnexpectedNull(&'static str),
}
impl fmt::Display for X11Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X11Error::Xlib(e) => write!(f, "Xlib error: {}", e),
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
}
}
}
impl std::error::Error for X11Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
X11Error::Xlib(e) => Some(e),
X11Error::Connect(e) => Some(e),
X11Error::Connection(e) => Some(e),
X11Error::XidsExhausted(e) => Some(e),
_ => None,
}
}
}
impl From<XError> for X11Error {
fn from(e: XError) -> Self {
X11Error::Xlib(e)
}
}
impl From<ConnectError> for X11Error {
fn from(e: ConnectError) -> Self {
X11Error::Connect(e)
}
}
impl From<ConnectionError> for X11Error {
fn from(e: ConnectionError) -> Self {
X11Error::Connection(e)
}
}
impl From<LogicalError> for X11Error {
fn from(e: LogicalError) -> Self {
X11Error::X11(e)
}
}
impl From<ReplyError> for X11Error {
fn from(value: ReplyError) -> Self {
match value {
ReplyError::ConnectionError(e) => e.into(),
ReplyError::X11Error(e) => e.into(),
}
}
}
impl From<ime::ImeContextCreationError> for X11Error {
fn from(value: ime::ImeContextCreationError) -> Self {
match value {
ime::ImeContextCreationError::XError(e) => e.into(),
ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"),
}
}
}
impl From<ReplyOrIdError> for X11Error {
fn from(value: ReplyOrIdError) -> Self {
match value {
ReplyOrIdError::ConnectionError(e) => e.into(),
ReplyOrIdError::X11Error(e) => e.into(),
ReplyOrIdError::IdsExhausted => Self::XidsExhausted(IdsExhausted),
}
}
}
/// The underlying x11rb connection that we are using.
type X11rbConnection = x11rb::xcb_ffi::XCBConnection;
/// Type alias for a void cookie.
type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>;
/// Extension trait for `Result<VoidCookie, E>`.
trait CookieResultExt {
/// Unwrap the send error and ignore the result.
fn expect_then_ignore_error(self, msg: &str);
}
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}
}
/// 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> {
@ -745,7 +885,7 @@ struct XExtension {
first_error_id: c_int,
}
fn mkwid(w: ffi::Window) -> crate::window::WindowId {
fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _))
}
fn mkdid(w: c_int) -> crate::event::DeviceId {

View file

@ -6,10 +6,10 @@ use once_cell::sync::Lazy;
use super::{
ffi::{
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
self, RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
},
util, XConnection, XError,
util, X11Error, XConnection,
};
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
@ -241,13 +241,13 @@ impl XConnection {
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} 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)
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
if resources.is_null() {
@ -256,7 +256,7 @@ impl XConnection {
let mut has_primary = false;
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root as ffi::Window);
let mut available = Vec::with_capacity((*resources).ncrtc as usize);
for crtc_index in 0..(*resources).ncrtc {
@ -311,7 +311,7 @@ impl XConnection {
.unwrap_or_else(MonitorHandle::dummy)
}
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, X11Error> {
let has_xrandr = unsafe {
let mut major = 0;
let mut minor = 0;

View file

@ -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<CString, ffi::Atom>;
static ATOM_CACHE: Lazy<Mutex<AtomCache>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048)));
impl XConnection {
pub fn get_atom<T: AsRef<CStr> + 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<Vec<ffi::Atom>, 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)
}
}

View file

@ -1,46 +1,31 @@
use super::*;
pub type ClientMsgPayload = [c_long; 5];
use x11rb::x11_utils::Serialize;
impl XConnection {
pub fn send_event<T: Into<ffi::XEvent>>(
&self,
target_window: c_ulong,
event_mask: Option<c_long>,
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<c_long>,
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<xproto::EventMask>,
data: impl Into<xproto::ClientMessageData>,
) -> Result<VoidCookie<'_>, X11Error> {
let event = xproto::ClientMessageEvent {
response_type: xproto::CLIENT_MESSAGE_EVENT,
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,
format: 32,
data: data.into(),
sequence: 0,
type_: message_type,
};
self.send_event(target_window, event_mask, event)
self.xcb_connection()
.send_event(
false,
target_window,
event_mask.unwrap_or(xproto::EventMask::NO_EVENT),
event.serialize(),
)
.map_err(Into::into)
}
}

View file

@ -1,11 +1,13 @@
use std::ffi::CString;
use x11rb::connection::Connection;
use crate::window::CursorIcon;
use super::*;
impl XConnection {
pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option<CursorIcon>) {
pub fn set_cursor_icon(&self, window: xproto::Window, cursor: Option<CursorIcon>) {
let cursor = *self
.cursor_cache
.lock()
@ -13,7 +15,8 @@ impl XConnection {
.entry(cursor)
.or_insert_with(|| self.get_cursor(cursor));
self.update_cursor(window, cursor);
self.update_cursor(window, cursor)
.expect("Failed to set cursor");
}
fn create_empty_cursor(&self) -> ffi::Cursor {
@ -59,11 +62,15 @@ impl XConnection {
}
}
fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) {
unsafe {
(self.xlib.XDefineCursor)(self.display, window, cursor);
fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> {
self.xcb_connection()
.change_window_attributes(
window,
&xproto::ChangeWindowAttributesAux::new().cursor(cursor as xproto::Cursor),
)?
.ignore_error();
self.flush_requests().expect("Failed to set the cursor");
}
self.xcb_connection().flush()?;
Ok(())
}
}

View file

@ -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<Self> {
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::<c_char>(),
Format::Short => mem::size_of::<c_short>(),
Format::Long => mem::size_of::<c_long>(),
}
}
}
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;
}

View file

@ -40,16 +40,9 @@ impl AaRect {
}
}
#[derive(Debug, Default)]
pub struct TranslatedCoords {
pub x_rel_root: c_int,
pub y_rel_root: c_int,
pub child: ffi::Window,
}
#[derive(Debug, Default)]
pub struct Geometry {
pub root: ffi::Window,
pub root: xproto::Window,
// If you want positions relative to the root window, use translate_coords.
// Note that the overwhelming majority of window managers are reparenting WMs, thus the window
// ID we get from window creation is for a nested window used as the window's client area. If
@ -69,14 +62,14 @@ pub struct Geometry {
#[derive(Debug, Clone)]
pub struct FrameExtents {
pub left: c_ulong,
pub right: c_ulong,
pub top: c_ulong,
pub bottom: c_ulong,
pub left: u32,
pub right: u32,
pub top: u32,
pub bottom: u32,
}
impl FrameExtents {
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self {
FrameExtents {
left,
right,
@ -85,7 +78,7 @@ impl FrameExtents {
}
}
pub fn from_border(border: c_ulong) -> Self {
pub fn from_border(border: u32) -> Self {
Self::new(border, border, border, border)
}
}
@ -144,52 +137,29 @@ impl XConnection {
// This is adequate for inner_position
pub fn translate_coords(
&self,
window: ffi::Window,
root: ffi::Window,
) -> Result<TranslatedCoords, XError> {
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)
window: xproto::Window,
root: xproto::Window,
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
self.xcb_connection()
.translate_coordinates(window, root, 0, 0)?
.reply()
.map_err(Into::into)
}
// This is adequate for inner_size
pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
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,
)
};
self.check_errors()?;
Ok(geometry)
pub fn get_geometry(
&self,
window: xproto::Window,
) -> Result<xproto::GetGeometryReply, X11Error> {
self.xcb_connection()
.get_geometry(window)?
.reply()
.map_err(Into::into)
}
fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
fn get_frame_extents(&self, window: xproto::Window) -> Option<FrameExtents> {
let atoms = self.atoms();
let extents_atom = atoms[_NET_FRAME_EXTENTS];
if !hint_is_supported(extents_atom) {
return None;
@ -198,8 +168,12 @@ impl XConnection {
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
// 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<Vec<c_ulong>> = self
.get_property(window, extents_atom, ffi::XA_CARDINAL)
let extents: Option<Vec<u32>> = self
.get_property(
window,
extents_atom,
xproto::Atom::from(xproto::AtomEnum::CARDINAL),
)
.ok();
extents.and_then(|extents| {
@ -216,52 +190,35 @@ impl XConnection {
})
}
pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
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<bool> {
let atoms = self.atoms();
let client_list_atom = atoms[_NET_CLIENT_LIST];
if !hint_is_supported(client_list_atom) {
return None;
}
let client_list: Option<Vec<ffi::Window>> = self
.get_property(root, client_list_atom, ffi::XA_WINDOW)
let client_list: Option<Vec<xproto::Window>> = self
.get_property(
root,
client_list_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
)
.ok();
client_list.map(|client_list| client_list.contains(&window))
client_list.map(|client_list| client_list.contains(&(window as xproto::Window)))
}
fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
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<xproto::Window, X11Error> {
let parent = self.xcb_connection().query_tree(window)?.reply()?.parent;
Ok(parent)
}
fn climb_hierarchy(
&self,
window: ffi::Window,
root: ffi::Window,
) -> Result<ffi::Window, XError> {
window: xproto::Window,
root: xproto::Window,
) -> Result<xproto::Window, X11Error> {
let mut outer_window = window;
loop {
let candidate = self.get_parent_window(outer_window)?;
@ -275,8 +232,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::*;
@ -288,7 +245,7 @@ impl XConnection {
let coords = self
.translate_coords(window, root)
.expect("Failed to translate window coordinates");
(coords.y_rel_root, coords.child)
(coords.dst_y, coords.child)
};
let (width, height, border) = {
@ -298,7 +255,7 @@ impl XConnection {
(
inner_geometry.width,
inner_geometry.height,
inner_geometry.border,
inner_geometry.border_width,
)
};
@ -353,7 +310,7 @@ impl XConnection {
.get_geometry(outer_window)
.expect("Failed to get outer window geometry");
(
outer_geometry.y_rel_parent,
outer_geometry.y,
outer_geometry.width,
outer_geometry.height,
)
@ -361,21 +318,16 @@ impl XConnection {
// Since we have the geometry of the outermost window and the geometry of the client
// 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 diff_x = outer_width.saturating_sub(width) as u32;
let diff_y = outer_height.saturating_sub(height) as u32;
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32;
let left = diff_x / 2;
let right = left;
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
let frame_extents = FrameExtents::new(
left as c_ulong,
right as c_ulong,
top as c_ulong,
bottom as c_ulong,
);
let frame_extents = FrameExtents::new(left, right, top, bottom);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,
@ -383,7 +335,7 @@ impl XConnection {
} else {
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
// border value. This is convenient, since we can use it to get an accurate frame.
let frame_extents = FrameExtents::from_border(border as c_ulong);
let frame_extents = FrameExtents::from_border(border.into());
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedBordered,

View file

@ -1,4 +1,3 @@
use std::slice;
use std::sync::Arc;
use super::*;
@ -66,25 +65,27 @@ pub enum WindowType {
}
impl WindowType {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> 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 => _NET_WM_WINDOW_TYPE_DESKTOP,
Dock => _NET_WM_WINDOW_TYPE_DOCK,
Toolbar => _NET_WM_WINDOW_TYPE_TOOLBAR,
Menu => _NET_WM_WINDOW_TYPE_MENU,
Utility => _NET_WM_WINDOW_TYPE_UTILITY,
Splash => _NET_WM_WINDOW_TYPE_SPLASH,
Dialog => _NET_WM_WINDOW_TYPE_DIALOG,
DropdownMenu => _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
PopupMenu => _NET_WM_WINDOW_TYPE_POPUP_MENU,
Tooltip => _NET_WM_WINDOW_TYPE_TOOLTIP,
Notification => _NET_WM_WINDOW_TYPE_NOTIFICATION,
Combo => _NET_WM_WINDOW_TYPE_COMBO,
Dnd => _NET_WM_WINDOW_TYPE_DND,
Normal => _NET_WM_WINDOW_TYPE_NORMAL,
};
unsafe { xconn.get_atom_unchecked(atom_name) }
let atoms = xconn.atoms();
atoms[atom_name]
}
}
@ -92,30 +93,27 @@ pub struct MotifHints {
hints: MwmHints,
}
#[repr(C)]
struct MwmHints {
flags: c_ulong,
functions: c_ulong,
decorations: c_ulong,
input_mode: c_long,
status: c_ulong,
flags: u32,
functions: u32,
decorations: u32,
input_mode: u32,
status: u32,
}
#[allow(dead_code)]
mod mwm {
use libc::c_ulong;
// Motif WM hints are obsolete, but still widely supported.
// https://stackoverflow.com/a/1909708
pub const MWM_HINTS_FUNCTIONS: c_ulong = 1 << 0;
pub const MWM_HINTS_DECORATIONS: c_ulong = 1 << 1;
pub const MWM_HINTS_FUNCTIONS: u32 = 1 << 0;
pub const MWM_HINTS_DECORATIONS: u32 = 1 << 1;
pub const MWM_FUNC_ALL: c_ulong = 1 << 0;
pub const MWM_FUNC_RESIZE: c_ulong = 1 << 1;
pub const MWM_FUNC_MOVE: c_ulong = 1 << 2;
pub const MWM_FUNC_MINIMIZE: c_ulong = 1 << 3;
pub const MWM_FUNC_MAXIMIZE: c_ulong = 1 << 4;
pub const MWM_FUNC_CLOSE: c_ulong = 1 << 5;
pub const MWM_FUNC_ALL: u32 = 1 << 0;
pub const MWM_FUNC_RESIZE: u32 = 1 << 1;
pub const MWM_FUNC_MOVE: u32 = 1 << 2;
pub const MWM_FUNC_MINIMIZE: u32 = 1 << 3;
pub const MWM_FUNC_MAXIMIZE: u32 = 1 << 4;
pub const MWM_FUNC_CLOSE: u32 = 1 << 5;
}
impl MotifHints {
@ -133,7 +131,7 @@ impl MotifHints {
pub fn set_decorations(&mut self, decorations: bool) {
self.hints.flags |= mwm::MWM_HINTS_DECORATIONS;
self.hints.decorations = decorations as c_ulong;
self.hints.decorations = decorations as u32;
}
pub fn set_maximizable(&mut self, maximizable: bool) {
@ -144,7 +142,7 @@ impl MotifHints {
}
}
fn add_func(&mut self, func: c_ulong) {
fn add_func(&mut self, func: u32) {
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 {
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
self.hints.functions &= !func;
@ -154,7 +152,7 @@ impl MotifHints {
}
}
fn remove_func(&mut self, func: c_ulong) {
fn remove_func(&mut self, func: u32) {
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 {
self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS;
self.hints.functions = mwm::MWM_FUNC_ALL;
@ -174,170 +172,47 @@ 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) }
}
}
pub(crate) struct NormalHints<'a> {
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
}
impl<'a> NormalHints<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
NormalHints {
size_hints: xconn.alloc_size_hints(),
}
}
pub fn get_resize_increments(&self) -> Option<(u32, u32)> {
has_flag(self.size_hints.flags, ffi::PResizeInc).then_some({
(
self.size_hints.width_inc as u32,
self.size_hints.height_inc as u32,
)
})
}
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
if let Some((x, y)) = position {
self.size_hints.flags |= ffi::PPosition;
self.size_hints.x = x as c_int;
self.size_hints.y = y as c_int;
} else {
self.size_hints.flags &= !ffi::PPosition;
}
}
// WARNING: This hint is obsolete
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
if let Some((width, height)) = size {
self.size_hints.flags |= ffi::PSize;
self.size_hints.width = width as c_int;
self.size_hints.height = height as c_int;
} else {
self.size_hints.flags &= !ffi::PSize;
}
}
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
if let Some((max_width, max_height)) = max_size {
self.size_hints.flags |= ffi::PMaxSize;
self.size_hints.max_width = max_width as c_int;
self.size_hints.max_height = max_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMaxSize;
}
}
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
if let Some((min_width, min_height)) = min_size {
self.size_hints.flags |= ffi::PMinSize;
self.size_hints.min_width = min_width as c_int;
self.size_hints.min_height = min_height as c_int;
} else {
self.size_hints.flags &= !ffi::PMinSize;
}
}
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
if let Some((width_inc, height_inc)) = resize_increments {
self.size_hints.flags |= ffi::PResizeInc;
self.size_hints.width_inc = width_inc as c_int;
self.size_hints.height_inc = height_inc as c_int;
} else {
self.size_hints.flags &= !ffi::PResizeInc;
}
}
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
if let Some((base_width, base_height)) = base_size {
self.size_hints.flags |= ffi::PBaseSize;
self.size_hints.base_width = base_width as c_int;
self.size_hints.base_height = base_height as c_int;
} else {
self.size_hints.flags &= !ffi::PBaseSize;
}
}
}
impl XConnection {
pub fn get_wm_hints(
&self,
window: ffi::Window,
) -> Result<XSmartPointer<'_, ffi::XWMHints>, XError> {
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
self.check_errors()?;
let wm_hints = if wm_hints.is_null() {
self.alloc_wm_hints()
} else {
XSmartPointer::new(self, wm_hints).unwrap()
};
Ok(wm_hints)
}
pub fn set_wm_hints(
&self,
window: ffi::Window,
wm_hints: XSmartPointer<'_, ffi::XWMHints>,
) -> Flusher<'_> {
unsafe {
(self.xlib.XSetWMHints)(self.display, window, wm_hints.ptr);
}
Flusher::new(self)
}
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
let size_hints = self.alloc_size_hints();
let mut supplied_by_user = MaybeUninit::uninit();
unsafe {
(self.xlib.XGetWMNormalHints)(
self.display,
window,
size_hints.ptr,
supplied_by_user.as_mut_ptr(),
);
}
self.check_errors().map(|_| NormalHints { size_hints })
}
pub fn set_normal_hints(
&self,
window: ffi::Window,
normal_hints: NormalHints<'_>,
) -> Flusher<'_> {
unsafe {
(self.xlib.XSetWMNormalHints)(self.display, window, 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) -> MotifHints {
let atoms = self.atoms();
let motif_hints = atoms[_MOTIF_WM_HINTS];
let mut hints = MotifHints::new();
if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
if let Ok(props) = self.get_property::<u32>(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.input_mode = props.get(3).cloned().unwrap_or(0);
hints.hints.status = props.get(4).cloned().unwrap_or(0);
}
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") };
#[allow(clippy::unnecessary_cast)]
pub fn set_motif_hints(
&self,
window: xproto::Window,
hints: &MotifHints,
) -> Result<VoidCookie<'_>, X11Error> {
let atoms = self.atoms();
let motif_hints = atoms[_MOTIF_WM_HINTS];
let hints_data: [u32; 5] = [
hints.hints.flags as u32,
hints.hints.functions as u32,
hints.hints.decorations as u32,
hints.hints.input_mode as u32,
hints.hints.status as u32,
];
self.change_property(
window,
motif_hints,
motif_hints,
PropMode::Replace,
hints.hints.as_slice(),
xproto::PropMode::REPLACE,
&hints_data,
)
}
}

View file

@ -1,9 +1,10 @@
use std::{slice, str};
use x11rb::protocol::xinput::{self, ConnectionExt as _};
use super::*;
pub const VIRTUAL_CORE_POINTER: c_int = 2;
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
pub const VIRTUAL_CORE_POINTER: u16 = 2;
pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
// 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.
@ -13,8 +14,8 @@ const TEXT_BUFFER_SIZE: usize = 1024;
// 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: xproto::Window,
pub child: xproto::Window,
pub root_x: c_double,
pub root_y: c_double,
pub win_x: c_double,
@ -38,82 +39,43 @@ impl<'a> Drop for PointerState<'a> {
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: xproto::Window,
device_id: u16,
mask: xinput::XIEventMask,
) -> Result<VoidCookie<'_>, X11Error> {
self.xcb_connection()
.xinput_xi_select_events(
window,
&mut event_mask as *mut ffi::XIEventMask,
1, // number of masks to read from pointer above
);
}
Flusher::new(self)
&[xinput::EventMask {
deviceid: device_id,
mask: vec![mask],
}],
)
.map_err(Into::into)
}
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher<'_>> {
let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id, mask, mask) };
pub fn select_xkb_events(&self, device_id: c_int, mask: c_ulong) -> Result<bool, X11Error> {
let status =
unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) };
if status == ffi::True {
Some(Flusher::new(self))
self.flush_requests()?;
Ok(true)
} else {
error!("Could not select XKB events: The XKB extension is not initialized!");
None
Ok(false)
}
}
pub fn query_pointer(
&self,
window: ffi::Window,
device_id: c_int,
) -> Result<PointerState<'_>, 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,
group,
relative_to_window,
})
}
window: xproto::Window,
device_id: u16,
) -> Result<xinput::XIQueryPointerReply, X11Error> {
self.xcb_connection()
.xinput_xi_query_pointer(window, device_id)?
.reply()
.map_err(Into::into)
}
fn lookup_utf8_inner(

View file

@ -40,20 +40,3 @@ 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")
}
pub fn alloc_wm_hints(&self) -> XSmartPointer<'_, ffi::XWMHints> {
XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() })
.expect("`XAllocWMHints` returned null; out of memory")
}
}

View file

@ -1,35 +1,30 @@
// 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;
mod input;
pub mod keys;
mod memory;
pub(crate) mod memory;
mod randr;
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::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
let wrapped = Some(value);
@ -48,30 +43,6 @@ where
bitset & flag == flag
}
#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."]
pub(crate) struct Flusher<'a> {
xconn: &'a XConnection,
}
impl<'a> Flusher<'a> {
pub fn new(xconn: &'a XConnection) -> Self {
Flusher { xconn }
}
// "I want this request sent now!"
pub fn flush(self) -> Result<(), XError> {
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) {}
}
impl XConnection {
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.

View file

@ -48,8 +48,8 @@ impl ModifierKeymap {
}
pub fn reset_from_x_connection(&mut self, xconn: &XConnection) {
unsafe {
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
{
let keymap = xconn.xcb_connection().get_modifier_mapping().expect("get_modifier_mapping failed").reply().expect("get_modifier_mapping failed");
if keymap.is_null() {
panic!("failed to allocate XModifierKeymap");

View file

@ -68,8 +68,7 @@ impl XConnection {
return None;
}
let screen = (self.xlib.XDefaultScreen)(self.display);
let bit_depth = (self.xlib.XDefaultDepth)(self.display, screen);
let bit_depth = self.default_root().root_depth;
let output_modes =
slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize);
@ -159,11 +158,11 @@ impl XConnection {
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
@ -197,11 +196,11 @@ impl XConnection {
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = (self.xlib.XDefaultRootWindow)(self.display);
let root = self.default_root().root;
let resources = if (major == 1 && minor >= 3) || major > 1 {
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window)
} else {
(self.xrandr.XRRGetScreenResources)(self.display, root)
(self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window)
};
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);

View file

@ -1,18 +1,28 @@
use super::*;
use bytemuck::{NoUninit, Pod};
use std::sync::Arc;
pub type Cardinal = c_long;
pub const CARDINAL_SIZE: usize = mem::size_of::<c_long>();
use x11rb::connection::Connection;
use x11rb::errors::ReplyError;
pub type Cardinal = u32;
pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
#[derive(Debug, Clone)]
pub enum GetPropertyError {
XError(XError),
TypeMismatch(ffi::Atom),
X11rbError(Arc<ReplyError>),
TypeMismatch(xproto::Atom),
FormatMismatch(c_int),
NothingAllocated,
}
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
fn from(e: T) -> Self {
Self::X11rbError(Arc::new(e.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 +33,150 @@ 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<T: Formattable>(
pub fn get_property<T: Pod>(
&self,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
window: xproto::Window,
property: xproto::Atom,
property_type: xproto::Atom,
) -> Result<Vec<T>, GetPropertyError> {
let mut data = Vec::new();
let mut offset = 0;
let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type);
let mut data = vec![];
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,
window,
property,
// This offset is in terms of 32-bit chunks.
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::<T>() * 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;
loop {
if !iter.next_window(&mut data)? {
break;
}
}
Ok(data)
}
pub fn change_property<'a, T: Formattable>(
pub fn change_property<'a, T: 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>(), T::FORMAT.get_actual_size());
unsafe {
(self.xlib.XChangeProperty)(
self.display,
) -> Result<VoidCookie<'a>, X11Error> {
assert!([1usize, 2, 4].contains(&mem::size_of::<T>()));
self.xcb_connection()
.change_property(
mode,
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:{:?}",
property,
new_value,
);*/
Flusher::new(self)
(mem::size_of::<T>() * 8) as u8,
new_value
.len()
.try_into()
.expect("too many items for propery"),
bytemuck::cast_slice::<T, u8>(new_value),
)
.map_err(Into::into)
}
}
/// An iterator over the "windows" of the property that we are fetching.
struct PropIterator<'a, C: ?Sized, T> {
/// Handle to the connection.
conn: &'a C,
/// The window that we're fetching the property from.
window: xproto::Window,
/// The property that we're fetching.
property: xproto::Atom,
/// The type of the property that we're fetching.
property_type: xproto::Atom,
/// The offset of the next window, in 32-bit chunks.
offset: u32,
/// The format of the type.
format: u8,
/// Keep a reference to `T`.
_phantom: std::marker::PhantomData<T>,
}
impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> {
/// Create a new property iterator.
fn new(
conn: &'a C,
window: xproto::Window,
property: xproto::Atom,
property_type: xproto::Atom,
) -> Self {
let format = match mem::size_of::<T>() {
1 => 8,
2 => 16,
4 => 32,
_ => unreachable!(),
};
Self {
conn,
window,
property,
property_type,
offset: 0,
format,
_phantom: Default::default(),
}
}
/// Get the next window and append it to `data`.
///
/// Returns whether there are more windows to fetch.
fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> {
// Send the request and wait for the reply.
let reply = self
.conn
.get_property(
false,
self.window,
self.property,
self.property_type,
self.offset,
PROPERTY_BUFFER_SIZE,
)?
.reply()?;
// Make sure that the reply is of the correct type.
if reply.type_ != self.property_type {
return Err(GetPropertyError::TypeMismatch(reply.type_));
}
// Make sure that the reply is of the correct format.
if reply.format != self.format {
return Err(GetPropertyError::FormatMismatch(reply.format.into()));
}
// Append the data to the output.
if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
// We can just do a bytewise append.
data.extend_from_slice(bytemuck::cast_slice(&reply.value));
} else {
// Rust's borrowing and types system makes this a bit tricky.
//
// We need to make sure that the data is properly aligned. Unfortunately the best
// safe way to do this is to copy the data to another buffer and then append.
//
// TODO(notgull): It may be worth it to use `unsafe` to copy directly from
// `reply.value` to `data`; check if this is faster. Use benchmarks!
let old_len = data.len();
let added_len = reply.value.len() / mem::size_of::<T>();
data.resize(old_len + added_len, T::zeroed());
bytemuck::cast_slice_mut::<T, u8>(&mut data[old_len..]).copy_from_slice(&reply.value);
}
// Check `bytes_after` to see if there are more windows to fetch.
self.offset += PROPERTY_BUFFER_SIZE;
Ok(reply.bytes_after != 0)
}
}

View file

@ -16,11 +16,11 @@ pub const MOVERESIZE_LEFT: isize = 7;
pub const MOVERESIZE_MOVE: isize = 8;
// This info is global to the window manager.
static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> =
static SUPPORTED_HINTS: Lazy<Mutex<Vec<xproto::Atom>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(0)));
static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
pub fn hint_is_supported(hint: xproto::Atom) -> bool {
(*SUPPORTED_HINTS.lock().unwrap()).contains(&hint)
}
@ -33,20 +33,27 @@ pub fn wm_name_is_one_of(names: &[&str]) -> bool {
}
impl XConnection {
pub fn update_cached_wm_info(&self, root: ffi::Window) {
pub fn update_cached_wm_info(&self, root: xproto::Window) {
*SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root);
*WM_NAME.lock().unwrap() = self.get_wm_name(root);
}
fn get_supported_hints(&self, root: ffi::Window) -> Vec<ffi::Atom> {
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) -> Vec<xproto::Atom> {
let atoms = self.atoms();
let supported_atom = atoms[_NET_SUPPORTED];
self.get_property(
root,
supported_atom,
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
.unwrap_or_else(|_| Vec::with_capacity(0))
}
fn get_wm_name(&self, root: ffi::Window) -> Option<String> {
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") };
#[allow(clippy::useless_conversion)]
fn get_wm_name(&self, root: xproto::Window) -> Option<String> {
let atoms = self.atoms();
let check_atom = atoms[_NET_SUPPORTING_WM_CHECK];
let wm_name_atom = 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,7 +77,11 @@ 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::<xproto::Window>(
root,
check_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
);
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
@ -80,7 +91,11 @@ impl XConnection {
// 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::<xproto::Window>(
root_window_wm_check.into(),
check_atom,
xproto::Atom::from(xproto::AtomEnum::WINDOW),
);
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
@ -94,9 +109,11 @@ impl XConnection {
// 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 atoms = self.atoms();
let utf8_string_atom = atoms[UTF8_STRING];
let result = self.get_property(root_window_wm_check, wm_name_atom, utf8_string_atom);
let result =
self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom);
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
// string. For more fun trivia, IceWM is also unique in including version and uname
@ -105,13 +122,17 @@ 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::Atom::from(xproto::AtomEnum::STRING))
} 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.into(),
wm_name_atom,
xproto::Atom::from(xproto::AtomEnum::STRING),
)
} else {
result
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,14 @@
use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr, sync::Mutex};
use std::{
collections::HashMap,
error::Error,
fmt, ptr,
sync::{Arc, Mutex},
};
use crate::window::CursorIcon;
use super::ffi;
use super::{atoms::Atoms, ffi};
use x11rb::{connection::Connection, protocol::xproto, xcb_ffi::XCBConnection};
/// A connection to an X server.
pub(crate) struct XConnection {
@ -11,9 +17,21 @@ pub(crate) struct XConnection {
pub xrandr: ffi::Xrandr_2_2_0,
pub xcursor: ffi::Xcursor,
pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb,
pub display: *mut ffi::Display,
pub x11_fd: c_int,
/// The manager for the XCB connection.
///
/// The `Option` ensures that we can drop it before we close the `Display`.
xcb: Option<XCBConnection>,
/// The atoms used by `winit`.
///
/// This is a large structure, so I've elected to Box it to make accessing the fields of
/// this struct easier. Feel free to unbox it if you like kicking puppies.
atoms: Box<Atoms>,
/// The index of the default screen.
default_screen: usize,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
}
@ -45,17 +63,38 @@ impl XConnection {
display
};
// Get X11 socket file descriptor
let fd = unsafe { (xlib.XConnectionNumber)(display) };
// Open the x11rb XCB connection.
let xcb = {
// Get a pointer to the underlying XCB connection
let xcb_connection =
unsafe { (xlib_xcb.XGetXCBConnection)(display as *mut ffi::Display) };
assert!(!xcb_connection.is_null());
// Wrap the XCB connection in an x11rb XCB connection
let conn =
unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection.cast(), false) };
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
};
// Get the default screen.
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
// Fetch the atoms.
let atoms = Atoms::new(&xcb)
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
.reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
Ok(XConnection {
xlib,
xrandr,
xcursor,
xinput2,
xlib_xcb,
display,
x11_fd: fd,
xcb: Some(xcb),
atoms: Box::new(atoms),
default_screen,
latest_error: Mutex::new(None),
cursor_cache: Default::default(),
})
@ -71,6 +110,32 @@ impl XConnection {
Ok(())
}
}
/// Get the underlying XCB connection.
#[inline]
pub fn xcb_connection(&self) -> &XCBConnection {
self.xcb
.as_ref()
.expect("xcb_connection somehow called after drop?")
}
/// Get the list of atoms.
#[inline]
pub fn atoms(&self) -> &Atoms {
&self.atoms
}
/// Get the index of the default screen.
#[inline]
pub fn default_screen_index(&self) -> usize {
self.default_screen
}
/// Get the default screen.
#[inline]
pub fn default_root(&self) -> &xproto::Screen {
&self.xcb_connection().setup().roots[self.default_screen]
}
}
impl fmt::Debug for XConnection {
@ -82,6 +147,7 @@ impl fmt::Debug for XConnection {
impl Drop for XConnection {
#[inline]
fn drop(&mut self) {
self.xcb = None;
unsafe { (self.xlib.XCloseDisplay)(self.display) };
}
}
@ -112,8 +178,12 @@ 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
XOpenDisplayFailed, // TODO: add better message.
/// We encountered an error while converting the connection to XCB.
XcbConversionError(Arc<dyn Error + Send + Sync + 'static>),
}
impl From<ffi::OpenError> for XNotSupported {
@ -128,6 +198,7 @@ impl XNotSupported {
match self {
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB",
}
}
}
@ -137,6 +208,7 @@ impl Error for XNotSupported {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
XNotSupported::LibraryOpenError(ref err) => Some(err),
XNotSupported::XcbConversionError(ref err) => Some(&**err),
_ => None,
}
}
@ -147,3 +219,19 @@ impl fmt::Display for XNotSupported {
formatter.write_str(self.description())
}
}
/// A newtype wrapper around a `ConnectError` that can't be accessed by downstream libraries.
///
/// Without this, `x11rb` would become a public dependency.
#[derive(Debug)]
struct WrapConnectError(x11rb::rust_connection::ConnectError);
impl fmt::Display for WrapConnectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl Error for WrapConnectError {
// We can't implement `source()` here or otherwise risk exposing `x11rb`.
}