Files
komorebi/komorebi/src/window_manager.rs
2026-05-01 15:17:33 -07:00

5943 lines
211 KiB
Rust

use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::env::temp_dir;
use std::fs::OpenOptions;
use std::io::ErrorKind;
use std::net::Shutdown;
use std::num::NonZeroUsize;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use color_eyre::eyre;
use color_eyre::eyre::OptionExt;
use color_eyre::eyre::bail;
use crossbeam_channel::Receiver;
use hotwatch::EventKind;
use hotwatch::Hotwatch;
use hotwatch::notify::ErrorKind as NotifyErrorKind;
use parking_lot::Mutex;
use uds_windows::UnixListener;
use uds_windows::UnixStream;
use crate::animation::ANIMATION_ENABLED_GLOBAL;
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
use crate::animation::AnimationEngine;
use crate::core::Arrangement;
use crate::core::Axis;
use crate::core::BorderImplementation;
use crate::core::CustomLayout;
use crate::core::CycleDirection;
use crate::core::DefaultLayout;
use crate::core::FocusFollowsMouseImplementation;
use crate::core::Layout;
use crate::core::MoveBehaviour;
use crate::core::OperationBehaviour;
use crate::core::OperationDirection;
use crate::core::Rect;
use crate::core::Sizing;
use crate::core::WindowContainerBehaviour;
use crate::core::WindowManagementBehaviour;
use crate::core::config_generation::MatchingRule;
use crate::CrossBoundaryBehaviour;
use crate::DATA_DIR;
use crate::HOME_DIR;
use crate::NO_TITLEBAR;
use crate::REGEX_IDENTIFIERS;
use crate::SUBSCRIPTION_SOCKETS;
use crate::WORKSPACE_MATCHING_RULES;
use crate::border_manager;
use crate::border_manager::BORDER_OFFSET;
use crate::border_manager::BORDER_WIDTH;
use crate::container::Container;
use crate::current_virtual_desktop;
use crate::load_configuration;
use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::should_act;
use crate::should_act_individual;
use crate::state::State;
use crate::static_config::StaticConfig;
use crate::transparency_manager;
use crate::window::Window;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent_listener;
use crate::workspace::Workspace;
use crate::workspace::WorkspaceLayer;
#[derive(Debug)]
pub struct WindowManager {
pub monitors: Ring<Monitor>,
pub monitor_usr_idx_map: HashMap<usize, usize>,
pub incoming_events: Receiver<WindowManagerEvent>,
pub command_listener: UnixListener,
pub is_paused: bool,
pub work_area_offset: Option<Rect>,
pub resize_delta: i32,
pub window_management_behaviour: WindowManagementBehaviour,
pub cross_monitor_move_behaviour: MoveBehaviour,
pub cross_boundary_behaviour: CrossBoundaryBehaviour,
pub unmanaged_window_operation_behaviour: OperationBehaviour,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub mouse_follows_focus: bool,
pub hotwatch: Hotwatch,
pub virtual_desktop_id: Option<Vec<u8>>,
pub has_pending_raise_op: bool,
pub pending_move_op: Arc<Option<(usize, usize, isize)>>,
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
pub uncloack_to_ignore: usize,
/// Maps each known window hwnd to the (monitor, workspace) index pair managing it
pub known_hwnds: HashMap<isize, (usize, usize)>,
}
impl AsRef<Self> for WindowManager {
fn as_ref(&self) -> &Self {
self
}
}
impl_ring_elements!(WindowManager, Monitor);
#[derive(Debug, Clone, Copy)]
struct EnforceWorkspaceRuleOp {
hwnd: isize,
origin_monitor_idx: usize,
origin_workspace_idx: usize,
target_monitor_idx: usize,
target_workspace_idx: usize,
floating: bool,
}
impl EnforceWorkspaceRuleOp {
const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx
}
const fn is_target(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
self.target_monitor_idx == monitor_idx && self.target_workspace_idx == workspace_idx
}
const fn is_enforced(&self) -> bool {
(self.origin_monitor_idx == self.target_monitor_idx)
&& (self.origin_workspace_idx == self.target_workspace_idx)
}
}
impl WindowManager {
#[tracing::instrument]
pub fn new(
incoming: Receiver<WindowManagerEvent>,
custom_socket_path: Option<PathBuf>,
) -> eyre::Result<Self> {
let socket = custom_socket_path.unwrap_or_else(|| DATA_DIR.join("komorebi.sock"));
match std::fs::remove_file(&socket) {
Ok(()) => {}
Err(error) => match error.kind() {
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
ErrorKind::NotFound => {}
_ => {
return Err(error.into());
}
},
};
let listener = UnixListener::bind(&socket)?;
Ok(Self {
monitors: Ring::default(),
monitor_usr_idx_map: HashMap::new(),
incoming_events: incoming,
command_listener: listener,
is_paused: false,
virtual_desktop_id: current_virtual_desktop(),
work_area_offset: None,
window_management_behaviour: WindowManagementBehaviour::default(),
cross_monitor_move_behaviour: MoveBehaviour::Swap,
cross_boundary_behaviour: CrossBoundaryBehaviour::Monitor,
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
resize_delta: 50,
focus_follows_mouse: None,
mouse_follows_focus: true,
hotwatch: Hotwatch::new()?,
has_pending_raise_op: false,
pending_move_op: Arc::new(None),
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
uncloack_to_ignore: 0,
known_hwnds: HashMap::new(),
})
}
#[tracing::instrument(skip(self))]
pub fn init(&mut self) -> eyre::Result<()> {
tracing::info!("initialising");
WindowsApi::load_monitor_information(self)?;
WindowsApi::load_workspace_information(&mut self.monitors)
}
#[tracing::instrument(skip(self, state))]
pub fn apply_state(&mut self, state: State) {
let mut can_apply = true;
let state_monitors_len = state.monitors.elements().len();
let current_monitors_len = self.monitors.elements().len();
if state_monitors_len != current_monitors_len {
tracing::warn!(
"cannot apply state from {}; state file has {state_monitors_len} monitors, but only {current_monitors_len} are currently connected",
temp_dir().join("komorebi.state.json").to_string_lossy()
);
return;
}
for monitor in state.monitors.elements() {
for workspace in monitor.workspaces() {
for container in workspace.containers() {
for window in container.windows() {
if window.exe().is_err() {
can_apply = false;
break;
}
}
}
if let Some(window) = workspace.maximized_window
&& window.exe().is_err()
{
can_apply = false;
break;
}
if let Some(container) = &workspace.monocle_container {
for window in container.windows() {
if window.exe().is_err() {
can_apply = false;
break;
}
}
}
for window in workspace.floating_windows() {
if window.exe().is_err() {
can_apply = false;
break;
}
}
}
}
if can_apply {
tracing::info!(
"applying state from {}",
temp_dir().join("komorebi.state.json").to_string_lossy()
);
let offset = self.work_area_offset;
let mouse_follows_focus = self.mouse_follows_focus;
for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {
let mut focused_workspace = 0;
if let Some(state_monitor) = state.monitors.elements().get(monitor_idx) {
monitor
.workspaces_mut()
.resize(state_monitor.workspaces().len(), Workspace::default());
for (workspace_idx, workspace) in
monitor.workspaces_mut().iter_mut().enumerate()
{
if let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
{
// to make sure padding and layout_options changes get applied for users after a quick restart
let container_padding = workspace.container_padding;
let workspace_padding = workspace.workspace_padding;
let layout_options = workspace.layout_options;
*workspace = state_workspace.clone();
workspace.container_padding = container_padding;
workspace.workspace_padding = workspace_padding;
workspace.layout_options = layout_options;
if state_monitor.focused_workspace_idx() == workspace_idx {
focused_workspace = workspace_idx;
}
}
}
}
if let Err(error) = monitor.focus_workspace(focused_workspace) {
tracing::warn!(
"cannot focus workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
if let Err(error) = monitor.load_focused_workspace(mouse_follows_focus) {
tracing::warn!(
"cannot load focused workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
if let Err(error) = monitor.update_focused_workspace(offset) {
tracing::warn!(
"cannot update workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
}
let focused_monitor_idx = state.monitors.focused_idx();
let focused_workspace_idx = state
.monitors
.elements()
.get(focused_monitor_idx)
.map(|m| m.focused_workspace_idx())
.unwrap_or_default();
if let Err(error) = self.focus_monitor(focused_monitor_idx) {
tracing::warn!(
"cannot focus monitor '{focused_monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
if let Err(error) = self.focus_workspace(focused_workspace_idx) {
tracing::warn!(
"cannot focus workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
if let Err(error) = self.update_focused_workspace(true, true) {
tracing::warn!(
"cannot update focused workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}",
temp_dir().join("komorebi.state.json").to_string_lossy(),
error,
);
}
} else {
tracing::warn!(
"cannot apply state from {}; some windows referenced in the state file no longer exist",
temp_dir().join("komorebi.state.json").to_string_lossy()
);
}
}
#[tracing::instrument]
pub fn reload_configuration() {
tracing::info!("reloading configuration");
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
}
#[tracing::instrument(skip(self))]
pub fn reload_static_configuration(&mut self, pathbuf: &PathBuf) -> eyre::Result<()> {
tracing::info!("reloading static configuration");
StaticConfig::reload(pathbuf, self)
}
pub fn window_management_behaviour(
&self,
monitor_idx: usize,
workspace_idx: usize,
) -> WindowManagementBehaviour {
if let Some(monitor) = self.monitors().get(monitor_idx)
&& let Some(workspace) = monitor.workspaces().get(workspace_idx)
{
let current_behaviour = if let Some(behaviour) = workspace.window_container_behaviour {
if workspace.containers().is_empty()
&& matches!(behaviour, WindowContainerBehaviour::Append)
{
// You can't append to an empty workspace
WindowContainerBehaviour::Create
} else {
behaviour
}
} else if workspace.containers().is_empty()
&& matches!(
self.window_management_behaviour.current_behaviour,
WindowContainerBehaviour::Append
)
{
// You can't append to an empty workspace
WindowContainerBehaviour::Create
} else {
self.window_management_behaviour.current_behaviour
};
let float_override = if let Some(float_override) = workspace.float_override {
float_override
} else {
self.window_management_behaviour.float_override
};
let floating_layer_behaviour =
if let Some(behaviour) = workspace.floating_layer_behaviour {
behaviour
} else {
monitor
.floating_layer_behaviour
.unwrap_or(self.window_management_behaviour.floating_layer_behaviour)
};
// If the workspace layer is `Floating` and the floating layer behaviour should
// float then change floating_layer_override to true so that new windows spawn
// as floating
let floating_layer_override = matches!(workspace.layer, WorkspaceLayer::Floating)
&& floating_layer_behaviour.should_float();
return WindowManagementBehaviour {
current_behaviour,
float_override,
floating_layer_override,
floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
float_override_placement: self.window_management_behaviour.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
};
}
WindowManagementBehaviour {
current_behaviour: WindowContainerBehaviour::Create,
float_override: self.window_management_behaviour.float_override,
floating_layer_override: self.window_management_behaviour.floating_layer_override,
floating_layer_behaviour: self.window_management_behaviour.floating_layer_behaviour,
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
float_override_placement: self.window_management_behaviour.float_override_placement,
float_rule_placement: self.window_management_behaviour.float_rule_placement,
}
}
#[tracing::instrument(skip(self))]
pub fn watch_configuration(&mut self, enable: bool) -> eyre::Result<()> {
let config_pwsh = HOME_DIR.join("komorebi.ps1");
let config_ahk = HOME_DIR.join("komorebi.ahk");
if config_pwsh.exists() {
self.configure_watcher(enable, config_pwsh)?;
} else if config_ahk.exists() {
self.configure_watcher(enable, config_ahk)?;
}
Ok(())
}
fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> eyre::Result<()> {
if enable {
tracing::info!("watching configuration for changes: {}", config.display());
// Always make absolutely sure that there isn't an already existing watch, because
// hotwatch allows multiple watches to be registered for the same path
match self.hotwatch.unwatch(&config) {
Ok(()) => {}
Err(error) => match error {
hotwatch::Error::Notify(ref notify_error) => match notify_error.kind {
NotifyErrorKind::WatchNotFound => {}
_ => return Err(error.into()),
},
error @ hotwatch::Error::Io(_) => return Err(error.into()),
},
}
self.hotwatch.watch(config, |event| match event.kind {
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
// a NoticeRemove, presumably because of the use of swap files?
EventKind::Modify(_) | EventKind::Remove(_) => {
std::thread::spawn(|| {
load_configuration().expect("could not load configuration");
});
}
_ => {}
})?;
} else {
tracing::info!(
"no longer watching configuration for changes: {}",
config.display()
);
self.hotwatch.unwatch(config)?;
};
Ok(())
}
pub fn monitor_idx_in_direction(&self, direction: OperationDirection) -> Option<usize> {
let current_monitor_size = self.focused_monitor_size().ok()?;
for (idx, monitor) in self.monitors.elements().iter().enumerate() {
match direction {
OperationDirection::Left => {
if monitor.size.left + monitor.size.right == current_monitor_size.left {
return Option::from(idx);
}
}
OperationDirection::Right => {
if current_monitor_size.right + current_monitor_size.left == monitor.size.left {
return Option::from(idx);
}
}
OperationDirection::Up => {
if monitor.size.top + monitor.size.bottom == current_monitor_size.top {
return Option::from(idx);
}
}
OperationDirection::Down => {
if current_monitor_size.top + current_monitor_size.bottom == monitor.size.top {
return Option::from(idx);
}
}
}
}
None
}
/// Calculates the direction of a move across monitors given a specific monitor index
pub fn direction_from_monitor_idx(
&self,
target_monitor_idx: usize,
) -> Option<OperationDirection> {
let current_monitor_idx = self.focused_monitor_idx();
if current_monitor_idx == target_monitor_idx {
return None;
}
let current_monitor_size = self.focused_monitor_size().ok()?;
let target_monitor_size = self.monitors().get(target_monitor_idx)?.size;
if target_monitor_size.left + target_monitor_size.right == current_monitor_size.left {
return Some(OperationDirection::Left);
}
if current_monitor_size.right + current_monitor_size.left == target_monitor_size.left {
return Some(OperationDirection::Right);
}
if target_monitor_size.top + target_monitor_size.bottom == current_monitor_size.top {
return Some(OperationDirection::Up);
}
if current_monitor_size.top + current_monitor_size.bottom == target_monitor_size.top {
return Some(OperationDirection::Down);
}
None
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(self), level = "debug")]
fn add_window_handle_to_move_based_on_workspace_rule(
&self,
window_title: &String,
hwnd: isize,
origin_monitor_idx: usize,
origin_workspace_idx: usize,
target_monitor_idx: usize,
target_workspace_idx: usize,
floating: bool,
to_move: &mut Vec<EnforceWorkspaceRuleOp>,
) {
tracing::trace!(
"{} should be on monitor {}, workspace {}",
window_title,
target_monitor_idx,
target_workspace_idx
);
// Create an operation outline and save it for later in the fn
to_move.push(EnforceWorkspaceRuleOp {
hwnd,
origin_monitor_idx,
origin_workspace_idx,
target_monitor_idx,
target_workspace_idx,
floating,
});
}
#[tracing::instrument(skip(self), level = "debug")]
pub fn enforce_workspace_rules(&mut self) -> eyre::Result<()> {
let mut to_move = vec![];
let focused_monitor_idx = self.focused_monitor_idx();
let focused_workspace_idx = self
.monitors()
.get(focused_monitor_idx)
.ok_or_eyre("there is no monitor with that index")?
.focused_workspace_idx();
// scope mutex locks to avoid deadlock if should_update_focused_workspace evaluates to true
// at the end of this function
{
let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
// Go through all the monitors and workspaces
for (i, monitor) in self.monitors().iter().enumerate() {
for (j, workspace) in monitor.workspaces().iter().enumerate() {
// And all the visible windows (at the top of a container)
for window in workspace.visible_windows().into_iter().flatten() {
let mut already_moved_window_handles =
self.already_moved_window_handles.lock();
if let (Ok(exe_name), Ok(title), Ok(class), Ok(path)) =
(window.exe(), window.title(), window.class(), window.path())
{
for rule in &*workspace_matching_rules {
let matched = match &rule.matching_rule {
MatchingRule::Simple(r) => should_act_individual(
&title,
&exe_name,
&class,
&path,
r,
&regex_identifiers,
),
MatchingRule::Composite(r) => {
let mut composite_results = vec![];
for identifier in r {
composite_results.push(should_act_individual(
&title,
&exe_name,
&class,
&path,
identifier,
&regex_identifiers,
));
}
composite_results.iter().all(|&x| x)
}
};
if matched {
let floating = workspace.floating_windows().contains(window);
if rule.initial_only {
if !already_moved_window_handles.contains(&window.hwnd) {
already_moved_window_handles.insert(window.hwnd);
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
} else {
self.add_window_handle_to_move_based_on_workspace_rule(
&window.title()?,
window.hwnd,
i,
j,
rule.monitor_index,
rule.workspace_index,
floating,
&mut to_move,
);
}
}
}
}
}
}
}
}
// Only retain operations where the target is not the current workspace
to_move.retain(|op| !op.is_target(focused_monitor_idx, focused_workspace_idx));
// Only retain operations where the rule has not already been enforced
to_move.retain(|op| !op.is_enforced());
let mut should_update_focused_workspace = false;
// Parse the operation and remove any windows that are not placed according to their rules
for op in &to_move {
let target_area = self
.monitors_mut()
.get_mut(op.target_monitor_idx)
.ok_or_eyre("there is no monitor with that index")?
.work_area_size;
let origin_monitor = self
.monitors_mut()
.get_mut(op.origin_monitor_idx)
.ok_or_eyre("there is no monitor with that index")?;
let origin_area = origin_monitor.work_area_size;
let origin_workspace = origin_monitor
.workspaces_mut()
.get_mut(op.origin_workspace_idx)
.ok_or_eyre("there is no workspace with that index")?;
let mut window = Window::from(op.hwnd);
// If it is a floating window move it to the target area
if op.floating {
window.move_to_area(&origin_area, &target_area)?;
}
// Hide the window we are about to remove if it is on the currently focused workspace
if op.is_origin(focused_monitor_idx, focused_workspace_idx) {
window.hide();
should_update_focused_workspace = true;
}
origin_workspace.remove_window(op.hwnd)?;
}
// Parse the operation again and associate those removed windows with the workspace that
// their rules have defined for them
for op in &to_move {
let target_monitor = self
.monitors_mut()
.get_mut(op.target_monitor_idx)
.ok_or_eyre("there is no monitor with that index")?;
// The very first time this fn is called, the workspace might not even exist yet
if target_monitor
.workspaces()
.get(op.target_workspace_idx)
.is_none()
{
// If it doesn't, let's make sure it does for the next step
target_monitor.ensure_workspace_count(op.target_workspace_idx + 1);
}
let target_workspace = target_monitor
.workspaces_mut()
.get_mut(op.target_workspace_idx)
.ok_or_eyre("there is no workspace with that index")?;
if op.floating {
target_workspace
.floating_windows_mut()
.push_back(Window::from(op.hwnd));
} else {
//TODO(alex-ds13): should this take into account the target workspace
//`window_container_behaviour`?
//In the case above a floating window should always be moved as floating,
//because it was set as so either manually by the user or by a
//`floating_applications` rule so it should stay that way. But a tiled window
//when moving to another workspace by a `workspace_rule` should honor that
//workspace `window_container_behaviour` in my opinion! Maybe this should be done
//on the `new_container_for_window` function instead.
target_workspace.new_container_for_window(Window::from(op.hwnd));
}
}
// Only re-tile the focused workspace if we need to
if should_update_focused_workspace {
self.update_focused_workspace(false, false)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn retile_all(&mut self, preserve_resize_dimensions: bool) -> eyre::Result<()> {
let offset = self.work_area_offset;
for monitor in self.monitors_mut() {
let offset = if monitor.work_area_offset.is_some() {
monitor.work_area_offset
} else {
offset
};
let focused_workspace_idx = monitor.focused_workspace_idx();
monitor.update_workspace_globals(focused_workspace_idx, offset);
let hmonitor = monitor.id;
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.focused_workspace_mut()
.ok_or_eyre("there is no workspace")?;
// Reset any resize adjustments if we want to force a retile
if !preserve_resize_dimensions {
for resize in &mut workspace.resize_dimensions {
*resize = None;
}
}
if (workspace.wallpaper.is_some() || monitor_wp.is_some())
&& let Err(error) = workspace.apply_wallpaper(hmonitor, &monitor_wp)
{
tracing::error!("failed to apply wallpaper: {}", error);
}
workspace.update()?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn manage_focused_window(&mut self) -> eyre::Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Manage(Window::from(hwnd));
Ok(winevent_listener::event_tx().send(event)?)
}
#[tracing::instrument(skip(self))]
pub fn unmanage_focused_window(&mut self) -> eyre::Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let event = WindowManagerEvent::Unmanage(Window::from(hwnd));
Ok(winevent_listener::event_tx().send(event)?)
}
#[tracing::instrument(skip(self))]
pub fn raise_window_at_cursor_pos(&mut self) -> eyre::Result<()> {
let mut hwnd = None;
let workspace = self.focused_workspace()?;
// first check the focused workspace
if let Some(container_idx) = workspace.container_idx_from_current_point()
&& let Some(container) = workspace.containers().get(container_idx)
&& let Some(window) = container.focused_window()
{
hwnd = Some(window.hwnd);
}
// then check all workspaces
if hwnd.is_none() {
for monitor in self.monitors() {
for ws in monitor.workspaces() {
if let Some(container_idx) = ws.container_idx_from_current_point()
&& let Some(container) = ws.containers().get(container_idx)
&& let Some(window) = container.focused_window()
{
hwnd = Some(window.hwnd);
}
}
}
}
// finally try matching the other way using a hwnd returned from the cursor pos
if hwnd.is_none() {
let cursor_pos_hwnd = WindowsApi::window_at_cursor_pos()?;
for monitor in self.monitors() {
for ws in monitor.workspaces() {
if ws.container_for_window(cursor_pos_hwnd).is_some() {
hwnd = Some(cursor_pos_hwnd);
}
}
}
}
if let Some(hwnd) = hwnd {
if self.has_pending_raise_op
|| self.focused_window()?.hwnd == hwnd
// Sometimes we need this check, because the focus may have been given by a click
// to a non-window such as the taskbar or system tray, and komorebi doesn't know that
// the focused window of the workspace is not actually focused by the OS at that point
|| WindowsApi::foreground_window()? == hwnd
{
return Ok(());
}
let event = WindowManagerEvent::Raise(Window::from(hwnd));
self.has_pending_raise_op = true;
winevent_listener::event_tx().send(event)?;
} else {
tracing::debug!(
"not raising unknown window: {}",
Window::from(WindowsApi::window_at_cursor_pos()?)
);
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn transfer_window(
&mut self,
origin: (usize, usize, isize),
target: (usize, usize, usize),
) -> eyre::Result<()> {
let (origin_monitor_idx, origin_workspace_idx, w_hwnd) = origin;
let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;
let origin_workspace = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("cannot get monitor idx")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("cannot get workspace idx")?;
let origin_container_idx = origin_workspace
.container_for_window(w_hwnd)
.and_then(|c| origin_workspace.containers().iter().position(|cc| cc == c));
if let Some(origin_container_idx) = origin_container_idx {
// Moving normal container window
self.transfer_container(
(
origin_monitor_idx,
origin_workspace_idx,
origin_container_idx,
),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
} else if let Some(idx) = origin_workspace
.floating_windows()
.iter()
.position(|w| w.hwnd == w_hwnd)
{
// Moving floating window
// There is no need to physically move the floating window between areas with
// `move_to_area` because the user already did that, so we only need to transfer the
// window to the target `floating_windows`
if let Some(floating_window) = origin_workspace.floating_windows_mut().remove(idx) {
let target_workspace = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_eyre("there is no monitor at this idx")?
.focused_workspace_mut()
.ok_or_eyre("there is no focused workspace for this monitor")?;
target_workspace
.floating_windows_mut()
.push_back(floating_window);
}
} else if origin_workspace
.monocle_container
.as_ref()
.and_then(|monocle| monocle.focused_window().map(|w| w.hwnd == w_hwnd))
.unwrap_or_default()
{
// Moving monocle container
if let Some(monocle_idx) = origin_workspace.monocle_container_restore_idx {
let origin_workspace = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this idx")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("there is no workspace for this monitor")?;
let mut uncloack_amount = 0;
for container in origin_workspace.containers_mut() {
container.restore();
uncloack_amount += 1;
}
origin_workspace.reintegrate_monocle_container()?;
self.transfer_container(
(origin_monitor_idx, origin_workspace_idx, monocle_idx),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
// After we restore the origin workspace, some windows that were cloacked
// by the monocle might now be uncloacked which would trigger a workspace
// reconciliation since the focused monitor would be different from origin.
// That workspace reconciliation would focus the window on the origin monitor.
// So we need to ignore the uncloak events produced by the origin workspace
// restore to avoid that issue.
self.uncloack_to_ignore = uncloack_amount;
}
} else if origin_workspace
.maximized_window
.as_ref()
.map(|max| max.hwnd == w_hwnd)
.unwrap_or_default()
{
// Moving maximized_window
if let Some(maximized_idx) = origin_workspace.maximized_window_restore_idx {
self.focus_monitor(origin_monitor_idx)?;
let origin_monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no origin monitor")?;
origin_monitor.focus_workspace(origin_workspace_idx)?;
self.unmaximize_window()?;
self.focus_monitor(target_monitor_idx)?;
let target_monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no target monitor")?;
target_monitor.focus_workspace(target_workspace_idx)?;
self.transfer_container(
(origin_monitor_idx, origin_workspace_idx, maximized_idx),
(
target_monitor_idx,
target_workspace_idx,
target_container_idx,
),
)?;
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn transfer_container(
&mut self,
origin: (usize, usize, usize),
target: (usize, usize, usize),
) -> eyre::Result<()> {
let (origin_monitor_idx, origin_workspace_idx, origin_container_idx) = origin;
let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;
let origin_container = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.remove_container(origin_container_idx)
.ok_or_eyre("there is no container at this index")?;
let target_workspace = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(target_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?;
target_workspace
.containers_mut()
.insert(target_container_idx, origin_container);
target_workspace.focus_container(target_container_idx);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn swap_containers(
&mut self,
origin: (usize, usize, usize),
target: (usize, usize, usize),
) -> eyre::Result<()> {
let (origin_monitor_idx, origin_workspace_idx, origin_container_idx) = origin;
let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;
let origin_container_is_valid = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.containers()
.get(origin_container_idx)
.is_some();
let target_container_is_valid = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(target_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.containers()
.get(origin_container_idx)
.is_some();
if origin_container_is_valid && target_container_is_valid {
let origin_container = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.remove_container(origin_container_idx)
.ok_or_eyre("there is no container at this index")?;
let target_container = self
.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(target_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.remove_container(target_container_idx);
self.monitors_mut()
.get_mut(target_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(target_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.containers_mut()
.insert(target_container_idx, origin_container);
if let Some(target_container) = target_container {
self.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.workspaces_mut()
.get_mut(origin_workspace_idx)
.ok_or_eyre("there is no workspace at this index")?
.containers_mut()
.insert(origin_container_idx, target_container);
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn update_focused_workspace(
&mut self,
follow_focus: bool,
trigger_focus: bool,
) -> eyre::Result<()> {
tracing::info!("updating");
let offset = self.work_area_offset;
self.focused_monitor_mut()
.ok_or_eyre("there is no monitor")?
.update_focused_workspace(offset)?;
if follow_focus {
if let Some(window) = self.focused_workspace()?.maximized_window {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
} else if let Some(container) = &self.focused_workspace()?.monocle_container {
if let Some(window) = container.focused_window()
&& trigger_focus
{
window.focus(self.mouse_follows_focus)?;
}
} else if let Ok(window) = self.focused_window_mut() {
if trigger_focus {
window.focus(self.mouse_follows_focus)?;
}
} else {
let desktop_window = Window::from(WindowsApi::desktop_window()?);
let rect = self.focused_monitor_size()?;
WindowsApi::center_cursor_in_rect(&rect)?;
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
}
} else {
if self.focused_workspace()?.is_empty() {
let desktop_window = Window::from(WindowsApi::desktop_window()?);
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
}
// if we passed false for follow_focus and there is a container on the workspace
if self.focused_container_mut().is_ok() {
// and we have a stack with >1 windows
if self.focused_container_mut()?.windows().len() > 1
// and we don't have a maxed window
&& self.focused_workspace()?.maximized_window.is_none()
// and we don't have a monocle container
&& self.focused_workspace()?.monocle_container.is_none()
// and we don't have any floating windows that should show on top
&& self.focused_workspace()?.floating_windows().is_empty()
&& let Ok(window) = self.focused_window_mut()
&& trigger_focus
{
window.focus(self.mouse_follows_focus)?;
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn resize_window(
&mut self,
direction: OperationDirection,
sizing: Sizing,
delta: i32,
update: bool,
) -> eyre::Result<()> {
let mouse_follows_focus = self.mouse_follows_focus;
let mut focused_monitor_work_area = self.focused_monitor_work_area()?;
let workspace = self.focused_workspace_mut()?;
match workspace.layer {
WorkspaceLayer::Floating => {
let workspace = self.focused_workspace()?;
let focused_hwnd = WindowsApi::foreground_window()?;
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
focused_monitor_work_area.left += border_offset;
focused_monitor_work_area.left += border_width;
focused_monitor_work_area.top += border_offset;
focused_monitor_work_area.top += border_width;
focused_monitor_work_area.right -= border_offset * 2;
focused_monitor_work_area.right -= border_width * 2;
focused_monitor_work_area.bottom -= border_offset * 2;
focused_monitor_work_area.bottom -= border_width * 2;
for window in workspace.floating_windows().iter() {
if window.hwnd == focused_hwnd {
let mut rect = WindowsApi::window_rect(window.hwnd)?;
match (direction, sizing) {
(OperationDirection::Left, Sizing::Increase) => {
if rect.left - delta < focused_monitor_work_area.left {
rect.left = focused_monitor_work_area.left;
} else {
rect.left -= delta;
}
}
(OperationDirection::Left, Sizing::Decrease) => {
rect.left += delta;
}
(OperationDirection::Right, Sizing::Increase) => {
if rect.left + rect.right + delta * 2
> focused_monitor_work_area.left
+ focused_monitor_work_area.right
{
rect.right = focused_monitor_work_area.left
+ focused_monitor_work_area.right
- rect.left;
} else {
rect.right += delta * 2;
}
}
(OperationDirection::Right, Sizing::Decrease) => {
rect.right -= delta * 2;
}
(OperationDirection::Up, Sizing::Increase) => {
if rect.top - delta < focused_monitor_work_area.top {
rect.top = focused_monitor_work_area.top;
} else {
rect.top -= delta;
}
}
(OperationDirection::Up, Sizing::Decrease) => {
rect.top += delta;
}
(OperationDirection::Down, Sizing::Increase) => {
if rect.top + rect.bottom + delta * 2
> focused_monitor_work_area.top
+ focused_monitor_work_area.bottom
{
rect.bottom = focused_monitor_work_area.top
+ focused_monitor_work_area.bottom
- rect.top;
} else {
rect.bottom += delta * 2;
}
}
(OperationDirection::Down, Sizing::Decrease) => {
rect.bottom -= delta * 2;
}
}
WindowsApi::position_window(window.hwnd, &rect, false, true)?;
if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&rect)?;
}
break;
}
}
}
WorkspaceLayer::Tiling => {
match workspace.layout {
Layout::Default(layout) => {
tracing::info!("resizing window");
let len = NonZeroUsize::new(workspace.containers().len())
.ok_or_eyre("there must be at least one container")?;
let focused_idx = workspace.focused_container_idx();
let focused_idx_resize = workspace
.resize_dimensions
.get(focused_idx)
.ok_or_eyre("there is no resize adjustment for this container")?;
if direction
.destination(
workspace.layout.as_boxed_direction().as_ref(),
workspace.layout_flip,
focused_idx,
len,
workspace.layout_options,
)
.is_some()
{
let unaltered = layout.calculate(
&focused_monitor_work_area,
len,
workspace.container_padding,
workspace.layout_flip,
&[],
workspace.focused_container_idx(),
workspace.layout_options,
&workspace.latest_layout,
);
let mut direction = direction;
// We only ever want to operate on the unflipped Rect positions when resizing, then we
// can flip them however they need to be flipped once the resizing has been done
if let Some(flip) = workspace.layout_flip {
match flip {
Axis::Horizontal => {
if matches!(direction, OperationDirection::Left)
|| matches!(direction, OperationDirection::Right)
{
direction = direction.opposite();
}
}
Axis::Vertical => {
if matches!(direction, OperationDirection::Up)
|| matches!(direction, OperationDirection::Down)
{
direction = direction.opposite();
}
}
Axis::HorizontalAndVertical => direction = direction.opposite(),
}
}
let resize = layout.resize(
unaltered
.get(focused_idx)
.ok_or_eyre("there is no last layout")?,
focused_idx_resize,
direction,
sizing,
delta,
);
workspace.resize_dimensions[focused_idx] = resize;
return if update {
self.update_focused_workspace(false, false)
} else {
Ok(())
};
}
tracing::warn!("cannot resize container in this direction");
}
Layout::Custom(_) => {
tracing::warn!("containers cannot be resized when using custom layouts");
}
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn stop(&mut self, ignore_restore: bool) -> eyre::Result<()> {
tracing::info!(
"received stop command, restoring all hidden windows and terminating process"
);
let state = &State::from(&*self);
std::fs::write(
temp_dir().join("komorebi.state.json"),
serde_json::to_string_pretty(&state)?,
)?;
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst);
self.restore_all_windows(ignore_restore)?;
AnimationEngine::wait_for_all_animations();
if WindowsApi::focus_follows_mouse()? {
WindowsApi::disable_focus_follows_mouse()?;
}
let sockets = SUBSCRIPTION_SOCKETS.lock();
for path in (*sockets).values() {
if let Ok(stream) = UnixStream::connect(path) {
stream.shutdown(Shutdown::Both)?;
}
}
let socket = DATA_DIR.join("komorebi.sock");
let _ = std::fs::remove_file(socket);
std::process::exit(0)
}
#[tracing::instrument(skip(self))]
pub fn restore_all_windows(&mut self, ignore_restore: bool) -> eyre::Result<()> {
tracing::info!("restoring all hidden windows");
let no_titlebar = NO_TITLEBAR.lock();
let regex_identifiers = REGEX_IDENTIFIERS.lock();
let known_transparent_hwnds = transparency_manager::known_hwnds();
let border_implementation = border_manager::IMPLEMENTATION.load();
for monitor in self.monitors_mut() {
for workspace in monitor.workspaces_mut() {
if let Some(monocle) = &workspace.monocle_container {
for window in monocle.windows() {
if matches!(border_implementation, BorderImplementation::Windows) {
window.remove_accent()?;
}
}
}
for containers in workspace.containers_mut() {
for window in containers.windows_mut() {
let should_remove_titlebar_for_window = should_act(
&window.title().unwrap_or_default(),
&window.exe().unwrap_or_default(),
&window.class().unwrap_or_default(),
&window.path().unwrap_or_default(),
&no_titlebar,
&regex_identifiers,
)
.is_some();
if should_remove_titlebar_for_window {
window.add_title_bar()?;
}
if known_transparent_hwnds.contains(&window.hwnd) {
window.opaque()?;
}
if matches!(border_implementation, BorderImplementation::Windows) {
window.remove_accent()?;
}
if !ignore_restore {
window.restore();
}
}
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn remove_all_accents(&mut self) -> eyre::Result<()> {
tracing::info!("removing all window accents");
for monitor in self.monitors() {
for workspace in monitor.workspaces() {
if let Some(monocle) = &workspace.monocle_container {
for window in monocle.windows() {
window.remove_accent()?
}
}
for containers in workspace.containers() {
for window in containers.windows() {
window.remove_accent()?;
}
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
fn handle_unmanaged_window_behaviour(&self) -> eyre::Result<()> {
if matches!(
self.unmanaged_window_operation_behaviour,
OperationBehaviour::NoOp
) {
let workspace = self.focused_workspace()?;
let focused_hwnd = WindowsApi::foreground_window()?;
if !workspace.contains_managed_window(focused_hwnd) {
bail!("ignoring commands while active window is not managed by komorebi");
}
}
Ok(())
}
/// Check for an existing wallpaper definition on the workspace/monitor index pair and apply it
/// if it exists
#[tracing::instrument(skip(self))]
pub fn apply_wallpaper_for_monitor_workspace(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
) -> eyre::Result<()> {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let hmonitor = monitor.id;
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.workspaces()
.get(workspace_idx)
.ok_or_eyre("there is no workspace")?;
workspace.apply_wallpaper(hmonitor, &monitor_wp)
}
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> eyre::Result<()> {
let offset = self.work_area_offset;
self.monitors_mut()
.get_mut(idx)
.ok_or_eyre("there is no monitor")?
.update_focused_workspace(offset)
}
#[tracing::instrument(skip(self))]
pub fn swap_monitor_workspaces(
&mut self,
first_idx: usize,
second_idx: usize,
) -> eyre::Result<()> {
tracing::info!("swaping monitors");
if first_idx == second_idx {
return Ok(());
}
let mouse_follows_focus = self.mouse_follows_focus;
let offset = self.work_area_offset;
let first_focused_workspace = {
let first_monitor = self
.monitors()
.get(first_idx)
.ok_or_eyre("There is no monitor")?;
first_monitor.focused_workspace_idx()
};
let second_focused_workspace = {
let second_monitor = self
.monitors()
.get(second_idx)
.ok_or_eyre("There is no monitor")?;
second_monitor.focused_workspace_idx()
};
// Swap workspaces between the first and second monitors
let first_workspaces = self
.monitors_mut()
.get_mut(first_idx)
.ok_or_eyre("There is no monitor")?
.remove_workspaces();
let second_workspaces = self
.monitors_mut()
.get_mut(second_idx)
.ok_or_eyre("There is no monitor")?
.remove_workspaces();
self.monitors_mut()
.get_mut(first_idx)
.ok_or_eyre("There is no monitor")?
.workspaces_mut()
.extend(second_workspaces);
self.monitors_mut()
.get_mut(second_idx)
.ok_or_eyre("There is no monitor")?
.workspaces_mut()
.extend(first_workspaces);
// Set the focused workspaces for the first and second monitors
if let Some(first_monitor) = self.monitors_mut().get_mut(first_idx) {
first_monitor.update_workspaces_globals(offset);
first_monitor.focus_workspace(second_focused_workspace)?;
first_monitor.load_focused_workspace(mouse_follows_focus)?;
}
if let Some(second_monitor) = self.monitors_mut().get_mut(second_idx) {
second_monitor.update_workspaces_globals(offset);
second_monitor.focus_workspace(first_focused_workspace)?;
second_monitor.load_focused_workspace(mouse_follows_focus)?;
}
self.update_focused_workspace_by_monitor_idx(second_idx)?;
self.update_focused_workspace_by_monitor_idx(first_idx)
}
#[tracing::instrument(skip(self))]
pub fn swap_focused_monitor(&mut self, idx: usize) -> eyre::Result<()> {
tracing::info!("swapping focused monitor");
let focused_monitor_idx = self.focused_monitor_idx();
let mouse_follows_focus = self.mouse_follows_focus;
self.swap_monitor_workspaces(focused_monitor_idx, idx)?;
self.update_focused_workspace(mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn move_container_to_monitor(
&mut self,
monitor_idx: usize,
workspace_idx: Option<usize>,
follow: bool,
move_direction: Option<OperationDirection>,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("moving container");
let focused_monitor_idx = self.focused_monitor_idx();
if focused_monitor_idx == monitor_idx
&& let Some(workspace_idx) = workspace_idx
{
return self.move_container_to_workspace(workspace_idx, follow, None);
}
let offset = self.work_area_offset;
let mouse_follows_focus = self.mouse_follows_focus;
let monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no monitor")?;
let current_area = monitor.work_area_size;
let workspace = monitor
.focused_workspace_mut()
.ok_or_eyre("there is no workspace")?;
if workspace.maximized_window.is_some() {
bail!("cannot move native maximized window to another monitor or workspace");
}
let foreground_hwnd = WindowsApi::foreground_window()?;
let floating_window_index = workspace
.floating_windows()
.iter()
.position(|w| w.hwnd == foreground_hwnd);
let floating_window =
floating_window_index.and_then(|idx| workspace.floating_windows_mut().remove(idx));
let container = if floating_window_index.is_none() {
Some(
workspace
.remove_focused_container()
.ok_or_eyre("there is no container")?,
)
} else {
None
};
monitor.update_focused_workspace(offset)?;
let target_monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let mut should_load_workspace = false;
if let Some(workspace_idx) = workspace_idx
&& workspace_idx != target_monitor.focused_workspace_idx()
{
target_monitor.focus_workspace(workspace_idx)?;
should_load_workspace = true;
}
let target_workspace = target_monitor
.focused_workspace_mut()
.ok_or_eyre("there is no focused workspace on target monitor")?;
if target_workspace.monocle_container.is_some() {
for container in target_workspace.containers_mut() {
container.restore();
}
for window in target_workspace.floating_windows_mut() {
window.restore();
}
target_workspace.reintegrate_monocle_container()?;
}
if let Some(window) = floating_window {
target_workspace.floating_windows_mut().push_back(window);
target_workspace.layer = WorkspaceLayer::Floating;
Window::from(window.hwnd)
.move_to_area(&current_area, &target_monitor.work_area_size)?;
} else if let Some(container) = container {
let container_hwnds = container
.windows()
.iter()
.map(|w| w.hwnd)
.collect::<Vec<_>>();
target_workspace.layer = WorkspaceLayer::Tiling;
if let Some(direction) = move_direction {
target_monitor.add_container_with_direction(container, workspace_idx, direction)?;
} else {
target_monitor.add_container(container, workspace_idx)?;
}
if let Some(workspace) = target_monitor.focused_workspace()
&& !workspace.tile
{
for hwnd in container_hwnds {
Window::from(hwnd)
.move_to_area(&current_area, &target_monitor.work_area_size)?;
}
}
} else {
bail!("failed to find a window to move");
}
if should_load_workspace {
target_monitor.load_focused_workspace(mouse_follows_focus)?;
}
target_monitor.update_focused_workspace(offset)?;
// this second one is for DPI changes when the target is another monitor
// if we don't do this the layout on the other monitor could look funny
// until it is interacted with again
target_monitor.update_focused_workspace(offset)?;
if follow {
self.focus_monitor(monitor_idx)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_container_to_workspace(
&mut self,
idx: usize,
follow: bool,
direction: Option<OperationDirection>,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("moving container");
let mouse_follows_focus = self.mouse_follows_focus;
let monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no monitor")?;
monitor.move_container_to_workspace(idx, follow, direction)?;
monitor.load_focused_workspace(mouse_follows_focus)?;
self.update_focused_workspace(mouse_follows_focus, true)?;
Ok(())
}
pub fn remove_focused_workspace(&mut self) -> Option<Workspace> {
let focused_monitor: &mut Monitor = self.focused_monitor_mut()?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspace = focused_monitor.remove_workspace_by_idx(focused_workspace_idx);
if let Err(error) = focused_monitor.focus_workspace(focused_workspace_idx.saturating_sub(1))
{
tracing::error!(
"Error focusing previous workspace while removing the focused workspace: {}",
error
);
}
workspace
}
#[tracing::instrument(skip(self))]
pub fn move_workspace_to_monitor(&mut self, idx: usize) -> eyre::Result<()> {
tracing::info!("moving workspace");
let mouse_follows_focus = self.mouse_follows_focus;
let offset = self.work_area_offset;
let workspace = self
.remove_focused_workspace()
.ok_or_eyre("there is no workspace")?;
{
let target_monitor: &mut Monitor = self
.monitors_mut()
.get_mut(idx)
.ok_or_eyre("there is no monitor")?;
target_monitor.workspaces_mut().push_back(workspace);
target_monitor.update_workspaces_globals(offset);
target_monitor.focus_workspace(target_monitor.workspaces().len().saturating_sub(1))?;
target_monitor.load_focused_workspace(mouse_follows_focus)?;
}
self.focus_monitor(idx)?;
self.update_focused_workspace(mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn focus_floating_window_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
let mouse_follows_focus = self.mouse_follows_focus;
let focused_workspace = self.focused_workspace_mut()?;
let mut target_idx = None;
let len = focused_workspace.floating_windows().len();
if len > 1 {
let focused_hwnd = WindowsApi::foreground_window()?;
let focused_rect = WindowsApi::window_rect(focused_hwnd)?;
match direction {
OperationDirection::Left => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.left < focused_rect.left)
.then_some((idx, i32::abs(r.left - focused_rect.left)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
OperationDirection::Right => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.left > focused_rect.left)
.then_some((idx, i32::abs(r.left - focused_rect.left)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
OperationDirection::Up => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.top < focused_rect.top)
.then_some((idx, i32::abs(r.top - focused_rect.top)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
OperationDirection::Down => {
let mut windows_in_direction = focused_workspace
.floating_windows()
.iter()
.enumerate()
.flat_map(|(idx, w)| {
(w.hwnd != focused_hwnd)
.then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))
})
.flatten()
.flat_map(|(idx, r)| {
(r.top > focused_rect.top)
.then_some((idx, i32::abs(r.top - focused_rect.top)))
})
.collect::<Vec<_>>();
// Sort by distance to focused
windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);
if let Some((idx, _)) = windows_in_direction.first() {
target_idx = Some(*idx);
}
}
};
}
if let Some(idx) = target_idx {
focused_workspace.floating_windows.focus(idx);
if let Some(window) = focused_workspace.floating_windows().get(idx) {
window.focus(mouse_follows_focus)?;
}
return Ok(());
}
let mut cross_monitor_monocle_or_max = false;
let workspace_idx = self.focused_workspace_idx()?;
// this is for when we are scrolling across workspaces like PaperWM
if matches!(
self.cross_boundary_behaviour,
CrossBoundaryBehaviour::Workspace
) && matches!(
direction,
OperationDirection::Left | OperationDirection::Right
) {
let workspace_count = if let Some(monitor) = self.focused_monitor() {
monitor.workspaces().len()
} else {
1
};
let next_idx = match direction {
OperationDirection::Left => match workspace_idx {
0 => workspace_count - 1,
n => n - 1,
},
OperationDirection::Right => match workspace_idx {
n if n == workspace_count - 1 => 0,
n => n + 1,
},
_ => workspace_idx,
};
self.focus_workspace(next_idx)?;
if let Ok(focused_workspace) = self.focused_workspace_mut()
&& focused_workspace.monocle_container.is_none()
{
match direction {
OperationDirection::Left => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
return Ok(());
}
// if there is no floating_window in that direction for this workspace
let monitor_idx = self
.monitor_idx_in_direction(direction)
.ok_or_eyre("there is no container or monitor in this direction")?;
self.focus_monitor(monitor_idx)?;
let mouse_follows_focus = self.mouse_follows_focus;
if let Ok(focused_workspace) = self.focused_workspace_mut() {
if let Some(window) = focused_workspace.maximized_window {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
} else if let Some(monocle) = &focused_workspace.monocle_container {
if let Some(window) = monocle.focused_window() {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
}
} else if focused_workspace.layer == WorkspaceLayer::Tiling {
match direction {
OperationDirection::Left => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
}
if !cross_monitor_monocle_or_max {
let ws = self.focused_workspace_mut()?;
if ws.is_empty() {
// This is to remove focus from the previous monitor
let desktop_window = Window::from(WindowsApi::desktop_window()?);
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
} else if ws.layer == WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
if let Some(window) = ws.focused_floating_window() {
window.focus(self.mouse_follows_focus)?;
}
} else {
ws.layer = WorkspaceLayer::Tiling;
if let Ok(focused_window) = self.focused_window() {
focused_window.focus(self.mouse_follows_focus)?;
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn preselect_container_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
let workspace = self.focused_workspace_mut()?;
let focused_idx = workspace.focused_container_idx();
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::warn!("preselection is not supported on the grid layout");
return Ok(());
}
tracing::info!("preselecting container");
let new_idx =
if workspace.maximized_window.is_some() || workspace.monocle_container.is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
match new_idx {
Some(new_idx) => {
let adjusted_idx = match direction {
OperationDirection::Left | OperationDirection::Up => {
if focused_idx.abs_diff(new_idx) == 1 {
new_idx + 1
} else {
new_idx
}
}
_ => new_idx,
};
workspace.preselect_container_idx(adjusted_idx);
}
None => {
tracing::debug!(
"this is not a valid preselection direction from the current position"
)
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn focus_container_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace()?;
let workspace_idx = self.focused_workspace_idx()?;
tracing::info!("focusing container");
if workspace.monocle_container.is_some() {
let cycle_direction = match direction {
OperationDirection::Left | OperationDirection::Down => CycleDirection::Previous,
OperationDirection::Right | OperationDirection::Up => CycleDirection::Next,
};
return self.cycle_monocle(cycle_direction);
}
let new_idx = if workspace.maximized_window.is_some() {
None
} else {
workspace.new_idx_for_direction(direction)
};
let mut cross_monitor_monocle_or_max = false;
// this is for when we are scrolling across workspaces like PaperWM
if new_idx.is_none()
&& matches!(
self.cross_boundary_behaviour,
CrossBoundaryBehaviour::Workspace
)
&& matches!(
direction,
OperationDirection::Left | OperationDirection::Right
)
{
let workspace_count = if let Some(monitor) = self.focused_monitor() {
monitor.workspaces().len()
} else {
1
};
let next_idx = match direction {
OperationDirection::Left => match workspace_idx {
0 => workspace_count - 1,
n => n - 1,
},
OperationDirection::Right => match workspace_idx {
n if n == workspace_count - 1 => 0,
n => n + 1,
},
_ => workspace_idx,
};
self.focus_workspace(next_idx)?;
if let Ok(focused_workspace) = self.focused_workspace_mut()
&& focused_workspace.monocle_container.is_none()
{
match direction {
OperationDirection::Left => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
return Ok(());
}
// if there is no container in that direction for this workspace
match new_idx {
None => {
let monitor_idx = self
.monitor_idx_in_direction(direction)
.ok_or_eyre("there is no container or monitor in this direction")?;
self.focus_monitor(monitor_idx)?;
let mouse_follows_focus = self.mouse_follows_focus;
if let Ok(focused_workspace) = self.focused_workspace_mut() {
if let Some(window) = focused_workspace.maximized_window {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
} else if let Some(monocle) = &focused_workspace.monocle_container {
if let Some(window) = monocle.focused_window() {
window.focus(mouse_follows_focus)?;
cross_monitor_monocle_or_max = true;
}
} else if focused_workspace.layer == WorkspaceLayer::Tiling {
match direction {
OperationDirection::Left => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index = layout
.rightmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(
focused_workspace.containers().len().saturating_sub(1),
);
}
},
OperationDirection::Right => match focused_workspace.layout {
Layout::Default(layout) => {
let target_index =
layout.leftmost_index(focused_workspace.containers().len());
focused_workspace.focus_container(target_index);
}
Layout::Custom(_) => {
focused_workspace.focus_container(0);
}
},
_ => {}
};
}
}
}
Some(idx) => {
let workspace = self.focused_workspace_mut()?;
workspace.focus_container(idx);
}
}
if !cross_monitor_monocle_or_max {
let ws = self.focused_workspace_mut()?;
if ws.is_empty() {
// This is to remove focus from the previous monitor
let desktop_window = Window::from(WindowsApi::desktop_window()?);
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
Ok(()) => {}
Err(error) => {
tracing::warn!("{} {}:{}", error, file!(), line!());
}
}
} else if ws.layer == WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
if let Some(window) = ws.focused_floating_window() {
window.focus(self.mouse_follows_focus)?;
}
} else {
ws.layer = WorkspaceLayer::Tiling;
if let Ok(focused_window) = self.focused_window() {
focused_window.focus(self.mouse_follows_focus)?;
}
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_floating_window_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
let mouse_follows_focus = self.mouse_follows_focus;
let mut focused_monitor_work_area = self.focused_monitor_work_area()?;
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
focused_monitor_work_area.left += border_offset;
focused_monitor_work_area.left += border_width;
focused_monitor_work_area.top += border_offset;
focused_monitor_work_area.top += border_width;
focused_monitor_work_area.right -= border_offset * 2;
focused_monitor_work_area.right -= border_width * 2;
focused_monitor_work_area.bottom -= border_offset * 2;
focused_monitor_work_area.bottom -= border_width * 2;
let focused_workspace = self.focused_workspace()?;
let delta = self.resize_delta;
let focused_hwnd = WindowsApi::foreground_window()?;
for window in focused_workspace.floating_windows().iter() {
if window.hwnd == focused_hwnd {
let mut rect = WindowsApi::window_rect(window.hwnd)?;
match direction {
OperationDirection::Left => {
if rect.left - delta < focused_monitor_work_area.left {
rect.left = focused_monitor_work_area.left;
} else {
rect.left -= delta;
}
}
OperationDirection::Right => {
if rect.left + delta + rect.right
> focused_monitor_work_area.left + focused_monitor_work_area.right
{
rect.left = focused_monitor_work_area.left
+ focused_monitor_work_area.right
- rect.right;
} else {
rect.left += delta;
}
}
OperationDirection::Up => {
if rect.top - delta < focused_monitor_work_area.top {
rect.top = focused_monitor_work_area.top;
} else {
rect.top -= delta;
}
}
OperationDirection::Down => {
if rect.top + delta + rect.bottom
> focused_monitor_work_area.top + focused_monitor_work_area.bottom
{
rect.top = focused_monitor_work_area.top
+ focused_monitor_work_area.bottom
- rect.bottom;
} else {
rect.top += delta;
}
}
}
WindowsApi::position_window(window.hwnd, &rect, false, true)?;
if mouse_follows_focus {
WindowsApi::center_cursor_in_rect(&rect)?;
}
break;
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_container_in_direction(
&mut self,
direction: OperationDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace()?;
let workspace_idx = self.focused_workspace_idx()?;
// removing this messes up the monitor / container / window index somewhere
// and results in the wrong window getting moved across the monitor boundary
if workspace.is_focused_window_monocle_or_maximized()? {
bail!("ignoring command while active window is in monocle mode or maximized");
}
tracing::info!("moving container");
let origin_container_idx = workspace.focused_container_idx();
let origin_monitor_idx = self.focused_monitor_idx();
let target_container_idx = workspace.new_idx_for_direction(direction);
// this is for when we are scrolling across workspaces like PaperWM
if target_container_idx.is_none()
&& matches!(
self.cross_boundary_behaviour,
CrossBoundaryBehaviour::Workspace
)
&& matches!(
direction,
OperationDirection::Left | OperationDirection::Right
)
{
let workspace_count = if let Some(monitor) = self.focused_monitor() {
monitor.workspaces().len()
} else {
1
};
let next_idx = match direction {
OperationDirection::Left => match workspace_idx {
0 => workspace_count - 1,
n => n - 1,
},
OperationDirection::Right => match workspace_idx {
n if n == workspace_count - 1 => 0,
n => n + 1,
},
_ => workspace_idx,
};
// passing the direction here is how we handle whether to insert at the front
// or the back of the container vecdeque in the target workspace
self.move_container_to_workspace(next_idx, true, Some(direction))?;
self.update_focused_workspace(self.mouse_follows_focus, true)?;
return Ok(());
}
match target_container_idx {
// If there is nowhere to move on the current workspace, try to move it onto the monitor
// in that direction if there is one
None => {
// Don't do anything if the user has set the MoveBehaviour to NoOp
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::NoOp) {
return Ok(());
}
let target_monitor_idx = self
.monitor_idx_in_direction(direction)
.ok_or_eyre("there is no container or monitor in this direction")?;
{
// actually move the container to target monitor using the direction
self.move_container_to_monitor(
target_monitor_idx,
None,
true,
Some(direction),
)?;
// focus the target monitor
self.focus_monitor(target_monitor_idx)?;
// unset monocle container on target workspace if there is one
let mut target_workspace_has_monocle = false;
if let Ok(target_workspace) = self.focused_workspace()
&& target_workspace.monocle_container.is_some()
{
target_workspace_has_monocle = true;
}
if target_workspace_has_monocle {
self.toggle_monocle()?;
}
// get a mutable ref to the focused workspace on the target monitor
let target_workspace = self.focused_workspace_mut()?;
// if there is only one container on the target workspace after the insertion
// it means that there won't be one swapped back, so we have to decrement the
// focused position
if target_workspace.containers().len() == 1 {
let origin_workspace =
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
origin_workspace.focus_container(
origin_workspace.focused_container_idx().saturating_sub(1),
);
}
}
// if our MoveBehaviour is Swap, let's try to send back the window container
// whose position which just took over
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::Swap) {
{
let target_workspace = self.focused_workspace_mut()?;
// if the target workspace doesn't have more than one container, this means it
// was previously empty, by only doing the second part of the swap when there is
// more than one container, we can fall back to a "move" if there is nothing to
// swap with on the target monitor
if target_workspace.containers().len() > 1 {
// remove the container from the target monitor workspace
let target_container = target_workspace
// this is now focused_container_idx + 1 because we have inserted our origin container
.remove_container_by_idx(
target_workspace.focused_container_idx() + 1,
)
.ok_or_eyre("could not remove container at given target index")?;
let origin_workspace =
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
// insert the container from the target monitor workspace into the origin monitor workspace
// at the same position from which our origin container was removed
origin_workspace
.insert_container_at_idx(origin_container_idx, target_container);
}
}
}
// make sure to update the origin monitor workspace layout because it is no
// longer focused so it won't get updated at the end of this fn
let offset = self.work_area_offset;
self.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.update_focused_workspace(offset)?;
let a = self
.focused_monitor()
.ok_or_eyre("there is no monitor focused monitor")?
.id;
let b = self
.monitors_mut()
.get_mut(origin_monitor_idx)
.ok_or_eyre("there is no monitor at this index")?
.id;
if !WindowsApi::monitors_have_same_dpi(a, b)? {
self.update_focused_workspace(self.mouse_follows_focus, true)?;
}
}
Some(new_idx) => {
let workspace = self.focused_workspace_mut()?;
workspace.swap_containers(origin_container_idx, new_idx);
workspace.focus_container(new_idx);
}
}
self.update_focused_workspace(self.mouse_follows_focus, true)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn focus_floating_window_in_cycle_direction(
&mut self,
direction: CycleDirection,
) -> eyre::Result<()> {
let mouse_follows_focus = self.mouse_follows_focus;
let focused_workspace = self.focused_workspace()?;
let mut target_idx = None;
let len = focused_workspace.floating_windows().len();
if len > 1 {
let focused_hwnd = WindowsApi::foreground_window()?;
for (idx, window) in focused_workspace.floating_windows().iter().enumerate() {
if window.hwnd == focused_hwnd {
match direction {
CycleDirection::Previous => {
if idx == 0 {
target_idx = Some(len - 1)
} else {
target_idx = Some(idx - 1)
}
}
CycleDirection::Next => {
if idx == len - 1 {
target_idx = Some(0)
} else {
target_idx = Some(idx - 1)
}
}
}
}
}
if target_idx.is_none() {
target_idx = Some(0);
}
}
if let Some(idx) = target_idx
&& let Some(window) = focused_workspace.floating_windows().get(idx)
{
window.focus(mouse_follows_focus)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn focus_container_in_cycle_direction(
&mut self,
direction: CycleDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("focusing container");
let mut maximize_next = false;
let mut monocle_next = false;
if self.focused_workspace_mut()?.maximized_window.is_some() {
maximize_next = true;
self.unmaximize_window()?;
}
if self.focused_workspace_mut()?.monocle_container.is_some() {
monocle_next = true;
self.monocle_off()?;
}
let workspace = self.focused_workspace_mut()?;
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.ok_or_eyre("this is not a valid direction from the current position")?;
workspace.focus_container(new_idx);
if maximize_next {
self.toggle_maximize()?;
} else if monocle_next {
self.toggle_monocle()?;
} else {
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_container_in_cycle_direction(
&mut self,
direction: CycleDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
if workspace.is_focused_window_monocle_or_maximized()? {
bail!("ignoring command while active window is in monocle mode or maximized");
}
tracing::info!("moving container");
let current_idx = workspace.focused_container_idx();
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.ok_or_eyre("this is not a valid direction from the current position")?;
workspace.swap_containers(current_idx, new_idx);
workspace.focus_container(new_idx);
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn cycle_container_window_in_direction(
&mut self,
direction: CycleDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("cycling container windows");
let container =
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
container
} else {
self.focused_container_mut()?
};
let len = NonZeroUsize::new(container.windows().len())
.ok_or_eyre("there must be at least one window in a container")?;
if len.get() == 1 {
bail!("there is only one window in this container");
}
let current_idx = container.focused_window_idx();
let next_idx = direction.next_idx(current_idx, len);
container.focus_window(next_idx);
container.load_focused_window();
if let Some(window) = container.focused_window() {
window.focus(self.mouse_follows_focus)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn cycle_container_window_index_in_direction(
&mut self,
direction: CycleDirection,
) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("cycling container window index");
let container =
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
container
} else {
self.focused_container_mut()?
};
let len = NonZeroUsize::new(container.windows().len())
.ok_or_eyre("there must be at least one window in a container")?;
if len.get() == 1 {
bail!("there is only one window in this container");
}
let current_idx = container.focused_window_idx();
let next_idx = direction.next_idx(current_idx, len);
container.windows_mut().swap(current_idx, next_idx);
container.focus_window(next_idx);
container.load_focused_window();
if let Some(window) = container.focused_window() {
window.focus(self.mouse_follows_focus)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn focus_container_window(&mut self, idx: usize) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("focusing container window at index {idx}");
let container =
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
container
} else {
self.focused_container_mut()?
};
let len = NonZeroUsize::new(container.windows().len())
.ok_or_eyre("there must be at least one window in a container")?;
if len.get() == 1 && idx != 0 {
bail!("there is only one window in this container");
}
if container.windows().get(idx).is_none() {
bail!("there is no window in this container at index {idx}");
}
container.focus_window(idx);
container.load_focused_window();
if let Some(window) = container.focused_window() {
window.focus(self.mouse_follows_focus)?;
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn stack_all(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("stacking all windows on workspace");
let workspace = self.focused_workspace_mut()?;
let mut focused_hwnd = None;
if let Some(container) = workspace.focused_container()
&& let Some(window) = container.focused_window()
{
focused_hwnd = Some(window.hwnd);
}
let workspace_hwnds =
workspace
.containers()
.iter()
.fold(VecDeque::new(), |mut hwnds, c| {
hwnds.extend(c.windows().clone());
hwnds
});
let mut container = Container::default();
*container.windows_mut() = workspace_hwnds;
*workspace.containers_mut() = VecDeque::from([container]);
workspace.focus_container(0);
if let Some(hwnd) = focused_hwnd
&& let Some(c) = workspace.focused_container_mut()
&& let Some(w_idx_to_focus) = c.idx_for_window(hwnd)
{
c.focus_window(w_idx_to_focus);
c.load_focused_window();
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn unstack_all(&mut self, update_workspace: bool) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("unstacking all windows in container");
let workspace = self.focused_workspace_mut()?;
let mut focused_hwnd = None;
if let Some(container) = workspace.focused_container()
&& let Some(window) = container.focused_window()
{
focused_hwnd = Some(window.hwnd);
}
let initial_focused_container_index = workspace.focused_container_idx();
let mut focused_container = workspace.focused_container().cloned();
while let Some(focused) = &focused_container {
if focused.windows().len() > 1 {
workspace.new_container_for_focused_window()?;
workspace.focus_container(initial_focused_container_index);
focused_container = workspace.focused_container().cloned();
} else {
focused_container = None;
}
}
if let Some(hwnd) = focused_hwnd {
workspace.focus_container_by_window(hwnd)?;
}
if update_workspace {
self.update_focused_workspace(self.mouse_follows_focus, true)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("adding window to container");
let workspace = self.focused_workspace_mut()?;
let len = NonZeroUsize::new(workspace.containers_mut().len())
.ok_or_eyre("there must be at least one container")?;
let current_container_idx = workspace.focused_container_idx();
let is_valid = direction
.destination(
workspace.layout.as_boxed_direction().as_ref(),
workspace.layout_flip,
workspace.focused_container_idx(),
len,
workspace.layout_options,
)
.is_some();
if is_valid {
let new_idx = workspace
.new_idx_for_direction(direction)
.ok_or_eyre("this is not a valid direction from the current position")?;
let mut changed_focus = false;
let adjusted_new_index = if new_idx > current_container_idx
&& !matches!(
workspace.layout,
Layout::Default(DefaultLayout::Grid)
| Layout::Default(DefaultLayout::UltrawideVerticalStack)
) {
workspace.focus_container(new_idx);
changed_focus = true;
new_idx.saturating_sub(1)
} else {
new_idx
};
let mut target_container_is_stack = false;
if let Some(container) = workspace.containers().get(adjusted_new_index)
&& container.windows().len() > 1
{
target_container_is_stack = true;
}
if let Some(current) = workspace.focused_container() {
if current.windows().len() > 1 && !target_container_is_stack {
workspace.focus_container(adjusted_new_index);
changed_focus = true;
workspace.move_window_to_container(current_container_idx)?;
} else {
workspace.move_window_to_container(adjusted_new_index)?;
}
}
if changed_focus && let Some(container) = workspace.focused_container_mut() {
container.load_focused_window();
if let Some(window) = container.focused_window() {
window.focus(self.mouse_follows_focus)?;
}
}
self.update_focused_workspace(self.mouse_follows_focus, false)?;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn promote_container_to_front(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::debug!("ignoring promote command for grid layout");
return Ok(());
}
tracing::info!("promoting container");
workspace.promote_container()?;
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn promote_container_swap(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
let focused_container_idx = workspace.focused_container_idx();
let primary_idx = match &workspace.layout {
Layout::Default(_) => 0,
Layout::Custom(layout) => layout.first_container_idx(
layout
.primary_idx()
.ok_or_eyre("this custom layout does not have a primary column")?,
),
};
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::debug!("ignoring promote-swap command for grid layout");
return Ok(());
}
let primary_tile_is_focused = focused_container_idx == primary_idx;
if primary_tile_is_focused && let Some(swap_idx) = workspace.promotion_swap_container_idx {
workspace.swap_containers(focused_container_idx, swap_idx);
} else {
workspace.promotion_swap_container_idx = Some(focused_container_idx);
workspace.swap_containers(focused_container_idx, primary_idx);
}
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn promote_focus_to_front(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
tracing::info!("ignoring promote focus command for grid layout");
return Ok(());
}
tracing::info!("promoting focus");
let target_idx = match &workspace.layout {
Layout::Default(_) => 0,
Layout::Custom(custom) => custom
.first_container_idx(custom.primary_idx().map_or(0, |primary_idx| primary_idx)),
};
workspace.focus_container(target_idx);
self.update_focused_workspace(self.mouse_follows_focus, true)
}
#[tracing::instrument(skip(self))]
pub fn remove_window_from_container(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("removing window");
if self.focused_container()?.windows().len() == 1 {
bail!("a container must have at least one window");
}
let workspace = self.focused_workspace_mut()?;
workspace.new_container_for_focused_window()?;
self.update_focused_workspace(self.mouse_follows_focus, false)
}
#[tracing::instrument(skip(self))]
pub fn toggle_tiling(&mut self) -> eyre::Result<()> {
let workspace = self.focused_workspace_mut()?;
workspace.tile = !workspace.tile;
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn toggle_float(&mut self, force_float: bool) -> eyre::Result<()> {
let hwnd = WindowsApi::foreground_window()?;
let workspace = self.focused_workspace_mut()?;
if workspace.monocle_container.is_some() {
tracing::warn!("ignoring toggle-float command while workspace has a monocle container");
return Ok(());
}
let mut is_floating_window = false;
for window in workspace.floating_windows() {
if window.hwnd == hwnd {
is_floating_window = true;
}
}
if is_floating_window && !force_float {
workspace.layer = WorkspaceLayer::Tiling;
self.unfloat_window()?;
} else {
workspace.layer = WorkspaceLayer::Floating;
self.float_window()?;
}
self.update_focused_workspace(is_floating_window, true)
}
#[tracing::instrument(skip(self))]
pub fn toggle_lock(&mut self) -> eyre::Result<()> {
let workspace = self.focused_workspace_mut()?;
if let Some(container) = workspace.focused_container_mut() {
// Toggle the locked flag
container.locked = !container.locked;
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn float_window(&mut self) -> eyre::Result<()> {
tracing::info!("floating window");
let work_area = self.focused_monitor_work_area()?;
let toggle_float_placement = self.window_management_behaviour.toggle_float_placement;
let workspace = self.focused_workspace_mut()?;
workspace.new_floating_window()?;
let window = workspace
.floating_windows_mut()
.back_mut()
.ok_or_eyre("there is no floating window")?;
if toggle_float_placement.should_center() {
window.center(&work_area, toggle_float_placement.should_resize())?;
}
window.focus(self.mouse_follows_focus)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn unfloat_window(&mut self) -> eyre::Result<()> {
tracing::info!("unfloating window");
let workspace = self.focused_workspace_mut()?;
workspace.new_container_for_floating_window()
}
#[tracing::instrument(skip(self))]
pub fn toggle_monocle(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace()?;
match workspace.monocle_container {
None => self.monocle_on()?,
Some(_) => self.monocle_off()?,
}
self.update_focused_workspace(true, true)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn monocle_on(&mut self) -> eyre::Result<()> {
tracing::info!("enabling monocle");
let workspace = self.focused_workspace_mut()?;
workspace.new_monocle_container()?;
for container in workspace.containers_mut() {
container.hide(None);
}
for window in workspace.floating_windows_mut() {
window.hide();
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn monocle_off(&mut self) -> eyre::Result<()> {
tracing::info!("disabling monocle");
let workspace = self.focused_workspace_mut()?;
for container in workspace.containers_mut() {
container.restore();
}
for window in workspace.floating_windows_mut() {
window.restore();
}
workspace.reintegrate_monocle_container()
}
#[tracing::instrument(skip(self))]
pub fn cycle_monocle(&mut self, direction: CycleDirection) -> eyre::Result<()> {
tracing::info!("cycling monocle container");
if self.focused_workspace()?.containers().is_empty() {
return Ok(());
}
self.focused_workspace_mut()?
.cycle_monocle_container(direction)?;
for container in self.focused_workspace_mut()?.containers_mut() {
container.hide(None);
}
// borders were getting funny during cycles, can't be bothered to root cause it
border_manager::destroy_all_borders()?;
self.update_focused_workspace(true, true)
}
#[tracing::instrument(skip(self))]
pub fn toggle_maximize(&mut self) -> eyre::Result<()> {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace_mut()?;
match workspace.maximized_window {
None => self.maximize_window()?,
Some(_) => self.unmaximize_window()?,
}
self.update_focused_workspace(true, false)
}
#[tracing::instrument(skip(self))]
pub fn maximize_window(&mut self) -> eyre::Result<()> {
tracing::info!("maximizing windowj");
let workspace = self.focused_workspace_mut()?;
workspace.new_maximized_window()
}
#[tracing::instrument(skip(self))]
pub fn unmaximize_window(&mut self) -> eyre::Result<()> {
tracing::info!("unmaximizing window");
let workspace = self.focused_workspace_mut()?;
workspace.reintegrate_maximized_window()
}
#[tracing::instrument(skip(self))]
pub fn flip_layout(&mut self, layout_flip: Axis) -> eyre::Result<()> {
let workspace = self.focused_workspace_mut()?;
tracing::info!("flipping layout");
#[allow(clippy::match_same_arms)]
match workspace.layout_flip {
None => {
workspace.layout_flip = Option::from(layout_flip);
}
Some(current_layout_flip) => {
match current_layout_flip {
Axis::Horizontal => match layout_flip {
Axis::Horizontal => workspace.layout_flip = None,
Axis::Vertical => {
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
}
Axis::HorizontalAndVertical => {
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
}
},
Axis::Vertical => match layout_flip {
Axis::Horizontal => {
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
}
Axis::Vertical => workspace.layout_flip = None,
Axis::HorizontalAndVertical => {
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
}
},
Axis::HorizontalAndVertical => match layout_flip {
Axis::Horizontal => workspace.layout_flip = Option::from(Axis::Vertical),
Axis::Vertical => workspace.layout_flip = Option::from(Axis::Horizontal),
Axis::HorizontalAndVertical => workspace.layout_flip = None,
},
};
}
}
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> eyre::Result<()> {
tracing::info!("changing layout");
let monitor_count = self.monitors().len();
let workspace = self.focused_workspace_mut()?;
if monitor_count > 1 && matches!(layout, DefaultLayout::Scrolling) {
tracing::warn!(
"scrolling layout is only supported for a single monitor; not changing layout"
);
return Ok(());
}
match &workspace.layout {
Layout::Default(_) => {}
Layout::Custom(layout) => {
let primary_idx = layout.first_container_idx(
layout
.primary_idx()
.ok_or_eyre("this custom layout does not have a primary column")?,
);
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
{
workspace.swap_containers(0, primary_idx);
}
}
}
workspace.layout = Layout::Default(layout);
self.update_focused_workspace(self.mouse_follows_focus, false)
}
#[tracing::instrument(skip(self))]
pub fn cycle_layout(&mut self, direction: CycleDirection) -> eyre::Result<()> {
tracing::info!("cycling layout");
let workspace = self.focused_workspace_mut()?;
let current_layout = &workspace.layout;
match current_layout {
Layout::Default(current) => {
let new_layout = match direction {
CycleDirection::Previous => current.cycle_previous(),
CycleDirection::Next => current.cycle_next(),
};
tracing::info!("next layout: {new_layout}");
workspace.layout = Layout::Default(new_layout);
}
Layout::Custom(_) => {}
}
self.update_focused_workspace(self.mouse_follows_focus, false)
}
#[tracing::instrument(skip(self))]
pub fn change_workspace_custom_layout<P>(&mut self, path: P) -> eyre::Result<()>
where
P: AsRef<Path> + std::fmt::Debug,
{
tracing::info!("changing layout");
let layout = CustomLayout::from_path(path)?;
let workspace = self.focused_workspace_mut()?;
match workspace.layout {
Layout::Default(_) => {
let primary_idx = layout.first_container_idx(
layout
.primary_idx()
.ok_or_eyre("this custom layout does not have a primary column")?,
);
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
{
workspace.swap_containers(0, primary_idx);
}
}
Layout::Custom(_) => {}
}
workspace.layout = Layout::Custom(layout);
workspace.layout_flip = None;
self.update_focused_workspace(self.mouse_follows_focus, false)
}
#[tracing::instrument(skip(self))]
pub fn adjust_workspace_padding(
&mut self,
sizing: Sizing,
adjustment: i32,
) -> eyre::Result<()> {
tracing::info!("adjusting workspace padding");
let workspace = self.focused_workspace_mut()?;
let padding = workspace
.workspace_padding
.ok_or_eyre("there is no workspace padding")?;
workspace.workspace_padding = Option::from(sizing.adjust_by(padding, adjustment));
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn adjust_container_padding(
&mut self,
sizing: Sizing,
adjustment: i32,
) -> eyre::Result<()> {
tracing::info!("adjusting container padding");
let workspace = self.focused_workspace_mut()?;
let padding = workspace
.container_padding
.ok_or_eyre("there is no container padding")?;
workspace.container_padding = Option::from(sizing.adjust_by(padding, adjustment));
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn set_workspace_tiling(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
tile: bool,
) -> eyre::Result<()> {
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.tile = tile;
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn add_workspace_layout_default_rule(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
at_container_count: usize,
layout: DefaultLayout,
) -> eyre::Result<()> {
tracing::info!("setting workspace layout");
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let focused_workspace_idx = monitor.focused_workspace_idx();
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
rules.retain(|pair| pair.0 != at_container_count);
rules.push((at_container_count, Layout::Default(layout)));
rules.sort_by_key(|a| a.0);
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update()?;
Ok(())
} else {
Ok(self.update_focused_workspace(false, false)?)
}
}
#[tracing::instrument(skip(self))]
pub fn add_workspace_layout_custom_rule<P>(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
at_container_count: usize,
path: P,
) -> eyre::Result<()>
where
P: AsRef<Path> + std::fmt::Debug,
{
tracing::info!("setting workspace layout");
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let focused_workspace_idx = monitor.focused_workspace_idx();
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
let layout = CustomLayout::from_path(path)?;
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
rules.retain(|pair| pair.0 != at_container_count);
rules.push((at_container_count, Layout::Custom(layout)));
rules.sort_by_key(|a| a.0);
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update()?;
Ok(())
} else {
Ok(self.update_focused_workspace(false, false)?)
}
}
#[tracing::instrument(skip(self))]
pub fn clear_workspace_layout_rules(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
) -> eyre::Result<()> {
tracing::info!("setting workspace layout");
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let focused_workspace_idx = monitor.focused_workspace_idx();
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
rules.clear();
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update()?;
Ok(())
} else {
Ok(self.update_focused_workspace(false, false)?)
}
}
#[tracing::instrument(skip(self))]
pub fn set_workspace_layout_default(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
layout: DefaultLayout,
) -> eyre::Result<()> {
tracing::info!("setting workspace layout");
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let focused_workspace_idx = monitor.focused_workspace_idx();
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.layout = Layout::Default(layout);
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update()?;
Ok(())
} else {
Ok(self.update_focused_workspace(false, false)?)
}
}
#[tracing::instrument(skip(self))]
pub fn set_workspace_layout_custom<P>(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
path: P,
) -> eyre::Result<()>
where
P: AsRef<Path> + std::fmt::Debug,
{
tracing::info!("setting workspace layout");
let layout = CustomLayout::from_path(path)?;
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let focused_workspace_idx = monitor.focused_workspace_idx();
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.layout = Layout::Custom(layout);
workspace.layout_flip = None;
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update()?;
Ok(())
} else {
Ok(self.update_focused_workspace(false, false)?)
}
}
#[tracing::instrument(skip(self))]
pub fn ensure_workspaces_for_monitor(
&mut self,
monitor_idx: usize,
workspace_count: usize,
) -> eyre::Result<()> {
tracing::info!("ensuring workspace count");
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
monitor.ensure_workspace_count(workspace_count);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn ensure_named_workspaces_for_monitor(
&mut self,
monitor_idx: usize,
names: &Vec<String>,
) -> eyre::Result<()> {
tracing::info!("ensuring workspace count");
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
monitor.ensure_workspace_count(names.len());
for (workspace_idx, name) in names.iter().enumerate() {
if let Some(workspace) = monitor.workspaces_mut().get_mut(workspace_idx) {
workspace.name = Option::from(name.clone());
}
}
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn set_workspace_padding(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
size: i32,
) -> eyre::Result<()> {
tracing::info!("setting workspace padding");
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.workspace_padding = Option::from(size);
self.update_focused_workspace(false, false)
}
#[tracing::instrument(skip(self))]
pub fn set_workspace_name(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
name: String,
) -> eyre::Result<()> {
tracing::info!("setting workspace name");
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.name = Option::from(name.clone());
monitor.workspace_names.insert(workspace_idx, name);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn set_container_padding(
&mut self,
monitor_idx: usize,
workspace_idx: usize,
size: i32,
) -> eyre::Result<()> {
tracing::info!("setting container padding");
let monitor = self
.monitors_mut()
.get_mut(monitor_idx)
.ok_or_eyre("there is no monitor")?;
let workspace = monitor
.workspaces_mut()
.get_mut(workspace_idx)
.ok_or_eyre("there is no monitor")?;
workspace.container_padding = Option::from(size);
self.update_focused_workspace(false, false)
}
pub fn focused_monitor_size(&self) -> eyre::Result<Rect> {
Ok(self
.focused_monitor()
.ok_or_eyre("there is no monitor")?
.size)
}
pub fn focused_monitor_work_area(&self) -> eyre::Result<Rect> {
Ok(self
.focused_monitor()
.ok_or_eyre("there is no monitor")?
.work_area_size)
}
#[tracing::instrument(skip(self))]
pub fn focus_monitor(&mut self, idx: usize) -> eyre::Result<()> {
tracing::info!("focusing monitor");
if self.monitors().get(idx).is_some() {
self.monitors.focus(idx);
} else {
bail!("this is not a valid monitor index");
}
Ok(())
}
pub fn monitor_idx_from_window(&mut self, window: Window) -> Option<usize> {
let hmonitor = WindowsApi::monitor_from_window(window.hwnd);
for (i, monitor) in self.monitors().iter().enumerate() {
if monitor.id == hmonitor {
return Option::from(i);
}
}
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
// info taken from win32_display_data and update our hmonitor while we're at it
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
if monitor.device_id == latest.device_id {
monitor.id = latest.id;
return Option::from(i);
}
}
}
None
}
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
for (i, monitor) in self.monitors().iter().enumerate() {
if monitor.id == hmonitor {
return Option::from(i);
}
}
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
// info taken from win32_display_data and update our hmonitor while we're at it
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
if monitor.device_id == latest.device_id {
monitor.id = latest.id;
return Option::from(i);
}
}
}
None
}
pub fn focused_workspace_idx(&self) -> eyre::Result<usize> {
Ok(self
.focused_monitor()
.ok_or_eyre("there is no monitor")?
.focused_workspace_idx())
}
pub fn focused_workspace(&self) -> eyre::Result<&Workspace> {
self.focused_monitor()
.ok_or_eyre("there is no monitor")?
.focused_workspace()
.ok_or_eyre("there is no workspace")
}
pub fn focused_workspace_mut(&mut self) -> eyre::Result<&mut Workspace> {
self.focused_monitor_mut()
.ok_or_eyre("there is no monitor")?
.focused_workspace_mut()
.ok_or_eyre("there is no workspace")
}
pub fn focused_workspace_idx_for_monitor_idx(&self, idx: usize) -> eyre::Result<usize> {
Ok(self
.monitors()
.get(idx)
.ok_or_eyre("there is no monitor at this index")?
.focused_workspace_idx())
}
pub fn focused_workspace_for_monitor_idx(&self, idx: usize) -> eyre::Result<&Workspace> {
self.monitors()
.get(idx)
.ok_or_eyre("there is no monitor at this index")?
.focused_workspace()
.ok_or_eyre("there is no workspace")
}
pub fn focused_workspace_for_monitor_idx_mut(
&mut self,
idx: usize,
) -> eyre::Result<&mut Workspace> {
self.monitors_mut()
.get_mut(idx)
.ok_or_eyre("there is no monitor at this index")?
.focused_workspace_mut()
.ok_or_eyre("there is no workspace")
}
#[tracing::instrument(skip(self))]
pub fn focus_workspace(&mut self, idx: usize) -> eyre::Result<()> {
tracing::info!("focusing workspace");
let mouse_follows_focus = self.mouse_follows_focus;
let monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no workspace")?;
monitor.focus_workspace(idx)?;
monitor.load_focused_workspace(mouse_follows_focus)?;
self.update_focused_workspace(false, true)
}
#[tracing::instrument(skip(self))]
pub fn monitor_workspace_index_by_name(&mut self, name: &str) -> Option<(usize, usize)> {
tracing::info!("looking up workspace by name");
for (monitor_idx, monitor) in self.monitors().iter().enumerate() {
for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() {
if let Some(workspace_name) = &workspace.name
&& workspace_name == name
{
return Option::from((monitor_idx, workspace_idx));
}
}
}
None
}
#[tracing::instrument(skip(self))]
pub fn new_workspace(&mut self) -> eyre::Result<()> {
tracing::info!("adding new workspace");
let mouse_follows_focus = self.mouse_follows_focus;
let monitor = self
.focused_monitor_mut()
.ok_or_eyre("there is no workspace")?;
monitor.focus_workspace(monitor.new_workspace_idx())?;
monitor.load_focused_workspace(mouse_follows_focus)?;
self.update_focused_workspace(self.mouse_follows_focus, false)
}
pub fn focused_container(&self) -> eyre::Result<&Container> {
self.focused_workspace()?
.focused_container()
.ok_or_eyre("there is no container")
}
pub fn focused_container_idx(&self) -> eyre::Result<usize> {
Ok(self.focused_workspace()?.focused_container_idx())
}
pub fn focused_container_mut(&mut self) -> eyre::Result<&mut Container> {
self.focused_workspace_mut()?
.focused_container_mut()
.ok_or_eyre("there is no container")
}
pub fn focused_window(&self) -> eyre::Result<&Window> {
self.focused_container()?
.focused_window()
.ok_or_eyre("there is no window")
}
fn focused_window_mut(&mut self) -> eyre::Result<&mut Window> {
self.focused_container_mut()?
.focused_window_mut()
.ok_or_eyre("there is no window")
}
/// Updates the list of `known_hwnds` and their monitor/workspace index pair
///
/// [`known_hwnds`]: `Self.known_hwnds`
pub fn update_known_hwnds(&mut self) {
tracing::trace!("updating list of known hwnds");
let mut known_hwnds = HashMap::new();
for (m_idx, monitor) in self.monitors().iter().enumerate() {
for (w_idx, workspace) in monitor.workspaces().iter().enumerate() {
for container in workspace.containers() {
for window in container.windows() {
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
}
}
for window in workspace.floating_windows() {
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
}
if let Some(window) = workspace.maximized_window {
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
}
if let Some(container) = &workspace.monocle_container {
for window in container.windows() {
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
}
}
}
}
if self.known_hwnds != known_hwnds {
// Update reaper cache
{
let mut reaper_cache = crate::reaper::HWNDS_CACHE.lock();
*reaper_cache = known_hwnds.clone();
}
// Save to file
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
match OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(hwnd_json)
{
Ok(file) => {
if let Err(error) =
serde_json::to_writer_pretty(&file, &known_hwnds.keys().collect::<Vec<_>>())
{
tracing::error!("Failed to save list of known_hwnds on file: {}", error);
}
}
Err(error) => {
tracing::error!("Failed to save list of known_hwnds on file: {}", error);
}
}
// Store new hwnds
self.known_hwnds = known_hwnds;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::monitor;
use crossbeam_channel::Sender;
use crossbeam_channel::bounded;
use std::path::PathBuf;
use uuid::Uuid;
struct TestContext {
socket_path: Option<PathBuf>,
}
impl Drop for TestContext {
fn drop(&mut self) {
if let Some(socket_path) = &self.socket_path {
// Clean up the socket file
std::fs::remove_file(socket_path).unwrap();
}
}
}
fn setup_window_manager() -> (WindowManager, TestContext) {
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
bounded(1);
// Temporary socket path for testing
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
let socket_path = PathBuf::from(socket_name);
// Create a new WindowManager instance
let wm = WindowManager::new(receiver, Some(socket_path.clone()));
// Window Manager should be created successfully
assert!(wm.is_ok());
(
wm.unwrap(),
TestContext {
socket_path: Some(socket_path),
},
)
}
#[test]
fn test_create_window_manager() {
let (_wm, _test_context) = setup_window_manager();
}
#[test]
fn test_focus_workspace() {
let (mut wm, _test_context) = setup_window_manager();
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// a new monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// the next index on the monitor should be the not-yet-created second workspace
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
{
// focusing a workspace which doesn't yet exist should create it
let monitor = wm.focused_monitor_mut().unwrap();
monitor.focus_workspace(new_workspace_index).unwrap();
assert_eq!(monitor.workspaces().len(), 2);
}
assert_eq!(wm.focused_workspace_idx().unwrap(), 1);
{
// focusing a workspace many indices ahead should create all workspaces
// required along the way
let monitor = wm.focused_monitor_mut().unwrap();
monitor.focus_workspace(new_workspace_index + 2).unwrap();
assert_eq!(monitor.workspaces().len(), 4);
}
assert_eq!(wm.focused_workspace_idx().unwrap(), 3);
// we should be able to successfully focus an existing workspace too
wm.focus_workspace(0).unwrap();
assert_eq!(wm.focused_workspace_idx().unwrap(), 0);
}
#[test]
fn test_remove_focused_workspace() {
let (mut wm, _context) = setup_window_manager();
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// a new monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// the next index on the monitor should be the not-yet-created second workspace
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
{
// focus a workspace which doesn't yet exist should create it
let monitor = wm.focused_monitor_mut().unwrap();
monitor.focus_workspace(new_workspace_index + 1).unwrap();
// Monitor focused workspace should be 2
assert_eq!(monitor.focused_workspace_idx(), 2);
// Should have 3 Workspaces
assert_eq!(monitor.workspaces().len(), 3);
}
// Remove the focused workspace
wm.remove_focused_workspace().unwrap();
{
let monitor = wm.focused_monitor_mut().unwrap();
monitor.focus_workspace(new_workspace_index).unwrap();
// Should be focused on workspace 1
assert_eq!(monitor.focused_workspace_idx(), 1);
// Should have 2 Workspaces
assert_eq!(monitor.workspaces().len(), 2);
}
}
#[test]
fn test_set_workspace_name() {
let (mut wm, _test_context) = setup_window_manager();
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// a new monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// create a new workspace
let new_workspace_index = m.new_workspace_idx();
assert_eq!(new_workspace_index, 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
{
// focusing a workspace which doesn't yet exist should create it
let monitor = wm.focused_monitor_mut().unwrap();
monitor.focus_workspace(new_workspace_index).unwrap();
assert_eq!(monitor.workspaces().len(), 2);
}
assert_eq!(wm.focused_workspace_idx().unwrap(), 1);
// set the name of the first workspace
wm.set_workspace_name(0, 0, "workspace1".to_string())
.unwrap();
// monitor_workspace_index_by_name should return the index of the workspace with the name "workspace1"
let workspace_index = wm.monitor_workspace_index_by_name("workspace1").unwrap();
// workspace index 0 should now have the name "workspace1"
assert_eq!(workspace_index.1, 0);
}
#[test]
fn test_switch_focus_monitors() {
let (mut wm, _test_context) = setup_window_manager();
{
// Create a first monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
assert_eq!(wm.monitors().len(), 1);
{
// Create a second monitor
let m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
assert_eq!(wm.monitors().len(), 2);
{
// Create a third monitor
let m = monitor::new(
2,
Rect::default(),
Rect::default(),
"TestMonitor3".to_string(),
"TestDevice3".to_string(),
"TestDeviceID3".to_string(),
Some("TestMonitorID3".to_string()),
);
// monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
assert_eq!(wm.monitors().len(), 3);
{
// Set the first monitor as focused and check if it is focused
wm.focus_monitor(0).unwrap();
let current_monitor_idx = wm.monitors.focused_idx();
assert_eq!(current_monitor_idx, 0);
}
{
// Set the second monitor as focused and check if it is focused
wm.focus_monitor(1).unwrap();
let current_monitor_idx = wm.monitors.focused_idx();
assert_eq!(current_monitor_idx, 1);
}
{
// Set the third monitor as focused and check if it is focused
wm.focus_monitor(2).unwrap();
let current_monitor_idx = wm.monitors.focused_idx();
assert_eq!(current_monitor_idx, 2);
}
// Switch back to the first monitor
wm.focus_monitor(0).unwrap();
let current_monitor_idx = wm.monitors.focused_idx();
assert_eq!(current_monitor_idx, 0);
}
#[test]
fn test_switch_focus_to_nonexistent_monitor() {
let (mut wm, _test_context) = setup_window_manager();
{
// Create a first monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should have 1 monitor and the monitor index should be 0
assert_eq!(wm.monitors().len(), 1);
assert_eq!(wm.focused_monitor_idx(), 0);
// Should receive an error when trying to focus a non-existent monitor
let result = wm.focus_monitor(1);
assert!(
result.is_err(),
"Expected an error when focusing a non-existent monitor"
);
// Should still be focused on the first monitor
assert_eq!(wm.focused_monitor_idx(), 0);
}
#[test]
fn test_focused_monitor_size() {
let (mut wm, _test_context) = setup_window_manager();
{
// Create a first monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// monitor should have a single workspace
assert_eq!(m.workspaces().len(), 1);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
assert_eq!(wm.monitors().len(), 1);
{
// Set the first monitor as focused and check if it is focused
wm.focus_monitor(0).unwrap();
let current_monitor_size = wm.focused_monitor_size().unwrap();
assert_eq!(current_monitor_size, Rect::default());
}
}
#[test]
fn test_focus_container_in_cycle_direction() {
let (mut wm, _test_context) = setup_window_manager();
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let workspace = m.focused_workspace_mut().unwrap();
workspace.layer = WorkspaceLayer::Tiling;
for i in 0..4 {
let mut container = Container::default();
container.windows_mut().push_back(Window::from(i));
workspace.add_container_to_back(container);
}
assert_eq!(workspace.containers().len(), 4);
workspace.focus_container(0);
// add the monitor to the window manager
wm.monitors_mut().push_back(m);
// container focus should be on the second container
wm.focus_container_in_cycle_direction(CycleDirection::Next)
.ok();
assert_eq!(wm.focused_container_idx().unwrap(), 1);
// container focus should be on the third container
wm.focus_container_in_cycle_direction(CycleDirection::Next)
.ok();
assert_eq!(wm.focused_container_idx().unwrap(), 2);
// container focus should be on the second container
wm.focus_container_in_cycle_direction(CycleDirection::Previous)
.ok();
assert_eq!(wm.focused_container_idx().unwrap(), 1);
// container focus should be on the first container
wm.focus_container_in_cycle_direction(CycleDirection::Previous)
.ok();
assert_eq!(wm.focused_container_idx().unwrap(), 0);
}
#[test]
fn test_transfer_window() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(0));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
{
// Create a second monitor
let mut m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
// Should contain 2 monitors
assert_eq!(wm.monitors().len(), 2);
{
// Monitor 0, Workspace 0, Window 0
let origin = (0, 0, 0);
// Monitor 1, Workspace 0, Window 0
let target = (1, 0, 0);
// Transfer the window from monitor 0 to monitor 1
wm.transfer_window(origin, target).unwrap();
// Monitor 1 should contain 0 containers
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 0);
// Monitor 2 should contain 2 containers
wm.focus_monitor(1).unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 2);
}
{
// Monitor 1, Workspace 0, Window 0
let origin = (1, 0, 0);
// Monitor 0, Workspace 0, Window 0
let target = (0, 0, 0);
// Transfer the window from monitor 1 back to monitor 0
wm.transfer_window(origin, target).unwrap();
// Monitor 2 should contain 1 containers
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
// Monitor 1 should contain 1 containers
wm.focus_monitor(0).unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
}
}
#[test]
fn test_transfer_window_to_nonexistent_monitor() {
// NOTE: transfer_window is primarily used when a window is being dragged by a mouse. The
// transfer_window function does return an error when the target monitor doesn't exist but
// there is a bug where the window isn't in the container after the window fails to
// transfer. The test will test for the result of the transfer_window function but not if
// the window is in the container after the transfer fails.
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(0));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
{
// Monitor 0, Workspace 0, Window 0
let origin = (0, 0, 0);
// Monitor 1, Workspace 0, Window 0
//
let target = (1, 0, 0);
// Attempt to transfer the window from monitor 0 to a non-existent monitor
let result = wm.transfer_window(origin, target);
// Result should be an error since the monitor doesn't exist
assert!(
result.is_err(),
"Expected an error when transferring to a non-existent monitor"
);
assert_eq!(wm.focused_container_idx().unwrap(), 0);
assert_eq!(wm.focused_workspace_idx().unwrap(), 0);
}
}
#[test]
fn test_transfer_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(0));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
{
// Create a second monitor
let mut m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
// Should contain 2 monitors
assert_eq!(wm.monitors().len(), 2);
{
// Monitor 0, Workspace 0, Window 0
let origin = (0, 0, 0);
// Monitor 1, Workspace 0, Window 0
let target = (1, 0, 0);
// Transfer the window from monitor 0 to monitor 1
wm.transfer_container(origin, target).unwrap();
// Monitor 1 should contain 0 containers
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 0);
// Monitor 2 should contain 2 containers
wm.focus_monitor(1).unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 2);
}
{
// Monitor 1, Workspace 0, Window 0
let origin = (1, 0, 0);
// Monitor 0, Workspace 0, Window 0
let target = (0, 0, 0);
// Transfer the window from monitor 1 back to monitor 0
wm.transfer_container(origin, target).unwrap();
// Monitor 2 should contain 1 containers
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
// Monitor 1 should contain 1 containers
wm.focus_monitor(0).unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
}
}
#[test]
fn test_remove_window_from_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor1".to_string(),
"TestDevice1".to_string(),
"TestDeviceID1".to_string(),
Some("TestMonitorID1".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Focus last window
container.focus_window(2);
// Should be focused on the 2nd window
assert_eq!(container.focused_window_idx(), 2);
// Add the container to a workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Remove the focused window from the container
wm.remove_window_from_container().ok();
{
// Should have 2 containers in the workspace
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 2);
// Should contain 1 window in the new container
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
{
// Switch to the old container
let workspace = wm.focused_workspace_mut().unwrap();
workspace.focus_container(0);
// Should contain 2 windows in the old container
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 2);
}
}
#[test]
fn test_remove_nonexistent_window_from_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor1".to_string(),
"TestDevice1".to_string(),
"TestDeviceID1".to_string(),
Some("TestMonitorID1".to_string()),
);
// Create a container
let container = Container::default();
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 0);
// Add the container to a workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should receive an error when trying to remove a window from an empty container
let result = wm.remove_window_from_container();
assert!(
result.is_err(),
"Expected an error when trying to remove a window from an empty container"
);
}
#[test]
fn cycle_container_window_in_direction() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let workspace = m.focused_workspace_mut().unwrap();
{
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add container to workspace
workspace.add_container_to_back(container);
}
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Cycle to the next window
wm.cycle_container_window_in_direction(CycleDirection::Next)
.ok();
{
// Should be on Window 1
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 1);
}
// Cycle to the next window
wm.cycle_container_window_in_direction(CycleDirection::Next)
.ok();
{
// Should be on Window 2
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 2);
}
// Cycle to the previous window
wm.cycle_container_window_in_direction(CycleDirection::Previous)
.ok();
{
// Should be on Window 1
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 1);
}
}
#[test]
fn test_cycle_nonexistent_windows() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor1".to_string(),
"TestDevice1".to_string(),
"TestDeviceID1".to_string(),
Some("TestMonitorID1".to_string()),
);
// Create a container
let container = Container::default();
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 0);
// Add the container to a workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should return an error when trying to cycle through windows in an empty container
let result = wm.cycle_container_window_in_direction(CycleDirection::Next);
assert!(
result.is_err(),
"Expected an error when cycling through windows in an empty container"
);
}
#[test]
fn test_cycle_container_window_index_in_direction() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let workspace = m.focused_workspace_mut().unwrap();
{
let mut container = Container::default();
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add container to workspace
workspace.add_container_to_back(container);
}
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Cycle to the next window
wm.cycle_container_window_index_in_direction(CycleDirection::Next)
.ok();
{
// Should be on Window 1
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 1);
}
// Cycle to the next window
wm.cycle_container_window_index_in_direction(CycleDirection::Next)
.ok();
{
// Should be on Window 2
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 2);
}
// Cycle to the Previous window
wm.cycle_container_window_index_in_direction(CycleDirection::Previous)
.ok();
{
// Should be on Window 1
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.focused_window_idx(), 1);
}
}
#[test]
fn test_swap_containers() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
{
// Create a second monitor
let mut m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
wm.monitors_mut().push_back(m);
}
// Should contain 2 monitors
assert_eq!(wm.monitors().len(), 2);
// Monitor 0, Workspace 0, Window 0
let origin = (0, 0, 0);
// Monitor 1, Workspace 0, Window 0
let target = (1, 0, 0);
wm.swap_containers(origin, target).unwrap();
{
// Monitor 0 Workspace 0 container 0 should contain 1 container
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
wm.focus_monitor(1).unwrap();
{
// Monitor 1 Workspace 0 container 0 should contain 3 containers
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 3);
}
}
#[test]
fn test_swap_container_with_nonexistent_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Monitor 0, Workspace 0, Window 0
let origin = (0, 0, 0);
// Monitor 1, Workspace 0, Window 0
let target = (0, 3, 0);
// Should be focused on the first container
assert_eq!(wm.focused_container_idx().unwrap(), 0);
// Should return an error since there is only one container in the workspace
let result = wm.swap_containers(origin, target);
assert!(
result.is_err(),
"Expected an error when swapping with a non-existent container"
);
// Should still be focused on the first container
assert_eq!(wm.focused_container_idx().unwrap(), 0);
{
// Should still have 1 container in the workspace
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.containers().len(), 1);
// Container should still have 3 windows
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 3);
}
}
#[test]
fn test_swap_monitor_workspaces() {
let (mut wm, _context) = setup_window_manager();
{
// Create a first monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
{
// Create a second monitor
let mut m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// Create a container
let workspace = m.focused_workspace_mut().unwrap();
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
workspace.add_container_to_back(container);
// Should contain 1 container
assert_eq!(workspace.containers().len(), 1);
// Add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Swap the workspaces between Monitor 0 and Monitor 1
wm.swap_monitor_workspaces(0, 1).ok();
{
// The focused workspace container in Monitor 0 should contain 3 containers
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 1);
}
// Switch to Monitor 1
wm.focus_monitor(1).unwrap();
assert_eq!(wm.focused_monitor_idx(), 1);
{
// The focused workspace container in Monitor 1 should contain 3 containers
let workspace = wm.focused_workspace_mut().unwrap();
let container = workspace.focused_container_mut().unwrap();
assert_eq!(container.windows().len(), 3);
}
}
#[test]
fn test_swap_workspace_with_nonexistent_monitor() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add another workspace
let new_workspace_index = m.new_workspace_idx();
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Add monitor to window manager
wm.monitors_mut().push_back(m);
}
// Should be an error since Monitor 1 does not exist
let result = wm.swap_monitor_workspaces(1, 0);
assert!(
result.is_err(),
"Expected an error when swapping with a non-existent monitor"
);
{
// Should still have 2 workspaces in Monitor 0
let monitor = wm.monitors().front().unwrap();
let workspaces = monitor.workspaces();
assert_eq!(
workspaces.len(),
2,
"Expected 2 workspaces after swap attempt"
);
assert_eq!(wm.focused_monitor_idx(), 0);
}
}
#[test]
fn test_move_workspace_to_monitor() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add another workspace
let new_workspace_index = m.new_workspace_idx();
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Add monitor to window manager
wm.monitors_mut().push_back(m);
}
{
let m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor2".to_string(),
"TestDevice2".to_string(),
"TestDeviceID2".to_string(),
Some("TestMonitorID2".to_string()),
);
// Should contain 1 workspace
assert_eq!(m.workspaces().len(), 1);
// Add monitor to workspace
wm.monitors_mut().push_back(m);
}
// Should contain 2 monitors
assert_eq!(wm.monitors().len(), 2);
// Move a workspace from Monitor 0 to Monitor 1
wm.move_workspace_to_monitor(1).ok();
{
// Should be focused on Monitor 1
assert_eq!(wm.focused_monitor_idx(), 1);
// Should contain 2 workspaces
let monitor = wm.focused_monitor_mut().unwrap();
assert_eq!(monitor.workspaces().len(), 2);
}
{
// Switch to Monitor 0
wm.focus_monitor(0).unwrap();
// Should contain 1 workspace
let monitor = wm.focused_monitor_mut().unwrap();
assert_eq!(monitor.workspaces().len(), 1);
}
}
#[test]
fn test_move_workspace_to_nonexistent_monitor() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add another workspace
let new_workspace_index = m.new_workspace_idx();
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Add monitor to window manager
wm.monitors_mut().push_back(m);
}
// Attempt to move a workspace to a non-existent monitor
let result = wm.move_workspace_to_monitor(1);
// Should be an error since Monitor 1 does not exist
assert!(
result.is_err(),
"Expected an error when moving to a non-existent monitor"
);
}
#[test]
fn test_toggle_tiling() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Set Workspace Layer to Tiling
let workspace = m.focused_workspace_mut().unwrap();
workspace.layer = WorkspaceLayer::Tiling;
// Tiling state should be true
assert!(workspace.tile);
// Add monitor to workspace
wm.monitors_mut().push_back(m);
}
{
// Tiling state should be false
wm.toggle_tiling().unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert!(!workspace.tile);
}
{
// Tiling state should be true
wm.toggle_tiling().unwrap();
let workspace = wm.focused_workspace_mut().unwrap();
assert!(workspace.tile);
}
}
#[test]
fn test_toggle_lock() {
let (mut wm, _context) = setup_window_manager();
{
// Add monitor with default workspace to
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
let workspace = m.focused_workspace_mut().unwrap();
// Create containers to add to the workspace
for _ in 0..3 {
let container = Container::default();
workspace.add_container_to_back(container);
}
wm.monitors_mut().push_back(m);
}
{
// Ensure container 2 is not locked
let workspace = wm.focused_workspace_mut().unwrap();
assert_eq!(workspace.focused_container_idx(), 2);
assert!(!workspace.focused_container().unwrap().locked);
}
// Toggle lock on focused container
wm.toggle_lock().unwrap();
{
// Ensure container 2 is locked
let workspace = wm.focused_workspace_mut().unwrap();
assert!(workspace.focused_container().unwrap().locked);
}
// Toggle lock on focused container
wm.toggle_lock().unwrap();
{
// Ensure container 2 is not locked
let workspace = wm.focused_workspace_mut().unwrap();
assert!(!workspace.focused_container().unwrap().locked);
}
}
#[test]
fn test_float_window() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Add focused window to floating window list
wm.float_window().ok();
{
let workspace = wm.focused_workspace().unwrap();
let floating_windows = workspace.floating_windows();
let container = workspace.focused_container().unwrap();
// Hwnd 0 should be added to floating_windows
assert_eq!(floating_windows[0].hwnd, 0);
// Should have a length of 1
assert_eq!(floating_windows.len(), 1);
// Should have 2 windows in the container
assert_eq!(container.windows().len(), 2);
// Should be focused on window 1
assert_eq!(container.focused_window(), Some(&Window { hwnd: 1 }));
}
// Add focused window to floating window list
wm.float_window().ok();
{
let workspace = wm.focused_workspace().unwrap();
let floating_windows = workspace.floating_windows();
let container = workspace.focused_container().unwrap();
// Hwnd 1 should be added to floating_windows
assert_eq!(floating_windows[1].hwnd, 1);
// Should have a length of 2
assert_eq!(floating_windows.len(), 2);
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// Should be focused on window 2
assert_eq!(container.focused_window(), Some(&Window { hwnd: 2 }));
}
}
#[test]
fn test_float_nonexistent_window() {
let (mut wm, _context) = setup_window_manager();
{
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add another workspace
let new_workspace_index = m.new_workspace_idx();
m.focus_workspace(new_workspace_index).unwrap();
// Should have 2 workspaces
assert_eq!(m.workspaces().len(), 2);
// Add monitor to window manager
wm.monitors_mut().push_back(m);
}
// Should return an error when trying to float a non-existent window
let result = wm.float_window();
assert!(
result.is_err(),
"Expected an error when trying to float a non-existent window"
);
}
#[test]
fn test_maximize_and_unmaximize_window() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, None);
}
// Maximize the focused window
wm.maximize_window().ok();
{
// Window 0 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, Some(Window::from(0)));
}
wm.unmaximize_window().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, None);
}
// Focus container at index 1
wm.focused_workspace_mut().unwrap().focus_container(1);
{
// Focus the window at index 1
let container = wm.focused_container_mut().unwrap();
container.focus_window(1);
}
// Maximize the focused window
wm.maximize_window().ok();
{
// Window 2 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, Some(Window::from(2)));
}
wm.unmaximize_window().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, None);
}
}
#[test]
fn test_toggle_maximize() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add three windows to the container
for i in 0..3 {
container.windows_mut().push_back(Window::from(i));
}
// Should have 3 windows in the container
assert_eq!(container.windows().len(), 3);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Toggle maximize on
wm.toggle_maximize().ok();
{
// Window 0 should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, Some(Window::from(0)));
}
// Toggle maximize off
wm.toggle_maximize().ok();
{
// No windows should be maximized
let workspace = wm.focused_workspace().unwrap();
let maximized_window = workspace.maximized_window;
assert_eq!(maximized_window, None);
}
}
#[test]
fn test_toggle_maximize_nonexistent_window() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let container = Container::default();
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should return an error when trying to toggle maximize on a non-existent window
let result = wm.toggle_maximize();
assert!(
result.is_err(),
"Expected an error when trying to toggle maximize on a non-existent window"
);
}
#[test]
fn test_monocle_on_and_monocle_off() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Move container to monocle container
wm.monocle_on().ok();
{
// Container should be a monocle container
let monocle_container = wm
.focused_workspace()
.unwrap()
.monocle_container
.as_ref()
.unwrap();
assert_eq!(monocle_container.windows().len(), 1);
assert_eq!(monocle_container.windows()[0].hwnd, 1);
}
{
// Should not have any containers
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 0);
}
// Move monocle container to regular container
wm.monocle_off().ok();
{
// Should have 1 container in the workspace
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 1);
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
}
{
// No windows should be in the monocle container
let monocle_container = &wm.focused_workspace().unwrap().monocle_container;
assert_eq!(*monocle_container, None);
}
}
#[test]
fn test_monocle_on_and_off_nonexistent_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should return an error when trying to move a non-existent container to monocle
let result = wm.monocle_on();
assert!(
result.is_err(),
"Expected an error when trying to move a non-existent container to monocle"
);
// Should return an error when trying to restore a non-existent container from monocle
let result = wm.monocle_off();
assert!(
result.is_err(),
"Expected an error when trying to restore a non-existent container from monocle"
);
}
#[test]
fn test_toggle_monocle() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let mut m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Create a container
let mut container = Container::default();
// Add a window to the container
container.windows_mut().push_back(Window::from(1));
// Should have 1 window in the container
assert_eq!(container.windows().len(), 1);
// Add the container to the workspace
let workspace = m.focused_workspace_mut().unwrap();
workspace.add_container_to_back(container);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Toggle monocle on
wm.toggle_monocle().ok();
{
// Container should be a monocle container
let monocle_container = wm
.focused_workspace()
.unwrap()
.monocle_container
.as_ref()
.unwrap();
assert_eq!(monocle_container.windows().len(), 1);
assert_eq!(monocle_container.windows()[0].hwnd, 1);
}
{
// Should not have any containers
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 0);
}
// Toggle monocle off
wm.toggle_monocle().ok();
{
// Should have 1 container in the workspace
let container = wm.focused_workspace().unwrap();
assert_eq!(container.containers().len(), 1);
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
}
{
// No windows should be in the monocle container
let monocle_container = &wm.focused_workspace().unwrap().monocle_container;
assert_eq!(*monocle_container, None);
}
}
#[test]
fn test_toggle_monocle_nonexistent_container() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Should return an error when trying to toggle monocle on a non-existent container
let result = wm.toggle_monocle();
assert!(
result.is_err(),
"Expected an error when trying to toggle monocle on a non-existent container"
);
}
#[test]
fn test_ensure_named_workspace_for_monitor() {
let (mut wm, _context) = setup_window_manager();
{
// Create a monitor
let m = monitor::new(
0,
Rect::default(),
Rect::default(),
"TestMonitor".to_string(),
"TestDevice".to_string(),
"TestDeviceID".to_string(),
Some("TestMonitorID".to_string()),
);
// Add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
{
// Create a monitor
let m = monitor::new(
1,
Rect::default(),
Rect::default(),
"TestMonitor1".to_string(),
"TestDevice1".to_string(),
"TestDeviceID1".to_string(),
Some("TestMonitorID1".to_string()),
);
// Add the monitor to the window manager
wm.monitors_mut().push_back(m);
}
// Workspace names list
let mut workspace_names = vec!["Workspace".to_string(), "Workspace1".to_string()];
// Ensure workspaces for monitor 1
wm.ensure_named_workspaces_for_monitor(1, &workspace_names)
.ok();
{
// Monitor 1 should have 2 workspaces with names "Workspace" and "Workspace1"
let monitor = wm.monitors().get(1).unwrap();
let workspaces = monitor.workspaces();
assert_eq!(workspaces.len(), workspace_names.len());
for (i, workspace) in workspaces.iter().enumerate() {
assert_eq!(workspace.name, Some(workspace_names[i].clone()));
}
}
// Add more workspaces to list
workspace_names.push("Workspace2".to_string());
workspace_names.push("Workspace3".to_string());
// Ensure workspaces for monitor 0
wm.ensure_named_workspaces_for_monitor(0, &workspace_names)
.ok();
{
// Monitor 0 should have 4 workspaces with names "Workspace", "Workspace1",
// "Workspace2" and "Workspace3"
let monitor = wm.monitors().front().unwrap();
let workspaces = monitor.workspaces();
assert_eq!(workspaces.len(), workspace_names.len());
for (i, workspace) in workspaces.iter().enumerate() {
assert_eq!(workspace.name, Some(workspace_names[i].clone()));
}
}
}
#[test]
fn test_add_window_handle_to_move_based_on_workspace_rule() {
let (wm, _context) = setup_window_manager();
// Mock Data representing a window and its workspace/movement details
let window_title = String::from("TestWindow");
let hwnd = 12345;
let origin_monitor_idx = 0;
let origin_workspace_idx = 0;
let target_monitor_idx = 2;
let target_workspace_idx = 3;
let floating = false;
// Empty vector to hold workspace rule enforcement operations
let mut to_move: Vec<EnforceWorkspaceRuleOp> = Vec::new();
// Call the function to add a window movement operation based on workspace rules
wm.add_window_handle_to_move_based_on_workspace_rule(
&window_title,
hwnd,
origin_monitor_idx,
origin_workspace_idx,
target_monitor_idx,
target_workspace_idx,
floating,
&mut to_move,
);
// Verify that the vector contains the expected operation with the correct values
assert_eq!(to_move.len(), 1);
let op = &to_move[0];
assert_eq!(op.hwnd, hwnd); // 12345
assert_eq!(op.origin_monitor_idx, origin_monitor_idx); // 0
assert_eq!(op.origin_workspace_idx, origin_workspace_idx); // 0
assert_eq!(op.target_monitor_idx, target_monitor_idx); // 2
assert_eq!(op.target_workspace_idx, target_workspace_idx); // 3
assert_eq!(op.floating, floating); // false
}
}