X11: General cleanup (#491)

* X11: General cleanup

This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.

- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).

* X11: Add better diagnostics when initialization fails

* X11: Handle XIQueryDevice failure

* X11: Use correct types in error handler
This commit is contained in:
Francesca Frangipane 2018-05-03 09:15:49 -04:00 committed by GitHub
parent fee874b5b7
commit c4b92ebd45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1911 additions and 1340 deletions

View file

@ -0,0 +1,59 @@
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::*;
use parking_lot::Mutex;
use super::*;
type AtomCache = HashMap<CString, ffi::Atom>;
lazy_static! {
static ref ATOM_CACHE: Mutex<AtomCache> = Mutex::new(HashMap::with_capacity(2048));
}
pub unsafe fn get_atom(xconn: &Arc<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> {
let name = CStr::from_bytes_with_nul_unchecked(name); // I trust you. Don't let me down.
let mut atom_cache_lock = ATOM_CACHE.lock();
let cached_atom = (*atom_cache_lock).get(name).cloned();
if let Some(atom) = cached_atom {
Ok(atom)
} else {
let atom = (xconn.xlib.XInternAtom)(
xconn.display,
name.as_ptr() as *const c_char,
ffi::False,
);
/*println!(
"XInternAtom name:{:?} atom:{:?}",
name,
atom,
);*/
xconn.check_errors()?;
(*atom_cache_lock).insert(name.to_owned(), atom);
Ok(atom)
}
}
// 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(
xconn: &Arc<XConnection>,
names: &[*mut c_char],
) -> Result<Vec<ffi::Atom>, XError> {
let mut atoms = Vec::with_capacity(names.len());
(xconn.xlib.XInternAtoms)(
xconn.display,
names.as_ptr() as *mut _,
names.len() as c_int,
ffi::False,
atoms.as_mut_ptr(),
);
xconn.check_errors()?;
atoms.set_len(names.len());
/*println!(
"XInternAtoms atoms:{:?}",
atoms,
);*/
Ok(atoms)
}

View file

