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 4eb9b034e7aa0f461c252d24e8387b892111222e
Author: Cedric BAIL <[email protected]>
AuthorDate: Tue Aug 18 23:53:46 2026 -0600
e_comp_wl_input - wl_seat.capabilities is a change notification
"Sent whenever a seat gains or loses the pointer, keyboard or touch
capabilities" - so it is sent on a change, and a client is entitled to
read one as "the set of devices on this seat is now different from the
set you have". Creating a wl_pointer in response is the obvious thing to
do, and it is what wlcs's own client does.
E sent it to every bound seat resource whenever a *new* client bound the
seat, which is neither a gain nor a loss. The client that had been
running for five minutes was told its devices had changed when nothing
had, made a second wl_pointer, and from then on received every pointer
event twice on two objects that both believed they held the pointer
focus.
Two changes, both of which have to be there:
* binding sends the capabilities to the resource that just bound, not
to everyone. That is the actual bug;
* the broadcast sends only when the value has changed, so a call that
toggles a capability to the value it already had stays quiet. That
is not the same bug, but it is the same mistake one layer down.
Found while making the text-input wlcs tests run at all: every one of
them with two clients and a pointer failed on "Pointer tried to enter
surface X without first leaving surface X", which is what a duplicated
wl_pointer looks like from the far end.
The test is two connections and a count. Nothing in it changes the
seat's devices, so the first connection must see exactly one
capabilities event no matter what the second connection does; reverting
either half of the fix makes it fail, and it was checked against a build
with each removed.
---
src/bin/e_comp_wl_input.c | 43 +++++++++--
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_seat_capabilities.c | 112 +++++++++++++++++++++++++++++
3 files changed, 151 insertions(+), 5 deletions(-)
diff --git a/src/bin/e_comp_wl_input.c b/src/bin/e_comp_wl_input.c
index 4deeff6a3..a55aa5903 100644
--- a/src/bin/e_comp_wl_input.c
+++ b/src/bin/e_comp_wl_input.c
@@ -19,11 +19,9 @@ static struct xkb_context *cached_context;
static struct xkb_keymap *cached_keymap;
static xkb_layout_index_t choosen_group;
-static void
-_e_comp_wl_input_update_seat_caps(void)
+static enum wl_seat_capability
+_e_comp_wl_input_seat_caps(void)
{
- Eina_List *l;
- struct wl_resource *res;
enum wl_seat_capability caps = 0;
if (e_comp_wl->ptr.enabled)
@@ -33,6 +31,33 @@ _e_comp_wl_input_update_seat_caps(void)
if (e_comp_wl->touch.enabled)
caps |= WL_SEAT_CAPABILITY_TOUCH;
+ return caps;
+}
+
+/* What every bound seat resource has last been told, so that a call which
+ * changes nothing sends nothing. -1 is not a capability bitmask, so the first
+ * call always sends. */
+static int _seat_caps_sent = -1;
+
+/* Tell every client the seat's devices changed.
+ *
+ * "Sent whenever a seat gains or loses the pointer, keyboard or touch
+ * capabilities" - wl_seat.capabilities. So it is sent on a change, and only on
+ * a change. A client is within its rights to read the event as "the set of
+ * devices is now different from what you had" and act on it; one that creates
+ * a wl_pointer each time it arrives ends up with two, and from then on
+ * receives every pointer event twice on two objects that both believe they
+ * hold the focus. */
+static void
+_e_comp_wl_input_update_seat_caps(void)
+{
+ Eina_List *l;
+ struct wl_resource *res;
+ enum wl_seat_capability caps = _e_comp_wl_input_seat_caps();
+
+ if ((int)caps == _seat_caps_sent) return;
+ _seat_caps_sent = (int)caps;
+
EINA_LIST_FOREACH(e_comp_wl->seat.resources, l, res)
wl_seat_send_capabilities(res, caps);
}
@@ -319,7 +344,15 @@ _e_comp_wl_input_cb_bind_seat(struct wl_client *client, void *data EINA_UNUSED,
e_comp->wl_comp_data,
_e_comp_wl_input_cb_unbind_seat);
- _e_comp_wl_input_update_seat_caps();
+ /* To this resource, not to every seat resource in the session. A second
+ * client connecting is not the seat gaining or losing a device, and the
+ * clients already running have no business hearing about it - the one that
+ * bound five minutes ago would be told its devices changed when nothing
+ * did. Every wlcs test with two clients and a pointer failed on exactly
+ * that: the first client made a second wl_pointer when the second client
+ * connected, and then saw two enter events for one crossing. */
+ wl_seat_send_capabilities(res, _e_comp_wl_input_seat_caps());
+
/* This client's own version, not seat.version: that field holds whoever
* bound last, and two clients on different versions are now the normal
* case rather than a curiosity - GTK binds 5, Firefox and Chromium 8. */
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index b313c0e34..4271d6bb7 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -90,6 +90,7 @@ wl_protocol_tests = [
['xdg-foreign', 'test_xdg_foreign.c'],
['content-type', 'test_content_type.c'],
['toplevel-drag', 'test_toplevel_drag.c'],
+ ['seat-capabilities', 'test_seat_capabilities.c'],
]
# Shared plumbing: registry binding, toplevel construction, enumeration and a
diff --git a/src/tests/wayland/test_seat_capabilities.c b/src/tests/wayland/test_seat_capabilities.c
new file mode 100644
index 000000000..b4db0a9fb
--- /dev/null
+++ b/src/tests/wayland/test_seat_capabilities.c
@@ -0,0 +1,112 @@
+/* wl_seat.capabilities is a change notification, not a greeting.
+ *
+ * "Sent whenever a seat gains or loses the pointer, keyboard or touch
+ * capabilities" - so a client is entitled to read one as "the set of devices
+ * on this seat is now different from the set you have" and act accordingly.
+ * Creating a wl_pointer in response is the obvious thing to do, and it is what
+ * wlcs's own client does.
+ *
+ * E used to send it to every bound seat resource whenever a *new* client bound
+ * the seat, which is neither a gain nor a loss. The client that had been
+ * running made a second wl_pointer for a change that never happened, and from
+ * then on received every pointer event twice, on two objects that both
+ * believed they held the pointer focus. Every wlcs test with two clients and a
+ * pointer failed on it once anything made them run at all.
+ *
+ * The test is two connections and a count. Nothing here changes the seat's
+ * devices, so the first connection must see exactly one capabilities event no
+ * matter what the second connection does.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "e_wl_testkit.h"
+
+#define PROG "test-seat-capabilities"
+
+typedef struct
+{
+ struct wl_seat *seat;
+ int events;
+ uint32_t caps;
+} Seat_Watch;
+
+static void
+_caps(void *data, struct wl_seat *seat, uint32_t caps)
+{
+ Seat_Watch *w = data;
+
+ (void)seat;
+ w->events++;
+ w->caps = caps;
+}
+
+static void
+_name(void *d, struct wl_seat *s, const char *n)
+{ (void)d; (void)s; (void)n; }
+
+static const struct wl_seat_listener _seat_listener = { _caps, _name };
+
+static void
+_seat_watch(Tk *tk, Seat_Watch *w)
+{
+ uint32_t version = tk_global_version(tk, "wl_seat");
+
+ if (!version) tk_fail(tk, "no wl_seat");
+ w->seat = tk_bind(tk, &wl_seat_interface, version);
+ if (!w->seat) tk_fail(tk, "wl_seat advertised but would not bind");
+ wl_seat_add_listener(w->seat, &_seat_listener, w);
+ tk_sync(tk);
+}
+
+int
+main(void)
+{
+ Tk *a, *b;
+ Seat_Watch wa = { 0 }, wb = { 0 };
+ uint32_t caps_after_first;
+
+ a = tk_connect(PROG "-a");
+ _seat_watch(a, &wa);
+
+ if (wa.events != 1)
+ tk_fail(a, "first client saw %d capabilities events on binding the seat, "
+ "expected exactly 1", wa.events);
+ caps_after_first = wa.caps;
+
+ /* A second client arrives. This is the whole test: nothing about the seat
+ * has changed, so the first client must hear nothing. */
+ b = tk_connect(PROG "-b");
+ _seat_watch(b, &wb);
+
+ if (wb.events != 1)
+ tk_fail(b, "second client saw %d capabilities events on binding the seat, "
+ "expected exactly 1", wb.events);
+
+ tk_sync(a);
+ tk_sync(a);
+
+ if (wa.events != 1)
+ tk_fail(a, "first client saw %d capabilities events after a second client "
+ "bound the seat, expected 1 - a client that creates a "
+ "wl_pointer for each one now has two, and gets every pointer "
+ "event twice", wa.events);
+
+ if (wa.caps != caps_after_first)
+ tk_fail(a, "capabilities changed from 0x%x to 0x%x without any device "
+ "being added or removed", caps_after_first, wa.caps);
+
+ /* Both clients must agree about what the seat has; they are looking at the
+ * same seat. A compositor that computes the value per bind rather than from
+ * one place can get this wrong without ever sending a spurious event. */
+ if (wa.caps != wb.caps)
+ tk_fail(a, "the two clients were told different capabilities for the same "
+ "seat: 0x%x and 0x%x", wa.caps, wb.caps);
+
+ printf(PROG ": ok - one capabilities event each, caps 0x%x\n", wa.caps);
+
+ tk_disconnect(b);
+ tk_disconnect(a);
+ return 0;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.