fix(borders): do multiple render passes when required

This commit addresses a border rendering issue when moving a window from
a higher-indexed monitor to a lower-indexed monitor.

Previously, we would do a single render pass across all monitors in
order of their indexes, destroying borders no longer needed, and
creating new borders for new windows.

This resulted in the window being moved to the lower-indexed monitor
still existing in the global border cache when that monitor's borders
were updated, but then being removed when the borders of the origin,
higher-indexed monitor were updated.

With the changes in this commit, if we encounter a situation like this,
an additional render pass will be executed to ensure that the window
will have a corresponding border created on the destination
lower-indexed monitor.
This commit is contained in:
LGUG2Z
2025-03-16 14:05:38 -07:00
parent ff2aa5e51a
commit 5919f88b38
+37 -6
View File
@@ -340,6 +340,24 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
continue 'receiver; continue 'receiver;
} }
// we may need multiple render passes if a window is moved from a monitor
// with a higher index to a monitor with a lower index
//
// this is because the border on the window will be removed in a later iteration
// (due to the higher monitor index), but without an extra render pass, we will
// never render a new border for it on the earlier monitor index which has already
// been processed
let mut render_pass_required = true;
let mut render_pass_count = 0;
while render_pass_required {
render_pass_count += 1;
tracing::debug!("executing render pass {render_pass_count}");
// optimistically assume this will be the last render pass
render_pass_required = false;
'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() { 'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {
// Only operate on the focused workspace of each monitor // Only operate on the focused workspace of each monitor
if let Some(ws) = m.focused_workspace() { if let Some(ws) = m.focused_workspace() {
@@ -388,7 +406,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Update the borders tracking_hwnd in case it changed and remove the // Update the borders tracking_hwnd in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed. // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
if border.tracking_hwnd != focused_window_hwnd { if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) { if let Some(previous) =
windows_borders.get(&border.tracking_hwnd)
{
// Only remove the border from `windows_borders` if it // Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then // still corresponds to the same border, if doesn't then
// it means it was already updated by another border for // it means it was already updated by another border for
@@ -426,7 +446,8 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
continue 'monitors; continue 'monitors;
} }
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default(); let foreground_hwnd =
WindowsApi::foreground_window().unwrap_or_default();
let foreground_monitor_id = let foreground_monitor_id =
WindowsApi::monitor_from_window(foreground_hwnd); WindowsApi::monitor_from_window(foreground_hwnd);
let is_maximized = foreground_monitor_id == m.id() let is_maximized = foreground_monitor_id == m.id()
@@ -456,13 +477,18 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
// Remove any borders not associated with the focused workspace // Remove any borders not associated with the focused workspace
remove_borders( let removed = remove_borders(
&mut borders, &mut borders,
&mut windows_borders, &mut windows_borders,
monitor_idx, monitor_idx,
|id, _| !container_and_floating_window_ids.contains(id), |id, _| !container_and_floating_window_ids.contains(id),
)?; )?;
if removed > 0 && monitor_idx > 0 {
render_pass_required = true;
tracing::debug!("another rendering pass required");
}
'containers: for (idx, c) in ws.containers().iter().enumerate() { 'containers: for (idx, c) in ws.containers().iter().enumerate() {
let focused_window_hwnd = let focused_window_hwnd =
c.focused_window().map(|w| w.hwnd).unwrap_or_default(); c.focused_window().map(|w| w.hwnd).unwrap_or_default();
@@ -502,7 +528,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Update the borders `tracking_hwnd` in case it changed and remove the // Update the borders `tracking_hwnd` in case it changed and remove the
// old `tracking_hwnd` from `WINDOWS_BORDERS` if needed. // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.
if border.tracking_hwnd != focused_window_hwnd { if border.tracking_hwnd != focused_window_hwnd {
if let Some(previous) = windows_borders.get(&border.tracking_hwnd) { if let Some(previous) =
windows_borders.get(&border.tracking_hwnd)
{
// Only remove the border from `windows_borders` if it // Only remove the border from `windows_borders` if it
// still corresponds to the same border, if doesn't then // still corresponds to the same border, if doesn't then
// it means it was already updated by another border for // it means it was already updated by another border for
@@ -594,6 +622,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
} }
} }
}
previous_snapshot = monitors; previous_snapshot = monitors;
previous_pending_move_op = pending_move_op; previous_pending_move_op = pending_move_op;
@@ -614,7 +643,7 @@ fn remove_borders(
windows_borders: &mut HashMap<isize, String>, windows_borders: &mut HashMap<isize, String>,
monitor_idx: usize, monitor_idx: usize,
condition: impl Fn(&String, &Border) -> bool, condition: impl Fn(&String, &Border) -> bool,
) -> color_eyre::Result<()> { ) -> color_eyre::Result<usize> {
let mut to_remove = vec![]; let mut to_remove = vec![];
for (id, border) in borders.iter() { for (id, border) in borders.iter() {
// if border is on this monitor // if border is on this monitor
@@ -629,11 +658,13 @@ fn remove_borders(
} }
} }
let len = to_remove.len();
for id in &to_remove { for id in &to_remove {
remove_border(id, borders, windows_borders)?; remove_border(id, borders, windows_borders)?;
} }
Ok(()) Ok(len)
} }
/// Removes the border with `id` and all its related info from all maps /// Removes the border with `id` and all its related info from all maps