@ -0,0 +1,350 @@
use super::*;
#[derive(Debug)]
pub struct TranslatedCoords {
pub x_rel_root: c_int,
pub y_rel_root: c_int,
pub child: ffi::Window,
}
// This is adequate for get_inner_position
pub unsafe fn translate_coords(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Result<TranslatedCoords, XError> {
let mut translated_coords: TranslatedCoords = mem::uninitialized();
(xconn.xlib.XTranslateCoordinates)(
xconn.display,
window,
root,
0,
0,
&mut translated_coords.x_rel_root,
&mut translated_coords.y_rel_root,
&mut translated_coords.child,
);
//println!("XTranslateCoordinates coords:{:?}", translated_coords);
xconn.check_errors().map(|_| translated_coords)
}
#[derive(Debug)]
pub struct Geometry {
pub root: ffi::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
// you call get_geometry with that window ID, then you'll get the position of that client area
// window relative to the parent it's nested in (the frame), which isn't helpful if you want
// to know the frame position.
pub x_rel_parent: c_int,
pub y_rel_parent: c_int,
// In that same case, this will give you client area size.
pub width: c_uint,
pub height: c_uint,
// xmonad and dwm were the only WMs tested that use the border return at all.
// The majority of WMs seem to simply fill it with 0 unconditionally.
pub border: c_uint,
pub depth: c_uint,
}
// This is adequate for get_inner_size
pub unsafe fn get_geometry(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Result<Geometry, XError> {
let mut geometry: Geometry = mem::uninitialized();
let _status = (xconn.xlib.XGetGeometry)(
xconn.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,
);
//println!("XGetGeometry geo:{:?}", geometry);
xconn.check_errors().map(|_| geometry)
}
#[derive(Debug, Clone)]
pub struct FrameExtents {
pub left: c_ulong,
pub right: c_ulong,
pub top: c_ulong,
pub bottom: c_ulong,
}
impl FrameExtents {
pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
FrameExtents { left, right, top, bottom }
}
pub fn from_border(border: c_ulong) -> Self {
Self::new(border, border, border, border)
}
}
fn get_frame_extents(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Option<self::FrameExtents> {
let extents_atom = unsafe { self::get_atom(xconn, b"_NET_FRAME_EXTENTS\0") }
.expect("Failed to call XInternAtom (_NET_FRAME_EXTENTS)");
if !self::hint_is_supported(extents_atom) {
return None;
}
// 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>> = unsafe {
self::get_property(
xconn,
window,
extents_atom,
ffi::XA_CARDINAL,
)
}.ok();
extents.and_then(|extents| {
if extents.len() >= 4 {
Some(self::FrameExtents {
left: extents[0],
right: extents[1],
top: extents[2],
bottom: extents[3],
})
} else {
None
}
})
}
pub fn is_top_level(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Option<bool> {
let client_list_atom = unsafe { self::get_atom(xconn, b"_NET_CLIENT_LIST\0") }
.expect("Failed to call XInternAtom (_NET_CLIENT_LIST)");
if !self::hint_is_supported(client_list_atom) {
return None;
}
let client_list: Option<Vec<ffi::Window>> = unsafe {
self::get_property(
xconn,
root,
client_list_atom,
ffi::XA_WINDOW,
)
}.ok();
client_list.map(|client_list| client_list.contains(&window))
}
unsafe fn get_parent_window(
xconn: &Arc<XConnection>,
window: ffi::Window,
) -> Result<ffi::Window, XError> {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: c_uint = mem::uninitialized();
let _status = (xconn.xlib.XQueryTree)(
xconn.display,
window,
&mut root,
&mut parent,
&mut children,
&mut nchildren,
);
// The list of children isn't used
if children != ptr::null_mut() {
(xconn.xlib.XFree)(children as *mut _);
}
xconn.check_errors().map(|_| parent)
}
fn climb_hierarchy(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> Result<ffi::Window, XError> {
let mut outer_window = window;
loop {
let candidate = unsafe { get_parent_window(xconn, outer_window) }?;
if candidate == root {
break;
}
outer_window = candidate;
}
Ok(outer_window)
}
#[derive(Debug, Clone, PartialEq)]
pub enum FrameExtentsHeuristicPath {
Supported,
UnsupportedNested,
UnsupportedBordered,
}
#[derive(Debug, Clone)]
pub struct FrameExtentsHeuristic {
pub frame_extents: FrameExtents,
pub heuristic_path: FrameExtentsHeuristicPath,
}
impl FrameExtentsHeuristic {
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
use self::FrameExtentsHeuristicPath::*;
if self.heuristic_path != UnsupportedBordered {
(x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
} else {
(x, y)
}
}
pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
(
width.saturating_add(
self.frame_extents.left.saturating_add(self.frame_extents.right) as u32
),
height.saturating_add(
self.frame_extents.top.saturating_add(self.frame_extents.bottom) as u32
),
)
}
}
pub fn get_frame_extents_heuristic(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
) -> FrameExtentsHeuristic {
use self::FrameExtentsHeuristicPath::*;
// Position relative to root window.
// With rare exceptions, this is the position of a nested window. Cases where the window
// isn't nested are outlined in the comments throghout this function, but in addition to
// that, fullscreen windows often aren't nested.
let (inner_y_rel_root, child) = {
let coords = unsafe { translate_coords(xconn, window, root) }
.expect("Failed to translate window coordinates");
(
coords.y_rel_root,
coords.child,
)
};
let (width, height, border) = {
let inner_geometry = unsafe { get_geometry(xconn, window) }
.expect("Failed to get inner window geometry");
(
inner_geometry.width,
inner_geometry.height,
inner_geometry.border,
)
};
// The first condition is only false for un-nested windows, but isn't always false for
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
// when y is on the range [0, 2] and if the window has been unfocused since being
// undecorated (or was undecorated upon construction), the first condition is true,
// requiring us to rely on the second condition.
let nested = !(window == child || is_top_level(xconn, child, root) == Some(true));
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
if let Some(mut frame_extents) = get_frame_extents(xconn, window) {
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
// decorations are disabled, but since the window becomes un-nested, it's easy to
// catch.
if !nested {
frame_extents = FrameExtents::new(0, 0, 0, 0);
}
// The difference between the nested window's position and the outermost window's
// position is equivalent to the frame size. In most scenarios, this is equivalent to
// manually climbing the hierarchy as is done in the case below. Here's a list of
// known discrepancies:
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
// addition to a 1px semi-transparent border. The margin can be easily observed by
// using a screenshot tool to get a screenshot of a selected window, and is
// presumably used for drawing drop shadows. Getting window geometry information
// via hierarchy-climbing results in this margin being included in both the
// position and outer size, so a window positioned at (0, 0) would be reported as
// having a position (-10, -8).
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
// on all sides, and there's no additional border.
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
// Without decorations, there's no difference. This is presumably related to
// Enlightenment's fairly unique concept of window position; it interprets
// positions given to XMoveWindow as a client area position rather than a position
// of the overall window.
FrameExtentsHeuristic {
frame_extents,
heuristic_path: Supported,
}
} else if nested {
// If the position value we have is for a nested window used as the client area, we'll
// just climb up the hierarchy and get the geometry of the outermost window we're
// nested in.
let outer_window = climb_hierarchy(xconn, window, root)
.expect("Failed to climb window hierarchy");
let (outer_y, outer_width, outer_height) = {
let outer_geometry = unsafe { get_geometry(xconn, outer_window) }
.expect("Failed to get outer window geometry");
(
outer_geometry.y_rel_parent,
outer_geometry.width,
outer_geometry.height,
)
};
// 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 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.into(),
right.into(),
top.into(),
bottom.into(),
);
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedNested,
}
} 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.into());
FrameExtentsHeuristic {
frame_extents,
heuristic_path: UnsupportedBordered,
}
}
}

