diff --git a/CHANGELOG.md b/CHANGELOG.md index b2fdb93e..822192e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,13 @@ - Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar. - Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`). - Implemented `Moved` event on macOS. +- On X11, the `Moved` event correctly use window positions rather than client area positions. Additionally, a stray `Moved` that unconditionally accompanied `Resized` with the client area position relative to the parent has been eliminated; `Moved` is still received alongside `Resized`, but now only once and always correctly. - On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground. - `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc. - Corrected `run_forever` on X11 to stop discarding `Awakened` events. +- Various safety and correctness improvements to the X11 backend internals. +- Fixed memory leak on X11 every time the mouse entered the window. +- On X11, drag and drop now works reliably in release mode. # Version 0.13.1 (2018-04-26) diff --git a/Cargo.toml b/Cargo.toml index e7f8f808..7328a21e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,4 +49,5 @@ wayland-protocols = { version = "0.12.0", features = ["unstable_protocols"] } wayland-kbd = "0.13.0" wayland-window = "0.13.0" x11-dl = "2.17.5" +parking_lot = "0.5" percent-encoding = "1.0" diff --git a/src/lib.rs b/src/lib.rs index d5ce6be5..fd1de99a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,8 @@ extern crate core_graphics; #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] extern crate x11_dl; #[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] +extern crate parking_lot; +#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] extern crate percent_encoding; #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))] #[macro_use] diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 242690d8..6446f591 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -1,18 +1,19 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] use std::collections::VecDeque; +use std::{env, mem}; +use std::ffi::CStr; +use std::os::raw::*; use std::sync::Arc; -use std::env; -use {CreationError, CursorState, EventsLoopClosed, MouseCursor, ControlFlow}; use libc; +use {CreationError, CursorState, EventsLoopClosed, MouseCursor, ControlFlow}; +use window::MonitorId as RootMonitorId; use self::x11::XConnection; use self::x11::XError; use self::x11::ffi::XVisualInfo; - pub use self::x11::XNotSupported; -use window::MonitorId as RootMonitorId; mod dlopen; pub mod wayland; @@ -301,15 +302,19 @@ impl Window { } } -unsafe extern "C" fn x_error_callback(dpy: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent) - -> libc::c_int -{ - use std::ffi::CStr; - - if let Ok(ref x) = *X11_BACKEND { - let mut buff: Vec = Vec::with_capacity(1024); - (x.xlib.XGetErrorText)(dpy, (*event).error_code as i32, buff.as_mut_ptr() as *mut libc::c_char, buff.capacity() as i32); - let description = CStr::from_ptr(buff.as_mut_ptr() as *const libc::c_char).to_string_lossy(); +unsafe extern "C" fn x_error_callback( + display: *mut x11::ffi::Display, + event: *mut x11::ffi::XErrorEvent, +) -> c_int { + if let Ok(ref xconn) = *X11_BACKEND { + let mut buf: [c_char; 1024] = mem::uninitialized(); + (xconn.xlib.XGetErrorText)( + display, + (*event).error_code as c_int, + buf.as_mut_ptr(), + buf.len() as c_int, + ); + let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy(); let error = XError { description: description.into_owned(), @@ -318,9 +323,9 @@ unsafe extern "C" fn x_error_callback(dpy: *mut x11::ffi::Display, event: *mut x minor_code: (*event).minor_code, }; - *x.latest_error.lock().unwrap() = Some(error); + *xconn.latest_error.lock() = Some(error); } - + // Fun fact: this return value is completely ignored. 0 } @@ -340,28 +345,39 @@ impl EventsLoop { if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { match env_var.as_str() { "x11" => { - return EventsLoop::new_x11().unwrap(); // TODO: propagate + // TODO: propagate + return EventsLoop::new_x11().expect("Failed to initialize X11 backend"); }, "wayland" => { - match EventsLoop::new_wayland() { - Ok(e) => return e, - Err(_) => panic!() // TODO: propagate - } + return EventsLoop::new_wayland() + .expect("Failed to initialize Wayland backend"); }, - _ => panic!("Unknown environment variable value for {}, try one of `x11`,`wayland`", - BACKEND_PREFERENCE_ENV_VAR), + _ => panic!( + "Unknown environment variable value for {}, try one of `x11`,`wayland`", + BACKEND_PREFERENCE_ENV_VAR, + ), } } - if let Ok(el) = EventsLoop::new_wayland() { - return el; - } + let wayland_err = match EventsLoop::new_wayland() { + Ok(event_loop) => return event_loop, + Err(err) => err, + }; - if let Ok(el) = EventsLoop::new_x11() { - return el; - } + let x11_err = match EventsLoop::new_x11() { + Ok(event_loop) => return event_loop, + Err(err) => err, + }; - panic!("No backend is available") + let err_string = format!( +r#"Failed to initialize any backend! + Wayland status: {:#?} + X11 status: {:#?} +"#, + wayland_err, + x11_err, + ); + panic!(err_string); } pub fn new_wayland() -> Result { diff --git a/src/platform/linux/x11/dnd.rs b/src/platform/linux/x11/dnd.rs index b35c1b18..571608a7 100644 --- a/src/platform/linux/x11/dnd.rs +++ b/src/platform/linux/x11/dnd.rs @@ -2,14 +2,12 @@ use std::io; use std::sync::Arc; use std::path::{Path, PathBuf}; use std::str::Utf8Error; +use std::os::raw::*; -use libc::{c_char, c_int, c_long, c_uchar, c_ulong}; use percent_encoding::percent_decode; use super::{ffi, util, XConnection, XError}; -const DND_ATOMS_LEN: usize = 12; - #[derive(Debug)] pub struct DndAtoms { pub aware: ffi::Atom, @@ -28,36 +26,21 @@ pub struct DndAtoms { impl DndAtoms { pub fn new(xconn: &Arc) -> Result { - let mut atoms = Vec::with_capacity(DND_ATOMS_LEN); - - let mut names = [ - b"XdndAware\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndEnter\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndLeave\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndDrop\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndPosition\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndStatus\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndActionPrivate\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndSelection\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndFinished\0".to_owned().as_mut_ptr() as *mut c_char, - b"XdndTypeList\0".to_owned().as_mut_ptr() as *mut c_char, - b"text/uri-list\0".to_owned().as_mut_ptr() as *mut c_char, - b"None\0".to_owned().as_mut_ptr() as *mut c_char, + let names = [ + b"XdndAware\0".as_ptr() as *mut c_char, + b"XdndEnter\0".as_ptr() as *mut c_char, + b"XdndLeave\0".as_ptr() as *mut c_char, + b"XdndDrop\0".as_ptr() as *mut c_char, + b"XdndPosition\0".as_ptr() as *mut c_char, + b"XdndStatus\0".as_ptr() as *mut c_char, + b"XdndActionPrivate\0".as_ptr() as *mut c_char, + b"XdndSelection\0".as_ptr() as *mut c_char, + b"XdndFinished\0".as_ptr() as *mut c_char, + b"XdndTypeList\0".as_ptr() as *mut c_char, + b"text/uri-list\0".as_ptr() as *mut c_char, + b"None\0".as_ptr() as *mut c_char, ]; - - unsafe { - (xconn.xlib.XInternAtoms)( - xconn.display, - names.as_mut_ptr(), - DND_ATOMS_LEN as c_int, - ffi::False, - atoms.as_mut_ptr(), - ); - } - xconn.check_errors()?; - unsafe { - atoms.set_len(DND_ATOMS_LEN); - } + let atoms = unsafe { util::get_atoms(xconn, &names) }?; Ok(DndAtoms { aware: atoms[0], enter: atoms[1], @@ -151,7 +134,7 @@ impl Dnd { self.atoms.status, None, (this_window as c_long, accepted, 0, 0, action), - ) + ).flush() } pub unsafe fn send_finished( @@ -171,7 +154,7 @@ impl Dnd { self.atoms.finished, None, (this_window as c_long, accepted, action, 0, 0), - ) + ).flush() } pub unsafe fn get_type_list( diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index 64eb42aa..5c188687 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -12,15 +12,16 @@ use {CreationError, Event, EventsLoopClosed, WindowEvent, DeviceEvent, use events::ModifiersState; use std::{mem, ptr, slice}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Weak}; use std::sync::atomic::{self, AtomicBool}; use std::sync::mpsc; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::CStr; -use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong}; +use std::os::raw::*; use libc::{self, setlocale, LC_CTYPE}; +use parking_lot::Mutex; mod events; mod monitor; @@ -33,13 +34,6 @@ mod util; use self::dnd::{Dnd, DndState}; use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime}; -// API TRANSITION -// -// We don't use the gen_api_transistion!() macro but rather do the expansion manually: -// -// As this module is nested into platform/linux, its code is not _exactly_ the same as -// the one generated by the macro. - pub struct EventsLoop { display: Arc, wm_delete_window: ffi::Atom, @@ -48,7 +42,9 @@ pub struct EventsLoop { ime_sender: ImeSender, ime: RefCell, windows: Arc>>, - devices: Mutex>, + // Please don't laugh at this type signature + shared_state: RefCell>>>, + devices: RefCell>, xi2ext: XExtension, pending_wakeup: Arc, root: ffi::Window, @@ -66,8 +62,8 @@ pub struct EventsLoopProxy { impl EventsLoop { pub fn new(display: Arc) -> EventsLoop { - let wm_delete_window = unsafe { (display.xlib.XInternAtom)(display.display, b"WM_DELETE_WINDOW\0".as_ptr() as *const c_char, 0) }; - display.check_errors().expect("Failed to call XInternAtom"); + let wm_delete_window = unsafe { util::get_atom(&display, b"WM_DELETE_WINDOW\0") } + .expect("Failed to call XInternAtom (WM_DELETE_WINDOW)"); let dnd = Dnd::new(Arc::clone(&display)) .expect("Failed to call XInternAtoms when initializing drag and drop"); @@ -105,19 +101,36 @@ impl EventsLoop { unsafe { let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; - - if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int { - panic!("X server has XInput extension {}.{} but does not support XInput2", xinput_major_ver, xinput_minor_ver); + if (display.xinput2.XIQueryVersion)( + display.display, + &mut xinput_major_ver, + &mut xinput_minor_ver, + ) != ffi::Success as libc::c_int { + panic!( + "X server has XInput extension {}.{} but does not support XInput2", + xinput_major_ver, + xinput_minor_ver, + ); } } let root = unsafe { (display.xlib.XDefaultRootWindow)(display.display) }; + util::update_cached_wm_info(&display, root); let wakeup_dummy_window = unsafe { let (x, y, w, h) = (10, 10, 10, 10); let (border_w, border_px, background_px) = (0, 0, 0); - (display.xlib.XCreateSimpleWindow)(display.display, root, x, y, w, h, - border_w, border_px, background_px) + (display.xlib.XCreateSimpleWindow)( + display.display, + root, + x, + y, + w, + h, + border_w, + border_px, + background_px, + ) }; let result = EventsLoop { @@ -129,27 +142,24 @@ impl EventsLoop { ime_sender, ime, windows: Arc::new(Mutex::new(HashMap::new())), - devices: Mutex::new(HashMap::new()), + shared_state: RefCell::new(HashMap::new()), + devices: RefCell::new(HashMap::new()), xi2ext, root, wakeup_dummy_window, }; - { - // Register for device hotplug events - let mask = ffi::XI_HierarchyChangedMask; - unsafe { - let mut event_mask = ffi::XIEventMask{ - deviceid: ffi::XIAllDevices, - mask: &mask as *const _ as *mut c_uchar, - mask_len: mem::size_of_val(&mask) as c_int, - }; - (result.display.xinput2.XISelectEvents)(result.display.display, root, - &mut event_mask as *mut ffi::XIEventMask, 1); - } + // 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 - result.init_device(ffi::XIAllDevices); - } + result.init_device(ffi::XIAllDevices); result } @@ -230,7 +240,8 @@ impl EventsLoop { return; } - match xev.get_type() { + let event_type = xev.get_type(); + match event_type { ffi::MappingNotify => { unsafe { (xlib.XRefreshKeyboardMapping)(xev.as_mut()); } self.display.check_errors().expect("Failed to call XRefreshKeyboardMapping"); @@ -375,47 +386,129 @@ impl EventsLoop { ffi::ConfigureNotify => { let xev: &ffi::XConfigureEvent = xev.as_ref(); + // So apparently... + // XSendEvent (synthetic ConfigureNotify) -> position relative to root + // XConfigureNotify (real ConfigureNotify) -> position relative to parent + // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 + // We don't want to send Moved when this is true, since then every Resized + // (whether the window moved or not) is accompanied by an extraneous Moved event + // that has a position relative to the parent window. + let is_synthetic = xev.send_event == ffi::True; + let window = xev.window; let window_id = mkwid(window); let new_size = (xev.width, xev.height); let new_position = (xev.x, xev.y); - // Gymnastics to ensure self.windows isn't locked when we invoke callback + let (resized, moved) = { - let mut windows = self.windows.lock().unwrap(); + let mut windows = self.windows.lock(); if let Some(window_data) = windows.get_mut(&WindowId(window)) { - if window_data.config.is_none() { - window_data.config = Some(WindowConfig::new(xev)); - (true, true) - } else { - let window_state = window_data.config.as_mut().unwrap(); - (if window_state.size != new_size { - window_state.size = new_size; - true - } else { false }, - if window_state.position != new_position { - window_state.position = new_position; - true - } else { false }) + let (mut resized, mut moved) = (false, false); + + if window_data.config.size.is_none() { + window_data.config.size = Some(new_size); + resized = true; } + if window_data.config.size.is_none() && is_synthetic { + window_data.config.position = Some(new_position); + moved = true; + } + + if !resized { + if window_data.config.size != Some(new_size) { + window_data.config.size = Some(new_size); + resized = true; + } + } + if !moved && is_synthetic { + if window_data.config.position != Some(new_position) { + window_data.config.position = Some(new_position); + moved = true; + } + } + + if !is_synthetic + && window_data.config.inner_position != Some(new_position) { + window_data.config.inner_position = Some(new_position); + // This way, we get sent Moved when the decorations are toggled. + window_data.config.position = None; + self.shared_state.borrow().get(&WindowId(window)).map(|window_state| { + if let Some(window_state) = window_state.upgrade() { + // Extra insurance against stale frame extents + (*window_state.lock()).frame_extents.take(); + } + }); + } + + (resized, moved) } else { return; } }; + if resized { + let (width, height) = (xev.width as u32, xev.height as u32); callback(Event::WindowEvent { window_id, - event: WindowEvent::Resized(xev.width as u32, xev.height as u32), + event: WindowEvent::Resized(width, height), }); } + if moved { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Moved(xev.x as i32, xev.y as i32), + // We need to convert client area position to window position. + self.shared_state.borrow().get(&WindowId(window)).map(|window_state| { + if let Some(window_state) = window_state.upgrade() { + let (x, y) = { + let (inner_x, inner_y) = (xev.x as i32, xev.y as i32); + let mut window_state_lock = window_state.lock(); + if (*window_state_lock).frame_extents.is_some() { + (*window_state_lock).frame_extents + .as_ref() + .unwrap() + .inner_pos_to_outer(inner_x, inner_y) + } else { + let extents = util::get_frame_extents_heuristic( + &self.display, + window, + self.root, + ); + let outer_pos = extents.inner_pos_to_outer(inner_x, inner_y); + (*window_state_lock).frame_extents = Some(extents); + outer_pos + } + }; + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Moved(x, y), + }); + } }); } } + ffi::ReparentNotify => { + let xev: &ffi::XReparentEvent = xev.as_ref(); + + let window = xev.window; + + // This is generally a reliable way to detect when the window manager's been + // replaced, though this event is only fired by reparenting window managers + // (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.shared_state + .borrow() + .get(&WindowId(window)) + .map(|window_state| { + if let Some(window_state) = window_state.upgrade() { + (*window_state.lock()).frame_extents.take(); + } + }); + } + ffi::DestroyNotify => { let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); @@ -424,7 +517,7 @@ impl EventsLoop { // In the event that the window's been destroyed without being dropped first, we // cleanup again here. - self.windows.lock().unwrap().remove(&WindowId(window)); + self.windows.lock().remove(&WindowId(window)); // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. @@ -460,42 +553,47 @@ impl EventsLoop { let window = xkev.window; let window_id = mkwid(window); - let modifiers = ModifiersState { - alt: xkev.state & ffi::Mod1Mask != 0, - shift: xkev.state & ffi::ShiftMask != 0, - ctrl: xkev.state & ffi::ControlMask != 0, - logo: xkev.state & ffi::Mod4Mask != 0, - }; - - let keysym = unsafe { - let mut keysym = 0; - (self.display.xlib.XLookupString)( - xkev, - ptr::null_mut(), - 0, - &mut keysym, - ptr::null_mut(), - ); - keysym - }; - - let virtual_keycode = events::keysym_to_element(keysym as c_uint); + // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable + // value, though this should only be an issue under multiseat configurations. + let device = 3; + let device_id = mkdid(device); // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // a keycode of 0. if xkev.keycode != 0 { - callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { - // Standard virtual core keyboard ID. XInput2 needs to be used to get a - // reliable value, though this should only be an issue under multiseat - // configurations. - device_id: mkdid(3), - input: KeyboardInput { - state, - scancode: xkev.keycode - 8, - virtual_keycode, - modifiers, - }, - }}); + let modifiers = ModifiersState { + alt: xkev.state & ffi::Mod1Mask != 0, + shift: xkev.state & ffi::ShiftMask != 0, + ctrl: xkev.state & ffi::ControlMask != 0, + logo: xkev.state & ffi::Mod4Mask != 0, + }; + + let keysym = unsafe { + let mut keysym = 0; + (self.display.xlib.XLookupString)( + xkev, + ptr::null_mut(), + 0, + &mut keysym, + ptr::null_mut(), + ); + self.display.check_errors().expect("Failed to lookup keysym"); + keysym + }; + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + input: KeyboardInput { + state, + scancode: xkev.keycode - 8, + virtual_keycode, + modifiers, + }, + } + }); } if state == Pressed { @@ -534,7 +632,7 @@ impl EventsLoop { let window_id = mkwid(xev.event); let device_id = mkdid(xev.deviceid); if (xev.flags & ffi::XIPointerEmulated) != 0 { - let windows = self.windows.lock().unwrap(); + let windows = self.windows.lock(); if let Some(window_data) = windows.get(&WindowId(xev.event)) { if window_data.multitouch { // Deliver multi-touch events instead of emulated mouse events. @@ -623,7 +721,7 @@ impl EventsLoop { // Gymnastics to ensure self.windows isn't locked when we invoke callback if { - let mut windows = self.windows.lock().unwrap(); + let mut windows = self.windows.lock(); let window_data = { if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) { window_data @@ -650,8 +748,11 @@ impl EventsLoop { let mut events = Vec::new(); { let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; - let mut devices = self.devices.lock().unwrap(); - let physical_device = devices.get_mut(&DeviceId(xev.sourceid)).unwrap(); + let mut devices = self.devices.borrow_mut(); + let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; let mut value = xev.valuators.values; for i in 0..xev.valuators.mask_len*8 { @@ -698,11 +799,16 @@ impl EventsLoop { let window_id = mkwid(xev.event); let device_id = mkdid(xev.deviceid); - let mut devices = self.devices.lock().unwrap(); - let physical_device = devices.get_mut(&DeviceId(xev.sourceid)).unwrap(); - for info in DeviceInfo::get(&self.display, ffi::XIAllDevices).iter() { - if info.deviceid == xev.sourceid { - physical_device.reset_scroll_position(info); + let mut devices = self.devices.borrow_mut(); + let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; + if let Some(all_info) = DeviceInfo::get(&self.display, ffi::XIAllDevices) { + for device_info in all_info.iter() { + if device_info.deviceid == xev.sourceid { + physical_device.reset_scroll_position(device_info); + } } } callback(Event::WindowEvent { @@ -711,15 +817,17 @@ impl EventsLoop { }); let new_cursor_pos = (xev.event_x, xev.event_y); - // The mods field on this event isn't actually useful, so we have to - // query the pointer device. + + // 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(); + ) + }.expect("Failed to query pointer device").get_modifier_state(); callback(Event::WindowEvent { window_id, event: CursorMoved { device_id, @@ -734,7 +842,6 @@ impl EventsLoop { // been destroyed, which the user presumably doesn't want to deal with. let window_closed = self.windows .lock() - .unwrap() .get(&WindowId(xev.event)) .is_none(); @@ -750,7 +857,7 @@ impl EventsLoop { let window_id = mkwid(xev.event); - if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) { + if let None = self.windows.lock().get(&WindowId(xev.event)) { return; } self.ime @@ -762,11 +869,11 @@ impl EventsLoop { // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. - let device_info = DeviceInfo::get(&self.display, xev.deviceid); - // For master devices, the attachment field contains the ID of the - // paired master device; for the master keyboard, the attachment is - // the master pointer, and vice versa. - let pointer_id = unsafe { (*device_info.info) }.attachment; + let pointer_id = self.devices + .borrow() + .get(&DeviceId(xev.deviceid)) + .map(|device| device.attachment) + .unwrap_or(2); callback(Event::WindowEvent { window_id, @@ -780,7 +887,7 @@ impl EventsLoop { ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) { + if let None = self.windows.lock().get(&WindowId(xev.event)) { return; } self.ime @@ -868,19 +975,44 @@ impl EventsLoop { } ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - // TODO: Use xkbcommon for keysym and text decoding let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - let xkeysym = unsafe { (self.display.xlib.XKeycodeToKeysym)(self.display.display, xev.detail as ffi::KeyCode, 0) }; - callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Key(KeyboardInput { - scancode: (xev.detail - 8) as u32, - virtual_keycode: events::keysym_to_element(xkeysym as libc::c_uint), - state: match xev.evtype { - ffi::XI_RawKeyPress => Pressed, - ffi::XI_RawKeyRelease => Released, - _ => unreachable!(), - }, - modifiers: ModifiersState::default(), - })}); + + let state = match xev.evtype { + ffi::XI_RawKeyPress => Pressed, + ffi::XI_RawKeyRelease => Released, + _ => unreachable!(), + }; + + let device_id = xev.sourceid; + let keycode = xev.detail; + if keycode < 8 { return; } + let scancode = (keycode - 8) as u32; + + let keysym = unsafe { + (self.display.xlib.XKeycodeToKeysym)( + self.display.display, + xev.detail as ffi::KeyCode, + 0, + ) + }; + self.display.check_errors().expect("Failed to lookup raw keysym"); + + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + + callback(Event::DeviceEvent { + device_id: mkdid(device_id), + event: DeviceEvent::Key(KeyboardInput { + scancode, + virtual_keycode, + state, + // So, in an ideal world we can use libxkbcommon to get modifiers. + // However, libxkbcommon-x11 isn't as commonly installed as one + // would hope. We can still use the Xkb extension to get + // comprehensive keyboard state updates, but interpreting that + // info manually is going to be involved. + modifiers: ModifiersState::default(), + }), + }); } ffi::XI_HierarchyChanged => { @@ -891,7 +1023,7 @@ impl EventsLoop { callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added }); } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) { callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed }); - let mut devices = self.devices.lock().unwrap(); + let mut devices = self.devices.borrow_mut(); devices.remove(&DeviceId(info.deviceid)); } } @@ -899,23 +1031,24 @@ impl EventsLoop { _ => {} } - } - - _ => {} + }, + _ => (), } match self.ime_receiver.try_recv() { Ok((window_id, x, y)) => { self.ime.borrow_mut().send_xim_spot(window_id, x, y); - } - Err(_) => () + }, + Err(_) => (), } } fn init_device(&self, device: c_int) { - let mut devices = self.devices.lock().unwrap(); - for info in DeviceInfo::get(&self.display, device).iter() { - devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + let mut devices = self.devices.borrow_mut(); + if let Some(info) = DeviceInfo::get(&self.display, device) { + for info in info.iter() { + devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + } } } } @@ -933,28 +1066,19 @@ impl EventsLoopProxy { // Push an event on the X event queue so that methods run_forever will advance. // - // NOTE: This code (and the following `XSendEvent` code) is taken from the old - // `WindowProxy::wakeup` implementation. The code assumes that X11 is thread safe. Is this - // true? - let mut xev = ffi::XClientMessageEvent { - type_: ffi::ClientMessage, - window: self.wakeup_dummy_window, - format: 32, - message_type: 0, - serial: 0, - send_event: 0, - display: display.display, - data: unsafe { mem::zeroed() }, - }; - + // 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 { - let propagate = false as i32; - let event_mask = 0; - let xevent = &mut xev as *mut ffi::XClientMessageEvent as *mut ffi::XEvent; - (display.xlib.XSendEvent)(display.display, self.wakeup_dummy_window, propagate, event_mask, xevent); - (display.xlib.XFlush)(display.display); - display.check_errors().expect("Failed to call XSendEvent after wakeup"); - } + 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"); Ok(()) } @@ -967,21 +1091,30 @@ struct DeviceInfo<'a> { } impl<'a> DeviceInfo<'a> { - fn get(display: &'a XConnection, device: c_int) -> Self { + fn get(display: &'a XConnection, device: c_int) -> Option { unsafe { let mut count = mem::uninitialized(); let info = (display.xinput2.XIQueryDevice)(display.display, device, &mut count); - DeviceInfo { - display: display, - info: info, - count: count as usize, - } + display.check_errors() + .ok() + .and_then(|_| { + if info.is_null() || count == 0 { + None + } else { + Some(DeviceInfo { + display, + info, + count: count as usize, + }) + } + }) } } } impl<'a> Drop for DeviceInfo<'a> { fn drop(&mut self) { + assert!(!self.info.is_null()); unsafe { (self.display.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; } } @@ -1020,15 +1153,19 @@ impl Window { window: &::WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes ) -> Result { - let win = Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs))); + let win = Arc::new(Window2::new(&x_events_loop, window, pl_attribs)?); + + x_events_loop.shared_state + .borrow_mut() + .insert(win.id(), Arc::downgrade(&win.shared_state)); x_events_loop.ime .borrow_mut() .create_context(win.id().0) .expect("Failed to create input context"); - x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData { - config: None, + x_events_loop.windows.lock().insert(win.id(), WindowData { + config: Default::default(), multitouch: window.multitouch, cursor_pos: None, }); @@ -1050,7 +1187,6 @@ impl Window { pub fn send_xim_spot(&self, x: i16, y: i16) { let _ = self.ime_sender .lock() - .unwrap() .send((self.window.id().0, x, y)); } } @@ -1058,10 +1194,9 @@ impl Window { impl Drop for Window { fn drop(&mut self) { if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) { - if let Some(_) = windows.lock().unwrap().remove(&self.window.id()) { + if let Some(_) = windows.lock().remove(&self.window.id()) { unsafe { (display.xlib.XDestroyWindow)(display.display, self.window.id().0); - display.check_errors().expect("Failed to destroy window"); } } } @@ -1069,8 +1204,9 @@ impl Drop for Window { } /// State maintained for translating window-related events +#[derive(Debug)] struct WindowData { - config: Option, + config: WindowConfig, multitouch: bool, cursor_pos: Option<(f64, f64)>, } @@ -1078,21 +1214,13 @@ struct WindowData { // Required by ffi members unsafe impl Send for WindowData {} +#[derive(Debug, Default)] struct WindowConfig { - size: (c_int, c_int), - position: (c_int, c_int), + pub size: Option<(c_int, c_int)>, + pub position: Option<(c_int, c_int)>, + pub inner_position: Option<(c_int, c_int)>, } -impl WindowConfig { - fn new(event: &ffi::XConfigureEvent) -> Self { - WindowConfig { - size: (event.width, event.height), - position: (event.x, event.y), - } - } -} - - /// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to /// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed struct GenericEventCookie<'a> { @@ -1136,6 +1264,9 @@ fn mkdid(w: c_int) -> ::DeviceId { ::DeviceId(::platform::DeviceId::X(DeviceId(w struct Device { name: String, scroll_axes: Vec<(i32, ScrollAxis)>, + // For master devices, this is the paired device (pointer <-> keyboard). + // For slave devices, this is the master. + attachment: c_int, } #[derive(Debug, Copy, Clone)] @@ -1152,24 +1283,25 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventsLoop, info: &ffi::XIDeviceInfo) -> Self - { + fn new(el: &EventsLoop, info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); if Device::physical_device(info) { // Register for global raw events let mask = ffi::XI_RawMotionMask - | ffi::XI_RawButtonPressMask | ffi::XI_RawButtonReleaseMask - | ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask; + | ffi::XI_RawButtonPressMask + | ffi::XI_RawButtonReleaseMask + | ffi::XI_RawKeyPressMask + | ffi::XI_RawKeyReleaseMask; unsafe { - let mut event_mask = ffi::XIEventMask{ - deviceid: info.deviceid, - mask: &mask as *const _ as *mut c_uchar, - mask_len: mem::size_of_val(&mask) as c_int, - }; - (el.display.xinput2.XISelectEvents)(el.display.display, el.root, &mut event_mask as *mut ffi::XIEventMask, 1); - } + util::select_xinput_events( + &el.display, + el.root, + info.deviceid, + mask, + ) + }.queue(); // The request buffer is flushed when we poll for events // Identify scroll axes for class_ptr in Device::classes(info) { @@ -1195,6 +1327,7 @@ impl Device { let mut device = Device { name: name.into_owned(), scroll_axes: scroll_axes, + attachment: info.attachment, }; device.reset_scroll_position(info); device diff --git a/src/platform/linux/x11/util.rs b/src/platform/linux/x11/util.rs deleted file mode 100644 index 5f185ac9..00000000 --- a/src/platform/linux/x11/util.rs +++ /dev/null @@ -1,365 +0,0 @@ -use std::mem; -use std::ptr; -use std::str; -use std::sync::Arc; -use std::ops::{Deref, DerefMut}; -use std::os::raw::{c_char, c_double, c_int, c_long, c_short, c_uchar, c_uint, c_ulong}; - -use super::{ffi, XConnection, XError}; -use events::ModifiersState; - -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 _); - } - } -} - -pub unsafe fn get_atom(xconn: &Arc, name: &[u8]) -> Result { - let atom_name: *const c_char = name.as_ptr() as _; - let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False); - xconn.check_errors().map(|_| atom) -} - -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), -) -> Result<(), XError> { - 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 = 32; - 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(), - ); - - xconn.check_errors().map(|_| ()) -} - -#[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 - } - } -} - -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 done = false; - while !done { - let mut actual_type: ffi::Atom = mem::uninitialized(); - let mut actual_format: c_int = mem::uninitialized(); - let mut byte_count: 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, - (data.len() / 4) as c_long, - 1024, - ffi::False, - property_type, - &mut actual_type, - &mut actual_format, - &mut byte_count, - &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)); - } - - // Fun fact: actual_format ISN'T the size of the type; it's more like a really bad enum - let format_mismatch = match actual_format as usize { - 8 => mem::size_of::() != mem::size_of::(), - 16 => mem::size_of::() != mem::size_of::(), - 32 => mem::size_of::() != mem::size_of::(), - _ => true, // this won't actually be reached; the XError condition above is triggered - }; - - if format_mismatch { - return Err(GetPropertyError::FormatMismatch(actual_format)); - } - - if !buf.is_null() { - let mut buf = - Vec::from_raw_parts(buf as *mut T, byte_count as usize, byte_count as usize); - data.append(&mut buf); - } else { - return Err(GetPropertyError::NothingAllocated); - } - - done = bytes_after == 0; - } - - Ok(data) -} - -impl From 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, - } - } -} - -#[derive(Debug)] -pub struct PointerState { - #[allow(dead_code)] - root: ffi::Window, - #[allow(dead_code)] - child: ffi::Window, - #[allow(dead_code)] - root_x: c_double, - #[allow(dead_code)] - root_y: c_double, - #[allow(dead_code)] - win_x: c_double, - #[allow(dead_code)] - win_y: c_double, - #[allow(dead_code)] - buttons: ffi::XIButtonState, - modifiers: ffi::XIModifierState, - #[allow(dead_code)] - group: ffi::XIGroupState, - #[allow(dead_code)] - relative_to_window: bool, -} - -impl PointerState { - pub fn get_modifier_state(&self) -> ModifiersState { - self.modifiers.into() - } -} - -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(); - - 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 { - 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, - }) -} - -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) -} - -pub unsafe fn lookup_utf8( - xconn: &Arc, - ic: ffi::XIC, - key_event: &mut ffi::XKeyEvent, -) -> String { - const INIT_BUFF_SIZE: usize = 16; - - // Buffer allocated on heap instead of stack, due to the possible reallocation - let mut buffer: Vec = vec![mem::uninitialized(); INIT_BUFF_SIZE]; - let (_, status, mut count) = lookup_utf8_inner( - xconn, - ic, - key_event, - &mut buffer, - ); - - // Buffer overflowed, dynamically reallocate - if status == ffi::XBufferOverflow { - buffer = vec![mem::uninitialized(); count as usize]; - let (_, _, new_count) = lookup_utf8_inner( - xconn, - ic, - key_event, - &mut buffer, - ); - count = new_count; - } - - str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string() -} - -#[derive(Debug)] -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) - } -} - -#[derive(Debug)] -pub struct WindowGeometry { - pub x: c_int, - pub y: c_int, - pub width: c_uint, - pub height: c_uint, - pub frame: FrameExtents, -} - -impl WindowGeometry { - pub fn get_position(&self) -> (i32, i32) { - (self.x as _, self.y as _) - } - - pub fn get_inner_position(&self) -> (i32, i32) { - ( - self.x.saturating_add(self.frame.left as c_int) as _, - self.y.saturating_add(self.frame.top as c_int) as _, - ) - } - - pub fn get_inner_size(&self) -> (u32, u32) { - (self.width as _, self.height as _) - } - - pub fn get_outer_size(&self) -> (u32, u32) { - ( - self.width.saturating_add( - self.frame.left.saturating_add(self.frame.right) as c_uint - ) as _, - self.height.saturating_add( - self.frame.top.saturating_add(self.frame.bottom) as c_uint - ) as _, - ) - } -} diff --git a/src/platform/linux/x11/util/atom.rs b/src/platform/linux/x11/util/atom.rs new file mode 100644 index 00000000..beb73cdb --- /dev/null +++ b/src/platform/linux/x11/util/atom.rs @@ -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; + +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, + 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, + 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/geometry.rs b/src/platform/linux/x11/util/geometry.rs new file mode 100644 index 00000000..6796a69f --- /dev/null +++ b/src/platform/linux/x11/util/geometry.rs @@ -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, + 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, + // 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, + 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, + 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, + 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, + 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, + 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, + } + } +} diff --git a/src/platform/linux/x11/util/hint.rs b/src/platform/linux/x11/util/hint.rs new file mode 100644 index 00000000..f377a1ab --- /dev/null +++ b/src/platform/linux/x11/util/hint.rs @@ -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 for StateOperation { + fn from(b: bool) -> Self { + if b { + StateOperation::Add + } else { + StateOperation::Remove + } + } +} diff --git a/src/platform/linux/x11/util/input.rs b/src/platform/linux/x11/util/input.rs new file mode 100644 index 00000000..7ff6ef6d --- /dev/null +++ b/src/platform/linux/x11/util/input.rs @@ -0,0 +1,187 @@ +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 + } +} + +impl From 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, + _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, + 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(); + + 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, + 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() + } +} diff --git a/src/platform/linux/x11/util/mod.rs b/src/platform/linux/x11/util/mod.rs new file mode 100644 index 00000000..5ac95d52 --- /dev/null +++ b/src/platform/linux/x11/util/mod.rs @@ -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 { + 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, +} + +impl<'a> Flusher<'a> { + pub fn new(xconn: &'a Arc) -> 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, + 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); + + 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) +} diff --git a/src/platform/linux/x11/util/window_property.rs b/src/platform/linux/x11/util/window_property.rs new file mode 100644 index 00000000..0b33d03e --- /dev/null +++ b/src/platform/linux/x11/util/window_property.rs @@ -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( + 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, +} + +#[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, + 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, + }, + )); + } + + (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) +} diff --git a/src/platform/linux/x11/util/wm.rs b/src/platform/linux/x11/util/wm.rs new file mode 100644 index 00000000..87db0989 --- /dev/null +++ b/src/platform/linux/x11/util/wm.rs @@ -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> = Mutex::new(Vec::with_capacity(0)); + static ref WM_NAME: Mutex> = 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, 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, 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, + root, + supported_atom, + ffi::XA_ATOM, + ) + }.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)"); + + // 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()) +} diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 56fe14f2..00d387ca 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -3,11 +3,12 @@ use CreationError; use CreationError::OsError; use libc; use std::borrow::Borrow; -use std::{mem, cmp, ptr}; -use std::sync::{Arc, Mutex}; -use std::os::raw::{c_int, c_long, c_uchar, c_uint, c_ulong, c_void}; -use std::thread; -use std::time::Duration; +use std::{mem, cmp}; +use std::sync::Arc; +use std::os::raw::*; +use std::ffi::CString; + +use parking_lot::Mutex; use CursorState; use WindowAttributes; @@ -21,72 +22,21 @@ use platform::x11::monitor::get_available_monitors; use super::{ffi, util, XConnection, XError, WindowId, EventsLoop}; -// TODO: remove me -fn with_c_str(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T { - use std::ffi::CString; - let c_str = CString::new(s.as_bytes().to_vec()).unwrap(); - f(c_str.as_ptr()) -} - -#[derive(Debug)] -enum StateOperation { - Remove = 0, // _NET_WM_STATE_REMOVE - Add = 1, // _NET_WM_STATE_ADD - #[allow(dead_code)] - Toggle = 2, // _NET_WM_STATE_TOGGLE -} - -impl From for StateOperation { - fn from(b: bool) -> Self { - if b { - StateOperation::Add - } else { - StateOperation::Remove - } - } +unsafe extern "C" fn visibility_predicate( + _display: *mut ffi::Display, + event: *mut ffi::XEvent, + arg: ffi::XPointer, // We populate this with the window ID (by value) when we call XIfEvent +) -> ffi::Bool { + let event: &ffi::XAnyEvent = (*event).as_ref(); + let window = arg as ffi::Window; + (event.window == window && event.type_ == ffi::VisibilityNotify) as _ } pub struct XWindow { - display: Arc, - window: ffi::Window, - root: ffi::Window, - screen_id: i32, -} - -impl XWindow { - /// Get parent window of `child` - /// - /// This method can return None if underlying xlib call fails. - /// - /// # Unsafety - /// - /// `child` must be a valid `Window`. - unsafe fn get_parent_window(&self, child: ffi::Window) -> Option { - 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: libc::c_uint = mem::uninitialized(); - - let res = (self.display.xlib.XQueryTree)( - self.display.display, - child, - &mut root, - &mut parent, - &mut children, - &mut nchildren - ); - - if res == 0 { - return None; - } - - // The list of children isn't used - if children != ptr::null_mut() { - (self.display.xlib.XFree)(children as *mut _); - } - - Some(parent) - } + pub display: Arc, + pub window: ffi::Window, + pub root: ffi::Window, + pub screen_id: i32, } unsafe impl Send for XWindow {} @@ -95,154 +45,27 @@ unsafe impl Sync for XWindow {} unsafe impl Send for Window2 {} unsafe impl Sync for Window2 {} +#[derive(Debug, Default)] +pub struct SharedState { + pub frame_extents: Option, +} + pub struct Window2 { pub x: Arc, cursor: Mutex, cursor_state: Mutex, - supported_hints: Vec, - wm_name: Option, -} - -fn get_supported_hints(xwin: &Arc) -> Vec { - let supported_atom = unsafe { util::get_atom(&xwin.display, b"_NET_SUPPORTED\0") } - .expect("Failed to call XInternAtom (_NET_SUPPORTED)"); - unsafe { - util::get_property( - &xwin.display, - xwin.root, - supported_atom, - ffi::XA_ATOM, - ) - }.unwrap_or_else(|_| Vec::with_capacity(0)) -} - -fn get_wm_name(xwin: &Arc, _supported_hints: &[ffi::Atom]) -> Option { - let check_atom = unsafe { util::get_atom(&xwin.display, b"_NET_SUPPORTING_WM_CHECK\0") } - .expect("Failed to call XInternAtom (_NET_SUPPORTING_WM_CHECK)"); - let wm_name_atom = unsafe { util::get_atom(&xwin.display, 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 { - util::get_property( - &xwin.display, - xwin.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 { - util::get_property( - &xwin.display, - 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 { util::get_atom(&xwin.display, b"UTF8_STRING\0") } - .unwrap_or(ffi::XA_STRING); - - let result = unsafe { - util::get_property( - &xwin.display, - 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 { - util::get_property( - &xwin.display, - 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()) + pub shared_state: Arc>, } impl Window2 { - pub fn new(ctx: &EventsLoop, window_attrs: &WindowAttributes, - pl_attribs: &PlatformSpecificWindowBuilderAttributes) - -> Result - { - let display = &ctx.display; - let dimensions = { + pub fn new( + ctx: &EventsLoop, + window_attrs: &WindowAttributes, + pl_attribs: &PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let xconn = &ctx.display; + let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions = window_attrs.dimensions.unwrap_or((800, 600)); @@ -250,18 +73,16 @@ impl Window2 { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } - if let Some(min) = window_attrs.min_dimensions { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } dimensions - }; let screen_id = match pl_attribs.screen_id { Some(id) => id, - None => unsafe { (display.xlib.XDefaultScreen)(display.display) }, + None => unsafe { (xconn.xlib.XDefaultScreen)(xconn.display) }, }; // getting the root window @@ -273,13 +94,18 @@ impl Window2 { swa.colormap = if let Some(vi) = pl_attribs.visual_infos { unsafe { let visual = vi.visual; - (display.xlib.XCreateColormap)(display.display, root, visual, ffi::AllocNone) + (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) } } else { 0 }; - swa.event_mask = ffi::ExposureMask | ffi::StructureNotifyMask | - ffi::VisibilityChangeMask | ffi::KeyPressMask | ffi::PointerMotionMask | - ffi::KeyReleaseMask | ffi::ButtonPressMask | - ffi::ButtonReleaseMask | ffi::KeymapStateMask; + swa.event_mask = ffi::ExposureMask + | ffi::StructureNotifyMask + | ffi::VisibilityChangeMask + | ffi::KeyPressMask + | ffi::KeyReleaseMask + | ffi::KeymapStateMask + | ffi::ButtonPressMask + | ffi::ButtonReleaseMask + | ffi::PointerMotionMask; swa.border_pixel = 0; if window_attrs.transparent { swa.background_pixel = 0; @@ -296,89 +122,93 @@ impl Window2 { // finally creating the window let window = unsafe { - let win = (display.xlib.XCreateWindow)(display.display, root, 0, 0, dimensions.0 as libc::c_uint, - dimensions.1 as libc::c_uint, 0, + (xconn.xlib.XCreateWindow)( + xconn.display, + root, + 0, + 0, + dimensions.0 as c_uint, + dimensions.1 as c_uint, + 0, match pl_attribs.visual_infos { Some(vi) => vi.depth, None => ffi::CopyFromParent }, - ffi::InputOutput as libc::c_uint, + ffi::InputOutput as c_uint, match pl_attribs.visual_infos { Some(vi) => vi.visual, None => ffi::CopyFromParent as *mut _ }, window_attributes, - &mut set_win_attr); - display.check_errors().expect("Failed to call XCreateWindow"); - win + &mut set_win_attr, + ) }; let x_window = Arc::new(XWindow { - display: display.clone(), + display: Arc::clone(xconn), window, root, screen_id, }); - // These values will cease to be correct if the user replaces the WM during the life of - // the window, so hopefully they don't do that. - let supported_hints = get_supported_hints(&x_window); - let wm_name = get_wm_name(&x_window, &supported_hints); - let window = Window2 { x: x_window, cursor: Mutex::new(MouseCursor::Default), cursor_state: Mutex::new(CursorState::Normal), - supported_hints, - wm_name, + shared_state: Arc::new(Mutex::new(SharedState::default())), }; - // Title must be set before mapping, lest some tiling window managers briefly pick up on - // the initial un-titled window state - window.set_title(&window_attrs.title); - window.set_decorations(window_attrs.decorations); + // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window + // title to determine placement/etc., so doing this after mapping would cause the WM to + // act on the wrong title state. + window.set_title_inner(&window_attrs.title).queue(); + window.set_decorations_inner(window_attrs.decorations).queue(); { let ref x_window: &XWindow = window.x.borrow(); - // Enable drag and drop + // Enable drag and drop (TODO: extend API to make this toggleable) unsafe { - let atom = util::get_atom(display, b"XdndAware\0") + let dnd_aware_atom = util::get_atom(xconn, b"XdndAware\0") .expect("Failed to call XInternAtom (XdndAware)"); - let version = &5; // Latest version; hasn't changed since 2002 - (display.xlib.XChangeProperty)( - display.display, + let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 + util::change_property( + xconn, x_window.window, - atom, + dnd_aware_atom, ffi::XA_ATOM, - 32, - ffi::PropModeReplace, + util::Format::Long, + util::PropMode::Replace, version, - 1 - ); - display.check_errors().expect("Failed to set drag and drop properties"); - } + ) + }.queue(); // Set ICCCM WM_CLASS property based on initial window title // Must be done *before* mapping the window by ICCCM 4.1.2.5 - unsafe { - with_c_str(&*window_attrs.title, |c_name| { - let hint = (display.xlib.XAllocClassHint)(); - (*hint).res_name = c_name as *mut libc::c_char; - (*hint).res_class = c_name as *mut libc::c_char; - (display.xlib.XSetClassHint)(display.display, x_window.window, hint); - display.check_errors().expect("Failed to call XSetClassHint"); - (display.xlib.XFree)(hint as *mut _); - }); + { + let name = CString::new(window_attrs.title.as_str()) + .expect("Window title contained null byte"); + let mut class_hints = { + let class_hints = unsafe { (xconn.xlib.XAllocClassHint)() }; + util::XSmartPointer::new(xconn, class_hints) + }.expect("XAllocClassHint returned null; out of memory"); + (*class_hints).res_name = name.as_ptr() as *mut c_char; + (*class_hints).res_class = name.as_ptr() as *mut c_char; + unsafe { + (xconn.xlib.XSetClassHint)( + xconn.display, + x_window.window, + class_hints.ptr, + ); + }//.queue(); } // set size hints { let mut size_hints = { - let size_hints = unsafe { (display.xlib.XAllocSizeHints)() }; - util::XSmartPointer::new(&display, size_hints) - .expect("XAllocSizeHints returned null; out of memory") - }; + let size_hints = unsafe { (xconn.xlib.XAllocSizeHints)() }; + util::XSmartPointer::new(xconn, size_hints) + }.expect("XAllocSizeHints returned null; out of memory"); (*size_hints).flags = ffi::PSize; (*size_hints).width = dimensions.0 as c_int; (*size_hints).height = dimensions.1 as c_int; @@ -393,94 +223,102 @@ impl Window2 { (*size_hints).max_height = dimensions.1 as c_int; } unsafe { - (display.xlib.XSetWMNormalHints)( - display.display, + (xconn.xlib.XSetWMNormalHints)( + xconn.display, x_window.window, size_hints.ptr, ); - } - display.check_errors().expect("Failed to call XSetWMNormalHints"); + }//.queue(); } // Opt into handling window close unsafe { - (display.xlib.XSetWMProtocols)(display.display, x_window.window, &ctx.wm_delete_window as *const _ as *mut _, 1); - display.check_errors().expect("Failed to call XSetWMProtocols"); - (display.xlib.XFlush)(display.display); - display.check_errors().expect("Failed to call XFlush"); - } + (xconn.xlib.XSetWMProtocols)( + xconn.display, + x_window.window, + &ctx.wm_delete_window as *const _ as *mut _, + 1, + ); + }//.queue(); // Set visibility (map window) if window_attrs.visible { unsafe { - (display.xlib.XMapRaised)(display.display, x_window.window); - (display.xlib.XFlush)(display.display); - } - - display.check_errors().expect("Failed to set window visibility"); + (xconn.xlib.XMapRaised)(xconn.display, x_window.window); + }//.queue(); } // Attempt to make keyboard input repeat detectable unsafe { let mut supported_ptr = ffi::False; - (display.xlib.XkbSetDetectableAutoRepeat)(display.display, ffi::True, &mut supported_ptr); + (xconn.xlib.XkbSetDetectableAutoRepeat)( + xconn.display, + ffi::True, + &mut supported_ptr, + ); if supported_ptr == ffi::False { return Err(OsError(format!("XkbSetDetectableAutoRepeat failed"))); } } // Select XInput2 events - { - let mask = ffi::XI_MotionMask - | ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask - // | ffi::XI_KeyPressMask | ffi::XI_KeyReleaseMask - | ffi::XI_EnterMask | ffi::XI_LeaveMask - | ffi::XI_FocusInMask | ffi::XI_FocusOutMask - | if window_attrs.multitouch { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 }; - unsafe { - let mut event_mask = ffi::XIEventMask{ - deviceid: ffi::XIAllMasterDevices, - mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32), - mask_len: mem::size_of_val(&mask) as c_int, - }; - (display.xinput2.XISelectEvents)(display.display, x_window.window, - &mut event_mask as *mut ffi::XIEventMask, 1); - }; - } + let mask = { + let mut mask = ffi::XI_MotionMask + | ffi::XI_ButtonPressMask + | ffi::XI_ButtonReleaseMask + //| ffi::XI_KeyPressMask + //| ffi::XI_KeyReleaseMask + | ffi::XI_EnterMask + | ffi::XI_LeaveMask + | ffi::XI_FocusInMask + | ffi::XI_FocusOutMask; + if window_attrs.multitouch { + mask |= ffi::XI_TouchBeginMask + | ffi::XI_TouchUpdateMask + | ffi::XI_TouchEndMask; + } + mask + }; + unsafe { + util::select_xinput_events( + xconn, + x_window.window, + ffi::XIAllMasterDevices, + mask, + ) + }.queue(); // These properties must be set after mapping - window.set_maximized(window_attrs.maximized); - window.set_fullscreen(window_attrs.fullscreen.clone()); + window.set_maximized_inner(window_attrs.maximized).queue(); + window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue(); if window_attrs.visible { unsafe { - // XSetInputFocus generates an error if the window is not visible, - // therefore we wait until it's the case. - loop { - let mut window_attributes = mem::uninitialized(); - (display.xlib.XGetWindowAttributes)(display.display, x_window.window, &mut window_attributes); - display.check_errors().expect("Failed to call XGetWindowAttributes"); - - if window_attributes.map_state == ffi::IsViewable { - (display.xlib.XSetInputFocus)( - display.display, - x_window.window, - ffi::RevertToParent, - ffi::CurrentTime - ); - display.check_errors().expect("Failed to call XSetInputFocus"); - break; - } - - // Wait about a frame to avoid too-busy waiting - thread::sleep(Duration::from_millis(16)); - } + // XSetInputFocus generates an error if the window is not visible, so we wait + // until we receive VisibilityNotify. + let mut event = mem::uninitialized(); + (xconn.xlib.XIfEvent)( // This will flush the request buffer IF it blocks. + xconn.display, + &mut event as *mut ffi::XEvent, + Some(visibility_predicate), + x_window.window as _, + ); + (xconn.xlib.XSetInputFocus)( + xconn.display, + x_window.window, + ffi::RevertToParent, + ffi::CurrentTime, + ); } } } - // returning - Ok(window) + // 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) } + .map(|_| window) + .map_err(|x_err| OsError( + format!("X server returned error while building window: {:?}", x_err) + )) } fn set_netwm( @@ -488,8 +326,8 @@ impl Window2 { window: ffi::Window, root: ffi::Window, properties: (c_long, c_long, c_long, c_long), - operation: StateOperation - ) { + 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)"); @@ -508,25 +346,45 @@ impl Window2 { properties.3, ) ) - }.expect("Failed to send NET_WM hint."); + } } - pub fn set_fullscreen(&self, monitor: Option) { + 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)"); + + Window2::set_netwm( + xconn, + self.x.window, + self.x.root, + (fullscreen_atom as c_long, 0, 0, 0), + fullscreen.into(), + ) + } + + fn set_fullscreen_inner(&self, monitor: Option) -> util::Flusher { match monitor { None => { - self.set_fullscreen_hint(false); + self.set_fullscreen_hint(false) }, Some(RootMonitorId { inner: PlatformMonitorId::X(monitor) }) => { let screenpos = monitor.get_position(); self.set_position(screenpos.0 as i32, screenpos.1 as i32); - self.set_fullscreen_hint(true); - } - _ => { - eprintln!("[winit] Something's broken, got an unknown fullscreen state in X11"); + self.set_fullscreen_hint(true) } + _ => unreachable!(), } } + pub fn set_fullscreen(&self, monitor: Option) { + self.set_fullscreen_inner(monitor) + .flush() + .expect("Failed to change window fullscreen state"); + self.invalidate_cached_frame_extents(); + } + pub fn get_current_monitor(&self) -> X11MonitorId { let monitors = get_available_monitors(&self.x.display); let default = monitors[0].clone(); @@ -564,7 +422,7 @@ impl Window2 { find } - pub fn set_maximized(&self, maximized: bool) { + 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") } @@ -577,362 +435,143 @@ impl Window2 { self.x.window, self.x.root, (horz_atom as c_long, vert_atom as c_long, 0, 0), - maximized.into() - ); + maximized.into(), + ) } - fn set_fullscreen_hint(&self, fullscreen: bool) { + pub fn set_maximized(&self, maximized: bool) { + self.set_maximized_inner(maximized) + .flush() + .expect("Failed to change window maximization"); + self.invalidate_cached_frame_extents(); + } + + fn set_title_inner(&self, title: &str) -> 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 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)"); - Window2::set_netwm( - xconn, - self.x.window, - self.x.root, - (fullscreen_atom as c_long, 0, 0, 0), - fullscreen.into() - ); + let title = CString::new(title).expect("Window title contained null byte"); + unsafe { + (xconn.xlib.XStoreName)( + xconn.display, + self.x.window, + title.as_ptr() as *const c_char, + ); + + util::change_property( + xconn, + self.x.window, + wm_name_atom, + utf8_atom, + util::Format::Char, + util::PropMode::Replace, + title.as_bytes_with_nul(), + ) + } } pub fn set_title(&self, title: &str) { - let wm_name = unsafe { - (self.x.display.xlib.XInternAtom)(self.x.display.display, b"_NET_WM_NAME\0".as_ptr() as *const _, 0) - }; - self.x.display.check_errors().expect("Failed to call XInternAtom"); - - let wm_utf8_string = unsafe { - (self.x.display.xlib.XInternAtom)(self.x.display.display, b"UTF8_STRING\0".as_ptr() as *const _, 0) - }; - self.x.display.check_errors().expect("Failed to call XInternAtom"); - - with_c_str(title, |c_title| unsafe { - (self.x.display.xlib.XStoreName)(self.x.display.display, self.x.window, c_title); - - let len = title.as_bytes().len(); - (self.x.display.xlib.XChangeProperty)(self.x.display.display, self.x.window, - wm_name, wm_utf8_string, 8, ffi::PropModeReplace, - c_title as *const u8, len as libc::c_int); - (self.x.display.xlib.XFlush)(self.x.display.display); - }); - self.x.display.check_errors().expect("Failed to set window title"); - + self.set_title_inner(title) + .flush() + .expect("Failed to set window title"); } - pub fn set_decorations(&self, decorations: bool) { - #[repr(C)] - struct MotifWindowHints { - flags: c_ulong, - functions: c_ulong, - decorations: c_ulong, - input_mode: c_long, - status: c_ulong, - } + fn set_decorations_inner(&self, decorations: bool) -> util::Flusher { + let xconn = &self.x.display; - let wm_hints = unsafe { util::get_atom(&self.x.display, b"_MOTIF_WM_HINTS\0") } + let wm_hints = unsafe { util::get_atom(xconn, b"_MOTIF_WM_HINTS\0") } .expect("Failed to call XInternAtom (_MOTIF_WM_HINTS)"); - let hints = MotifWindowHints { - flags: 2, // MWM_HINTS_DECORATIONS - functions: 0, - decorations: decorations as _, - input_mode: 0, - status: 0, - }; - unsafe { - (self.x.display.xlib.XChangeProperty)( - self.x.display.display, + util::change_property( + xconn, self.x.window, wm_hints, wm_hints, - 32, // struct members are longs - ffi::PropModeReplace, - &hints as *const _ as *const u8, - 5 // struct has 5 members - ); - (self.x.display.xlib.XFlush)(self.x.display.display); + util::Format::Long, + util::PropMode::Replace, + &[ + util::MWM_HINTS_DECORATIONS, // flags + 0, // functions + decorations as c_ulong, // decorations + 0, // input mode + 0, // status + ], + ) } + } - self.x.display.check_errors().expect("Failed to set decorations"); + pub fn set_decorations(&self, decorations: bool) { + self.set_decorations_inner(decorations) + .flush() + .expect("Failed to set decoration state"); + self.invalidate_cached_frame_extents(); } pub fn show(&self) { unsafe { (self.x.display.xlib.XMapRaised)(self.x.display.display, self.x.window); - (self.x.display.xlib.XFlush)(self.x.display.display); - self.x.display.check_errors().expect("Failed to call XMapRaised"); + util::flush_requests(&self.x.display) + .expect("Failed to call XMapRaised"); } } pub fn hide(&self) { unsafe { (self.x.display.xlib.XUnmapWindow)(self.x.display.display, self.x.window); - (self.x.display.xlib.XFlush)(self.x.display.display); - self.x.display.check_errors().expect("Failed to call XUnmapWindow"); + util::flush_requests(&self.x.display) + .expect("Failed to call XUnmapWindow"); } } - fn get_frame_extents(&self) -> Option { - let extents_atom = unsafe { util::get_atom(&self.x.display, b"_NET_FRAME_EXTENTS\0") } - .expect("Failed to call XInternAtom (_NET_FRAME_EXTENTS)"); - - if !self.supported_hints.contains(&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 { - util::get_property( - &self.x.display, - self.x.window, - extents_atom, - ffi::XA_CARDINAL, - ) - }.ok(); - - extents.and_then(|extents| { - if extents.len() >= 4 { - Some(util::FrameExtents { - left: extents[0], - right: extents[1], - top: extents[2], - bottom: extents[3], - }) - } else { - None - } - }) + fn update_cached_frame_extents(&self) { + let extents = util::get_frame_extents_heuristic( + &self.x.display, + self.x.window, + self.x.root, + ); + (*self.shared_state.lock()).frame_extents = Some(extents); } - fn is_top_level(&self, id: ffi::Window) -> Option { - let client_list_atom = unsafe { util::get_atom(&self.x.display, b"_NET_CLIENT_LIST\0") } - .expect("Failed to call XInternAtom (_NET_CLIENT_LIST)"); - - if !self.supported_hints.contains(&client_list_atom) { - return None; - } - - let client_list: Option> = unsafe { - util::get_property( - &self.x.display, - self.x.root, - client_list_atom, - ffi::XA_WINDOW, - ) - }.ok(); - - client_list.map(|client_list| { - client_list.contains(&id) - }) - } - - fn get_geometry(&self) -> Option { - // 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 sometimes aren't nested. - let (inner_x_rel_root, inner_y_rel_root, child) = unsafe { - let mut inner_x_rel_root: c_int = mem::uninitialized(); - let mut inner_y_rel_root: c_int = mem::uninitialized(); - let mut child: ffi::Window = mem::uninitialized(); - - (self.x.display.xlib.XTranslateCoordinates)( - self.x.display.display, - self.x.window, - self.x.root, - 0, - 0, - &mut inner_x_rel_root, - &mut inner_y_rel_root, - &mut child, - ); - - (inner_x_rel_root, inner_y_rel_root, child) - }; - - let (inner_x, inner_y, width, height, border) = unsafe { - let mut root: ffi::Window = mem::uninitialized(); - // The same caveat outlined in the comment above for XTranslateCoordinates applies - // here as well. The only difference is that this position is relative to the parent - // window, rather than the root window. - let mut inner_x: c_int = mem::uninitialized(); - let mut inner_y: c_int = mem::uninitialized(); - // The width and height here are for the client area. - let mut width: c_uint = mem::uninitialized(); - let mut height: c_uint = mem::uninitialized(); - // 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. - let mut border: c_uint = mem::uninitialized(); - let mut depth: c_uint = mem::uninitialized(); - - let status = (self.x.display.xlib.XGetGeometry)( - self.x.display.display, - self.x.window, - &mut root, - &mut inner_x, - &mut inner_y, - &mut width, - &mut height, - &mut border, - &mut depth, - ); - - if status == 0 { - return None; - } - - (inner_x, inner_y, width, height, 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 = !(self.x.window == child || self.is_top_level(child) == Some(true)); - - // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames. - if let Some(mut extents) = self.get_frame_extents() { - // 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 { - extents = util::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. - let abs_x = inner_x_rel_root - extents.left as c_int; - let abs_y = inner_y_rel_root - extents.top as c_int; - - Some(util::WindowGeometry { - x: abs_x, - y: abs_y, - width, - height, - frame: extents, - }) - } 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 window = { - let root = self.x.root; - let mut window = self.x.window; - loop { - let candidate = unsafe { - self.x.get_parent_window(window).unwrap() - }; - if candidate == root { - break window; - } - window = candidate; - } - }; - - let (outer_x, outer_y, outer_width, outer_height) = unsafe { - let mut root: ffi::Window = mem::uninitialized(); - let mut outer_x: c_int = mem::uninitialized(); - let mut outer_y: c_int = mem::uninitialized(); - let mut outer_width: c_uint = mem::uninitialized(); - let mut outer_height: c_uint = mem::uninitialized(); - let mut border: c_uint = mem::uninitialized(); - let mut depth: c_uint = mem::uninitialized(); - - let status = (self.x.display.xlib.XGetGeometry)( - self.x.display.display, - window, - &mut root, - &mut outer_x, - &mut outer_y, - &mut outer_width, - &mut outer_height, - &mut border, - &mut depth, - ); - - if status == 0 { - return None; - } - - (outer_x, outer_y, outer_width, outer_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 frame = { - 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); - - util::FrameExtents::new(left.into(), right.into(), top.into(), bottom.into()) - }; - - Some(util::WindowGeometry { - x: outer_x, - y: outer_y, - width, - height, - frame, - }) - } 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 = util::FrameExtents::from_border(border.into()); - Some(util::WindowGeometry { - x: inner_x, - y: inner_y, - width, - height, - frame, - }) - } + fn invalidate_cached_frame_extents(&self) { + (*self.shared_state.lock()).frame_extents.take(); } #[inline] pub fn get_position(&self) -> Option<(i32, i32)> { - self.get_geometry().map(|geo| geo.get_position()) + let extents = (*self.shared_state.lock()).frame_extents.clone(); + if let Some(extents) = extents { + self.get_inner_position().map(|(x, y)| + extents.inner_pos_to_outer(x, y) + ) + } else { + self.update_cached_frame_extents(); + self.get_position() + } } #[inline] pub fn get_inner_position(&self) -> Option<(i32, i32)> { - self.get_geometry().map(|geo| geo.get_inner_position()) + unsafe { util::translate_coords(&self.x.display, self.x.window, self.x.root )} + .ok() + .map(|coords| (coords.x_rel_root, coords.y_rel_root)) } pub fn set_position(&self, mut x: i32, mut y: i32) { - if let Some(ref wm_name) = self.wm_name { - // There are a few WMs that set client area position rather than window position, so - // we'll translate for consistency. - if ["Enlightenment", "FVWM"].contains(&wm_name.as_str()) { - if let Some(extents) = self.get_frame_extents() { - x += extents.left as i32; - y += extents.top as i32; - } + // There are a few WMs that set client area position rather than window position, so + // we'll translate for consistency. + if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { + let extents = (*self.shared_state.lock()).frame_extents.clone(); + if let Some(extents) = extents { + x += extents.frame_extents.left as i32; + y += extents.frame_extents.top as i32; + } else { + self.update_cached_frame_extents(); + self.set_position(x, y) } } unsafe { @@ -942,24 +581,41 @@ impl Window2 { x as c_int, y as c_int, ); - } - self.x.display.check_errors().expect("Failed to call XMoveWindow"); + util::flush_requests(&self.x.display) + }.expect("Failed to call XMoveWindow"); } #[inline] pub fn get_inner_size(&self) -> Option<(u32, u32)> { - self.get_geometry().map(|geo| geo.get_inner_size()) + unsafe { util::get_geometry(&self.x.display, self.x.window) } + .ok() + .map(|geo| (geo.width, geo.height)) } #[inline] pub fn get_outer_size(&self) -> Option<(u32, u32)> { - self.get_geometry().map(|geo| geo.get_outer_size()) + let extents = (*self.shared_state.lock()).frame_extents.clone(); + if let Some(extents) = extents { + self.get_inner_size().map(|(w, h)| + extents.inner_size_to_outer(w, h) + ) + } else { + self.update_cached_frame_extents(); + self.get_outer_size() + } } #[inline] pub fn set_inner_size(&self, x: u32, y: u32) { - unsafe { (self.x.display.xlib.XResizeWindow)(self.x.display.display, self.x.window, x as libc::c_uint, y as libc::c_uint); } - self.x.display.check_errors().expect("Failed to call XResizeWindow"); + unsafe { + (self.x.display.xlib.XResizeWindow)( + self.x.display.display, + self.x.window, + x as c_uint, + y as c_uint, + ); + util::flush_requests(&self.x.display) + }.expect("Failed to call XResizeWindow"); } unsafe fn update_normal_hints(&self, callback: F) -> Result<(), XError> @@ -990,7 +646,7 @@ impl Window2 { self.x.window, size_hints.ptr, ); - xconn.check_errors()?; + util::flush_requests(xconn)?; Ok(()) } @@ -1055,19 +711,20 @@ impl Window2 { pub fn get_xcb_connection(&self) -> *mut c_void { unsafe { - (self.x.display.xlib_xcb.XGetXCBConnection)(self.get_xlib_display() as *mut _) as *mut _ + (self.x.display.xlib_xcb.XGetXCBConnection)(self.x.display.display) as *mut _ } } - fn load_cursor(&self, name: &str) -> ffi::Cursor { - use std::ffi::CString; + fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { unsafe { - let c_string = CString::new(name.as_bytes()).unwrap(); - (self.x.display.xcursor.XcursorLibraryLoadCursor)(self.x.display.display, c_string.as_ptr()) + (self.x.display.xcursor.XcursorLibraryLoadCursor)( + self.x.display.display, + name.as_ptr() as *const c_char, + ) } } - fn load_first_existing_cursor(&self, names :&[&str]) -> ffi::Cursor { + fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { for name in names.iter() { let xcursor = self.load_cursor(name); if xcursor != 0 { @@ -1078,11 +735,11 @@ impl Window2 { } fn get_cursor(&self, cursor: MouseCursor) -> ffi::Cursor { - let load = |name: &str| { + let load = |name: &[u8]| { self.load_cursor(name) }; - let loadn = |names: &[&str]| { + let loadn = |names: &[&[u8]]| { self.load_first_existing_cursor(names) }; @@ -1091,50 +748,51 @@ impl Window2 { // // Try the better looking (or more suiting) names first. match cursor { - MouseCursor::Alias => load("link"), - MouseCursor::Arrow => load("arrow"), - MouseCursor::Cell => load("plus"), - MouseCursor::Copy => load("copy"), - MouseCursor::Crosshair => load("crosshair"), - MouseCursor::Default => load("left_ptr"), - MouseCursor::Hand => loadn(&["hand2", "hand1"]), - MouseCursor::Help => load("question_arrow"), - MouseCursor::Move => load("move"), - MouseCursor::Grab => loadn(&["openhand", "grab"]), - MouseCursor::Grabbing => loadn(&["closedhand", "grabbing"]), - MouseCursor::Progress => load("left_ptr_watch"), - MouseCursor::AllScroll => load("all-scroll"), - MouseCursor::ContextMenu => load("context-menu"), + MouseCursor::Alias => load(b"link\0"), + MouseCursor::Arrow => load(b"arrow\0"), + MouseCursor::Cell => load(b"plus\0"), + MouseCursor::Copy => load(b"copy\0"), + MouseCursor::Crosshair => load(b"crosshair\0"), + MouseCursor::Default => load(b"left_ptr\0"), + MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]), + MouseCursor::Help => load(b"question_arrow\0"), + MouseCursor::Move => load(b"move\0"), + MouseCursor::Grab => loadn(&[b"openhand\0", b"grab\0"]), + MouseCursor::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), + MouseCursor::Progress => load(b"left_ptr_watch\0"), + MouseCursor::AllScroll => load(b"all-scroll\0"), + MouseCursor::ContextMenu => load(b"context-menu\0"), - MouseCursor::NoDrop => loadn(&["no-drop", "circle"]), - MouseCursor::NotAllowed => load("crossed_circle"), + MouseCursor::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), + MouseCursor::NotAllowed => load(b"crossed_circle\0"), // Resize cursors - MouseCursor::EResize => load("right_side"), - MouseCursor::NResize => load("top_side"), - MouseCursor::NeResize => load("top_right_corner"), - MouseCursor::NwResize => load("top_left_corner"), - MouseCursor::SResize => load("bottom_side"), - MouseCursor::SeResize => load("bottom_right_corner"), - MouseCursor::SwResize => load("bottom_left_corner"), - MouseCursor::WResize => load("left_side"), - MouseCursor::EwResize => load("h_double_arrow"), - MouseCursor::NsResize => load("v_double_arrow"), - MouseCursor::NwseResize => loadn(&["bd_double_arrow", "size_bdiag"]), - MouseCursor::NeswResize => loadn(&["fd_double_arrow", "size_fdiag"]), - MouseCursor::ColResize => loadn(&["split_h", "h_double_arrow"]), - MouseCursor::RowResize => loadn(&["split_v", "v_double_arrow"]), + MouseCursor::EResize => load(b"right_side\0"), + MouseCursor::NResize => load(b"top_side\0"), + MouseCursor::NeResize => load(b"top_right_corner\0"), + MouseCursor::NwResize => load(b"top_left_corner\0"), + MouseCursor::SResize => load(b"bottom_side\0"), + MouseCursor::SeResize => load(b"bottom_right_corner\0"), + MouseCursor::SwResize => load(b"bottom_left_corner\0"), + MouseCursor::WResize => load(b"left_side\0"), + MouseCursor::EwResize => load(b"h_double_arrow\0"), + MouseCursor::NsResize => load(b"v_double_arrow\0"), + MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), + MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), + MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), + MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), - MouseCursor::Text => loadn(&["text", "xterm"]), - MouseCursor::VerticalText => load("vertical-text"), + MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]), + MouseCursor::VerticalText => load(b"vertical-text\0"), - MouseCursor::Wait => load("watch"), + MouseCursor::Wait => load(b"watch\0"), - MouseCursor::ZoomIn => load("zoom-in"), - MouseCursor::ZoomOut => load("zoom-out"), + MouseCursor::ZoomIn => load(b"zoom-in\0"), + MouseCursor::ZoomOut => load(b"zoom-out\0"), - MouseCursor::NoneCursor => self.create_empty_cursor(), + MouseCursor::NoneCursor => self.create_empty_cursor() + .expect("Failed to create empty cursor"), } } @@ -1144,14 +802,14 @@ impl Window2 { if cursor != 0 { (self.x.display.xlib.XFreeCursor)(self.x.display.display, cursor); } - self.x.display.check_errors().expect("Failed to set or free the cursor"); + util::flush_requests(&self.x.display).expect("Failed to set or free the cursor"); } } pub fn set_cursor(&self, cursor: MouseCursor) { - let mut current_cursor = self.cursor.lock().unwrap(); + let mut current_cursor = self.cursor.lock(); *current_cursor = cursor; - if *self.cursor_state.lock().unwrap() != CursorState::Hide { + if *self.cursor_state.lock() != CursorState::Hide { self.update_cursor(self.get_cursor(*current_cursor)); } } @@ -1159,34 +817,45 @@ impl Window2 { // TODO: This could maybe be cached. I don't think it's worth // the complexity, since cursor changes are not so common, // and this is just allocating a 1x1 pixmap... - fn create_empty_cursor(&self) -> ffi::Cursor { - use std::mem; - + fn create_empty_cursor(&self) -> Option { let data = 0; - unsafe { - let pixmap = (self.x.display.xlib.XCreateBitmapFromData)(self.x.display.display, self.x.window, &data, 1, 1); - if pixmap == 0 { - // Failed to allocate - return 0; - } + let pixmap = unsafe { + (self.x.display.xlib.XCreateBitmapFromData)( + self.x.display.display, + self.x.window, + &data, + 1, + 1, + ) + }; + if pixmap == 0 { + // Failed to allocate + return None; + } + let cursor = unsafe { // We don't care about this color, since it only fills bytes // in the pixmap which are not 0 in the mask. let dummy_color: ffi::XColor = mem::uninitialized(); - let cursor = (self.x.display.xlib.XCreatePixmapCursor)(self.x.display.display, - pixmap, - pixmap, - &dummy_color as *const _ as *mut _, - &dummy_color as *const _ as *mut _, 0, 0); + let cursor = (self.x.display.xlib.XCreatePixmapCursor)( + self.x.display.display, + pixmap, + pixmap, + &dummy_color as *const _ as *mut _, + &dummy_color as *const _ as *mut _, + 0, + 0, + ); (self.x.display.xlib.XFreePixmap)(self.x.display.display, pixmap); cursor - } + }; + Some(cursor) } pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { use CursorState::{ Grab, Normal, Hide }; - let mut cursor_state = self.cursor_state.lock().unwrap(); + let mut cursor_state = self.cursor_state.lock(); match (state, *cursor_state) { (Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()), _ => {}, @@ -1196,11 +865,11 @@ impl Window2 { Grab => { unsafe { (self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime); - self.x.display.check_errors().expect("Failed to call XUngrabPointer"); + util::flush_requests(&self.x.display).expect("Failed to call XUngrabPointer"); } }, Normal => {}, - Hide => self.update_cursor(self.get_cursor(*self.cursor.lock().unwrap())), + Hide => self.update_cursor(self.get_cursor(*self.cursor.lock())), } match state { @@ -1210,7 +879,9 @@ impl Window2 { }, Hide => { *cursor_state = state; - self.update_cursor(self.create_empty_cursor()); + self.update_cursor( + self.create_empty_cursor().expect("Failed to create empty cursor") + ); Ok(()) }, Grab => { @@ -1225,7 +896,7 @@ impl Window2 { ffi::LeaveWindowMask | ffi::PointerMotionMask | ffi::PointerMotionHintMask | ffi::Button1MotionMask | ffi::Button2MotionMask | ffi::Button3MotionMask | ffi::Button4MotionMask | ffi::Button5MotionMask | ffi::ButtonMotionMask | - ffi::KeymapStateMask) as libc::c_uint, + ffi::KeymapStateMask) as c_uint, ffi::GrabModeAsync, ffi::GrabModeAsync, self.x.window, 0, ffi::CurrentTime ) { @@ -1256,8 +927,18 @@ impl Window2 { pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { unsafe { - (self.x.display.xlib.XWarpPointer)(self.x.display.display, 0, self.x.window, 0, 0, 0, 0, x, y); - self.x.display.check_errors().map_err(|_| ()) + (self.x.display.xlib.XWarpPointer)( + self.x.display.display, + 0, + self.x.window, + 0, + 0, + 0, + 0, + x, + y, + ); + util::flush_requests(&self.x.display).map_err(|_| ()) } } diff --git a/src/platform/linux/x11/xdisplay.rs b/src/platform/linux/x11/xdisplay.rs index ec325929..c0f1e7d2 100644 --- a/src/platform/linux/x11/xdisplay.rs +++ b/src/platform/linux/x11/xdisplay.rs @@ -1,9 +1,9 @@ use std::ptr; use std::fmt; use std::error::Error; -use std::sync::Mutex; use libc; +use parking_lot::Mutex; use super::ffi; @@ -29,12 +29,12 @@ pub type XErrorHandler = Option Result { // opening the libraries - let xlib = try!(ffi::Xlib::open()); - let xcursor = try!(ffi::Xcursor::open()); - let xrandr = try!(ffi::Xrandr_2_2_0::open()); + let xlib = ffi::Xlib::open()?; + let xcursor = ffi::Xcursor::open()?; + let xrandr = ffi::Xrandr_2_2_0::open()?; let xrandr_1_5 = ffi::Xrandr::open().ok(); - let xinput2 = try!(ffi::XInput2::open()); - let xlib_xcb = try!(ffi::Xlib_xcb::open()); + let xinput2 = ffi::XInput2::open()?; + let xlib_xcb = ffi::Xlib_xcb::open()?; unsafe { (xlib.XInitThreads)() }; unsafe { (xlib.XSetErrorHandler)(error_handler) }; @@ -49,13 +49,13 @@ impl XConnection { }; Ok(XConnection { - xlib: xlib, - xrandr: xrandr, - xrandr_1_5: xrandr_1_5, - xcursor: xcursor, - xinput2: xinput2, - xlib_xcb: xlib_xcb, - display: display, + xlib, + xrandr, + xrandr_1_5, + xcursor, + xinput2, + xlib_xcb, + display, latest_error: Mutex::new(None), }) } @@ -63,8 +63,7 @@ impl XConnection { /// Checks whether an error has been triggered by the previous function calls. #[inline] pub fn check_errors(&self) -> Result<(), XError> { - let error = self.latest_error.lock().unwrap().take(); - + let error = self.latest_error.lock().take(); if let Some(error) = error { Err(error) } else { @@ -75,7 +74,13 @@ impl XConnection { /// Ignores any previous error. #[inline] pub fn ignore_error(&self) { - *self.latest_error.lock().unwrap() = None; + *self.latest_error.lock() = None; + } +} + +impl fmt::Debug for XConnection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.display.fmt(f) } }