mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-31 14:33:29 +02:00
This commit adds a default_workspace_layout opt, which defaults to BSP to maintain backwards compatibility. This can also be set to "None". When set to "None" or omitted, the default behaviour for new or undefined workspaces (i.e. on monitors without config blocks) will be non-tiling. Otherwise, the given value will be the default layout applied.
5908 lines
209 KiB
Rust
5908 lines
209 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::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::core::custom_layout::CustomLayout;
|
|
|
|
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;
|
|
for (workspace_idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
|
|
if let Some(state_monitor) = state.monitors.elements().get(monitor_idx)
|
|
&& let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
|
|
{
|
|
// to make sure padding changes get applied for users after a quick restart
|
|
let container_padding = workspace.container_padding;
|
|
let workspace_padding = workspace.workspace_padding;
|
|
|
|
*workspace = state_workspace.clone();
|
|
|
|
workspace.container_padding = container_padding;
|
|
workspace.workspace_padding = workspace_padding;
|
|
|
|
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,
|
|
®ex_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,
|
|
®ex_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,
|
|
®ex_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(¤t_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(¤t_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");
|
|
|
|
let new_idx =
|
|
if workspace.maximized_window.is_some() || workspace.monocle_container.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 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(|a, b| a.0.cmp(&b.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(|a, b| a.0.cmp(&b.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::DEFAULT_WORKSPACE_LAYOUT;
|
|
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();
|
|
DEFAULT_WORKSPACE_LAYOUT.store(Some(DefaultLayout::BSP));
|
|
|
|
{
|
|
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
|
|
}
|
|
}
|