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 afb8d16d4c54b89c9232450ee98ae21463e35bf3
Author: Cedric BAIL <[email protected]>
AuthorDate: Wed Aug 12 15:35:47 2026 -0600

    wl_desktop_shell - xdg_wm_base version 3, reactive popups and reposition
    
    The version a browser notices. Chromium and GTK use xdg_popup.reposition for
    menus; without it a menu that needs to move near a screen edge is destroyed
    and recreated, which is the flicker.
    
    Three positioner requests and one popup request:
    
     - set_parent_size and set_parent_configure are recorded. They describe the
       parent's next geometry rather than its current one, for placing a popup
       against a resize that has been configured but not yet committed.
    
     - set_reactive says "place me again whenever the answer would change". The
       parent moving is how that usually happens, so a hook on MOVE_UPDATE and
       MOVE_END walks the moved client's transients and re-places the reactive
       popups among them. No repositioned event goes with it - that event answers
       a reposition request and names its token, and there is neither here.
    
     - reposition re-runs the positioner and replies repositioned(token) before
       the configure, so the client can tell which request it is being answered.
    
    A popup outlives the positioner that placed it - a client may destroy the
    positioner the moment get_popup returns - so a reactive popup keeps its own
    copy of the recipe rather than a pointer to it, dropped when the client goes.
    
    Placing a mapped popup is not placing a new one. _apply_positioner writes its
    answer straight into ec->x and ec->y, which is all the initial placement needs
    because the map then puts the window where the coordinates say. Once the popup
    is on screen those fields only record where it is, and assigning them moves
    nothing: the first version of this sent a configure with the right coordinates
    in it and left the popup where it was. _xdg_popup_place takes the answer back
    out and asks E to move the window to it.
    
    wlcs XdgPopupTest: 4 of 4, from 3 of 4 - reposition, and both the reactive and
    the non-reactive parent-move cases.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
 src/modules/wl_desktop_shell/xdg.c | 156 ++++++++++++++++++++++++++++++++++++-
 src/tests/wayland/globals.expected |   2 +-
 src/tests/wlcs/e_wlcs.c            |   2 +-
 3 files changed, 157 insertions(+), 3 deletions(-)

diff --git a/src/modules/wl_desktop_shell/xdg.c b/src/modules/wl_desktop_shell/xdg.c
index 5df02c58c..05606e5af 100644
--- a/src/modules/wl_desktop_shell/xdg.c
+++ b/src/modules/wl_desktop_shell/xdg.c
@@ -6,7 +6,7 @@
 
 /* Every child object is created at the version its parent was bound at,
  * so this is the only number to move when a version is finished. */
-#define XDG_WM_BASE_SERVER_VERSION 2
+#define XDG_WM_BASE_SERVER_VERSION 3
 
 typedef enum
 {
@@ -38,8 +38,20 @@ typedef struct Positioner
    enum xdg_positioner_gravity gravity;
    enum xdg_positioner_constraint_adjustment constrain;
    Evas_Coord_Point offset;
+   /* v3 */
+   Evas_Coord_Size parent_size;
+   uint32_t parent_configure;
+   Eina_Bool reactive;
 } Positioner;
 
+/* A popup outlives the positioner that placed it - the client is free to
+ * destroy the positioner the moment get_popup returns - so a reactive popup
+ * has to keep its own copy of the recipe rather than a pointer to it. Keyed
+ * by E_Client, dropped when the client goes. */
+static Eina_Hash *xdg_popup_positioners;
+
+static void _apply_positioner(E_Client *ec, Positioner *p);
+
 
 static void
 _e_xdg_shell_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
@@ -230,6 +242,36 @@ _e_xdg_positioner_set_offset(struct wl_client *client EINA_UNUSED, struct wl_res
    p->offset.y = y;
 }
 
