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

@ -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
}