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 8ea1ce5ef9b7f24784c6ea17f7f29fd0b29a64f7
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 15 12:41:07 2026 -0600
wl_test - report the scale and transform in effect, and rescale an output
set_buffer_scale and set_buffer_transform are write-only on the wire. A
client can send them and has no way to read back what the compositor
latched, so nothing short of reading pixels off a screen can tell "honoured"
from "parsed and thrown away" - and reading pixels needs a real display,
which the headless backend the suite runs on has not got. That is exactly
the bug this interface exists to catch, and it is why the file used to carry
a note saying to add the buffer scale here once E had somewhere real to keep
it. It has one now.
output_scale_set is the other half. Every output the backends make comes up
at the same scale, so a mixed arrangement - the case where a surface's size
depends on which screen it is on - cannot otherwise be built at all, on any
backend. With it, the new test moves a surface between two differently
scaled outputs and asserts the size follows; without it, the resize bug that
found is not reachable from a test.
Both new requests and the new event are appended at the end of the interface
rather than filed next to the ones they belong with. Opcodes are positional:
inserting in the middle renumbers everything after it and silently rebinds
the listener slots of every client already written against this file.
The two accessors on E's side are there because E_Comp_Client_Data is an
incomplete type to a module, and the module's own header says to keep its
reach into E's internals small.
test_output's scale expectation loses its E-00 caveat. It still expects 1,
but now because 1 is the default rather than because the value meant
nothing.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/modules/wl_test/e_mod_main.c | 59 ++++++-
src/protocol/wl-test.xml | 60 ++++++-
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_buffer_scale.c | 306 ++++++++++++++++++++++++++++++++++
src/tests/wayland/test_output.c | 14 +-
5 files changed, 426 insertions(+), 14 deletions(-)
diff --git a/src/modules/wl_test/e_mod_main.c b/src/modules/wl_test/e_mod_main.c
index d0bab7168..790e04f04 100644
--- a/src/modules/wl_test/e_mod_main.c
+++ b/src/modules/wl_test/e_mod_main.c
@@ -86,6 +86,24 @@ _wl_test_cb_get_surface_info(struct wl_client *client EINA_UNUSED, struct wl_res
(e_client_focused_get() == ec) ? 1 : 0);
}
+static void
+_wl_test_cb_get_surface_buffer_info(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *surface)
+{
+ E_Client *ec;
+
+ ec = _wl_test_ec_get(surface);
+ if (!ec)
+ {
+ wl_test_send_surface_unknown(resource, surface);
+ return;
+ }
+
+ wl_test_send_surface_buffer_info(resource, surface,
+ e_comp_wl_client_buffer_scale_get(ec),
+ e_comp_wl_client_buffer_transform_get(ec),
+ e_comp_wl_client_scale_get(ec));
+}
+
static void
_wl_test_cb_move_surface(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, struct wl_resource *surface, int32_t x, int32_t y)
{
@@ -124,6 +142,42 @@ _wl_test_cb_zone_add(struct wl_client *client EINA_UNUSED, struct wl_resource *r
ERR("wl_test: could not create wl_output for zone %d", num);
}
+static void
+_wl_test_cb_output_scale_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, uint32_t index, int32_t scale)
+{
+ E_Comp_Wl_Output *output;
+ struct wl_resource *res;
+ Eina_List *l;
+
+ if (scale < 1)
+ {
+ ERR("wl_test: output scale must be at least 1, got %d", scale);
+ return;
+ }
+
+ output = eina_list_nth(e_comp_wl->outputs, index);
+ if (!output)
+ {
+ ERR("wl_test: no output %u to scale", index);
+ return;
+ }
+
+ output->scale = scale;
+
+ /* Tell whoever is already bound, the way a real scale change would. A
+ * client that never asked about outputs is unaffected, which is correct -
+ * the compositor's own idea of the scale has changed either way, and that
+ * is what surface sizing reads. */
+ EINA_LIST_FOREACH(output->resources, l, res)
+ {
+ if (wl_resource_get_version(res) < WL_OUTPUT_SCALE_SINCE_VERSION)
+ continue;
+ wl_output_send_scale(res, scale);
+ if (wl_resource_get_version(res) >= WL_OUTPUT_DONE_SINCE_VERSION)
+ wl_output_send_done(res);
+ }
+}
+
/* Where the pointer is, asked of E rather than remembered here. A module-side
* tally is only right while nothing but this module moves the pointer, and a
* constraint does: once one has held the pointer at an edge, E's answer and
@@ -311,6 +365,9 @@ static const struct wl_test_interface _wl_test_implementation =
_wl_test_cb_touch_up,
_wl_test_cb_key,
_wl_test_cb_sync,
+ /* version 3, appended in the order the protocol declares them */
+ _wl_test_cb_get_surface_buffer_info,
+ _wl_test_cb_output_scale_set,
};
static void
@@ -350,7 +407,7 @@ e_modapi_init(E_Module *m)
wl_display_add_client_created_listener(e_comp_wl->wl.disp,
&_client_created_listener);
- _wl_test_global = wl_global_create(e_comp_wl->wl.disp, &wl_test_interface, 2,
+ _wl_test_global = wl_global_create(e_comp_wl->wl.disp, &wl_test_interface, 3,
NULL, _wl_test_cb_bind);
if (!_wl_test_global)
{
diff --git a/src/protocol/wl-test.xml b/src/protocol/wl-test.xml
index e40d5b193..36c52206b 100644
--- a/src/protocol/wl-test.xml
+++ b/src/protocol/wl-test.xml
@@ -23,7 +23,7 @@
DEALINGS IN THE SOFTWARE.
</copyright>
- <interface name="wl_test" version="2">
+ <interface name="wl_test" version="3">
<description summary="private interface for the compositor test suite">
A back door into the compositor for its own test suite. It exposes
internal state and synthesises input, so it is deliberately NOT built
@@ -92,11 +92,6 @@
<arg name="focused" type="uint" summary="non-zero if it holds the focus"/>
</event>
- <!-- Deliberately no buffer_scale here. set_buffer_scale is an empty stub
- and E stores nothing, so the module would have to invent a value -
- which is worse than no value, because a test asserting on it would
- be asserting on the module rather than on the compositor. Add it
- when E-17 gives it somewhere real to come from. -->
<event name="surface_unknown">
<description summary="the surface has no E_Client"/>
@@ -218,5 +213,58 @@
<event name="sync_done">
<description summary="reply to sync"/>
</event>
+
+ <!-- Version 3 additions. Appended rather than filed next to the requests
+ they belong with: opcodes are positional, so inserting anything in
+ the middle renumbers everything after it and silently rebinds the
+ listener slots of every client already written against this file. -->
+
+ <request name="get_surface_buffer_info" since="3">
+ <description summary="ask what scale and transform a surface has in effect">
+ set_buffer_scale and set_buffer_transform are write-only on the wire.
+ A client can send them and has no way to read back what the compositor
+ latched, so nothing short of reading pixels off a screen can tell
+ "honoured" from "parsed and thrown away" - and reading pixels needs a
+ real display, which the headless backend this suite runs on has not
+ got.
+
+ Answered with surface_buffer_info, or surface_unknown when there is no
+ E_Client behind the surface.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <event name="surface_buffer_info" since="3">
+ <description summary="reply to get_surface_buffer_info">
+ buffer_scale and buffer_transform are the values in effect, i.e. as of
+ the last commit rather than as last requested.
+
+ output_scale is the scale the compositor resolved for the surface's
+ zone, which is what it multiplies surface-local coordinates by to get
+ screen ones. It is reported alongside because the two scales together
+ decide how big the surface ends up, and because a surface moved onto a
+ differently scaled output has to see it change - which is the only way
+ a test can observe that happening at all.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="buffer_scale" type="int"/>
+ <arg name="buffer_transform" type="int"/>
+ <arg name="output_scale" type="int"/>
+ </event>
+
+ <request name="output_scale_set" since="3">
+ <description summary="change one output's scale at runtime">
+ Every output the backends make comes up at the same scale, so a mixed
+ arrangement - the case where a surface's size depends on which screen
+ it is on - cannot otherwise be built, on any backend, headless or not.
+
+ Sends wl_output.scale and wl_output.done to everyone bound to that
+ output, exactly as a real scale change would. It does not resize
+ anything already on screen: what surfaces do about that is the
+ behaviour under test.
+ </description>
+ <arg name="index" type="uint" summary="0-based, in wl_output creation order"/>
+ <arg name="scale" type="int"/>
+ </request>
</interface>
</protocol>
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 24009633b..5a1700eee 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -54,6 +54,7 @@ wl_protocol_tests = [
['surface-unmap', 'test_surface_unmap.c'],
['output', 'test_output.c'],
['activation', 'test_activation.c'],
+ ['buffer-scale', 'test_buffer_scale.c'],
]
foreach t: wl_protocol_tests
diff --git a/src/tests/wayland/test_buffer_scale.c b/src/tests/wayland/test_buffer_scale.c
new file mode 100644
index 000000000..362a8a4d6
--- /dev/null
+++ b/src/tests/wayland/test_buffer_scale.c
@@ -0,0 +1,306 @@
+/* wl_surface.set_buffer_scale and set_buffer_transform (E-17).
+ *
+ * Both requests are write-only on the wire: a client can send them and never
+ * learn what the compositor did with them. That is exactly the shape of bug
+ * this suite exists for - E used to parse both and throw the values away, and
+ * no black-box protocol test could tell the difference. So the assertions
+ * here come in pairs: what E says it latched, via the private test protocol,
+ * and what E did about it, via the surface geometry it reports.
+ *
+ * The last section is the case a single-headed backend cannot otherwise
+ * reach: two outputs at different scales, and a surface moved from one to the
+ * other. A surface's size on screen depends on the scale of the output it is
+ * on, so the same buffer has to come out a different size on each.
+ */
+#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 FAIL(fmt, ...) \
+ do { fprintf(stderr, "test-buffer-scale: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+static struct wl_display *dpy;
+static struct wl_compositor *compositor;
+static struct wl_shm *shm;
+static struct xdg_wm_base *wm_base;
+static struct wl_test *tester;
+static uint32_t tester_version;
+
+static int sync_done;
+
+static int info_valid, info_unknown;
+static int32_t info_x, info_y, info_w, info_h;
+
+static int binfo_valid, binfo_unknown;
+static int32_t binfo_scale, binfo_transform, binfo_output_scale;
+
+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
+_test_client_serial(void *d, struct wl_test *t, uint32_t serial)
+{ (void)d; (void)t; (void)serial; }
+
+static void
+_test_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 visible, uint32_t focused)
+{
+ (void)d; (void)t; (void)s; (void)visible; (void)focused;
+ info_valid = 1;
+ info_x = x; info_y = y; info_w = w; info_h = h;
+}
+
+static void
+_test_surface_buffer_info(void *d, struct wl_test *t, struct wl_surface *s,
+ int32_t scale, int32_t transform, int32_t output_scale)
+{
+ (void)d; (void)t; (void)s;
+ binfo_valid = 1;
+ binfo_scale = scale;
+ binfo_transform = transform;
+ binfo_output_scale = output_scale;
+}
+
+static void
+_test_surface_unknown(void *d, struct wl_test *t, struct wl_surface *s)
+{
+ (void)d; (void)t; (void)s;
+ info_unknown = 1;
+ binfo_unknown = 1;
+}
+
+static void
+_test_sync_done(void *d, struct wl_test *t)
+{ (void)d; (void)t; sync_done = 1; }
+
+static const struct wl_test_listener test_listener = {
+ _test_client_serial,
+ _test_surface_info,
+ _test_surface_unknown,
+ _test_sync_done,
+ _test_surface_buffer_info, /* version 3, so last */
+};
+
+static void
+_reg_global(void *d, struct wl_registry *reg, uint32_t name, const char *iface, uint32_t ver)
+{
+ (void)d;
+ if (!strcmp(iface, "wl_compositor"))
+ compositor = wl_registry_bind(reg, name, &wl_compositor_interface, ver < 4 ? ver : 4);
+ else if (!strcmp(iface, "wl_shm"))
+ shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
+ else if (!strcmp(iface, "xdg_wm_base"))
+ {
+ wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(wm_base, &wm_listener, NULL);
+ }
+ else if (!strcmp(iface, "wl_test"))
+ {
+ tester_version = ver;
+ tester = wl_registry_bind(reg, name, &wl_test_interface, ver < 3 ? ver : 3);
+ wl_test_add_listener(tester, &test_listener, NULL);
+ }
+}
+static void _reg_remove(void *d, struct wl_registry *r, uint32_t n) { (void)d; (void)r; (void)n; }
+static const struct wl_registry_listener reg_listener = { _reg_global, _reg_remove };
+
+/* One trip through the compositor's own main loop, so anything E does from a
+ * job or an idler has happened before the next question is asked. */
+static void
+csync(void)
+{
+ sync_done = 0;
+ wl_test_sync(tester);
+ while (!sync_done) wl_display_dispatch(dpy);
+}
+
+static void
+ask_info(struct wl_surface *s)
+{
+ info_valid = info_unknown = 0;
+ wl_test_get_surface_info(tester, s);
+ wl_display_roundtrip(dpy);
+}
+
+static void
+ask_binfo(struct wl_surface *s)
+{
+ binfo_valid = binfo_unknown = 0;
+ wl_test_get_surface_buffer_info(tester, s);
+ wl_display_roundtrip(dpy);
+}
+
+static struct wl_buffer *
+make_buffer(int w, int h)
+{
+ struct wl_shm_pool *pool;
+ struct wl_buffer *buf;
+ int fd, size = w * h * 4;
+ uint32_t *px;
+
+ fd = memfd_create("bs", 0);
+ if (fd < 0) return NULL;
+ if (ftruncate(fd, size) < 0) return NULL;
+ px = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (px == MAP_FAILED) return NULL;
+ for (int i = 0; i < w * h; i++) px[i] = 0xff804020;
+
+ pool = wl_shm_create_pool(shm, fd, size);
+ buf = wl_shm_pool_create_buffer(pool, 0, w, h, w * 4, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(pool);
+ close(fd);
+ return buf;
+}
+
+static int configured;
+static void
+_surf_configure(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 surf_listener = { _surf_configure };
+
+static void
+_top_configure(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_configure, _top_close };
+
+/* A mapped toplevel showing one buffer, with the scale and transform applied
+ * before the first attach so that the very first commit is the one under
+ * test. */
+static struct wl_surface *
+map_surface(int bw, int bh, int scale, int transform)
+{
+ struct wl_surface *surface;
+ struct xdg_surface *xdgsurf;
+ struct xdg_toplevel *toplevel;
+ struct wl_buffer *buf;
+
+ surface = wl_compositor_create_surface(compositor);
+ xdgsurf = xdg_wm_base_get_xdg_surface(wm_base, surface);
+ xdg_surface_add_listener(xdgsurf, &surf_listener, NULL);
+ toplevel = xdg_surface_get_toplevel(xdgsurf);
+ xdg_toplevel_add_listener(toplevel, &top_listener, NULL);
+
+ configured = 0;
+ wl_surface_commit(surface);
+ while (!configured) wl_display_dispatch(dpy);
+
+ buf = make_buffer(bw, bh);
+ if (!buf) return NULL;
+
+ if (scale > 0) wl_surface_set_buffer_scale(surface, scale);
+ if (transform >= 0) wl_surface_set_buffer_transform(surface, transform);
+ wl_surface_attach(surface, buf, 0, 0);
+ wl_surface_damage_buffer(surface, 0, 0, bw, bh);
+ wl_surface_commit(surface);
+ csync();
+ return surface;
+}
+
+int
+main(void)
+{
+ struct wl_registry *reg;
+ struct wl_surface *a, *b;
+
+ dpy = wl_display_connect(NULL);
+ if (!dpy) FAIL("could not connect to the compositor");
+
+ reg = wl_display_get_registry(dpy);
+ wl_registry_add_listener(reg, ®_listener, NULL);
+ wl_display_roundtrip(dpy);
+
+ if (!compositor || !shm || !wm_base) FAIL("missing core globals");
+ if (!tester) FAIL("wl_test is not advertised; is the test module loaded?");
+ if (tester_version < 3)
+ FAIL("wl_test is version %u, need 3 for the buffer info", tester_version);
+
+ /* --- set_buffer_scale ------------------------------------------------ */
+
+ a = map_surface(200, 200, 2, -1);
+ if (!a) FAIL("could not map the scaled surface");
+
+ ask_binfo(a);
+ if (binfo_unknown) FAIL("no E_Client behind the scaled surface");
+ if (!binfo_valid) FAIL("no surface_buffer_info for the scaled surface");
+ if (binfo_scale != 2)
+ FAIL("set_buffer_scale(2) latched as %d", binfo_scale);
+ if (binfo_output_scale != 1)
+ FAIL("expected the first output at scale 1, got %d", binfo_output_scale);
+
+ /* A 200x200 buffer at scale 2 is a 100x100 surface, and on a scale 1
+ * output that is 100x100 on screen. Dropping the buffer scale would leave
+ * it 200x200 - which is what E did before E-17. */
+ ask_info(a);
+ if (!info_valid) FAIL("no surface_info for the scaled surface");
+ if ((info_w != 100) || (info_h != 100))
+ FAIL("200x200 buffer at scale 2 came out %dx%d, expected 100x100",
+ info_w, info_h);
+
+ /* --- set_buffer_transform -------------------------------------------- */
+
+ b = map_surface(200, 100, 1, WL_OUTPUT_TRANSFORM_90);
+ if (!b) FAIL("could not map the transformed surface");
+
+ ask_binfo(b);
+ if (!binfo_valid) FAIL("no surface_buffer_info for the transformed surface");
+ if (binfo_transform != WL_OUTPUT_TRANSFORM_90)
+ FAIL("set_buffer_transform(90) latched as %d", binfo_transform);
+
+ /* A quarter turn exchanges the axes: the surface is the buffer on its
+ * side. */
+ ask_info(b);
+ if (!info_valid) FAIL("no surface_info for the transformed surface");
+ if ((info_w != 100) || (info_h != 200))
+ FAIL("200x100 buffer at transform 90 came out %dx%d, expected 100x200",
+ info_w, info_h);
+
+ /* --- two outputs at different scales ---------------------------------- */
+
+ wl_test_zone_add(tester, 1024, 0, 800, 600);
+ csync();
+ wl_test_output_scale_set(tester, 1, 2);
+ csync();
+
+ /* Still on the first output, so nothing has changed yet. */
+ ask_binfo(b);
+ if (!binfo_valid) FAIL("no surface_buffer_info after adding the output");
+ if (binfo_output_scale != 1)
+ FAIL("scaling the second output changed the first surface's scale to %d",
+ binfo_output_scale);
+
+ wl_test_move_surface(tester, b, 1200, 100);
+ csync();
+
+ ask_binfo(b);
+ if (!binfo_valid) FAIL("no surface_buffer_info after the move");
+ if (binfo_output_scale != 2)
+ FAIL("moved onto the scale 2 output, but E still resolves scale %d",
+ binfo_output_scale);
+
+ /* The same buffer on a twice-scaled output is twice the size on screen.
+ * The surface is 100x200 surface-local after its transform, so 200x400. */
+ wl_surface_damage_buffer(b, 0, 0, 200, 100);
+ wl_surface_commit(b);
+ csync();
+
+ ask_info(b);
+ if (!info_valid) FAIL("no surface_info after the move");
+ if ((info_w != 200) || (info_h != 400))
+ FAIL("on the scale 2 output the surface is %dx%d, expected 200x400",
+ info_w, info_h);
+
+ printf("test-buffer-scale: ok\n");
+ return 0;
+}
diff --git a/src/tests/wayland/test_output.c b/src/tests/wayland/test_output.c
index b90691c7b..8c54a949a 100644
--- a/src/tests/wayland/test_output.c
+++ b/src/tests/wayland/test_output.c
@@ -7,8 +7,7 @@
* - 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;
- * - scale is 1 while set_buffer_scale is still a stub (E-00). When E-17
- * lands and E-00 is reverted, this expectation goes with it.
+ * - scale is the default of 1, rather than E's own widget magnification.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -180,12 +179,13 @@ main(void)
FAIL("scale arrived after done #%d, must be in the initial burst",
out.scale_at_done);
- /* E-00: set_buffer_scale is still a stub, so claiming anything but 1 makes
- * clients hand us buffers we then paint at the wrong size. */
+ /* The scale now means what wl_output says it means - E honours
+ * set_buffer_scale, and E_WL_OUTPUT_SCALE is what changes this. 1 is the
+ * default, and it is what an unconfigured compositor must advertise: it
+ * used to advertise e_scale, which is the magnification factor for E's own
+ * widgets and has nothing to do with what clients should draw. */
if (out.scale != 1)
- FAIL("advertised scale %d, expected 1 while set_buffer_scale is a stub "
- "(E-00); if E-17 has landed, this check should have been removed "
- "with it", out.scale);
+ FAIL("advertised scale %d, expected the default of 1", out.scale);
/* v3 release. If this is not implemented the compositor kills the client
* for an unknown opcode, so the roundtrip below is the assertion. */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.