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 pub fn window_created_handler(&mut self, mut window: Window<H>, x: i32, y: i32) -> bool {
19 if self.state.windows.iter().any(|w| w.handle == window.handle) {
21 return false;
22 }
23
24 self.config
26 .setup_predefined_window(&mut self.state, &mut window);
27
28 let mut is_first = false;
30 let mut on_same_tag = true;
31 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 act = DisplayAction::AddedWindow(window.handle, window.floating(), follow_mouse);
55 self.state.actions.push_back(act);
56
57 if window.tag.is_some() {
59 let act = DisplayAction::SetWindowTag(window.handle, window.tag);
60 self.state.actions.push_back(act);
61 }
62
63 self.state.sort_windows();
65
66 self.state.handle_single_border(self.config.border_width());
68
69 if (self.state.focus_manager.focus_new_windows || is_first) && on_same_tag {
73 self.state.focus_window(&window.handle);
74 }
75
76 if let Some(cmd) = &self.config.on_new_window_cmd() {
78 exec_shell(cmd, &mut self.children);
79 }
80
81 true
82 }
83
84 pub fn window_destroyed_handler(&mut self, handle: &WindowHandle<H>) -> bool {
89 let new_handle = if let Some(Some(last_focused_window)) = self
91 .state
92 .focus_manager
93 .window_history
94 .iter()
95 .find(|w| w != &&Some(*handle))
97 {
98 Some(*last_focused_window)
99 } else {
100 self.get_next_or_previous_handle(handle)
101 };
102 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 update_workspace_avoid_list(&mut self.state);
118
119 let focused = self.state.focus_manager.window_history.front();
120 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 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 self.state
145 .focus_manager
146 .window_history
147 .retain(|w| w != &Some(*handle));
148
149 visible
151 }
152
153 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 }
202 }
203 if fullscreen_changed {
204 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 self.state.sort_windows();
219 }
220 if strut_changed {
221 self.state.update_static();
222 }
223 changed
224 }
225
226 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
247fn find_terminal<H: Handle>(state: &State<H>, pid: Option<u32>) -> Option<&Window<H>> {
250 let shell_path = env::var("SHELL").ok()?;
252 let shell = shell_path.split('/').next_back()?;
254 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 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 let monocle = layouts::MONOCLE;
326 let main_and_deck = layouts::MAIN_AND_DECK;
327 if layout == monocle || layout == main_and_deck {
328 let mut to_reorder = helpers::vec_extract(&mut state.windows, for_active_workspace);
330 if layout == monocle || to_reorder.is_empty() {
331 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 to_reorder.insert(0, window.clone());
343 } else {
344 to_reorder.insert(1, window.clone());
346 }
347 state.windows.append(&mut to_reorder);
348 return;
349 }
350 }
351
352 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 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
389fn 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
407fn 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 let ws = state
427 .focus_manager
428 .create_follows_cursor() .then_some(
430 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)); 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 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 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 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 if respect_dialog_position && window.r#type.is_dialog_like() {
485 set_requested_floating(window, ws);
486 return;
487 }
488
489 if let Some(parent) = find_transient_parent(&state.windows, window.transient) {
491 if window.r#type != WindowType::Utility {
494 set_relative_floating(window, ws, parent.exact_xyhw());
495 return;
496 }
497 }
498
499 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.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}