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: i32, y: i32): (i32, i32), (width: u32, height: u32): (u32, u32)) -> Self {
16 let (x: i64, y: i64) = (x as i64, y as i64);
17 let (width: i64, height: i64) = (width as i64, height as i64);
18 AaRect { x, y, width, height }
19 }
20
21 pub fn contains_point(&self, x: i64, y: i64) -> bool {
22 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
23 }
24
25 pub fn get_overlapping_area(&self, other: &Self) -> i64 {
26 let x_overlap: i64 = cmp::max(
27 v1:0,
28 v2:cmp::min(self.x + self.width, v2:other.x + other.width) - cmp::max(self.x, v2:other.x),
29 );
30 let y_overlap: i64 = cmp::max(
31 v1:0,
32 v2:cmp::min(self.y + self.height, v2:other.y + other.height) - cmp::max(self.y, v2:other.y),
33 );
34 x_overlap * y_overlap
35 }
36}
37
38#[derive(Debug, Clone)]
39pub struct FrameExtents {
40 pub left: u32,
41 pub right: u32,
42 pub top: u32,
43 pub bottom: u32,
44}
45
46impl FrameExtents {
47 pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self {
48 FrameExtents { left, right, top, bottom }
49 }
50
51 pub fn from_border(border: u32) -> Self {
52 Self::new(left:border, right:border, top:border, bottom:border)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum FrameExtentsHeuristicPath {
58 Supported,
59 UnsupportedNested,
60 UnsupportedBordered,
61}
62
63#[derive(Debug, Clone)]
64pub struct FrameExtentsHeuristic {
65 pub frame_extents: FrameExtents,
66 pub heuristic_path: FrameExtentsHeuristicPath,
67}
68
69impl FrameExtentsHeuristic {
70 pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
71 use self::FrameExtentsHeuristicPath::*;
72 if self.heuristic_path != UnsupportedBordered {
73 (x - self.frame_extents.left as i32, y - self.frame_extents.top as i32)
74 } else {
75 (x, y)
76 }
77 }
78
79 pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
80 (
81 width.saturating_add(
82 self.frame_extents.left.saturating_add(self.frame_extents.right) as _
83 ),
84 height.saturating_add(
85 self.frame_extents.top.saturating_add(self.frame_extents.bottom) as _
86 ),
87 )
88 }
89}
90
91impl XConnection {
92 // This is adequate for inner_position
93 pub fn translate_coords(
94 &self,
95 window: xproto::Window,
96 root: xproto::Window,
97 ) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
98 self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
99 }
100
101 // This is adequate for inner_size
102 pub fn get_geometry(
103 &self,
104 window: xproto::Window,
105 ) -> Result<xproto::GetGeometryReply, X11Error> {
106 self.xcb_connection().get_geometry(window)?.reply().map_err(Into::into)
107 }
108
109 fn get_frame_extents(&self, window: xproto::Window) -> Option<FrameExtents> {
110 let atoms = self.atoms();
111 let extents_atom = atoms[_NET_FRAME_EXTENTS];
112
113 if !hint_is_supported(extents_atom) {
114 return None;
115 }
116
117 // Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
118 // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
119 // be unsupported by many smaller WMs.
120 let extents: Option<Vec<u32>> = self
121 .get_property(window, extents_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL))
122 .ok();
123
124 extents.and_then(|extents| {
125 if extents.len() >= 4 {
126 Some(FrameExtents {
127 left: extents[0],
128 right: extents[1],
129 top: extents[2],
130 bottom: extents[3],
131 })
132 } else {
133 None
134 }
135 })
136 }
137
138 pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option<bool> {
139 let atoms = self.atoms();
140 let client_list_atom = atoms[_NET_CLIENT_LIST];
141
142 if !hint_is_supported(client_list_atom) {
143 return None;
144 }
145
146 let client_list: Option<Vec<xproto::Window>> = self
147 .get_property(root, client_list_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW))
148 .ok();
149
150 client_list.map(|client_list| client_list.contains(&(window as xproto::Window)))
151 }
152
153 fn get_parent_window(&self, window: xproto::Window) -> Result<xproto::Window, X11Error> {
154 let parent = self.xcb_connection().query_tree(window)?.reply()?.parent;
155 Ok(parent)
156 }
157
158 fn climb_hierarchy(
159 &self,
160 window: xproto::Window,
161 root: xproto::Window,
162 ) -> Result<xproto::Window, X11Error> {
163 let mut outer_window = window;
164 loop {
165 let candidate = self.get_parent_window(outer_window)?;
166 if candidate == root {
167 break;
168 }
169 outer_window = candidate;
170 }
171 Ok(outer_window)
172 }
173
174 pub fn get_frame_extents_heuristic(
175 &self,
176 window: xproto::Window,
177 root: xproto::Window,
178 ) -> FrameExtentsHeuristic {
179 use self::FrameExtentsHeuristicPath::*;
180
181 // Position relative to root window.
182 // With rare exceptions, this is the position of a nested window. Cases where the window
183 // isn't nested are outlined in the comments throughout this function, but in addition to
184 // that, fullscreen windows often aren't nested.
185 let (inner_y_rel_root, child) = {
186 let coords = self
187 .translate_coords(window, root)
188 .expect("Failed to translate window coordinates");
189 (coords.dst_y, coords.child)
190 };
191
192 let (width, height, border) = {
193 let inner_geometry =
194 self.get_geometry(window).expect("Failed to get inner window geometry");
195 (inner_geometry.width, inner_geometry.height, inner_geometry.border_width)
196 };
197
198 // The first condition is only false for un-nested windows, but isn't always false for
199 // un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
200 // when y is on the range [0, 2] and if the window has been unfocused since being
201 // undecorated (or was undecorated upon construction), the first condition is true,
202 // requiring us to rely on the second condition.
203 let nested = !(window == child || self.is_top_level(child, root) == Some(true));
204
205 // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
206 if let Some(mut frame_extents) = self.get_frame_extents(window) {
207 // Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
208 // decorations are disabled, but since the window becomes un-nested, it's easy to
209 // catch.
210 if !nested {
211 frame_extents = FrameExtents::new(0, 0, 0, 0);
212 }
213
214 // The difference between the nested window's position and the outermost window's
215 // position is equivalent to the frame size. In most scenarios, this is equivalent to
216 // manually climbing the hierarchy as is done in the case below. Here's a list of
217 // known discrepancies:
218 // * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
219 // addition to a 1px semi-transparent border. The margin can be easily observed by
220 // using a screenshot tool to get a screenshot of a selected window, and is presumably
221 // used for drawing drop shadows. Getting window geometry information via
222 // hierarchy-climbing results in this margin being included in both the position and
223 // outer size, so a window positioned at (0, 0) would be reported as having a position
224 // (-10, -8).
225 // * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px on
226 // all sides, and there's no additional border.
227 // * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root. Without
228 // decorations, there's no difference. This is presumably related to Enlightenment's
229 // fairly unique concept of window position; it interprets positions given to
230 // XMoveWindow as a client area position rather than a position of the overall window.
231
232 FrameExtentsHeuristic { frame_extents, heuristic_path: Supported }
233 } else if nested {
234 // If the position value we have is for a nested window used as the client area, we'll
235 // just climb up the hierarchy and get the geometry of the outermost window we're
236 // nested in.
237 let outer_window =
238 self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
239 let (outer_y, outer_width, outer_height) = {
240 let outer_geometry =
241 self.get_geometry(outer_window).expect("Failed to get outer window geometry");
242 (outer_geometry.y, outer_geometry.width, outer_geometry.height)
243 };
244
245 // Since we have the geometry of the outermost window and the geometry of the client
246 // area, we can figure out what's in between.
247 let diff_x = outer_width.saturating_sub(width) as u32;
248 let diff_y = outer_height.saturating_sub(height) as u32;
249 let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32;
250
251 let left = diff_x / 2;
252 let right = left;
253 let top = offset_y;
254 let bottom = diff_y.saturating_sub(offset_y);
255
256 let frame_extents = FrameExtents::new(left, right, top, bottom);
257 FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedNested }
258 } else {
259 // This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
260 // border value. This is convenient, since we can use it to get an accurate frame.
261 let frame_extents = FrameExtents::from_border(border.into());
262 FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedBordered }
263 }
264 }
265}
266