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