use super::{Manager, Window, WindowChange, WindowType, Workspace};
use crate::child_process::exec_shell;
use crate::config::{Config, InsertBehavior};
use crate::display_action::DisplayAction;
use crate::display_servers::DisplayServer;
use crate::layouts::{self, MAIN_AND_VERT_STACK};
use crate::models::{Handle, WindowHandle, WindowState, Xyhw};
use crate::state::State;
use crate::utils::helpers;
use std::env;
use std::str::FromStr;
impl<H: Handle, C: Config, SERVER: DisplayServer<H>> Manager<H, C, SERVER> {
pub fn window_created_handler(&mut self, mut window: Window<H>, x: i32, y: i32) -> bool {
if self.state.windows.iter().any(|w| w.handle == window.handle) {
return false;
}
self.config
.setup_predefined_window(&mut self.state, &mut window);
let mut is_first = false;
let mut on_same_tag = true;
let mut layout = MAIN_AND_VERT_STACK.to_string();
let respect_dialog_position = self.config.respect_dialog_position();
setup_window(
&mut self.state,
&mut window,
(x, y),
respect_dialog_position,
&mut layout,
&mut is_first,
&mut on_same_tag,
);
self.config.load_window(&mut window);
insert_window(&mut self.state, &mut window, &layout);
let follow_mouse = self.state.focus_manager.focus_new_windows
&& self.state.focus_manager.behaviour.is_sloppy()
&& self.state.focus_manager.sloppy_mouse_follows_focus
&& window.is_managed()
&& on_same_tag
&& window.allows_mouse_warp();
let act = DisplayAction::AddedWindow(window.handle, window.floating(), follow_mouse);
self.state.actions.push_back(act);
if window.tag.is_some() {
let act = DisplayAction::SetWindowTag(window.handle, window.tag);
self.state.actions.push_back(act);
}
self.state.sort_windows();
self.state.handle_single_border(self.config.border_width());
if (self.state.focus_manager.focus_new_windows || is_first) && on_same_tag {
self.state.focus_window(&window.handle);
}
if let Some(cmd) = &self.config.on_new_window_cmd() {
exec_shell(cmd, &mut self.children);
}
true
}
pub fn window_destroyed_handler(&mut self, handle: &WindowHandle<H>) -> bool {
let new_handle = if let Some(Some(last_focused_window)) = self
.state
.focus_manager
.window_history
.iter()
.find(|w| w != &&Some(*handle))
{
Some(*last_focused_window)
} else {
self.get_next_or_previous_handle(handle)
};
let (transient, floating, visible) =
match self.state.windows.iter().find(|w| &w.handle == handle) {
Some(window) => (window.transient, window.floating(), window.visible()),
None => return false,
};
self.state
.focus_manager
.tags_last_window
.retain(|_, h| h != handle);
self.state.windows.retain(|w| &w.handle != handle);
self.state.handle_single_border(self.config.border_width());
update_workspace_avoid_list(&mut self.state);
let focused = self.state.focus_manager.window_history.front();
if focused == Some(&Some(*handle)) {
if self.state.focus_manager.behaviour.is_sloppy()
&& self.state.focus_manager.sloppy_mouse_follows_focus
{
let act = DisplayAction::FocusWindowUnderCursor;
self.state.actions.push_back(act);
self.state.focus_manager.window_history.push_front(None);
} else if let Some(parent) =
find_transient_parent(&self.state.windows, transient).map(|p| p.handle)
{
self.state.focus_window(&parent);
} else if let Some(handle) = new_handle {
self.state.focus_window(&handle);
} else {
let act = DisplayAction::Unfocus(Some(*handle), floating);
self.state.actions.push_back(act);
self.state.focus_manager.window_history.push_front(None);
}
}
self.state
.focus_manager
.window_history
.retain(|w| w != &Some(*handle));
visible
}
pub fn window_changed_handler(&mut self, change: WindowChange<H>) -> bool {
let mut changed = false;
let mut fullscreen_changed = false;
let mut above_changed = false;
let mut transient_changed = false;
let strut_changed = change.strut.is_some();
let windows = self.state.windows.clone();
let respect_dialog_position = self.config.respect_dialog_position();
if let Some(window) = self
.state
.windows
.iter_mut()
.find(|w| w.handle == change.handle)
{
transient_changed = change
.transient
.as_ref()
.is_some_and(|transient| transient != &window.transient);
if let Some(states) = &change.states {
fullscreen_changed =
states.contains(&WindowState::Fullscreen) != window.is_fullscreen();
above_changed = states.contains(&WindowState::Above)
!= window.states.contains(&WindowState::Above);
}
let container = if respect_dialog_position && window.r#type.is_dialog_like() {
None
} else {
match find_transient_parent(&windows, window.transient) {
Some(parent) => Some(parent.exact_xyhw()),
None if window.r#type == WindowType::Dialog => self
.state
.workspaces
.iter()
.find(|ws| ws.tag == window.tag)
.map(|ws| ws.xyhw),
_ => None,
}
};
changed = change.update(window, container);
if window.r#type == WindowType::Dock {
update_workspace_avoid_list(&mut self.state);
}
}
if fullscreen_changed {
if let Some(windows) = self
.state
.windows
.iter()
.find(|w| w.r#type == WindowType::Dock)
{
self.display_server.update_windows(vec![windows]);
}
}
if fullscreen_changed || above_changed || transient_changed {
self.state.sort_windows();
}
if strut_changed {
self.state.update_static();
}
changed
}
pub fn get_next_or_previous_handle(
&mut self,
handle: &WindowHandle<H>,
) -> Option<WindowHandle<H>> {
let focused_workspace = self.state.focus_manager.workspace(&self.state.workspaces)?;
let on_focused_workspace = |x: &Window<H>| -> bool { focused_workspace.is_managed(x) };
let mut windows_on_workspace =
helpers::vec_extract(&mut self.state.windows, on_focused_workspace);
let is_handle = |x: &Window<H>| -> bool { &x.handle == handle };
let new_handle = helpers::relative_find(&windows_on_workspace, is_handle, 1, false)
.or_else(|| helpers::relative_find(&windows_on_workspace, is_handle, -1, false))
.map(|w| w.handle);
self.state.windows.append(&mut windows_on_workspace);
new_handle
}
}
fn find_terminal<H: Handle>(state: &State<H>, pid: Option<u32>) -> Option<&Window<H>> {
let shell_path = env::var("SHELL").ok()?;
let shell = shell_path.split('/').next_back()?;
let is_terminal = |pid: u32| -> Option<bool> {
let parent = std::fs::read(format!("/proc/{pid}/comm")).ok()?;
let parent_bytes = parent.split(|&c| c == b' ').next()?;
let parent_str = std::str::from_utf8(parent_bytes).ok()?.strip_suffix('\n')?;
Some(parent_str == shell)
};
let get_parent = |pid: u32| -> Option<u32> {
let stat = std::fs::read(format!("/proc/{pid}/stat")).ok()?;
let ppid_bytes = stat.split(|&c| c == b' ').nth(3)?;
let ppid_str = std::str::from_utf8(ppid_bytes).ok()?;
let ppid_u32 = u32::from_str(ppid_str).ok()?;
Some(ppid_u32)
};
let pid = pid?;
let shell_id = get_parent(pid)?;
if is_terminal(shell_id)? {
let terminal = get_parent(shell_id)?;
return state.windows.iter().find(|w| w.pid == Some(terminal));
}
None
}
fn find_transient_parent<H: Handle>(
windows: &[Window<H>],
transient: Option<WindowHandle<H>>,
) -> Option<&Window<H>> {
let mut transient = transient?;
loop {
transient = if let Some(found) = windows
.iter()
.find(|x| x.handle == transient)
.and_then(|x| x.transient)
{
found
} else {
return windows.iter().find(|x| x.handle == transient);
};
}
}
fn insert_window<H: Handle>(state: &mut State<H>, window: &mut Window<H>, layout: &str) {
let mut was_fullscreen = false;
if window.r#type == WindowType::Normal {
let for_active_workspace =
|x: &Window<H>| -> bool { window.tag == x.tag && x.is_managed() };
if let Some(fsw) = state
.windows
.iter_mut()
.find(|w| for_active_workspace(w) && w.is_fullscreen())
{
let act =
DisplayAction::SetState(fsw.handle, !fsw.is_fullscreen(), WindowState::Fullscreen);
state.actions.push_back(act);
was_fullscreen = true;
} else if let Some(fsw) = state
.windows
.iter_mut()
.find(|w| for_active_workspace(w) && w.is_maximized())
&& !window.floating()
{
let act =
DisplayAction::SetState(fsw.handle, !fsw.is_maximized(), WindowState::Maximized);
state.actions.push_back(act);
}
let monocle = layouts::MONOCLE;
let main_and_deck = layouts::MAIN_AND_DECK;
if layout == monocle || layout == main_and_deck {
let mut to_reorder = helpers::vec_extract(&mut state.windows, for_active_workspace);
if layout == monocle || to_reorder.is_empty() {
if was_fullscreen {
let act = DisplayAction::SetState(
window.handle,
!window.is_fullscreen(),
WindowState::Fullscreen,
);
state.actions.push_back(act);
}
to_reorder.insert(0, window.clone());
} else {
to_reorder.insert(1, window.clone());
}
state.windows.append(&mut to_reorder);
return;
}
}
if window.r#type.is_dialog_like() || window.floating() || is_scratchpad(state, window) {
state.windows.insert(0, window.clone());
return;
}
let current_index = state
.focus_manager
.window(&state.windows)
.and_then(|current| {
state
.windows
.iter()
.position(|w| w.handle == current.handle)
})
.unwrap_or(0);
match state.insert_behavior {
InsertBehavior::Top => state.windows.insert(0, window.clone()),
InsertBehavior::Bottom => state.windows.push(window.clone()),
InsertBehavior::AfterCurrent if current_index < state.windows.len() => {
state.windows.insert(current_index + 1, window.clone());
}
InsertBehavior::AfterCurrent | InsertBehavior::BeforeCurrent => {
state.windows.insert(current_index, window.clone());
}
}
}
fn is_scratchpad<H: Handle>(state: &State<H>, window: &Window<H>) -> bool {
state
.active_scratchpads
.iter()
.any(|(_, id)| id.iter().any(|id| window.pid == Some(*id)))
}
fn set_relative_floating<H: Handle>(window: &mut Window<H>, ws: &Workspace, outer: Xyhw) {
window.set_floating(true);
window.normal = ws.xyhw;
let xyhw = window.requested.map_or_else(
|| ws.center_halfed(),
|mut requested| {
requested.center_relative(outer, window.border);
if !ws.xyhw_avoided.contains_xyhw(&requested) {
requested.center_relative(ws.xyhw_avoided, window.border);
}
requested
},
);
window.set_floating_exact(xyhw);
}
fn set_requested_floating<H: Handle>(window: &mut Window<H>, ws: &Workspace) {
window.set_floating(true);
window.normal = ws.xyhw;
let xyhw = window.requested.unwrap_or_else(|| ws.center_halfed());
window.set_floating_exact(xyhw);
}
fn setup_window<H: Handle>(
state: &mut State<H>,
window: &mut Window<H>,
xy: (i32, i32),
respect_dialog_position: bool,
layout: &mut String,
is_first: &mut bool,
on_same_tag: &mut bool,
) {
let ws = state
.focus_manager
.create_follows_cursor() .then_some(
state
.workspaces
.iter()
.find(|ws| ws.xyhw.contains_point(xy.0, xy.1)),
)
.flatten()
.or(state.focus_manager.workspace(&state.workspaces));
let Some(ws) = ws else {
tracing::warn!("setup_window failed to identify workspace. Falling back to tag 1");
window.tag = Some(1);
if is_scratchpad(state, window)
&& let Some(scratchpad_tag) = state.tags.get_hidden_by_label("NSP")
{
window.tag(&scratchpad_tag.id);
window.set_floating(true);
}
return;
};
let for_active_workspace = |x: &Window<H>| -> bool { ws.tag == x.tag && x.is_managed() };
*is_first = !state.windows.iter().any(for_active_workspace);
if window.tag.is_none() {
window.tag =
find_terminal(state, window.pid).map_or_else(|| ws.tag, |terminal| terminal.tag);
}
*on_same_tag = ws.tag == window.tag;
layout.clone_from(&state.layout_manager.layout(ws.id, window.tag.unwrap()).name);
if let Some((scratchpad_name, _)) = state
.active_scratchpads
.iter()
.find(|(_, id)| id.iter().any(|id| Some(*id) == window.pid))
{
window.set_floating(true);
if let Some(s) = state
.scratchpads
.iter()
.find(|s| *scratchpad_name == s.name)
{
let new_float_exact = s.xyhw(&ws.xyhw);
window.normal = ws.xyhw;
window.set_floating_exact(new_float_exact);
return;
}
}
if respect_dialog_position && window.r#type.is_dialog_like() {
set_requested_floating(window, ws);
return;
}
if let Some(parent) = find_transient_parent(&state.windows, window.transient) {
if window.r#type != WindowType::Utility {
set_relative_floating(window, ws, parent.exact_xyhw());
return;
}
}
match window.r#type {
WindowType::Normal => {
window.apply_margin_multiplier(ws.margin_multiplier);
if window.floating() {
set_relative_floating(window, ws, ws.xyhw_avoided);
}
}
WindowType::Dialog | WindowType::Splash => {
set_relative_floating(window, ws, ws.xyhw_avoided);
}
_ => {}
}
}
fn update_workspace_avoid_list<H: Handle>(state: &mut State<H>) {
let mut avoid = vec![];
state
.windows
.iter()
.filter(|w| w.r#type == WindowType::Dock)
.filter_map(|w| w.strut.map(|strut| (w.handle, strut)))
.for_each(|(handle, to_avoid)| {
tracing::trace!("AVOID STRUT:[{:?}] {:?}", handle, to_avoid);
avoid.push(to_avoid);
});
for ws in &mut state.workspaces {
let struts = avoid
.clone()
.into_iter()
.filter(|s| {
let (x, y) = s.center();
ws.contains_point(x, y)
})
.collect();
ws.avoid = struts;
ws.update_avoided_areas();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Manager;
use crate::layouts::MONOCLE;
use crate::models::{FocusBehaviour, MockHandle, Screen, XyhwBuilder, XyhwChange};
fn last_window_order(state: &State<MockHandle>) -> Vec<WindowHandle<MockHandle>> {
state
.actions
.iter()
.rev()
.find_map(|action| match action {
DisplayAction::SetWindowOrder(order) => Some(order.clone()),
_ => None,
})
.expect("expected a window order action")
}
fn follows_mouse_on_creation(
window: Window<MockHandle>,
focus_new_windows: bool,
focus_behaviour: FocusBehaviour,
sloppy_mouse_follows_focus: bool,
) -> bool {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.screen_create_handler(Screen::default());
manager.state.actions.clear();
manager.state.focus_manager.focus_new_windows = focus_new_windows;
manager.state.focus_manager.behaviour = focus_behaviour;
manager.state.focus_manager.sloppy_mouse_follows_focus = sloppy_mouse_follows_focus;
manager.window_created_handler(window, -1, -1);
manager
.state
.actions
.iter()
.find_map(|action| match action {
DisplayAction::AddedWindow(_, _, follow_mouse) => Some(*follow_mouse),
_ => None,
})
.expect("expected an AddedWindow action")
}
#[test]
fn new_window_mouse_warp_uses_type_default_and_window_override() {
let cases = [
(1, WindowType::Normal, None, true),
(2, WindowType::Utility, None, false),
(3, WindowType::Normal, Some(true), false),
(4, WindowType::Utility, Some(false), true),
];
for (handle, window_type, disable_mouse_grab, expected) in cases {
let mut window = Window::new(WindowHandle(handle), None, None);
window.r#type = window_type;
window.set_disable_mouse_grab(disable_mouse_grab);
assert_eq!(
follows_mouse_on_creation(window, true, FocusBehaviour::Sloppy, true),
expected,
"unexpected follow_mouse for disable_mouse_grab={disable_mouse_grab:?}",
);
}
}
fn dialog_like_types() -> [WindowType; 10] {
[
WindowType::Dialog,
WindowType::Splash,
WindowType::Utility,
WindowType::Menu,
WindowType::DropdownMenu,
WindowType::PopupMenu,
WindowType::Tooltip,
WindowType::Notification,
WindowType::Combo,
WindowType::Dnd,
]
}
#[test]
fn dialog_like_windows_keep_their_requested_geometry_when_enabled() {
let requested: Xyhw = XyhwBuilder {
x: 120,
y: 80,
w: 240,
h: 160,
..XyhwBuilder::default()
}
.into();
for (handle, window_type) in dialog_like_types().into_iter().enumerate() {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.config.respect_dialog_position = true;
manager.screen_create_handler(Screen::default());
let handle = WindowHandle::<MockHandle>(handle as i32 + 1);
let mut window = Window::new(handle, None, None);
window.r#type = window_type;
window.requested = Some(requested);
manager.window_created_handler(window, -1, -1);
let actual = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the dialog-like window")
.exact_xyhw();
assert_eq!(actual, requested);
}
}
#[test]
fn dialog_windows_center_by_default_but_keep_their_requested_size() {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.screen_create_handler(Screen::default());
let requested: Xyhw = XyhwBuilder {
x: 120,
y: 80,
w: 240,
h: 160,
..XyhwBuilder::default()
}
.into();
let handle = WindowHandle::<MockHandle>(1);
let mut window = Window::new(handle, None, None);
window.r#type = WindowType::Dialog;
window.requested = Some(requested);
let mut expected = requested;
expected.center_relative(manager.state.workspaces[0].xyhw_avoided, window.border);
manager.window_created_handler(window, -1, -1);
let actual = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the dialog window")
.exact_xyhw();
assert_eq!(actual, expected);
assert_eq!((actual.w(), actual.h()), (requested.w(), requested.h()));
assert_ne!((actual.x(), actual.y()), (requested.x(), requested.y()));
}
#[test]
fn mouse_warp_permission_does_not_bypass_global_focus_settings() {
let cases = [
(false, FocusBehaviour::Sloppy, true),
(true, FocusBehaviour::ClickTo, true),
(true, FocusBehaviour::Sloppy, false),
];
for (focus_new_windows, focus_behaviour, sloppy_mouse_follows_focus) in cases {
let window = Window::new(WindowHandle(1), None, None);
assert!(!follows_mouse_on_creation(
window,
focus_new_windows,
focus_behaviour,
sloppy_mouse_follows_focus,
));
}
}
#[test]
fn dialog_like_windows_without_geometry_keep_the_centered_fallback() {
for (handle, window_type) in dialog_like_types().into_iter().enumerate() {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.config.respect_dialog_position = true;
manager.screen_create_handler(Screen::default());
let expected = manager.state.workspaces[0].center_halfed();
let handle = WindowHandle::<MockHandle>(handle as i32 + 1);
let mut window = Window::new(handle, None, None);
window.r#type = window_type;
manager.window_created_handler(window, -1, -1);
let actual = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the dialog-like window")
.exact_xyhw();
assert_eq!(actual, expected);
}
}
#[test]
fn dialog_like_configure_changes_only_update_requested_fields_when_enabled() {
for (index, window_type) in dialog_like_types().into_iter().enumerate() {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.config.respect_dialog_position = true;
manager.screen_create_handler(Screen::default());
let parent_handle = WindowHandle::<MockHandle>(1);
manager.window_created_handler(Window::new(parent_handle, None, None), -1, -1);
let requested: Xyhw = XyhwBuilder {
x: 120,
y: 80,
w: 240,
h: 160,
..XyhwBuilder::default()
}
.into();
let handle = WindowHandle::<MockHandle>(index as i32 + 2);
let mut window = Window::new(handle, None, None);
window.r#type = window_type;
window.transient = Some(parent_handle);
window.border = 0;
window.requested = Some(requested);
manager.window_created_handler(window, -1, -1);
let mut resize = WindowChange::new(handle);
resize.floating = Some(XyhwChange {
w: Some(300),
h: Some(190),
..XyhwChange::default()
});
manager.window_changed_handler(resize);
let resized = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the resized dialog-like window")
.exact_xyhw();
assert_eq!((resized.x(), resized.y()), (120, 80));
assert_eq!((resized.w(), resized.h()), (300, 190));
let mut movement = WindowChange::new(handle);
movement.floating = Some(XyhwChange {
x: Some(-40),
y: Some(210),
..XyhwChange::default()
});
manager.window_changed_handler(movement);
let moved = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the moved dialog-like window")
.exact_xyhw();
assert_eq!((moved.x(), moved.y()), (-40, 210));
assert_eq!((moved.w(), moved.h()), (300, 190));
}
}
#[test]
fn dialog_configure_centers_by_default_but_keeps_the_requested_size() {
let mut manager = Manager::new_test(vec!["1".to_owned()]);
manager.screen_create_handler(Screen::default());
let parent_handle = WindowHandle::<MockHandle>(1);
manager.window_created_handler(Window::new(parent_handle, None, None), -1, -1);
let parent = manager
.state
.windows
.iter()
.find(|window| window.handle == parent_handle)
.expect("expected the parent window")
.exact_xyhw();
let handle = WindowHandle::<MockHandle>(2);
let mut window = Window::new(handle, None, None);
window.r#type = WindowType::Dialog;
window.transient = Some(parent_handle);
window.border = 0;
window.requested = Some(
XyhwBuilder {
x: 120,
y: 80,
w: 240,
h: 160,
..XyhwBuilder::default()
}
.into(),
);
manager.window_created_handler(window, -1, -1);
let mut requested: Xyhw = XyhwBuilder {
x: -40,
y: 210,
w: 300,
h: 190,
..XyhwBuilder::default()
}
.into();
let mut change = WindowChange::new(handle);
change.floating = Some(XyhwChange {
x: Some(requested.x()),
y: Some(requested.y()),
w: Some(requested.w()),
h: Some(requested.h()),
..XyhwChange::default()
});
manager.window_changed_handler(change);
requested.center_relative(parent, 0);
let actual = manager
.state
.windows
.iter()
.find(|window| window.handle == handle)
.expect("expected the dialog window")
.exact_xyhw();
assert_eq!(actual, requested);
assert_eq!((actual.w(), actual.h()), (300, 190));
assert_ne!((actual.x(), actual.y()), (-40, 210));
}
#[test]
fn dialog_is_visible_and_stacked_above_fullscreen_without_focus() {
let mut manager = Manager::new_test(vec!["1".to_string()]);
manager.screen_create_handler(Screen::default());
let mut fullscreen = Window::new(WindowHandle::<MockHandle>(1), None, None);
fullscreen.states.push(WindowState::Fullscreen);
manager.window_created_handler(fullscreen, -1, -1);
let mut dialog = Window::new(WindowHandle::<MockHandle>(2), None, None);
dialog.r#type = WindowType::Dialog;
manager.window_created_handler(dialog, -1, -1);
assert_eq!(
manager
.state
.focus_manager
.window(&manager.state.windows)
.map(|window| window.handle),
Some(WindowHandle(1))
);
assert_eq!(
last_window_order(&manager.state),
vec![WindowHandle(2), WindowHandle(1)]
);
manager.update_windows();
assert!(
manager
.state
.windows
.iter()
.find(|window| window.handle == WindowHandle(2))
.is_some_and(Window::visible)
);
}
#[test]
fn late_transient_hint_restacks_dialog_above_fullscreen_parent() {
let mut manager = Manager::new_test(vec!["1".to_string()]);
manager.screen_create_handler(Screen::default());
let mut fullscreen = Window::new(WindowHandle::<MockHandle>(1), None, None);
fullscreen.states.push(WindowState::Fullscreen);
manager.window_created_handler(fullscreen, -1, -1);
let mut dialog = Window::new(WindowHandle::<MockHandle>(2), None, None);
dialog.r#type = WindowType::Dialog;
manager.window_created_handler(dialog, -1, -1);
manager.state.actions.clear();
let mut change = WindowChange::new(WindowHandle(2));
change.transient = Some(Some(WindowHandle(1)));
assert!(manager.window_changed_handler(change));
assert_eq!(
last_window_order(&manager.state),
vec![WindowHandle(2), WindowHandle(1)]
);
manager.update_windows();
assert!(
manager
.state
.windows
.iter()
.find(|window| window.handle == WindowHandle(2))
.is_some_and(Window::visible)
);
}
#[test]
fn insert_behavior_bottom_add_window_at_the_end_of_the_stack() {
let mut manager = Manager::new_test(vec![]);
manager.state.insert_behavior = InsertBehavior::Bottom;
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
let expected = vec![WindowHandle::<MockHandle>(1), WindowHandle::<MockHandle>(2)];
let actual: Vec<WindowHandle<MockHandle>> =
manager.state.windows.iter().map(|w| w.handle).collect();
assert_eq!(actual, expected);
}
#[test]
fn insert_behavior_top_add_window_at_the_top_of_the_stack() {
let mut manager = Manager::new_test(vec![]);
manager.state.insert_behavior = InsertBehavior::Top;
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
let expected = vec![WindowHandle::<MockHandle>(2), WindowHandle::<MockHandle>(1)];
let actual: Vec<WindowHandle<MockHandle>> =
manager.state.windows.iter().map(|w| w.handle).collect();
assert_eq!(actual, expected);
}
#[test]
fn insert_behavior_after_current_add_window_after_the_current_window() {
let mut manager = Manager::new_test(vec![]);
manager.state.insert_behavior = InsertBehavior::AfterCurrent;
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(3), None, None),
-1,
-1,
);
let expected = vec![
WindowHandle::<MockHandle>(1),
WindowHandle::<MockHandle>(3),
WindowHandle::<MockHandle>(2),
];
let actual: Vec<WindowHandle<MockHandle>> =
manager.state.windows.iter().map(|w| w.handle).collect();
assert_eq!(actual, expected);
}
#[test]
fn insert_behavior_before_current_add_window_before_the_current_window() {
let mut manager = Manager::new_test(vec![]);
manager.state.insert_behavior = InsertBehavior::BeforeCurrent;
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(3), None, None),
-1,
-1,
);
let expected = vec![
WindowHandle::<MockHandle>(2),
WindowHandle::<MockHandle>(3),
WindowHandle::<MockHandle>(1),
];
let actual: Vec<WindowHandle<MockHandle>> =
manager.state.windows.iter().map(|w| w.handle).collect();
assert_eq!(actual, expected);
}
#[test]
fn single_window_has_no_border() {
let mut manager = Manager::new_test_with_border(vec![], 1);
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
assert_eq!((manager.state.windows[0]).border(), 0);
}
#[test]
fn multiple_windows_have_borders() {
let mut manager = Manager::new_test_with_border(vec![], 1);
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
assert_eq!((manager.state.windows[0]).border(), 1);
assert_eq!((manager.state.windows[1]).border(), 1);
}
#[test]
fn remaining_single_window_has_no_border() {
let mut manager = Manager::new_test_with_border(vec![], 1);
manager.screen_create_handler(Screen::default());
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
manager.window_destroyed_handler(&manager.state.windows[1].handle.clone());
assert_eq!((manager.state.windows[0]).border(), 0);
}
#[test]
fn single_windows_on_different_tags_have_no_border() {
let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
manager.screen_create_handler(Screen::default());
let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
first_window.tag(&1);
manager.window_created_handler(first_window, -1, -1);
let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
second_window.tag(&2);
manager.window_created_handler(second_window, -1, -1);
assert_eq!((manager.state.windows[0]).border(), 0);
assert_eq!((manager.state.windows[1]).border(), 0);
}
#[test]
fn single_window_has_no_border_and_windows_on_another_tag_have_borders() {
let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
manager.screen_create_handler(Screen::default());
let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
first_window.tag(&1);
manager.window_created_handler(first_window, -1, -1);
let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
second_window.tag(&2);
manager.window_created_handler(second_window, -1, -1);
let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
third_window.tag(&2);
manager.window_created_handler(third_window, -1, -1);
assert_eq!((manager.state.windows[0]).border(), 0);
assert_eq!((manager.state.windows[1]).border(), 1);
assert_eq!((manager.state.windows[2]).border(), 1);
}
#[test]
fn remaining_single_window_on_another_tag_has_no_border() {
let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
manager.screen_create_handler(Screen::default());
let mut first_window = Window::new(WindowHandle::<MockHandle>(1), None, None);
first_window.tag(&1);
manager.window_created_handler(first_window, -1, -1);
let mut second_window = Window::new(WindowHandle::<MockHandle>(2), None, None);
second_window.tag(&2);
manager.window_created_handler(second_window, -1, -1);
let mut third_window = Window::new(WindowHandle::<MockHandle>(3), None, None);
third_window.tag(&2);
manager.window_created_handler(third_window, -1, -1);
manager.window_destroyed_handler(&manager.state.windows[2].handle.clone());
assert_eq!((manager.state.windows[0]).border(), 0);
assert_eq!((manager.state.windows[1]).border(), 0);
}
#[test]
fn monocle_layout_only_has_single_windows() {
let mut manager = Manager::new_test_with_border(vec!["1".to_string()], 1);
manager.screen_create_handler(Screen::default());
manager.state.layout_manager.set_layout(1, 1, MONOCLE);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(1), None, None),
-1,
-1,
);
manager.window_created_handler(
Window::new(WindowHandle::<MockHandle>(2), None, None),
-1,
-1,
);
assert_eq!((manager.state.windows[0]).border(), 0);
assert_eq!((manager.state.windows[1]).border(), 0);
}
}