View file

@ -0,0 +1,20 @@
use super::*;
pub const MWM_HINTS_DECORATIONS: c_ulong = 2;
#[derive(Debug)]
pub enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
_Toggle = 2, // _NET_WM_STATE_TOGGLE
}
impl From<bool> for StateOperation {
fn from(b: bool) -> Self {
if b {
StateOperation::Add
} else {
StateOperation::Remove
}
}
}

View file

@ -0,0 +1,187 @@
use super::*;
use events::ModifiersState;
pub unsafe fn select_xinput_events(
xconn: &Arc<XConnection>,
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,
};
(xconn.xinput2.XISelectEvents)(
xconn.display,
window,
&mut event_mask as *mut ffi::XIEventMask,
1, // number of masks to read from pointer above
);
Flusher::new(xconn)
}
#[allow(dead_code)]
pub unsafe fn select_xkb_events(
xconn: &Arc<XConnection>,
device_id: c_uint,
mask: c_ulong,
) -> Option<Flusher> {
let status = (xconn.xlib.XkbSelectEvents)(
xconn.display,
device_id,
mask,
mask,
);
if status == ffi::True {
Some(Flusher::new(xconn))
} else {
None
}
}
impl From<ffi::XIModifierState> for ModifiersState {
fn from(mods: ffi::XIModifierState) -> Self {
let state = mods.effective as c_uint;
ModifiersState {
alt: state & ffi::Mod1Mask != 0,
shift: state & ffi::ShiftMask != 0,
ctrl: state & ffi::ControlMask != 0,
logo: state & ffi::Mod4Mask != 0,
}
}
}
pub struct PointerState<'a> {
xconn: &'a Arc<XConnection>,
_root: ffi::Window,
_child: ffi::Window,
_root_x: c_double,
_root_y: c_double,
_win_x: c_double,
_win_y: c_double,
_buttons: ffi::XIButtonState,
modifiers: ffi::XIModifierState,
_group: ffi::XIGroupState,
_relative_to_window: bool,
}
impl<'a> PointerState<'a> {
pub fn get_modifier_state(&self) -> ModifiersState {
self.modifiers.into()
}
}
impl<'a> Drop for PointerState<'a> {
fn drop(&mut self) {
unsafe {
// This is why you need to read the docs carefully...
(self.xconn.xlib.XFree)(self._buttons.mask as _);
}
}
}
pub unsafe fn query_pointer(
xconn: &Arc<XConnection>,
window: ffi::Window,
device_id: c_int,
) -> Result<PointerState, XError> {
let mut root_return = mem::uninitialized();
let mut child_return = mem::uninitialized();
let mut root_x_return = mem::uninitialized();
let mut root_y_return = mem::uninitialized();
let mut win_x_return = mem::uninitialized();
let mut win_y_return = mem::uninitialized();
let mut buttons_return = mem::uninitialized();
let mut modifiers_return = mem::uninitialized();
let mut group_return = mem::uninitialized();
let relative_to_window = (xconn.xinput2.XIQueryPointer)(
xconn.display,
device_id,
window,
&mut root_return,
&mut child_return,
&mut root_x_return,
&mut root_y_return,
&mut win_x_return,
&mut win_y_return,
&mut buttons_return,
&mut modifiers_return,
&mut group_return,
) == ffi::True;
xconn.check_errors()?;
Ok(PointerState {
xconn,
_root: root_return,
_child: child_return,
_root_x: root_x_return,
_root_y: root_y_return,
_win_x: win_x_return,
_win_y: win_y_return,
_buttons: buttons_return,
modifiers: modifiers_return,
_group: group_return,
_relative_to_window: relative_to_window,
})
}
unsafe fn lookup_utf8_inner(
xconn: &Arc<XConnection>,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
buffer: &mut [u8],
) -> (ffi::KeySym, ffi::Status, c_int) {
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let count = (xconn.xlib.Xutf8LookupString)(
ic,
key_event,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
&mut keysym,
&mut status,
);
(keysym, status, count)
}
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
// re-allocate (and make another round-trip) in the *vast* majority of cases.
// To test if lookup_utf8 works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024;
pub unsafe fn lookup_utf8(
xconn: &Arc<XConnection>,
ic: ffi::XIC,
key_event: &mut ffi::XKeyEvent,
) -> String {
let mut buffer: [u8; TEXT_BUFFER_SIZE] = mem::uninitialized();
let (_, status, count) = lookup_utf8_inner(
xconn,
ic,
key_event,
&mut buffer,
);
// The buffer overflowed, so we'll make a new one on the heap.
if status == ffi::XBufferOverflow {
let mut buffer = Vec::with_capacity(count as usize);
buffer.set_len(count as usize);
let (_, _, new_count) = lookup_utf8_inner(
xconn,
ic,
key_event,
&mut buffer,
);
debug_assert_eq!(count, new_count);
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
} else {
str::from_utf8(&buffer[..count as usize])
.unwrap_or("")
.to_string()
}
}

