1 use std::cmp;
2 
3 use super::*;
4 
5 // Friendly neighborhood axis-aligned rectangle
6 #[derive(Debug, Clone, PartialEq, Eq)]
7 pub struct AaRect {
8     x: i64,
9     y: i64,
10     width: i64,
11     height: i64,
12 }
13 
14 impl AaRect {
new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self15     pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
16         let (x, y) = (x as i64, y as i64);
17         let (width, height) = (width as i64, height as i64);
18         AaRect {
19             x,
20             y,
21             width,
22             height,
23         }
24     }
25 
contains_point(&self, x: i64, y: i64) -> bool26     pub fn contains_point(&self, x: i64, y: i64) -> bool {
27         x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
28     }
29 
get_overlapping_area(&self, other: &Self) -> i6430     pub fn get_overlapping_area(&self, other: &Self) -> i64 {
31         let x_overlap = cmp::max(
32             0,
33             cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
34         );
35         let y_overlap = cmp::max(
36             0,
37             cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
38         );
39         x_overlap * y_overlap
40     }
41 }
42 
43 #[derive(Debug, Default)]
44 pub struct TranslatedCoords {
45     pub x_rel_root: c_int,
46     pub y_rel_root: c_int,
47     pub child: ffi::Window,
48 }
49 
50 #[derive(Debug, Default)]
51 pub struct Geometry {
52     pub root: ffi::Window,
53     // If you want positions relative to the root window, use translate_coords.
54     // Note that the overwhelming majority of window managers are reparenting WMs, thus the window
55     // ID we get from window creation is for a nested window used as the window's client area. If
56     // you call get_geometry with that window ID, then you'll get the position of that client area
57     // window relative to the parent it's nested in (the frame), which isn't helpful if you want
58     // to know the frame position.
59     pub x_rel_parent: c_int,
60     pub y_rel_parent: c_int,
61     // In that same case, this will give you client area size.
62     pub width: c_uint,
63     pub height: c_uint,
64     // xmonad and dwm were the only WMs tested that use the border return at all.
65     // The majority of WMs seem to simply fill it with 0 unconditionally.
66     pub border: c_uint,
67     pub depth: c_uint,
68 }
69 
70 #[derive(Debug, Clone)]
71 pub struct FrameExtents {
72     pub left: c_ulong,
73     pub right: c_ulong,
74     pub top: c_ulong,
75     pub bottom: c_ulong,
76 }
77 
78 impl FrameExtents {
new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self79     pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self {
80         FrameExtents {
81             left,
82             right,
83             top,
84             bottom,
85         }
86     }
87 
from_border(border: c_ulong) -> Self88     pub fn from_border(border: c_ulong) -> Self {
89         Self::new(border, border, border, border)
90     }
91 }
92 
93 #[derive(Debug, Clone)]
94 pub struct LogicalFrameExtents {
95     pub left: f64,
96     pub right: f64,
97     pub top: f64,
98     pub bottom: f64,
99 }
100 
101 #[derive(Debug, Clone, PartialEq)]
102 pub enum FrameExtentsHeuristicPath {
103     Supported,
104     UnsupportedNested,
105     UnsupportedBordered,
106 }
107 
108 #[derive(Debug, Clone)]
109 pub struct FrameExtentsHeuristic {
110     pub frame_extents: FrameExtents,
111     pub heuristic_path: FrameExtentsHeuristicPath,
112 }
113 
114 impl FrameExtentsHeuristic {
inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32)115     pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
116         use self::FrameExtentsHeuristicPath::*;
117         if self.heuristic_path != UnsupportedBordered {
118             (
119                 x - self.frame_extents.left as i32,
120                 y - self.frame_extents.top as i32,
121             )
122         } else {
123             (x, y)
124         }
125     }
126 
inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32)127     pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
128         (
129             width.saturating_add(
130                 self.frame_extents
131                     .left
132                     .saturating_add(self.frame_extents.right) as u32,
133             ),
134             height.saturating_add(
135                 self.frame_extents
136                     .top
137                     .saturating_add(self.frame_extents.bottom) as u32,
138             ),
139         )
140     }
141 }
142 
143 impl XConnection {
144     // This is adequate for inner_position
translate_coords( &self, window: ffi::Window, root: ffi::Window, ) -> Result<TranslatedCoords, XError>145     pub fn translate_coords(
146         &self,
147         window: ffi::Window,
148         root: ffi::Window,
149     ) -> Result<TranslatedCoords, XError> {
150         let mut coords = TranslatedCoords::default();
151 
152         unsafe {
153             (self.xlib.XTranslateCoordinates)(
154                 self.display,
155                 window,
156                 root,
157                 0,
158                 0,
159                 &mut coords.x_rel_root,
160                 &mut coords.y_rel_root,
161                 &mut coords.child,
162             );
163         }
164 
165         self.check_errors()?;
166         Ok(coords)
167     }
168 
169     // This is adequate for inner_size
get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError>170     pub fn get_geometry(&self, window: ffi::Window) -> Result<Geometry, XError> {
171         let mut geometry = Geometry::default();
172 
173         let _status = unsafe {
174             (self.xlib.XGetGeometry)(
175                 self.display,
176                 window,
177                 &mut geometry.root,
178                 &mut geometry.x_rel_parent,
179                 &mut geometry.y_rel_parent,
180                 &mut geometry.width,
181                 &mut geometry.height,
182                 &mut geometry.border,
183                 &mut geometry.depth,
184             )
185         };
186 
187         self.check_errors()?;
188         Ok(geometry)
189     }
190 
get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents>191     fn get_frame_extents(&self, window: ffi::Window) -> Option<FrameExtents> {
192         let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") };
193 
194         if !hint_is_supported(extents_atom) {
195             return None;
196         }
197 
198         // Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
199         // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
200         // be unsupported by many smaller WMs.
201         let extents: Option<Vec<c_ulong>> = self
202             .get_property(window, extents_atom, ffi::XA_CARDINAL)
203             .ok();
204 
205         extents.and_then(|extents| {
206             if extents.len() >= 4 {
207                 Some(FrameExtents {
208                     left: extents[0],
209                     right: extents[1],
210                     top: extents[2],
211                     bottom: extents[3],
212                 })
213             } else {
214                 None
215             }
216         })
217     }
218 
is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool>219     pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option<bool> {
220         let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") };
221 
222         if !hint_is_supported(client_list_atom) {
223             return None;
224         }
225 
226         let client_list: Option<Vec<ffi::Window>> = self
227             .get_property(root, client_list_atom, ffi::XA_WINDOW)
228             .ok();
229 
230         client_list.map(|client_list| client_list.contains(&window))
231     }
232 
get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError>233     fn get_parent_window(&self, window: ffi::Window) -> Result<ffi::Window, XError> {
234         let parent = unsafe {
235             let mut root = 0;
236             let mut parent = 0;
237             let mut children: *mut ffi::Window = ptr::null_mut();
238             let mut nchildren = 0;
239 
240             // What's filled into `parent` if `window` is the root window?
241             let _status = (self.xlib.XQueryTree)(
242                 self.display,
243                 window,
244                 &mut root,
245                 &mut parent,
246                 &mut children,
247                 &mut nchildren,
248             );
249 
250             // The list of children isn't used
251             if children != ptr::null_mut() {
252                 (self.xlib.XFree)(children as *mut _);
253             }
254 
255             parent
256         };
257         self.check_errors().map(|_| parent)
258     }
259 
climb_hierarchy( &self, window: ffi::Window, root: ffi::Window, ) -> Result<ffi::Window, XError>260     fn climb_hierarchy(
261         &self,
262         window: ffi::Window,
263         root: ffi::Window,
264     ) -> Result<ffi::Window, XError> {
265         let mut outer_window = window;
266         loop {
267             let candidate = self.get_parent_window(outer_window)?;
268             if candidate == root {
269                 break;
270             }
271             outer_window = candidate;
272         }
273         Ok(outer_window)
274     }
275 
get_frame_extents_heuristic( &self, window: ffi::Window, root: ffi::Window, ) -> FrameExtentsHeuristic276     pub fn get_frame_extents_heuristic(
277         &self,
278         window: ffi::Window,
279         root: ffi::Window,
280     ) -> FrameExtentsHeuristic {
281         use self::FrameExtentsHeuristicPath::*;
282 
283         // Position relative to root window.
284         // With rare exceptions, this is the position of a nested window. Cases where the window
285         // isn't nested are outlined in the comments throghout this function, but in addition to
286         // that, fullscreen windows often aren't nested.
287         let (inner_y_rel_root, child) = {
288             let coords = self
289                 .translate_coords(window, root)
290                 .expect("Failed to translate window coordinates");
291             (coords.y_rel_root, coords.child)
292         };
293 
294         let (width, height, border) = {
295             let inner_geometry = self
296                 .get_geometry(window)
297                 .expect("Failed to get inner window geometry");
298             (
299                 inner_geometry.width,
300                 inner_geometry.height,
301                 inner_geometry.border,
302             )
303         };
304 
305         // The first condition is only false for un-nested windows, but isn't always false for
306         // un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
307         // when y is on the range [0, 2] and if the window has been unfocused since being
308         // undecorated (or was undecorated upon construction), the first condition is true,
309         // requiring us to rely on the second condition.
310         let nested = !(window == child || self.is_top_level(child, root) == Some(true));
311 
312         // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
313         if let Some(mut frame_extents) = self.get_frame_extents(window) {
314             // Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
315             // decorations are disabled, but since the window becomes un-nested, it's easy to
316             // catch.
317             if !nested {
318                 frame_extents = FrameExtents::new(0, 0, 0, 0);
319             }
320 
321             // The difference between the nested window's position and the outermost window's
322             // position is equivalent to the frame size. In most scenarios, this is equivalent to
323             // manually climbing the hierarchy as is done in the case below. Here's a list of
324             // known discrepancies:
325             // * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
326             //   addition to a 1px semi-transparent border. The margin can be easily observed by
327             //   using a screenshot tool to get a screenshot of a selected window, and is
328             //   presumably used for drawing drop shadows. Getting window geometry information
329             //   via hierarchy-climbing results in this margin being included in both the
330             //   position and outer size, so a window positioned at (0, 0) would be reported as
331             //   having a position (-10, -8).
332             // * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
333             //   on all sides, and there's no additional border.
334             // * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
335             //   Without decorations, there's no difference. This is presumably related to
336             //   Enlightenment's fairly unique concept of window position; it interprets
337             //   positions given to XMoveWindow as a client area position rather than a position
338             //   of the overall window.
339 
340             FrameExtentsHeuristic {
341                 frame_extents,
342                 heuristic_path: Supported,
343             }
344         } else if nested {
345             // If the position value we have is for a nested window used as the client area, we'll
346             // just climb up the hierarchy and get the geometry of the outermost window we're
347             // nested in.
348             let outer_window = self
349                 .climb_hierarchy(window, root)
350                 .expect("Failed to climb window hierarchy");
351             let (outer_y, outer_width, outer_height) = {
352                 let outer_geometry = self
353                     .get_geometry(outer_window)
354                     .expect("Failed to get outer window geometry");
355                 (
356                     outer_geometry.y_rel_parent,
357                     outer_geometry.width,
358                     outer_geometry.height,
359                 )
360             };
361 
362             // Since we have the geometry of the outermost window and the geometry of the client
363             // area, we can figure out what's in between.
364             let diff_x = outer_width.saturating_sub(width);
365             let diff_y = outer_height.saturating_sub(height);
366             let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
367 
368             let left = diff_x / 2;
369             let right = left;
370             let top = offset_y;
371             let bottom = diff_y.saturating_sub(offset_y);
372 
373             let frame_extents =
374                 FrameExtents::new(left.into(), right.into(), top.into(), bottom.into());
375             FrameExtentsHeuristic {
376                 frame_extents,
377                 heuristic_path: UnsupportedNested,
378             }
379         } else {
380             // This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
381             // border value. This is convenient, since we can use it to get an accurate frame.
382             let frame_extents = FrameExtents::from_border(border.into());
383             FrameExtentsHeuristic {
384                 frame_extents,
385                 heuristic_path: UnsupportedBordered,
386             }
387         }
388     }
389 }
390