Skip to main content

leftwm_core/handlers/
window_handler.rs

1use super::{Manager, Window, WindowChange, WindowType, Workspace};
2use crate::child_process::exec_shell;
3use crate::config::{Config, InsertBehavior};
4use crate::display_action::DisplayAction;
5use crate::display_servers::DisplayServer;
6use crate::layouts::{self, MAIN_AND_VERT_STACK};
7use crate::models::{Handle, WindowHandle, WindowState, Xyhw};
8use crate::state::State;
9use crate::utils::helpers;
10use std::env;
11use std::str::FromStr;
12
13impl<H: Handle, C: Config, SERVER: DisplayServer<H>> Manager<H, C, SERVER> {
14    /// `window_created_handler` is called when the display server sends
15    /// the event `DisplayEvent::WindowCreate(w, x, y)`.
16    ///
17    /// Returns true if changes need to be rendered.
18    pub fn window_created_handler(&mut self, mut window: Window<H>, x: i32, y: i32) -> bool {
19        // Don't add the window if the manager already knows about it.
20        if self.state.windows.iter().any(|w| w.handle == window.handle) {
21            return false;
22        }
23
24        // Setup any predefined hooks.
25        self.config
26            .setup_predefined_window(&mut self.state, &mut window);
27
28        // TODO: this seems very janky.
29        let mut is_first = false;
30        let mut on_same_tag = true;
31        // Random value
32        let mut layout = MAIN_AND_VERT_STACK.to_string();
33        let respect_dialog_position = self.config.respect_dialog_position();
34        setup_window(
35            &mut self.state,
36            &mut window,
37            (x, y),
38            respect_dialog_position,
39            &mut layout,
40            &mut is_first,
41            &mut on_same_tag,
42        );
43        self.config.load_window(&mut window);
44        insert_window(&mut self.state, &mut window, &layout);
45
46        let follow_mouse = self.state.focus_manager.focus_new_windows
47            && self.state.focus_manager.behaviour.is_sloppy()
48            && self.state.focus_manager.sloppy_mouse_follows_focus
49            && window.is_managed()
50            && on_same_tag
51            && window.allows_mouse_warp();
52
53        // Let the DS know we are managing this window.
54        let act = DisplayAction::AddedWindow(window.handle, window.floating(), follow_mouse);
55        self.state.actions.push_back(act);
56
57        // Let the DS know the correct desktop to find this window.
58        if window.tag.is_some() {
59            let act = DisplayAction::SetWindowTag(window.handle, window.tag);
60            self.state.actions.push_back(act);
61        }
62
63        // Tell the WM to reevaluate the stacking order, so the new window is put in the correct layer
64        self.state.sort_windows();
65
66        // if `single_window_border` is `false`, remove borders if there is a single visible window
67        self.state.handle_single_border(self.config.border_width());
68
69        // `is_first` and `on_same_tag` are set by `setup_window`
70        // TODO: remove focus_new_windows variable from focus_manager,
71        // TODO: use self.config.focus_new_windows() instead
72        if (self.state.focus_manager.focus_new_windows || is_first) && on_same_tag {
73            self.state.focus_window(&window.handle);
74        }
75
76        // run the `on_new_window_cmd` set in `config.ron`
77        if let Some(cmd) = &self.config.on_new_window_cmd() {
78            exec_shell(cmd, &mut self.children);
79        }
80
81        true
82    }
83
84    /// `window_destroyed_handler` is called when the display server sends
85    /// the `DisplayEvent::WindowDestroy(handle)` event.
86    ///
87    /// Returns true if changes need to be rendered.
88    pub fn window_destroyed_handler(&mut self, handle: &WindowHandle<H>) -> bool {
89        // Get the previous focused window else find the next or previous window on the workspace.
90        let new_handle = if let Some(Some(last_focused_window)) = self
91            .state
92            .focus_manager
93            .window_history
94            .iter()
95            // Take the first window that is not the destroyed window.
96            .find(|w| w != &&Some(*handle))
97        {
98            Some(*last_focused_window)
99        } else {
100            self.get_next_or_previous_handle(handle)
101        };
102        // If there is a parent we would want to focus it.
103        let (transient, floating, visible) =
104            match self.state.windows.iter().find(|w| &w.handle == handle) {
105                Some(window) => (window.transient, window.floating(), window.visible()),
106                None => return false,
107            };
108        self.state
109            .focus_manager
110            .tags_last_window
111            .retain(|_, h| h != handle);
112        self.state.windows.retain(|w| &w.handle != handle);
113
114        self.state.handle_single_border(self.config.border_width());
115
116        // Make sure the workspaces do not draw on the docks.
117        update_workspace_avoid_list(&mut self.state);
118
119        let focused = self.state.focus_manager.window_history.front();
120        // Make sure focus is recalculated if we closed the currently focused window
121        if focused == Some(&Some(*handle)) {
122            if self.state.focus_manager.behaviour.is_sloppy()
123                && self.state.focus_manager.sloppy_mouse_follows_focus
124            {
125                let act = DisplayAction::FocusWindowUnderCursor;
126                self.state.actions.push_back(act);
127                // Make sure we actually update the focus, as the currently
128                // focused window will be removed from history.
129                self.state.focus_manager.window_history.push_front(None);
130            } else if let Some(parent) =
131                find_transient_parent(&self.state.windows, transient).map(|p| p.handle)
132            {
133                self.state.focus_window(&parent);
134            } else if let Some(handle) = new_handle {
135                self.state.focus_window(&handle);
136            } else {
137                let act = DisplayAction::Unfocus(Some(*handle), floating);
138                self.state.actions.push_back(act);
139                self.state.focus_manager.window_history.push_front(None);
140            }
141        }
142
143        // Remove destroyed window from history.
144        self.state
145            .focus_manager
146            .window_history
147            .retain(|w| w != &Some(*handle));
148
149        // Only update windows if this window is visible.
150        visible
151    }
152
153    /// `window_changed_handler` is called when the display server sends
154    /// the `DisplayEvent::WindowChange(change)` event.
155    ///
156    /// Returns true if changes need to be rendered.
157    pub fn window_changed_handler(&mut self, change: WindowChange<H>) -> bool {
158        let mut changed = false;
159        let mut fullscreen_changed = false;
160        let mut above_changed = false;
161        let mut transient_changed = false;
162        let strut_changed = change.strut.is_some();
163        let windows = self.state.windows.clone();
164        let respect_dialog_position = self.config.respect_dialog_position();
165        if let Some(window) = self
166            .state
167            .windows
168            .iter_mut()
169            .find(|w| w.handle == change.handle)
170        {
171            transient_changed = change
172                .transient
173                .as_ref()
174                .is_some_and(|transient| transient != &window.transient);
175            if let Some(states) = &change.states {
176                fullscreen_changed =
177                    states.contains(&WindowState::Fullscreen) != window.is_fullscreen();
178                above_changed = states.contains(&WindowState::Above)
179                    != window.states.contains(&WindowState::Above);
180            }
181            let container = if respect_dialog_position && window.r#type.is_dialog_like() {
182                None
183            } else {
184                match find_transient_parent(&windows, window.transient) {
185                    Some(parent) => Some(parent.exact_xyhw()),
186                    None if window.r#type == WindowType::Dialog => self
187                        .state
188                        .workspaces
189                        .iter()
190                        .find(|ws| ws.tag == window.tag)
191                        .map(|ws| ws.xyhw),
192                    _ => None,
193                }
194            };
195
196            changed = change.update(window, container);
197            if window.r#type == WindowType::Dock {
198                update_workspace_avoid_list(&mut self.state);
199                // Don't let changes from docks re-render the worker. This will result in an
200                // infinite loop. Just be patient a rerender will occur.
201            }
202        }
203        if fullscreen_changed {
204            // Update `dock` windows once, so they can recieve mouse click events again.
205            // This is necessary, since we exclude them from the general update loop above.
206            if let Some(windows) = self
207                .state
208                .windows
209                .iter()
210                .find(|w| w.r#type == WindowType::Dock)
211            {
212                self.display_server.update_windows(vec![windows]);
213            }
214        }
215
216        if fullscreen_changed || above_changed || transient_changed {
217            // Reorder windows.
218            self.state.sort_windows();
219        }
220        if strut_changed {
221            self.state.update_static();
222        }
223        changed
224    }
225
226    /// Find the next or previous window on the currently focused workspace.
227    /// May return `None` if no other window is present.
228    ///
229    /// Returns true if changes need to be rendered.
230    pub fn get_next_or_previous_handle(
231        &mut self,
232        handle: &WindowHandle<H>,
233    ) -> Option<WindowHandle<H>> {
234        let focused_workspace = self.state.focus_manager.workspace(&self.state.workspaces)?;
235        let on_focused_workspace = |x: &Window<H>| -> bool { focused_workspace.is_managed(x) };
236        let mut windows_on_workspace =
237            helpers::vec_extract(&mut self.state.windows, on_focused_workspace);
238        let is_handle = |x: &Window<H>| -> bool { &x.handle == handle };
239        let new_handle = helpers::relative_find(&windows_on_workspace, is_handle, 1, false)
240            .or_else(|| helpers::relative_find(&windows_on_workspace, is_handle, -1, false))
241            .map(|w| w.handle);
242        self.state.windows.append(&mut windows_on_workspace);
243        new_handle
244    }
245}
246
247// Private helper functions.
248
249fn find_terminal<H: Handle>(state: &State<H>, pid: Option<u32>) -> Option<&Window<H>> {
250    // Get $SHELL, e.g. /bin/zsh
251    let shell_path = env::var("SHELL").ok()?;
252    // Remove /bin/
253    let shell = shell_path.split('/').next_back()?;
254    // Try and find the shell that launched this app, if such a thing exists.
255    let is_terminal = |pid: u32| -> Option<bool> {
256        let parent = std::fs::read(format!("/proc/{pid}/comm")).ok()?;
257        let parent_bytes = parent.split(|&c| c == b' ').next()?;
258        let parent_str = std::str::from_utf8(parent_bytes).ok()?.strip_suffix('\n')?;
259        Some(parent_str == shell)
260    };
261
262    let get_parent = |pid: u32| -> Option<u32> {
263        let stat = std::fs::read(format!("/proc/{pid}/stat")).ok()?;
264        let ppid_bytes = stat.split(|&c| c == b' ').nth(3)?;
265        let ppid_str = std::str::from_utf8(ppid_bytes).ok()?;
266        let ppid_u32 = u32::from_str(ppid_str).ok()?;
267        Some(ppid_u32)
268    };
269
270    let pid = pid?;
271    let shell_id = get_parent(pid)?;
272    if is_terminal(shell_id)? {
273        let terminal = get_parent(shell_id)?;
274        return state.windows.iter().find(|w| w.pid == Some(terminal));
275    }
276
277    None
278}
279
280fn find_transient_parent<H: Handle>(
281    windows: &[Window<H>],
282    transient: Option<WindowHandle<H>>,
283) -> Option<&Window<H>> {
284    let mut transient = transient?;
285    loop {
286        transient = if let Some(found) = windows
287            .iter()
288            .find(|x| x.handle == transient)
289            .and_then(|x| x.transient)
290        {
291            found
292        } else {
293            return windows.iter().find(|x| x.handle == transient);
294        };
295    }
296}
297
298fn insert_window<H: Handle>(state: &mut State<H>, window: &mut Window<H>, layout: &str) {
299    let mut was_fullscreen = false;
300    if window.r#type == WindowType::Normal {
301        let for_active_workspace =
302            |x: &Window<H>| -> bool { window.tag == x.tag && x.is_managed() };
303        // Only minimize when the new window is type normal.
304        if let Some(fsw) = state
305            .windows
306            .iter_mut()
307            .find(|w| for_active_workspace(w) && w.is_fullscreen())
308        {
309            let act =
310                DisplayAction::SetState(fsw.handle, !fsw.is_fullscreen(), WindowState::Fullscreen);
311            state.actions.push_back(act);
312            was_fullscreen = true;
313        } else if let Some(fsw) = state
314            .windows
315            .iter_mut()
316            .find(|w| for_active_workspace(w) && w.is_maximized())
317            && !window.floating()
318        {
319            let act =
320                DisplayAction::SetState(fsw.handle, !fsw.is_maximized(), WindowState::Maximized);
321            state.actions.push_back(act);
322        }
323
324        // TODO: remove hard coded layout names.
325        let monocle = layouts::MONOCLE;
326        let main_and_deck = layouts::MAIN_AND_DECK;
327        if layout == monocle || layout == main_and_deck {
328            // Extract the current windows on the same workspace.
329            let mut to_reorder = helpers::vec_extract(&mut state.windows, for_active_workspace);
330            if layout == monocle || to_reorder.is_empty() {
331                // When in monocle we want the new window to be fullscreen if the previous window was
332                // fullscreen.
333                if was_fullscreen {
334                    let act = DisplayAction::SetState(
335                        window.handle,
336                        !window.is_fullscreen(),
337                        WindowState::Fullscreen,
338                    );
339                    state.actions.push_back(act);
340                }
341                // Place the window above the other windows on the workspace.
342                to_reorder.insert(0, window.clone());
343            } else {
344                // Place the window second within the other windows on the workspace.
345                to_reorder.insert(1, window.clone());
346            }
347            state.windows.append(&mut to_reorder);
348            return;
349        }
350    }
351
352    // Keep dialog-like, floating, and scratchpad windows at the top.
353    if window.r#type.is_dialog_like() || window.floating() || is_scratchpad(state, window) {
354        state.windows.insert(0, window.clone());
355        return;
356    }
357
358    let current_index = state
359        .focus_manager
360        .window(&state.windows)
361        .and_then(|current| {
362            state
363                .windows
364                .iter()
365                .position(|w| w.handle == current.handle)
366        })
367        .unwrap_or(0);
368
369    // Past special cases we just insert the window based on the configured insert behavior
370    match state.insert_behavior {
371        InsertBehavior::Top => state.windows.insert(0, window.clone()),
372        InsertBehavior::Bottom => state.windows.push(window.clone()),
373        InsertBehavior::AfterCurrent if current_index < state.windows.len() => {
374            state.windows.insert(current_index + 1, window.clone());
375        }
376        InsertBehavior::AfterCurrent | InsertBehavior::BeforeCurrent => {
377            state.windows.insert(current_index, window.clone());
378        }
379    }
380}
381
382fn is_scratchpad<H: Handle>(state: &State<H>, window: &Window<H>) -> bool {
383    state
384        .active_scratchpads
385        .iter()
386        .any(|(_, id)| id.iter().any(|id| window.pid == Some(*id)))
387}
388
389// Tries to position a window according to the requested sizes.
390// When no size was requested, defaults to `ws.center_halfed()`
391fn set_relative_floating<H: Handle>(window: &mut Window<H>, ws: &Workspace, outer: Xyhw) {
392    window.set_floating(true);
393    window.normal = ws.xyhw;
394    let xyhw = window.requested.map_or_else(
395        || ws.center_halfed(),
396        |mut requested| {
397            requested.center_relative(outer, window.border);
398            if !ws.xyhw_avoided.contains_xyhw(&requested) {
399                requested.center_relative(ws.xyhw_avoided, window.border);
400            }
401            requested
402        },
403    );
404    window.set_floating_exact(xyhw);
405}
406
407// Dialog-like windows already carry the geometry selected by their client. Keep that geometry
408// instead of replacing its position with LeftWM's centering policy.
409fn set_requested_floating<H: Handle>(window: &mut Window<H>, ws: &Workspace) {
410    window.set_floating(true);
411    window.normal = ws.xyhw;
412    let xyhw = window.requested.unwrap_or_else(|| ws.center_halfed());
413    window.set_floating_exact(xyhw);
414}
415
416fn setup_window<H: Handle>(
417    state: &mut State<H>,
418    window: &mut Window<H>,
419    xy: (i32, i32),
420    respect_dialog_position: bool,
421    layout: &mut String,
422    is_first: &mut bool,
423    on_same_tag: &mut bool,
424) {
425    // Identify the workspace in which to create the window.
426    let ws = state
427        .focus_manager
428        .create_follows_cursor() // If the window must be created under the cursor...
429        .then_some(
430            // ...look for the workspace with the cursor.
431            state
432                .workspaces
433                .iter()
434                .find(|ws| ws.xyhw.contains_point(xy.0, xy.1)),
435        )
436        .flatten()
437        .or(state.focus_manager.workspace(&state.workspaces)); // Else, use the workspace which has the focus.
438
439    // If no workspace was found, put the window on tag 1.
440    let Some(ws) = ws else {
441        tracing::warn!("setup_window failed to identify workspace. Falling back to tag 1");
442        window.tag = Some(1);
443        if is_scratchpad(state, window)
444            && let Some(scratchpad_tag) = state.tags.get_hidden_by_label("NSP")
445        {
446            window.tag(&scratchpad_tag.id);
447            window.set_floating(true);
448        }
449        return;
450    };
451
452    // Setup basic variables.
453    let for_active_workspace = |x: &Window<H>| -> bool { ws.tag == x.tag && x.is_managed() };
454    *is_first = !state.windows.iter().any(for_active_workspace);
455    // May have been set by a predefined tag.
456    if window.tag.is_none() {
457        window.tag =
458            find_terminal(state, window.pid).map_or_else(|| ws.tag, |terminal| terminal.tag);
459    }
460    *on_same_tag = ws.tag == window.tag;
461    layout.clone_from(&state.layout_manager.layout(ws.id, window.tag.unwrap()).name);
462
463    // Setup a scratchpad window.
464    if let Some((scratchpad_name, _)) = state
465        .active_scratchpads
466        .iter()
467        .find(|(_, id)| id.iter().any(|id| Some(*id) == window.pid))
468    {
469        window.set_floating(true);
470        if let Some(s) = state
471            .scratchpads
472            .iter()
473            .find(|s| *scratchpad_name == s.name)
474        {
475            let new_float_exact = s.xyhw(&ws.xyhw);
476            window.normal = ws.xyhw;
477            window.set_floating_exact(new_float_exact);
478            return;
479        }
480    }
481
482    // Dialog-like clients are responsible for their own geometry. This must run before transient
483    // handling, which intentionally centers normal child windows relative to their parent.
484    if respect_dialog_position && window.r#type.is_dialog_like() {
485        set_requested_floating(window, ws);
486        return;
487    }
488
489    // Setup a child window.
490    if let Some(parent) = find_transient_parent(&state.windows, window.transient) {
491        // This is currently for vlc, this probably will need to be more general if another
492        // case comes up where we don't want to move the window.
493        if window.r#type != WindowType::Utility {
494            set_relative_floating(window, ws, parent.exact_xyhw());
495            return;
496        }
497    }
498
499    // Setup window based on type.
500    match window.r#type {
501        WindowType::Normal => {
502            window.apply_margin_multiplier(ws.margin_multiplier);
503            if window.floating() {
504                set_relative_floating(window, ws, ws.xyhw_avoided);
505            }
506        }
507        WindowType::Dialog | WindowType::Splash => {
508            set_relative_floating(window, ws, ws.xyhw_avoided);
509        }
510        _ => {}
511    }
512}
513
514fn update_workspace_avoid_list<H: Handle>(state: &mut State<H>) {
515    let mut avoid = vec![];
516    state
517        .windows
518        .iter()
519        .filter(|w| w.r#type == WindowType::Dock)
520        .filter_map(|w| w.strut.map(|strut| (w.handle, strut)))
521        .for_each(|(handle, to_avoid)| {
522            tracing::trace!("AVOID STRUT:[{:?}] {:?}", handle, to_avoid);
523            avoid.push(to_avoid);
524        });
525    for ws in &mut state.workspaces {
526        let struts = avoid
527            .clone()
528            .into_iter()
529            .filter(|s| {
530                let (x, y) = s.center();
531                ws.contains_point(x, y)
532            })
533            .collect();
534        ws.avoid = struts;
535        ws.update_avoided_areas();
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use crate::Manager;
543    use crate::layouts::MONOCLE;
544    use crate::models::{FocusBehaviour, MockHandle, Screen, XyhwBuilder, XyhwChange};
545
546    fn last_window_order(state: &State<MockHandle>) -> Vec<WindowHandle<MockHandle>> {
547        state
548            .actions
549            .iter()
550            .rev()
551            .find_map(|action| match action {
552                DisplayAction::SetWindowOrder(order) => Some(order.clone()),
553                _ => None,
554            })
555            .expect("expected a window order action")
556    }
557
558    fn follows_mouse_on_creation(
559        window: Window<MockHandle>,
560        focus_new_windows: bool,
561        focus_behaviour: FocusBehaviour,
562        sloppy_mouse_follows_focus: bool,
563    ) -> bool {
564        let mut manager = Manager::new_test(vec!["1".to_owned()]);
565        manager.screen_create_handler(Screen::default());
566        manager.state.actions.clear();
567        manager.state.focus_manager.focus_new_windows = focus_new_windows;
568        manager.state.focus_manager.behaviour = focus_behaviour;
569        manager.state.focus_manager.sloppy_mouse_follows_focus = sloppy_mouse_follows_focus;
570
571        manager.window_created_handler(window, -1, -1);
572
573        manager
574            .state
575            .actions
576            .iter()
577            .find_map(|action| match action {
578                DisplayAction::AddedWindow(_, _, follow_mouse) => Some(*follow_mouse),
579                _ => None,
580            })
581            .expect("expected an AddedWindow action")
582    }
583
584    #[test]
585    fn new_window_mouse_warp_uses_type_default_and_window_override() {
586        let cases = [
587            (1, WindowType::Normal, None, true),
588            (2, WindowType::Utility, None, false),
589            (3, WindowType::Normal, Some(true), false),
590            (4, WindowType::Utility, Some(false), true),
591        ];
592
593        for (handle, window_type, disable_mouse_grab, expected) in cases {
594            let mut window = Window::new(WindowHandle(handle), None, None);
595            window.r#type = window_type;
596            window.set_disable_mouse_grab(disable_mouse_grab);
597
598            assert_eq!(
599                follows_mouse_on_creation(window, true, FocusBehaviour::Sloppy, true),
600                expected,
601                "unexpected follow_mouse for disable_mouse_grab={disable_mouse_grab:?}",
602            );
603        }
604    }
605
606    fn dialog_like_types() -> [WindowType; 10] {
607        [
608            WindowType::Dialog,
609            WindowType::Splash,
610            WindowType::Utility,
611            WindowType::Menu,
612            WindowType::DropdownMenu,
613            WindowType::PopupMenu,
614            WindowType::Tooltip,
615            WindowType::Notification,
616            WindowType::Combo,
617            WindowType::Dnd,
618        ]
619    }
620
621    #[test]
622    fn dialog_like_windows_keep_their_requested_geometry_when_enabled() {
623        let requested: Xyhw = XyhwBuilder {
624            x: 120,
625            y: 80,
626            w: 240,
627            h: 160,
628            ..XyhwBuilder::default()
629        }
630        .into();
631
632        for (handle, window_type) in dialog_like_types().into_iter().enumerate() {
633            let mut manager = Manager::new_test(vec!["1".to_owned()]);
634            manager.config.respect_dialog_position = true;
635            manager.screen_create_handler(Screen::default());
636
637            let handle = WindowHandle::<MockHandle>(handle as i32 + 1);
638            let mut window = Window::new(handle, None, None);
639            window.r#type = window_type;
640            window.requested = Some(requested);
641            manager.window_created_handler(window, -1, -1);
642
643            let actual = manager
644                .state
645                .windows
646                .iter()
647                .find(|window| window.handle == handle)
648                .expect("expected the dialog-like window")
649                .exact_xyhw();
650            assert_eq!(actual, requested);
651        }
652    }
653
654    #[test]
655    fn dialog_windows_center_by_default_but_keep_their_requested_size() {
656        let mut manager = Manager::new_test(vec!["1".to_owned()]);
657        manager.screen_create_handler(Screen::default());
658
659        let requested: Xyhw = XyhwBuilder {
660            x: 120,
661            y: 80,
662            w: 240,
663            h: 160,
664            ..XyhwBuilder::default()
665        }
666        .into();
667        let handle = WindowHandle::<MockHandle>(1);
668        let mut window = Window::new(handle, None, None);
669        window.r#type = WindowType::Dialog;
670        window.requested = Some(requested);
671        let mut expected = requested;
672        expected.center_relative(manager.state.workspaces[0].xyhw_avoided, window.border);
673        manager.window_created_handler(window, -1, -1);
674
675        let actual = manager
676            .state
677            .windows
678            .iter()
679            .find(|window| window.handle == handle)
680            .expect("expected the dialog window")
681            .exact_xyhw();
682        assert_eq!(actual, expected);
683        assert_eq!((actual.w(), actual.h()), (requested.w(), requested.h()));
684        assert_ne!((actual.x(), actual.y()), (requested.x(), requested.y()));
685    }
686
687    #[test]
688    fn mouse_warp_permission_does_not_bypass_global_focus_settings() {
689        let cases = [
690            (false, FocusBehaviour::Sloppy, true),
691            (true, FocusBehaviour::ClickTo, true),
692            (true, FocusBehaviour::Sloppy, false),
693        ];
694
695        for (focus_new_windows, focus_behaviour, sloppy_mouse_follows_focus) in cases {
696            let window = Window::new(WindowHandle(1), None, None);
697
698            assert!(!follows_mouse_on_creation(
699                window,
700                focus_new_windows,
701                focus_behaviour,
702                sloppy_mouse_follows_focus,
703            ));
704        }
705    }
706
707    #[test]
708    fn dialog_like_windows_without_geometry_keep_the_centered_fallback() {
709        for (handle, window_type) in dialog_like_types().into_iter().enumerate() {
710            let mut manager = Manager::new_test(vec!["1".to_owned()]);
711            manager.config.respect_dialog_position = true;
712            manager.screen_create_handler(Screen::default());
713            let expected = manager.state.workspaces[0].center_halfed();
714
715            let handle = WindowHandle::<MockHandle>(handle as i32 + 1);
716            let mut window = Window::new(handle, None, None);
717            window.r#type = window_type;
718            manager.window_created_handler(window, -1, -1);
719
720            let actual = manager
721                .state
722                .windows
723                .iter()
724                .find(|window| window.handle == handle)
725                .expect("expected the dialog-like window")
726                .exact_xyhw();
727            assert_eq!(actual, expected);
728        }
729    }
730
731    #[test]
732    fn dialog_like_configure_changes_only_update_requested_fields_when_enabled() {
733        for (index, window_type) in dialog_like_types().into_iter().enumerate() {
734            let mut manager = Manager::new_test(vec!["1".to_owned()]);
735            manager.config.respect_dialog_position = true;
736            manager.screen_create_handler(Screen::default());
737
738            let parent_handle = WindowHandle::<MockHandle>(1);
739            manager.window_created_handler(Window::new(parent_handle, None, None), -1, -1);
740
741            let requested: Xyhw = XyhwBuilder {
742                x: 120,
743                y: 80,
744                w: 240,
745                h: 160,
746                ..XyhwBuilder::default()
747            }
748            .into();
749            let handle = WindowHandle::<MockHandle>(index as i32 + 2);
750            let mut window = Window::new(handle, None, None);
751            window.r#type = window_type;
752            window.transient = Some(parent_handle);
753            window.border = 0;
754            window.requested = Some(requested);
755            manager.window_created_handler(window, -1, -1);
756
757            let mut resize = WindowChange::new(handle);
758            resize.floating = Some(XyhwChange {
759                w: Some(300),
760                h: Some(190),
761                ..XyhwChange::default()
762            });
763            manager.window_changed_handler(resize);
764
765            let resized = manager
766                .state
767                .windows
768                .iter()
769                .find(|window| window.handle == handle)
770                .expect("expected the resized dialog-like window")
771                .exact_xyhw();
772            assert_eq!((resized.x(), resized.y()), (120, 80));
773            assert_eq!((resized.w(), resized.h()), (300, 190));
774
775            let mut movement = WindowChange::new(handle);
776            movement.floating = Some(XyhwChange {
777                x: Some(-40),
778                y: Some(210),
779                ..XyhwChange::default()
780            });
781            manager.window_changed_handler(movement);
782
783            let moved = manager
784                .state
785                .windows
786                .iter()
787                .find(|window| window.handle == handle)
788                .expect("expected the moved dialog-like window")
789                .exact_xyhw();
790            assert_eq!((moved.x(), moved.y()), (-40, 210));
791            assert_eq!((moved.w(), moved.h()), (300, 190));
792        }
793    }
794
795    #[test]
796    fn dialog_configure_centers_by_default_but_keeps_the_requested_size() {
797        let mut manager = Manager::new_test(vec!["1".to_owned()]);
798        manager.screen_create_handler(Screen::default());
799
800        let parent_handle = WindowHandle::<MockHandle>(1);
801        manager.window_created_handler(Window::new(parent_handle, None, None), -1, -1);
802        let parent = manager
803            .state
804            .windows
805            .iter()
806            .find(|window| window.handle == parent_handle)
807            .expect("expected the parent window")
808            .exact_xyhw();
809
810        let handle = WindowHandle::<MockHandle>(2);
811        let mut window = Window::new(handle, None, None);
812        window.r#type = WindowType::Dialog;
813        window.transient = Some(parent_handle);
814        window.border = 0;
815        window.requested = Some(
816            XyhwBuilder {
817                x: 120,
818                y: 80,
819                w: 240,
820                h: 160,
821                ..XyhwBuilder::default()
822            }
823            .into(),
824        );
825        manager.window_created_handler(window, -1, -1);
826
827        let mut requested: Xyhw = XyhwBuilder {
828            x: -40,
829            y: 210,
830            w: 300,
831            h: 190,
832            ..XyhwBuilder::default()
833        }
834        .into();
835        let mut change = WindowChange::new(handle);
836        change.floating = Some(XyhwChange {
837            x: Some(requested.x()),
838            y: Some(requested.y()),
839            w: Some(requested.w()),
840            h: Some(requested.h()),
841            ..XyhwChange::default()
842        });
843        manager.window_changed_handler(change);
844
845        requested.center_relative(parent, 0);
846        let actual = manager
847            .state
848            .windows
849            .iter()
850            .find(|window| window.handle == handle)
851            .expect("expected the dialog window")
852            .exact_xyhw();
853        assert_eq!(actual, requested);
854        assert_eq!((actual.w(), actual.h()), (300, 190));
855        assert_ne!((actual.x(), actual.y()), (-40, 210));
856    }
857
858    #[test]
859    fn dialog_is_visible_and_stacked_above_fullscreen_without_focus() {
860        let mut manager = Manager::new_test(vec!["1".to_string()]);
861        manager.screen_create_handler(Screen::default());
862
863        let mut fullscreen = Window::new(WindowHandle::<MockHandle>(1), None, None);
864        fullscreen.states.push(WindowState::Fullscreen);
865        manager.window_created_handler(fullscreen, -1, -1);
866
867        let mut dialog = Window::new(WindowHandle::<MockHandle>(2), None, None);
868        dialog.r#type = WindowType::Dialog;
869        manager.window_created_handler(dialog, -1, -1);
870
871        assert_eq!(
872            manager
873                .state
874                .focus_manager
875                .window(&manager.state.windows)
876                .map(|window| window.handle),
877            Some(WindowHandle(1))
878        );
879        assert_eq!(
880            last_window_order(&manager.state),
881            vec![WindowHandle(2), WindowHandle(1)]
882        );
883
884        manager.update_windows();
885        assert!(
886            manager
887                .state
888                .windows
889                .iter()
890                .find(|window| window.handle == WindowHandle(2))
891                .is_some_and(Window::visible)
892        );
893    }
894
895    #[test]
896    fn late_transient_hint_restacks_dialog_above_fullscreen_parent() {
897        let mut manager = Manager::new_test(vec!["1".to_string()]);
898        manager.screen_create_handler(Screen::default());
899
900        let mut fullscreen = Window::new(WindowHandle::<MockHandle>(1), None, None);
901        fullscreen.states.push(WindowState::Fullscreen);
902        manager.window_created_handler(fullscreen, -1, -1);
903
904        let mut dialog = Window::new(WindowHandle::<MockHandle>(2), None, None);
905        dialog.r#type = WindowType::Dialog;
906        manager.window_created_handler(dialog, -1, -1);
907
908        manager.state.actions.clear();
909        let mut change = WindowChange::new(WindowHandle(2));
910        change.transient = Some(Some(WindowHandle(1)));
911
912        assert!(manager.window_changed_handler(change));
913        assert_eq!(
914            last_window_order(&manager.state),
915            vec![WindowHandle(2), WindowHandle(1)]
916        );
917
918        manager.update_windows();
919        assert!(
920            manager
921                .state
922                .windows
923                .iter()
924                .find(|window| window.handle == WindowHandle(2))
925                .is_some_and(Window::visible)
926        );
927    }
928
929    #[test]
930    fn insert_behavior_bottom_add_window_at_the_end_of_the_stack() {
931        let mut manager = Manager::new_test(vec![]);
932        manager.state.insert_behavior = InsertBehavior::Bottom;
933
934        manager.screen_create_handler(Screen::default());
935        manager.window_created_handler(
936            Window::new(WindowHandle::<MockHandle>(1), None, None),
937            -1,
938            -1,
939        );
940        manager.window_created_handler(
941            Window::new(WindowHandle::<MockHandle>(2), None, None),
942            -1,
943            -1,
944        );
945
946        let expected = vec![WindowHandle::<MockHandle>(1), WindowHandle::<MockHandle>(2)];
947
948        let actual: Vec<WindowHandle<MockHandle>> =
949            manager.state.windows.iter().map(|w| w.handle).collect();
950
951        assert_eq!(actual, expected);
952    }
953
954    #[test]
955    fn insert_behavior_top_add_window_at_the_top_of_the_stack() {
956        let mut manager = Manager::new_test(vec![]);
957        manager.state.insert_behavior = InsertBehavior::Top;
958
959        manager.screen_create_handler(Screen::default());
960        manager.window_created_handler(
961            Window::new(WindowHandle::<MockHandle>(1), None, None),
962            -1,
963            -1,
964        );
965        manager.window_created_handler(
966            Window::new(WindowHandle::<MockHandle>(2), None, None),
967            -1,
968            -1,
969        );
970
971        let expected = vec![WindowHandle::<MockHandle>(2), WindowHandle::<MockHandle>(1)];
972        let actual: Vec<WindowHandle<MockHandle>> =
973            manager.state.windows.iter().map(|w| w.handle).collect();
974
975        assert_eq!(actual, expected);
976    }
977
978    #[test]
979    fn insert_behavior_after_current_add_window_after_the_current_window() {
980        let mut manager = Manager::new_test(vec![]);
981        manager.state.insert_behavior = InsertBehavior::AfterCurrent;
982
983        manager.screen_create_handler(Screen::default());
984        manager.window_created_handler(
985            Window::new(WindowHandle::<MockHandle>(1), None, None),
986            -1,
987            -1,
988        );
989        manager.window_created_handler(
990            Window::new(WindowHandle::<MockHandle>(2), None, None),
991            -1,
992            -1,
993        );
994        manager.window_created_handler(
995            Window::new(WindowHandle::<MockHandle>(3), None, None),
996            -1,
997            -1,
998        );
999
1000        let expected = vec![
1001            WindowHandle::<MockHandle>(1),
1002            WindowHandle::<MockHandle>(3),
1003            WindowHandle::<MockHandle>(2),
1004        ];
1005        let actual: Vec<WindowHandle<MockHandle>> =
1006            manager.state.windows.iter().map(|w| w.handle).collect();
1007
1008        assert_eq!(actual, expected);
1009    }
1010
1011    #[test]
1012    fn insert_behavior_before_current_add_window_before_the_current_window() {
1013        let mut manager = Manager::new_test(vec![]);
1014        manager.state.insert_behavior = InsertBehavior::BeforeCurrent;
1015
1016        manager.screen_create_handler(Screen::default());
1017        manager.window_created_handler(
1018            Window::new(WindowHandle::<MockHandle>(1), None, None),
1019            -1,
1020            -1,
1021        );
1022        manager.window_created_handler(
1023            Window::new(WindowHandle::<MockHandle>(2), None, None),
1024            -1,
1025            -1,
1026        );
1027
1028        manager.window_created_handler(
1029            Window::new(WindowHandle::<MockHandle>(3), None, None),
1030            -1,
1031            -1,
1032        );
1033
1034        let expected = vec![
1035            WindowHandle::<MockHandle>(2),
1036            WindowHandle::<MockHandle>(3),
1037            WindowHandle::<MockHandle>(1),
1038        ];
1039        let actual: Vec<WindowHandle<MockHandle>> =
1040            manager.state.windows.iter().map(|w| w.handle).collect();
1041
1042        assert_eq!(actual, expected);
1043    }
1044
1045    #[test]
1046    fn single_window_has_no_border() {
1047        let mut manager = Manager::new_test_with_border(vec![], 1);
1048        manager.screen_create_handler(Screen::default());
1049
1050        manager.window_created_handler(
1051            Window::new(WindowHandle::<MockHandle>(1), None, None),
1052            -1,
1053            -1,
1054        );
1055
1056        assert_eq!((manager.state.windows[0]).border(), 0);
1057    }
1058
1059    #[test]
1060    fn multiple_windows_have_borders() {
1061        let mut manager = Manager::new_test_with_border(vec![], 1);
1062        manager.screen_create_handler(Screen::default());
1063
1064        manager.window_created_handler(
1065            Window::new(WindowHandle::<MockHandle>(1), None, None),
1066            -1,
1067            -1,
1068        );
1069        manager.window_created_handler(
1070            Window::new(WindowHandle::<MockHandle>(2), None, None),
1071            -1,
1072            -1,
1073        );
1074
1075        assert_eq!((manager.state.windows[0]).border(), 1);
1076        assert_eq!((manager.state.windows[1]).border(), 1);
1077    }
1078
1079    #[test]
1080    fn remaining_single_window_has_no_border() {
1081        let mut manager = Manager::new_test_with_border(vec![], 1);
1082        manager.screen_create_handler(Screen::default());
1083
1084        manager.window_created_handler(
1085            Window::new(WindowHandle::<MockHandle>(1), None, None),
1086            -1,
1087            -1,
1088        );
1089        manager.window_created_handler(
1090            Window::new(WindowHandle::<MockHandle>(2), None, None),
1091            -1,
1092            -1,
1093        );
1094
1095        manager.window_destroyed_handler(&manager.state.windows[1].handle.clone());
1096
1097        assert_eq!((manager.state.windows[0]).border(), 0);
1098    }
1099
1100    #[test]
1101    fn single_windows_on_different_tags_have_no_border() {
1102        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
1103        manager.screen_create_handler(Screen::default());
1104
1105        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
1106        first_window.tag(&1);
1107        manager.window_created_handler(first_window, -1, -1);
1108
1109        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
1110        second_window.tag(&2);
1111        manager.window_created_handler(second_window, -1, -1);
1112
1113        assert_eq!((manager.state.windows[0]).border(), 0);
1114        assert_eq!((manager.state.windows[1]).border(), 0);
1115    }
1116
1117    #[test]
1118    fn single_window_has_no_border_and_windows_on_another_tag_have_borders() {
1119        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
1120        manager.screen_create_handler(Screen::default());
1121
1122        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
1123        first_window.tag(&1);
1124        manager.window_created_handler(first_window, -1, -1);
1125
1126        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
1127        second_window.tag(&2);
1128        manager.window_created_handler(second_window, -1, -1);
1129
1130        let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
1131        third_window.tag(&2);
1132        manager.window_created_handler(third_window, -1, -1);
1133
1134        assert_eq!((manager.state.windows[0]).border(), 0);
1135        assert_eq!((manager.state.windows[1]).border(), 1);
1136        assert_eq!((manager.state.windows[2]).border(), 1);
1137    }
1138
1139    #[test]
1140    fn remaining_single_window_on_another_tag_has_no_border() {
1141        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
1142        manager.screen_create_handler(Screen::default());
1143
1144        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
1145        first_window.tag(&1);
1146        manager.window_created_handler(first_window, -1, -1);
1147
1148        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
1149        second_window.tag(&2);
1150        manager.window_created_handler(second_window, -1, -1);
1151
1152        let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
1153        third_window.tag(&2);
1154        manager.window_created_handler(third_window, -1, -1);
1155
1156        manager.window_destroyed_handler(&manager.state.windows[2].handle.clone());
1157
1158        assert_eq!((manager.state.windows[0]).border(), 0);
1159        assert_eq!((manager.state.windows[1]).border(), 0);
1160    }
1161
1162    #[test]
1163    fn monocle_layout_only_has_single_windows() {
1164        let mut manager = Manager::new_test_with_border(vec!["1".to_string()], 1);
1165        manager.screen_create_handler(Screen::default());
1166        manager.state.layout_manager.set_layout(1, 1, MONOCLE);
1167        // manager.state.tags.get_mut(1).unwrap().set_layout(String::from("Monocle"));
1168        manager.window_created_handler(
1169            Window::new(WindowHandle::<MockHandle>(1), None, None),
1170            -1,
1171            -1,
1172        );
1173        manager.window_created_handler(
1174            Window::new(WindowHandle::<MockHandle>(2), None, None),
1175            -1,
1176            -1,
1177        );
1178        assert_eq!((manager.state.windows[0]).border(), 0);
1179        assert_eq!((manager.state.windows[1]).border(), 0);
1180    }
1181}