mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-04 09:54:17 +02:00
5943 lines
211 KiB
Rust
5943 lines
211 KiB
Rust
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::collections::VecDeque;
|
|
use std::env::temp_dir;
|
|
use std::fs::OpenOptions;
|
|
use std::io::ErrorKind;
|
|
use std::net::Shutdown;
|
|
use std::num::NonZeroUsize;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use color_eyre::eyre;
|
|
use color_eyre::eyre::OptionExt;
|
|
use color_eyre::eyre::bail;
|
|
use crossbeam_channel::Receiver;
|
|
use hotwatch::EventKind;
|
|
use hotwatch::Hotwatch;
|
|
use hotwatch::notify::ErrorKind as NotifyErrorKind;
|
|
use parking_lot::Mutex;
|
|
use uds_windows::UnixListener;
|
|
use uds_windows::UnixStream;
|
|
|
|
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
|
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
|
use crate::animation::AnimationEngine;
|
|
use crate::core::Arrangement;
|
|
use crate::core::Axis;
|
|
use crate::core::BorderImplementation;
|
|
use crate::core::CustomLayout;
|
|
use crate::core::CycleDirection;
|
|
use crate::core::DefaultLayout;
|
|
use crate::core::FocusFollowsMouseImplementation;
|
|
use crate::core::Layout;
|
|
use crate::core::MoveBehaviour;
|
|
use crate::core::OperationBehaviour;
|
|
use crate::core::OperationDirection;
|
|
use crate::core::Rect;
|
|
use crate::core::Sizing;
|
|
use crate::core::WindowContainerBehaviour;
|
|
use crate::core::WindowManagementBehaviour;
|
|
use crate::core::config_generation::MatchingRule;
|
|
|
|
use crate::CrossBoundaryBehaviour;
|
|
use crate::DATA_DIR;
|
|
use crate::HOME_DIR;
|
|
use crate::NO_TITLEBAR;
|
|
use crate::REGEX_IDENTIFIERS;
|
|
use crate::SUBSCRIPTION_SOCKETS;
|
|
use crate::WORKSPACE_MATCHING_RULES;
|
|
use crate::border_manager;
|
|
use crate::border_manager::BORDER_OFFSET;
|
|
use crate::border_manager::BORDER_WIDTH;
|
|
use crate::container::Container;
|
|
use crate::current_virtual_desktop;
|
|
use crate::load_configuration;
|
|
use crate::monitor::Monitor;
|
|
use crate::ring::Ring;
|
|
use crate::should_act;
|
|
use crate::should_act_individual;
|
|
use crate::state::State;
|
|
use crate::static_config::StaticConfig;
|
|
use crate::transparency_manager;
|
|
use crate::window::Window;
|
|
use crate::window_manager_event::WindowManagerEvent;
|
|
use crate::windows_api::WindowsApi;
|
|
use crate::winevent_listener;
|
|
use crate::workspace::Workspace;
|
|
use crate::workspace::WorkspaceLayer;
|
|
|
|
#[derive(Debug)]
|
|
pub struct WindowManager {
|
|
pub monitors: Ring<Monitor>,
|
|
pub monitor_usr_idx_map: HashMap<usize, usize>,
|
|
pub incoming_events: Receiver<WindowManagerEvent>,
|
|
pub command_listener: UnixListener,
|
|
pub is_paused: bool,
|
|
pub work_area_offset: Option<Rect>,
|
|
pub resize_delta: i32,
|
|
pub window_management_behaviour: WindowManagementBehaviour,
|
|
pub cross_monitor_move_behaviour: MoveBehaviour,
|
|
pub cross_boundary_behaviour: CrossBoundaryBehaviour,
|
|
pub unmanaged_window_operation_behaviour: OperationBehaviour,
|
|
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
|
pub mouse_follows_focus: bool,
|
|
pub hotwatch: Hotwatch,
|
|
pub virtual_desktop_id: Option<Vec<u8>>,
|
|
pub has_pending_raise_op: bool,
|
|
pub pending_move_op: Arc<Option<(usize, usize, isize)>>,
|
|
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
|
|
pub uncloack_to_ignore: usize,
|
|
/// Maps each known window hwnd to the (monitor, workspace) index pair managing it
|
|
pub known_hwnds: HashMap<isize, (usize, usize)>,
|
|
}
|
|
|
|
impl AsRef<Self> for WindowManager {
|
|
fn as_ref(&self) -> &Self {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl_ring_elements!(WindowManager, Monitor);
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct EnforceWorkspaceRuleOp {
|
|
hwnd: isize,
|
|
origin_monitor_idx: usize,
|
|
origin_workspace_idx: usize,
|
|
target_monitor_idx: usize,
|
|
target_workspace_idx: usize,
|
|
floating: bool,
|
|
}
|
|
impl EnforceWorkspaceRuleOp {
|
|
const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
|
self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx
|
|
}
|
|
|
|
const fn is_target(&self, monitor_idx: usize, workspace_idx: usize) -> bool {
|
|
self.target_monitor_idx == monitor_idx && self.target_workspace_idx == workspace_idx
|
|
}
|
|
|
|
const fn is_enforced(&self) -> bool {
|
|
(self.origin_monitor_idx == self.target_monitor_idx)
|
|
&& (self.origin_workspace_idx == self.target_workspace_idx)
|
|
}
|
|
}
|
|
|
|
impl WindowManager {
|
|
#[tracing::instrument]
|
|
pub fn new(
|
|
incoming: Receiver<WindowManagerEvent>,
|
|
custom_socket_path: Option<PathBuf>,
|
|
) -> eyre::Result<Self> {
|
|
let socket = custom_socket_path.unwrap_or_else(|| DATA_DIR.join("komorebi.sock"));
|
|
|
|
match std::fs::remove_file(&socket) {
|
|
Ok(()) => {}
|
|
Err(error) => match error.kind() {
|
|
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
|
ErrorKind::NotFound => {}
|
|
_ => {
|
|
return Err(error.into());
|
|
}
|
|
},
|
|
};
|
|
|
|
let listener = UnixListener::bind(&socket)?;
|
|
|
|
Ok(Self {
|
|
monitors: Ring::default(),
|
|
monitor_usr_idx_map: HashMap::new(),
|
|
incoming_events: incoming,
|
|
command_listener: listener,
|
|
is_paused: false,
|
|
virtual_desktop_id: current_virtual_desktop(),
|
|
work_area_offset: None,
|
|
window_management_behaviour: WindowManagementBehaviour::default(),
|
|
cross_monitor_move_behaviour: MoveBehaviour::Swap,
|
|
cross_boundary_behaviour: CrossBoundaryBehaviour::Monitor,
|
|
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
|
|
resize_delta: 50,
|
|
focus_follows_mouse: None,
|
|
mouse_follows_focus: true,
|
|
hotwatch: Hotwatch::new()?,
|
|
has_pending_raise_op: false,
|
|
pending_move_op: Arc::new(None),
|
|
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
|
|
uncloack_to_ignore: 0,
|
|
known_hwnds: HashMap::new(),
|
|
})
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn init(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("initialising");
|
|
WindowsApi::load_monitor_information(self)?;
|
|
WindowsApi::load_workspace_information(&mut self.monitors)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self, state))]
|
|
pub fn apply_state(&mut self, state: State) {
|
|
let mut can_apply = true;
|
|
|
|
let state_monitors_len = state.monitors.elements().len();
|
|
let current_monitors_len = self.monitors.elements().len();
|
|
if state_monitors_len != current_monitors_len {
|
|
tracing::warn!(
|
|
"cannot apply state from {}; state file has {state_monitors_len} monitors, but only {current_monitors_len} are currently connected",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy()
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
for monitor in state.monitors.elements() {
|
|
for workspace in monitor.workspaces() {
|
|
for container in workspace.containers() {
|
|
for window in container.windows() {
|
|
if window.exe().is_err() {
|
|
can_apply = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(window) = workspace.maximized_window
|
|
&& window.exe().is_err()
|
|
{
|
|
can_apply = false;
|
|
break;
|
|
}
|
|
|
|
if let Some(container) = &workspace.monocle_container {
|
|
for window in container.windows() {
|
|
if window.exe().is_err() {
|
|
can_apply = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for window in workspace.floating_windows() {
|
|
if window.exe().is_err() {
|
|
can_apply = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if can_apply {
|
|
tracing::info!(
|
|
"applying state from {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy()
|
|
);
|
|
|
|
let offset = self.work_area_offset;
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
|
let mut focused_workspace = 0;
|
|
if let Some(state_monitor) = state.monitors.elements().get(monitor_idx) {
|
|
monitor
|
|
.workspaces_mut()
|
|
.resize(state_monitor.workspaces().len(), Workspace::default());
|
|
|
|
for (workspace_idx, workspace) in
|
|
monitor.workspaces_mut().iter_mut().enumerate()
|
|
{
|
|
if let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)
|
|
{
|
|
// to make sure padding and layout_options changes get applied for users after a quick restart
|
|
let container_padding = workspace.container_padding;
|
|
let workspace_padding = workspace.workspace_padding;
|
|
let layout_options = workspace.layout_options;
|
|
|
|
*workspace = state_workspace.clone();
|
|
|
|
workspace.container_padding = container_padding;
|
|
workspace.workspace_padding = workspace_padding;
|
|
workspace.layout_options = layout_options;
|
|
|
|
if state_monitor.focused_workspace_idx() == workspace_idx {
|
|
focused_workspace = workspace_idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Err(error) = monitor.focus_workspace(focused_workspace) {
|
|
tracing::warn!(
|
|
"cannot focus workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
|
|
if let Err(error) = monitor.load_focused_workspace(mouse_follows_focus) {
|
|
tracing::warn!(
|
|
"cannot load focused workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
|
|
if let Err(error) = monitor.update_focused_workspace(offset) {
|
|
tracing::warn!(
|
|
"cannot update workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
let focused_monitor_idx = state.monitors.focused_idx();
|
|
let focused_workspace_idx = state
|
|
.monitors
|
|
.elements()
|
|
.get(focused_monitor_idx)
|
|
.map(|m| m.focused_workspace_idx())
|
|
.unwrap_or_default();
|
|
|
|
if let Err(error) = self.focus_monitor(focused_monitor_idx) {
|
|
tracing::warn!(
|
|
"cannot focus monitor '{focused_monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
|
|
if let Err(error) = self.focus_workspace(focused_workspace_idx) {
|
|
tracing::warn!(
|
|
"cannot focus workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
|
|
if let Err(error) = self.update_focused_workspace(true, true) {
|
|
tracing::warn!(
|
|
"cannot update focused workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy(),
|
|
error,
|
|
);
|
|
}
|
|
} else {
|
|
tracing::warn!(
|
|
"cannot apply state from {}; some windows referenced in the state file no longer exist",
|
|
temp_dir().join("komorebi.state.json").to_string_lossy()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument]
|
|
pub fn reload_configuration() {
|
|
tracing::info!("reloading configuration");
|
|
std::thread::spawn(|| load_configuration().expect("could not load configuration"));
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn reload_static_configuration(&mut self, pathbuf: &PathBuf) -> eyre::Result<()> {
|
|
tracing::info!("reloading static configuration");
|
|
StaticConfig::reload(pathbuf, self)
|
|
}
|
|
|
|
pub fn window_management_behaviour(
|
|
&self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
) -> WindowManagementBehaviour {
|
|
if let Some(monitor) = self.monitors().get(monitor_idx)
|
|
&& let Some(workspace) = monitor.workspaces().get(workspace_idx)
|
|
{
|
|
let current_behaviour = if let Some(behaviour) = workspace.window_container_behaviour {
|
|
if workspace.containers().is_empty()
|
|
&& matches!(behaviour, WindowContainerBehaviour::Append)
|
|
{
|
|
// You can't append to an empty workspace
|
|
WindowContainerBehaviour::Create
|
|
} else {
|
|
behaviour
|
|
}
|
|
} else if workspace.containers().is_empty()
|
|
&& matches!(
|
|
self.window_management_behaviour.current_behaviour,
|
|
WindowContainerBehaviour::Append
|
|
)
|
|
{
|
|
// You can't append to an empty workspace
|
|
WindowContainerBehaviour::Create
|
|
} else {
|
|
self.window_management_behaviour.current_behaviour
|
|
};
|
|
|
|
let float_override = if let Some(float_override) = workspace.float_override {
|
|
float_override
|
|
} else {
|
|
self.window_management_behaviour.float_override
|
|
};
|
|
|
|
let floating_layer_behaviour =
|
|
if let Some(behaviour) = workspace.floating_layer_behaviour {
|
|
behaviour
|
|
} else {
|
|
monitor
|
|
.floating_layer_behaviour
|
|
.unwrap_or(self.window_management_behaviour.floating_layer_behaviour)
|
|
};
|
|
|
|
// If the workspace layer is `Floating` and the floating layer behaviour should
|
|
// float then change floating_layer_override to true so that new windows spawn
|
|
// as floating
|
|
let floating_layer_override = matches!(workspace.layer, WorkspaceLayer::Floating)
|
|
&& floating_layer_behaviour.should_float();
|
|
|
|
return WindowManagementBehaviour {
|
|
current_behaviour,
|
|
float_override,
|
|
floating_layer_override,
|
|
floating_layer_behaviour,
|
|
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
|
|
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
|
|
float_override_placement: self.window_management_behaviour.float_override_placement,
|
|
float_rule_placement: self.window_management_behaviour.float_rule_placement,
|
|
};
|
|
}
|
|
|
|
WindowManagementBehaviour {
|
|
current_behaviour: WindowContainerBehaviour::Create,
|
|
float_override: self.window_management_behaviour.float_override,
|
|
floating_layer_override: self.window_management_behaviour.floating_layer_override,
|
|
floating_layer_behaviour: self.window_management_behaviour.floating_layer_behaviour,
|
|
toggle_float_placement: self.window_management_behaviour.toggle_float_placement,
|
|
floating_layer_placement: self.window_management_behaviour.floating_layer_placement,
|
|
float_override_placement: self.window_management_behaviour.float_override_placement,
|
|
float_rule_placement: self.window_management_behaviour.float_rule_placement,
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn watch_configuration(&mut self, enable: bool) -> eyre::Result<()> {
|
|
let config_pwsh = HOME_DIR.join("komorebi.ps1");
|
|
let config_ahk = HOME_DIR.join("komorebi.ahk");
|
|
|
|
if config_pwsh.exists() {
|
|
self.configure_watcher(enable, config_pwsh)?;
|
|
} else if config_ahk.exists() {
|
|
self.configure_watcher(enable, config_ahk)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> eyre::Result<()> {
|
|
if enable {
|
|
tracing::info!("watching configuration for changes: {}", config.display());
|
|
// Always make absolutely sure that there isn't an already existing watch, because
|
|
// hotwatch allows multiple watches to be registered for the same path
|
|
match self.hotwatch.unwatch(&config) {
|
|
Ok(()) => {}
|
|
Err(error) => match error {
|
|
hotwatch::Error::Notify(ref notify_error) => match notify_error.kind {
|
|
NotifyErrorKind::WatchNotFound => {}
|
|
_ => return Err(error.into()),
|
|
},
|
|
error @ hotwatch::Error::Io(_) => return Err(error.into()),
|
|
},
|
|
}
|
|
|
|
self.hotwatch.watch(config, |event| match event.kind {
|
|
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
|
// a NoticeRemove, presumably because of the use of swap files?
|
|
EventKind::Modify(_) | EventKind::Remove(_) => {
|
|
std::thread::spawn(|| {
|
|
load_configuration().expect("could not load configuration");
|
|
});
|
|
}
|
|
_ => {}
|
|
})?;
|
|
} else {
|
|
tracing::info!(
|
|
"no longer watching configuration for changes: {}",
|
|
config.display()
|
|
);
|
|
|
|
self.hotwatch.unwatch(config)?;
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn monitor_idx_in_direction(&self, direction: OperationDirection) -> Option<usize> {
|
|
let current_monitor_size = self.focused_monitor_size().ok()?;
|
|
|
|
for (idx, monitor) in self.monitors.elements().iter().enumerate() {
|
|
match direction {
|
|
OperationDirection::Left => {
|
|
if monitor.size.left + monitor.size.right == current_monitor_size.left {
|
|
return Option::from(idx);
|
|
}
|
|
}
|
|
OperationDirection::Right => {
|
|
if current_monitor_size.right + current_monitor_size.left == monitor.size.left {
|
|
return Option::from(idx);
|
|
}
|
|
}
|
|
OperationDirection::Up => {
|
|
if monitor.size.top + monitor.size.bottom == current_monitor_size.top {
|
|
return Option::from(idx);
|
|
}
|
|
}
|
|
OperationDirection::Down => {
|
|
if current_monitor_size.top + current_monitor_size.bottom == monitor.size.top {
|
|
return Option::from(idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Calculates the direction of a move across monitors given a specific monitor index
|
|
pub fn direction_from_monitor_idx(
|
|
&self,
|
|
target_monitor_idx: usize,
|
|
) -> Option<OperationDirection> {
|
|
let current_monitor_idx = self.focused_monitor_idx();
|
|
if current_monitor_idx == target_monitor_idx {
|
|
return None;
|
|
}
|
|
|
|
let current_monitor_size = self.focused_monitor_size().ok()?;
|
|
let target_monitor_size = self.monitors().get(target_monitor_idx)?.size;
|
|
|
|
if target_monitor_size.left + target_monitor_size.right == current_monitor_size.left {
|
|
return Some(OperationDirection::Left);
|
|
}
|
|
if current_monitor_size.right + current_monitor_size.left == target_monitor_size.left {
|
|
return Some(OperationDirection::Right);
|
|
}
|
|
if target_monitor_size.top + target_monitor_size.bottom == current_monitor_size.top {
|
|
return Some(OperationDirection::Up);
|
|
}
|
|
if current_monitor_size.top + current_monitor_size.bottom == target_monitor_size.top {
|
|
return Some(OperationDirection::Down);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
#[tracing::instrument(skip(self), level = "debug")]
|
|
fn add_window_handle_to_move_based_on_workspace_rule(
|
|
&self,
|
|
window_title: &String,
|
|
hwnd: isize,
|
|
origin_monitor_idx: usize,
|
|
origin_workspace_idx: usize,
|
|
target_monitor_idx: usize,
|
|
target_workspace_idx: usize,
|
|
floating: bool,
|
|
to_move: &mut Vec<EnforceWorkspaceRuleOp>,
|
|
) {
|
|
tracing::trace!(
|
|
"{} should be on monitor {}, workspace {}",
|
|
window_title,
|
|
target_monitor_idx,
|
|
target_workspace_idx
|
|
);
|
|
|
|
// Create an operation outline and save it for later in the fn
|
|
to_move.push(EnforceWorkspaceRuleOp {
|
|
hwnd,
|
|
origin_monitor_idx,
|
|
origin_workspace_idx,
|
|
target_monitor_idx,
|
|
target_workspace_idx,
|
|
floating,
|
|
});
|
|
}
|
|
|
|
#[tracing::instrument(skip(self), level = "debug")]
|
|
pub fn enforce_workspace_rules(&mut self) -> eyre::Result<()> {
|
|
let mut to_move = vec![];
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
let focused_workspace_idx = self
|
|
.monitors()
|
|
.get(focused_monitor_idx)
|
|
.ok_or_eyre("there is no monitor with that index")?
|
|
.focused_workspace_idx();
|
|
|
|
// scope mutex locks to avoid deadlock if should_update_focused_workspace evaluates to true
|
|
// at the end of this function
|
|
{
|
|
let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
|
let regex_identifiers = REGEX_IDENTIFIERS.lock();
|
|
// Go through all the monitors and workspaces
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
for (j, workspace) in monitor.workspaces().iter().enumerate() {
|
|
// And all the visible windows (at the top of a container)
|
|
for window in workspace.visible_windows().into_iter().flatten() {
|
|
let mut already_moved_window_handles =
|
|
self.already_moved_window_handles.lock();
|
|
|
|
if let (Ok(exe_name), Ok(title), Ok(class), Ok(path)) =
|
|
(window.exe(), window.title(), window.class(), window.path())
|
|
{
|
|
for rule in &*workspace_matching_rules {
|
|
let matched = match &rule.matching_rule {
|
|
MatchingRule::Simple(r) => should_act_individual(
|
|
&title,
|
|
&exe_name,
|
|
&class,
|
|
&path,
|
|
r,
|
|
®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");
|
|
|
|
if workspace.monocle_container.is_some() {
|
|
let cycle_direction = match direction {
|
|
OperationDirection::Left | OperationDirection::Down => CycleDirection::Previous,
|
|
OperationDirection::Right | OperationDirection::Up => CycleDirection::Next,
|
|
};
|
|
return self.cycle_monocle(cycle_direction);
|
|
}
|
|
|
|
let new_idx = if workspace.maximized_window.is_some() {
|
|
None
|
|
} else {
|
|
workspace.new_idx_for_direction(direction)
|
|
};
|
|
|
|
let mut cross_monitor_monocle_or_max = false;
|
|
|
|
// this is for when we are scrolling across workspaces like PaperWM
|
|
if new_idx.is_none()
|
|
&& matches!(
|
|
self.cross_boundary_behaviour,
|
|
CrossBoundaryBehaviour::Workspace
|
|
)
|
|
&& matches!(
|
|
direction,
|
|
OperationDirection::Left | OperationDirection::Right
|
|
)
|
|
{
|
|
let workspace_count = if let Some(monitor) = self.focused_monitor() {
|
|
monitor.workspaces().len()
|
|
} else {
|
|
1
|
|
};
|
|
|
|
let next_idx = match direction {
|
|
OperationDirection::Left => match workspace_idx {
|
|
0 => workspace_count - 1,
|
|
n => n - 1,
|
|
},
|
|
OperationDirection::Right => match workspace_idx {
|
|
n if n == workspace_count - 1 => 0,
|
|
n => n + 1,
|
|
},
|
|
_ => workspace_idx,
|
|
};
|
|
|
|
self.focus_workspace(next_idx)?;
|
|
|
|
if let Ok(focused_workspace) = self.focused_workspace_mut()
|
|
&& focused_workspace.monocle_container.is_none()
|
|
{
|
|
match direction {
|
|
OperationDirection::Left => match focused_workspace.layout {
|
|
Layout::Default(layout) => {
|
|
let target_index =
|
|
layout.rightmost_index(focused_workspace.containers().len());
|
|
focused_workspace.focus_container(target_index);
|
|
}
|
|
Layout::Custom(_) => {
|
|
focused_workspace.focus_container(
|
|
focused_workspace.containers().len().saturating_sub(1),
|
|
);
|
|
}
|
|
},
|
|
OperationDirection::Right => match focused_workspace.layout {
|
|
Layout::Default(layout) => {
|
|
let target_index =
|
|
layout.leftmost_index(focused_workspace.containers().len());
|
|
focused_workspace.focus_container(target_index);
|
|
}
|
|
Layout::Custom(_) => {
|
|
focused_workspace.focus_container(0);
|
|
}
|
|
},
|
|
_ => {}
|
|
};
|
|
}
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
// if there is no container in that direction for this workspace
|
|
match new_idx {
|
|
None => {
|
|
let monitor_idx = self
|
|
.monitor_idx_in_direction(direction)
|
|
.ok_or_eyre("there is no container or monitor in this direction")?;
|
|
|
|
self.focus_monitor(monitor_idx)?;
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
|
|
if let Ok(focused_workspace) = self.focused_workspace_mut() {
|
|
if let Some(window) = focused_workspace.maximized_window {
|
|
window.focus(mouse_follows_focus)?;
|
|
cross_monitor_monocle_or_max = true;
|
|
} else if let Some(monocle) = &focused_workspace.monocle_container {
|
|
if let Some(window) = monocle.focused_window() {
|
|
window.focus(mouse_follows_focus)?;
|
|
cross_monitor_monocle_or_max = true;
|
|
}
|
|
} else if focused_workspace.layer == WorkspaceLayer::Tiling {
|
|
match direction {
|
|
OperationDirection::Left => match focused_workspace.layout {
|
|
Layout::Default(layout) => {
|
|
let target_index = layout
|
|
.rightmost_index(focused_workspace.containers().len());
|
|
focused_workspace.focus_container(target_index);
|
|
}
|
|
Layout::Custom(_) => {
|
|
focused_workspace.focus_container(
|
|
focused_workspace.containers().len().saturating_sub(1),
|
|
);
|
|
}
|
|
},
|
|
OperationDirection::Right => match focused_workspace.layout {
|
|
Layout::Default(layout) => {
|
|
let target_index =
|
|
layout.leftmost_index(focused_workspace.containers().len());
|
|
focused_workspace.focus_container(target_index);
|
|
}
|
|
Layout::Custom(_) => {
|
|
focused_workspace.focus_container(0);
|
|
}
|
|
},
|
|
_ => {}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
Some(idx) => {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.focus_container(idx);
|
|
}
|
|
}
|
|
|
|
if !cross_monitor_monocle_or_max {
|
|
let ws = self.focused_workspace_mut()?;
|
|
if ws.is_empty() {
|
|
// This is to remove focus from the previous monitor
|
|
let desktop_window = Window::from(WindowsApi::desktop_window()?);
|
|
|
|
match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {
|
|
Ok(()) => {}
|
|
Err(error) => {
|
|
tracing::warn!("{} {}:{}", error, file!(), line!());
|
|
}
|
|
}
|
|
} else if ws.layer == WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {
|
|
if let Some(window) = ws.focused_floating_window() {
|
|
window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
} else {
|
|
ws.layer = WorkspaceLayer::Tiling;
|
|
if let Ok(focused_window) = self.focused_window() {
|
|
focused_window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_floating_window_in_direction(
|
|
&mut self,
|
|
direction: OperationDirection,
|
|
) -> eyre::Result<()> {
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
|
|
let mut focused_monitor_work_area = self.focused_monitor_work_area()?;
|
|
let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);
|
|
let border_width = BORDER_WIDTH.load(Ordering::SeqCst);
|
|
focused_monitor_work_area.left += border_offset;
|
|
focused_monitor_work_area.left += border_width;
|
|
focused_monitor_work_area.top += border_offset;
|
|
focused_monitor_work_area.top += border_width;
|
|
focused_monitor_work_area.right -= border_offset * 2;
|
|
focused_monitor_work_area.right -= border_width * 2;
|
|
focused_monitor_work_area.bottom -= border_offset * 2;
|
|
focused_monitor_work_area.bottom -= border_width * 2;
|
|
|
|
let focused_workspace = self.focused_workspace()?;
|
|
let delta = self.resize_delta;
|
|
|
|
let focused_hwnd = WindowsApi::foreground_window()?;
|
|
for window in focused_workspace.floating_windows().iter() {
|
|
if window.hwnd == focused_hwnd {
|
|
let mut rect = WindowsApi::window_rect(window.hwnd)?;
|
|
match direction {
|
|
OperationDirection::Left => {
|
|
if rect.left - delta < focused_monitor_work_area.left {
|
|
rect.left = focused_monitor_work_area.left;
|
|
} else {
|
|
rect.left -= delta;
|
|
}
|
|
}
|
|
OperationDirection::Right => {
|
|
if rect.left + delta + rect.right
|
|
> focused_monitor_work_area.left + focused_monitor_work_area.right
|
|
{
|
|
rect.left = focused_monitor_work_area.left
|
|
+ focused_monitor_work_area.right
|
|
- rect.right;
|
|
} else {
|
|
rect.left += delta;
|
|
}
|
|
}
|
|
OperationDirection::Up => {
|
|
if rect.top - delta < focused_monitor_work_area.top {
|
|
rect.top = focused_monitor_work_area.top;
|
|
} else {
|
|
rect.top -= delta;
|
|
}
|
|
}
|
|
OperationDirection::Down => {
|
|
if rect.top + delta + rect.bottom
|
|
> focused_monitor_work_area.top + focused_monitor_work_area.bottom
|
|
{
|
|
rect.top = focused_monitor_work_area.top
|
|
+ focused_monitor_work_area.bottom
|
|
- rect.bottom;
|
|
} else {
|
|
rect.top += delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
WindowsApi::position_window(window.hwnd, &rect, false, true)?;
|
|
if mouse_follows_focus {
|
|
WindowsApi::center_cursor_in_rect(&rect)?;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_in_direction(
|
|
&mut self,
|
|
direction: OperationDirection,
|
|
) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace()?;
|
|
let workspace_idx = self.focused_workspace_idx()?;
|
|
|
|
// removing this messes up the monitor / container / window index somewhere
|
|
// and results in the wrong window getting moved across the monitor boundary
|
|
if workspace.is_focused_window_monocle_or_maximized()? {
|
|
bail!("ignoring command while active window is in monocle mode or maximized");
|
|
}
|
|
|
|
tracing::info!("moving container");
|
|
|
|
let origin_container_idx = workspace.focused_container_idx();
|
|
let origin_monitor_idx = self.focused_monitor_idx();
|
|
let target_container_idx = workspace.new_idx_for_direction(direction);
|
|
|
|
// this is for when we are scrolling across workspaces like PaperWM
|
|
if target_container_idx.is_none()
|
|
&& matches!(
|
|
self.cross_boundary_behaviour,
|
|
CrossBoundaryBehaviour::Workspace
|
|
)
|
|
&& matches!(
|
|
direction,
|
|
OperationDirection::Left | OperationDirection::Right
|
|
)
|
|
{
|
|
let workspace_count = if let Some(monitor) = self.focused_monitor() {
|
|
monitor.workspaces().len()
|
|
} else {
|
|
1
|
|
};
|
|
|
|
let next_idx = match direction {
|
|
OperationDirection::Left => match workspace_idx {
|
|
0 => workspace_count - 1,
|
|
n => n - 1,
|
|
},
|
|
OperationDirection::Right => match workspace_idx {
|
|
n if n == workspace_count - 1 => 0,
|
|
n => n + 1,
|
|
},
|
|
_ => workspace_idx,
|
|
};
|
|
|
|
// passing the direction here is how we handle whether to insert at the front
|
|
// or the back of the container vecdeque in the target workspace
|
|
self.move_container_to_workspace(next_idx, true, Some(direction))?;
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
match target_container_idx {
|
|
// If there is nowhere to move on the current workspace, try to move it onto the monitor
|
|
// in that direction if there is one
|
|
None => {
|
|
// Don't do anything if the user has set the MoveBehaviour to NoOp
|
|
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::NoOp) {
|
|
return Ok(());
|
|
}
|
|
|
|
let target_monitor_idx = self
|
|
.monitor_idx_in_direction(direction)
|
|
.ok_or_eyre("there is no container or monitor in this direction")?;
|
|
|
|
{
|
|
// actually move the container to target monitor using the direction
|
|
self.move_container_to_monitor(
|
|
target_monitor_idx,
|
|
None,
|
|
true,
|
|
Some(direction),
|
|
)?;
|
|
|
|
// focus the target monitor
|
|
self.focus_monitor(target_monitor_idx)?;
|
|
|
|
// unset monocle container on target workspace if there is one
|
|
let mut target_workspace_has_monocle = false;
|
|
if let Ok(target_workspace) = self.focused_workspace()
|
|
&& target_workspace.monocle_container.is_some()
|
|
{
|
|
target_workspace_has_monocle = true;
|
|
}
|
|
|
|
if target_workspace_has_monocle {
|
|
self.toggle_monocle()?;
|
|
}
|
|
|
|
// get a mutable ref to the focused workspace on the target monitor
|
|
let target_workspace = self.focused_workspace_mut()?;
|
|
|
|
// if there is only one container on the target workspace after the insertion
|
|
// it means that there won't be one swapped back, so we have to decrement the
|
|
// focused position
|
|
if target_workspace.containers().len() == 1 {
|
|
let origin_workspace =
|
|
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
|
|
|
|
origin_workspace.focus_container(
|
|
origin_workspace.focused_container_idx().saturating_sub(1),
|
|
);
|
|
}
|
|
}
|
|
|
|
// if our MoveBehaviour is Swap, let's try to send back the window container
|
|
// whose position which just took over
|
|
if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::Swap) {
|
|
{
|
|
let target_workspace = self.focused_workspace_mut()?;
|
|
|
|
// if the target workspace doesn't have more than one container, this means it
|
|
// was previously empty, by only doing the second part of the swap when there is
|
|
// more than one container, we can fall back to a "move" if there is nothing to
|
|
// swap with on the target monitor
|
|
if target_workspace.containers().len() > 1 {
|
|
// remove the container from the target monitor workspace
|
|
let target_container = target_workspace
|
|
// this is now focused_container_idx + 1 because we have inserted our origin container
|
|
.remove_container_by_idx(
|
|
target_workspace.focused_container_idx() + 1,
|
|
)
|
|
.ok_or_eyre("could not remove container at given target index")?;
|
|
|
|
let origin_workspace =
|
|
self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;
|
|
|
|
// insert the container from the target monitor workspace into the origin monitor workspace
|
|
// at the same position from which our origin container was removed
|
|
origin_workspace
|
|
.insert_container_at_idx(origin_container_idx, target_container);
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure to update the origin monitor workspace layout because it is no
|
|
// longer focused so it won't get updated at the end of this fn
|
|
let offset = self.work_area_offset;
|
|
|
|
self.monitors_mut()
|
|
.get_mut(origin_monitor_idx)
|
|
.ok_or_eyre("there is no monitor at this index")?
|
|
.update_focused_workspace(offset)?;
|
|
|
|
let a = self
|
|
.focused_monitor()
|
|
.ok_or_eyre("there is no monitor focused monitor")?
|
|
.id;
|
|
let b = self
|
|
.monitors_mut()
|
|
.get_mut(origin_monitor_idx)
|
|
.ok_or_eyre("there is no monitor at this index")?
|
|
.id;
|
|
|
|
if !WindowsApi::monitors_have_same_dpi(a, b)? {
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
|
}
|
|
}
|
|
Some(new_idx) => {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.swap_containers(origin_container_idx, new_idx);
|
|
workspace.focus_container(new_idx);
|
|
}
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_floating_window_in_cycle_direction(
|
|
&mut self,
|
|
direction: CycleDirection,
|
|
) -> eyre::Result<()> {
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
let focused_workspace = self.focused_workspace()?;
|
|
|
|
let mut target_idx = None;
|
|
let len = focused_workspace.floating_windows().len();
|
|
|
|
if len > 1 {
|
|
let focused_hwnd = WindowsApi::foreground_window()?;
|
|
for (idx, window) in focused_workspace.floating_windows().iter().enumerate() {
|
|
if window.hwnd == focused_hwnd {
|
|
match direction {
|
|
CycleDirection::Previous => {
|
|
if idx == 0 {
|
|
target_idx = Some(len - 1)
|
|
} else {
|
|
target_idx = Some(idx - 1)
|
|
}
|
|
}
|
|
CycleDirection::Next => {
|
|
if idx == len - 1 {
|
|
target_idx = Some(0)
|
|
} else {
|
|
target_idx = Some(idx - 1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if target_idx.is_none() {
|
|
target_idx = Some(0);
|
|
}
|
|
}
|
|
|
|
if let Some(idx) = target_idx
|
|
&& let Some(window) = focused_workspace.floating_windows().get(idx)
|
|
{
|
|
window.focus(mouse_follows_focus)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_container_in_cycle_direction(
|
|
&mut self,
|
|
direction: CycleDirection,
|
|
) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("focusing container");
|
|
let mut maximize_next = false;
|
|
let mut monocle_next = false;
|
|
|
|
if self.focused_workspace_mut()?.maximized_window.is_some() {
|
|
maximize_next = true;
|
|
self.unmaximize_window()?;
|
|
}
|
|
|
|
if self.focused_workspace_mut()?.monocle_container.is_some() {
|
|
monocle_next = true;
|
|
self.monocle_off()?;
|
|
}
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let new_idx = workspace
|
|
.new_idx_for_cycle_direction(direction)
|
|
.ok_or_eyre("this is not a valid direction from the current position")?;
|
|
|
|
workspace.focus_container(new_idx);
|
|
|
|
if maximize_next {
|
|
self.toggle_maximize()?;
|
|
} else if monocle_next {
|
|
self.toggle_monocle()?;
|
|
} else {
|
|
self.focused_window_mut()?.focus(self.mouse_follows_focus)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn move_container_in_cycle_direction(
|
|
&mut self,
|
|
direction: CycleDirection,
|
|
) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
if workspace.is_focused_window_monocle_or_maximized()? {
|
|
bail!("ignoring command while active window is in monocle mode or maximized");
|
|
}
|
|
|
|
tracing::info!("moving container");
|
|
|
|
let current_idx = workspace.focused_container_idx();
|
|
let new_idx = workspace
|
|
.new_idx_for_cycle_direction(direction)
|
|
.ok_or_eyre("this is not a valid direction from the current position")?;
|
|
|
|
workspace.swap_containers(current_idx, new_idx);
|
|
workspace.focus_container(new_idx);
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn cycle_container_window_in_direction(
|
|
&mut self,
|
|
direction: CycleDirection,
|
|
) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("cycling container windows");
|
|
|
|
let container =
|
|
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
|
|
container
|
|
} else {
|
|
self.focused_container_mut()?
|
|
};
|
|
|
|
let len = NonZeroUsize::new(container.windows().len())
|
|
.ok_or_eyre("there must be at least one window in a container")?;
|
|
|
|
if len.get() == 1 {
|
|
bail!("there is only one window in this container");
|
|
}
|
|
|
|
let current_idx = container.focused_window_idx();
|
|
let next_idx = direction.next_idx(current_idx, len);
|
|
|
|
container.focus_window(next_idx);
|
|
container.load_focused_window();
|
|
|
|
if let Some(window) = container.focused_window() {
|
|
window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn cycle_container_window_index_in_direction(
|
|
&mut self,
|
|
direction: CycleDirection,
|
|
) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("cycling container window index");
|
|
|
|
let container =
|
|
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
|
|
container
|
|
} else {
|
|
self.focused_container_mut()?
|
|
};
|
|
|
|
let len = NonZeroUsize::new(container.windows().len())
|
|
.ok_or_eyre("there must be at least one window in a container")?;
|
|
|
|
if len.get() == 1 {
|
|
bail!("there is only one window in this container");
|
|
}
|
|
|
|
let current_idx = container.focused_window_idx();
|
|
let next_idx = direction.next_idx(current_idx, len);
|
|
container.windows_mut().swap(current_idx, next_idx);
|
|
|
|
container.focus_window(next_idx);
|
|
container.load_focused_window();
|
|
|
|
if let Some(window) = container.focused_window() {
|
|
window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_container_window(&mut self, idx: usize) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("focusing container window at index {idx}");
|
|
|
|
let container =
|
|
if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {
|
|
container
|
|
} else {
|
|
self.focused_container_mut()?
|
|
};
|
|
|
|
let len = NonZeroUsize::new(container.windows().len())
|
|
.ok_or_eyre("there must be at least one window in a container")?;
|
|
|
|
if len.get() == 1 && idx != 0 {
|
|
bail!("there is only one window in this container");
|
|
}
|
|
|
|
if container.windows().get(idx).is_none() {
|
|
bail!("there is no window in this container at index {idx}");
|
|
}
|
|
|
|
container.focus_window(idx);
|
|
container.load_focused_window();
|
|
|
|
if let Some(window) = container.focused_window() {
|
|
window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn stack_all(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
tracing::info!("stacking all windows on workspace");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let mut focused_hwnd = None;
|
|
if let Some(container) = workspace.focused_container()
|
|
&& let Some(window) = container.focused_window()
|
|
{
|
|
focused_hwnd = Some(window.hwnd);
|
|
}
|
|
|
|
let workspace_hwnds =
|
|
workspace
|
|
.containers()
|
|
.iter()
|
|
.fold(VecDeque::new(), |mut hwnds, c| {
|
|
hwnds.extend(c.windows().clone());
|
|
hwnds
|
|
});
|
|
|
|
let mut container = Container::default();
|
|
*container.windows_mut() = workspace_hwnds;
|
|
*workspace.containers_mut() = VecDeque::from([container]);
|
|
workspace.focus_container(0);
|
|
|
|
if let Some(hwnd) = focused_hwnd
|
|
&& let Some(c) = workspace.focused_container_mut()
|
|
&& let Some(w_idx_to_focus) = c.idx_for_window(hwnd)
|
|
{
|
|
c.focus_window(w_idx_to_focus);
|
|
c.load_focused_window();
|
|
}
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unstack_all(&mut self, update_workspace: bool) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
tracing::info!("unstacking all windows in container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let mut focused_hwnd = None;
|
|
if let Some(container) = workspace.focused_container()
|
|
&& let Some(window) = container.focused_window()
|
|
{
|
|
focused_hwnd = Some(window.hwnd);
|
|
}
|
|
|
|
let initial_focused_container_index = workspace.focused_container_idx();
|
|
let mut focused_container = workspace.focused_container().cloned();
|
|
|
|
while let Some(focused) = &focused_container {
|
|
if focused.windows().len() > 1 {
|
|
workspace.new_container_for_focused_window()?;
|
|
workspace.focus_container(initial_focused_container_index);
|
|
focused_container = workspace.focused_container().cloned();
|
|
} else {
|
|
focused_container = None;
|
|
}
|
|
}
|
|
|
|
if let Some(hwnd) = focused_hwnd {
|
|
workspace.focus_container_by_window(hwnd)?;
|
|
}
|
|
|
|
if update_workspace {
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn add_window_to_container(&mut self, direction: OperationDirection) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("adding window to container");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
let len = NonZeroUsize::new(workspace.containers_mut().len())
|
|
.ok_or_eyre("there must be at least one container")?;
|
|
let current_container_idx = workspace.focused_container_idx();
|
|
|
|
let is_valid = direction
|
|
.destination(
|
|
workspace.layout.as_boxed_direction().as_ref(),
|
|
workspace.layout_flip,
|
|
workspace.focused_container_idx(),
|
|
len,
|
|
workspace.layout_options,
|
|
)
|
|
.is_some();
|
|
|
|
if is_valid {
|
|
let new_idx = workspace
|
|
.new_idx_for_direction(direction)
|
|
.ok_or_eyre("this is not a valid direction from the current position")?;
|
|
|
|
let mut changed_focus = false;
|
|
|
|
let adjusted_new_index = if new_idx > current_container_idx
|
|
&& !matches!(
|
|
workspace.layout,
|
|
Layout::Default(DefaultLayout::Grid)
|
|
| Layout::Default(DefaultLayout::UltrawideVerticalStack)
|
|
) {
|
|
workspace.focus_container(new_idx);
|
|
changed_focus = true;
|
|
new_idx.saturating_sub(1)
|
|
} else {
|
|
new_idx
|
|
};
|
|
|
|
let mut target_container_is_stack = false;
|
|
|
|
if let Some(container) = workspace.containers().get(adjusted_new_index)
|
|
&& container.windows().len() > 1
|
|
{
|
|
target_container_is_stack = true;
|
|
}
|
|
|
|
if let Some(current) = workspace.focused_container() {
|
|
if current.windows().len() > 1 && !target_container_is_stack {
|
|
workspace.focus_container(adjusted_new_index);
|
|
changed_focus = true;
|
|
workspace.move_window_to_container(current_container_idx)?;
|
|
} else {
|
|
workspace.move_window_to_container(adjusted_new_index)?;
|
|
}
|
|
}
|
|
|
|
if changed_focus && let Some(container) = workspace.focused_container_mut() {
|
|
container.load_focused_window();
|
|
if let Some(window) = container.focused_window() {
|
|
window.focus(self.mouse_follows_focus)?;
|
|
}
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn promote_container_to_front(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
|
tracing::debug!("ignoring promote command for grid layout");
|
|
return Ok(());
|
|
}
|
|
|
|
tracing::info!("promoting container");
|
|
|
|
workspace.promote_container()?;
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn promote_container_swap(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
let focused_container_idx = workspace.focused_container_idx();
|
|
|
|
let primary_idx = match &workspace.layout {
|
|
Layout::Default(_) => 0,
|
|
Layout::Custom(layout) => layout.first_container_idx(
|
|
layout
|
|
.primary_idx()
|
|
.ok_or_eyre("this custom layout does not have a primary column")?,
|
|
),
|
|
};
|
|
|
|
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
|
tracing::debug!("ignoring promote-swap command for grid layout");
|
|
return Ok(());
|
|
}
|
|
|
|
let primary_tile_is_focused = focused_container_idx == primary_idx;
|
|
|
|
if primary_tile_is_focused && let Some(swap_idx) = workspace.promotion_swap_container_idx {
|
|
workspace.swap_containers(focused_container_idx, swap_idx);
|
|
} else {
|
|
workspace.promotion_swap_container_idx = Some(focused_container_idx);
|
|
workspace.swap_containers(focused_container_idx, primary_idx);
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn promote_focus_to_front(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {
|
|
tracing::info!("ignoring promote focus command for grid layout");
|
|
return Ok(());
|
|
}
|
|
|
|
tracing::info!("promoting focus");
|
|
|
|
let target_idx = match &workspace.layout {
|
|
Layout::Default(_) => 0,
|
|
Layout::Custom(custom) => custom
|
|
.first_container_idx(custom.primary_idx().map_or(0, |primary_idx| primary_idx)),
|
|
};
|
|
|
|
workspace.focus_container(target_idx);
|
|
self.update_focused_workspace(self.mouse_follows_focus, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn remove_window_from_container(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
tracing::info!("removing window");
|
|
|
|
if self.focused_container()?.windows().len() == 1 {
|
|
bail!("a container must have at least one window");
|
|
}
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
workspace.new_container_for_focused_window()?;
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_tiling(&mut self) -> eyre::Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.tile = !workspace.tile;
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_float(&mut self, force_float: bool) -> eyre::Result<()> {
|
|
let hwnd = WindowsApi::foreground_window()?;
|
|
let workspace = self.focused_workspace_mut()?;
|
|
if workspace.monocle_container.is_some() {
|
|
tracing::warn!("ignoring toggle-float command while workspace has a monocle container");
|
|
return Ok(());
|
|
}
|
|
|
|
let mut is_floating_window = false;
|
|
|
|
for window in workspace.floating_windows() {
|
|
if window.hwnd == hwnd {
|
|
is_floating_window = true;
|
|
}
|
|
}
|
|
|
|
if is_floating_window && !force_float {
|
|
workspace.layer = WorkspaceLayer::Tiling;
|
|
self.unfloat_window()?;
|
|
} else {
|
|
workspace.layer = WorkspaceLayer::Floating;
|
|
self.float_window()?;
|
|
}
|
|
|
|
self.update_focused_workspace(is_floating_window, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_lock(&mut self) -> eyre::Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
if let Some(container) = workspace.focused_container_mut() {
|
|
// Toggle the locked flag
|
|
container.locked = !container.locked;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn float_window(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("floating window");
|
|
|
|
let work_area = self.focused_monitor_work_area()?;
|
|
|
|
let toggle_float_placement = self.window_management_behaviour.toggle_float_placement;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_floating_window()?;
|
|
|
|
let window = workspace
|
|
.floating_windows_mut()
|
|
.back_mut()
|
|
.ok_or_eyre("there is no floating window")?;
|
|
|
|
if toggle_float_placement.should_center() {
|
|
window.center(&work_area, toggle_float_placement.should_resize())?;
|
|
}
|
|
window.focus(self.mouse_follows_focus)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unfloat_window(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("unfloating window");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_container_for_floating_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_monocle(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace()?;
|
|
match workspace.monocle_container {
|
|
None => self.monocle_on()?,
|
|
Some(_) => self.monocle_off()?,
|
|
}
|
|
|
|
self.update_focused_workspace(true, true)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn monocle_on(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("enabling monocle");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_monocle_container()?;
|
|
|
|
for container in workspace.containers_mut() {
|
|
container.hide(None);
|
|
}
|
|
|
|
for window in workspace.floating_windows_mut() {
|
|
window.hide();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn monocle_off(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("disabling monocle");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
for container in workspace.containers_mut() {
|
|
container.restore();
|
|
}
|
|
|
|
for window in workspace.floating_windows_mut() {
|
|
window.restore();
|
|
}
|
|
|
|
workspace.reintegrate_monocle_container()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn cycle_monocle(&mut self, direction: CycleDirection) -> eyre::Result<()> {
|
|
tracing::info!("cycling monocle container");
|
|
|
|
if self.focused_workspace()?.containers().is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
self.focused_workspace_mut()?
|
|
.cycle_monocle_container(direction)?;
|
|
|
|
for container in self.focused_workspace_mut()?.containers_mut() {
|
|
container.hide(None);
|
|
}
|
|
|
|
// borders were getting funny during cycles, can't be bothered to root cause it
|
|
border_manager::destroy_all_borders()?;
|
|
|
|
self.update_focused_workspace(true, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn toggle_maximize(&mut self) -> eyre::Result<()> {
|
|
self.handle_unmanaged_window_behaviour()?;
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.maximized_window {
|
|
None => self.maximize_window()?,
|
|
Some(_) => self.unmaximize_window()?,
|
|
}
|
|
|
|
self.update_focused_workspace(true, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn maximize_window(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("maximizing windowj");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.new_maximized_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn unmaximize_window(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("unmaximizing window");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
workspace.reintegrate_maximized_window()
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn flip_layout(&mut self, layout_flip: Axis) -> eyre::Result<()> {
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
tracing::info!("flipping layout");
|
|
|
|
#[allow(clippy::match_same_arms)]
|
|
match workspace.layout_flip {
|
|
None => {
|
|
workspace.layout_flip = Option::from(layout_flip);
|
|
}
|
|
Some(current_layout_flip) => {
|
|
match current_layout_flip {
|
|
Axis::Horizontal => match layout_flip {
|
|
Axis::Horizontal => workspace.layout_flip = None,
|
|
Axis::Vertical => {
|
|
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
|
|
}
|
|
Axis::HorizontalAndVertical => {
|
|
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
|
|
}
|
|
},
|
|
Axis::Vertical => match layout_flip {
|
|
Axis::Horizontal => {
|
|
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
|
|
}
|
|
Axis::Vertical => workspace.layout_flip = None,
|
|
Axis::HorizontalAndVertical => {
|
|
workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)
|
|
}
|
|
},
|
|
Axis::HorizontalAndVertical => match layout_flip {
|
|
Axis::Horizontal => workspace.layout_flip = Option::from(Axis::Vertical),
|
|
Axis::Vertical => workspace.layout_flip = Option::from(Axis::Horizontal),
|
|
Axis::HorizontalAndVertical => workspace.layout_flip = None,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> eyre::Result<()> {
|
|
tracing::info!("changing layout");
|
|
|
|
let monitor_count = self.monitors().len();
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
if monitor_count > 1 && matches!(layout, DefaultLayout::Scrolling) {
|
|
tracing::warn!(
|
|
"scrolling layout is only supported for a single monitor; not changing layout"
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
match &workspace.layout {
|
|
Layout::Default(_) => {}
|
|
Layout::Custom(layout) => {
|
|
let primary_idx = layout.first_container_idx(
|
|
layout
|
|
.primary_idx()
|
|
.ok_or_eyre("this custom layout does not have a primary column")?,
|
|
);
|
|
|
|
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
|
|
{
|
|
workspace.swap_containers(0, primary_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
workspace.layout = Layout::Default(layout);
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn cycle_layout(&mut self, direction: CycleDirection) -> eyre::Result<()> {
|
|
tracing::info!("cycling layout");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
let current_layout = &workspace.layout;
|
|
|
|
match current_layout {
|
|
Layout::Default(current) => {
|
|
let new_layout = match direction {
|
|
CycleDirection::Previous => current.cycle_previous(),
|
|
CycleDirection::Next => current.cycle_next(),
|
|
};
|
|
|
|
tracing::info!("next layout: {new_layout}");
|
|
workspace.layout = Layout::Default(new_layout);
|
|
}
|
|
Layout::Custom(_) => {}
|
|
}
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn change_workspace_custom_layout<P>(&mut self, path: P) -> eyre::Result<()>
|
|
where
|
|
P: AsRef<Path> + std::fmt::Debug,
|
|
{
|
|
tracing::info!("changing layout");
|
|
|
|
let layout = CustomLayout::from_path(path)?;
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
match workspace.layout {
|
|
Layout::Default(_) => {
|
|
let primary_idx = layout.first_container_idx(
|
|
layout
|
|
.primary_idx()
|
|
.ok_or_eyre("this custom layout does not have a primary column")?,
|
|
);
|
|
|
|
if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()
|
|
{
|
|
workspace.swap_containers(0, primary_idx);
|
|
}
|
|
}
|
|
Layout::Custom(_) => {}
|
|
}
|
|
|
|
workspace.layout = Layout::Custom(layout);
|
|
workspace.layout_flip = None;
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn adjust_workspace_padding(
|
|
&mut self,
|
|
sizing: Sizing,
|
|
adjustment: i32,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("adjusting workspace padding");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let padding = workspace
|
|
.workspace_padding
|
|
.ok_or_eyre("there is no workspace padding")?;
|
|
|
|
workspace.workspace_padding = Option::from(sizing.adjust_by(padding, adjustment));
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn adjust_container_padding(
|
|
&mut self,
|
|
sizing: Sizing,
|
|
adjustment: i32,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("adjusting container padding");
|
|
|
|
let workspace = self.focused_workspace_mut()?;
|
|
|
|
let padding = workspace
|
|
.container_padding
|
|
.ok_or_eyre("there is no container padding")?;
|
|
|
|
workspace.container_padding = Option::from(sizing.adjust_by(padding, adjustment));
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_tiling(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
tile: bool,
|
|
) -> eyre::Result<()> {
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.tile = tile;
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn add_workspace_layout_default_rule(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
at_container_count: usize,
|
|
layout: DefaultLayout,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting workspace layout");
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
|
|
rules.retain(|pair| pair.0 != at_container_count);
|
|
rules.push((at_container_count, Layout::Default(layout)));
|
|
rules.sort_by_key(|a| a.0);
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update()?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false, false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn add_workspace_layout_custom_rule<P>(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
at_container_count: usize,
|
|
path: P,
|
|
) -> eyre::Result<()>
|
|
where
|
|
P: AsRef<Path> + std::fmt::Debug,
|
|
{
|
|
tracing::info!("setting workspace layout");
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let layout = CustomLayout::from_path(path)?;
|
|
|
|
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
|
|
rules.retain(|pair| pair.0 != at_container_count);
|
|
rules.push((at_container_count, Layout::Custom(layout)));
|
|
rules.sort_by_key(|a| a.0);
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update()?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false, false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn clear_workspace_layout_rules(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting workspace layout");
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;
|
|
rules.clear();
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update()?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false, false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_layout_default(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
layout: DefaultLayout,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting workspace layout");
|
|
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.layout = Layout::Default(layout);
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update()?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false, false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_layout_custom<P>(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
path: P,
|
|
) -> eyre::Result<()>
|
|
where
|
|
P: AsRef<Path> + std::fmt::Debug,
|
|
{
|
|
tracing::info!("setting workspace layout");
|
|
let layout = CustomLayout::from_path(path)?;
|
|
let focused_monitor_idx = self.focused_monitor_idx();
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let focused_workspace_idx = monitor.focused_workspace_idx();
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.layout = Layout::Custom(layout);
|
|
workspace.layout_flip = None;
|
|
|
|
// If this is the focused workspace on a non-focused screen, let's update it
|
|
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
|
|
workspace.update()?;
|
|
Ok(())
|
|
} else {
|
|
Ok(self.update_focused_workspace(false, false)?)
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn ensure_workspaces_for_monitor(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_count: usize,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("ensuring workspace count");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
monitor.ensure_workspace_count(workspace_count);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn ensure_named_workspaces_for_monitor(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
names: &Vec<String>,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("ensuring workspace count");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
monitor.ensure_workspace_count(names.len());
|
|
|
|
for (workspace_idx, name) in names.iter().enumerate() {
|
|
if let Some(workspace) = monitor.workspaces_mut().get_mut(workspace_idx) {
|
|
workspace.name = Option::from(name.clone());
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_padding(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
size: i32,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting workspace padding");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.workspace_padding = Option::from(size);
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_workspace_name(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
name: String,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting workspace name");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.name = Option::from(name.clone());
|
|
monitor.workspace_names.insert(workspace_idx, name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn set_container_padding(
|
|
&mut self,
|
|
monitor_idx: usize,
|
|
workspace_idx: usize,
|
|
size: i32,
|
|
) -> eyre::Result<()> {
|
|
tracing::info!("setting container padding");
|
|
|
|
let monitor = self
|
|
.monitors_mut()
|
|
.get_mut(monitor_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
let workspace = monitor
|
|
.workspaces_mut()
|
|
.get_mut(workspace_idx)
|
|
.ok_or_eyre("there is no monitor")?;
|
|
|
|
workspace.container_padding = Option::from(size);
|
|
|
|
self.update_focused_workspace(false, false)
|
|
}
|
|
|
|
pub fn focused_monitor_size(&self) -> eyre::Result<Rect> {
|
|
Ok(self
|
|
.focused_monitor()
|
|
.ok_or_eyre("there is no monitor")?
|
|
.size)
|
|
}
|
|
|
|
pub fn focused_monitor_work_area(&self) -> eyre::Result<Rect> {
|
|
Ok(self
|
|
.focused_monitor()
|
|
.ok_or_eyre("there is no monitor")?
|
|
.work_area_size)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_monitor(&mut self, idx: usize) -> eyre::Result<()> {
|
|
tracing::info!("focusing monitor");
|
|
|
|
if self.monitors().get(idx).is_some() {
|
|
self.monitors.focus(idx);
|
|
} else {
|
|
bail!("this is not a valid monitor index");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn monitor_idx_from_window(&mut self, window: Window) -> Option<usize> {
|
|
let hmonitor = WindowsApi::monitor_from_window(window.hwnd);
|
|
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
if monitor.id == hmonitor {
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
|
|
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
|
|
// info taken from win32_display_data and update our hmonitor while we're at it
|
|
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
|
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
|
if monitor.device_id == latest.device_id {
|
|
monitor.id = latest.id;
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {
|
|
let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);
|
|
|
|
for (i, monitor) in self.monitors().iter().enumerate() {
|
|
if monitor.id == hmonitor {
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
|
|
// our hmonitor might be stale, so if we didn't return above, try querying via the latest
|
|
// info taken from win32_display_data and update our hmonitor while we're at it
|
|
if let Ok(latest) = WindowsApi::monitor(hmonitor) {
|
|
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
|
|
if monitor.device_id == latest.device_id {
|
|
monitor.id = latest.id;
|
|
return Option::from(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn focused_workspace_idx(&self) -> eyre::Result<usize> {
|
|
Ok(self
|
|
.focused_monitor()
|
|
.ok_or_eyre("there is no monitor")?
|
|
.focused_workspace_idx())
|
|
}
|
|
|
|
pub fn focused_workspace(&self) -> eyre::Result<&Workspace> {
|
|
self.focused_monitor()
|
|
.ok_or_eyre("there is no monitor")?
|
|
.focused_workspace()
|
|
.ok_or_eyre("there is no workspace")
|
|
}
|
|
|
|
pub fn focused_workspace_mut(&mut self) -> eyre::Result<&mut Workspace> {
|
|
self.focused_monitor_mut()
|
|
.ok_or_eyre("there is no monitor")?
|
|
.focused_workspace_mut()
|
|
.ok_or_eyre("there is no workspace")
|
|
}
|
|
|
|
pub fn focused_workspace_idx_for_monitor_idx(&self, idx: usize) -> eyre::Result<usize> {
|
|
Ok(self
|
|
.monitors()
|
|
.get(idx)
|
|
.ok_or_eyre("there is no monitor at this index")?
|
|
.focused_workspace_idx())
|
|
}
|
|
|
|
pub fn focused_workspace_for_monitor_idx(&self, idx: usize) -> eyre::Result<&Workspace> {
|
|
self.monitors()
|
|
.get(idx)
|
|
.ok_or_eyre("there is no monitor at this index")?
|
|
.focused_workspace()
|
|
.ok_or_eyre("there is no workspace")
|
|
}
|
|
|
|
pub fn focused_workspace_for_monitor_idx_mut(
|
|
&mut self,
|
|
idx: usize,
|
|
) -> eyre::Result<&mut Workspace> {
|
|
self.monitors_mut()
|
|
.get_mut(idx)
|
|
.ok_or_eyre("there is no monitor at this index")?
|
|
.focused_workspace_mut()
|
|
.ok_or_eyre("there is no workspace")
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn focus_workspace(&mut self, idx: usize) -> eyre::Result<()> {
|
|
tracing::info!("focusing workspace");
|
|
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_eyre("there is no workspace")?;
|
|
|
|
monitor.focus_workspace(idx)?;
|
|
monitor.load_focused_workspace(mouse_follows_focus)?;
|
|
|
|
self.update_focused_workspace(false, true)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn monitor_workspace_index_by_name(&mut self, name: &str) -> Option<(usize, usize)> {
|
|
tracing::info!("looking up workspace by name");
|
|
|
|
for (monitor_idx, monitor) in self.monitors().iter().enumerate() {
|
|
for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() {
|
|
if let Some(workspace_name) = &workspace.name
|
|
&& workspace_name == name
|
|
{
|
|
return Option::from((monitor_idx, workspace_idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn new_workspace(&mut self) -> eyre::Result<()> {
|
|
tracing::info!("adding new workspace");
|
|
|
|
let mouse_follows_focus = self.mouse_follows_focus;
|
|
let monitor = self
|
|
.focused_monitor_mut()
|
|
.ok_or_eyre("there is no workspace")?;
|
|
|
|
monitor.focus_workspace(monitor.new_workspace_idx())?;
|
|
monitor.load_focused_workspace(mouse_follows_focus)?;
|
|
|
|
self.update_focused_workspace(self.mouse_follows_focus, false)
|
|
}
|
|
|
|
pub fn focused_container(&self) -> eyre::Result<&Container> {
|
|
self.focused_workspace()?
|
|
.focused_container()
|
|
.ok_or_eyre("there is no container")
|
|
}
|
|
|
|
pub fn focused_container_idx(&self) -> eyre::Result<usize> {
|
|
Ok(self.focused_workspace()?.focused_container_idx())
|
|
}
|
|
|
|
pub fn focused_container_mut(&mut self) -> eyre::Result<&mut Container> {
|
|
self.focused_workspace_mut()?
|
|
.focused_container_mut()
|
|
.ok_or_eyre("there is no container")
|
|
}
|
|
|
|
pub fn focused_window(&self) -> eyre::Result<&Window> {
|
|
self.focused_container()?
|
|
.focused_window()
|
|
.ok_or_eyre("there is no window")
|
|
}
|
|
|
|
fn focused_window_mut(&mut self) -> eyre::Result<&mut Window> {
|
|
self.focused_container_mut()?
|
|
.focused_window_mut()
|
|
.ok_or_eyre("there is no window")
|
|
}
|
|
|
|
/// Updates the list of `known_hwnds` and their monitor/workspace index pair
|
|
///
|
|
/// [`known_hwnds`]: `Self.known_hwnds`
|
|
pub fn update_known_hwnds(&mut self) {
|
|
tracing::trace!("updating list of known hwnds");
|
|
let mut known_hwnds = HashMap::new();
|
|
for (m_idx, monitor) in self.monitors().iter().enumerate() {
|
|
for (w_idx, workspace) in monitor.workspaces().iter().enumerate() {
|
|
for container in workspace.containers() {
|
|
for window in container.windows() {
|
|
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
|
|
}
|
|
}
|
|
|
|
for window in workspace.floating_windows() {
|
|
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
|
|
}
|
|
|
|
if let Some(window) = workspace.maximized_window {
|
|
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
|
|
}
|
|
|
|
if let Some(container) = &workspace.monocle_container {
|
|
for window in container.windows() {
|
|
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.known_hwnds != known_hwnds {
|
|
// Update reaper cache
|
|
{
|
|
let mut reaper_cache = crate::reaper::HWNDS_CACHE.lock();
|
|
*reaper_cache = known_hwnds.clone();
|
|
}
|
|
|
|
// Save to file
|
|
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
|
|
match OpenOptions::new()
|
|
.write(true)
|
|
.truncate(true)
|
|
.create(true)
|
|
.open(hwnd_json)
|
|
{
|
|
Ok(file) => {
|
|
if let Err(error) =
|
|
serde_json::to_writer_pretty(&file, &known_hwnds.keys().collect::<Vec<_>>())
|
|
{
|
|
tracing::error!("Failed to save list of known_hwnds on file: {}", error);
|
|
}
|
|
}
|
|
Err(error) => {
|
|
tracing::error!("Failed to save list of known_hwnds on file: {}", error);
|
|
}
|
|
}
|
|
|
|
// Store new hwnds
|
|
self.known_hwnds = known_hwnds;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::monitor;
|
|
use crossbeam_channel::Sender;
|
|
use crossbeam_channel::bounded;
|
|
use std::path::PathBuf;
|
|
use uuid::Uuid;
|
|
|
|
struct TestContext {
|
|
socket_path: Option<PathBuf>,
|
|
}
|
|
|
|
impl Drop for TestContext {
|
|
fn drop(&mut self) {
|
|
if let Some(socket_path) = &self.socket_path {
|
|
// Clean up the socket file
|
|
std::fs::remove_file(socket_path).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn setup_window_manager() -> (WindowManager, TestContext) {
|
|
let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
|
|
bounded(1);
|
|
|
|
// Temporary socket path for testing
|
|
let socket_name = format!("komorebi-test-{}.sock", Uuid::new_v4());
|
|
let socket_path = PathBuf::from(socket_name);
|
|
|
|
// Create a new WindowManager instance
|
|
let wm = WindowManager::new(receiver, Some(socket_path.clone()));
|
|
|
|
// Window Manager should be created successfully
|
|
assert!(wm.is_ok());
|
|
|
|
(
|
|
wm.unwrap(),
|
|
TestContext {
|
|
socket_path: Some(socket_path),
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_window_manager() {
|
|
let (_wm, _test_context) = setup_window_manager();
|
|
}
|
|
|
|
#[test]
|
|
fn test_focus_workspace() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// a new monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// the next index on the monitor should be the not-yet-created second workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
assert_eq!(new_workspace_index, 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
|
|
{
|
|
// focusing a workspace which doesn't yet exist should create it
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
monitor.focus_workspace(new_workspace_index).unwrap();
|
|
assert_eq!(monitor.workspaces().len(), 2);
|
|
}
|
|
assert_eq!(wm.focused_workspace_idx().unwrap(), 1);
|
|
|
|
{
|
|
// focusing a workspace many indices ahead should create all workspaces
|
|
// required along the way
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
monitor.focus_workspace(new_workspace_index + 2).unwrap();
|
|
assert_eq!(monitor.workspaces().len(), 4);
|
|
}
|
|
assert_eq!(wm.focused_workspace_idx().unwrap(), 3);
|
|
|
|
// we should be able to successfully focus an existing workspace too
|
|
wm.focus_workspace(0).unwrap();
|
|
assert_eq!(wm.focused_workspace_idx().unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_focused_workspace() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// a new monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// the next index on the monitor should be the not-yet-created second workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
assert_eq!(new_workspace_index, 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
|
|
{
|
|
// focus a workspace which doesn't yet exist should create it
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
monitor.focus_workspace(new_workspace_index + 1).unwrap();
|
|
|
|
// Monitor focused workspace should be 2
|
|
assert_eq!(monitor.focused_workspace_idx(), 2);
|
|
|
|
// Should have 3 Workspaces
|
|
assert_eq!(monitor.workspaces().len(), 3);
|
|
}
|
|
|
|
// Remove the focused workspace
|
|
wm.remove_focused_workspace().unwrap();
|
|
|
|
{
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
monitor.focus_workspace(new_workspace_index).unwrap();
|
|
|
|
// Should be focused on workspace 1
|
|
assert_eq!(monitor.focused_workspace_idx(), 1);
|
|
|
|
// Should have 2 Workspaces
|
|
assert_eq!(monitor.workspaces().len(), 2);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_workspace_name() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// a new monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// create a new workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
assert_eq!(new_workspace_index, 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
|
|
{
|
|
// focusing a workspace which doesn't yet exist should create it
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
monitor.focus_workspace(new_workspace_index).unwrap();
|
|
assert_eq!(monitor.workspaces().len(), 2);
|
|
}
|
|
assert_eq!(wm.focused_workspace_idx().unwrap(), 1);
|
|
|
|
// set the name of the first workspace
|
|
wm.set_workspace_name(0, 0, "workspace1".to_string())
|
|
.unwrap();
|
|
|
|
// monitor_workspace_index_by_name should return the index of the workspace with the name "workspace1"
|
|
let workspace_index = wm.monitor_workspace_index_by_name("workspace1").unwrap();
|
|
|
|
// workspace index 0 should now have the name "workspace1"
|
|
assert_eq!(workspace_index.1, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_switch_focus_monitors() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
assert_eq!(wm.monitors().len(), 1);
|
|
|
|
{
|
|
// Create a second monitor
|
|
let m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
assert_eq!(wm.monitors().len(), 2);
|
|
|
|
{
|
|
// Create a third monitor
|
|
let m = monitor::new(
|
|
2,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor3".to_string(),
|
|
"TestDevice3".to_string(),
|
|
"TestDeviceID3".to_string(),
|
|
Some("TestMonitorID3".to_string()),
|
|
);
|
|
|
|
// monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
assert_eq!(wm.monitors().len(), 3);
|
|
|
|
{
|
|
// Set the first monitor as focused and check if it is focused
|
|
wm.focus_monitor(0).unwrap();
|
|
let current_monitor_idx = wm.monitors.focused_idx();
|
|
assert_eq!(current_monitor_idx, 0);
|
|
}
|
|
|
|
{
|
|
// Set the second monitor as focused and check if it is focused
|
|
wm.focus_monitor(1).unwrap();
|
|
let current_monitor_idx = wm.monitors.focused_idx();
|
|
assert_eq!(current_monitor_idx, 1);
|
|
}
|
|
|
|
{
|
|
// Set the third monitor as focused and check if it is focused
|
|
wm.focus_monitor(2).unwrap();
|
|
let current_monitor_idx = wm.monitors.focused_idx();
|
|
assert_eq!(current_monitor_idx, 2);
|
|
}
|
|
|
|
// Switch back to the first monitor
|
|
wm.focus_monitor(0).unwrap();
|
|
let current_monitor_idx = wm.monitors.focused_idx();
|
|
assert_eq!(current_monitor_idx, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_switch_focus_to_nonexistent_monitor() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should have 1 monitor and the monitor index should be 0
|
|
assert_eq!(wm.monitors().len(), 1);
|
|
assert_eq!(wm.focused_monitor_idx(), 0);
|
|
|
|
// Should receive an error when trying to focus a non-existent monitor
|
|
let result = wm.focus_monitor(1);
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when focusing a non-existent monitor"
|
|
);
|
|
|
|
// Should still be focused on the first monitor
|
|
assert_eq!(wm.focused_monitor_idx(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_focused_monitor_size() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// monitor should have a single workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
assert_eq!(wm.monitors().len(), 1);
|
|
|
|
{
|
|
// Set the first monitor as focused and check if it is focused
|
|
wm.focus_monitor(0).unwrap();
|
|
let current_monitor_size = wm.focused_monitor_size().unwrap();
|
|
assert_eq!(current_monitor_size, Rect::default());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_focus_container_in_cycle_direction() {
|
|
let (mut wm, _test_context) = setup_window_manager();
|
|
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.layer = WorkspaceLayer::Tiling;
|
|
|
|
for i in 0..4 {
|
|
let mut container = Container::default();
|
|
container.windows_mut().push_back(Window::from(i));
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
assert_eq!(workspace.containers().len(), 4);
|
|
|
|
workspace.focus_container(0);
|
|
|
|
// add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
|
|
// container focus should be on the second container
|
|
wm.focus_container_in_cycle_direction(CycleDirection::Next)
|
|
.ok();
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 1);
|
|
|
|
// container focus should be on the third container
|
|
wm.focus_container_in_cycle_direction(CycleDirection::Next)
|
|
.ok();
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 2);
|
|
|
|
// container focus should be on the second container
|
|
wm.focus_container_in_cycle_direction(CycleDirection::Previous)
|
|
.ok();
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 1);
|
|
|
|
// container focus should be on the first container
|
|
wm.focus_container_in_cycle_direction(CycleDirection::Previous)
|
|
.ok();
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_transfer_window() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(0));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Create a second monitor
|
|
let mut m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should contain 2 monitors
|
|
assert_eq!(wm.monitors().len(), 2);
|
|
|
|
{
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let origin = (0, 0, 0);
|
|
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let target = (1, 0, 0);
|
|
|
|
// Transfer the window from monitor 0 to monitor 1
|
|
wm.transfer_window(origin, target).unwrap();
|
|
|
|
// Monitor 1 should contain 0 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 0);
|
|
|
|
// Monitor 2 should contain 2 containers
|
|
wm.focus_monitor(1).unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 2);
|
|
}
|
|
|
|
{
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let origin = (1, 0, 0);
|
|
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let target = (0, 0, 0);
|
|
|
|
// Transfer the window from monitor 1 back to monitor 0
|
|
wm.transfer_window(origin, target).unwrap();
|
|
|
|
// Monitor 2 should contain 1 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
// Monitor 1 should contain 1 containers
|
|
wm.focus_monitor(0).unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_transfer_window_to_nonexistent_monitor() {
|
|
// NOTE: transfer_window is primarily used when a window is being dragged by a mouse. The
|
|
// transfer_window function does return an error when the target monitor doesn't exist but
|
|
// there is a bug where the window isn't in the container after the window fails to
|
|
// transfer. The test will test for the result of the transfer_window function but not if
|
|
// the window is in the container after the transfer fails.
|
|
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(0));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let origin = (0, 0, 0);
|
|
|
|
// Monitor 1, Workspace 0, Window 0
|
|
//
|
|
let target = (1, 0, 0);
|
|
|
|
// Attempt to transfer the window from monitor 0 to a non-existent monitor
|
|
let result = wm.transfer_window(origin, target);
|
|
|
|
// Result should be an error since the monitor doesn't exist
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when transferring to a non-existent monitor"
|
|
);
|
|
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 0);
|
|
assert_eq!(wm.focused_workspace_idx().unwrap(), 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_transfer_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(0));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Create a second monitor
|
|
let mut m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should contain 2 monitors
|
|
assert_eq!(wm.monitors().len(), 2);
|
|
|
|
{
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let origin = (0, 0, 0);
|
|
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let target = (1, 0, 0);
|
|
|
|
// Transfer the window from monitor 0 to monitor 1
|
|
wm.transfer_container(origin, target).unwrap();
|
|
|
|
// Monitor 1 should contain 0 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 0);
|
|
|
|
// Monitor 2 should contain 2 containers
|
|
wm.focus_monitor(1).unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 2);
|
|
}
|
|
|
|
{
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let origin = (1, 0, 0);
|
|
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let target = (0, 0, 0);
|
|
|
|
// Transfer the window from monitor 1 back to monitor 0
|
|
wm.transfer_container(origin, target).unwrap();
|
|
|
|
// Monitor 2 should contain 1 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
// Monitor 1 should contain 1 containers
|
|
wm.focus_monitor(0).unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_window_from_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor1".to_string(),
|
|
"TestDevice1".to_string(),
|
|
"TestDeviceID1".to_string(),
|
|
Some("TestMonitorID1".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Focus last window
|
|
container.focus_window(2);
|
|
|
|
// Should be focused on the 2nd window
|
|
assert_eq!(container.focused_window_idx(), 2);
|
|
|
|
// Add the container to a workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Remove the focused window from the container
|
|
wm.remove_window_from_container().ok();
|
|
|
|
{
|
|
// Should have 2 containers in the workspace
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 2);
|
|
|
|
// Should contain 1 window in the new container
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 1);
|
|
}
|
|
|
|
{
|
|
// Switch to the old container
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
workspace.focus_container(0);
|
|
|
|
// Should contain 2 windows in the old container
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 2);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_nonexistent_window_from_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor1".to_string(),
|
|
"TestDevice1".to_string(),
|
|
"TestDeviceID1".to_string(),
|
|
Some("TestMonitorID1".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let container = Container::default();
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 0);
|
|
|
|
// Add the container to a workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should receive an error when trying to remove a window from an empty container
|
|
let result = wm.remove_window_from_container();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to remove a window from an empty container"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cycle_container_window_in_direction() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
|
|
{
|
|
let mut container = Container::default();
|
|
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add container to workspace
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Cycle to the next window
|
|
wm.cycle_container_window_in_direction(CycleDirection::Next)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 1
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 1);
|
|
}
|
|
|
|
// Cycle to the next window
|
|
wm.cycle_container_window_in_direction(CycleDirection::Next)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 2
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 2);
|
|
}
|
|
|
|
// Cycle to the previous window
|
|
wm.cycle_container_window_in_direction(CycleDirection::Previous)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 1
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_cycle_nonexistent_windows() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor1".to_string(),
|
|
"TestDevice1".to_string(),
|
|
"TestDeviceID1".to_string(),
|
|
Some("TestMonitorID1".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let container = Container::default();
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 0);
|
|
|
|
// Add the container to a workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should return an error when trying to cycle through windows in an empty container
|
|
let result = wm.cycle_container_window_in_direction(CycleDirection::Next);
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when cycling through windows in an empty container"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cycle_container_window_index_in_direction() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
|
|
{
|
|
let mut container = Container::default();
|
|
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add container to workspace
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Cycle to the next window
|
|
wm.cycle_container_window_index_in_direction(CycleDirection::Next)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 1
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 1);
|
|
}
|
|
|
|
// Cycle to the next window
|
|
wm.cycle_container_window_index_in_direction(CycleDirection::Next)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 2
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 2);
|
|
}
|
|
|
|
// Cycle to the Previous window
|
|
wm.cycle_container_window_index_in_direction(CycleDirection::Previous)
|
|
.ok();
|
|
|
|
{
|
|
// Should be on Window 1
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.focused_window_idx(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_swap_containers() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Create a second monitor
|
|
let mut m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should contain 2 monitors
|
|
assert_eq!(wm.monitors().len(), 2);
|
|
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let origin = (0, 0, 0);
|
|
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let target = (1, 0, 0);
|
|
|
|
wm.swap_containers(origin, target).unwrap();
|
|
|
|
{
|
|
// Monitor 0 Workspace 0 container 0 should contain 1 container
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 1);
|
|
}
|
|
|
|
wm.focus_monitor(1).unwrap();
|
|
|
|
{
|
|
// Monitor 1 Workspace 0 container 0 should contain 3 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 3);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_swap_container_with_nonexistent_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Monitor 0, Workspace 0, Window 0
|
|
let origin = (0, 0, 0);
|
|
|
|
// Monitor 1, Workspace 0, Window 0
|
|
let target = (0, 3, 0);
|
|
|
|
// Should be focused on the first container
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 0);
|
|
|
|
// Should return an error since there is only one container in the workspace
|
|
let result = wm.swap_containers(origin, target);
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when swapping with a non-existent container"
|
|
);
|
|
|
|
// Should still be focused on the first container
|
|
assert_eq!(wm.focused_container_idx().unwrap(), 0);
|
|
|
|
{
|
|
// Should still have 1 container in the workspace
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
// Container should still have 3 windows
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 3);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_swap_monitor_workspaces() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a first monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Create a second monitor
|
|
let mut m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Should contain 1 container
|
|
assert_eq!(workspace.containers().len(), 1);
|
|
|
|
// Add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Swap the workspaces between Monitor 0 and Monitor 1
|
|
wm.swap_monitor_workspaces(0, 1).ok();
|
|
|
|
{
|
|
// The focused workspace container in Monitor 0 should contain 3 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 1);
|
|
}
|
|
|
|
// Switch to Monitor 1
|
|
wm.focus_monitor(1).unwrap();
|
|
assert_eq!(wm.focused_monitor_idx(), 1);
|
|
|
|
{
|
|
// The focused workspace container in Monitor 1 should contain 3 containers
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
let container = workspace.focused_container_mut().unwrap();
|
|
assert_eq!(container.windows().len(), 3);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_swap_workspace_with_nonexistent_monitor() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add another workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
m.focus_workspace(new_workspace_index).unwrap();
|
|
|
|
// Should have 2 workspaces
|
|
assert_eq!(m.workspaces().len(), 2);
|
|
|
|
// Add monitor to window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should be an error since Monitor 1 does not exist
|
|
let result = wm.swap_monitor_workspaces(1, 0);
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when swapping with a non-existent monitor"
|
|
);
|
|
|
|
{
|
|
// Should still have 2 workspaces in Monitor 0
|
|
let monitor = wm.monitors().front().unwrap();
|
|
let workspaces = monitor.workspaces();
|
|
assert_eq!(
|
|
workspaces.len(),
|
|
2,
|
|
"Expected 2 workspaces after swap attempt"
|
|
);
|
|
assert_eq!(wm.focused_monitor_idx(), 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_move_workspace_to_monitor() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add another workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
m.focus_workspace(new_workspace_index).unwrap();
|
|
|
|
// Should have 2 workspaces
|
|
assert_eq!(m.workspaces().len(), 2);
|
|
|
|
// Add monitor to window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
let m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor2".to_string(),
|
|
"TestDevice2".to_string(),
|
|
"TestDeviceID2".to_string(),
|
|
Some("TestMonitorID2".to_string()),
|
|
);
|
|
|
|
// Should contain 1 workspace
|
|
assert_eq!(m.workspaces().len(), 1);
|
|
|
|
// Add monitor to workspace
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should contain 2 monitors
|
|
assert_eq!(wm.monitors().len(), 2);
|
|
|
|
// Move a workspace from Monitor 0 to Monitor 1
|
|
wm.move_workspace_to_monitor(1).ok();
|
|
|
|
{
|
|
// Should be focused on Monitor 1
|
|
assert_eq!(wm.focused_monitor_idx(), 1);
|
|
|
|
// Should contain 2 workspaces
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
assert_eq!(monitor.workspaces().len(), 2);
|
|
}
|
|
|
|
{
|
|
// Switch to Monitor 0
|
|
wm.focus_monitor(0).unwrap();
|
|
|
|
// Should contain 1 workspace
|
|
let monitor = wm.focused_monitor_mut().unwrap();
|
|
assert_eq!(monitor.workspaces().len(), 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_move_workspace_to_nonexistent_monitor() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add another workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
m.focus_workspace(new_workspace_index).unwrap();
|
|
|
|
// Should have 2 workspaces
|
|
assert_eq!(m.workspaces().len(), 2);
|
|
|
|
// Add monitor to window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Attempt to move a workspace to a non-existent monitor
|
|
let result = wm.move_workspace_to_monitor(1);
|
|
|
|
// Should be an error since Monitor 1 does not exist
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when moving to a non-existent monitor"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_tiling() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Set Workspace Layer to Tiling
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.layer = WorkspaceLayer::Tiling;
|
|
|
|
// Tiling state should be true
|
|
assert!(workspace.tile);
|
|
|
|
// Add monitor to workspace
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Tiling state should be false
|
|
wm.toggle_tiling().unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert!(!workspace.tile);
|
|
}
|
|
|
|
{
|
|
// Tiling state should be true
|
|
wm.toggle_tiling().unwrap();
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert!(workspace.tile);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_lock() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Add monitor with default workspace to
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
|
|
// Create containers to add to the workspace
|
|
for _ in 0..3 {
|
|
let container = Container::default();
|
|
workspace.add_container_to_back(container);
|
|
}
|
|
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Ensure container 2 is not locked
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert_eq!(workspace.focused_container_idx(), 2);
|
|
assert!(!workspace.focused_container().unwrap().locked);
|
|
}
|
|
|
|
// Toggle lock on focused container
|
|
wm.toggle_lock().unwrap();
|
|
|
|
{
|
|
// Ensure container 2 is locked
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert!(workspace.focused_container().unwrap().locked);
|
|
}
|
|
|
|
// Toggle lock on focused container
|
|
wm.toggle_lock().unwrap();
|
|
|
|
{
|
|
// Ensure container 2 is not locked
|
|
let workspace = wm.focused_workspace_mut().unwrap();
|
|
assert!(!workspace.focused_container().unwrap().locked);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_float_window() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Add focused window to floating window list
|
|
wm.float_window().ok();
|
|
|
|
{
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let floating_windows = workspace.floating_windows();
|
|
let container = workspace.focused_container().unwrap();
|
|
|
|
// Hwnd 0 should be added to floating_windows
|
|
assert_eq!(floating_windows[0].hwnd, 0);
|
|
|
|
// Should have a length of 1
|
|
assert_eq!(floating_windows.len(), 1);
|
|
|
|
// Should have 2 windows in the container
|
|
assert_eq!(container.windows().len(), 2);
|
|
|
|
// Should be focused on window 1
|
|
assert_eq!(container.focused_window(), Some(&Window { hwnd: 1 }));
|
|
}
|
|
|
|
// Add focused window to floating window list
|
|
wm.float_window().ok();
|
|
|
|
{
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let floating_windows = workspace.floating_windows();
|
|
let container = workspace.focused_container().unwrap();
|
|
|
|
// Hwnd 1 should be added to floating_windows
|
|
assert_eq!(floating_windows[1].hwnd, 1);
|
|
|
|
// Should have a length of 2
|
|
assert_eq!(floating_windows.len(), 2);
|
|
|
|
// Should have 1 window in the container
|
|
assert_eq!(container.windows().len(), 1);
|
|
|
|
// Should be focused on window 2
|
|
assert_eq!(container.focused_window(), Some(&Window { hwnd: 2 }));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_float_nonexistent_window() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add another workspace
|
|
let new_workspace_index = m.new_workspace_idx();
|
|
m.focus_workspace(new_workspace_index).unwrap();
|
|
|
|
// Should have 2 workspaces
|
|
assert_eq!(m.workspaces().len(), 2);
|
|
|
|
// Add monitor to window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should return an error when trying to float a non-existent window
|
|
let result = wm.float_window();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to float a non-existent window"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_maximize_and_unmaximize_window() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// No windows should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, None);
|
|
}
|
|
|
|
// Maximize the focused window
|
|
wm.maximize_window().ok();
|
|
|
|
{
|
|
// Window 0 should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, Some(Window::from(0)));
|
|
}
|
|
|
|
wm.unmaximize_window().ok();
|
|
|
|
{
|
|
// No windows should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, None);
|
|
}
|
|
|
|
// Focus container at index 1
|
|
wm.focused_workspace_mut().unwrap().focus_container(1);
|
|
|
|
{
|
|
// Focus the window at index 1
|
|
let container = wm.focused_container_mut().unwrap();
|
|
container.focus_window(1);
|
|
}
|
|
|
|
// Maximize the focused window
|
|
wm.maximize_window().ok();
|
|
|
|
{
|
|
// Window 2 should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, Some(Window::from(2)));
|
|
}
|
|
|
|
wm.unmaximize_window().ok();
|
|
|
|
{
|
|
// No windows should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_maximize() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add three windows to the container
|
|
for i in 0..3 {
|
|
container.windows_mut().push_back(Window::from(i));
|
|
}
|
|
|
|
// Should have 3 windows in the container
|
|
assert_eq!(container.windows().len(), 3);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Toggle maximize on
|
|
wm.toggle_maximize().ok();
|
|
|
|
{
|
|
// Window 0 should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, Some(Window::from(0)));
|
|
}
|
|
|
|
// Toggle maximize off
|
|
wm.toggle_maximize().ok();
|
|
|
|
{
|
|
// No windows should be maximized
|
|
let workspace = wm.focused_workspace().unwrap();
|
|
let maximized_window = workspace.maximized_window;
|
|
assert_eq!(maximized_window, None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_maximize_nonexistent_window() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let container = Container::default();
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should return an error when trying to toggle maximize on a non-existent window
|
|
let result = wm.toggle_maximize();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to toggle maximize on a non-existent window"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_monocle_on_and_monocle_off() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
|
|
// Should have 1 window in the container
|
|
assert_eq!(container.windows().len(), 1);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Move container to monocle container
|
|
wm.monocle_on().ok();
|
|
|
|
{
|
|
// Container should be a monocle container
|
|
let monocle_container = wm
|
|
.focused_workspace()
|
|
.unwrap()
|
|
.monocle_container
|
|
.as_ref()
|
|
.unwrap();
|
|
assert_eq!(monocle_container.windows().len(), 1);
|
|
assert_eq!(monocle_container.windows()[0].hwnd, 1);
|
|
}
|
|
|
|
{
|
|
// Should not have any containers
|
|
let container = wm.focused_workspace().unwrap();
|
|
assert_eq!(container.containers().len(), 0);
|
|
}
|
|
|
|
// Move monocle container to regular container
|
|
wm.monocle_off().ok();
|
|
|
|
{
|
|
// Should have 1 container in the workspace
|
|
let container = wm.focused_workspace().unwrap();
|
|
assert_eq!(container.containers().len(), 1);
|
|
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
|
|
}
|
|
|
|
{
|
|
// No windows should be in the monocle container
|
|
let monocle_container = &wm.focused_workspace().unwrap().monocle_container;
|
|
assert_eq!(*monocle_container, None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_monocle_on_and_off_nonexistent_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should return an error when trying to move a non-existent container to monocle
|
|
let result = wm.monocle_on();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to move a non-existent container to monocle"
|
|
);
|
|
|
|
// Should return an error when trying to restore a non-existent container from monocle
|
|
let result = wm.monocle_off();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to restore a non-existent container from monocle"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_monocle() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Create a container
|
|
let mut container = Container::default();
|
|
|
|
// Add a window to the container
|
|
container.windows_mut().push_back(Window::from(1));
|
|
|
|
// Should have 1 window in the container
|
|
assert_eq!(container.windows().len(), 1);
|
|
|
|
// Add the container to the workspace
|
|
let workspace = m.focused_workspace_mut().unwrap();
|
|
workspace.add_container_to_back(container);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Toggle monocle on
|
|
wm.toggle_monocle().ok();
|
|
|
|
{
|
|
// Container should be a monocle container
|
|
let monocle_container = wm
|
|
.focused_workspace()
|
|
.unwrap()
|
|
.monocle_container
|
|
.as_ref()
|
|
.unwrap();
|
|
assert_eq!(monocle_container.windows().len(), 1);
|
|
assert_eq!(monocle_container.windows()[0].hwnd, 1);
|
|
}
|
|
|
|
{
|
|
// Should not have any containers
|
|
let container = wm.focused_workspace().unwrap();
|
|
assert_eq!(container.containers().len(), 0);
|
|
}
|
|
|
|
// Toggle monocle off
|
|
wm.toggle_monocle().ok();
|
|
|
|
{
|
|
// Should have 1 container in the workspace
|
|
let container = wm.focused_workspace().unwrap();
|
|
assert_eq!(container.containers().len(), 1);
|
|
assert_eq!(container.containers()[0].windows()[0].hwnd, 1);
|
|
}
|
|
|
|
{
|
|
// No windows should be in the monocle container
|
|
let monocle_container = &wm.focused_workspace().unwrap().monocle_container;
|
|
assert_eq!(*monocle_container, None);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_toggle_monocle_nonexistent_container() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Should return an error when trying to toggle monocle on a non-existent container
|
|
let result = wm.toggle_monocle();
|
|
assert!(
|
|
result.is_err(),
|
|
"Expected an error when trying to toggle monocle on a non-existent container"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ensure_named_workspace_for_monitor() {
|
|
let (mut wm, _context) = setup_window_manager();
|
|
|
|
{
|
|
// Create a monitor
|
|
let m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor".to_string(),
|
|
"TestDevice".to_string(),
|
|
"TestDeviceID".to_string(),
|
|
Some("TestMonitorID".to_string()),
|
|
);
|
|
|
|
// Add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
{
|
|
// Create a monitor
|
|
let m = monitor::new(
|
|
1,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"TestMonitor1".to_string(),
|
|
"TestDevice1".to_string(),
|
|
"TestDeviceID1".to_string(),
|
|
Some("TestMonitorID1".to_string()),
|
|
);
|
|
|
|
// Add the monitor to the window manager
|
|
wm.monitors_mut().push_back(m);
|
|
}
|
|
|
|
// Workspace names list
|
|
let mut workspace_names = vec!["Workspace".to_string(), "Workspace1".to_string()];
|
|
|
|
// Ensure workspaces for monitor 1
|
|
wm.ensure_named_workspaces_for_monitor(1, &workspace_names)
|
|
.ok();
|
|
|
|
{
|
|
// Monitor 1 should have 2 workspaces with names "Workspace" and "Workspace1"
|
|
let monitor = wm.monitors().get(1).unwrap();
|
|
let workspaces = monitor.workspaces();
|
|
assert_eq!(workspaces.len(), workspace_names.len());
|
|
for (i, workspace) in workspaces.iter().enumerate() {
|
|
assert_eq!(workspace.name, Some(workspace_names[i].clone()));
|
|
}
|
|
}
|
|
|
|
// Add more workspaces to list
|
|
workspace_names.push("Workspace2".to_string());
|
|
workspace_names.push("Workspace3".to_string());
|
|
|
|
// Ensure workspaces for monitor 0
|
|
wm.ensure_named_workspaces_for_monitor(0, &workspace_names)
|
|
.ok();
|
|
|
|
{
|
|
// Monitor 0 should have 4 workspaces with names "Workspace", "Workspace1",
|
|
// "Workspace2" and "Workspace3"
|
|
let monitor = wm.monitors().front().unwrap();
|
|
let workspaces = monitor.workspaces();
|
|
assert_eq!(workspaces.len(), workspace_names.len());
|
|
for (i, workspace) in workspaces.iter().enumerate() {
|
|
assert_eq!(workspace.name, Some(workspace_names[i].clone()));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_add_window_handle_to_move_based_on_workspace_rule() {
|
|
let (wm, _context) = setup_window_manager();
|
|
|
|
// Mock Data representing a window and its workspace/movement details
|
|
let window_title = String::from("TestWindow");
|
|
let hwnd = 12345;
|
|
let origin_monitor_idx = 0;
|
|
let origin_workspace_idx = 0;
|
|
let target_monitor_idx = 2;
|
|
let target_workspace_idx = 3;
|
|
let floating = false;
|
|
|
|
// Empty vector to hold workspace rule enforcement operations
|
|
let mut to_move: Vec<EnforceWorkspaceRuleOp> = Vec::new();
|
|
|
|
// Call the function to add a window movement operation based on workspace rules
|
|
wm.add_window_handle_to_move_based_on_workspace_rule(
|
|
&window_title,
|
|
hwnd,
|
|
origin_monitor_idx,
|
|
origin_workspace_idx,
|
|
target_monitor_idx,
|
|
target_workspace_idx,
|
|
floating,
|
|
&mut to_move,
|
|
);
|
|
|
|
// Verify that the vector contains the expected operation with the correct values
|
|
assert_eq!(to_move.len(), 1);
|
|
let op = &to_move[0];
|
|
assert_eq!(op.hwnd, hwnd); // 12345
|
|
assert_eq!(op.origin_monitor_idx, origin_monitor_idx); // 0
|
|
assert_eq!(op.origin_workspace_idx, origin_workspace_idx); // 0
|
|
assert_eq!(op.target_monitor_idx, target_monitor_idx); // 2
|
|
assert_eq!(op.target_workspace_idx, target_workspace_idx); // 3
|
|
assert_eq!(op.floating, floating); // false
|
|
}
|
|
}
|