feat: select overlay windows with the mouse cursor
Moving the cursor over an overlay row now previews that window, the same as dragging a finger across it; sliding off every row keeps the last preview. Clicks while the overlay is up do nothing and never reach clients, and releasing Alt commits the preview.
This commit is contained in:
parent
d91cc8ba8d
commit
a4e5633a27
2 changed files with 142 additions and 16 deletions
73
kick.c
73
kick.c
|
|
@ -1053,6 +1053,10 @@ static struct kick_window *tab_current_window(struct kick_tab *tab);
|
|||
static bool overlay_hit_test(double ox, double oy,
|
||||
struct kick_tab **tab_out,
|
||||
struct kick_window **win_out);
|
||||
/* True while pointer motion/buttons must not reach clients: the Alt overlay
|
||||
is up (hover previews tabs, clicks are swallowed). Defined with the
|
||||
touch-selection state; called from the pointer paths above it. */
|
||||
static bool overlay_eats_pointer(void);
|
||||
/* Toplevel-icon body lives with the overlay code; called from teardown. */
|
||||
static void icon_proto_release(struct kick_window *w);
|
||||
/* Toplevel-tag bodies live with the foreign-handle section; called from
|
||||
|
|
@ -10665,6 +10669,12 @@ static void standalone_ptr_button_event(struct wl_listener *listener, void *data
|
|||
if (!server.seat) return;
|
||||
if (server.idle_notifier)
|
||||
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
|
||||
/* Clicks while the overlay is up are swallowed: hover already previewed,
|
||||
and the client beneath must not see a press/release pair. */
|
||||
if (overlay_eats_pointer()) {
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
return;
|
||||
}
|
||||
wlr_seat_pointer_notify_button(server.seat, ev->time_msec, ev->button,
|
||||
ev->state);
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
|
|
@ -10898,8 +10908,13 @@ static void ctl_dispatch(const uint8_t *msg, size_t len) {
|
|||
ctl_time += 1;
|
||||
/* Button press/release at the current pointer position, through the
|
||||
same seat entry real devices use. Button codes are evdev-style
|
||||
(BTN_LEFT = 0x110); state 1 = press, 0 = release. */
|
||||
(BTN_LEFT = 0x110); state 1 = press, 0 = release. Swallowed
|
||||
while the overlay is up, like every other button path. */
|
||||
if (!server.seat) return;
|
||||
if (overlay_eats_pointer()) {
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
return;
|
||||
}
|
||||
wlr_seat_pointer_notify_button(server.seat, ctl_time,
|
||||
u32[0], u32[1]);
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
|
|
@ -11124,21 +11139,26 @@ static void overlay_touch_release(int32_t id) {
|
|||
overlay_touch_driver = -1;
|
||||
}
|
||||
|
||||
/* A tap on an overlay chip while the prefix is held: preview that window
|
||||
(the release commits it to the MRU front via the usual overlay path).
|
||||
Mirrors mru_step's set_active_tab + window_enter pinning so a stacked
|
||||
member is selected exactly, not just its tab's current window. Re-taps
|
||||
(or drag motion) landing on the already-shown window are a no-op, so
|
||||
motion-rate events never spam the log. */
|
||||
static void overlay_touch_select(struct kick_window *w) {
|
||||
if (!w || server.session_locked) return;
|
||||
if (w == mru_current()) return;
|
||||
/* Preview a window while the overlay is up (the Alt release commits it to
|
||||
the MRU front via the usual overlay path). Shared by touch taps/drags
|
||||
and pointer hover. Mirrors mru_step's set_active_tab + window_enter
|
||||
pinning so a stacked member is selected exactly, not just its tab's
|
||||
current window. Landing on the already-shown window is a no-op (false),
|
||||
so motion-rate events never spam the log; callers log on true. */
|
||||
static bool overlay_preview_select(struct kick_window *w) {
|
||||
if (!w || server.session_locked) return false;
|
||||
if (w == mru_current()) return false;
|
||||
struct kick_tab *tab = w->tab;
|
||||
if (!tab || !tab_overlay_row(tab)) return;
|
||||
if (!tab || !tab_overlay_row(tab)) return false;
|
||||
if (tab != server.active_tab)
|
||||
set_active_tab(tab);
|
||||
window_enter(w);
|
||||
kick_log(KICK_LOG_INFO, "[touch] select app=%d\n", tab_index(tab));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool overlay_eats_pointer(void) {
|
||||
return host.overlay_visible && !confirm_quit_pending &&
|
||||
!server.session_locked;
|
||||
}
|
||||
|
||||
static void seat_touch_deliver(uint32_t time_msec, int32_t id, int action,
|
||||
|
|
@ -11159,8 +11179,10 @@ static void seat_touch_deliver(uint32_t time_msec, int32_t id, int action,
|
|||
if (overlay_touch_driver == -1) {
|
||||
struct kick_tab *ht = NULL;
|
||||
struct kick_window *hw = NULL;
|
||||
if (overlay_hit_test(ox, oy, &ht, &hw) && hw)
|
||||
overlay_touch_select(hw);
|
||||
if (overlay_hit_test(ox, oy, &ht, &hw) && hw &&
|
||||
overlay_preview_select(hw))
|
||||
kick_log(KICK_LOG_INFO, "[touch] select app=%d\n",
|
||||
tab_index(ht));
|
||||
overlay_touch_consume(id);
|
||||
overlay_touch_driver = id;
|
||||
} else {
|
||||
|
|
@ -11176,8 +11198,10 @@ static void seat_touch_deliver(uint32_t time_msec, int32_t id, int action,
|
|||
if (id == overlay_touch_driver) {
|
||||
struct kick_tab *ht = NULL;
|
||||
struct kick_window *hw = NULL;
|
||||
if (overlay_hit_test(ox, oy, &ht, &hw) && hw)
|
||||
overlay_touch_select(hw);
|
||||
if (overlay_hit_test(ox, oy, &ht, &hw) && hw &&
|
||||
overlay_preview_select(hw))
|
||||
kick_log(KICK_LOG_INFO, "[touch] select app=%d\n",
|
||||
tab_index(ht));
|
||||
}
|
||||
} else {
|
||||
overlay_touch_release(id);
|
||||
|
|
@ -11243,6 +11267,19 @@ static void host_ptr_motion(uint32_t time, double sx, double sy) {
|
|||
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
|
||||
server.last_ptr_x = sx;
|
||||
server.last_ptr_y = sy;
|
||||
/* Hovering the overlay previews rows like a touch drag (no click
|
||||
needed); motion never reaches clients while it is up. Raw coords:
|
||||
the overlay sits outside any constrained surface. */
|
||||
if (overlay_eats_pointer()) {
|
||||
struct kick_tab *ht = NULL;
|
||||
struct kick_window *hw = NULL;
|
||||
if (overlay_hit_test(sx, sy, &ht, &hw) && hw &&
|
||||
overlay_preview_select(hw))
|
||||
kick_log(KICK_LOG_INFO, "[pointer] select app=%d\n",
|
||||
tab_index(ht));
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
return;
|
||||
}
|
||||
constraint_filter_abs(&sx, &sy);
|
||||
double nx = 0.0, ny = 0.0;
|
||||
struct wlr_surface *surface = surface_at(sx, sy, &nx, &ny);
|
||||
|
|
@ -11294,6 +11331,10 @@ static void host_ptr_button(void *data, struct wl_pointer *p, uint32_t serial,
|
|||
if (!server.seat) return;
|
||||
if (server.idle_notifier)
|
||||
wlr_idle_notifier_v1_notify_activity(server.idle_notifier, server.seat);
|
||||
if (overlay_eats_pointer()) {
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
return;
|
||||
}
|
||||
wlr_seat_pointer_notify_button(server.seat, time, button, state);
|
||||
wlr_seat_pointer_notify_frame(server.seat);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4962,6 +4962,91 @@ def test_overlay_touch_drag(session):
|
|||
assert session.active() == drag_target
|
||||
|
||||
|
||||
def test_overlay_pointer_hover(session):
|
||||
"""Moving the cursor over the overlay while Alt is held previews rows
|
||||
like a touch drag (no click needed); motion off every row sticks to
|
||||
the last preview; clicks are swallowed (no new selection, no active
|
||||
change, nothing reaches the client); Alt release commits."""
|
||||
session.wait_any_active()
|
||||
session.wait_surface_mapped(0)
|
||||
session.wait_surface_mapped(1)
|
||||
session.mods(ALT)
|
||||
try:
|
||||
path = session.dump_host()
|
||||
w, h, _ = session.ppm_rgb(path)
|
||||
cx = w // 2
|
||||
# Locate both rows by warping down the middle.
|
||||
rows = {}
|
||||
deadline = time.monotonic() + 25.0
|
||||
y = 0
|
||||
while time.monotonic() < deadline and len(rows) < 2 and y < h:
|
||||
before = len(re.findall(r"\[pointer\] select app=", session.read_log()))
|
||||
session.warp(cx, y)
|
||||
time.sleep(0.18)
|
||||
m = re.findall(r"\[pointer\] select app=(\d+)", session.read_log())
|
||||
if len(m) > before and m[-1] not in rows:
|
||||
rows[m[-1]] = y
|
||||
y += 20
|
||||
assert len(rows) == 2, f"hover sweep found rows {rows}"
|
||||
yA, yB = sorted(rows.values())
|
||||
# Hover from the upper row into the lower one.
|
||||
session.warp(cx, yA)
|
||||
time.sleep(0.3)
|
||||
session.warp(cx, yB)
|
||||
time.sleep(0.3)
|
||||
sels = re.findall(r"\[pointer\] select app=(\d+)", session.read_log())
|
||||
assert len(sels) >= 2 and sels[-2] != sels[-1], \
|
||||
f"hover never re-targeted: {sels[-2:]}"
|
||||
hover_target = int(sels[-1])
|
||||
# Motion off every row keeps the last preview.
|
||||
n0 = len(re.findall(r"\[pointer\] select app=", session.read_log()))
|
||||
session.warp(5, 5)
|
||||
time.sleep(0.3)
|
||||
n1 = len(re.findall(r"\[pointer\] select app=", session.read_log()))
|
||||
assert n1 == n0, "off-row hover changed selection"
|
||||
# Clicks are swallowed: no selection, no active change.
|
||||
a0 = session.active_count()
|
||||
session.button(BTN_LEFT, 1)
|
||||
time.sleep(0.15)
|
||||
session.button(BTN_LEFT, 0)
|
||||
time.sleep(0.3)
|
||||
n2 = len(re.findall(r"\[pointer\] select app=", session.read_log()))
|
||||
assert n2 == n1, "click while overlay up selected a window"
|
||||
assert session.active_count() == a0, "click while overlay up moved focus"
|
||||
assert session.active() == hover_target
|
||||
finally:
|
||||
session.mods(0)
|
||||
session.wait_for(r"\[mru\] commit app=%d" % hover_target, timeout=10.0)
|
||||
assert session.active() == hover_target
|
||||
|
||||
|
||||
def test_overlay_pointer_click_swallowed(tmp_path):
|
||||
"""Clicks while the overlay is up never reach clients: a wltest client
|
||||
with --pointer-out sees buttons with the overlay down and nothing
|
||||
new while Alt is held."""
|
||||
out = tmp_path / "pointer.out"
|
||||
with _run_kick(
|
||||
["%s --pointer-out '%s' '#ff0000'" % (WL, out)]) as s:
|
||||
s.wait_any_active()
|
||||
s.wait_surface_mapped(0)
|
||||
path = s.dump_host()
|
||||
w, h, _ = s.ppm_rgb(path)
|
||||
s.warp(w // 2, h // 2)
|
||||
s.click()
|
||||
assert _wait_out(out, "button 272 1", timeout=10.0), \
|
||||
f"client saw no click with overlay down: {out.read_text()!r}"
|
||||
n0 = out.read_text().count("button")
|
||||
s.mods(ALT)
|
||||
try:
|
||||
time.sleep(0.7)
|
||||
s.click()
|
||||
time.sleep(0.8)
|
||||
finally:
|
||||
s.mods(0)
|
||||
n1 = out.read_text().count("button")
|
||||
assert n1 == n0, f"click leaked to client while overlay up: {out.read_text()!r}"
|
||||
|
||||
|
||||
def test_synth_feed_drives_host_listeners(tmp_path):
|
||||
"""Synthesized host pointer/touch events go through the real host
|
||||
wl_pointer/wl_touch listeners instead of bypassing them for the shared
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue