This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch wl/browser-all
in repository enlightenment.
View the commit online.
commit a4bebd68316d17c2c52e59dfa9ef7367db323a37
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 8 23:02:20 2026 -0600
tests - isolate pointer delivery, and rule out three input hypotheses
A wlcs run failed 144 "input_inside_region_seen" tests and the shape of the
result was misleading: every *negative* input assertion passed
(input_not_seen_over_empty_region, unmapping_parent_stops_...) and every
*positive* one failed. Tests that expect nothing to happen pass for free
when nothing happens at all, so that pattern is equally consistent with "no
pointer input is delivered at all" as with an input-region bug.
test_pointer_enter.c settles it from the other end, in about two seconds
rather than wlcs's eleven, and the answer is that all three of the obvious
hypotheses are wrong:
- basic delivery works. A plain toplevel gets wl_pointer.enter at the
correct surface-local coordinates.
- input regions work. With a region covering only the right half, a point
inside is seen and a point outside is correctly ignored - both
directions, because only checking the positive case would pass just as
well against a compositor that delivers input everywhere.
- the test-only attach relaxation is not the cause. A surface mapped the
way wlcs maps one - no initial commit, no configure - also receives
input, so the wlcs input results are not an artefact of
E_TEST_ALLOW_UNCONFIGURED_BUFFER.
So whatever those 144 tests are finding, it is none of those, and the next
person does not need to re-eliminate them. The trace of a single failing
case shows E *does* send enter with a plausible surface-local position, yet
wlcs's current_surface() is still NULL - so the remaining lead is what wlcs
does between those two points, not E's pointer path.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_pointer_enter.c | 372 +++++++++++++++++++++++++++++++++
2 files changed, 373 insertions(+)
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index f8bcaf00a..64bc76ae9 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -48,6 +48,7 @@ test('wl-globals',
# with an explanatory message; run-nested.sh supplies the compositor.
wl_protocol_tests = [
['test-module', 'test_wl_test.c'],
+ ['pointer-enter', 'test_pointer_enter.c'],
]
foreach t: wl_protocol_tests
diff --git a/src/tests/wayland/test_pointer_enter.c b/src/tests/wayland/test_pointer_enter.c
new file mode 100644
index 000000000..ca7e44c15
--- /dev/null
+++ b/src/tests/wayland/test_pointer_enter.c
@@ -0,0 +1,372 @@
+/* Does a client get wl_pointer.enter when the pointer is moved onto it?
+ *
+ * The most basic input question there is, and worth its own test because a
+ * wlcs run made it look like a much more specific bug than it is: every
+ * *negative* input assertion passed ("input_not_seen_over_empty_region",
+ * "unmapping_parent_stops_subsurface_getting_input") while every *positive*
+ * one failed ("input_inside_region_seen", 144 tests). Tests that expect
+ * nothing to happen pass for free when nothing happens at all, so a suite can
+ * look like it has found an input-region bug when in fact no pointer input is
+ * being delivered.
+ *
+ * This isolates that: one surface, no regions, no subsurfaces, pointer moved
+ * to the middle of it. If this fails, nothing downstream of it means anything.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <wayland-client.h>
+#include "wl-test-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+#define W 200
+#define H 150
+#define SX 120 /* where we put the surface */
+#define SY 90
+
+#define FAIL(fmt, ...) \
+ do { fprintf(stderr, "test-pointer-enter: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+static struct wl_compositor *compositor;
+static struct wl_shm *shm;
+static struct xdg_wm_base *wm_base;
+static struct wl_seat *seat;
+static struct wl_pointer *pointer;
+static struct wl_test *tester;
+
+static struct wl_surface *surface;
+static struct wl_surface *entered;
+static int enter_count, leave_count, motion_count;
+static wl_fixed_t enter_x, enter_y;
+static int configured, sync_done;
+static int32_t info_x, info_y, info_w, info_h;
+static int info_valid;
+
+static void
+_ptr_enter(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *s,
+ wl_fixed_t x, wl_fixed_t y)
+{
+ (void)d; (void)p; (void)serial;
+ entered = s; enter_x = x; enter_y = y; enter_count++;
+}
+
+static void
+_ptr_leave(void *d, struct wl_pointer *p, uint32_t serial, struct wl_surface *s)
+{
+ (void)d; (void)p; (void)serial; (void)s;
+ leave_count++;
+}
+
+static void
+_ptr_motion(void *d, struct wl_pointer *p, uint32_t t, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)d; (void)p; (void)t; (void)x; (void)y;
+ motion_count++;
+}
+
+static void _ptr_button(void *d, struct wl_pointer *p, uint32_t se, uint32_t t, uint32_t b, uint32_t st)
+{ (void)d; (void)p; (void)se; (void)t; (void)b; (void)st; }
+static void _ptr_axis(void *d, struct wl_pointer *p, uint32_t t, uint32_t a, wl_fixed_t v)
+{ (void)d; (void)p; (void)t; (void)a; (void)v; }
+static void _ptr_frame(void *d, struct wl_pointer *p) { (void)d; (void)p; }
+static void _ptr_axis_source(void *d, struct wl_pointer *p, uint32_t s) { (void)d; (void)p; (void)s; }
+static void _ptr_axis_stop(void *d, struct wl_pointer *p, uint32_t t, uint32_t a) { (void)d; (void)p; (void)t; (void)a; }
+static void _ptr_axis_discrete(void *d, struct wl_pointer *p, uint32_t a, int32_t v) { (void)d; (void)p; (void)a; (void)v; }
+
+static const struct wl_pointer_listener _ptr_listener =
+{
+ _ptr_enter, _ptr_leave, _ptr_motion, _ptr_button, _ptr_axis,
+ _ptr_frame, _ptr_axis_source, _ptr_axis_stop, _ptr_axis_discrete
+};
+
+static void
+_surface_info(void *d, struct wl_test *t, struct wl_surface *s,
+ int32_t x, int32_t y, int32_t w, int32_t h, uint32_t vis, uint32_t foc)
+{
+ (void)d; (void)t; (void)s; (void)vis; (void)foc;
+ info_valid = 1; info_x = x; info_y = y; info_w = w; info_h = h;
+}
+static void _surface_unknown(void *d, struct wl_test *t, struct wl_surface *s)
+{ (void)d; (void)t; (void)s; info_valid = 0; }
+static void _client_serial(void *d, struct wl_test *t, uint32_t s) { (void)d; (void)t; (void)s; }
+static void _sync_done(void *d, struct wl_test *t) { (void)d; (void)t; sync_done = 1; }
+
+static const struct wl_test_listener _tester_listener =
+{ _client_serial, _surface_info, _surface_unknown, _sync_done };
+
+static void _wm_ping(void *d, struct xdg_wm_base *b, uint32_t serial)
+{ (void)d; xdg_wm_base_pong(b, serial); }
+static const struct xdg_wm_base_listener _wm_listener = { _wm_ping };
+
+static void _xdg_conf(void *d, struct xdg_surface *s, uint32_t serial)
+{ (void)d; xdg_surface_ack_configure(s, serial); configured = 1; }
+static const struct xdg_surface_listener _xdg_listener = { _xdg_conf };
+
+static void _top_conf(void *d, struct xdg_toplevel *t, int32_t w, int32_t h, struct wl_array *st)
+{ (void)d; (void)t; (void)w; (void)h; (void)st; }
+static void _top_close(void *d, struct xdg_toplevel *t) { (void)d; (void)t; }
+static const struct xdg_toplevel_listener _top_listener = { _top_conf, _top_close };
+
+static void
+_global(void *data, struct wl_registry *reg, uint32_t id, const char *iface, uint32_t ver)
+{
+ (void)data; (void)ver;
+ if (!strcmp(iface, "wl_compositor"))
+ compositor = wl_registry_bind(reg, id, &wl_compositor_interface, 4);
+ else if (!strcmp(iface, "wl_shm"))
+ shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
+ else if (!strcmp(iface, "xdg_wm_base"))
+ {
+ wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(wm_base, &_wm_listener, NULL);
+ }
+ else if (!strcmp(iface, "wl_seat"))
+ seat = wl_registry_bind(reg, id, &wl_seat_interface, 4);
+ else if (!strcmp(iface, "wl_test"))
+ tester = wl_registry_bind(reg, id, &wl_test_interface, 1);
+}
+
+static void _global_rm(void *d, struct wl_registry *r, uint32_t id) { (void)d; (void)r; (void)id; }
+static const struct wl_registry_listener _reg_listener = { _global, _global_rm };
+
+static struct wl_display *disp;
+
+static int
+tester_sync(void)
+{
+ sync_done = 0;
+ wl_test_sync(tester);
+ while (!sync_done)
+ if (wl_display_dispatch(disp) < 0) return -1;
+ return 0;
+}
+
+static struct wl_buffer *
+make_buffer_named(const char *name)
+{
+ int fd, stride = W * 4, size = stride * H;
+ void *map;
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buf;
+
+ fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd < 0) return NULL;
+ shm_unlink(name);
+ if (ftruncate(fd, size) < 0) { close(fd); return NULL; }
+ map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (map == MAP_FAILED) { close(fd); return NULL; }
+ memset(map, 0xff, size);
+ pool = wl_shm_create_pool(shm, fd, size);
+ buf = wl_shm_pool_create_buffer(pool, 0, W, H, stride, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(pool);
+ close(fd);
+ return buf;
+}
+
+static struct wl_buffer *make_buffer(void)
+{ return make_buffer_named("/e-test-ptr-shm"); }
+
+static struct wl_buffer *make_buffer2(void)
+{ return make_buffer_named("/e-test-ptr-shm2"); }
+
+int
+main(void)
+{
+ struct wl_registry *reg;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *toplevel;
+ struct wl_buffer *buffer;
+ int i;
+
+ disp = wl_display_connect(NULL);
+ if (!disp) FAIL("cannot connect to WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY") ?: "(unset)");
+
+ reg = wl_display_get_registry(disp);
+ wl_registry_add_listener(reg, &_reg_listener, NULL);
+ if (wl_display_roundtrip(disp) < 0) FAIL("registry roundtrip failed");
+
+ if (!compositor || !shm || !wm_base) FAIL("missing core globals");
+ if (!seat) FAIL("no wl_seat");
+ if (!tester) FAIL("no wl_test -- built without -Dtests=true?");
+ wl_test_add_listener(tester, &_tester_listener, NULL);
+
+ pointer = wl_seat_get_pointer(seat);
+ if (!pointer) FAIL("seat has no pointer");
+ wl_pointer_add_listener(pointer, &_ptr_listener, NULL);
+
+ /* Map a plain toplevel. No input region, no subsurface. */
+ surface = wl_compositor_create_surface(compositor);
+ xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
+ xdg_surface_add_listener(xdg_surface, &_xdg_listener, NULL);
+ toplevel = xdg_surface_get_toplevel(xdg_surface);
+ xdg_toplevel_add_listener(toplevel, &_top_listener, NULL);
+ xdg_toplevel_set_title(toplevel, "pointer-enter");
+ wl_surface_commit(surface);
+ while (!configured)
+ if (wl_display_dispatch(disp) < 0) FAIL("dispatch failed awaiting configure");
+
+ buffer = make_buffer();
+ if (!buffer) FAIL("could not create an shm buffer");
+ wl_surface_attach(surface, buffer, 0, 0);
+ wl_surface_damage(surface, 0, 0, W, H);
+ wl_surface_commit(surface);
+ if (tester_sync() < 0) FAIL("sync failed after mapping");
+
+ /* Put it somewhere known, and confirm the compositor agrees before
+ * blaming the pointer for missing it. */
+ wl_test_move_surface(tester, surface, SX, SY);
+ if (tester_sync() < 0) FAIL("sync failed after move");
+ wl_test_get_surface_info(tester, surface);
+ if (wl_display_roundtrip(disp) < 0) FAIL("surface_info roundtrip failed");
+ if (!info_valid) FAIL("compositor has no E_Client for the mapped surface");
+ if ((info_x != SX) || (info_y != SY))
+ FAIL("surface should be at (%d,%d), compositor says (%d,%d)",
+ SX, SY, info_x, info_y);
+
+ /* Now walk the pointer onto the middle of it. Several small steps rather
+ * than one jump: a compositor that only re-evaluates what is under the
+ * pointer on movement should get every chance to notice. */
+ for (i = 0; i <= 4; i++)
+ {
+ wl_test_pointer_warp(tester, SX + (W / 4) + i, SY + (H / 2) + i);
+ if (tester_sync() < 0) FAIL("sync failed during warp");
+ }
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after warp failed");
+
+ if (!enter_count)
+ FAIL("no wl_pointer.enter after moving the pointer to (%d,%d), which is "
+ "inside the surface at (%d,%d) %dx%d "
+ "[leave=%d motion=%d] -- basic pointer delivery is not working, so "
+ "any input-region or subsurface result is meaningless",
+ SX + W / 4, SY + H / 2, info_x, info_y, info_w, info_h,
+ leave_count, motion_count);
+
+ if (entered != surface) FAIL("enter reported a different surface");
+
+ printf("test-pointer-enter: plain surface ok (enter=%d motion=%d, at %.1f,%.1f)\n",
+ enter_count, motion_count,
+ wl_fixed_to_double(enter_x), wl_fixed_to_double(enter_y));
+
+ /* Phase 1b: the same surface, built the way wlcs builds one - attach
+ * straight after get_toplevel, with no initial empty commit and no waiting
+ * for configure. That path only exists because of the test-only relaxation
+ * (E_TEST_ALLOW_UNCONFIGURED_BUFFER), and if it leaves E's state
+ * half-built then every wlcs input result is an artefact of the relaxation
+ * rather than a fact about E. Only run when the relaxation is active. */
+ if (getenv("E_TEST_ALLOW_UNCONFIGURED_BUFFER"))
+ {
+ struct wl_surface *s2 = wl_compositor_create_surface(compositor);
+ struct xdg_surface *xs2 = xdg_wm_base_get_xdg_surface(wm_base, s2);
+ struct xdg_toplevel *tl2;
+ struct wl_buffer *b2;
+
+ xdg_surface_add_listener(xs2, &_xdg_listener, NULL);
+ tl2 = xdg_surface_get_toplevel(xs2);
+ xdg_toplevel_add_listener(tl2, &_top_listener, NULL);
+ b2 = make_buffer2();
+ if (!b2) FAIL("could not create the second buffer");
+ wl_surface_attach(s2, b2, 0, 0); /* no initial commit first */
+ wl_surface_damage(s2, 0, 0, W, H);
+ wl_surface_commit(s2);
+ if (tester_sync() < 0) FAIL("sync failed after wlcs-style map");
+
+ wl_test_move_surface(tester, s2, SX + 300, SY);
+ if (tester_sync() < 0) FAIL("sync failed after moving second surface");
+
+ info_valid = 0;
+ wl_test_get_surface_info(tester, s2);
+ if (wl_display_roundtrip(disp) < 0) FAIL("surface_info roundtrip failed");
+ if (!info_valid) FAIL("no E_Client for the wlcs-style surface");
+
+ entered = NULL; enter_count = 0;
+ for (i = 0; i <= 4; i++)
+ {
+ wl_test_pointer_warp(tester, SX + 300 + (W / 2) + i, SY + (H / 2));
+ if (tester_sync() < 0) FAIL("sync failed during warp");
+ }
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after warp failed");
+
+ printf("test-pointer-enter: wlcs-style surface: geom=%dx%d at %d,%d "
+ "enter=%d%s\n", info_w, info_h, info_x, info_y, enter_count,
+ enter_count ? "" : " <-- INPUT NOT DELIVERED");
+ if (!enter_count)
+ FAIL("a surface mapped without the initial commit gets no pointer "
+ "input, even though the identical surface mapped properly does. "
+ "The relaxation lets the buffer through but leaves E's state "
+ "incomplete, so wlcs's input results measure the relaxation, "
+ "not E");
+ }
+
+ /* Phase 2: the same thing, but with an input region covering the whole
+ * surface. Setting a region that covers everything must not change who
+ * gets the pointer - so if this fails while the above passed,
+ * set_input_region itself is what breaks input delivery.
+ *
+ * This is the difference between "E ignores input regions" (harmless here,
+ * the test would still pass) and "E applies them wrongly and ends up with
+ * an empty input area" (every positive input test fails, every negative
+ * one passes for free - which is exactly the shape of the wlcs results). */
+ {
+ struct wl_region *region;
+
+ /* Move the pointer well away first, so the enter we look for is new. */
+ wl_test_pointer_warp(tester, 5, 5);
+ if (tester_sync() < 0) FAIL("sync failed moving pointer away");
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip failed");
+
+ /* Only the right half. A region covering everything is not
+ * discriminating: if E applied the rectangle at the wrong origin the
+ * point in the middle would still be covered. wlcs's region tests all
+ * use partial regions, so match that. */
+ region = wl_compositor_create_region(compositor);
+ wl_region_add(region, W / 2, 0, W / 2, H);
+ wl_surface_set_input_region(surface, region);
+ wl_surface_commit(surface);
+ wl_region_destroy(region);
+ if (tester_sync() < 0) FAIL("sync failed after set_input_region");
+
+ /* Inside the region: must be seen. */
+ enter_count = 0;
+ for (i = 0; i <= 4; i++)
+ {
+ wl_test_pointer_warp(tester, SX + (3 * W / 4) + i, SY + (H / 2));
+ if (tester_sync() < 0) FAIL("sync failed during warp");
+ }
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after warp failed");
+
+ if (!enter_count)
+ FAIL("no wl_pointer.enter at (%d,%d), which is inside the input region "
+ "(right half of a %dx%d surface at %d,%d). This is wlcs's "
+ "input_inside_region_seen in miniature - 144 of its tests fail "
+ "here",
+ SX + 3 * W / 4, SY + H / 2, W, H, SX, SY);
+ printf("test-pointer-enter: inside region ok (enter=%d)\n", enter_count);
+
+ /* Outside the region but still over the surface: must NOT be seen.
+ * Checked because "no input anywhere" would pass the test above only by
+ * accident of ordering, and passes every wlcs input_not_seen_* test for
+ * free. */
+ wl_test_pointer_warp(tester, 5, 5);
+ if (tester_sync() < 0) FAIL("sync failed moving pointer away");
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip failed");
+
+ enter_count = 0;
+ for (i = 0; i <= 4; i++)
+ {
+ wl_test_pointer_warp(tester, SX + (W / 4) + i, SY + (H / 2));
+ if (tester_sync() < 0) FAIL("sync failed during warp");
+ }
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after warp failed");
+
+ if (enter_count)
+ FAIL("got wl_pointer.enter at (%d,%d), which is OUTSIDE the input "
+ "region - the region is being ignored",
+ SX + W / 4, SY + H / 2);
+ printf("test-pointer-enter: outside region correctly ignored\n");
+ }
+
+ return 0;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.