View file

@ -0,0 +1,177 @@
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
// *results may vary
mod atom;
mod geometry;
mod hint;
mod input;
mod window_property;
mod wm;
pub use self::atom::*;
pub use self::geometry::*;
pub use self::hint::*;
pub use self::input::*;
pub use self::window_property::*;
pub use self::wm::*;
use std::mem;
use std::ptr;
use std::str;
use std::sync::Arc;
use std::ops::{Deref, DerefMut};
use std::os::raw::*;
use super::{ffi, XConnection, XError};
// 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)]
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 is_same_size_as<T>(&self) -> bool {
mem::size_of::<T>() == self.get_actual_size()
}
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 struct XSmartPointer<'a, T> {
xconn: &'a Arc<XConnection>,
pub ptr: *mut T,
}
impl<'a, T> XSmartPointer<'a, T> {
// You're responsible for only passing things to this that should be XFree'd.
// Returns None if ptr is null.
pub fn new(xconn: &'a Arc<XConnection>, ptr: *mut T) -> Option<Self> {
if !ptr.is_null() {
Some(XSmartPointer {
xconn,
ptr,
})
} else {
None
}
}
}
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.ptr }
}
}
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<'a, T> Drop for XSmartPointer<'a, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);
}
}
}
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.
// This buffer contains the requests you make, and is flushed under various circumstances:
// 1. XPending, XNextEvent, and XWindowEvent flush "as needed"
// 2. XFlush explicitly flushes
// 3. XSync flushes and blocks until all requests are responded to
// 4. Calls that have a return dependent on a response (i.e. XGetWindowProperty) sync internally.
// When in doubt, check the X11 source; if a function calls _XReply, it flushes and waits.
// All util functions that abstract an async function will return a Flusher.
pub unsafe fn flush_requests(xconn: &Arc<XConnection>) -> Result<(), XError> {
(xconn.xlib.XFlush)(xconn.display);
//println!("XFlush");
// This isn't necessarily a useful time to check for errors (since our request hasn't
// necessarily been processed yet)
xconn.check_errors()
}
pub unsafe fn sync_with_server(xconn: &Arc<XConnection>) -> Result<(), XError> {
(xconn.xlib.XSync)(xconn.display, ffi::False);
//println!("XSync");
xconn.check_errors()
}
#[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 struct Flusher<'a> {
xconn: &'a Arc<XConnection>,
}
impl<'a> Flusher<'a> {
pub fn new(xconn: &'a Arc<XConnection>) -> Self {
Flusher { xconn }
}
// "I want this request sent now!"
pub fn flush(self) -> Result<(), XError> {
unsafe { flush_requests(self.xconn) }
}
// "I'm aware that this request hasn't been sent, and I'm okay with waiting."
pub fn queue(self) {}
}
pub unsafe fn send_client_msg(
xconn: &Arc<XConnection>,
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: (c_long, c_long, c_long, c_long, c_long),
) -> Flusher {
let mut event: ffi::XClientMessageEvent = mem::uninitialized();
event.type_ = ffi::ClientMessage;
event.display = xconn.display;
event.window = window;
event.message_type = message_type;
event.format = Format::Long as c_int;
event.data = ffi::ClientMessageData::new();
event.data.set_long(0, data.0);
event.data.set_long(1, data.1);
event.data.set_long(2, data.2);
event.data.set_long(3, data.3);
event.data.set_long(4, data.4);
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
(xconn.xlib.XSendEvent)(
xconn.display,
target_window,
ffi::False,
event_mask,
&mut event.into(),
);
Flusher::new(xconn)
}

