feat(wm): add cross boundary behaviour options

This commit introduces a new configuration option,
cross_boundary_behaviour, which allows the user to decide if they want
Focus and Move operations to operate across Workspace or Monitor
boundaries.

The default behaviour in komorebi has always been Monitor.  Setting this
to Workspace will make komorebi act a little like PaperWM, where
"komorebic focus left" and "komorebic focus right" will switch to the
next or previous workspace respectively if the currently focused window
as at either the left or right monitor boundary.

resolve #959
This commit is contained in:
LGUG2Z
2024-08-26 21:40:07 -07:00
parent 3c03528750
commit b799fd3077
7 changed files with 121 additions and 11 deletions

View File

@@ -361,6 +361,16 @@ pub enum MoveBehaviour {
NoOp,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]
pub enum CrossBoundaryBehaviour {
/// Attempt to perform actions across a workspace boundary
Workspace,
/// Attempt to perform actions across a monitor boundary
Monitor,
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema,
)]

View File

@@ -17,6 +17,7 @@ use crate::core::Rect;
use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
use crate::OperationDirection;
#[derive(
Debug,
@@ -114,7 +115,7 @@ impl Monitor {
.ok_or_else(|| anyhow!("there is no workspace"))?
};
workspace.add_container(container);
workspace.add_container_to_back(container);
Ok(())
}
@@ -149,6 +150,7 @@ impl Monitor {
&mut self,
target_workspace_idx: usize,
follow: bool,
direction: Option<OperationDirection>,
) -> Result<()> {
let workspace = self
.focused_workspace_mut()
@@ -173,7 +175,11 @@ impl Monitor {
Some(workspace) => workspace,
};
target_workspace.add_container(container);
if matches!(direction, Some(OperationDirection::Right)) {
target_workspace.add_container_to_front(container);
} else {
target_workspace.add_container_to_back(container);
}
if follow {
self.focus_workspace(target_workspace_idx)?;

View File

@@ -319,7 +319,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Put the orphaned containers somewhere visible
for container in orphaned_containers {
focused_ws.add_container(container);
focused_ws.add_container_to_back(container);
}
// Gotta reset the focus or the movement will feel "off"

View File

@@ -441,7 +441,7 @@ impl WindowManager {
self.adjust_workspace_padding(sizing, adjustment)?;
}
SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, true)?;
self.move_container_to_workspace(workspace_idx, true, None)?;
}
SocketMessage::CycleMoveContainerToWorkspace(direction) => {
let focused_monitor = self
@@ -457,7 +457,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.move_container_to_workspace(workspace_idx, true)?;
self.move_container_to_workspace(workspace_idx, true, None)?;
}
SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, true)?;
@@ -475,7 +475,7 @@ impl WindowManager {
self.move_container_to_monitor(monitor_idx, None, true)?;
}
SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {
self.move_container_to_workspace(workspace_idx, false)?;
self.move_container_to_workspace(workspace_idx, false, None)?;
}
SocketMessage::CycleSendContainerToWorkspace(direction) => {
let focused_monitor = self
@@ -491,7 +491,7 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.move_container_to_workspace(workspace_idx, false)?;
self.move_container_to_workspace(workspace_idx, false, None)?;
}
SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {
self.move_container_to_monitor(monitor_idx, None, false)?;

View File

@@ -26,6 +26,7 @@ use crate::window_manager::WindowManager;
use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::workspace::Workspace;
use crate::CrossBoundaryBehaviour;
use crate::ANIMATION_DURATION;
use crate::ANIMATION_ENABLED;
use crate::ANIMATION_FPS;
@@ -265,6 +266,9 @@ pub struct StaticConfig {
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
/// Determine what happens when an action is called on a window at a monitor boundary (default: Monitor)
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_boundary_behaviour: Option<CrossBoundaryBehaviour>,
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
#[serde(skip_serializing_if = "Option::is_none")]
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
@@ -565,6 +569,7 @@ impl From<&WindowManager> for StaticConfig {
resize_delta: Option::from(value.resize_delta),
window_container_behaviour: Option::from(value.window_container_behaviour),
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),
unmanaged_window_operation_behaviour: Option::from(
value.unmanaged_window_operation_behaviour,
),
@@ -866,6 +871,9 @@ impl StaticConfig {
cross_monitor_move_behaviour: value
.cross_monitor_move_behaviour
.unwrap_or(MoveBehaviour::Swap),
cross_boundary_behaviour: value
.cross_boundary_behaviour
.unwrap_or(CrossBoundaryBehaviour::Monitor),
unmanaged_window_operation_behaviour: value
.unmanaged_window_operation_behaviour
.unwrap_or(OperationBehaviour::Op),

View File

@@ -65,6 +65,7 @@ use crate::winevent_listener;
use crate::workspace::Workspace;
use crate::BorderColours;
use crate::Colour;
use crate::CrossBoundaryBehaviour;
use crate::Rgb;
use crate::WorkspaceRule;
use crate::CUSTOM_FFM;
@@ -92,6 +93,7 @@ pub struct WindowManager {
pub resize_delta: i32,
pub window_container_behaviour: WindowContainerBehaviour,
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,
@@ -274,6 +276,7 @@ impl WindowManager {
work_area_offset: None,
window_container_behaviour: WindowContainerBehaviour::Create,
cross_monitor_move_behaviour: MoveBehaviour::Swap,
cross_boundary_behaviour: CrossBoundaryBehaviour::Workspace,
unmanaged_window_operation_behaviour: OperationBehaviour::Op,
resize_delta: 50,
focus_follows_mouse: None,
@@ -1119,7 +1122,7 @@ impl WindowManager {
if focused_monitor_idx == monitor_idx {
if let Some(workspace_idx) = workspace_idx {
return self.move_container_to_workspace(workspace_idx, follow);
return self.move_container_to_workspace(workspace_idx, follow, None);
}
}
@@ -1187,7 +1190,12 @@ impl WindowManager {
}
#[tracing::instrument(skip(self))]
pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> {
pub fn move_container_to_workspace(
&mut self,
idx: usize,
follow: bool,
direction: Option<OperationDirection>,
) -> Result<()> {
self.handle_unmanaged_window_behaviour()?;
tracing::info!("moving container");
@@ -1197,7 +1205,7 @@ impl WindowManager {
.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?;
monitor.move_container_to_workspace(idx, follow)?;
monitor.move_container_to_workspace(idx, follow, direction)?;
monitor.load_focused_workspace(mouse_follows_focus)?;
self.update_focused_workspace(mouse_follows_focus, true)?;
@@ -1239,6 +1247,7 @@ impl WindowManager {
self.handle_unmanaged_window_behaviour()?;
let workspace = self.focused_workspace()?;
let workspace_idx = self.focused_workspace_idx()?;
tracing::info!("focusing container");
@@ -1250,6 +1259,39 @@ impl WindowManager {
let mut cross_monitor_monocle = false;
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)?;
return Ok(());
}
// if there is no container in that direction for this workspace
match new_idx {
None => {
@@ -1292,6 +1334,7 @@ impl WindowManager {
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
@@ -1305,6 +1348,40 @@ impl WindowManager {
let origin_monitor_idx = self.focused_monitor_idx();
let target_container_idx = workspace.new_idx_for_direction(direction);
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,
};
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

View File

@@ -656,11 +656,16 @@ impl Workspace {
Ok(())
}
pub fn add_container(&mut self, container: Container) {
pub fn add_container_to_back(&mut self, container: Container) {
self.containers_mut().push_back(container);
self.focus_last_container();
}
pub fn add_container_to_front(&mut self, container: Container) {
self.containers_mut().push_front(container);
self.focus_first_container();
}
pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) {
self.containers_mut().insert(idx, container);
}
@@ -1442,4 +1447,8 @@ impl Workspace {
fn focus_last_container(&mut self) {
self.focus_container(self.containers().len().saturating_sub(1));
}
fn focus_first_container(&mut self) {
self.focus_container(0);
}
}