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