test: locked abs-pointer pin, rogue second lock surface; fix wltest abs gating
This commit is contained in:
parent
21d686df5d
commit
16c5ed58e5
3 changed files with 114 additions and 4 deletions
|
|
@ -1107,6 +1107,56 @@ def test_standalone_headless_confined_pointer_clamps():
|
|||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_locked_pointer_abs_holds():
|
||||
"""A LOCKED pointer constraint also pins absolute motion: a virtual
|
||||
pointer warp must not carry the cursor away from the lock position.
|
||||
Regression: native_ptr_abs skipped the constraint clamp that the
|
||||
relative path applies, so tablets and virtual-pointer warps escaped
|
||||
the lock."""
|
||||
with _run_standalone({}, "%s --constrain locked '#ff0000'" % WL, wait="app0") as s:
|
||||
_assert_alive(s)
|
||||
|
||||
def patch_at(path, cx, cy, r=22):
|
||||
w, h, px = s.ppm_rgb(path)
|
||||
py = h - 1 - cy
|
||||
hits = []
|
||||
for y in range(max(0, py - r), min(h, py + r + 1)):
|
||||
row = y * w * 3
|
||||
for x in range(max(0, cx - r), min(w, cx + r + 1)):
|
||||
i = row + x * 3
|
||||
hits.append((px[i], px[i + 1], px[i + 2]))
|
||||
return hits
|
||||
|
||||
def distinct(a, b):
|
||||
return any(ca != cb for ca, cb in zip(a, b))
|
||||
|
||||
lock = (300, 300)
|
||||
# Far corner of the output, well away from the lock position.
|
||||
target = (720, 540)
|
||||
|
||||
pure_lock = patch_at(s.dump_host(), *lock)
|
||||
pure_target = patch_at(s.dump_host(), *target)
|
||||
assert len(set(pure_lock)) == 1, "lock probe should be pure client colour"
|
||||
assert len(set(pure_target)) == 1, "target probe should be pure client colour"
|
||||
|
||||
pos = len(s.read_log())
|
||||
s.warp(*lock)
|
||||
s.wait_for_new_after(pos, r"\[constraint\] locked on surface")
|
||||
s.wait_for_new_after(pos, r"\[cursor\] pos=%d,%d" % lock)
|
||||
assert distinct(patch_at(s.dump_host(), *lock), pure_lock), \
|
||||
"cursor never appeared at the lock position"
|
||||
|
||||
# An absolute warp must leave the pinned cursor where it is.
|
||||
pos = len(s.read_log())
|
||||
_oneshot_virtual_abs(s.socket, 0.9, 0.9)
|
||||
s.wait_for_new_after(pos, r"\[cursor\] pos=%d,%d" % lock)
|
||||
path = s.dump_host()
|
||||
assert distinct(patch_at(path, *lock), pure_lock) and \
|
||||
not distinct(patch_at(path, *target), pure_target), \
|
||||
"locked pointer escaped via absolute motion"
|
||||
_assert_alive(s)
|
||||
|
||||
|
||||
def test_standalone_headless_screen_mode_cursor_stays_aligned():
|
||||
"""--screen-mode + pointer motion must keep the visible cursor exactly on
|
||||
the warp target plus the relative delta, on a non-default output size. The
|
||||
|
|
@ -4817,6 +4867,27 @@ def test_standalone_headless_session_lock_unlock(tmp_path):
|
|||
_assert_alive(s, "compositor died under takeover lock")
|
||||
|
||||
|
||||
def test_standalone_headless_session_lock_second_surface(tmp_path):
|
||||
"""A rogue second lock surface on one lock is survived: the first
|
||||
surface's protocol object is dropped while the second stays live, then
|
||||
unlock still tears everything down and the session survives. Covers
|
||||
the singleton lock-surface bookkeeping against a misbehaving client
|
||||
(re-adding the listeners must first detach them)."""
|
||||
out = tmp_path / "lock.out"
|
||||
with _run_standalone(
|
||||
{},
|
||||
"%s --app-id testwin '#ff0000' 320x240" % WL,
|
||||
"%s --lock --lock-unlock --lock-second-surface --lock-out '%s'"
|
||||
% (WL, out), wait="any") as s:
|
||||
# The creation milestone is on stderr (session log): the out file
|
||||
# only keeps the last note, so unlocked-exit would hide it.
|
||||
s.wait_for("second lock surface created", timeout=10.0)
|
||||
assert _wait_out(out, "unlocked-exit", timeout=10.0), \
|
||||
f"lock client never finished its unlock: " \
|
||||
f"{(out.read_text().strip() if out.exists() else '(missing)')!r}"
|
||||
_assert_alive(s, "compositor died on double lock surface")
|
||||
|
||||
|
||||
def test_standalone_headless_session_lock_locker_dies(tmp_path):
|
||||
"""A lock client that dies without unlocking must not take the session
|
||||
down: the compositor survives the destroy path and a fresh lock client
|
||||
|
|
|
|||
|
|
@ -1735,10 +1735,16 @@ static void idle_run(void) {
|
|||
a solid buffer on it (proving the compositor blanked the session and
|
||||
accepted the surface). Writes "locked" then "surface-config" to --lock-out
|
||||
as each stage completes; the strongest observable is the locked event the
|
||||
compositor only sends once it has set up the blanked session. */
|
||||
compositor only sends once it has set up the blanked session.
|
||||
--lock-second-surface additionally opens a rogue second lock surface on
|
||||
the same lock (noting "second-surface"), proving the compositor survives
|
||||
the extra surface and its teardown. */
|
||||
static bool lock_mode;
|
||||
static const char *lock_out;
|
||||
static bool lock_unlock_after;
|
||||
static bool lock_unlocked;
|
||||
static bool lock_second_surface;
|
||||
static bool lock_second_created;
|
||||
static struct ext_session_lock_v1 *lock;
|
||||
static struct ext_session_lock_surface_v1 *lock_surface;
|
||||
static struct wl_surface *lock_wl_surface;
|
||||
|
|
@ -1817,7 +1823,33 @@ static void lock_surface_configure(void *data,
|
|||
ext_session_lock_surface_v1_ack_configure(l, serial);
|
||||
lock_fill_buffer();
|
||||
lock_note("surface-config");
|
||||
if (lock_unlock_after) {
|
||||
/* Rogue-client probe: a second lock surface on the same lock. Created
|
||||
once, before any unlock, so the compositor must survive the extra
|
||||
new_surface plus the teardown of both. The first surface's protocol
|
||||
object is then dropped while the second stays live, so its destroy
|
||||
runs against a live lock. */
|
||||
if (lock_second_surface && !lock_second_created) {
|
||||
lock_second_created = true;
|
||||
struct wl_surface *s2 = wl_compositor_create_surface(compositor);
|
||||
if (s2 && wl_output) {
|
||||
struct ext_session_lock_surface_v1 *ls2 =
|
||||
ext_session_lock_v1_get_lock_surface(lock, s2, wl_output);
|
||||
if (ls2) {
|
||||
ext_session_lock_surface_v1_add_listener(ls2,
|
||||
&lock_surface_listener, NULL);
|
||||
lock_note("second-surface");
|
||||
/* Also on stderr (the session log at KICK_LOG=2): the
|
||||
out file only keeps the last note, so a later
|
||||
unlocked-exit would hide this milestone from polls. */
|
||||
fprintf(stderr, "wltest: second lock surface created\n");
|
||||
ext_session_lock_surface_v1_destroy(lock_surface);
|
||||
lock_surface = ls2;
|
||||
lock_note("first-destroyed");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lock_unlock_after && !lock_unlocked) {
|
||||
lock_unlocked = true;
|
||||
ext_session_lock_v1_unlock_and_destroy(lock);
|
||||
/* The unlock reply is the unblanked screen itself
|
||||
(unlock_and_destroy leaves no proxy for a finished event):
|
||||
|
|
@ -3942,6 +3974,9 @@ int main(int argc, char **argv) {
|
|||
} else if (strcmp(argv[i], "--lock-unlock") == 0) {
|
||||
lock_unlock_after = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--lock-second-surface") == 0) {
|
||||
lock_second_surface = true;
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--screencopy") == 0) {
|
||||
screencopy_mode = true;
|
||||
i += 1;
|
||||
|
|
@ -4166,8 +4201,10 @@ int main(int argc, char **argv) {
|
|||
return 0;
|
||||
}
|
||||
/* Virtual pointer: motion (+optional click) is requested via
|
||||
--virtual-motion; --virtual-click alone is meaningless. */
|
||||
if (virtual_dx != 0 || virtual_dy != 0 || virtual_click) {
|
||||
--virtual-motion, absolute warp via --virtual-abs (either alone
|
||||
suffices); --virtual-click alone is meaningless. */
|
||||
if (virtual_dx != 0 || virtual_dy != 0 || virtual_click ||
|
||||
(virtual_abs_x >= 0.0 && virtual_abs_y >= 0.0)) {
|
||||
virtual_motion_run();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue