Skip to main content

leftwm_core/
state.rs

1//! Save and restore manager state.
2
3use crate::DisplayAction;
4use crate::child_process::ChildID;
5use crate::config::{Config, InsertBehavior, ScratchPad};
6use crate::layouts::LayoutManager;
7use crate::models::{
8    FocusManager, Handle, Mode, ResizeCorner, ScratchPadName, Screen, Tags, Window, WindowHandle,
9    WindowState, WindowType, Workspace,
10};
11use leftwm_layouts::Layout;
12use serde::{Deserialize, Serialize};
13use std::collections::{HashMap, VecDeque};
14
15#[derive(Serialize, Deserialize, Debug)]
16pub struct State<H: Handle> {
17    #[serde(bound = "")]
18    pub screens: Vec<Screen<H>>,
19    #[serde(bound = "")]
20    pub windows: Vec<Window<H>>,
21    pub workspaces: Vec<Workspace>,
22    #[serde(bound = "")]
23    pub focus_manager: FocusManager<H>,
24    pub layout_manager: LayoutManager,
25    #[serde(bound = "")]
26    pub mode: Mode<H>,
27    /// The corner the resize in progress is anchored to, set when the drag
28    /// starts from whichever corner the pointer was closest to.
29    #[serde(default)]
30    pub resize_corner: ResizeCorner,
31    pub active_scratchpads: HashMap<ScratchPadName, VecDeque<ChildID>>,
32    #[serde(bound = "")]
33    pub actions: VecDeque<DisplayAction<H>>,
34    pub tags: Tags, // List of all known tags.
35    // entries below are loaded from config and are never changed
36    pub scratchpads: Vec<ScratchPad>,
37    pub layout_definitions: Vec<Layout>,
38    pub mousekey: Vec<String>,
39    pub default_width: i32,
40    pub default_height: i32,
41    pub disable_tile_drag: bool,
42    pub reposition_cursor_on_resize: bool,
43    pub insert_behavior: InsertBehavior,
44    pub single_window_border: bool,
45}
46
47// This clippy is mainly for readability, but changing it triggers `error[E0277]` '... is not an iterator'
48// and solving this feels like a lot just for a little bit of readability
49#[allow(clippy::explicit_iter_loop)]
50impl<H: Handle> State<H> {
51    pub(crate) fn new(config: &impl Config) -> Self {
52        let mut tags = Tags::new();
53        config.create_list_of_tag_labels().iter().for_each(|label| {
54            tags.add_new(label.as_str());
55        });
56        tags.add_new_hidden("NSP");
57
58        Self {
59            focus_manager: FocusManager::new(config),
60            layout_manager: LayoutManager::new(config),
61            screens: Default::default(),
62            windows: Default::default(),
63            workspaces: Default::default(),
64            mode: Default::default(),
65            resize_corner: Default::default(),
66            active_scratchpads: Default::default(),
67            actions: Default::default(),
68            tags,
69            scratchpads: config.create_list_of_scratchpads(),
70            layout_definitions: config.layout_definitions(),
71            mousekey: config.mousekey(),
72            default_width: config.default_width(),
73            default_height: config.default_height(),
74            disable_tile_drag: config.disable_tile_drag(),
75            reposition_cursor_on_resize: config.reposition_cursor_on_resize(),
76            insert_behavior: config.insert_behavior(),
77            single_window_border: config.single_window_border(),
78        }
79    }
80
81    /// Sorts the windows and puts them in order of importance.
82    pub fn sort_windows(&mut self) {
83        let mut sorter = WindowSorter::new(self.windows.iter().collect());
84
85        // Windows explicitly marked as on top
86        sorter.sort(|w| w.states.contains(&WindowState::Above) && w.floating());
87
88        // Transient windows should be above a fullscreen/maximized parent
89        sorter.sort(|w| {
90            w.transient.is_some_and(|trans| {
91                self.windows
92                    .iter()
93                    .any(|w| w.handle == trans && (w.is_fullscreen() || w.is_maximized()))
94            })
95        });
96
97        // Dialogs and modals.
98        sorter.sort(|w| w.r#type.is_dialog_like());
99
100        // Fullscreen windows
101        sorter.sort(Window::is_fullscreen);
102
103        // Floating windows.
104        sorter.sort(|w| w.r#type == WindowType::Normal && w.floating());
105
106        // Maximized windows.
107        sorter.sort(|w| w.r#type == WindowType::Normal && w.is_maximized());
108
109        // Tiled windows.
110        sorter.sort(|w| w.r#type == WindowType::Normal);
111
112        // Last docks.
113        sorter.sort(|w| w.r#type == WindowType::Dock);
114
115        // Finish and put all unsorted at the end.
116        let windows = sorter.finish();
117        let handles = windows.iter().map(|w| w.handle).collect();
118
119        // SetWindowOrder is passed to the display server
120        let act = DisplayAction::SetWindowOrder(handles);
121        self.actions.push_back(act);
122    }
123
124    /// Removes border if there is a single visible window.
125    /// Only will run if `single_window_border` is set to `false` in the configuration file.
126    pub fn handle_single_border(&mut self, border_width: i32) {
127        if self.single_window_border {
128            return;
129        }
130
131        for tag in self.tags.normal() {
132            let mut windows_on_tag: Vec<&mut Window<H>> = self
133                .windows
134                .iter_mut()
135                .filter(|w| w.tag.unwrap_or(0) == tag.id && w.r#type == WindowType::Normal)
136                .collect();
137
138            let wsid = self
139                .workspaces
140                .iter()
141                .find(|ws| ws.has_tag(&tag.id))
142                .map(|w| w.id);
143            let layout = self.layout_manager.layout(wsid.unwrap_or(1), tag.id);
144
145            // TODO: hardcoded layout name.
146            if layout.is_monocle() {
147                for w in &mut windows_on_tag.iter_mut() {
148                    w.border = 0;
149                }
150                continue;
151            }
152
153            if windows_on_tag.len() == 1 {
154                if let Some(w) = windows_on_tag.first_mut() {
155                    w.border = 0;
156                }
157                continue;
158            }
159
160            for w in &mut windows_on_tag.iter_mut() {
161                w.border = border_width;
162            }
163        }
164    }
165
166    /// Moves `handle` in front of all other windows of the same order of importance.
167    /// See `sort_windows()` for the order of importance.
168    pub fn move_to_top(&mut self, handle: &WindowHandle<H>) -> Option<()> {
169        let index = self.windows.iter().position(|w| &w.handle == handle)?;
170        let window = self.windows.remove(index);
171        self.windows.insert(0, window);
172        self.sort_windows();
173        Some(())
174    }
175
176    pub fn update_static(&mut self) {
177        self.windows
178            .iter_mut()
179            .filter(|w| w.strut.is_some() || w.is_sticky())
180            .for_each(|w| {
181                let (x, y) = match w.strut {
182                    Some(strut) => strut.center(),
183                    None => w.calculated_xyhw().center(),
184                };
185                if let Some(ws) = self.workspaces.iter().find(|ws| ws.contains_point(x, y)) {
186                    w.tag = ws.tag;
187                }
188            });
189    }
190
191    pub(crate) fn load_theme_config(&mut self, config: &impl Config) {
192        for win in &mut self.windows {
193            config.load_window(win);
194        }
195        for ws in &mut self.workspaces {
196            ws.load_config(config);
197        }
198        self.default_height = config.default_height();
199        self.default_width = config.default_width();
200    }
201
202    /// Apply saved state to a running manager.
203    pub fn restore_state(&mut self, old_state: &Self) {
204        tracing::debug!("Restoring old state");
205
206        // Restore tags.
207        for old_tag in old_state.tags.all() {
208            if let Some(tag) = self.tags.get_mut(old_tag.id) {
209                tag.hidden = old_tag.hidden;
210            }
211        }
212
213        let are_tags_equal = self.tags.all().eq(&old_state.tags.all());
214
215        // Restore windows.
216        let mut ordered = vec![];
217        let mut had_strut = false;
218        old_state.windows.iter().for_each(|old_window| {
219            if let Some((index, new_window)) = self
220                .windows
221                .clone()
222                .iter_mut()
223                .enumerate()
224                .find(|w| w.1.handle == old_window.handle)
225            {
226                had_strut = old_window.strut.is_some() || had_strut;
227
228                new_window.set_floating(old_window.floating());
229                new_window.set_floating_offsets(old_window.get_floating_offsets());
230                new_window.apply_margin_multiplier(old_window.margin_multiplier);
231                new_window.pid = old_window.pid;
232                new_window.normal = old_window.normal;
233                if are_tags_equal {
234                    new_window.tag = old_window.tag;
235                } else {
236                    let mut new_tag = old_window.tag;
237                    // Only retain the tag if it still exists, otherwise default to tag 1
238                    match new_tag {
239                        Some(tag) if self.tags.get(tag).is_some() => {}
240                        _ => new_tag = Some(1),
241                    }
242                    new_window.untag();
243                    for &tag_id in new_tag.iter() {
244                        new_window.tag(&tag_id);
245                    }
246                }
247                new_window.strut = old_window.strut;
248                new_window.states.clone_from(&old_window.states);
249                ordered.push(new_window.clone());
250                self.windows.remove(index);
251
252                // Make the x server aware of any tag changes for the window.
253                let act = DisplayAction::SetWindowTag(new_window.handle, new_window.tag);
254                self.actions.push_back(act);
255            }
256        });
257        if had_strut {
258            self.update_static();
259        }
260        self.windows.append(&mut ordered);
261
262        // This is needed due to mutable/immutable borrows.
263        let all_tags = &self.tags;
264
265        // Restore workspaces.
266        for workspace in &mut self.workspaces {
267            if let Some(old_workspace) = old_state.workspaces.iter().find(|w| w.id == workspace.id)
268            {
269                workspace.margin_multiplier = old_workspace.margin_multiplier;
270                if are_tags_equal {
271                    workspace.tag = old_workspace.tag;
272                } else {
273                    let mut new_tag = old_workspace.tag;
274                    // Only retain the tag if it still exists, otherwise default to tag 1
275                    match new_tag {
276                        Some(tag) if all_tags.get(tag).is_some() => {}
277                        _ => new_tag = Some(1),
278                    }
279                    for &tag_id in new_tag.iter() {
280                        workspace.tag = Some(tag_id);
281                    }
282                }
283            }
284        }
285
286        // Restore scratchpads.
287        for (scratchpad, id) in &old_state.active_scratchpads {
288            self.active_scratchpads
289                .insert(scratchpad.clone(), id.clone());
290        }
291
292        // Restore focus.
293        self.focus_manager
294            .tags_last_window
295            .clone_from(&old_state.focus_manager.tags_last_window);
296        self.focus_manager
297            .tags_last_window
298            .retain(|&id, _| all_tags.get(id).is_some());
299        let tag_id = match old_state.focus_manager.tag(0) {
300            // If the tag still exists it should be displayed on a workspace.
301            Some(tag_id) if self.tags.get(tag_id).is_some() => tag_id,
302            // If the tag doesn't exist, tag 1 should be displayed on a workspace.
303            Some(_) => 1,
304            // If we don't have any tag history (We should), focus the tag on workspace 1.
305            None => match self.workspaces.first() {
306                Some(ws) => ws.tag.unwrap_or(1),
307                // This should never happen.
308                _ => 1,
309            },
310        };
311        self.focus_tag(&tag_id);
312
313        // Restore layout manager
314        self.layout_manager.restore(&old_state.layout_manager);
315    }
316}
317
318/// Helper struct for sorting windows.
319/// Sorts windows in `unsorted` via their order of importance
320/// and pushes sorted list onto `stack`.
321struct WindowSorter<'a, H: Handle> {
322    stack: Vec<&'a Window<H>>,
323    unsorted: Vec<&'a Window<H>>,
324}
325
326impl<'a, H: Handle> WindowSorter<'a, H> {
327    pub fn new(windows: Vec<&'a Window<H>>) -> Self {
328        Self {
329            stack: Vec::with_capacity(windows.len()),
330            unsorted: windows,
331        }
332    }
333
334    pub fn sort<F: Fn(&Window<H>) -> bool>(&mut self, filter: F) {
335        self.unsorted.retain(|window| {
336            if filter(window) {
337                self.stack.push(window);
338                false
339            } else {
340                true
341            }
342        });
343    }
344
345    pub fn finish(mut self) -> Vec<&'a Window<H>> {
346        self.stack.append(&mut self.unsorted);
347        self.stack
348    }
349}