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 cd8c0ddb7bf997f7a5270b4e95bdd2c3b3248a57
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 8 18:11:40 2026 -0600
e_comp_wl - raise wl_output to version 4
E-01.
We advertised wl_output at version 2. Firefox guards its wl_output bind on
version >= 3 and *skips the bind entirely* below that rather than clamping
(widget/gtk/nsWaylandDisplay.cpp), so against E it never called
AddMonitorConfig and simply had no monitor list at all: no DPI, no colour
management, no per-monitor surface tracking. Chromium binds up to 4 and
uses the name.
v3 adds the release request, v4 adds name and description.
name has to be unique among outputs and immutable for the life of the
global, so it is decided once at creation and kept in E_Comp_Wl_Output
rather than derived per send. The randr backends give us a connector name
to use, but wl_wl, wl_buffer and the fake-xinerama path all pass NULL, and
naming every one of those the same thing would let a client key off the
name and collapse two outputs into one. Those get the WL-n form the other
compositors use.
The two send paths - initial bind, and the update loop in
e_comp_wl_output_init() - had been drifting copies of each other. Fold
them into one _e_comp_wl_output_resource_update() so a future event cannot
be added to one and not the other.
Tested by src/tests/wayland/test_output.c, which checks the bound version,
that release does not kill the connection, that name is not a placeholder,
and that name/description/scale all arrive before the first done - a
client that applies state on done would otherwise see a nameless output.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/bin/e_comp_wl.c | 111 ++++++++++++++-------
src/bin/e_comp_wl.h | 13 +++
src/tests/wayland/globals.expected | 2 +-
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_output.c | 194 +++++++++++++++++++++++++++++++++++++
5 files changed, 285 insertions(+), 36 deletions(-)
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index 4846dbe76..bdc1be553 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -3280,6 +3280,59 @@ _e_comp_wl_cb_output_unbind(struct wl_resource *resource)
output->resources = eina_list_remove(output->resources, resource);
}
+static void
+_e_comp_wl_output_cb_release(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static const struct wl_output_interface _e_comp_wl_output_interface =
+{
+ _e_comp_wl_output_cb_release
+};
+
+/* Send everything a freshly bound - or newly reconfigured - output resource
+ * needs, ending with the done marker that closes the atomic update.
+ *
+ * Order matters: name and description belong to the initial burst and have to
+ * arrive before the first done. name is additionally required to be immutable
+ * for the lifetime of the global, so it comes from the output id rather than
+ * from anything a reconfigure can change. */
+static void
+_e_comp_wl_output_resource_update(E_Comp_Wl_Output *output, struct wl_resource *resource)
+{
+ uint32_t version = wl_resource_get_version(resource);
+
+ if (version >= WL_OUTPUT_NAME_SINCE_VERSION)
+ {
+ char buf[512];
+
+ wl_output_send_name(resource, output->name);
+
+ if (output->make && output->model)
+ snprintf(buf, sizeof(buf), "%s %s", output->make, output->model);
+ else if (output->model)
+ snprintf(buf, sizeof(buf), "%s", output->model);
+ else
+ snprintf(buf, sizeof(buf), "%s", output->name);
+ wl_output_send_description(resource, buf);
+ }
+
+ wl_output_send_geometry(resource, output->x, output->y,
+ output->phys_width, output->phys_height,
+ output->subpixel, output->make ?: "",
+ output->model ?: "", output->transform);
+
+ if (version >= WL_OUTPUT_SCALE_SINCE_VERSION)
+ wl_output_send_scale(resource, output->scale);
+
+ /* 3 == preferred + current */
+ wl_output_send_mode(resource, 3, output->w, output->h, output->refresh);
+
+ if (version >= WL_OUTPUT_DONE_SINCE_VERSION)
+ wl_output_send_done(resource);
+}
+
static void
_e_comp_wl_cb_output_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
@@ -3301,23 +3354,10 @@ _e_comp_wl_cb_output_bind(struct wl_client *client, void *data, uint32_t version
output->resources = eina_list_append(output->resources, resource);
- wl_resource_set_implementation(resource, NULL, output,
- _e_comp_wl_cb_output_unbind);
- wl_resource_set_user_data(resource, output);
+ wl_resource_set_implementation(resource, &_e_comp_wl_output_interface,
+ output, _e_comp_wl_cb_output_unbind);
- wl_output_send_geometry(resource, output->x, output->y,
- output->phys_width, output->phys_height,
- output->subpixel, output->make ?: "",
- output->model ?: "", output->transform);
-
- if (version >= WL_OUTPUT_SCALE_SINCE_VERSION)
- wl_output_send_scale(resource, output->scale);
-
- /* 3 == preferred + current */
- wl_output_send_mode(resource, 3, output->w, output->h, output->refresh);
-
- if (version >= WL_OUTPUT_DONE_SINCE_VERSION)
- wl_output_send_done(resource);
+ _e_comp_wl_output_resource_update(output, resource);
}
static Eina_Bool
@@ -3631,6 +3671,7 @@ e_comp_wl_shutdown(void)
if (output->id) eina_stringshare_del(output->id);
if (output->make) eina_stringshare_del(output->make);
if (output->model) eina_stringshare_del(output->model);
+ if (output->name) eina_stringshare_del(output->name);
free(output);
}
@@ -3850,11 +3891,28 @@ e_comp_wl_output_init(const char *id, const char *make, const char *model,
if (make) output->make = eina_stringshare_add(make);
if (model) output->model = eina_stringshare_add(model);
+ /* wl_output.name has to be unique across outputs. The randr backends
+ * hand us a connector name ("HDMI-A-1"), but wl_wl, wl_buffer and the
+ * fake-xinerama path all pass NULL - and naming every one of those
+ * the same thing would break any client that keys off the name. Fall
+ * back to the WL-n form the other compositors use. */
+ if (id)
+ output->name = eina_stringshare_ref(output->id);
+ else
+ {
+ static int fallback_num = 0;
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "WL-%d", ++fallback_num);
+ output->name = eina_stringshare_add(buf);
+ }
+
e_comp_wl->outputs = eina_list_append(e_comp_wl->outputs, output);
output->global =
wl_global_create(e_comp_wl->wl.disp, &wl_output_interface,
- 2, output, _e_comp_wl_cb_output_bind);
+ E_COMP_WL_OUTPUT_VERSION, output,
+ _e_comp_wl_cb_output_bind);
output->resources = NULL;
output->scale = e_scale;
@@ -3878,24 +3936,7 @@ e_comp_wl_output_init(const char *id, const char *make, const char *model,
/* if we have bound resources, send updates */
EINA_LIST_FOREACH(output->resources, l2, resource)
- {
- wl_output_send_geometry(resource,
- output->x, output->y,
- output->phys_width,
- output->phys_height,
- output->subpixel,
- output->make ?: "", output->model ?: "",
- output->transform);
-
- if (wl_resource_get_version(resource) >= WL_OUTPUT_SCALE_SINCE_VERSION)
- wl_output_send_scale(resource, output->scale);
-
- /* 3 == preferred + current */
- wl_output_send_mode(resource, 3, output->w, output->h, output->refresh);
-
- if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION)
- wl_output_send_done(resource);
- }
+ _e_comp_wl_output_resource_update(output, resource);
return EINA_TRUE;
}
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index 61af8c1bd..a1e9e61eb 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -31,6 +31,15 @@ typedef void (*E_Comp_Wl_Grab_End_Cb)(E_Client*);
/* # define GL_GLEXT_PROTOTYPES */
/* # endif */
+/* Highest wl_output version we implement.
+ *
+ * v3 adds the release request, v4 the name and description events. Firefox
+ * guards its wl_output bind on version >= 3 and skips the bind entirely below
+ * that - at v2 it never called AddMonitorConfig and simply had no monitor
+ * list at all: no DPI, no per-monitor surface tracking. Chromium binds up to
+ * 4 and uses the name. */
+# define E_COMP_WL_OUTPUT_VERSION 4
+
# ifdef __linux__
# include <linux/input.h>
# else
@@ -406,6 +415,10 @@ struct _E_Comp_Wl_Output
struct wl_global *global;
Eina_List *resources;
const char *id, *make, *model;
+ /* wl_output.name (v4). Required to be unique among outputs and immutable
+ * for the lifetime of the global, so it is decided once at creation and
+ * never touched by a reconfigure. */
+ const char *name;
int x, y, w, h;
int phys_width, phys_height;
unsigned int refresh;
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 2887da7b1..1041a716f 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -2,7 +2,7 @@ action_route 1
efl_aux_hints 1
wl_compositor 4
wl_data_device_manager 3
-wl_output 2
+wl_output 4
wl_seat 5
wl_shell 1
wl_shm 1
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index c69e63868..1dc6922b1 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -51,6 +51,7 @@ wl_protocol_tests = [
['pointer-enter', 'test_pointer_enter.c'],
['pointer-frame', 'test_pointer_frame.c'],
['surface-unmap', 'test_surface_unmap.c'],
+ ['output', 'test_output.c'],
]
foreach t: wl_protocol_tests
diff --git a/src/tests/wayland/test_output.c b/src/tests/wayland/test_output.c
new file mode 100644
index 000000000..488f943ba
--- /dev/null
+++ b/src/tests/wayland/test_output.c
@@ -0,0 +1,194 @@
+/* wl_output conformance (E-01).
+ *
+ * Asserts the things a real toolkit depends on and that are easy to get
+ * subtly wrong:
+ *
+ * - the global binds at version 4 at all;
+ * - name and description arrive, name is not a shared placeholder, and both
+ * arrive *before* the first done as the initial burst requires;
+ * - the release request is accepted (v3) rather than killing the client.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+
+#define FAIL(fmt, ...) \
+ do { fprintf(stderr, "test-output: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+typedef struct
+{
+ struct wl_output *output;
+ uint32_t bound_version;
+
+ char name[256];
+ char description[256];
+
+ int32_t scale;
+
+ int geometry_count;
+ int mode_count;
+ int done_count;
+
+ /* Everything below records ordering: the value of done_count at the moment
+ * the event arrived. Anything in the initial burst must see 0. */
+ int name_at_done;
+ int description_at_done;
+ int scale_at_done;
+} Output;
+
+static Output out;
+
+static void
+_geometry(void *data, struct wl_output *o, int32_t x, int32_t y,
+ int32_t pw, int32_t ph, int32_t subpixel,
+ const char *make, const char *model, int32_t transform)
+{
+ Output *t = data;
+
+ (void)o; (void)x; (void)y; (void)pw; (void)ph;
+ (void)subpixel; (void)make; (void)model; (void)transform;
+ t->geometry_count++;
+}
+
+static void
+_mode(void *data, struct wl_output *o, uint32_t flags, int32_t w, int32_t h, int32_t refresh)
+{
+ Output *t = data;
+
+ (void)o; (void)flags; (void)w; (void)h; (void)refresh;
+ t->mode_count++;
+}
+
+static void
+_done(void *data, struct wl_output *o)
+{
+ Output *t = data;
+
+ (void)o;
+ t->done_count++;
+}
+
+static void
+_scale(void *data, struct wl_output *o, int32_t factor)
+{
+ Output *t = data;
+
+ (void)o;
+ t->scale = factor;
+ t->scale_at_done = t->done_count;
+}
+
+static void
+_name(void *data, struct wl_output *o, const char *name)
+{
+ Output *t = data;
+
+ (void)o;
+ snprintf(t->name, sizeof(t->name), "%s", name ?: "");
+ t->name_at_done = t->done_count;
+}
+
+static void
+_description(void *data, struct wl_output *o, const char *description)
+{
+ Output *t = data;
+
+ (void)o;
+ snprintf(t->description, sizeof(t->description), "%s", description ?: "");
+ t->description_at_done = t->done_count;
+}
+
+static const struct wl_output_listener _output_listener =
+{
+ _geometry, _mode, _done, _scale, _name, _description
+};
+
+static void
+_global_add(void *data, struct wl_registry *reg, uint32_t id,
+ const char *iface, uint32_t version)
+{
+ Output *t = data;
+
+ if (strcmp(iface, "wl_output")) return;
+ if (t->output) return; /* first output is enough */
+
+ t->bound_version = version < 4 ? version : 4;
+ t->output = wl_registry_bind(reg, id, &wl_output_interface, t->bound_version);
+ wl_output_add_listener(t->output, &_output_listener, t);
+}
+
+static void
+_global_remove(void *data, struct wl_registry *reg, uint32_t id)
+{
+ (void)data; (void)reg; (void)id;
+}
+
+static const struct wl_registry_listener _registry_listener =
+{
+ _global_add, _global_remove
+};
+
+int
+main(void)
+{
+ struct wl_display *disp;
+ struct wl_registry *reg;
+
+ out.scale = -1;
+
+ 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, &_registry_listener, &out);
+
+ if (wl_display_roundtrip(disp) < 0) FAIL("registry roundtrip failed");
+ if (!out.output) FAIL("compositor advertises no wl_output");
+ if (wl_display_roundtrip(disp) < 0) FAIL("output roundtrip failed");
+
+ if (out.bound_version < 4)
+ FAIL("wl_output bound at v%u, expected 4 -- Firefox skips the bind "
+ "entirely below v3", out.bound_version);
+
+ if (!out.geometry_count) FAIL("no geometry event");
+ if (!out.mode_count) FAIL("no mode event");
+ if (!out.done_count) FAIL("no done event -- the initial burst never closed");
+
+ if (!out.name[0]) FAIL("no name event at v4");
+ if (!out.description[0]) FAIL("no description event at v4");
+
+ /* The protocol requires name to be unique among outputs, so a placeholder
+ * is not good enough: with two zones every output would answer to the same
+ * name and any client keying off it would collapse them into one. */
+ if (!strcmp(out.name, "unknown") || !strcmp(out.name, ""))
+ FAIL("name is the placeholder \"%s\"; it has to be unique per output",
+ out.name);
+
+ /* The initial burst has to be complete before done. A client that applies
+ * state on done would otherwise see a nameless output. */
+ if (out.name_at_done != 0)
+ FAIL("name arrived after done #%d, must be in the initial burst",
+ out.name_at_done);
+ if (out.description_at_done != 0)
+ FAIL("description arrived after done #%d, must be in the initial burst",
+ out.description_at_done);
+ if (out.scale_at_done != 0)
+ FAIL("scale arrived after done #%d, must be in the initial burst",
+ out.scale_at_done);
+
+ /* v3 release. If this is not implemented the compositor kills the client
+ * for an unknown opcode, so the roundtrip below is the assertion. */
+ wl_output_release(out.output);
+ out.output = NULL;
+ if (wl_display_roundtrip(disp) < 0)
+ FAIL("connection died after wl_output.release -- v3 request missing");
+
+ wl_registry_destroy(reg);
+ wl_display_disconnect(disp);
+
+ printf("test-output: ok (v%u, name=\"%s\", description=\"%s\", scale=%d)\n",
+ out.bound_version, out.name, out.description, out.scale);
+ return 0;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.