1 //! Window Information 2 #![allow(clippy::module_name_repetitions)] 3 use super::WindowState; 4 use super::WindowType; 5 use crate::config::Config; 6 use crate::models::Margins; 7 use crate::models::TagId; 8 use crate::models::Xyhw; 9 use crate::models::XyhwBuilder; 10 use serde::{Deserialize, Serialize}; 11 use x11_dl::xlib; 12 13 type MockHandle = i32; 14 15 #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] 16 pub enum WindowHandle { 17 MockHandle(MockHandle), 18 XlibHandle(xlib::Window), 19 } 20 21 /// Store Window information. 22 // We allow this as we're not managing state directly. This could be refactored in the future. 23 // TODO: Refactor floating 24 #[allow(clippy::struct_excessive_bools)] 25 #[derive(Serialize, Deserialize, Debug, Clone)] 26 pub struct Window { 27 pub handle: WindowHandle, 28 pub transient: Option<WindowHandle>, 29 visible: bool, 30 pub can_resize: bool, 31 is_floating: bool, 32 must_float: bool, 33 floating: Option<Xyhw>, 34 pub never_focus: bool, 35 pub debugging: bool, 36 pub name: Option<String>, 37 pub pid: Option<u32>, 38 pub r#type: WindowType, 39 pub tags: Vec<TagId>, 40 pub border: i32, 41 pub margin: Margins, 42 pub margin_multiplier: f32, 43 states: Vec<WindowState>, 44 pub requested: Option<Xyhw>, 45 pub normal: Xyhw, 46 pub start_loc: Option<Xyhw>, 47 pub container_size: Option<Xyhw>, 48 pub strut: Option<Xyhw>, 49 } 50 51 impl Window { 52 #[must_use] new(h: WindowHandle, name: Option<String>, pid: Option<u32>) -> Self53 pub fn new(h: WindowHandle, name: Option<String>, pid: Option<u32>) -> Self { 54 Self { 55 handle: h, 56 transient: None, 57 visible: false, 58 can_resize: true, 59 is_floating: false, 60 must_float: false, 61 debugging: false, 62 never_focus: false, 63 name, 64 pid, 65 r#type: WindowType::Normal, 66 tags: Vec::new(), 67 border: 1, 68 margin: Margins::new(10), 69 margin_multiplier: 1.0, 70 states: vec![], 71 normal: XyhwBuilder::default().into(), 72 requested: None, 73 floating: None, 74 start_loc: None, 75 container_size: None, 76 strut: None, 77 } 78 } 79 load_config(&mut self, config: &impl Config)80 pub(crate) fn load_config(&mut self, config: &impl Config) { 81 if self.r#type == WindowType::Normal { 82 self.margin = config.margin(); 83 self.border = config.border_width(); 84 self.must_float = config.always_float(); 85 } else { 86 self.margin = Margins::new(0); 87 self.border = 0; 88 } 89 } 90 set_visible(&mut self, value: bool)91 pub fn set_visible(&mut self, value: bool) { 92 self.visible = value; 93 } 94 95 #[must_use] visible(&self) -> bool96 pub fn visible(&self) -> bool { 97 self.visible 98 || self.r#type == WindowType::Menu 99 || self.r#type == WindowType::Splash 100 || self.r#type == WindowType::Dialog 101 || self.r#type == WindowType::Toolbar 102 } 103 set_floating(&mut self, value: bool)104 pub fn set_floating(&mut self, value: bool) { 105 if !self.is_floating && value && self.floating.is_none() { 106 //NOTE: We float relative to the normal position. 107 self.reset_float_offset(); 108 } 109 self.is_floating = value; 110 } 111 112 #[must_use] floating(&self) -> bool113 pub fn floating(&self) -> bool { 114 self.is_floating || self.must_float() 115 } 116 117 #[must_use] get_floating_offsets(&self) -> Option<Xyhw>118 pub const fn get_floating_offsets(&self) -> Option<Xyhw> { 119 self.floating 120 } 121 reset_float_offset(&mut self)122 pub fn reset_float_offset(&mut self) { 123 let mut new_value = Xyhw::default(); 124 new_value.clear_minmax(); 125 self.floating = Some(new_value); 126 } 127 set_floating_offsets(&mut self, value: Option<Xyhw>)128 pub fn set_floating_offsets(&mut self, value: Option<Xyhw>) { 129 self.floating = value; 130 if let Some(value) = &mut self.floating { 131 value.clear_minmax(); 132 } 133 } 134 set_floating_exact(&mut self, value: Xyhw)135 pub fn set_floating_exact(&mut self, value: Xyhw) { 136 let mut new_value = value - self.normal; 137 new_value.clear_minmax(); 138 self.floating = Some(new_value); 139 } 140 141 #[must_use] is_fullscreen(&self) -> bool142 pub fn is_fullscreen(&self) -> bool { 143 self.states.contains(&WindowState::Fullscreen) 144 } 145 #[must_use] is_sticky(&self) -> bool146 pub fn is_sticky(&self) -> bool { 147 self.states.contains(&WindowState::Sticky) 148 } 149 #[must_use] must_float(&self) -> bool150 pub fn must_float(&self) -> bool { 151 self.must_float 152 || self.transient.is_some() 153 || self.is_unmanaged() 154 || self.r#type == WindowType::Splash 155 } 156 #[must_use] can_move(&self) -> bool157 pub fn can_move(&self) -> bool { 158 !self.is_unmanaged() 159 } 160 #[must_use] can_resize(&self) -> bool161 pub fn can_resize(&self) -> bool { 162 self.can_resize && !self.is_unmanaged() 163 } 164 165 #[must_use] can_focus(&self) -> bool166 pub fn can_focus(&self) -> bool { 167 !self.never_focus && !self.is_unmanaged() && self.visible() 168 } 169 set_width(&mut self, width: i32)170 pub fn set_width(&mut self, width: i32) { 171 self.normal.set_w(width); 172 } 173 set_height(&mut self, height: i32)174 pub fn set_height(&mut self, height: i32) { 175 self.normal.set_h(height); 176 } 177 set_states(&mut self, states: Vec<WindowState>)178 pub fn set_states(&mut self, states: Vec<WindowState>) { 179 self.states = states; 180 } 181 182 #[must_use] has_state(&self, state: &WindowState) -> bool183 pub fn has_state(&self, state: &WindowState) -> bool { 184 self.states.contains(state) 185 } 186 187 #[must_use] states(&self) -> Vec<WindowState>188 pub fn states(&self) -> Vec<WindowState> { 189 self.states.clone() 190 } 191 apply_margin_multiplier(&mut self, value: f32)192 pub fn apply_margin_multiplier(&mut self, value: f32) { 193 self.margin_multiplier = value.abs(); 194 if value < 0 as f32 { 195 log::warn!( 196 "Negative margin multiplier detected. Will be applied as absolute: {:?}", 197 self.margin_multiplier() 198 ); 199 }; 200 } 201 202 #[must_use] margin_multiplier(&self) -> f32203 pub const fn margin_multiplier(&self) -> f32 { 204 self.margin_multiplier 205 } 206 207 #[must_use] width(&self) -> i32208 pub fn width(&self) -> i32 { 209 let mut value; 210 if self.is_fullscreen() { 211 value = self.normal.w(); 212 } else if self.floating() && self.floating.is_some() { 213 let relative = self.normal + self.floating.unwrap_or_default(); 214 value = relative.w() - (self.border * 2); 215 } else { 216 value = self.normal.w() 217 - (((self.margin.left + self.margin.right) as f32) * self.margin_multiplier) as i32 218 - (self.border * 2); 219 } 220 let limit = match self.requested { 221 Some(requested) if requested.minw() > 0 && self.floating() => requested.minw(), 222 _ => 100, 223 }; 224 if value < limit && !self.is_unmanaged() { 225 value = limit; 226 } 227 value 228 } 229 230 #[must_use] height(&self) -> i32231 pub fn height(&self) -> i32 { 232 let mut value; 233 if self.is_fullscreen() { 234 value = self.normal.h(); 235 } else if self.floating() && self.floating.is_some() { 236 let relative = self.normal + self.floating.unwrap_or_default(); 237 value = relative.h() - (self.border * 2); 238 } else { 239 value = self.normal.h() 240 - (((self.margin.top + self.margin.bottom) as f32) * self.margin_multiplier) as i32 241 - (self.border * 2); 242 } 243 let limit = match self.requested { 244 Some(requested) if requested.minh() > 0 && self.floating() => requested.minh(), 245 _ => 100, 246 }; 247 if value < limit && !self.is_unmanaged() { 248 value = limit; 249 } 250 value 251 } 252 set_x(&mut self, x: i32)253 pub fn set_x(&mut self, x: i32) { 254 self.normal.set_x(x); 255 } set_y(&mut self, y: i32)256 pub fn set_y(&mut self, y: i32) { 257 self.normal.set_y(y); 258 } 259 260 #[must_use] border(&self) -> i32261 pub fn border(&self) -> i32 { 262 if self.is_fullscreen() { 263 0 264 } else { 265 self.border 266 } 267 } 268 269 #[must_use] x(&self) -> i32270 pub fn x(&self) -> i32 { 271 if self.is_fullscreen() { 272 self.normal.x() 273 } else if self.floating() && self.floating.is_some() { 274 let relative = self.normal + self.floating.unwrap_or_default(); 275 relative.x() 276 } else { 277 self.normal.x() + (self.margin.left as f32 * self.margin_multiplier) as i32 278 } 279 } 280 281 #[must_use] y(&self) -> i32282 pub fn y(&self) -> i32 { 283 if self.is_fullscreen() { 284 self.normal.y() 285 } else if self.floating() && self.floating.is_some() { 286 let relative = self.normal + self.floating.unwrap_or_default(); 287 relative.y() 288 } else { 289 self.normal.y() + (self.margin.top as f32 * self.margin_multiplier) as i32 290 } 291 } 292 293 #[must_use] calculated_xyhw(&self) -> Xyhw294 pub fn calculated_xyhw(&self) -> Xyhw { 295 XyhwBuilder { 296 h: self.height(), 297 w: self.width(), 298 x: self.x(), 299 y: self.y(), 300 ..XyhwBuilder::default() 301 } 302 .into() 303 } 304 305 #[must_use] exact_xyhw(&self) -> Xyhw306 pub fn exact_xyhw(&self) -> Xyhw { 307 if self.floating() && self.floating.is_some() { 308 self.normal + self.floating.unwrap_or_default() 309 } else { 310 self.normal 311 } 312 } 313 314 #[must_use] contains_point(&self, x: i32, y: i32) -> bool315 pub fn contains_point(&self, x: i32, y: i32) -> bool { 316 self.calculated_xyhw().contains_point(x, y) 317 } 318 tag(&mut self, tag: &TagId)319 pub fn tag(&mut self, tag: &TagId) { 320 if !self.tags.contains(tag) { 321 self.tags.push(*tag); 322 } 323 } 324 clear_tags(&mut self)325 pub fn clear_tags(&mut self) { 326 self.tags = vec![]; 327 } 328 329 #[must_use] has_tag(&self, tag: &TagId) -> bool330 pub fn has_tag(&self, tag: &TagId) -> bool { 331 self.tags.contains(tag) 332 } 333 untag(&mut self, tag: &TagId)334 pub fn untag(&mut self, tag: &TagId) { 335 self.tags.retain(|t| t != tag); 336 } 337 338 #[must_use] is_unmanaged(&self) -> bool339 pub fn is_unmanaged(&self) -> bool { 340 self.r#type == WindowType::Desktop || self.r#type == WindowType::Dock 341 } 342 } 343 344 #[cfg(test)] 345 mod tests { 346 use super::*; 347 348 #[test] should_be_able_to_tag_a_window()349 fn should_be_able_to_tag_a_window() { 350 let mut subject = Window::new(WindowHandle::MockHandle(1), None, None); 351 subject.tag(&1); 352 assert!(subject.has_tag(&1), "was unable to tag the window"); 353 } 354 355 #[test] should_be_able_to_untag_a_window()356 fn should_be_able_to_untag_a_window() { 357 let mut subject = Window::new(WindowHandle::MockHandle(1), None, None); 358 subject.tag(&1); 359 subject.untag(&1); 360 assert!(!subject.has_tag(&1), "was unable to untag the window"); 361 } 362 } 363