Fix x11 Window::get_position wrong values (#386)

Some window managers like i3wm will actually nest application windows
(like those opened by winit) within other windows to, for example, add
decorations. Initially when debugging this method on i3, the x and y
positions were always returned as "2".

The solution that other xlib abstractions use is to climb up the window
hierarchy until just below the root window, and that window must be used
to determine the appropriate position.

This patch doesn't take into account borders or the window's offset
within its parent, but it's much more usable than the original
implementation on certain WMs.
This commit is contained in:
Joe Wilm 2018-01-27 05:26:13 -08:00 committed by Pierre Krieger
parent 7d38ed2fab
commit 150d2706f9

View file

@ -3,7 +3,7 @@ use CreationError;
use CreationError::OsError;
use libc;
use std::borrow::Borrow;
use std::{mem, cmp};
use std::{mem, cmp, ptr};
use std::sync::{Arc, Mutex};
use std::os::raw::{c_int, c_long, c_uchar, c_ulong, c_void};
use std::thread;
@ -53,6 +53,42 @@ pub struct XWindow {
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<ffi::Window> {
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)
}
}
unsafe impl Send for XWindow {}
unsafe impl Sync for XWindow {}
@ -497,7 +533,29 @@ impl Window2 {
let mut border: libc::c_uint = mem::uninitialized();
let mut depth: libc::c_uint = mem::uninitialized();
if (self.x.display.xlib.XGetGeometry)(self.x.display.display, self.x.window,
// Some window managers like i3wm will actually nest application
// windows (like those opened by winit) within other windows to, for
// example, add decorations. Initially when debugging this method on
// i3, the x and y positions were always returned as "2".
//
// The solution that other xlib abstractions use is to climb up the
// window hierarchy until just below the root window, and that
// window must be used to determine the appropriate position.
let window = {
let root = self.x.root;
let mut window = self.x.window;
loop {
let candidate = self.x.get_parent_window(window).unwrap();
if candidate == root {
break window;
}
window = candidate;
}
};
if (self.x.display.xlib.XGetGeometry)(self.x.display.display, window,
&mut root, &mut x, &mut y, &mut width, &mut height,
&mut border, &mut depth) == 0
{