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        setup_window(
34            &mut self.state,
35            &mut window,
36            (x, y),
37            &mut layout,
38            &mut is_first,
39            &mut on_same_tag,
40        );
41        self.config.load_window(&mut window);
42        insert_window(&mut self.state, &mut window, &layout);
43
44        let follow_mouse = self.state.focus_manager.focus_new_windows
45            && self.state.focus_manager.behaviour.is_sloppy()
46            && self.state.focus_manager.sloppy_mouse_follows_focus
47            && on_same_tag;
48
49        // Let the DS know we are managing this window.
50        let act = DisplayAction::AddedWindow(window.handle, window.floating(), follow_mouse);
51        self.state.actions.push_back(act);
52
53        // Let the DS know the correct desktop to find this window.
54        if window.tag.is_some() {
55            let act = DisplayAction::SetWindowTag(window.handle, window.tag);
56            self.state.actions.push_back(act);
57        }
58
59        // Tell the WM to reevaluate the stacking order, so the new window is put in the correct layer
60        self.state.sort_windows();
61
62        // if `single_window_border` is `false`, remove borders if there is a single visible window
63        self.state.handle_single_border(self.config.border_width());
64
65        // `is_first` and `on_same_tag` are set by `setup_window`
66        // TODO: remove focus_new_windows variable from focus_manager,
67        // TODO: use self.config.focus_new_windows() instead
68        if (self.state.focus_manager.focus_new_windows || is_first) && on_same_tag {
69            self.state.focus_window(&window.handle);
70        }
71
72        // run the `on_new_window_cmd` set in `config.ron`
73        if let Some(cmd) = &self.config.on_new_window_cmd() {
74            exec_shell(cmd, &mut self.children);
75        }
76
77        true
78    }
79
80    /// `window_destroyed_handler` is called when the display server sends
81    /// the `DisplayEvent::WindowDestroy(handle)` event.
82    ///
83    /// Returns true if changes need to be rendered.
84    pub fn window_destroyed_handler(&mut self, handle: &WindowHandle<H>) -> bool {
85        // Get the previous focused window else find the next or previous window on the workspace.
86        let new_handle = if let Some(Some(last_focused_window)) = self
87            .state
88            .focus_manager
89            .window_history
90            .iter()
91            // Take the first window that is not the destroyed window.
92            .find(|w| w != &&Some(*handle))
93        {
94            Some(*last_focused_window)
95        } else {
96            self.get_next_or_previous_handle(handle)
97        };
98        // If there is a parent we would want to focus it.
99        let (transient, floating, visible) =
100            match self.state.windows.iter().find(|w| &w.handle == handle) {
101                Some(window) => (window.transient, window.floating(), window.visible()),
102                None => return false,
103            };
104        self.state
105            .focus_manager
106            .tags_last_window
107            .retain(|_, h| h != handle);
108        self.state.windows.retain(|w| &w.handle != handle);
109
110        self.state.handle_single_border(self.config.border_width());
111
112        // Make sure the workspaces do not draw on the docks.
113        update_workspace_avoid_list(&mut self.state);
114
115        let focused = self.state.focus_manager.window_history.front();
116        // Make sure focus is recalculated if we closed the currently focused window
117        if focused == Some(&Some(*handle)) {
118            if self.state.focus_manager.behaviour.is_sloppy()
119                && self.state.focus_manager.sloppy_mouse_follows_focus
120            {
121                let act = DisplayAction::FocusWindowUnderCursor;
122                self.state.actions.push_back(act);
123                // Make sure we actually update the focus, as the currently
124                // focused window will be removed from history.
125                self.state.focus_manager.window_history.push_front(None);
126            } else if let Some(parent) =
127                find_transient_parent(&self.state.windows, transient).map(|p| p.handle)
128            {
129                self.state.focus_window(&parent);
130            } else if let Some(handle) = new_handle {
131                self.state.focus_window(&handle);
132            } else {
133                let act = DisplayAction::Unfocus(Some(*handle), floating);
134                self.state.actions.push_back(act);
135                self.state.focus_manager.window_history.push_front(None);
136            }
137        }
138
139        // Remove destroyed window from history.
140        self.state
141            .focus_manager
142            .window_history
143            .retain(|w| w != &Some(*handle));
144
145        // Only update windows if this window is visible.
146        visible
147    }
148
149    /// `window_changed_handler` is called when the display server sends
150    /// the `DisplayEvent::WindowChange(change)` event.
151    ///
152    /// Returns true if changes need to be rendered.
153    pub fn window_changed_handler(&mut self, change: WindowChange<H>) -> bool {
154        let mut changed = false;
155        let mut fullscreen_changed = false;
156        let mut above_changed = false;
157        let strut_changed = change.strut.is_some();
158        let windows = self.state.windows.clone();
159        if let Some(window) = self
160            .state
161            .windows
162            .iter_mut()
163            .find(|w| w.handle == change.handle)
164        {
165            if let Some(states) = &change.states {
166                fullscreen_changed =
167                    states.contains(&WindowState::Fullscreen) != window.is_fullscreen();
168                above_changed = states.contains(&WindowState::Above)
169                    != window.states.contains(&WindowState::Above);
170            }
171            let container = match find_transient_parent(&windows, window.transient) {
172                Some(parent) => Some(parent.exact_xyhw()),
173                None if window.r#type == WindowType::Dialog => self
174                    .state
175                    .workspaces
176                    .iter()
177                    .find(|ws| ws.tag == window.tag)
178                    .map(|ws| ws.xyhw),
179                _ => None,
180            };
181
182            changed = change.update(window, container);
183            if window.r#type == WindowType::Dock {
184                update_workspace_avoid_list(&mut self.state);
185                // Don't let changes from docks re-render the worker. This will result in an
186                // infinite loop. Just be patient a rerender will occur.
187            }
188        }
189        if fullscreen_changed {
190            // Update `dock` windows once, so they can recieve mouse click events again.
191            // This is necessary, since we exclude them from the general update loop above.
192            if let Some(windows) = self
193                .state
194                .windows
195                .iter()
196                .find(|w| w.r#type == WindowType::Dock)
197            {
198                self.display_server.update_windows(vec![windows]);
199            }
200        }
201
202        if fullscreen_changed || above_changed {
203            // Reorder windows.
204            self.state.sort_windows();
205        }
206        if strut_changed {
207            self.state.update_static();
208        }
209        changed
210    }
211
212    /// Find the next or previous window on the currently focused workspace.
213    /// May return `None` if no other window is present.
214    ///
215    /// Returns true if changes need to be rendered.
216    pub fn get_next_or_previous_handle(
217        &mut self,
218        handle: &WindowHandle<H>,
219    ) -> Option<WindowHandle<H>> {
220        let focused_workspace = self.state.focus_manager.workspace(&self.state.workspaces)?;
221        let on_focused_workspace = |x: &Window<H>| -> bool { focused_workspace.is_managed(x) };
222        let mut windows_on_workspace =
223            helpers::vec_extract(&mut self.state.windows, on_focused_workspace);
224        let is_handle = |x: &Window<H>| -> bool { &x.handle == handle };
225        let new_handle = helpers::relative_find(&windows_on_workspace, is_handle, 1, false)
226            .or_else(|| helpers::relative_find(&windows_on_workspace, is_handle, -1, false))
227            .map(|w| w.handle);
228        self.state.windows.append(&mut windows_on_workspace);
229        new_handle
230    }
231}
232
233// Private helper functions.
234
235fn find_terminal<H: Handle>(state: &State<H>, pid: Option<u32>) -> Option<&Window<H>> {
236    // Get $SHELL, e.g. /bin/zsh
237    let shell_path = env::var("SHELL").ok()?;
238    // Remove /bin/
239    let shell = shell_path.split('/').last()?;
240    // Try and find the shell that launched this app, if such a thing exists.
241    let is_terminal = |pid: u32| -> Option<bool> {
242        let parent = std::fs::read(format!("/proc/{pid}/comm")).ok()?;
243        let parent_bytes = parent.split(|&c| c == b' ').next()?;
244        let parent_str = std::str::from_utf8(parent_bytes).ok()?.strip_suffix('\n')?;
245        Some(parent_str == shell)
246    };
247
248    let get_parent = |pid: u32| -> Option<u32> {
249        let stat = std::fs::read(format!("/proc/{pid}/stat")).ok()?;
250        let ppid_bytes = stat.split(|&c| c == b' ').nth(3)?;
251        let ppid_str = std::str::from_utf8(ppid_bytes).ok()?;
252        let ppid_u32 = u32::from_str(ppid_str).ok()?;
253        Some(ppid_u32)
254    };
255
256    let pid = pid?;
257    let shell_id = get_parent(pid)?;
258    if is_terminal(shell_id)? {
259        let terminal = get_parent(shell_id)?;
260        return state.windows.iter().find(|w| w.pid == Some(terminal));
261    }
262
263    None
264}
265
266fn find_transient_parent<H: Handle>(
267    windows: &[Window<H>],
268    transient: Option<WindowHandle<H>>,
269) -> Option<&Window<H>> {
270    let mut transient = transient?;
271    loop {
272        transient = if let Some(found) = windows
273            .iter()
274            .find(|x| x.handle == transient)
275            .and_then(|x| x.transient)
276        {
277            found
278        } else {
279            return windows.iter().find(|x| x.handle == transient);
280        };
281    }
282}
283
284fn insert_window<H: Handle>(state: &mut State<H>, window: &mut Window<H>, layout: &str) {
285    let mut was_fullscreen = false;
286    if window.r#type == WindowType::Normal {
287        let for_active_workspace =
288            |x: &Window<H>| -> bool { window.tag == x.tag && x.is_managed() };
289        // Only minimize when the new window is type normal.
290        if let Some(fsw) = state
291            .windows
292            .iter_mut()
293            .find(|w| for_active_workspace(w) && w.is_fullscreen())
294        {
295            let act =
296                DisplayAction::SetState(fsw.handle, !fsw.is_fullscreen(), WindowState::Fullscreen);
297            state.actions.push_back(act);
298            was_fullscreen = true;
299        } else if let Some(fsw) = state
300            .windows
301            .iter_mut()
302            .find(|w| for_active_workspace(w) && w.is_maximized())
303        {
304            if !window.floating() {
305                let act = DisplayAction::SetState(
306                    fsw.handle,
307                    !fsw.is_maximized(),
308                    WindowState::Maximized,
309                );
310                state.actions.push_back(act);
311            }
312        }
313
314        // TODO: remove hard coded layout names.
315        let monocle = layouts::MONOCLE;
316        let main_and_deck = layouts::MAIN_AND_DECK;
317        if layout == monocle || layout == main_and_deck {
318            // Extract the current windows on the same workspace.
319            let mut to_reorder = helpers::vec_extract(&mut state.windows, for_active_workspace);
320            if layout == monocle || to_reorder.is_empty() {
321                // When in monocle we want the new window to be fullscreen if the previous window was
322                // fullscreen.
323                if was_fullscreen {
324                    let act = DisplayAction::SetState(
325                        window.handle,
326                        !window.is_fullscreen(),
327                        WindowState::Fullscreen,
328                    );
329                    state.actions.push_back(act);
330                }
331                // Place the window above the other windows on the workspace.
332                to_reorder.insert(0, window.clone());
333            } else {
334                // Place the window second within the other windows on the workspace.
335                to_reorder.insert(1, window.clone());
336            }
337            state.windows.append(&mut to_reorder);
338            return;
339        }
340    }
341
342    // If a window is a dialog, splash, utility, floating or scractchpad we want it to be at the top.
343    if window.r#type == WindowType::Dialog
344        || window.r#type == WindowType::Splash
345        || window.r#type == WindowType::Utility
346        || window.floating()
347        || is_scratchpad(state, window)
348    {
349        state.windows.insert(0, window.clone());
350        return;
351    }
352
353    let current_index = state
354        .focus_manager
355        .window(&state.windows)
356        .and_then(|current| {
357            state
358                .windows
359                .iter()
360                .position(|w| w.handle == current.handle)
361        })
362        .unwrap_or(0);
363
364    // Past special cases we just insert the window based on the configured insert behavior
365    match state.insert_behavior {
366        InsertBehavior::Top => state.windows.insert(0, window.clone()),
367        InsertBehavior::Bottom => state.windows.push(window.clone()),
368        InsertBehavior::AfterCurrent if current_index < state.windows.len() => {
369            state.windows.insert(current_index + 1, window.clone());
370        }
371        InsertBehavior::AfterCurrent | InsertBehavior::BeforeCurrent => {
372            state.windows.insert(current_index, window.clone());
373        }
374    }
375}
376
377fn is_scratchpad<H: Handle>(state: &State<H>, window: &Window<H>) -> bool {
378    state
379        .active_scratchpads
380        .iter()
381        .any(|(_, id)| id.iter().any(|id| window.pid == Some(*id)))
382}
383
384// Tries to position a window according to the requested sizes.
385// When no size was requested, defaults to `ws.center_halfed()`
386fn set_relative_floating<H: Handle>(window: &mut Window<H>, ws: &Workspace, outer: Xyhw) {
387    window.set_floating(true);
388    window.normal = ws.xyhw;
389    let xyhw = window.requested.map_or_else(
390        || ws.center_halfed(),
391        |mut requested| {
392            requested.center_relative(outer, window.border);
393            if !ws.xyhw_avoided.contains_xyhw(&requested) {
394                requested.center_relative(ws.xyhw_avoided, window.border);
395            }
396            requested
397        },
398    );
399    window.set_floating_exact(xyhw);
400}
401
402fn setup_window<H: Handle>(
403    state: &mut State<H>,
404    window: &mut Window<H>,
405    xy: (i32, i32),
406    layout: &mut String,
407    is_first: &mut bool,
408    on_same_tag: &mut bool,
409) {
410    // Identify the workspace in which to create the window.
411    let ws = state
412        .focus_manager
413        .create_follows_cursor() // If the window must be created under the cursor...
414        .then_some(
415            // ...look for the workspace with the cursor.
416            state
417                .workspaces
418                .iter()
419                .find(|ws| ws.xyhw.contains_point(xy.0, xy.1)),
420        )
421        .flatten()
422        .or(state.focus_manager.workspace(&state.workspaces)); // Else, use the workspace which has the focus.
423
424    // If no workspace was found, put the window on tag 1.
425    let Some(ws) = ws else {
426        tracing::warn!("setup_window failed to identify workspace. Falling back to tag 1");
427        window.tag = Some(1);
428        if is_scratchpad(state, window) {
429            if let Some(scratchpad_tag) = state.tags.get_hidden_by_label("NSP") {
430                window.tag(&scratchpad_tag.id);
431                window.set_floating(true);
432            }
433        }
434        return;
435    };
436
437    // Setup basic variables.
438    let for_active_workspace = |x: &Window<H>| -> bool { ws.tag == x.tag && x.is_managed() };
439    *is_first = !state.windows.iter().any(for_active_workspace);
440    // May have been set by a predefined tag.
441    if window.tag.is_none() {
442        window.tag =
443            find_terminal(state, window.pid).map_or_else(|| ws.tag, |terminal| terminal.tag);
444    }
445    *on_same_tag = ws.tag == window.tag;
446    layout.clone_from(&state.layout_manager.layout(ws.id, window.tag.unwrap()).name);
447
448    // Setup a scratchpad window.
449    if let Some((scratchpad_name, _)) = state
450        .active_scratchpads
451        .iter()
452        .find(|(_, id)| id.iter().any(|id| Some(*id) == window.pid))
453    {
454        window.set_floating(true);
455        if let Some(s) = state
456            .scratchpads
457            .iter()
458            .find(|s| *scratchpad_name == s.name)
459        {
460            let new_float_exact = s.xyhw(&ws.xyhw);
461            window.normal = ws.xyhw;
462            window.set_floating_exact(new_float_exact);
463            return;
464        }
465    }
466
467    // Setup a child window.
468    if let Some(parent) = find_transient_parent(&state.windows, window.transient) {
469        // This is currently for vlc, this probably will need to be more general if another
470        // case comes up where we don't want to move the window.
471        if window.r#type != WindowType::Utility {
472            set_relative_floating(window, ws, parent.exact_xyhw());
473            return;
474        }
475    }
476
477    // Setup window based on type.
478    match window.r#type {
479        WindowType::Normal => {
480            window.apply_margin_multiplier(ws.margin_multiplier);
481            if window.floating() {
482                set_relative_floating(window, ws, ws.xyhw_avoided);
483            }
484        }
485        WindowType::Dialog | WindowType::Splash => {
486            set_relative_floating(window, ws, ws.xyhw_avoided);
487        }
488        _ => {}
489    }
490}
491
492fn update_workspace_avoid_list<H: Handle>(state: &mut State<H>) {
493    let mut avoid = vec![];
494    state
495        .windows
496        .iter()
497        .filter(|w| w.r#type == WindowType::Dock)
498        .filter_map(|w| w.strut.map(|strut| (w.handle, strut)))
499        .for_each(|(handle, to_avoid)| {
500            tracing::trace!("AVOID STRUT:[{:?}] {:?}", handle, to_avoid);
501            avoid.push(to_avoid);
502        });
503    for ws in &mut state.workspaces {
504        let struts = avoid
505            .clone()
506            .into_iter()
507            .filter(|s| {
508                let (x, y) = s.center();
509                ws.contains_point(x, y)
510            })
511            .collect();
512        ws.avoid = struts;
513        ws.update_avoided_areas();
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520    use crate::layouts::MONOCLE;
521    use crate::models::{MockHandle, Screen};
522    use crate::Manager;
523
524    #[test]
525    fn insert_behavior_bottom_add_window_at_the_end_of_the_stack() {
526        let mut manager = Manager::new_test(vec![]);
527        manager.state.insert_behavior = InsertBehavior::Bottom;
528
529        manager.screen_create_handler(Screen::default());
530        manager.window_created_handler(
531            Window::new(WindowHandle::<MockHandle>(1), None, None),
532            -1,
533            -1,
534        );
535        manager.window_created_handler(
536            Window::new(WindowHandle::<MockHandle>(2), None, None),
537            -1,
538            -1,
539        );
540
541        let expected = vec![WindowHandle::<MockHandle>(1), WindowHandle::<MockHandle>(2)];
542
543        let actual: Vec<WindowHandle<MockHandle>> =
544            manager.state.windows.iter().map(|w| w.handle).collect();
545
546        assert_eq!(actual, expected);
547    }
548
549    #[test]
550    fn insert_behavior_top_add_window_at_the_top_of_the_stack() {
551        let mut manager = Manager::new_test(vec![]);
552        manager.state.insert_behavior = InsertBehavior::Top;
553
554        manager.screen_create_handler(Screen::default());
555        manager.window_created_handler(
556            Window::new(WindowHandle::<MockHandle>(1), None, None),
557            -1,
558            -1,
559        );
560        manager.window_created_handler(
561            Window::new(WindowHandle::<MockHandle>(2), None, None),
562            -1,
563            -1,
564        );
565
566        let expected = vec![WindowHandle::<MockHandle>(2), WindowHandle::<MockHandle>(1)];
567        let actual: Vec<WindowHandle<MockHandle>> =
568            manager.state.windows.iter().map(|w| w.handle).collect();
569
570        assert_eq!(actual, expected);
571    }
572
573    #[test]
574    fn insert_behavior_after_current_add_window_after_the_current_window() {
575        let mut manager = Manager::new_test(vec![]);
576        manager.state.insert_behavior = InsertBehavior::AfterCurrent;
577
578        manager.screen_create_handler(Screen::default());
579        manager.window_created_handler(
580            Window::new(WindowHandle::<MockHandle>(1), None, None),
581            -1,
582            -1,
583        );
584        manager.window_created_handler(
585            Window::new(WindowHandle::<MockHandle>(2), None, None),
586            -1,
587            -1,
588        );
589        manager.window_created_handler(
590            Window::new(WindowHandle::<MockHandle>(3), None, None),
591            -1,
592            -1,
593        );
594
595        let expected = vec![
596            WindowHandle::<MockHandle>(1),
597            WindowHandle::<MockHandle>(3),
598            WindowHandle::<MockHandle>(2),
599        ];
600        let actual: Vec<WindowHandle<MockHandle>> =
601            manager.state.windows.iter().map(|w| w.handle).collect();
602
603        assert_eq!(actual, expected);
604    }
605
606    #[test]
607    fn insert_behavior_before_current_add_window_before_the_current_window() {
608        let mut manager = Manager::new_test(vec![]);
609        manager.state.insert_behavior = InsertBehavior::BeforeCurrent;
610
611        manager.screen_create_handler(Screen::default());
612        manager.window_created_handler(
613            Window::new(WindowHandle::<MockHandle>(1), None, None),
614            -1,
615            -1,
616        );
617        manager.window_created_handler(
618            Window::new(WindowHandle::<MockHandle>(2), None, None),
619            -1,
620            -1,
621        );
622
623        manager.window_created_handler(
624            Window::new(WindowHandle::<MockHandle>(3), None, None),
625            -1,
626            -1,
627        );
628
629        let expected = vec![
630            WindowHandle::<MockHandle>(2),
631            WindowHandle::<MockHandle>(3),
632            WindowHandle::<MockHandle>(1),
633        ];
634        let actual: Vec<WindowHandle<MockHandle>> =
635            manager.state.windows.iter().map(|w| w.handle).collect();
636
637        assert_eq!(actual, expected);
638    }
639
640    #[test]
641    fn single_window_has_no_border() {
642        let mut manager = Manager::new_test_with_border(vec![], 1);
643        manager.screen_create_handler(Screen::default());
644
645        manager.window_created_handler(
646            Window::new(WindowHandle::<MockHandle>(1), None, None),
647            -1,
648            -1,
649        );
650
651        assert_eq!((manager.state.windows[0]).border(), 0);
652    }
653
654    #[test]
655    fn multiple_windows_have_borders() {
656        let mut manager = Manager::new_test_with_border(vec![], 1);
657        manager.screen_create_handler(Screen::default());
658
659        manager.window_created_handler(
660            Window::new(WindowHandle::<MockHandle>(1), None, None),
661            -1,
662            -1,
663        );
664        manager.window_created_handler(
665            Window::new(WindowHandle::<MockHandle>(2), None, None),
666            -1,
667            -1,
668        );
669
670        assert_eq!((manager.state.windows[0]).border(), 1);
671        assert_eq!((manager.state.windows[1]).border(), 1);
672    }
673
674    #[test]
675    fn remaining_single_window_has_no_border() {
676        let mut manager = Manager::new_test_with_border(vec![], 1);
677        manager.screen_create_handler(Screen::default());
678
679        manager.window_created_handler(
680            Window::new(WindowHandle::<MockHandle>(1), None, None),
681            -1,
682            -1,
683        );
684        manager.window_created_handler(
685            Window::new(WindowHandle::<MockHandle>(2), None, None),
686            -1,
687            -1,
688        );
689
690        manager.window_destroyed_handler(&manager.state.windows[1].handle.clone());
691
692        assert_eq!((manager.state.windows[0]).border(), 0);
693    }
694
695    #[test]
696    fn single_windows_on_different_tags_have_no_border() {
697        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
698        manager.screen_create_handler(Screen::default());
699
700        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
701        first_window.tag(&1);
702        manager.window_created_handler(first_window, -1, -1);
703
704        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
705        second_window.tag(&2);
706        manager.window_created_handler(second_window, -1, -1);
707
708        assert_eq!((manager.state.windows[0]).border(), 0);
709        assert_eq!((manager.state.windows[1]).border(), 0);
710    }
711
712    #[test]
713    fn single_window_has_no_border_and_windows_on_another_tag_have_borders() {
714        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
715        manager.screen_create_handler(Screen::default());
716
717        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
718        first_window.tag(&1);
719        manager.window_created_handler(first_window, -1, -1);
720
721        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
722        second_window.tag(&2);
723        manager.window_created_handler(second_window, -1, -1);
724
725        let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
726        third_window.tag(&2);
727        manager.window_created_handler(third_window, -1, -1);
728
729        assert_eq!((manager.state.windows[0]).border(), 0);
730        assert_eq!((manager.state.windows[1]).border(), 1);
731        assert_eq!((manager.state.windows[2]).border(), 1);
732    }
733
734    #[test]
735    fn remaining_single_window_on_another_tag_has_no_border() {
736        let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
737        manager.screen_create_handler(Screen::default());
738
739        let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
740        first_window.tag(&1);
741        manager.window_created_handler(first_window, -1, -1);
742
743        let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
744        second_window.tag(&2);
745        manager.window_created_handler(second_window, -1, -1);
746
747        let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
748        third_window.tag(&2);
749        manager.window_created_handler(third_window, -1, -1);
750
751        manager.window_destroyed_handler(&manager.state.windows[2].handle.clone());
752
753        assert_eq!((manager.state.windows[0]).border(), 0);
754        assert_eq!((manager.state.windows[1]).border(), 0);
755    }
756
757    #[test]
758    fn monocle_layout_only_has_single_windows() {
759        let mut manager = Manager::new_test_with_border(vec!["1".to_string()], 1);
760        manager.screen_create_handler(Screen::default());
761        manager.state.layout_manager.set_layout(1, 1, MONOCLE);
762        // manager.state.tags.get_mut(1).unwrap().set_layout(String::from("Monocle"));
763        manager.window_created_handler(
764            Window::new(WindowHandle::<MockHandle>(1), None, None),
765            -1,
766            -1,
767        );
768        manager.window_created_handler(
769            Window::new(WindowHandle::<MockHandle>(2), None, None),
770            -1,
771            -1,
772        );
773        assert_eq!((manager.state.windows[0]).border(), 0);
774        assert_eq!((manager.state.windows[1]).border(), 0);
775    }
776}