mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-06 02:43:26 +02:00
fix(wm): handle cross-monitor drag/move events
This commit ensures that when a window is dragged across a monitor boundary, the ownership of the window container will be transferred to the target monitor's currently focused workspace. In order to achieve this, a new WindowManagerEvent variant has been added, MoveResizeStart, which will store an optional pending_move_op on the WindowManager struct. This must be consumed at the beginning of the handler for MoveResizeEnd. This is necessary because as soon as the window is dragged across a monitor boundary, an event is sent (and handled) to update the currently focused monitor and workspace as the target monitor and workspace, and we still need to have the information about the original monitor, workspace and container in order to make comparisons and ultimately remove the origin container to be able to transfer it. fix #58
This commit is contained in:
2
justfile
2
justfile
@@ -13,7 +13,7 @@ install-komorebic:
|
||||
cargo +stable install --path komorebic --locked
|
||||
|
||||
install-komorebi:
|
||||
cargo +stable install --path komorebic --locked
|
||||
cargo +stable install --path komorebi --locked
|
||||
|
||||
install:
|
||||
just install-komorebic
|
||||
|
||||
@@ -34,7 +34,7 @@ WorkspaceTiling(0, 4, "disable") ; Everything floats here
|
||||
|
||||
; Configure floating rules
|
||||
FloatRule("class", "SunAwtDialog") ; All the IntelliJ popups
|
||||
FloatRule("title", "Control Panek")
|
||||
FloatRule("title", "Control Panel")
|
||||
FloatRule("class", "TaskManagerWindow")
|
||||
FloatRule("exe", "Wally.exe")
|
||||
FloatRule("exe", "wincompose.exe")
|
||||
|
||||
@@ -208,7 +208,32 @@ impl WindowManager {
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(_, _) => {
|
||||
let monitor_idx = self.focused_monitor_idx();
|
||||
let workspace_idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||
.focused_workspace_idx();
|
||||
let container_idx = self
|
||||
.focused_monitor()
|
||||
.ok_or_else(|| anyhow!("there is no monitor with this idx"))?
|
||||
.focused_workspace()
|
||||
.ok_or_else(|| anyhow!("there is no workspace with this idx"))?
|
||||
.focused_container_idx();
|
||||
|
||||
self.pending_move_op = Option::from((monitor_idx, workspace_idx, container_idx));
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(_, window) => {
|
||||
// We need this because if the event ends on a different monitor,
|
||||
// that monitor will already have been focused and updated in the state
|
||||
let pending = self.pending_move_op;
|
||||
// Always consume the pending move op whenever this event is handled
|
||||
self.pending_move_op = None;
|
||||
|
||||
let target_monitor_idx = self
|
||||
.monitor_idx_from_current_pos()
|
||||
.ok_or_else(|| anyhow!("cannot get monitor idx from current position"))?;
|
||||
|
||||
let workspace = self.focused_workspace_mut()?;
|
||||
if workspace
|
||||
.floating_windows()
|
||||
@@ -218,12 +243,32 @@ impl WindowManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let focused_idx = workspace.focused_container_idx();
|
||||
let focused_container_idx = workspace.focused_container_idx();
|
||||
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
|
||||
let old_position = *workspace
|
||||
.latest_layout()
|
||||
.get(focused_idx)
|
||||
.ok_or_else(|| anyhow!("there is no latest layout"))?;
|
||||
let mut new_position = WindowsApi::window_rect(window.hwnd())?;
|
||||
.get(focused_container_idx)
|
||||
// If the move was to another monitor with an empty workspace, the
|
||||
// workspace here will refer to that empty workspace, which won't
|
||||
// have any latest layout set. We fall back to a Default for Rect
|
||||
// which allows us to make a reasonable guess that the drag has taken
|
||||
// place across a monitor boundary to an empty workspace
|
||||
.unwrap_or(&Rect::default());
|
||||
|
||||
// This will be true if we have moved to an empty workspace on another monitor
|
||||
let mut moved_across_monitors = old_position == Rect::default();
|
||||
|
||||
if let Some((origin_monitor_idx, _, _)) = pending {
|
||||
// If we didn't move to another monitor with an empty workspace, it is
|
||||
// still possible that we moved to another monitor with a populated workspace
|
||||
if !moved_across_monitors {
|
||||
// So we'll check if the origin monitor index and the target monitor index
|
||||
// are different, if they are, we can set the override
|
||||
moved_across_monitors = origin_monitor_idx != target_monitor_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust for the invisible borders
|
||||
new_position.left += invisible_borders.left;
|
||||
@@ -238,16 +283,72 @@ impl WindowManager {
|
||||
bottom: new_position.bottom - old_position.bottom,
|
||||
};
|
||||
|
||||
let is_move = resize.right == 0 && resize.bottom == 0;
|
||||
// If we have moved across the monitors, use that override, otherwise determine
|
||||
// if a move has taken place by ruling out a resize
|
||||
let is_move = moved_across_monitors || resize.right == 0 && resize.bottom == 0;
|
||||
|
||||
if is_move {
|
||||
tracing::info!("moving with mouse");
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace.swap_containers(focused_idx, target_idx);
|
||||
|
||||
if moved_across_monitors {
|
||||
if let Some((
|
||||
origin_monitor_idx,
|
||||
origin_workspace_idx,
|
||||
origin_container_idx,
|
||||
)) = pending
|
||||
{
|
||||
let target_workspace_idx = self
|
||||
.monitors()
|
||||
.get(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace_idx();
|
||||
|
||||
let target_container_idx = self
|
||||
.monitors()
|
||||
.get(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this idx"))?
|
||||
.focused_workspace()
|
||||
.ok_or_else(|| {
|
||||
anyhow!("there is no focused workspace for this monitor")
|
||||
})?
|
||||
.container_idx_from_current_point()
|
||||
// Default to 0 in the case of an empty workspace
|
||||
.unwrap_or(0);
|
||||
|
||||
self.transfer_container(
|
||||
(
|
||||
origin_monitor_idx,
|
||||
origin_workspace_idx,
|
||||
origin_container_idx,
|
||||
),
|
||||
(
|
||||
target_monitor_idx,
|
||||
target_workspace_idx,
|
||||
target_container_idx,
|
||||
),
|
||||
)?;
|
||||
|
||||
// We want to make sure both the origin and target monitors are updated,
|
||||
// so that we don't have ghost tiles until we force an interaction on
|
||||
// the origin monitor's focused workspace
|
||||
self.focus_monitor(origin_monitor_idx)?;
|
||||
self.focus_workspace(origin_workspace_idx)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
|
||||
self.focus_monitor(target_monitor_idx)?;
|
||||
self.focus_workspace(target_workspace_idx)?;
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
None => self.update_focused_workspace(true)?,
|
||||
} else {
|
||||
// Here we handle a simple move on the same monitor which is treated as
|
||||
// a container swap
|
||||
match workspace.container_idx_from_current_point() {
|
||||
Some(target_idx) => {
|
||||
workspace.swap_containers(focused_container_idx, target_idx);
|
||||
self.update_focused_workspace(false)?;
|
||||
}
|
||||
None => self.update_focused_workspace(true)?,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!("resizing with mouse");
|
||||
|
||||
@@ -53,6 +53,7 @@ pub struct WindowManager {
|
||||
pub hotwatch: Hotwatch,
|
||||
pub virtual_desktop_id: Option<usize>,
|
||||
pub has_pending_raise_op: bool,
|
||||
pub pending_move_op: Option<(usize, usize, usize)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -153,6 +154,7 @@ impl WindowManager {
|
||||
hotwatch: Hotwatch::new()?,
|
||||
virtual_desktop_id,
|
||||
has_pending_raise_op: false,
|
||||
pending_move_op: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -538,6 +540,93 @@ impl WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn transfer_container(
|
||||
&mut self,
|
||||
origin: (usize, usize, usize),
|
||||
target: (usize, usize, usize),
|
||||
) -> 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_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(origin_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace at this index"))?
|
||||
.remove_container(origin_container_idx)
|
||||
.ok_or_else(|| anyhow!("there is no container at this index"))?;
|
||||
|
||||
let target_workspace = self
|
||||
.monitors_mut()
|
||||
.get_mut(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(target_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("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),
|
||||
) -> 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_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(origin_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace at this index"))?
|
||||
.remove_container(origin_container_idx)
|
||||
.ok_or_else(|| anyhow!("there is no container at this index"))?;
|
||||
|
||||
let target_container = self
|
||||
.monitors_mut()
|
||||
.get_mut(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(target_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("there is no workspace at this index"))?
|
||||
.remove_container(target_container_idx);
|
||||
|
||||
self.monitors_mut()
|
||||
.get_mut(target_monitor_idx)
|
||||
.ok_or_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(target_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("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_else(|| anyhow!("there is no monitor at this index"))?
|
||||
.workspaces_mut()
|
||||
.get_mut(origin_workspace_idx)
|
||||
.ok_or_else(|| anyhow!("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, mouse_follows_focus: bool) -> Result<()> {
|
||||
tracing::info!("updating");
|
||||
|
||||
@@ -15,6 +15,7 @@ pub enum WindowManagerEvent {
|
||||
Hide(WinEvent, Window),
|
||||
Minimize(WinEvent, Window),
|
||||
Show(WinEvent, Window),
|
||||
MoveResizeStart(WinEvent, Window),
|
||||
MoveResizeEnd(WinEvent, Window),
|
||||
MouseCapture(WinEvent, Window),
|
||||
Manage(Window),
|
||||
@@ -51,6 +52,13 @@ impl Display for WindowManagerEvent {
|
||||
WindowManagerEvent::Show(winevent, window) => {
|
||||
write!(f, "Show (WinEvent: {}, Window: {})", winevent, window)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeStart(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
"MoveResizeStart (WinEvent: {}, Window: {})",
|
||||
winevent, window
|
||||
)
|
||||
}
|
||||
WindowManagerEvent::MoveResizeEnd(winevent, window) => {
|
||||
write!(
|
||||
f,
|
||||
@@ -87,6 +95,7 @@ impl WindowManagerEvent {
|
||||
| WindowManagerEvent::Hide(_, window)
|
||||
| WindowManagerEvent::Minimize(_, window)
|
||||
| WindowManagerEvent::Show(_, window)
|
||||
| WindowManagerEvent::MoveResizeStart(_, window)
|
||||
| WindowManagerEvent::MoveResizeEnd(_, window)
|
||||
| WindowManagerEvent::MouseCapture(_, window)
|
||||
| WindowManagerEvent::MonitorPoll(_, window)
|
||||
@@ -113,6 +122,7 @@ impl WindowManagerEvent {
|
||||
WinEvent::ObjectFocus | WinEvent::SystemForeground => {
|
||||
Option::from(Self::FocusChange(winevent, window))
|
||||
}
|
||||
WinEvent::SystemMoveSizeStart => Option::from(Self::MoveResizeStart(winevent, window)),
|
||||
WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)),
|
||||
WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {
|
||||
Option::from(Self::MouseCapture(winevent, window))
|
||||
|
||||
@@ -471,6 +471,13 @@ impl Workspace {
|
||||
container
|
||||
}
|
||||
|
||||
pub fn remove_container(&mut self, idx: usize) -> Option<Container> {
|
||||
let container = self.remove_container_by_idx(idx);
|
||||
self.focus_previous_container();
|
||||
|
||||
container
|
||||
}
|
||||
|
||||
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
|
||||
let len = NonZeroUsize::new(self.containers().len())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user