+/* "Reposition me again whenever the answer would change." Recorded here;
+ * _xdg_popup_reactive_parent_moved is what acts on it. */
+static void
+_e_xdg_positioner_set_reactive(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+   Positioner *p = wl_resource_get_user_data(resource);
+
+   p->reactive = EINA_TRUE;
+}
+
+/* The size the parent is about to become, told to us before the commit that
+ * makes it so, so a popup can be placed against the parent's next geometry
+ * instead of its current one. */
+static void
+_e_xdg_positioner_set_parent_size(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t w, int32_t h)
+{
+   Positioner *p = wl_resource_get_user_data(resource);
+
+   p->parent_size.w = w;
+   p->parent_size.h = h;
+}
+
+static void
+_e_xdg_positioner_set_parent_configure(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t serial)
+{
+   Positioner *p = wl_resource_get_user_data(resource);
+
+   p->parent_configure = serial;
+}
+
 static void
 _e_xdg_positioner_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
 {
@@ -245,6 +287,9 @@ static const struct xdg_positioner_interface _e_xdg_positioner_interface =
    .set_gravity = _e_xdg_positioner_set_gravity,
    .set_constraint_adjustment = _e_xdg_positioner_set_constraint_adjustment,
    .set_offset = _e_xdg_positioner_set_offset,
+   .set_reactive = _e_xdg_positioner_set_reactive,
+   .set_parent_size = _e_xdg_positioner_set_parent_size,
+   .set_parent_configure = _e_xdg_positioner_set_parent_configure,
 };
 
 static void
@@ -620,9 +665,88 @@ _e_xdg_popup_cb_grab(struct wl_client *client EINA_UNUSED, struct wl_resource *r
    e_comp_wl_grab_client_add(ec, _e_xdg_popup_grab_dismiss);
 }
 
+static void
+_xdg_popup_positioner_remember(E_Client *ec, Positioner *p)
+{
+   Positioner *copy;
+
+   if (!xdg_popup_positioners)
+     xdg_popup_positioners = eina_hash_pointer_new(free);
+   copy = malloc(sizeof(*copy));
+   if (!copy) return;
+   memcpy(copy, p, sizeof(*copy));
+   /* The copy is a recipe, not a live object: it must not claim to be either
+    * the wl_resource or the shell it came from, both of which can go first. */
+   copy->res = NULL;
+   copy->v = NULL;
+   eina_hash_set(xdg_popup_positioners, &ec, copy);
+}
+
+static void
+_xdg_popup_positioner_forget(E_Client *ec)
+{
+   if (xdg_popup_positioners) eina_hash_del_by_key(xdg_popup_positioners, &ec);
+}
+
+/* Re-place a popup that is already on screen.
+ *
+ * _apply_positioner writes the answer straight into ec->x and ec->y, which is
+ * all the initial placement needs because nothing has been drawn yet - the map
+ * puts the window where the coordinates say. Once the popup is mapped those
+ * two fields are only a record of where it is, and assigning them moves
+ * nothing. So take the answer back out and ask E to move the window to it. */
+static void
+_xdg_popup_place(E_Client *ec, Positioner *p)
+{
+   int ox = ec->x, oy = ec->y, nx, ny;
+
+   _apply_positioner(ec, p);
+   nx = ec->x;
+   ny = ec->y;
+   ec->client.x = nx;
+   ec->client.y = ny;
+   if (!ec->comp_data->mapped) return;
+
+   ec->x = ox;
+   ec->y = oy;
+   e_client_util_move_without_frame(ec, nx, ny);
+}
+
+static void
+_e_xdg_popup_cb_reposition(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *positioner_resource, uint32_t token)
+{
+   E_Client *ec;
+   Positioner *p;
+
+   ec = wl_resource_get_user_data(resource);
+   if ((!ec) || e_object_is_del(E_OBJECT(ec)))
+     {
+        wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
+                               "No Client For Shell Surface");
+        return;
+     }
+   p = wl_resource_get_user_data(positioner_resource);
+   if ((!p) || (!p->size.w) || (p->anchor_rect.w < 0))
+     {
+        wl_resource_post_error(resource, XDG_WM_BASE_ERROR_INVALID_POSITIONER,
+                               "invalid positioner");
+        return;
+     }
+   if (!ec->parent) return;
+
+   _xdg_popup_positioner_remember(ec, p);
+   _xdg_popup_place(ec, p);
+
+   /* repositioned comes first and names the token, so the client can tell
+    * which of its requests the configure that follows is answering. */
+   xdg_popup_send_repositioned(resource, token);
+   _e_xdg_shell_surface_configure_send(ec->comp_data->shell.surface, 0, 0, 0);
+}
+
 static const struct xdg_popup_interface _e_xdg_popup_interface = {
    _e_xdg_popup_cb_destroy,
    _e_xdg_popup_cb_grab,
+   _e_xdg_popup_cb_reposition,
 };
 
 static void
