diff options
| author | srdusr <[email protected]> | 2024-08-08 23:56:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-08-08 23:56:00 +0200 |
| commit | 52b0b8927fa56f42ad6b39fb45bbed5a2d392dce (patch) | |
| tree | d72e8a090c28e828c7e3980a9b0e69b31ebfc262 /crates/core/src | |
| parent | b0a6b67cbfb080ee44b6a6fa005dd63ab0f93cd8 (diff) | |
| download | srdwm-52b0b8927fa56f42ad6b39fb45bbed5a2d392dce.tar.gz srdwm-52b0b8927fa56f42ad6b39fb45bbed5a2d392dce.zip | |
Fix clicks landing on a window hidden on another workspace
WindowManager::hit_test/window_at filtered only by `!w.minimized`,
never by workspace - but a window on a workspace that isn't current
is not minimized, it's just not shown. Rendering (visible_windows/
visible_windows_front_to_back) already restricted to the current
workspace; hit-testing didn't, so a click landing on where an
invisible window's stale on-screen geometry happened to sit routed to
that window instead of whatever was actually visible underneath.
Reported live. Fixed by adding the same workspace check rendering
already uses, plus a regression test with two identically-positioned
windows on different workspaces.
Diffstat (limited to 'crates/core/src')
| -rw-r--r-- | crates/core/src/manager/hittest.rs | 21 | ||||
| -rw-r--r-- | crates/core/src/manager/tests.rs | 23 |
2 files changed, 39 insertions, 5 deletions
diff --git a/crates/core/src/manager/hittest.rs b/crates/core/src/manager/hittest.rs index 918acb5..2d52b57 100644 --- a/crates/core/src/manager/hittest.rs +++ b/crates/core/src/manager/hittest.rs @@ -10,9 +10,18 @@ impl WindowManager { /// Topmost window whose frame contains `(x, y)`, along with what part of /// its titlebar/border was hit (button, drag area, resize edge). + /// + /// Restricted to the current workspace, same as `visible_windows`/ + /// `visible_windows_front_to_back` - a window on another workspace is + /// never minimized (that's a separate flag from "not currently shown"), + /// so without this a click landing on its old on-screen geometry hit + /// *that* window instead of whatever the user could actually see. + /// Reported live: clicking a window while a differently-workspaced one + /// happened to occupy the same screen coordinates sent the click to the + /// invisible one. pub fn hit_test(&self, x: i32, y: i32) -> Option<(WindowId, TitlebarHit)> { for w in self.order.iter().rev().filter_map(|id| self.windows.get(id)) { - if w.minimized { + if w.minimized || w.workspace != self.current_workspace { continue; } if let Some(hit) = ResizeEdge::hit_test(w.geometry, x, y, w.decorated, w.border_width, self.resize_margin) { @@ -22,15 +31,17 @@ impl WindowManager { None } - /// Topmost non-minimised window containing a point, ignoring - /// decorations. Used for modifier+drag, where the grab applies anywhere - /// in the window rather than only on the titlebar (`hit_test`). + /// Topmost non-minimised window on the current workspace containing a + /// point, ignoring decorations. Used for modifier+drag, where the grab + /// applies anywhere in the window rather than only on the titlebar + /// (`hit_test`). See `hit_test`'s doc comment for why the workspace + /// check is load-bearing, not redundant with `minimized`. pub fn window_at(&self, x: i32, y: i32) -> Option<WindowId> { self.order .iter() .rev() .filter_map(|id| self.windows.get(id)) - .find(|w| !w.minimized && w.geometry.contains_point(x, y)) + .find(|w| !w.minimized && w.workspace == self.current_workspace && w.geometry.contains_point(x, y)) .map(|w| w.id) } diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs index a098245..24e8932 100644 --- a/crates/core/src/manager/tests.rs +++ b/crates/core/src/manager/tests.rs @@ -223,6 +223,29 @@ } #[test] + fn hit_test_ignores_a_window_on_another_workspace_even_if_its_geometry_overlaps() { + // Reported live: clicking a window sent the click to a different, + // invisible window that merely happened to sit at the same screen + // coordinates on a workspace that wasn't current. Rendering already + // filtered by workspace (`visible_windows`); hit-testing didn't. + let mut wm = wm_with_monitor(); + let a = wm.alloc_window_id(); + let mut wa = Window::new(a, "a"); + wa.geometry = Rect::new(0, 0, 400, 300); + wm.add_window(wa); + let b = wm.alloc_window_id(); + let mut wb = Window::new(b, "b"); + wb.geometry = Rect::new(0, 0, 400, 300); // identical geometry to `a` + wm.add_window(wb); + let other_workspace = wm.add_workspace("2", "dynamic"); + wm.move_window_to_workspace(b, other_workspace); // b is now off-screen, not minimized + + let (hit_id, _) = wm.hit_test(200, 10).unwrap(); + assert_eq!(hit_id, a, "a click must land on the visible window, not one hidden on another workspace"); + assert_eq!(wm.window_at(200, 10), Some(a)); + } + + #[test] fn moving_window_to_another_workspace_removes_it_from_current() { let mut wm = wm_with_monitor(); let a = wm.alloc_window_id(); |