From 1f708f8aa09bf8bbce82314a76b7f34def90b798 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Sat, 31 Jan 2026 14:34:00 +0200 Subject: Fix desktop-icon deselection and workspace-teleport-on-close; document a shadow limit Desktop icons stayed highlighted after clicking a window: select_desktop_icon(None) was only ever called from start_desktop_marquee, never from the one place every focus path (click, Alt-Tab, dock IPC, scratchpad show, snap flyout) already funnels through. Added the deselect there instead of per-caller. Closing a focused window could silently switch the user's active workspace: remove_window's fallback picked self.order.last(), but that list is global, not per-workspace, so it could land on a background window elsewhere - and focus_window already switches workspace to match whatever it's given (a real, separate feature for a deliberate srd dispatch focus). Fixed by preferring a same-workspace window first. New general.close_focus_follows_workspace (default false, live-settable) controls what happens only when nothing is left on the current workspace at all: off leaves focus at nothing, matching Windows/GNOME/macOS; on restores the old always-follow-the-global-fallback behaviour. Three new tests. Also documented, not fixed: shadows can still bleed onto a neighbouring *monitor* near a multi-output seam (shadow_rect has no monitor-boundary awareness), found via a live cross-monitor screenshot. Moot for this session since general.shadows is already off in the live config, but a real, open gap for anyone who re-enables shadows on a multi-monitor setup. --- crates/core/src/manager/tests.rs | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'crates/core/src/manager/tests.rs') diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs index f703f25..6a3ba3f 100644 --- a/crates/core/src/manager/tests.rs +++ b/crates/core/src/manager/tests.rs @@ -10,6 +10,72 @@ wm } + #[test] + fn closing_the_focused_window_prefers_a_same_workspace_fallback_over_a_more_recent_global_one() { + let mut wm = wm_with_monitor(); + let a = wm.alloc_window_id(); + wm.add_window(Window::new(a, "a")); // workspace 1 + + let ws2 = wm.add_workspace("2", "dynamic"); + wm.switch_workspace(ws2); + let c = wm.alloc_window_id(); + wm.add_window(Window::new(c, "c")); // workspace 2, now globally most-recent + + wm.switch_workspace(1); + let b = wm.alloc_window_id(); + wm.add_window(Window::new(b, "b")); // workspace 1, now focused and globally most-recent + + assert_eq!(wm.current_workspace(), 1); + wm.remove_window(b); + // The naive "global most recent" fallback would have landed on `c` + // (workspace 2) here - `a`, still on the workspace the user is + // actually looking at, is what a real desktop would land on. + assert_eq!(wm.focused_id(), Some(a)); + assert_eq!(wm.current_workspace(), 1, "must not have been dragged onto workspace 2 by the fallback"); + } + + #[test] + fn closing_the_last_window_on_a_workspace_leaves_focus_none_by_default() { + let mut wm = wm_with_monitor(); + assert!(!wm.close_focus_follows_workspace, "default must be off, matching every mainstream desktop"); + let a = wm.alloc_window_id(); + wm.add_window(Window::new(a, "a")); // workspace 1 + + let ws2 = wm.add_workspace("2", "dynamic"); + wm.switch_workspace(ws2); + let c = wm.alloc_window_id(); + wm.add_window(Window::new(c, "c")); // workspace 2 + + wm.switch_workspace(1); + wm.focus_window(a); // re-focus `a`; current_workspace stays 1 (already there) + assert_eq!(wm.current_workspace(), 1); + + wm.remove_window(a); + // No window left on workspace 1 at all - with the setting off, + // this must not silently jump the user over to `c` on workspace 2. + assert_eq!(wm.focused_id(), None); + assert_eq!(wm.current_workspace(), 1); + } + + #[test] + fn closing_the_last_window_on_a_workspace_can_still_follow_when_opted_in() { + let mut wm = wm_with_monitor(); + wm.close_focus_follows_workspace = true; + let a = wm.alloc_window_id(); + wm.add_window(Window::new(a, "a")); // workspace 1 + + let ws2 = wm.add_workspace("2", "dynamic"); + wm.switch_workspace(ws2); + let c = wm.alloc_window_id(); + wm.add_window(Window::new(c, "c")); // workspace 2 + + wm.switch_workspace(1); + wm.focus_window(a); + wm.remove_window(a); + // Opted in: the old always-follow-the-global-fallback behaviour. + assert_eq!(wm.focused_id(), Some(c)); + } + #[test] fn new_window_on_dynamic_workspace_uses_smart_placement() { let mut wm = wm_with_monitor(); -- cgit v1.2.3