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
This commit is contained in:
LGUG2Z
2022-06-21 16:19:37 -07:00
parent c874bfc7bf
commit 3c84bfd27e
6 changed files with 91 additions and 30 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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<Rect>,
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<FocusFollowsMouseImplementation>,
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<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
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()?;

View File

@@ -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
}

View File

@@ -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)