Replace parts of the Xlib backend with x11-rb
This commit is contained in:
parent
5379d60e4d
commit
d7ec899d69
24 changed files with 1550 additions and 1395 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue