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 9b190de48e07157e5efebedf6bba0b04359c9495
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 16 22:32:03 2026 -0600

    e_comp_object - a window with no visibility effect animates forever
    
    Minimising a window did nothing. E set iconic, the window stayed on screen, and
    it stayed there - not for a few frames while an effect ran, but permanently:
    the test waits five seconds and the window is still being painted.
    
    _e_comp_object_effect_visibility_start opens the animation bracket itself, and
    only when it has an effect to run:
    
        if ((!cw->visibility_effect) || (!e_comp_object_effect_allowed_get(...)))
          return EINA_TRUE;
        if (!cw->effect_running) _e_comp_object_animating_begin(cw);
    
    Both callers opened one as well, immediately before calling it. When there is
    no visibility effect the function returns straight away having started nothing,
    so _e_comp_object_done_defer never fires and the bracket the caller opened is
    never closed. cw->animating stays 1 for the life of the window.
    
    The show path even has a guard for exactly this - "ensure some random effect
    doesn't lock the client offscreen", testing !cw->animating - and it could never
    fire, because the animating it tests had been set two lines above it.
    
    A window stuck animating defers every hide it is ever asked to do. Traced on
    iconify:
    
        show_helper: iconic=0 visible=0 animating=0 defer=0
        iconify
        smart_hide: vis=1 animating=1 showing=1 iconic=1 viseffect=(nil)
        smart_hide end: animating=1 defer=1        <- deferred
        ...nothing ever closes the bracket, so nothing retries...
    
    The deferred hide finally ran at teardown, which is why this looks like "the
    window never hides" rather than "the window hides late".
    
    So let the effect starter own the bracket on both paths. When there is no
    effect nothing is opened, the guard below works as written, and the hide
    happens immediately.
    
    Reached through iconify here, but it is not an iconify bug: every window shown
    without a visibility effect is left permanently animating, and every later hide
    of it is deferred. The wltest profile has no effects, and neither does a
    compositor built without them.
    
    test_client_state.c asserts the fullscreen and iconify round trips and is what
    found this. Fullscreen passed from the start; iconify is what failed. Both pass
    now, along with wl-globals, the other nine protocol tests and e_wlcs_driver,
    and Firefox against a nested E is unaffected.
    
    The kit grows tk_wait_state for it: some of what E does to a window is animated,
    so a state is reached a few frames after the request. It polls a predicate to a
    deadline through E's own main loop rather than sleeping, and on timeout says
    what it was waiting for and dumps the window list.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 src/bin/e_comp_object.c               | 20 ++++++--
 src/tests/wayland/e_wl_testkit.c      | 39 ++++++++++++++
 src/tests/wayland/e_wl_testkit.h      | 16 ++++++
 src/tests/wayland/meson.build         |  1 +
 src/tests/wayland/test_client_state.c | 97 +++++++++++++++++++++++++++++++++++
 5 files changed, 170 insertions(+), 3 deletions(-)

diff --git a/src/bin/e_comp_object.c b/src/bin/e_comp_object.c
index 0127e17e2..dd7aa0111 100644
--- a/src/bin/e_comp_object.c
+++ b/src/bin/e_comp_object.c
@@ -1709,8 +1709,8 @@ _e_comp_intercept_hide(void *data, Evas_Object *obj)
              if ((!cw->ec->iconic) || (cw->ec->iconic && (!cw->animating)))
                {
                   e_comp_object_signal_emit(obj, "e,state,hidden", "e");
-                  if (!cw->showing)
-                    _e_comp_object_animating_begin(cw);
+                  /* Same as the show path above: let the effect starter open
+                   * the bracket, and only when there is an effect. */
                   if (!_e_comp_object_effect_visibility_start(cw, 0)) return;
                }
              evas_object_smart_callback_call(obj, "hiding", cw->ec);
@@ -2513,7 +2513,21 @@ _e_comp_smart_show(Evas_Object *obj)
      {
         /* if cw->showing set, client was ec->hidden during show animation */
         e_comp_object_signal_emit(cw->smart_obj, "e,state,visible", "e");
-        _e_comp_object_animating_begin(cw);
+        /* Opening the animation bracket is
+         * _e_comp_object_effect_visibility_start's job, and it only does it
+         * when it has an effect to run. Opening it here as well opened one
+         * that nothing would ever close: with no visibility effect, or with
+         * effects not allowed, that function returns straight away having
+         * started nothing, so _e_comp_object_done_defer never fires and
+         * cw->animating stays set for the life of the window. The "ensure some
+         * random effect doesn't lock the client offscreen" guard just below is
+         * meant to catch exactly that and could not, because the animating it
+         * tests had been set two lines earlier.
+         *
+         * A window left permanently animating defers every hide it is ever
+         * asked to do, which is why iconifying one did nothing visible: the
+         * hide was deferred against a show animation that had finished long
+         * ago and never would. */
         cw->showing = 1;
         if (!_e_comp_object_effect_visibility_start(cw, 1)) return;
      }
