leftwm_core/
state.rs

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