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