diff --git a/src/tests/wayland/e_wl_testkit.c b/src/tests/wayland/e_wl_testkit.c
index 2132bd458..e1ee07c15 100644
--- a/src/tests/wayland/e_wl_testkit.c
+++ b/src/tests/wayland/e_wl_testkit.c
@@ -8,6 +8,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/mman.h>
+#include <time.h>
 
 #include "e_wl_testkit.h"
 
@@ -463,6 +464,44 @@ tk_expect(Tk *tk, const char *app_id)
    return c;
 }
 
+static double
+_now_ms(void)
+{
+   struct timespec ts;
+
+   clock_gettime(CLOCK_MONOTONIC, &ts);
+   return (ts.tv_sec * 1000.0) + (ts.tv_nsec / 1000000.0);
+}
+
+Tk_Client *
+tk_wait_state(Tk *tk, const char *app_id, unsigned int mask, unsigned int want,
+              int timeout_ms, const char *what)
+{
+   double deadline = _now_ms() + timeout_ms;
+   Tk_Client *c = NULL;
+   unsigned int last = 0;
+   int found = 0;
+
+   for (;;)
+     {
+        c = tk_find(tk, app_id);
+        if (c)
+          {
+             found = 1;
+             last = c->states;
+             if ((c->states & mask) == want) return c;
+          }
+        if (_now_ms() >= deadline) break;
+        tk_sync(tk);
+     }
+
+   if (!found)
+     tk_fail(tk, "waited %dms for %s: no window with app_id '%s' ever appeared",
+             timeout_ms, what, app_id);
+   tk_fail(tk, "waited %dms for %s: states are 0x%x, wanted 0x%x in mask 0x%x",
+           timeout_ms, what, last, want, mask);
+}
+
 void
 tk_action(Tk *tk, unsigned int id, const char *name, const char *params)
 {
diff --git a/src/tests/wayland/e_wl_testkit.h b/src/tests/wayland/e_wl_testkit.h
index 4f2354104..59bc71004 100644
--- a/src/tests/wayland/e_wl_testkit.h
+++ b/src/tests/wayland/e_wl_testkit.h
@@ -94,6 +94,22 @@ Tk_Client *tk_find(Tk *tk, const char *app_id);
 /* Like tk_find, but a miss is a failure rather than a NULL to check. */
 Tk_Client *tk_expect(Tk *tk, const char *app_id);
 
+/* Wait until (states & mask) == want, or fail after timeout_ms saying what was
+ * being waited for and dumping the window list.
+ *
+ * The one thing in the kit that waits on the clock, and it earns it: some of
+ * what E does to a window is animated, so the state a test asserts is reached
+ * a few frames after the request rather than in the same main loop iteration.
+ * Iconify is the clear case - e_client_iconify hides the frame, and
+ * e_comp_object defers that hide until its effect finishes, so a window is
+ * briefly both iconified and still painted.
+ *
+ * Still not a sleep. Every iteration is a round trip through E's own main
+ * loop, so it returns the moment the condition holds; the deadline exists only
+ * so a compositor that never gets there says so instead of hanging. */
+Tk_Client *tk_wait_state(Tk *tk, const char *app_id, unsigned int mask,
+                         unsigned int want, int timeout_ms, const char *what);
+
 /* Run one of E's own actions on a window. Empty or NULL params means none. */
 void tk_action(Tk *tk, unsigned int id, const char *name, const char *params);
 
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 897548e71..fdad19c04 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -76,6 +76,7 @@ wl_protocol_tests = [
   ['shortcuts-inhibit', 'test_shortcuts_inhibit.c'],
   ['client-list', 'test_client_list.c'],
   ['client-action', 'test_client_action.c'],
+  ['client-state', 'test_client_state.c'],
 ]
 
 # Shared plumbing: registry binding, toplevel construction, enumeration and a
diff --git a/src/tests/wayland/test_client_state.c b/src/tests/wayland/test_client_state.c
new file mode 100644
index 000000000..78ccfd769
--- /dev/null
+++ b/src/tests/wayland/test_client_state.c
@@ -0,0 +1,97 @@
+/* Window states a user drives, asserted against what the compositor believes.
+ *
+ * Maximise is covered next door in test_client_action.c. This is the rest of
+ * what someone actually does to a browser window: put it fullscreen for a
+ * video and take it back, minimise it and get it back.
+ *
+ * Every case is a round trip on purpose. The outward leg is easy - anything
+ * can make a window bigger or hide it - and it is the return that catches the
+ * bugs, because it needs E to have remembered what the window was before and
+ * to have told the client the same story it told itself.
+ */
+#include <stdio.h>
+
+#include "e_wl_testkit.h"
+
+#define APP_ID "e.test.client_state"
+#define TITLE  "states under test"
+#define W 320
+#define H 240
+
+int
+main(void)
+{
+   Tk *tk;
+   Tk_Toplevel *top;
+   Tk_Client *c;
+   unsigned int id;
+   int x0, y0, w0, h0, cw, ch;
+
+   tk = tk_connect("test-client-state");
+   top = tk_toplevel_new(tk, APP_ID, TITLE, W, H);
+
+   c = tk_expect(tk, APP_ID);
+   id = c->id;
+   x0 = c->x; y0 = c->y; w0 = c->w; h0 = c->h;
+
+   /* ------------------------------------------------------- fullscreen */
+
+   if (c->states & WL_TEST_CLIENT_STATE_FULLSCREEN)
+     tk_fail(tk, "a freshly mapped window is already fullscreen "
+                 "(states=0x%x)", c->states);
+
+   tk_action(tk, id, "window_fullscreen_toggle", NULL);
+
+   c = tk_expect(tk, APP_ID);
+   if (!(c->states & WL_TEST_CLIENT_STATE_FULLSCREEN))
+     tk_fail(tk, "not fullscreen after the toggle (states=0x%x)", c->states);
+
+   tk_toplevel_configured(top, &cw, &ch, NULL);
+   if ((cw <= w0) || (ch <= h0))
+     tk_fail(tk, "fullscreen, but the configure asked for %dx%d, no bigger "
+                 "than the original %dx%d", cw, ch, w0, h0);
+   if ((c->w != cw) || (c->h != ch))
+     tk_fail(tk, "fullscreen: told the client %dx%d and the frame is %dx%d - "
+                 "the compositor and the client disagree about the size",
+             cw, ch, c->w, c->h);
+   if ((c->x != 0) || (c->y != 0))
+     tk_fail(tk, "fullscreen at +%d+%d, expected the origin", c->x, c->y);
+
+   tk_action(tk, id, "window_fullscreen_toggle", NULL);
+
+   c = tk_expect(tk, APP_ID);
+   if (c->states & WL_TEST_CLIENT_STATE_FULLSCREEN)
+     tk_fail(tk, "still fullscreen after the second toggle (states=0x%x)",
+             c->states);
+   if ((c->x != x0) || (c->y != y0) || (c->w != w0) || (c->h != h0))
+     tk_fail(tk, "restored to %dx%d+%d+%d, was %dx%d+%d+%d before going "
+                 "fullscreen", c->w, c->h, c->x, c->y, w0, h0, x0, y0);
+
+   /* ---------------------------------------------------------- iconify */
+
+   tk_action(tk, id, "window_iconic_toggle", NULL);
+
+   /* Iconified *and* off screen. E defers the hide until its effect finishes,
+    * so both bits matter and the second one takes a moment - a window that
+    * stays painted after being minimised is the bug this checks for. */
+   c = tk_wait_state(tk, APP_ID,
+                     WL_TEST_CLIENT_STATE_ICONIFIED | WL_TEST_CLIENT_STATE_VISIBLE,
+                     WL_TEST_CLIENT_STATE_ICONIFIED, 5000,
+                     "the window to be iconified and stop being painted");
+
+   tk_action(tk, id, "window_iconic_toggle", NULL);
+
+   c = tk_wait_state(tk, APP_ID,
+                     WL_TEST_CLIENT_STATE_ICONIFIED | WL_TEST_CLIENT_STATE_VISIBLE,
+                     WL_TEST_CLIENT_STATE_VISIBLE, 5000,
+                     "the window to come back from iconified");
+   if ((c->x != x0) || (c->y != y0) || (c->w != w0) || (c->h != h0))
+     tk_fail(tk, "restored to %dx%d+%d+%d, was %dx%d+%d+%d before "
+                 "iconifying", c->w, c->h, c->x, c->y, w0, h0, x0, y0);
+
+   printf("test-client-state: ok (id=%u fullscreen and iconify both round "
+          "trip to %dx%d+%d+%d)\n", id, c->w, c->h, c->x, c->y);
+
+   tk_disconnect(tk);
+   return 0;
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to