Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(borders): in overview the ring is on the hovered window only
`Window::is_adjust_target` keys on the pointer's hover target in
overview too, not on focus: a pointer on the background shows no ring,
and the ring follows the pointer from window to window exactly as it
does with Super held. Overview's hover-to-focus is unchanged — focus
still follows the pointer there — it just no longer decides where the
handles are drawn.
Verified in a shadow with two windows and a desktop image: background →
no ring; over a window → its ring only; back to the background → none.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
CLAUDE.md | 15 ++++++++-------
src/server/cursor.rs | 39 ++++++++++++++++++---------------------
src/server/window.rs | 30 ++++++++++++++----------------
3 files changed, 40 insertions(+), 44 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 9253a9b..adf15b9 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -417,10 +417,10 @@ sat outside the edges and the ring that followed hugged them.
body-drag as overview, gated by one predicate,
`WindowManager::window_adjust_active()` (overview OR `adjust_held`).
But NOT hover-to-focus: the ring lands on the window **under the
- pointer** (`Cursor::adjust_hover`, set by `passthrough`), focused or not,
- and focus stays put — so pressing Super arms whatever the pointer is
- already on, and a focus chord pressed next acts on the window the user
- had. **A drag never focuses the window it moves or resizes** (the grab
+ pointer** (`Cursor::adjust_hover`, set by `passthrough` — the same
+ target overview uses), focused or not, and focus stays put — so pressing
+ Super arms whatever the pointer is already on, and a focus chord pressed
+ next acts on the window the user had. **A drag never focuses the window it moves or resizes** (the grab
paths in `handle_button` call no `seat.focus`; `op_start_pointer` raises
a Floating one instead); a tap on the band or body — press+release
without motion — is a click and focuses in `op_end`.
@@ -432,10 +432,11 @@ sat outside the edges and the ring that followed hugged them.
A background press with Super held is an ordinary desktop press — only
overview exits on it.
- Handles are shown on the **adjust target only** — `Window::is_adjust_target`:
- the focused window in overview, the hovered one with Super held — for as
+ the window under the pointer (`Cursor::adjust_hover`), in overview and
+ with Super held alike; a pointer on the background shows none — for as
long as the mode is on (`step_border_fade`'s `all_on` branch, `draw_borders`'
- `handles_live`, and `get_border_zone` all ask it). **Focus follows the
- pointer in overview**: the
+ `handles_live`, and `get_border_zone` all ask it). Separately, **focus
+ follows the pointer in overview** (the ring does not key on it): the
motion path focuses the hovered toplevel — guarded on an actual change,
because `seat.focus` raises a Floating window *before* its same-focus
short-circuit, so an unguarded call would raise and relayout on every
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 492444e..21a5d60 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -123,13 +123,12 @@ pub struct Cursor {
pub hovered_border_window: *mut crate::window::Window,
/// Which of that window's 8 border zones is highlighted.
pub hovered_border_element: Option<crate::window::BorderElement>,
- /// The toplevel under the pointer while Super-held adjust mode is on:
- /// the window the ring lands on and whose handles are live, focused or
- /// not. Set by `passthrough` on every hover evaluation and cleared when
- /// the pointer rests on nothing adjustable or the mode is off. Overview
- /// keys the ring on focus instead — see `Window::is_adjust_target`. May
- /// dangle after a close: compared by address only, and nulled in
- /// `Window::destroy`.
+ /// The toplevel under the pointer while adjust mode (overview, or Super
+ /// held) is on: the window the ring lands on and whose handles are live,
+ /// focused or not. Set by `passthrough` on every hover evaluation and
+ /// cleared when the pointer rests on nothing adjustable or the mode is
+ /// off — see `Window::is_adjust_target`. May dangle after a close:
+ /// compared by address only, and nulled in `Window::destroy`.
pub adjust_hover: *mut crate::window::Window,
pub right_click_on_bg: bool,
pub right_click_on_border: bool,
@@ -865,11 +864,10 @@ impl Cursor {
is_window = true;
hovered_toplevel = window;
}
- // Super held at zoom 1: the ring lands on the window
- // under the pointer, focused or not. Set BEFORE the zone
- // test below, so the band is live on the first hover.
- // (Null for the status bar, wallpaper and grid; overview
- // keys the ring on focus and ignores this.)
+ // Adjust mode: the ring lands on the window under the
+ // pointer, focused or not. Set BEFORE the zone test
+ // below, so the band is live on the first hover. (Null
+ // for the status bar, wallpaper and grid.)
self.set_adjust_hover(if (*server).wm.window_adjust_active() {
hovered_toplevel
} else {
@@ -928,9 +926,9 @@ impl Cursor {
&& !hovered_chrome
&& (*server).wm.window_adjust_active()
{
- // Focus follows the pointer in overview: there the ring is
- // drawn on the focused window only, so hovering is how it
- // moves between windows without a click. (Super-held adjust mode takes this
+ // Focus follows the pointer in overview, so a click-less
+ // hover chooses the window a focus chord or the exit lands
+ // on. (The ring itself keys on `adjust_hover`, not focus.) (Super-held adjust mode takes this
// branch too, for the pointer-focus clear below, but not the
// refocus.) Guarded on an actual change —
// seat.focus raises a Floating window BEFORE its same-focus
@@ -4010,12 +4008,11 @@ pub unsafe fn get_border_zone(window: *mut crate::window::Window, lx: f64, ly: f
if !crate::window::window_takes_handles(window) {
return BorderZone::None;
}
- // The adjust target only, matching what draw_borders draws: the focused
- // window in overview, the hovered one with Super held. A window showing
- // no ring has no band, and a grab that is not drawn is the failure mode
- // this file keeps warning about. Either way the pointer reaches a
- // window's edge through its body — hover-to-focus in overview, the hover
- // target at zoom 1 — so the band is live by the time it arrives.
+ // The adjust target only — the window under the pointer — matching what
+ // draw_borders draws. A window showing no ring has no band, and a grab
+ // that is not drawn is the failure mode this file keeps warning about.
+ // The pointer reaches a window's edge through its body, so the band is
+ // live by the time it arrives.
if !(*window).is_adjust_target() {
return BorderZone::None;
}
diff --git a/src/server/window.rs b/src/server/window.rs
index 0e6a8a6..325bce9 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -3570,19 +3570,18 @@ impl Window {
false
}
- /// Whether this is the window the adjust-mode ring and handles belong
- /// to. In overview that is the focused window (hover-to-focus moves it
- /// from window to window); with Super held at zoom 1 it is the toplevel
- /// under some seat's pointer (`Cursor::adjust_hover`), focused or not —
- /// pressing Super arms the window the pointer is already on, and a drag
- /// on it never changes focus. The reveal (`step_border_fade`), the drawn
- /// handles and catchers (`draw_borders`) and the hit test
+ /// Whether this is the window the adjust-mode handles belong to: the
+ /// toplevel under some seat's pointer (`Cursor::adjust_hover`), focused
+ /// or not, in overview and with Super held alike — the handles are
+ /// shown on what the pointer is over and on nothing else, so a pointer
+ /// on the background shows none. (Overview's hover-to-focus still moves
+ /// focus with the pointer, but focus is not what the ring keys on: a
+ /// pointer resting on the background would otherwise keep the last
+ /// window's ring up.) The reveal (`step_border_fade`), the drawn handles
+ /// and catchers (`draw_borders`) and the hit test
/// (`cursor::get_border_zone`) all ask this one predicate, so the ring
/// cannot be drawn on one window and grabbed on another.
pub unsafe fn is_adjust_target(&self) -> bool {
- if (*self.server).wm.mode == crate::window_manager::WindowManagerMode::Overview {
- return self.is_seat_focused();
- }
let me = self as *const Window as *mut Window;
let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
let mut curr = (*seats).next;
@@ -3808,11 +3807,10 @@ impl Window {
pub unsafe fn step_border_fade(&mut self) -> bool {
let mut moving = false;
let mut changed = false;
- // The adjust TARGET shows its whole ring for as long as the mode is
- // on — the focused window in overview, the hovered one with Super
- // held; other windows show nothing. Either way the ring follows the
- // pointer from window to window, each swap easing through this same
- // fade. Hover still reads through on the revealed ring, as
+ // The adjust TARGET — the window under the pointer — shows its whole
+ // ring for as long as the mode is on; other windows show nothing.
+ // The ring follows the pointer from window to window, each swap
+ // easing through this same fade. Hover still reads through on the revealed ring, as
// `color_for` paints the hovered zone in hover_color over the full
// reveal.
let all_on = (*self.server).wm.window_adjust_active()
@@ -4174,7 +4172,7 @@ impl Window {
// A window thinner than two bands has no interior left for a
// ring; drawing one would be a solid block over the whole window.
// Live handles: the mode is on and this is the adjust target
- // (focused in overview, hovered with Super held). Target-only,
+ // (the window under the pointer). Target-only,
// like the reveal in step_border_fade: without this the
// invisible catcher rects would keep intercepting scene hits on
// windows whose ring is not even drawn.