View file

@ -0,0 +1,160 @@
use std;
use std::fmt::Debug;
use super::*;
#[derive(Debug, Clone)]
pub enum GetPropertyError {
XError(XError),
TypeMismatch(ffi::Atom),
FormatMismatch(c_int),
NothingAllocated,
}
impl GetPropertyError {
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
if let GetPropertyError::TypeMismatch(actual_type) = *self {
actual_type == t
} else {
false
}
}
}
// 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!
pub unsafe fn get_property<T: Debug + Clone>(
xconn: &Arc<XConnection>,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
) -> Result<Vec<T>, GetPropertyError> {
let mut data = Vec::new();
let mut offset = 0;
let mut done = false;
while !done {
let mut actual_type: ffi::Atom = mem::uninitialized();
let mut actual_format: c_int = mem::uninitialized();
let mut quantity_returned: c_ulong = mem::uninitialized();
let mut bytes_after: c_ulong = mem::uninitialized();
let mut buf: *mut c_uchar = ptr::null_mut();
(xconn.xlib.XGetWindowProperty)(
xconn.display,
window,
property,
// This offset is in terms of 32-bit chunks.
offset,
// This is the quanity 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) = xconn.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 _)
.map(|actual_format| !actual_format.is_same_size_as::<T>())
// This won't actually be reached; the XError condition above is triggered first.
.unwrap_or(true);
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.
(xconn.xlib.XFree)(buf as _); // Don't try to access new_data after this.
} else {
return Err(GetPropertyError::NothingAllocated);
}
done = bytes_after == 0;
}
Ok(data)
}
#[derive(Debug)]
pub enum PropMode {
Replace = ffi::PropModeReplace as isize,
_Prepend = ffi::PropModePrepend as isize,
_Append = ffi::PropModeAppend as isize,
}
#[derive(Debug, Clone)]
pub struct InvalidFormat {
format_used: Format,
size_passed: usize,
size_expected: usize,
}
pub unsafe fn change_property<'a, T: Debug>(
xconn: &'a Arc<XConnection>,
window: c_ulong,
property: ffi::Atom,
property_type: ffi::Atom,
format: Format,
mode: PropMode,
new_value: &[T],
) -> Flusher<'a> {
if !format.is_same_size_as::<T>() {
panic!(format!(
"[winit developer error] Incorrect usage of `util::change_property`: {:#?}",
InvalidFormat {
format_used: format,
size_passed: mem::size_of::<T>() * 8,
size_expected: format.get_actual_size() * 8,
},
));
}
(xconn.xlib.XChangeProperty)(
xconn.display,
window,
property,
property_type,
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(xconn)
}

