From 3c84bfd27e74d0f50b327da25c4432f4f46b56ef Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Tue, 21 Jun 2022 16:19:37 -0700 Subject: [PATCH] feat(wm): add cross-monitor move behaviour config This commit provides two new commands, to set and toggle the behaviour (swap, insert) when moving window containers across monitor boundaries. MoveBehaviour::Swap has been selected as the default as this seems to be the default on bspwm. resolve #145 --- README.md | 4 ++ komorebi-core/src/lib.rs | 9 +++++ komorebi/src/process_command.rs | 14 +++++++ komorebi/src/window_manager.rs | 71 +++++++++++++++++++-------------- komorebic.lib.sample.ahk | 8 ++++ komorebic/src/main.rs | 15 +++++++ 6 files changed, 91 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index de021c50..d0e19a44 100644 --- a/README.md +++ b/README.md @@ -495,6 +495,8 @@ unmanage Unmanage a window that was forcibly m reload-configuration Reload ~/komorebi.ahk (if it exists) watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists) window-hiding-behaviour Set the window behaviour when switching workspaces / cycling stacks +cross-monitor-move-behaviour Set the behaviour when moving windows across monitor boundaries +toggle-cross-monitor-move-behaviour Toggle the behaviour when moving windows across monitor boundaries unmanaged-window-operation-behaviour Set the operation behaviour when the focused window is not managed float-rule Add a rule to always float the specified application manage-rule Add a rule to always manage the specified application @@ -531,7 +533,9 @@ used [is available here](komorebi.sample.with.lib.ahk). - [x] Window stacks - [x] Cycle through stacked windows - [x] Change focused window by direction +- [x] Change focused window by direction across monitor boundary - [x] Move focused window container in direction +- [x] Move focused window container in direction across monitor boundary - [x] Move focused window container to monitor and follow - [x] Move focused window container to workspace follow - [x] Send focused window container to monitor diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 7f7a41cc..f3c87c06 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -57,6 +57,8 @@ pub enum SocketMessage { ToggleMaximize, ToggleWindowContainerBehaviour, WindowHidingBehaviour(HidingBehaviour), + ToggleCrossMonitorMoveBehaviour, + CrossMonitorMoveBehaviour(MoveBehaviour), UnmanagedWindowOperationBehaviour(OperationBehaviour), // Current Workspace Commands ManageFocusedWindow, @@ -161,6 +163,13 @@ pub enum WindowContainerBehaviour { Append, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] +#[strum(serialize_all = "snake_case")] +pub enum MoveBehaviour { + Swap, + Insert, +} + #[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum HidingBehaviour { diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 7ac3f11c..980c4068 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -20,6 +20,7 @@ use komorebi_core::ApplicationIdentifier; use komorebi_core::Axis; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::Layout; +use komorebi_core::MoveBehaviour; use komorebi_core::OperationDirection; use komorebi_core::Rect; use komorebi_core::Sizing; @@ -706,6 +707,19 @@ impl WindowManager { let mut hiding_behaviour = HIDING_BEHAVIOUR.lock(); *hiding_behaviour = behaviour; } + SocketMessage::ToggleCrossMonitorMoveBehaviour => { + match self.cross_monitor_move_behaviour { + MoveBehaviour::Swap => { + self.cross_monitor_move_behaviour = MoveBehaviour::Insert; + } + MoveBehaviour::Insert => { + self.cross_monitor_move_behaviour = MoveBehaviour::Swap; + } + } + } + SocketMessage::CrossMonitorMoveBehaviour(behaviour) => { + self.cross_monitor_move_behaviour = behaviour; + } SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => { self.unmanaged_window_operation_behaviour = behaviour; } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index b1a74bbc..b08c0d39 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -22,6 +22,7 @@ use komorebi_core::CycleDirection; use komorebi_core::DefaultLayout; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::Layout; +use komorebi_core::MoveBehaviour; use komorebi_core::OperationBehaviour; use komorebi_core::OperationDirection; use komorebi_core::Rect; @@ -58,6 +59,7 @@ pub struct WindowManager { pub work_area_offset: Option, pub resize_delta: i32, pub window_container_behaviour: WindowContainerBehaviour, + pub cross_monitor_move_behaviour: MoveBehaviour, pub unmanaged_window_operation_behaviour: OperationBehaviour, pub focus_follows_mouse: Option, pub mouse_follows_focus: bool, @@ -74,6 +76,7 @@ pub struct State { pub invisible_borders: Rect, pub resize_delta: i32, pub new_window_behaviour: WindowContainerBehaviour, + pub cross_monitor_move_behaviour: MoveBehaviour, pub work_area_offset: Option, pub focus_follows_mouse: Option, pub mouse_follows_focus: bool, @@ -101,6 +104,7 @@ impl From<&WindowManager> for State { work_area_offset: wm.work_area_offset, resize_delta: wm.resize_delta, new_window_behaviour: wm.window_container_behaviour, + cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour, focus_follows_mouse: wm.focus_follows_mouse.clone(), mouse_follows_focus: wm.mouse_follows_focus, has_pending_raise_op: wm.has_pending_raise_op, @@ -172,6 +176,7 @@ impl WindowManager { virtual_desktop_id: current_virtual_desktop(), work_area_offset: None, window_container_behaviour: WindowContainerBehaviour::Create, + cross_monitor_move_behaviour: MoveBehaviour::Swap, unmanaged_window_operation_behaviour: OperationBehaviour::Op, resize_delta: 50, focus_follows_mouse: None, @@ -1013,41 +1018,47 @@ impl WindowManager { } } - { - let target_workspace = self.focused_workspace_mut()?; + // 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_else(|| { - anyhow!("could not remove container at given target index") - })?; + // 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_else(|| { + anyhow!("could not remove container at given target index") + })?; - let origin_workspace = - self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?; + 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); + // 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; - let invisible_borders = self.invisible_borders; - - self.monitors_mut() - .get_mut(origin_monitor_idx) - .ok_or_else(|| anyhow!("there is no monitor at this index"))? - .update_focused_workspace(offset, &invisible_borders)?; } + + // 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; + let invisible_borders = self.invisible_borders; + + self.monitors_mut() + .get_mut(origin_monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor at this index"))? + .update_focused_workspace(offset, &invisible_borders)?; } Some(new_idx) => { let workspace = self.focused_workspace_mut()?; diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index 8424799a..f0466f14 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -256,6 +256,14 @@ WindowHidingBehaviour(hiding_behaviour) { Run, komorebic.exe window-hiding-behaviour %hiding_behaviour%, , Hide } +CrossMonitorMoveBehaviour(move_behaviour) { + Run, komorebic.exe cross-monitor-move-behaviour %move_behaviour%, , Hide +} + +ToggleCrossMonitorMoveBehaviour() { + Run, komorebic.exe toggle-cross-monitor-move-behaviour, , Hide +} + UnmanagedWindowOperationBehaviour(operation_behaviour) { Run, komorebic.exe unmanaged-window-operation-behaviour %operation_behaviour%, , Hide } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 92675a64..ef8e0886 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -36,6 +36,7 @@ use komorebi_core::CycleDirection; use komorebi_core::DefaultLayout; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::HidingBehaviour; +use komorebi_core::MoveBehaviour; use komorebi_core::OperationBehaviour; use komorebi_core::OperationDirection; use komorebi_core::Rect; @@ -118,6 +119,7 @@ gen_enum_subcommand_args! { MouseFollowsFocus: BooleanState, Query: StateQuery, WindowHidingBehaviour: HidingBehaviour, + CrossMonitorMoveBehaviour: MoveBehaviour, UnmanagedWindowOperationBehaviour: OperationBehaviour, } @@ -627,6 +629,11 @@ enum SubCommand { /// Set the window behaviour when switching workspaces / cycling stacks #[clap(arg_required_else_help = true)] WindowHidingBehaviour(WindowHidingBehaviour), + /// Set the behaviour when moving windows across monitor boundaries + #[clap(arg_required_else_help = true)] + CrossMonitorMoveBehaviour(CrossMonitorMoveBehaviour), + /// Toggle the behaviour when moving windows across monitor boundaries + ToggleCrossMonitorMoveBehaviour, /// Set the operation behaviour when the focused window is not managed #[clap(arg_required_else_help = true)] UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour), @@ -1167,6 +1174,14 @@ fn main() -> Result<()> { SubCommand::WindowHidingBehaviour(arg) => { send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?; } + SubCommand::CrossMonitorMoveBehaviour(arg) => { + send_message( + &*SocketMessage::CrossMonitorMoveBehaviour(arg.move_behaviour).as_bytes()?, + )?; + } + SubCommand::ToggleCrossMonitorMoveBehaviour => { + send_message(&*SocketMessage::ToggleCrossMonitorMoveBehaviour.as_bytes()?)?; + } SubCommand::UnmanagedWindowOperationBehaviour(arg) => { send_message( &*SocketMessage::UnmanagedWindowOperationBehaviour(arg.operation_behaviour)