| 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 { |
| 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)] |
| 39 | pub struct FrameExtents { |
| 40 | pub left: u32, |
| 41 | pub right: u32, |
| 42 | pub top: u32, |
| 43 | pub bottom: u32, |
| 44 | } |
| 45 | |
| 46 | impl 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)] |
| 57 | pub enum FrameExtentsHeuristicPath { |
| 58 | Supported, |
| 59 | UnsupportedNested, |
| 60 | UnsupportedBordered, |
| 61 | } |
| 62 | |
| 63 | #[derive (Debug, Clone)] |
| 64 | pub struct FrameExtentsHeuristic { |
| 65 | pub frame_extents: FrameExtents, |
| 66 | pub heuristic_path: FrameExtentsHeuristicPath, |
| 67 | } |
| 68 | |
| 69 | impl 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 | |
| 91 | impl 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 | |