diff --git a/src/platform/linux/x11/dnd.rs b/src/platform/linux/x11/dnd.rs index 571608a7..c8221fb4 100644 --- a/src/platform/linux/x11/dnd.rs +++ b/src/platform/linux/x11/dnd.rs @@ -40,7 +40,7 @@ impl DndAtoms { b"text/uri-list\0".as_ptr() as *mut c_char, b"None\0".as_ptr() as *mut c_char, ]; - let atoms = unsafe { util::get_atoms(xconn, &names) }?; + let atoms = unsafe { xconn.get_atoms(&names) }?; Ok(DndAtoms { aware: atoms[0], enter: atoms[1], @@ -127,13 +127,12 @@ impl Dnd { DndState::Accepted => (1, self.atoms.action_private as c_long), DndState::Rejected => (0, self.atoms.none as c_long), }; - util::send_client_msg( - &self.xconn, + self.xconn.send_client_msg( target_window, target_window, self.atoms.status, None, - (this_window as c_long, accepted, 0, 0, action), + [this_window as c_long, accepted, 0, 0, action], ).flush() } @@ -147,13 +146,12 @@ impl Dnd { DndState::Accepted => (1, self.atoms.action_private as c_long), DndState::Rejected => (0, self.atoms.none as c_long), }; - util::send_client_msg( - &self.xconn, + self.xconn.send_client_msg( target_window, target_window, self.atoms.finished, None, - (this_window as c_long, accepted, action, 0, 0), + [this_window as c_long, accepted, action, 0, 0], ).flush() } @@ -161,8 +159,7 @@ impl Dnd { &self, source_window: c_ulong, ) -> Result, util::GetPropertyError> { - util::get_property( - &self.xconn, + self.xconn.get_property( source_window, self.atoms.type_list, ffi::XA_ATOM, @@ -184,8 +181,7 @@ impl Dnd { &self, window: c_ulong, ) -> Result, util::GetPropertyError> { - util::get_property( - &self.xconn, + self.xconn.get_property( window, self.atoms.selection, self.atoms.uri_list, diff --git a/src/platform/linux/x11/ime/input_method.rs b/src/platform/linux/x11/ime/input_method.rs index 185a9d20..7af30126 100644 --- a/src/platform/linux/x11/ime/input_method.rs +++ b/src/platform/linux/x11/ime/input_method.rs @@ -85,13 +85,11 @@ enum GetXimServersError { // modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set // XMODIFIERS to `@server=ibus`?!?" unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXimServersError> { - let servers_atom = util::get_atom(&xconn, b"XIM_SERVERS\0") - .map_err(GetXimServersError::XError)?; + let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0"); let root = (xconn.xlib.XDefaultRootWindow)(xconn.display); - let mut atoms: Vec = util::get_property( - &xconn, + let mut atoms: Vec = xconn.get_property( root, servers_atom, ffi::XA_ATOM, diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index 1c362a8d..8e3623d0 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -75,8 +75,7 @@ impl EventsLoop { pub fn new(display: Arc) -> EventsLoop { let root = unsafe { (display.xlib.XDefaultRootWindow)(display.display) }; - let wm_delete_window = unsafe { util::get_atom(&display, b"WM_DELETE_WINDOW\0") } - .expect("Failed to call XInternAtom (WM_DELETE_WINDOW)"); + let wm_delete_window = unsafe { display.get_atom_unchecked(b"WM_DELETE_WINDOW\0") }; let dnd = Dnd::new(Arc::clone(&display)) .expect("Failed to call XInternAtoms when initializing drag and drop"); @@ -130,7 +129,7 @@ impl EventsLoop { } } - util::update_cached_wm_info(&display, root); + display.update_cached_wm_info(root); let wakeup_dummy_window = unsafe { let (x, y, w, h) = (10, 10, 10, 10); @@ -166,14 +165,8 @@ impl EventsLoop { }; // Register for device hotplug events - unsafe { - util::select_xinput_events( - &result.display, - root, - ffi::XIAllDevices, - ffi::XI_HierarchyChangedMask, - ) - }.queue(); // The request buffer is flushed during init_device + // (The request buffer is flushed during `init_device`) + result.display.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask).queue(); result.init_device(ffi::XIAllDevices); @@ -484,11 +477,7 @@ impl EventsLoop { .unwrap() .inner_pos_to_outer(inner_x, inner_y) } else { - let extents = util::get_frame_extents_heuristic( - &self.display, - window, - self.root, - ); + let extents = self.display.get_frame_extents_heuristic(window, self.root); let outer_pos = extents.inner_pos_to_outer(inner_x, inner_y); (*window_state_lock).frame_extents = Some(extents); outer_pos @@ -513,7 +502,7 @@ impl EventsLoop { // (which is almost all of them). Failing to correctly update WM info doesn't // really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only // effect is that we waste some time trying to query unsupported properties. - util::update_cached_wm_info(&self.display, self.root); + self.display.update_cached_wm_info(self.root); self.shared_state .borrow() @@ -614,7 +603,7 @@ impl EventsLoop { if state == Pressed { let written = if let Some(ic) = self.ime.borrow().get_context(window) { - unsafe { util::lookup_utf8(&self.display, ic, xkev) } + self.display.lookup_utf8(ic, xkev) } else { return; }; @@ -841,13 +830,8 @@ impl EventsLoop { // The mods field on this event isn't actually populated, so query the // pointer device. In the future, we can likely remove this round-trip by // relying on Xkb for modifier values. - let modifiers = unsafe { - util::query_pointer( - &self.display, - xev.event, - xev.deviceid, - ) - }.expect("Failed to query pointer device").get_modifier_state(); + let modifiers = self.display.query_pointer(xev.event, xev.deviceid) + .expect("Failed to query pointer device").get_modifier_state(); callback(Event::WindowEvent { window_id, event: CursorMoved { device_id, @@ -1094,16 +1078,13 @@ impl EventsLoopProxy { // NOTE: This design is taken from the old `WindowProxy::wakeup` implementation. It // assumes that X11 is thread safe. Is this true? // (WARNING: it's probably not true) - unsafe { - util::send_client_msg( - &display, - self.wakeup_dummy_window, - self.wakeup_dummy_window, - 0, - None, - (0, 0, 0, 0, 0), - ) - }.flush().expect("Failed to call XSendEvent after wakeup"); + display.send_client_msg( + self.wakeup_dummy_window, + self.wakeup_dummy_window, + 0, + None, + [0, 0, 0, 0, 0], + ).flush().expect("Failed to call XSendEvent after wakeup"); Ok(()) } @@ -1320,14 +1301,8 @@ impl Device { | ffi::XI_RawButtonReleaseMask | ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask; - unsafe { - util::select_xinput_events( - &el.display, - el.root, - info.deviceid, - mask, - ) - }.queue(); // The request buffer is flushed when we poll for events + // The request buffer is flushed when we poll for events + el.display.select_xinput_events(el.root, info.deviceid, mask).queue(); // Identify scroll axes for class_ptr in Device::classes(info) { diff --git a/src/platform/linux/x11/monitor.rs b/src/platform/linux/x11/monitor.rs index 4d18f058..62311e4b 100644 --- a/src/platform/linux/x11/monitor.rs +++ b/src/platform/linux/x11/monitor.rs @@ -66,7 +66,7 @@ impl MonitorId { repr: util::MonitorRepr, primary: bool, ) -> Self { - let (name, hidpi_factor) = unsafe { util::get_output_info(xconn, resources, &repr) }; + let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr) }; let (dimensions, position) = unsafe { (repr.get_dimensions(), repr.get_position()) }; let rect = util::Rect::new(position, dimensions); MonitorId { diff --git a/src/platform/linux/x11/util/atom.rs b/src/platform/linux/x11/util/atom.rs index beb73cdb..fe97d3ae 100644 --- a/src/platform/linux/x11/util/atom.rs +++ b/src/platform/linux/x11/util/atom.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::ffi::{CStr, CString}; +use std::fmt::Debug; use std::os::raw::*; use parking_lot::Mutex; @@ -12,48 +13,60 @@ lazy_static! { static ref ATOM_CACHE: Mutex = Mutex::new(HashMap::with_capacity(2048)); } -pub unsafe fn get_atom(xconn: &Arc, name: &[u8]) -> Result { - 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, +impl XConnection { + pub fn get_atom + Debug>(&self, name: T) -> ffi::Atom { + let name = name.as_ref(); + let mut atom_cache_lock = ATOM_CACHE.lock(); + 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 { + let msg = format!( + "`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}", + name, + self.check_errors(), + ); + panic!(msg); + } + /*println!( + "XInternAtom name:{:?} atom:{:?}", + name, + atom, + );*/ + (*atom_cache_lock).insert(name.to_owned(), atom); + atom + } + } + + pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom { + debug_assert!(CStr::from_bytes_with_nul(name).is_ok()); + let name = CStr::from_bytes_with_nul_unchecked(name); + self.get_atom(name) + } + + // Note: this doesn't use caching, for the sake of simplicity. + // If you're dealing with this many atoms, you'll usually want to cache them locally anyway. + pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result, XError> { + let mut atoms = Vec::with_capacity(names.len()); + (self.xlib.XInternAtoms)( + self.display, + names.as_ptr() as *mut _, + names.len() as c_int, ffi::False, + atoms.as_mut_ptr(), ); + self.check_errors()?; + atoms.set_len(names.len()); /*println!( - "XInternAtom name:{:?} atom:{:?}", - name, - atom, + "XInternAtoms atoms:{:?}", + atoms, );*/ - xconn.check_errors()?; - (*atom_cache_lock).insert(name.to_owned(), atom); - Ok(atom) + Ok(atoms) } } - -// 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, - names: &[*mut c_char], -) -> Result, 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) -} diff --git a/src/platform/linux/x11/util/client_msg.rs b/src/platform/linux/x11/util/client_msg.rs new file mode 100644 index 00000000..399db4f1 --- /dev/null +++ b/src/platform/linux/x11/util/client_msg.rs @@ -0,0 +1,95 @@ +use super::*; + +pub type ClientMsgPayload = [c_long; 5]; + +impl XConnection { + pub fn send_event>( + &self, + target_window: c_ulong, + event_mask: Option, + event: T, + ) -> Flusher { + let event_mask = event_mask.unwrap_or(ffi::NoEventMask); + unsafe { + (self.xlib.XSendEvent)( + self.display, + target_window, + ffi::False, + event_mask, + &mut event.into(), + ); + } + Flusher::new(self) + } + + pub fn send_client_msg( + &self, + window: c_ulong, // The window this is "about"; not necessarily this window + target_window: c_ulong, // The window we're sending to + message_type: ffi::Atom, + event_mask: Option, + data: ClientMsgPayload, + ) -> Flusher { + let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() }; + event.type_ = ffi::ClientMessage; + event.display = self.display; + event.window = window; + event.message_type = message_type; + event.format = c_long::FORMAT as c_int; + event.data = unsafe { mem::transmute(data) }; + self.send_event(target_window, event_mask, event) + } + + // Prepare yourself for the ultimate in unsafety! + // You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you + // to send more than one message worth of data. + pub fn send_client_msg_multi( + &self, + window: c_ulong, // The window this is "about"; not necessarily this window + target_window: c_ulong, // The window we're sending to + message_type: ffi::Atom, + event_mask: Option, + data: &[T], + ) -> Flusher { + let format = T::FORMAT; + let size_of_t = mem::size_of::(); + debug_assert_eq!(size_of_t, format.get_actual_size()); + let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() }; + event.type_ = ffi::ClientMessage; + event.display = self.display; + event.window = window; + event.message_type = message_type; + event.format = format as c_int; + + let t_per_payload = format.get_payload_size() / size_of_t; + assert!(t_per_payload > 0); + let payload_count = data.len() / t_per_payload; + let payload_remainder = data.len() % t_per_payload; + let payload_ptr = data.as_ptr() as *const ClientMsgPayload; + + let mut payload_index = 0; + while payload_index < payload_count { + let payload = unsafe { payload_ptr.offset(payload_index as isize) }; + payload_index += 1; + event.data = unsafe { mem::transmute(*payload) }; + self.send_event(target_window, event_mask, &event).queue(); + } + + if payload_remainder > 0 { + let mut payload: ClientMsgPayload = [0; 5]; + let t_payload = payload.as_mut_ptr() as *mut T; + let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) }; + let invalid_t_payload = invalid_payload as *const T; + let mut t_index = 0; + while t_index < payload_remainder { + let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) }; + unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() }; + t_index += 1; + } + event.data = unsafe { mem::transmute(payload) }; + self.send_event(target_window, event_mask, &event).queue(); + } + + Flusher::new(self) + } +} diff --git a/src/platform/linux/x11/util/format.rs b/src/platform/linux/x11/util/format.rs new file mode 100644 index 00000000..7871472b --- /dev/null +++ b/src/platform/linux/x11/util/format.rs @@ -0,0 +1,58 @@ +use std::fmt::Debug; +use std::mem; +use std::os::raw::*; + +// This isn't actually the number of the bits in the format. +// X11 does a match on this value to determine which type to call sizeof on. +// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64. +// ...if that sounds confusing, then you know why this enum is here. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Format { + Char = 8, + Short = 16, + Long = 32, +} + +impl Format { + pub fn from_format(format: usize) -> Option { + match format { + 8 => Some(Format::Char), + 16 => Some(Format::Short), + 32 => Some(Format::Long), + _ => None, + } + } + + pub fn is_same_size_as(&self) -> bool { + mem::size_of::() == self.get_actual_size() + } + + pub fn get_actual_size(&self) -> usize { + match self { + &Format::Char => mem::size_of::(), + &Format::Short => mem::size_of::(), + &Format::Long => mem::size_of::(), + } + } + + pub fn get_payload_size(&self) -> usize { + match self { + // Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit). + &Format::Char => mem::size_of::() * 20, + &Format::Short => mem::size_of::() * 10, + &Format::Long => mem::size_of::() * 5, + } + } +} + +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_char { const FORMAT: Format = Format::Char; } +impl Formattable for c_uchar { const FORMAT: Format = Format::Char; } +impl Formattable for c_short { const FORMAT: Format = Format::Short; } +impl Formattable for c_ushort { const FORMAT: Format = Format::Short; } +impl Formattable for c_long { const FORMAT: Format = Format::Long; } +impl Formattable for c_ulong { const FORMAT: Format = Format::Long; } diff --git a/src/platform/linux/x11/util/geometry.rs b/src/platform/linux/x11/util/geometry.rs index a1393a41..3c4bd9bd 100644 --- a/src/platform/linux/x11/util/geometry.rs +++ b/src/platform/linux/x11/util/geometry.rs @@ -42,30 +42,6 @@ pub struct TranslatedCoords { pub child: ffi::Window, } -// This is adequate for get_inner_position -pub unsafe fn translate_coords( - xconn: &Arc, - window: ffi::Window, - root: ffi::Window, -) -> Result { - 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, @@ -86,30 +62,6 @@ pub struct Geometry { pub depth: c_uint, } -// This is adequate for get_inner_size -pub unsafe fn get_geometry( - xconn: &Arc, - window: ffi::Window, -) -> Result { - 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, @@ -128,109 +80,6 @@ impl FrameExtents { } } -fn get_frame_extents( - xconn: &Arc, - window: ffi::Window, -) -> Option { - 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> = 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, - window: ffi::Window, - root: ffi::Window, -) -> Option { - 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> = 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, - window: ffi::Window, -) -> Result { - 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, - window: ffi::Window, - root: ffi::Window, -) -> Result { - 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, @@ -266,120 +115,237 @@ impl FrameExtentsHeuristic { } } -pub fn get_frame_extents_heuristic( - xconn: &Arc, - window: ffi::Window, - root: ffi::Window, -) -> FrameExtentsHeuristic { - use self::FrameExtentsHeuristicPath::*; +impl XConnection { + // This is adequate for get_inner_position + pub fn translate_coords(&self, window: ffi::Window, root: ffi::Window) -> Result { + let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() }; + unsafe { + (self.xlib.XTranslateCoordinates)( + self.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); + self.check_errors().map(|_| translated_coords) + } - // 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, - ) - }; + // This is adequate for get_inner_size + pub fn get_geometry(&self, window: ffi::Window) -> Result { + let mut geometry: Geometry = unsafe { mem::uninitialized() }; + 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, + ) + }; + //println!("XGetGeometry geo:{:?}", geometry); + self.check_errors().map(|_| geometry) + } - 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, - ) - }; + fn get_frame_extents(&self, window: ffi::Window) -> Option { + let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") }; - // 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); + if !hint_is_supported(extents_atom) { + return None; } - // 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. + // 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> = self.get_property( + window, + extents_atom, + ffi::XA_CARDINAL, + ).ok(); - FrameExtentsHeuristic { - frame_extents, - heuristic_path: Supported, + extents.and_then(|extents| { + if extents.len() >= 4 { + Some(FrameExtents { + left: extents[0], + right: extents[1], + top: extents[2], + bottom: extents[3], + }) + } else { + None + } + }) + } + + pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option { + let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") }; + + if !hint_is_supported(client_list_atom) { + return None; } - } 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"); + let client_list: Option> = self.get_property( + root, + client_list_atom, + ffi::XA_WINDOW, + ).ok(); + + client_list.map(|client_list| client_list.contains(&window)) + } + + fn get_parent_window(&self, window: ffi::Window) -> Result { + let parent = unsafe { + let mut root: 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(); + + // 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 != ptr::null_mut() { + (self.xlib.XFree)(children as *mut _); + } + + parent + }; + self.check_errors().map(|_| parent) + } + + fn climb_hierarchy(&self, window: ffi::Window, root: ffi::Window) -> Result { + let mut outer_window = window; + loop { + let candidate = self.get_parent_window(outer_window)?; + if candidate == root { + break; + } + outer_window = candidate; + } + Ok(outer_window) + } + + pub fn get_frame_extents_heuristic(&self, 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 = self.translate_coords(window, root).expect("Failed to translate window coordinates"); ( - outer_geometry.y_rel_parent, - outer_geometry.width, - outer_geometry.height, + coords.y_rel_root, + coords.child, ) }; - // 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 (width, height, border) = { + let inner_geometry = self.get_geometry(window).expect("Failed to get inner window geometry"); + ( + inner_geometry.width, + inner_geometry.height, + inner_geometry.border, + ) + }; - let left = diff_x / 2; - let right = left; - let top = offset_y; - let bottom = diff_y.saturating_sub(offset_y); + // 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 || self.is_top_level(child, root) == Some(true)); - 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, + // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames. + if let Some(mut frame_extents) = self.get_frame_extents(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 = self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy"); + let (outer_y, outer_width, outer_height) = { + let outer_geometry = self.get_geometry(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, + } } } } diff --git a/src/platform/linux/x11/util/hint.rs b/src/platform/linux/x11/util/hint.rs index abd90a16..0727af6e 100644 --- a/src/platform/linux/x11/util/hint.rs +++ b/src/platform/linux/x11/util/hint.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use super::*; pub const MWM_HINTS_DECORATIONS: c_ulong = 2; @@ -6,12 +8,12 @@ pub const MWM_HINTS_DECORATIONS: c_ulong = 2; pub enum StateOperation { Remove = 0, // _NET_WM_STATE_REMOVE Add = 1, // _NET_WM_STATE_ADD - _Toggle = 2, // _NET_WM_STATE_TOGGLE + Toggle = 2, // _NET_WM_STATE_TOGGLE } impl From for StateOperation { - fn from(b: bool) -> Self { - if b { + fn from(op: bool) -> Self { + if op { StateOperation::Add } else { StateOperation::Remove @@ -62,7 +64,30 @@ impl WindowType { &Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", &Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", }; - unsafe { get_atom(xconn, atom_name) } - .expect("Failed to get atom for `WindowType`") + unsafe { xconn.get_atom_unchecked(atom_name) } + } +} + +impl XConnection { + pub fn get_wm_hints(&self, window: ffi::Window) -> Result, 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) -> Flusher { + unsafe { + (self.xlib.XSetWMHints)( + self.display, + window, + wm_hints.ptr, + ); + } + Flusher::new(self) } } diff --git a/src/platform/linux/x11/util/input.rs b/src/platform/linux/x11/util/input.rs index 7ff6ef6d..aa31c0f5 100644 --- a/src/platform/linux/x11/util/input.rs +++ b/src/platform/linux/x11/util/input.rs @@ -1,44 +1,12 @@ +use std::str; + use super::*; use events::ModifiersState; -pub unsafe fn select_xinput_events( - xconn: &Arc, - 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, - device_id: c_uint, - mask: c_ulong, -) -> Option { - let status = (xconn.xlib.XkbSelectEvents)( - xconn.display, - device_id, - mask, - mask, - ); - if status == ffi::True { - Some(Flusher::new(xconn)) - } else { - None - } -} +// 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; impl From for ModifiersState { fn from(mods: ffi::XIModifierState) -> Self { @@ -53,17 +21,17 @@ impl From for ModifiersState { } pub struct PointerState<'a> { - xconn: &'a Arc, - _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, + xconn: &'a 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, + group: ffi::XIGroupState, + relative_to_window: bool, } impl<'a> PointerState<'a> { @@ -74,114 +42,115 @@ impl<'a> PointerState<'a> { 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 _); + if !self.buttons.mask.is_null() { + unsafe { + // This is why you need to read the docs carefully... + (self.xconn.xlib.XFree)(self.buttons.mask as _); + } } } } -pub unsafe fn query_pointer( - xconn: &Arc, - window: ffi::Window, - device_id: c_int, -) -> Result { - 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(); +impl XConnection { + pub fn select_xinput_events(&self, window: c_ulong, device_id: c_int, mask: i32) -> Flusher { + let mut event_mask = ffi::XIEventMask { + deviceid: device_id, + mask: &mask as *const _ as *mut c_uchar, + mask_len: mem::size_of_val(&mask) as c_int, + }; + unsafe { + (self.xinput2.XISelectEvents)( + self.display, + window, + &mut event_mask as *mut ffi::XIEventMask, + 1, // number of masks to read from pointer above + ); + } + Flusher::new(self) + } - 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; + #[allow(dead_code)] + pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option { + let status = unsafe { + (self.xlib.XkbSelectEvents)( + self.display, + device_id, + mask, + mask, + ) + }; + if status == ffi::True { + Some(Flusher::new(self)) + } else { + None + } + } - xconn.check_errors()?; + pub fn query_pointer(&self, window: ffi::Window, device_id: c_int) -> Result { + unsafe { + let mut pointer_state: PointerState = mem::uninitialized(); + pointer_state.xconn = self; + pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)( + self.display, + device_id, + window, + &mut pointer_state.root, + &mut pointer_state.child, + &mut pointer_state.root_x, + &mut pointer_state.root_y, + &mut pointer_state.win_x, + &mut pointer_state.win_y, + &mut pointer_state.buttons, + &mut pointer_state.modifiers, + &mut pointer_state.group, + ) == ffi::True; + if let Err(err) = self.check_errors() { + // Running the destrutor would be bad news for us... + mem::forget(pointer_state); + Err(err) + } else { + Ok(pointer_state) + } + } + } - 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, - }) -} + fn lookup_utf8_inner( + &self, + 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 = unsafe { + (self.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) + } -unsafe fn lookup_utf8_inner( - xconn: &Arc, - 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, - 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() + pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { + let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() }; + let (_, status, count) = self.lookup_utf8_inner(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); + unsafe { buffer.set_len(count as usize) }; + let (_, _, new_count) = self.lookup_utf8_inner(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() + } } } diff --git a/src/platform/linux/x11/util/memory.rs b/src/platform/linux/x11/util/memory.rs new file mode 100644 index 00000000..eef0bdc9 --- /dev/null +++ b/src/platform/linux/x11/util/memory.rs @@ -0,0 +1,62 @@ +use std::ops::{Deref, DerefMut}; + +use super::*; + +pub struct XSmartPointer<'a, T> { + xconn: &'a 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 XConnection, ptr: *mut T) -> Option { + 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 _); + } + } +} + +impl XConnection { + pub fn alloc_class_hint(&self) -> XSmartPointer { + XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() }) + .expect("`XAllocClassHint` returned null; out of memory") + } + + pub fn alloc_size_hints(&self) -> XSmartPointer { + XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() }) + .expect("`XAllocSizeHints` returned null; out of memory") + } + + pub fn alloc_wm_hints(&self) -> XSmartPointer { + XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() }) + .expect("`XAllocWMHints` returned null; out of memory") + } +} diff --git a/src/platform/linux/x11/util/mod.rs b/src/platform/linux/x11/util/mod.rs index e67faf82..101521f5 100644 --- a/src/platform/linux/x11/util/mod.rs +++ b/src/platform/linux/x11/util/mod.rs @@ -2,180 +2,80 @@ // *results may vary mod atom; +mod client_msg; +mod format; mod geometry; mod hint; mod icon; mod input; +mod memory; mod randr; mod window_property; mod wm; pub use self::atom::*; +pub use self::client_msg::*; +pub use self::format::*; pub use self::geometry::*; pub use self::hint::*; pub use self::icon::*; pub use self::input::*; +pub use self::memory::*; pub use self::randr::*; 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 { - match format { - 8 => Some(Format::Char), - 16 => Some(Format::Short), - 32 => Some(Format::Long), - _ => None, - } - } - - pub fn is_same_size_as(&self) -> bool { - mem::size_of::() == self.get_actual_size() - } - - pub fn get_actual_size(&self) -> usize { - match self { - &Format::Char => mem::size_of::(), - &Format::Short => mem::size_of::(), - &Format::Long => mem::size_of::(), - } - } -} - -pub struct XSmartPointer<'a, T> { - xconn: &'a Arc, - 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, ptr: *mut T) -> Option { - 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) -> 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) -> 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, + xconn: &'a XConnection, } impl<'a> Flusher<'a> { - pub fn new(xconn: &'a Arc) -> Self { + pub fn new(xconn: &'a XConnection) -> Self { Flusher { xconn } } // "I want this request sent now!" pub fn flush(self) -> Result<(), XError> { - unsafe { flush_requests(self.xconn) } + 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) {} } -pub unsafe fn send_client_msg( - xconn: &Arc, - window: c_ulong, // The window this is "about"; not necessarily this window - target_window: c_ulong, // The window we're sending to - message_type: ffi::Atom, - event_mask: Option, - data: (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); +impl XConnection { + // 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 fn flush_requests(&self) -> Result<(), XError> { + unsafe { (self.xlib.XFlush)(self.display) }; + //println!("XFlush"); + // This isn't necessarily a useful time to check for errors (since our request hasn't + // necessarily been processed yet) + self.check_errors() + } - 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) + pub fn sync_with_server(&self) -> Result<(), XError> { + unsafe { (self.xlib.XSync)(self.display, ffi::False) }; + //println!("XSync"); + self.check_errors() + } } diff --git a/src/platform/linux/x11/util/randr.rs b/src/platform/linux/x11/util/randr.rs index 0683201c..53a20179 100644 --- a/src/platform/linux/x11/util/randr.rs +++ b/src/platform/linux/x11/util/randr.rs @@ -60,25 +60,23 @@ pub fn calc_dpi_factor( ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0) } -pub unsafe fn get_output_info( - xconn: &Arc, - resources: *mut XRRScreenResources, - repr: &MonitorRepr, -) -> (String, f32) { - let output_info = (xconn.xrandr.XRRGetOutputInfo)( - xconn.display, - resources, - repr.get_output(), - ); - let name_slice = slice::from_raw_parts( - (*output_info).name as *mut u8, - (*output_info).nameLen as usize, - ); - let name = String::from_utf8_lossy(name_slice).into(); - let hidpi_factor = calc_dpi_factor( - repr.get_dimensions(), - ((*output_info).mm_width as u64, (*output_info).mm_height as u64), - ) as f32; - (xconn.xrandr.XRRFreeOutputInfo)(output_info); - (name, hidpi_factor) +impl XConnection { + pub unsafe fn get_output_info(&self, resources: *mut XRRScreenResources, repr: &MonitorRepr) -> (String, f32) { + let output_info = (self.xrandr.XRRGetOutputInfo)( + self.display, + resources, + repr.get_output(), + ); + let name_slice = slice::from_raw_parts( + (*output_info).name as *mut u8, + (*output_info).nameLen as usize, + ); + let name = String::from_utf8_lossy(name_slice).into(); + let hidpi_factor = calc_dpi_factor( + repr.get_dimensions(), + ((*output_info).mm_width as u64, (*output_info).mm_height as u64), + ) as f32; + (self.xrandr.XRRFreeOutputInfo)(output_info); + (name, hidpi_factor) + } } diff --git a/src/platform/linux/x11/util/window_property.rs b/src/platform/linux/x11/util/window_property.rs index d87be219..47d984e6 100644 --- a/src/platform/linux/x11/util/window_property.rs +++ b/src/platform/linux/x11/util/window_property.rs @@ -1,5 +1,4 @@ use std; -use std::fmt::Debug; use super::*; @@ -25,139 +24,121 @@ 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. +// 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( - xconn: &Arc, - window: c_ulong, - property: ffi::Atom, - property_type: ffi::Atom, -) -> Result, 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::()) - // 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::() * 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, + 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, -} +impl XConnection { + pub fn get_property( + &self, + window: c_ulong, + property: ffi::Atom, + property_type: ffi::Atom, + ) -> Result, GetPropertyError> { + let mut data = Vec::new(); + let mut offset = 0; -pub unsafe fn change_property<'a, T: Debug>( - xconn: &'a Arc, - 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::() { - panic!(format!( - "[winit developer error] Incorrect usage of `util::change_property`: {:#?}", - InvalidFormat { - format_used: format, - size_passed: mem::size_of::() * 8, - size_expected: format.get_actual_size() * 8, - }, - )); + let mut done = false; + while !done { + unsafe { + 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(); + (self.xlib.XGetWindowProperty)( + self.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) = self.check_errors() { + return Err(GetPropertyError::XError(e)); + } + + if actual_type != property_type { + return Err(GetPropertyError::TypeMismatch(actual_type)); + } + + let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT); + if format_mismatch { + return Err(GetPropertyError::FormatMismatch(actual_format)); + } + + if !buf.is_null() { + offset += PROPERTY_BUFFER_SIZE; + let new_data = std::slice::from_raw_parts( + buf as *mut T, + quantity_returned as usize, + ); + /*println!( + "XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}", + property, + mem::size_of::() * 8, + data.len(), + offset, + quantity_returned, + new_data, + );*/ + data.extend_from_slice(&new_data); + // Fun fact: XGetWindowProperty allocates one extra byte at the end. + (self.xlib.XFree)(buf as _); // Don't try to access new_data after this. + } else { + return Err(GetPropertyError::NothingAllocated); + } + + done = bytes_after == 0; + } + } + + Ok(data) } - (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) + pub fn change_property<'a, T: Formattable>( + &'a self, + window: c_ulong, + property: ffi::Atom, + property_type: ffi::Atom, + mode: PropMode, + new_value: &[T], + ) -> Flusher<'a> { + debug_assert_eq!(mem::size_of::(), T::FORMAT.get_actual_size()); + unsafe { + (self.xlib.XChangeProperty)( + self.display, + window, + property, + property_type, + T::FORMAT as c_int, + mode as c_int, + new_value.as_ptr() as *const c_uchar, + new_value.len() as c_int, + ); + } + /*println!( + "XChangeProperty prop:{:?} val:{:?}", + property, + new_value, + );*/ + Flusher::new(self) + } } diff --git a/src/platform/linux/x11/util/wm.rs b/src/platform/linux/x11/util/wm.rs index 87db0989..738ab76d 100644 --- a/src/platform/linux/x11/util/wm.rs +++ b/src/platform/linux/x11/util/wm.rs @@ -20,139 +20,122 @@ pub fn wm_name_is_one_of(names: &[&str]) -> bool { } } -pub fn update_cached_wm_info(xconn: &Arc, root: ffi::Window) { - *SUPPORTED_HINTS.lock() = self::get_supported_hints(xconn, root); - *WM_NAME.lock() = self::get_wm_name(xconn, root); -} +impl XConnection { + pub fn update_cached_wm_info(&self, root: ffi::Window) { + *SUPPORTED_HINTS.lock() = self.get_supported_hints(root); + *WM_NAME.lock() = self.get_wm_name(root); + } -fn get_supported_hints(xconn: &Arc, root: ffi::Window) -> Vec { - let supported_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTED\0") } - .expect("Failed to call XInternAtom (_NET_SUPPORTED)"); - unsafe { - self::get_property( - xconn, + fn get_supported_hints(&self, root: ffi::Window) -> Vec { + let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") }; + self.get_property( root, supported_atom, ffi::XA_ATOM, - ) - }.unwrap_or_else(|_| Vec::with_capacity(0)) -} + ).unwrap_or_else(|_| Vec::with_capacity(0)) + } -fn get_wm_name(xconn: &Arc, root: ffi::Window) -> Option { - 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)"); + fn get_wm_name(&self, root: ffi::Window) -> Option { + let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") }; + let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") }; - // 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; - }*/ + // 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; - }*/ + // 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. + // 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, + // 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 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; + } }; - 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, + // 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 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; + } }; - let wm_check = result - .ok() - .and_then(|wm_check| wm_check.get(0).cloned()); - - if let Some(wm_check) = wm_check { - wm_check - } else { + // These values should be the same. + if root_window_wm_check != child_window_wm_check { 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_unchecked(b"UTF8_STRING\0") }; - // 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, + let result = self.get_property( 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 - }; + // 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, + if no_utf8 { + self.get_property( root_window_wm_check, wm_name_atom, ffi::XA_STRING, ) + } else { + result } - } else { - result - } - }.ok(); + }.ok(); - wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok()) + wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok()) + } } diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index a7cd6170..75671364 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -169,15 +169,12 @@ impl Window2 { // Enable drag and drop (TODO: extend API to make this toggleable) unsafe { - let dnd_aware_atom = util::get_atom(xconn, b"XdndAware\0") - .expect("Failed to call XInternAtom (XdndAware)"); + let dnd_aware_atom = xconn.get_atom_unchecked(b"XdndAware\0"); let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 - util::change_property( - xconn, + xconn.change_property( x_window.window, dnd_aware_atom, ffi::XA_ATOM, - util::Format::Long, util::PropMode::Replace, version, ) @@ -185,11 +182,6 @@ impl Window2 { // WM_CLASS must be set *before* mapping the window, as per ICCCM! { - let mut class_hints = { - let class_hints = unsafe { (xconn.xlib.XAllocClassHint)() }; - util::XSmartPointer::new(xconn, class_hints) - }.expect("`XAllocClassHint` returned null; out of memory"); - let (class, instance) = if let Some((instance, class)) = pl_attribs.class { let instance = CString::new(instance.as_str()) .expect("`WM_CLASS` instance contained null byte"); @@ -216,14 +208,15 @@ impl Window2 { (instance, class) }; - (*class_hints).res_name = class.as_ptr() as *mut c_char; - (*class_hints).res_class = instance.as_ptr() as *mut c_char; + let mut class_hint = xconn.alloc_class_hint(); + (*class_hint).res_name = class.as_ptr() as *mut c_char; + (*class_hint).res_class = instance.as_ptr() as *mut c_char; unsafe { (xconn.xlib.XSetClassHint)( xconn.display, x_window.window, - class_hints.ptr, + class_hint.ptr, ); }//.queue(); } @@ -241,10 +234,7 @@ impl Window2 { // set size hints { - let mut size_hints = { - let size_hints = unsafe { (xconn.xlib.XAllocSizeHints)() }; - util::XSmartPointer::new(xconn, size_hints) - }.expect("XAllocSizeHints returned null; out of memory"); + let mut size_hints = xconn.alloc_size_hints(); (*size_hints).flags = ffi::PSize; (*size_hints).width = dimensions.0 as c_int; (*size_hints).height = dimensions.1 as c_int; @@ -330,14 +320,7 @@ impl Window2 { } mask }; - unsafe { - util::select_xinput_events( - xconn, - x_window.window, - ffi::XIAllMasterDevices, - mask, - ) - }.queue(); + xconn.select_xinput_events(x_window.window, ffi::XIAllMasterDevices, mask).queue(); // These properties must be set after mapping if window_attrs.maximized { @@ -372,7 +355,7 @@ impl Window2 { } // We never want to give the user a broken window, since by then, it's too late to handle. - unsafe { util::sync_with_server(xconn) } + xconn.sync_with_server() .map(|_| window) .map_err(|x_err| OsError( format!("X server returned error while building window: {:?}", x_err) @@ -380,10 +363,8 @@ impl Window2 { } fn set_pid(xconn: &Arc, window: ffi::Window) -> Option { - let pid_atom = unsafe { util::get_atom(xconn, b"_NET_WM_PID\0") } - .expect("Failed to call XInternAtom (_NET_WM_PID)"); - let client_machine_atom = unsafe { util::get_atom(xconn, b"WM_CLIENT_MACHINE\0") } - .expect("Failed to call XInternAtom (WM_CLIENT_MACHINE)"); + let pid_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; + let client_machine_atom = unsafe { xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; unsafe { let (hostname, hostname_length) = { // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is @@ -396,21 +377,17 @@ impl Window2 { let hostname_length = libc::strlen(hostname.as_ptr()); (hostname, hostname_length as usize) }; - util::change_property( - xconn, + xconn.change_property( window, pid_atom, ffi::XA_CARDINAL, - util::Format::Long, util::PropMode::Replace, &[libc::getpid() as util::Cardinal], ).queue(); - let flusher = util::change_property( - xconn, + let flusher = xconn.change_property( window, client_machine_atom, ffi::XA_STRING, - util::Format::Char, util::PropMode::Replace, &hostname[0..hostname_length], ); @@ -423,50 +400,26 @@ impl Window2 { window: ffi::Window, window_type: util::WindowType, ) -> util::Flusher { - let hint_atom = unsafe { util::get_atom(xconn, b"_NET_WM_WINDOW_TYPE\0") } - .expect("Failed to call XInternAtom (_NET_WM_WINDOW_TYPE)"); + let hint_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; let window_type_atom = window_type.as_atom(xconn); - unsafe { - util::change_property( - xconn, - window, - hint_atom, - ffi::XA_ATOM, - util::Format::Long, - util::PropMode::Replace, - &[window_type_atom], - ) - } + xconn.change_property( + window, + hint_atom, + ffi::XA_ATOM, + util::PropMode::Replace, + &[window_type_atom], + ) } pub fn set_urgent(&self, is_urgent: bool) { let xconn = &self.x.display; - - let mut wm_hints = { - let mut wm_hints = unsafe { - (xconn.xlib.XGetWMHints)(xconn.display, self.x.window) - }; - xconn.check_errors().expect("`XGetWMHints` failed"); - if wm_hints.is_null() { - wm_hints = unsafe { (xconn.xlib.XAllocWMHints)() }; - } - util::XSmartPointer::new(xconn, wm_hints) - }.expect("`XAllocWMHints` returned null; out of memory"); - + let mut wm_hints = xconn.get_wm_hints(self.x.window).expect("`XGetWMHints` failed"); if is_urgent { (*wm_hints).flags |= ffi::XUrgencyHint; } else { (*wm_hints).flags &= !ffi::XUrgencyHint; } - - unsafe { - (xconn.xlib.XSetWMHints)( - xconn.display, - self.x.window, - wm_hints.ptr, - ); - util::flush_requests(xconn).expect("Failed to set urgency hint"); - } + xconn.set_wm_hints(self.x.window, wm_hints).flush().expect("Failed to set urgency hint"); } fn set_netwm( @@ -476,33 +429,25 @@ impl Window2 { properties: (c_long, c_long, c_long, c_long), operation: util::StateOperation ) -> util::Flusher { - let state_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE\0") } - .expect("Failed to call XInternAtom (_NET_WM_STATE)"); - - unsafe { - util::send_client_msg( - xconn, - window, - root, - state_atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), - ( - operation as c_long, - properties.0, - properties.1, - properties.2, - properties.3, - ) - ) - } + let state_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_STATE\0") }; + xconn.send_client_msg( + window, + root, + state_atom, + Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + [ + operation as c_long, + properties.0, + properties.1, + properties.2, + properties.3, + ], + ) } fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher { let xconn = &self.x.display; - - let fullscreen_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_FULLSCREEN\0") } - .expect("Failed to call XInternAtom (_NET_WM_STATE_FULLSCREEN)"); - + let fullscreen_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") }; Window2::set_netwm( xconn, self.x.window, @@ -548,12 +493,8 @@ impl Window2 { fn set_maximized_inner(&self, maximized: bool) -> util::Flusher { let xconn = &self.x.display; - - let horz_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_MAXIMIZED_HORZ\0") } - .expect("Failed to call XInternAtom (_NET_WM_STATE_MAXIMIZED_HORZ)"); - let vert_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_MAXIMIZED_VERT\0") } - .expect("Failed to call XInternAtom (_NET_WM_STATE_MAXIMIZED_VERT)"); - + let horz_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0") }; + let vert_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0") }; Window2::set_netwm( xconn, self.x.window, @@ -572,12 +513,8 @@ impl Window2 { fn set_title_inner(&self, title: &str) -> util::Flusher { let xconn = &self.x.display; - - let wm_name_atom = unsafe { util::get_atom(xconn, b"_NET_WM_NAME\0") } - .expect("Failed to call XInternAtom (_NET_WM_NAME)"); - let utf8_atom = unsafe { util::get_atom(xconn, b"UTF8_STRING\0") } - .expect("Failed to call XInternAtom (UTF8_STRING)"); - + let wm_name_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_NAME\0") }; + let utf8_atom = unsafe { xconn.get_atom_unchecked(b"UTF8_STRING\0") }; let title = CString::new(title).expect("Window title contained null byte"); unsafe { (xconn.xlib.XStoreName)( @@ -585,13 +522,10 @@ impl Window2 { self.x.window, title.as_ptr() as *const c_char, ); - - util::change_property( - xconn, + xconn.change_property( self.x.window, wm_name_atom, utf8_atom, - util::Format::Char, util::PropMode::Replace, title.as_bytes_with_nul(), ) @@ -606,27 +540,20 @@ impl Window2 { fn set_decorations_inner(&self, decorations: bool) -> util::Flusher { let xconn = &self.x.display; - - let wm_hints = unsafe { util::get_atom(xconn, b"_MOTIF_WM_HINTS\0") } - .expect("Failed to call XInternAtom (_MOTIF_WM_HINTS)"); - - unsafe { - util::change_property( - xconn, - self.x.window, - wm_hints, - wm_hints, - util::Format::Long, - util::PropMode::Replace, - &[ - util::MWM_HINTS_DECORATIONS, // flags - 0, // functions - decorations as c_ulong, // decorations - 0, // input mode - 0, // status - ], - ) - } + let wm_hints = unsafe { xconn.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; + xconn.change_property( + self.x.window, + wm_hints, + wm_hints, + util::PropMode::Replace, + &[ + util::MWM_HINTS_DECORATIONS, // flags + 0, // functions + decorations as c_ulong, // decorations + 0, // input mode + 0, // status + ], + ) } pub fn set_decorations(&self, decorations: bool) { @@ -638,10 +565,7 @@ impl Window2 { fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher { let xconn = &self.x.display; - - let above_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_ABOVE\0") } - .expect("Failed to call XInternAtom (_NET_WM_STATE_ABOVE)"); - + let above_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_STATE_ABOVE\0") }; Window2::set_netwm( xconn, self.x.window, @@ -659,42 +583,28 @@ impl Window2 { fn set_icon_inner(&self, icon: Icon) -> util::Flusher { let xconn = &self.x.display; - - let icon_atom = unsafe { util::get_atom(xconn, b"_NET_WM_ICON\0") } - .expect("Failed to call XInternAtom (_NET_WM_ICON)"); - + let icon_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; let data = icon.to_cardinals(); - unsafe { - util::change_property( - xconn, - self.x.window, - icon_atom, - ffi::XA_CARDINAL, - util::Format::Long, - util::PropMode::Replace, - data.as_slice(), - ) - } + xconn.change_property( + self.x.window, + icon_atom, + ffi::XA_CARDINAL, + util::PropMode::Replace, + data.as_slice(), + ) } fn unset_icon_inner(&self) -> util::Flusher { let xconn = &self.x.display; - - let icon_atom = unsafe { util::get_atom(xconn, b"_NET_WM_ICON\0") } - .expect("Failed to call XInternAtom (_NET_WM_ICON)"); - + let icon_atom = unsafe { xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; let empty_data: [util::Cardinal; 0] = []; - unsafe { - util::change_property( - xconn, - self.x.window, - icon_atom, - ffi::XA_CARDINAL, - util::Format::Long, - util::PropMode::Replace, - &empty_data, - ) - } + xconn.change_property( + self.x.window, + icon_atom, + ffi::XA_CARDINAL, + util::PropMode::Replace, + &empty_data, + ) } pub fn set_window_icon(&self, icon: Option) { @@ -707,7 +617,7 @@ impl Window2 { pub fn show(&self) { unsafe { (self.x.display.xlib.XMapRaised)(self.x.display.display, self.x.window); - util::flush_requests(&self.x.display) + self.x.display.flush_requests() .expect("Failed to call XMapRaised"); } } @@ -715,17 +625,13 @@ impl Window2 { pub fn hide(&self) { unsafe { (self.x.display.xlib.XUnmapWindow)(self.x.display.display, self.x.window); - util::flush_requests(&self.x.display) + self.x.display.flush_requests() .expect("Failed to call XUnmapWindow"); } } fn update_cached_frame_extents(&self) { - let extents = util::get_frame_extents_heuristic( - &self.x.display, - self.x.window, - self.x.root, - ); + let extents = self.x.display.get_frame_extents_heuristic(self.x.window, self.x.root); (*self.shared_state.lock()).frame_extents = Some(extents); } @@ -748,7 +654,7 @@ impl Window2 { #[inline] pub fn get_inner_position(&self) -> Option<(i32, i32)> { - unsafe { util::translate_coords(&self.x.display, self.x.window, self.x.root )} + self.x.display.translate_coords(self.x.window, self.x.root ) .ok() .map(|coords| (coords.x_rel_root, coords.y_rel_root)) } @@ -773,13 +679,13 @@ impl Window2 { x as c_int, y as c_int, ); - util::flush_requests(&self.x.display) + self.x.display.flush_requests() }.expect("Failed to call XMoveWindow"); } #[inline] pub fn get_inner_size(&self) -> Option<(u32, u32)> { - unsafe { util::get_geometry(&self.x.display, self.x.window) } + self.x.display.get_geometry(self.x.window) .ok() .map(|geo| (geo.width, geo.height)) } @@ -806,7 +712,7 @@ impl Window2 { width as c_uint, height as c_uint, ); - util::flush_requests(&self.x.display) + self.x.display.flush_requests() }.expect("Failed to call XResizeWindow"); } @@ -815,14 +721,8 @@ impl Window2 { { let xconn = &self.x.display; - let size_hints = { - let size_hints = (xconn.xlib.XAllocSizeHints)(); - util::XSmartPointer::new(&xconn, size_hints) - .expect("XAllocSizeHints returned null; out of memory") - }; - + let size_hints = xconn.alloc_size_hints(); let mut flags: c_long = mem::uninitialized(); - (xconn.xlib.XGetWMNormalHints)( xconn.display, self.x.window, @@ -838,7 +738,7 @@ impl Window2 { self.x.window, size_hints.ptr, ); - util::flush_requests(xconn)?; + xconn.flush_requests()?; Ok(()) } @@ -994,7 +894,7 @@ impl Window2 { if cursor != 0 { (self.x.display.xlib.XFreeCursor)(self.x.display.display, cursor); } - util::flush_requests(&self.x.display).expect("Failed to set or free the cursor"); + self.x.display.flush_requests().expect("Failed to set or free the cursor"); } } @@ -1057,7 +957,7 @@ impl Window2 { Grab => { unsafe { (self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime); - util::flush_requests(&self.x.display).expect("Failed to call XUngrabPointer"); + self.x.display.flush_requests().expect("Failed to call XUngrabPointer"); } }, Normal => {}, @@ -1123,7 +1023,7 @@ impl Window2 { x, y, ); - util::flush_requests(&self.x.display).map_err(|_| ()) + self.x.display.flush_requests().map_err(|_| ()) } }