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 7dfa0e5669671f8d1ddc9c325f453e89d7b37328
Author: Cedric BAIL <[email protected]>
AuthorDate: Mon Aug 17 21:12:52 2026 -0600
tests - a window with a shadow must come back the size it was
Every other client in this directory has a surface exactly as big as its
window, and for those "restore it to the size it was" has one possible reading.
A client that draws its own shadow has two, and E picks the wrong one.
So this test is a client with a 16px shadow - the number Chromium and GTK
actually use - declared the way every CSD toolkit declares it, with
set_window_geometry naming a 400x300 window inside a 432x332 surface. It
maximizes and restores twice, once as the client
(xdg_toplevel.set_maximized / unset_maximized, handled in xdg.c out of
ec->saved.w/h) and once as the compositor (E's own window_maximized_toggle,
which goes through the generic configure path and never touches those
handlers). They are different code and only one of them was ever exercised.
Both the configure E sends and the frame E then reports are asserted, because
they are separate claims: E can send the right configure and record the wrong
frame, and a test that checks one leaves the other free to drift.
Marked xfail, and it fails on the first of the two: E restores a 400x300 window
to 432x332, which is exactly the surface. ec->saved.w/h comes from
ec->client.w/h in e_client.c, and for this client that is the surface rather
than the geometry. The fix belongs in code shared with the X11 compositor,
where _GTK_FRAME_EXTENTS makes the same distinction, so it waits on X11
coverage instead of going in on one backend's evidence.
Until now this was reachable only by running a browser: a 40-second test that
needs Chromium installed, reporting as a geometry mismatch. It is now two
seconds and names the surface size in the failure.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/tests/wayland/e_wl_testkit.c | 37 +++++++-
src/tests/wayland/e_wl_testkit.h | 18 ++++
src/tests/wayland/meson.build | 6 ++
src/tests/wayland/test_maximize_restore.c | 138 ++++++++++++++++++++++++++++++
4 files changed, 195 insertions(+), 4 deletions(-)
diff --git a/src/tests/wayland/e_wl_testkit.c b/src/tests/wayland/e_wl_testkit.c
index 31c31ca25..c2dbe4843 100644
--- a/src/tests/wayland/e_wl_testkit.c
+++ b/src/tests/wayland/e_wl_testkit.c
@@ -18,7 +18,8 @@ struct _Tk_Toplevel
struct wl_surface *surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *toplevel;
- int w, h; /* the size we last painted */
+ int w, h; /* the window geometry we last painted */
+ int shadow; /* inset on every side; 0 for a plain surface */
int pending_w, pending_h; /* what the last configure asked for */
int configures;
};
@@ -163,6 +164,7 @@ _xdg_surface_configure(void *data, struct xdg_surface *s, uint32_t serial)
{
Tk_Toplevel *top = data;
struct wl_buffer *buffer;
+ int sw, sh;
xdg_surface_ack_configure(s, serial);
@@ -170,11 +172,22 @@ _xdg_surface_configure(void *data, struct xdg_surface *s, uint32_t serial)
if (top->pending_w > 0) top->w = top->pending_w;
if (top->pending_h > 0) top->h = top->pending_h;
- buffer = _buffer_make(top->tk, top->w, top->h);
- if (!buffer) tk_fail(top->tk, "cannot make a %dx%d buffer", top->w, top->h);
+ /* A client with a shadow paints a surface bigger than its window geometry
+ * on every side, and says so. That is what every CSD toolkit does, and it
+ * is the case where "the size" is ambiguous unless you say which one you
+ * mean - so it is the case worth testing. */
+ sw = top->w + (2 * top->shadow);
+ sh = top->h + (2 * top->shadow);
+
+ buffer = _buffer_make(top->tk, sw, sh);
+ if (!buffer) tk_fail(top->tk, "cannot make a %dx%d buffer", sw, sh);
+
+ if (top->shadow)
+ xdg_surface_set_window_geometry(top->xdg_surface, top->shadow, top->shadow,
+ top->w, top->h);
wl_surface_attach(top->surface, buffer, 0, 0);
- wl_surface_damage(top->surface, 0, 0, top->w, top->h);
+ wl_surface_damage(top->surface, 0, 0, sw, sh);
wl_surface_commit(top->surface);
top->configures++;
@@ -377,6 +390,21 @@ tk_fail(Tk *tk, const char *fmt, ...)
Tk_Toplevel *
tk_toplevel_new(Tk *tk, const char *app_id, const char *title, int w, int h)
+{
+ return tk_toplevel_new_shadowed(tk, app_id, title, w, h, 0);
+}
+
+void
+tk_toplevel_maximize(Tk_Toplevel *top, int on)
+{
+ if (on) xdg_toplevel_set_maximized(top->toplevel);
+ else xdg_toplevel_unset_maximized(top->toplevel);
+ wl_surface_commit(top->surface);
+ tk_settle(top->tk);
+}
+
+Tk_Toplevel *
+tk_toplevel_new_shadowed(Tk *tk, const char *app_id, const char *title, int w, int h, int shadow)
{
Tk_Toplevel *top;
@@ -385,6 +413,7 @@ tk_toplevel_new(Tk *tk, const char *app_id, const char *title, int w, int h)
top->tk = tk;
top->w = w;
top->h = h;
+ top->shadow = shadow;
top->surface = wl_compositor_create_surface(tk->compositor);
top->xdg_surface = xdg_wm_base_get_xdg_surface(tk->wm_base, top->surface);
diff --git a/src/tests/wayland/e_wl_testkit.h b/src/tests/wayland/e_wl_testkit.h
index 1d494584b..b353dc138 100644
--- a/src/tests/wayland/e_wl_testkit.h
+++ b/src/tests/wayland/e_wl_testkit.h
@@ -73,6 +73,24 @@ Tk_Toplevel *tk_toplevel_new(Tk *tk, const char *app_id, const char *title,
* owns it and destroys it with the connection. */
struct wl_surface *tk_toplevel_surface(Tk_Toplevel *top);
+/* A toplevel whose surface is `shadow` pixels larger than its window geometry
+ * on every side, declared with xdg_surface.set_window_geometry - what every
+ * client that draws its own shadow looks like on the wire.
+ *
+ * Worth having as its own constructor because it is the only shape in which
+ * "the window's size" is ambiguous: the surface, or the geometry. A compositor
+ * that confuses the two behaves perfectly against every other client in this
+ * directory, all of which have surface == geometry, and then hands a browser
+ * the wrong number. */
+Tk_Toplevel *tk_toplevel_new_shadowed(Tk *tk, const char *app_id,
+ const char *title, int w, int h,
+ int shadow);
+
+/* Ask to be maximized or restored, as the client rather than as the user.
+ * xdg_toplevel.set_maximized / unset_maximized: a different code path in E
+ * from its own maximize action, and the one no test covered. */
+void tk_toplevel_maximize(Tk_Toplevel *top, int on);
+
/* The size the compositor last asked this toplevel to be, and the number of
* configures it has answered. What E asked for and what E then reports as the
* frame are different questions, and when they disagree only one of them is
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 903550528..91fadd779 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -80,6 +80,12 @@ wl_protocol_tests = [
['client-action', 'test_client_action.c'],
['client-state', 'test_client_state.c'],
['client-move', 'test_client_move.c'],
+ # xfail until #44: xdg_toplevel.unset_maximized answers out of ec->saved.w/h,
+ # and e_client.c fills those from ec->client.w/h - the surface size, which for
+ # a client with a shadow is 2*inset larger than the window geometry in each
+ # axis. Measured: restores a 400x300 window to 432x332. The fix is in code
+ # shared with X11, so it waits on X11 coverage rather than going in blind.
+ ['maximize-restore', 'test_maximize_restore.c', 'xfail'],
]
# Shared plumbing: registry binding, toplevel construction, enumeration and a
diff --git a/src/tests/wayland/test_maximize_restore.c b/src/tests/wayland/test_maximize_restore.c
new file mode 100644
index 000000000..849d2051a
--- /dev/null
+++ b/src/tests/wayland/test_maximize_restore.c
@@ -0,0 +1,138 @@
+/* Does a window come back to the size it was, after being maximized?
+ *
+ * Trivially yes for a client whose surface is its window - and every other
+ * test in this directory is one of those, which is why this went unnoticed for
+ * as long as it did. The case that matters is a client that draws its own
+ * shadow: its surface is larger than its window geometry, "the size" means two
+ * different numbers, and a compositor that saves the wrong one restores the
+ * wrong one.
+ *
+ * Both ways of asking are covered, because they are different code in E:
+ *
+ * * the client asks - xdg_toplevel.set_maximized / unset_maximized, handled
+ * in xdg.c, which answers out of ec->saved.w/h;
+ * * the compositor decides - E's own window_maximized_toggle action, which
+ * goes through the generic configure path instead and never touches those
+ * handlers.
+ *
+ * A browser exercises the first and the browser test exercises the second, so
+ * between them they cover both - but only by accident, only for as long as
+ * someone has Chromium installed, and the failure arrives as a geometry
+ * mismatch in a 40-second test that needed a browser to run at all. This asks
+ * the same question in two seconds with no browser and no ambiguity.
+ *
+ * What it asserts is the restored *window geometry*, twice over: the size E
+ * configures the client back to, and the size E then reports for the window.
+ * Those are separate claims - E can send the right configure and record the
+ * wrong frame, or the reverse - and a test that checks only one of them leaves
+ * the other free to drift.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "e_wl_testkit.h"
+
+#define PROG "test-maximize-restore"
+#define APP_ID "maximize-restore"
+
+/* 16 a side, which is what Chromium and GTK actually use, so the arithmetic in
+ * a failure message lines up with the arithmetic in a bug report. */
+#define SHADOW 16
+#define GEOM_W 400
+#define GEOM_H 300
+
+#define MAXIMIZED (WL_TEST_CLIENT_STATE_MAXIMIZED_H | \
+ WL_TEST_CLIENT_STATE_MAXIMIZED_V)
+
+#define SETTLE_MS 5000
+
+static void
+_check_restored(Tk *tk, Tk_Toplevel *top, const char *who)
+{
+ Tk_Client *c;
+ int cw, ch;
+
+ tk_toplevel_configured(top, &cw, &ch, NULL);
+ if ((cw != GEOM_W) || (ch != GEOM_H))
+ tk_fail(tk, "%s: restored to a configure of %dx%d, but the window "
+ "geometry before maximizing was %dx%d. The surface is "
+ "%dx%d - if the difference is %d in each axis, the saved "
+ "size is the surface rather than the geometry",
+ who, cw, ch, GEOM_W, GEOM_H,
+ GEOM_W + (2 * SHADOW), GEOM_H + (2 * SHADOW), 2 * SHADOW);
+
+ c = tk_expect(tk, APP_ID);
+ if ((c->w != GEOM_W) || (c->h != GEOM_H))
+ tk_fail(tk, "%s: E configured %dx%d correctly but then reports the "
+ "window as %dx%d", who, cw, ch, c->w, c->h);
+
+ printf(PROG ": %s - restored to %dx%d\n", who, c->w, c->h);
+}
+
+int
+main(void)
+{
+ Tk *tk;
+ Tk_Toplevel *top;
+ Tk_Client *c;
+ unsigned int id;
+ int mw, mh;
+
+ tk = tk_connect(PROG);
+
+ top = tk_toplevel_new_shadowed(tk, APP_ID, "maximize restore",
+ GEOM_W, GEOM_H, SHADOW);
+ tk_sync(tk);
+
+ /* Before anything else: E has to be reporting the geometry, not the
+ * surface. If it is not, every later number in this test is measured
+ * against the wrong baseline and the failures would be misleading. */
+ c = tk_expect(tk, APP_ID);
+ id = c->id;
+ if ((c->w != GEOM_W) || (c->h != GEOM_H))
+ tk_fail(tk, "a window with geometry %dx%d inside a %dx%d surface is "
+ "reported as %dx%d - E is using the surface size, so "
+ "set_window_geometry is not being honoured at all",
+ GEOM_W, GEOM_H, GEOM_W + (2 * SHADOW), GEOM_H + (2 * SHADOW),
+ c->w, c->h);
+ printf(PROG ": mapped, geometry %dx%d inside a %dx%d surface\n",
+ c->w, c->h, GEOM_W + (2 * SHADOW), GEOM_H + (2 * SHADOW));
+
+ /* ---------------------------------------------- the client asks */
+
+ tk_toplevel_maximize(top, 1);
+ c = tk_wait_state(tk, APP_ID, MAXIMIZED, MAXIMIZED, SETTLE_MS,
+ "the client's own set_maximized to take effect");
+ mw = c->w; mh = c->h;
+ if ((mw <= GEOM_W) || (mh <= GEOM_H))
+ tk_fail(tk, "set_maximized left the window at %dx%d, no bigger than the "
+ "%dx%d it started at", mw, mh, GEOM_W, GEOM_H);
+
+ tk_toplevel_maximize(top, 0);
+ tk_wait_state(tk, APP_ID, MAXIMIZED, 0, SETTLE_MS,
+ "the client's own unset_maximized to take effect");
+ _check_restored(tk, top, "client-initiated");
+
+ /* ------------------------------------------ the compositor decides */
+
+ /* Same question through E's own action, which is what a user clicking a
+ * titlebar button or pressing a binding gets. Different code, and it was
+ * the half that was broken first. */
+ tk_action(tk, id, "window_maximized_toggle", NULL);
+ c = tk_wait_state(tk, APP_ID, MAXIMIZED, MAXIMIZED, SETTLE_MS,
+ "E's own maximize action to take effect");
+ if ((c->w != mw) || (c->h != mh))
+ tk_fail(tk, "E's action maximized to %dx%d where the client's own request "
+ "gave %dx%d - the two paths disagree about how big maximized "
+ "is", c->w, c->h, mw, mh);
+
+ tk_action(tk, id, "window_maximized_toggle", NULL);
+ tk_wait_state(tk, APP_ID, MAXIMIZED, 0, SETTLE_MS,
+ "E's own maximize action to restore");
+ _check_restored(tk, top, "compositor-initiated");
+
+ printf(PROG ": ok\n");
+ tk_disconnect(tk);
+ return 0;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.