View file

@ -0,0 +1,158 @@
use parking_lot::Mutex;
use super::*;
// This info is global to the window manager.
lazy_static! {
static ref SUPPORTED_HINTS: Mutex<Vec<ffi::Atom>> = Mutex::new(Vec::with_capacity(0));
static ref WM_NAME: Mutex<Option<String>> = Mutex::new(None);
}
pub fn hint_is_supported(hint: ffi::Atom) -> bool {
(*SUPPORTED_HINTS.lock()).contains(&hint)
}
pub fn wm_name_is_one_of(names: &[&str]) -> bool {
if let Some(ref name) = *WM_NAME.lock() {
names.contains(&name.as_str())
} else {
false
}
}
pub fn update_cached_wm_info(xconn: &Arc<XConnection>, root: ffi::Window) {
*SUPPORTED_HINTS.lock() = self::get_supported_hints(xconn, root);
*WM_NAME.lock() = self::get_wm_name(xconn, root);
}
fn get_supported_hints(xconn: &Arc<XConnection>, root: ffi::Window) -> Vec<ffi::Atom> {
let supported_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTED\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTED)");
unsafe {
self::get_property(
xconn,
root,
supported_atom,
ffi::XA_ATOM,
)
}.unwrap_or_else(|_| Vec::with_capacity(0))
}
fn get_wm_name(xconn: &Arc<XConnection>, root: ffi::Window) -> Option<String> {
let check_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTING_WM_CHECK\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTING_WM_CHECK)");
let wm_name_atom = unsafe { self::get_atom(xconn, b"_NET_WM_NAME\0") }
.expect("Failed to call XInternAtom (_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
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
// regardless of whether or not the WM claims to support it.
//
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
// in 0.72.
/*if !supported_hints.contains(&check_atom) {
return None;
}*/
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
/*if !supported_hints.contains(&wm_name_atom) {
return None;
}*/
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
// 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 = unsafe {
self::get_property(
xconn,
root,
check_atom,
ffi::XA_WINDOW,
)
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// Querying the same property on the child window we were given, we should get this child
// window's ID again.
let child_window_wm_check = {
let result = unsafe {
self::get_property(
xconn,
root_window_wm_check,
check_atom,
ffi::XA_WINDOW,
)
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// These values should be the same.
if root_window_wm_check != child_window_wm_check {
return None;
}
// All of that work gives us a window ID that we can get the WM name from.
let wm_name = {
let utf8_string_atom = unsafe { self::get_atom(xconn, b"UTF8_STRING\0") }
.expect("Failed to call XInternAtom (UTF8_STRING)");
let result = unsafe {
self::get_property(
xconn,
root_window_wm_check,
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
// information in this string (this means you'll have to be careful if you want to match
// against it, though).
// 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)
} else {
false
};
if no_utf8 {
unsafe {
self::get_property(
xconn,
root_window_wm_check,
wm_name_atom,
ffi::XA_STRING,
)
}
} else {
result
}
}.ok();
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
}