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 7bd95b11368385760058d66fb71d96d5cada8bef
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 9 11:21:24 2026 -0600
e_comp_wl - close every input event group with a frame
wl_pointer.frame and wl_touch.frame mark the end of a set of events the
client must apply together. wayland.xml is explicit that everything before
a frame "belongs logically together", and clients take that literally:
wlcs, GTK and Chromium all park the enter, the motion and the button in a
pending slot and only commit them when the frame arrives.
E has never sent either. It advertised wl_seat 4, where wl_pointer.frame
does not exist, and simply omitted wl_touch.frame, which has been there
since version 1.
The effect is total and invisible on the wire. Instrumenting
XdgToplevelStableTest.pointer_respects_window_geom_offset, which fails with
window_under_cursor() == NULL, shows E getting everything right - the
window rect at 200,280 165x308, the buffer at 165,268 after the
set_window_geometry offset, ec->mouse.in set - and putting the enter out
with the coordinates the test asks for:
[email protected](5, wl_surface@13, 55.00000000, 42.00000000)
and the client throwing it away. That failure had been read as a focus bug
and then as a window-geometry bug. It was neither, and neither were most of
the input failures filed alongside it.
So: seat to version 5, a frame after every enter, leave, motion, button and
axis, and after every touch down, motion and up. The frame is gated per
resource, so a client that binds at 4 sees exactly what it saw before.
Version 5 also brings axis_source and axis_discrete, and those go in here
rather than in E-02 because advertising 5 without them is worse than not
advertising it: a v5 client that gets a bare axis assumes a continuous
source and scrolls by the raw value, so the wheel has to say it is a wheel.
axis_stop and value120 remain E-02's.
Measured, wlcs v1.7.0, relaxed mode, before -> after:
E-22 passed 1 failed 5 -> passed 4 failed 2
E-24 passed 10 failed 21 -> passed 16 failed 15
E-20 passed 3 failed 15 -> passed 7 failed 11
E-21 and E-01 unchanged, as expected - neither touches input.
The in-tree test asserts ordering rather than counts, because a count
cannot tell a missing frame from a working one: it records the events as
they arrive and fails if any group is left unclosed. It also pins the seat
version, since dropping back to 4 would disable everything it checks
without failing any assertion.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/bin/e_comp_wl.c | 34 +++
src/bin/e_comp_wl.h | 1 +
src/bin/e_comp_wl_data.c | 1 +
src/bin/e_comp_wl_input.c | 7 +-
src/tests/wayland/globals.expected | 2 +-
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_pointer_frame.c | 466 +++++++++++++++++++++++++++++++++
src/tests/wlcs/e_wlcs.c | 2 +-
src/tests/wlcs/tasks.txt | 14 +-
9 files changed, 521 insertions(+), 7 deletions(-)
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index 99e16295c..a0d6b8351 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -186,6 +186,22 @@ _e_comp_wl_evas_cb_hide(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EIN
e_object_unref(E_OBJECT(ec));
}
+/* wl_pointer.frame ends a group of pointer events the client must apply as
+ * one - the enter and the motion that placed the cursor, or a motion and the
+ * axis it produced. It is not a formality: wayland.xml says "all wl_pointer
+ * events before a wl_pointer.frame event belong logically together", and a
+ * client is entitled to buffer everything until the frame arrives. Several do,
+ * so a group that is never closed is never delivered at all.
+ *
+ * Sent only to wl_pointer version 5 and up; older clients have no such event
+ * and treat each one as self-contained. */
+EINTERN void
+e_comp_wl_pointer_frame_send(struct wl_resource *res)
+{
+ if (wl_resource_get_version(res) >= WL_POINTER_FRAME_SINCE_VERSION)
+ wl_pointer_send_frame(res);
+}
+
static void
_e_comp_wl_mouse_in(E_Client *ec, Evas_Event_Mouse_In *ev)
{
@@ -217,6 +233,7 @@ _e_comp_wl_mouse_in(E_Client *ec, Evas_Event_Mouse_In *ev)
wl_pointer_send_enter(res, serial, ec->comp_data->surface,
wl_fixed_from_int(ev->canvas.x - ec->client.x),
wl_fixed_from_int(ev->canvas.y - ec->client.y));
+ e_comp_wl_pointer_frame_send(res);
e_comp_wl_input_pointer_cursor_update(ptr);
}
}
@@ -274,6 +291,7 @@ _e_comp_wl_mouse_out(E_Client *ec)
if (wl_resource_get_client(res) != wc) continue;
ptr->entered = 0;
wl_pointer_send_leave(res, serial, ec->comp_data->surface);
+ e_comp_wl_pointer_frame_send(res);
}
}
@@ -358,6 +376,7 @@ _e_comp_wl_send_mouse_move(E_Client *ec, int x, int y, unsigned int timestamp)
wl_pointer_send_motion(res, timestamp,
wl_fixed_from_int(x - ec->client.x),
wl_fixed_from_int(y - ec->client.y));
+ e_comp_wl_pointer_frame_send(res);
}
}
@@ -448,7 +467,18 @@ _e_comp_wl_evas_cb_mouse_wheel(void *data, Evas *evas EINA_UNUSED, Evas_Object *
{
if (!e_comp_wl_input_pointer_check(res)) continue;
if (wl_resource_get_client(res) != wc) continue;
+ /* A v5 client that gets a bare wl_pointer.axis assumes a continuous
+ * source - a touchpad - and scrolls by the raw value. Evas only ever
+ * reports whole wheel clicks here, so say so: axis_source first,
+ * then the click count before the value it stands for, which is the
+ * order wayland.xml requires. */
+ if (wl_resource_get_version(res) >= WL_POINTER_AXIS_SOURCE_SINCE_VERSION)
+ {
+ wl_pointer_send_axis_source(res, WL_POINTER_AXIS_SOURCE_WHEEL);
+ wl_pointer_send_axis_discrete(res, axis, ev->z);
+ }
wl_pointer_send_axis(res, ev->timestamp, axis, dir);
+ e_comp_wl_pointer_frame_send(res);
}
}
@@ -479,6 +509,7 @@ _e_comp_wl_evas_cb_multi_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *o
if (!e_comp_wl_input_touch_check(res)) continue;
wl_touch_send_down(res, serial, ev->timestamp,
ec->comp_data->surface, ev->device, x, y);
+ wl_touch_send_frame(res);
}
}
@@ -504,6 +535,7 @@ _e_comp_wl_evas_cb_multi_up(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj
if (wl_resource_get_client(res) != wc) continue;
if (!e_comp_wl_input_touch_check(res)) continue;
wl_touch_send_up(res, serial, ev->timestamp, ev->device);
+ wl_touch_send_frame(res);
}
}
@@ -531,6 +563,7 @@ _e_comp_wl_evas_cb_multi_move(void *data, Evas *evas EINA_UNUSED, Evas_Object *o
if (wl_resource_get_client(res) != wc) continue;
if (!e_comp_wl_input_touch_check(res)) continue;
wl_touch_send_motion(res, ev->timestamp, ev->device, x, y);
+ wl_touch_send_frame(res);
}
}
@@ -3613,6 +3646,7 @@ e_comp_wl_evas_handle_mouse_button(E_Client *ec, uint32_t timestamp, uint32_t bu
if (wl_resource_get_client(res) != wc) continue;
if (!e_comp_wl_input_pointer_check(res)) continue;
wl_pointer_send_button(res, serial, timestamp, btn, state);
+ e_comp_wl_pointer_frame_send(res);
}
return EINA_TRUE;
}
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index a18746c5d..77485a0a5 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -414,6 +414,7 @@ E_API void e_comp_wl_output_remove(const char *id);
EINTERN Eina_Bool e_comp_wl_key_down(Ecore_Event_Key *ev, E_Client *ec);
EINTERN Eina_Bool e_comp_wl_key_up(Ecore_Event_Key *ev, E_Client *ec);
E_API Eina_Bool e_comp_wl_evas_handle_mouse_button(E_Client *ec, uint32_t timestamp, uint32_t button_id, uint32_t state);
+EINTERN void e_comp_wl_pointer_frame_send(struct wl_resource *res);
E_API extern int E_EVENT_WAYLAND_GLOBAL_ADD;
diff --git a/src/bin/e_comp_wl_data.c b/src/bin/e_comp_wl_data.c
index d68a7e8dc..bb7b9ad06 100644
--- a/src/bin/e_comp_wl_data.c
+++ b/src/bin/e_comp_wl_data.c
@@ -670,6 +670,7 @@ _e_comp_wl_data_device_cb_drag_start(struct wl_client *client, struct wl_resourc
if (!e_comp_wl_input_pointer_check(res)) continue;
if (wl_resource_get_client(res) != client) continue;
wl_pointer_send_leave(res, serial, e_comp_wl->kbd.focus);
+ e_comp_wl_pointer_frame_send(res);
}
evas_pointer_canvas_xy_get(e_comp->evas, &x, &y);
diff --git a/src/bin/e_comp_wl_input.c b/src/bin/e_comp_wl_input.c
index 733e0d424..74df5d968 100644
--- a/src/bin/e_comp_wl_input.c
+++ b/src/bin/e_comp_wl_input.c
@@ -476,9 +476,12 @@ e_comp_wl_input_init(void)
if (!e_comp_wl->seat.name)
e_comp_wl->seat.name = "seat0";
- /* create the global resource for input seat */
+ /* Version 5 for wl_pointer.frame, and with it axis_source and
+ * axis_discrete. wl_seat has had them since 2015 and every current toolkit
+ * binds at 5 or above; the events are gated per resource, so a client that
+ * binds lower still sees exactly what it saw before. */
e_comp_wl->seat.global =
- wl_global_create(e_comp_wl->wl.disp, &wl_seat_interface, 4,
+ wl_global_create(e_comp_wl->wl.disp, &wl_seat_interface, 5,
e_comp->wl_comp_data, _e_comp_wl_input_cb_bind_seat);
if (!e_comp_wl->seat.global)
{
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 33367355e..2887da7b1 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -3,7 +3,7 @@ efl_aux_hints 1
wl_compositor 4
wl_data_device_manager 3
wl_output 2
-wl_seat 4
+wl_seat 5
wl_shell 1
wl_shm 1
wl_subcompositor 1
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 64bc76ae9..a3d3d7b35 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -49,6 +49,7 @@ test('wl-globals',
wl_protocol_tests = [
['test-module', 'test_wl_test.c'],
['pointer-enter', 'test_pointer_enter.c'],
+ ['pointer-frame', 'test_pointer_frame.c'],
]
foreach t: wl_protocol_tests
diff --git a/src/tests/wayland/test_pointer_frame.c b/src/tests/wayland/test_pointer_frame.c
new file mode 100644
index 000000000..1b5667395
--- /dev/null
+++ b/src/tests/wayland/test_pointer_frame.c
@@ -0,0 +1,466 @@
+/* Does every group of input events end with a frame event?
+ *
+ * wl_pointer.frame (seat v5) and wl_touch.frame (v1) mark the end of a set of
+ * events the client must apply together. wayland.xml is explicit that a client
+ * may buffer everything until the frame arrives, and real ones do: wlcs, GTK
+ * and Chromium all keep the enter, the motion and the button in a pending slot
+ * and only commit them in the frame handler.
+ *
+ * That makes a missing frame invisible on the wire and total in effect. E used
+ * to send a perfectly correct enter with the right surface and the right
+ * surface-local coordinates, and the client would throw it away. It looked
+ * like a focus bug, then like a window-geometry bug, and was neither.
+ *
+ * So this test asserts *ordering*, not counts: it records the events as they
+ * arrive and checks that nothing is left open at the end of each group. It
+ * also pins the seat version, because sending frame is only legal from 5 up
+ * and dropping back to 4 would silently disable everything checked here.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <linux/input-event-codes.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
+#define SY 90
+
+#define FAIL(fmt, ...) \
+ do { fprintf(stderr, "test-pointer-frame: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+/* One slot per event we care about, in arrival order. Small fixed buffer:
+ * a test that overruns it has gone wrong in a way a bigger buffer would only
+ * hide. */
+typedef enum
+{
+ EV_ENTER, EV_LEAVE, EV_MOTION, EV_BUTTON, EV_AXIS,
+ EV_AXIS_SOURCE, EV_AXIS_DISCRETE, EV_FRAME,
+ EV_TOUCH_DOWN, EV_TOUCH_UP, EV_TOUCH_MOTION, EV_TOUCH_FRAME
+} Ev;
+
+static const char *_ev_name[] =
+{
+ "enter", "leave", "motion", "button", "axis",
+ "axis_source", "axis_discrete", "frame",
+ "touch.down", "touch.up", "touch.motion", "touch.frame"
+};
+
+#define MAX_EV 256
+static Ev _log[MAX_EV];
+static int _log_n;
+
+static void
+_rec(Ev e)
+{
+ if (_log_n < MAX_EV) _log[_log_n++] = e;
+}
+
+static void
+_log_reset(void)
+{
+ _log_n = 0;
+}
+
+static void
+_log_dump(void)
+{
+ int i;
+
+ fprintf(stderr, " events seen:");
+ if (!_log_n) fprintf(stderr, " (none)");
+ for (i = 0; i < _log_n; i++) fprintf(stderr, " %s", _ev_name[_log[i]]);
+ fprintf(stderr, "\n");
+}
+
+static int
+_seen(Ev e)
+{
+ int i;
+
+ for (i = 0; i < _log_n; i++) if (_log[i] == e) return 1;
+ return 0;
+}
+
+/* Is every event of kind `open` closed by a later `closer`, with no `open`
+ * left dangling at the end? That is the whole contract. */
+static int
+_closed_by(Ev open, Ev closer)
+{
+ int i, pending = 0;
+
+ for (i = 0; i < _log_n; i++)
+ {
+ if (_log[i] == open) pending = 1;
+ else if (_log[i] == closer) pending = 0;
+ }
+ return !pending;
+}
+
+/* Index of the first occurrence, or -1. */
+static int
+_first(Ev e)
+{
+ int i;
+
+ for (i = 0; i < _log_n; i++) if (_log[i] == e) return i;
+ return -1;
+}
+
+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_touch *touch;
+static struct wl_test *tester;
+static uint32_t seat_advertised_version;
+
+static struct wl_surface *surface;
+static int configured, sync_done;
+static int32_t info_x, info_y;
+static int info_valid;
+static int32_t axis_discrete_value;
+static uint32_t axis_source_value;
+
+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; (void)s; (void)x; (void)y; _rec(EV_ENTER); }
+
+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; _rec(EV_LEAVE); }
+
+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; _rec(EV_MOTION); }
+
+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; _rec(EV_BUTTON); }
+
+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; _rec(EV_AXIS); }
+
+static void
+_ptr_frame(void *d, struct wl_pointer *p)
+{ (void)d; (void)p; _rec(EV_FRAME); }
+
+static void
+_ptr_axis_source(void *d, struct wl_pointer *p, uint32_t s)
+{ (void)d; (void)p; axis_source_value = s; _rec(EV_AXIS_SOURCE); }
+
+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; axis_discrete_value = v; _rec(EV_AXIS_DISCRETE); }
+
+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
+_touch_down(void *d, struct wl_touch *t, uint32_t se, uint32_t ti,
+ struct wl_surface *s, int32_t id, wl_fixed_t x, wl_fixed_t y)
+{ (void)d; (void)t; (void)se; (void)ti; (void)s; (void)id; (void)x; (void)y; _rec(EV_TOUCH_DOWN); }
+
+static void
+_touch_up(void *d, struct wl_touch *t, uint32_t se, uint32_t ti, int32_t id)
+{ (void)d; (void)t; (void)se; (void)ti; (void)id; _rec(EV_TOUCH_UP); }
+
+static void
+_touch_motion(void *d, struct wl_touch *t, uint32_t ti, int32_t id, wl_fixed_t x, wl_fixed_t y)
+{ (void)d; (void)t; (void)ti; (void)id; (void)x; (void)y; _rec(EV_TOUCH_MOTION); }
+
+static void
+_touch_frame(void *d, struct wl_touch *t)
+{ (void)d; (void)t; _rec(EV_TOUCH_FRAME); }
+
+static void
+_touch_cancel(void *d, struct wl_touch *t)
+{ (void)d; (void)t; }
+
+static void
+_touch_shape(void *d, struct wl_touch *t, int32_t id, wl_fixed_t maj, wl_fixed_t min)
+{ (void)d; (void)t; (void)id; (void)maj; (void)min; }
+
+static void
+_touch_orientation(void *d, struct wl_touch *t, int32_t id, wl_fixed_t o)
+{ (void)d; (void)t; (void)id; (void)o; }
+
+static const struct wl_touch_listener _touch_listener =
+{
+ _touch_down, _touch_up, _touch_motion, _touch_frame, _touch_cancel,
+ _touch_shape, _touch_orientation
+};
+
+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)w; (void)h; (void)vis; (void)foc;
+ info_valid = 1; info_x = x; info_y = y;
+}
+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;
+ 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"))
+ {
+ /* Bind at 5 exactly rather than at whatever is offered: this test is
+ * about what a v5 client sees, and binding higher later would change
+ * the answer without changing the test. */
+ seat_advertised_version = ver;
+ if (ver >= 5)
+ seat = wl_registry_bind(reg, id, &wl_seat_interface, 5);
+ }
+ 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(void)
+{
+ int fd, stride = W * 4, size = stride * H;
+ void *map;
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buf;
+
+ fd = shm_open("/e-test-frame-shm", O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd < 0) return NULL;
+ shm_unlink("/e-test-frame-shm");
+ 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;
+}
+
+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 (!tester) FAIL("no wl_test -- built without -Dtests=true?");
+ if (!seat)
+ FAIL("wl_seat is advertised at version %u; wl_pointer.frame needs 5. "
+ "Every v5 client buffers pointer input until the frame arrives, so "
+ "at 4 they get nothing at all",
+ seat_advertised_version);
+ 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);
+
+ touch = wl_seat_get_touch(seat);
+ if (!touch) FAIL("seat has no touch");
+ wl_touch_add_listener(touch, &_touch_listener, NULL);
+
+ 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-frame");
+ 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");
+
+ 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);
+
+ /* Pointer enter and motion. */
+ _log_reset();
+ for (i = 0; i <= 4; i++)
+ {
+ wl_test_pointer_warp(tester, SX + (W / 2) + 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 (!_seen(EV_ENTER)) { _log_dump(); FAIL("no wl_pointer.enter -- see test-pointer-enter first"); }
+ if (!_seen(EV_FRAME))
+ {
+ _log_dump();
+ FAIL("wl_pointer.enter arrived but no wl_pointer.frame ever did. A v5 "
+ "client holds the enter in a pending slot and commits it in the "
+ "frame handler, so as far as it is concerned the pointer never "
+ "arrived");
+ }
+ if (!_closed_by(EV_ENTER, EV_FRAME))
+ { _log_dump(); FAIL("an enter was left unterminated by a frame"); }
+ if (!_closed_by(EV_MOTION, EV_FRAME))
+ { _log_dump(); FAIL("a motion was left unterminated by a frame"); }
+ printf("test-pointer-frame: enter and motion are framed\n");
+
+ /* Buttons. */
+ _log_reset();
+ wl_test_pointer_button(tester, BTN_LEFT, 1);
+ if (tester_sync() < 0) FAIL("sync failed after button down");
+ wl_test_pointer_button(tester, BTN_LEFT, 0);
+ if (tester_sync() < 0) FAIL("sync failed after button up");
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after button failed");
+
+ if (!_seen(EV_BUTTON)) { _log_dump(); FAIL("no wl_pointer.button delivered"); }
+ if (!_closed_by(EV_BUTTON, EV_FRAME))
+ {
+ _log_dump();
+ FAIL("a button was left unterminated by a frame. This is what makes "
+ "every interactive move and resize test time out: the client is "
+ "waiting for a press it has already been sent");
+ }
+ printf("test-pointer-frame: buttons are framed\n");
+
+ /* Scroll. A v5 client that gets a bare axis assumes a touchpad and scrolls
+ * by the raw value, so the wheel has to identify itself. */
+ _log_reset();
+ axis_discrete_value = 0;
+ axis_source_value = 0xffffffff;
+ wl_test_pointer_axis(tester, 0 /* vertical */, 1);
+ if (tester_sync() < 0) FAIL("sync failed after axis");
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after axis failed");
+
+ if (!_seen(EV_AXIS)) { _log_dump(); FAIL("no wl_pointer.axis delivered"); }
+ if (!_closed_by(EV_AXIS, EV_FRAME))
+ { _log_dump(); FAIL("an axis was left unterminated by a frame"); }
+ if (!_seen(EV_AXIS_SOURCE))
+ { _log_dump(); FAIL("wl_pointer.axis with no axis_source: the client cannot tell a wheel from a touchpad"); }
+ if (axis_source_value != WL_POINTER_AXIS_SOURCE_WHEEL)
+ FAIL("axis_source is %u, expected wheel (%u)",
+ axis_source_value, WL_POINTER_AXIS_SOURCE_WHEEL);
+ if (!_seen(EV_AXIS_DISCRETE))
+ { _log_dump(); FAIL("a wheel click with no axis_discrete"); }
+ if (axis_discrete_value != 1)
+ FAIL("one wheel click reported as %d discrete steps", axis_discrete_value);
+ if (_first(EV_AXIS_SOURCE) > _first(EV_AXIS))
+ { _log_dump(); FAIL("axis_source came after the axis it describes"); }
+ if (_first(EV_AXIS_DISCRETE) > _first(EV_AXIS))
+ { _log_dump(); FAIL("axis_discrete came after the axis it describes"); }
+ printf("test-pointer-frame: axis is framed, sourced and discrete\n");
+
+ /* Touch. wl_touch.frame is not version-gated -- it has been part of the
+ * protocol since the beginning.
+ *
+ * Touch id 1, never 0: Evas reserves multi-touch device 0 for the mouse
+ * pointer and drops MULTI_DOWN/MOVE/UP for it silently. Passing 0 here
+ * produces no wl_touch events whatever the compositor does, which is
+ * indistinguishable from a compositor bug. See src/protocol/wl-test.xml. */
+ _log_reset();
+ wl_test_touch_down(tester, 1, SX + (W / 2), SY + (H / 2));
+ if (tester_sync() < 0) FAIL("sync failed after touch down");
+ wl_test_touch_move(tester, 1, SX + (W / 2) + 10, SY + (H / 2) + 10);
+ if (tester_sync() < 0) FAIL("sync failed after touch move");
+ wl_test_touch_up(tester, 1);
+ if (tester_sync() < 0) FAIL("sync failed after touch up");
+ if (wl_display_roundtrip(disp) < 0) FAIL("roundtrip after touch failed");
+
+ if (!_seen(EV_TOUCH_DOWN)) { _log_dump(); FAIL("no wl_touch.down delivered"); }
+ if (!_closed_by(EV_TOUCH_DOWN, EV_TOUCH_FRAME))
+ { _log_dump(); FAIL("a touch down was left unterminated by a wl_touch.frame"); }
+ if (!_closed_by(EV_TOUCH_UP, EV_TOUCH_FRAME))
+ { _log_dump(); FAIL("a touch up was left unterminated by a wl_touch.frame"); }
+ if (_seen(EV_TOUCH_MOTION) && (!_closed_by(EV_TOUCH_MOTION, EV_TOUCH_FRAME)))
+ { _log_dump(); FAIL("a touch motion was left unterminated by a wl_touch.frame"); }
+ printf("test-pointer-frame: touch is framed\n");
+
+ /* Leave. The pointer is moved right off the surface. */
+ _log_reset();
+ 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 after leaving failed");
+
+ if (!_seen(EV_LEAVE)) { _log_dump(); FAIL("no wl_pointer.leave after moving off the surface"); }
+ if (!_closed_by(EV_LEAVE, EV_FRAME))
+ { _log_dump(); FAIL("a leave was left unterminated by a frame"); }
+ printf("test-pointer-frame: leave is framed\n");
+
+ return 0;
+}
diff --git a/src/tests/wlcs/e_wlcs.c b/src/tests/wlcs/e_wlcs.c
index e8b2d350a..cbc40e1f4 100644
--- a/src/tests/wlcs/e_wlcs.c
+++ b/src/tests/wlcs/e_wlcs.c
@@ -672,7 +672,7 @@ static const WlcsExtensionDescriptor _extensions[] =
{ "wl_compositor", 4 },
{ "wl_subcompositor", 1 },
{ "wl_shm", 1 },
- { "wl_seat", 4 },
+ { "wl_seat", 5 },
{ "wl_output", 2 },
{ "wl_data_device_manager", 3 },
{ "xdg_wm_base", 1 },
diff --git a/src/tests/wlcs/tasks.txt b/src/tests/wlcs/tasks.txt
index 1c8187ec7..f1eb8cda5 100644
--- a/src/tests/wlcs/tasks.txt
+++ b/src/tests/wlcs/tasks.txt
@@ -44,15 +44,23 @@ E-21 BadBufferTest.*:SecondBadBufferTest.* # buffer validatio
E-22 ClientSurfaceEventsTest.*:SurfacePointerMotionTest.* # enter/leave bookkeeping
E-24 XdgToplevelStableTest.*:XdgToplevelV6Test.*:XdgToplevelStableConfigurationTest.*:XdgToplevelV6ConfigurationTest.* # interactive move/resize, window geometry
-# Not a single task - the big input-region and subsurface suites fail for a mix
-# of reasons (focus never arriving, spurious leaves, subsurface input routing).
-# Triage these before assigning them; they are the largest remaining block.
+# The big input-region and subsurface block. Originally listed as "a mix of
+# reasons (focus never arriving, spurious leaves, subsurface input routing)";
+# most of it turned out to be one missing event, E-02a - wlcs commits all
+# pointer and touch state in its frame handler, so without wl_pointer.frame and
+# wl_touch.frame every event E delivers is correct and then discarded. Re-run
+# this after E-02a before splitting it into tasks; whatever survives is real.
TRIAGE *RegionSurfaceInputCombinations.*:*SurfaceInputCombinations.*:*SubsurfaceTest.*:*SubsurfaceMultilevelTest.*:*ToplevelInputCombinations.*:*TouchTest.*
# No wlcs coverage. Listed so their absence is deliberate rather than an
# oversight: use the in-tree tests and the third-party clients instead.
# E-00 advertised scale - needs a HiDPI zone, not headless
# E-02 axis/value120 - wlcs does not exercise scroll axes
+# E-02a wl_pointer/wl_touch.frame - no filter of its own; it is the thing
+# standing between E-22, E-24 and TRIAGE and any meaningful number.
+# Its own criterion is the in-tree test, src/tests/wayland/
+# test_pointer_frame.c, which checks the ordering rather than counting
+# events: a group left unclosed by a frame is never applied at all.
# E-04 xdg-activation - no suite
# E-06 xdg-decoration - no suite
# E-07 presentation-time - no suite; use weston-presentation-shm
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.