Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: desktop images are draggable in overview mode
A press on the grid client used to count as background in overview and
exit it — but hitting the grid at all means the press landed on a desktop
item, since its input region covers exactly the item rects and nothing
else. Bare canvas misses the grid by the same mechanism, so the
background-press exit is untouched.
Three gates opened, all in cursor.rs:
- The overview click branch treats a grid hit as chrome-like fall-through
(press delivered to the client) instead of an overview exit.
- should_block_button no longer counts the grid as an app surface, so the
press is actually forwarded while overview is up.
- The hover path no longer treats the grid as a toplevel: the overview
branch was clearing pointer focus over items (and focus-following onto
a surface seat.focus refuses), so the grid never received the enter
that button delivery depends on.
And one correctness fix the zoomed-out case forced: an implicit grab held
on the grid maps motion through the surface node (grid_surface_at, the
drop path's mapping) rather than the fixed-origin offset, which assumes
an unscaled surface. The grid displays at the patch's resolution ratio —
off 1 whenever the zoom sits between pow2 quantization steps — so offset
deltas would drag the item faster or slower than the pointer by exactly
that ratio. Verified in a shadow session at zoom 0.25: a 200x150 px
pointer drag moved the item 200x150 px on screen and 800x600 virtual.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/server/cursor.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 50 insertions(+), 7 deletions(-)
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 3a9faa9..8b253b5 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -698,6 +698,26 @@ impl Cursor {
let focused =
ffi::river_wlr_seat_get_pointer_focused_surface((*self.seat).wlr_seat);
if !focused.is_null() {
+ // A grab held on the GRID maps through the surface node like
+ // the drop path below, not through the fixed origin: the
+ // offset formula assumes an unscaled surface, and the grid
+ // displays at the patch's resolution ratio — near 1, but off
+ // it whenever the zoom sits between pow2 quantization steps
+ // (most of overview) — so offset deltas would drag the item
+ // faster or slower than the pointer by exactly that ratio.
+ // A patch re-latch mid-drag moves the node under the pointer;
+ // the client's delta re-baselining absorbs that step.
+ if let Some((gsurf, gsx, gsy)) = grid_surface_at(server, lx, ly) {
+ if gsurf == focused {
+ ffi::wlr_seat_pointer_notify_motion(
+ (*self.seat).wlr_seat,
+ time_msec,
+ gsx,
+ gsy,
+ );
+ return;
+ }
+ }
ffi::wlr_seat_pointer_notify_motion(
(*self.seat).wlr_seat,
time_msec,
@@ -728,7 +748,16 @@ impl Cursor {
let mut hovered_toplevel: *mut crate::window::Window = std::ptr::null_mut();
match result.data {
SceneNodeDataVal::Window(window) => {
- if !(*window).is_status_bar() && !(*window).is_wallpaper() {
+ // The grid is not a toplevel for hover purposes: hitting it
+ // means the pointer is on a desktop item (its input region
+ // covers nothing else), and the item drag needs the
+ // enter/motion delivery below — in overview too, where the
+ // is_window branch would clear pointer focus and try to
+ // focus-follow onto a surface seat.focus refuses.
+ if !(*window).is_status_bar()
+ && !(*window).is_wallpaper()
+ && !(*window).is_grid()
+ {
is_window = true;
hovered_toplevel = window;
}
@@ -1033,7 +1062,14 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
if let Some(result) = (*server).scene.at(lx, ly) {
match result.data {
SceneNodeDataVal::Window(window) => {
- if !(*window).is_status_bar() && !(*window).is_wallpaper() {
+ // The grid does not block: hitting it means the press is on a
+ // desktop item (its input region covers nothing else), and
+ // the item drag needs the press delivered in overview like
+ // any chrome click.
+ if !(*window).is_status_bar()
+ && !(*window).is_wallpaper()
+ && !(*window).is_grid()
+ {
is_app_surface = true;
// Popup counts as chrome like Overlay: the cce-cloud
// launcher must keep receiving clicks in overview.
@@ -1173,19 +1209,26 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
(*clicked_win).tiling_mode,
crate::tiling::TilingMode::Overlay | crate::tiling::TilingMode::Popup
));
- // The grid counts as background in overview: a press on it must
- // exit overview like any desktop press, never grab the canvas
- // itself as if it were a window.
+ // Hitting the grid means the press landed on a DESKTOP ITEM —
+ // its input region covers the item rects and nothing else — so
+ // it is chrome-like: fall through to normal delivery and the
+ // grid client starts its item drag, in overview exactly as in
+ // normal mode. Bare canvas misses the grid entirely (that is
+ // the input region again) and still exits overview below.
+ let clicked_grid = !clicked_win.is_null() && (*clicked_win).is_grid();
+ // The bare canvas counts as background in overview: a press on
+ // it must exit overview like any desktop press, never grab the
+ // canvas itself as if it were a window.
let overview_win_valid = !clicked_win.is_null()
&& !(*clicked_win).is_status_bar()
&& !(*clicked_win).is_wallpaper()
- && !(*clicked_win).is_grid();
+ && !clicked_grid;
let overview_border_zone = if overview_win_valid {
get_border_zone(clicked_win, lx, ly)
} else {
BorderZone::None
};
- if overview_chrome {
+ if overview_chrome || clicked_grid {
// fall through
} else if overview_win_valid && matches!(overview_border_zone, BorderZone::None) {
(*server).wm.stop_panning_animation();