@@ -1179,6 +1303,7 @@ _e_xdg_surface_cb_popup_get(struct wl_client *client, struct wl_resource *resour
    /* set this client as a transient for parent */
    if (parent_resource) e_shell_surface_parent_set(ec, parent_resource);
 
+   _xdg_popup_positioner_remember(ec, p);
    _apply_positioner(ec, p);
    ec->client.x = ec->x;
    ec->client.y = ec->y;
@@ -1747,9 +1872,36 @@ _e_xdg_shell_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t
 static void
 _xdg_client_hook_del(void *d EINA_UNUSED, E_Client *ec)
 {
+   _xdg_popup_positioner_forget(ec);
    _xdg_client_destroy(ec, 1);
 }
 
+/* A reactive popup asked to be re-placed whenever the answer would change,
+ * and the parent moving is the way that most often happens. No repositioned
+ * event here: that one answers an xdg_popup.reposition request and names its
+ * token, and there is no request and no token behind this. */
+static void
+_xdg_popup_reactive_parent_moved(void *d EINA_UNUSED, E_Client *ec)
+{
+   const Eina_List *l;
+   E_Client *child;
+
+   if (!xdg_popup_positioners) return;
+   EINA_LIST_FOREACH(ec->transients, l, child)
+     {
+        Positioner *p;
+
+        if (e_object_is_del(E_OBJECT(child))) continue;
+        if (!e_client_util_is_popup(child)) continue;
+        if (!child->comp_data->shell.surface) continue;
+        p = eina_hash_find(xdg_popup_positioners, &child);
+        if ((!p) || (!p->reactive)) continue;
+
+        _xdg_popup_place(child, p);
+        _e_xdg_shell_surface_configure_send(child->comp_data->shell.surface, 0, 0, 0);
+     }
+}
+
 EINTERN Eina_Bool
 e_xdg_shell_init(void)
 {
@@ -1762,5 +1914,7 @@ e_xdg_shell_init(void)
         return EINA_FALSE;
      }
    hooks = eina_list_append(hooks, e_client_hook_add(E_CLIENT_HOOK_DEL, _xdg_client_hook_del, NULL));
+   hooks = eina_list_append(hooks, e_client_hook_add(E_CLIENT_HOOK_MOVE_UPDATE, _xdg_popup_reactive_parent_moved, NULL));
+   hooks = eina_list_append(hooks, e_client_hook_add(E_CLIENT_HOOK_MOVE_END, _xdg_popup_reactive_parent_moved, NULL));
    return EINA_TRUE;
 }
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 8dd0e8140..cacb0d0d5 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -7,7 +7,7 @@ wl_seat	5
 wl_shell	1
 wl_shm	1
 wl_subcompositor	1
-xdg_wm_base	2
+xdg_wm_base	3
 zwp_e_session_recovery	1
 zwp_pointer_constraints_v1	1
 zwp_relative_pointer_manager_v1	1
diff --git a/src/tests/wlcs/e_wlcs.c b/src/tests/wlcs/e_wlcs.c
index 0dfbbaf21..f3be2e82e 100644
--- a/src/tests/wlcs/e_wlcs.c
+++ b/src/tests/wlcs/e_wlcs.c
@@ -700,7 +700,7 @@ static const WlcsExtensionDescriptor _extensions[] =
    { "wl_seat", 5 },
    { "wl_output", 2 },
    { "wl_data_device_manager", 3 },
-   { "xdg_wm_base", 2 },
+   { "xdg_wm_base", 3 },
    { "zxdg_shell_v6", 1 },
    { "wl_shell", 1 },
 };

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

Reply via email to