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 8cbbb27b41c4a578445833136c5c5a4afddcb888
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 9 12:37:22 2026 -0600

    e_comp_wl - keep pointer focus honest across an interactive grab
    
    Three related things, all about E telling a client where the pointer is
    rather than leaving it to guess.
    
    1. Do not send a leave to a client that was never sent an enter, and do
       not send a second enter without a leave between. Both are protocol
       violations and both were happening. Harmless while nobody acted on
       these events; not harmless now that wl_pointer.frame makes clients
       apply them - wlcs fails a test outright on either.
    
    2. Send the leave xdg-shell requires when an interactive move or resize
       starts. "If triggered, the surface will lose the focus of the device
       (wl_pointer, wl_touch, etc) used for the move" - xdg_toplevel.move,
       and again under .resize. E never did, and could not arrive there by
       accident: the grab puts an evas event grabber over the canvas, but
       evas only recomputes what is under the pointer on the next motion,
       and a compositor-driven move need not produce one.
    
    3. Work out the focus again when the grab ends. The spec permits not
       returning it - "there is no guarantee that the device focus will
       return when the move is completed" - but the cursor never left the
       window as far as the user is concerned, so hand it back.
    
    (2) on its own is a regression: it correctly takes focus away and
    nothing gives it back, which turns touch_can_not_steal_pointer_based_move
    from passing into failing. That test had only ever passed because E did
    nothing at all. (3) is what makes (2) safe, and the two belong in one
    commit for that reason.
    
    Measured, wlcs v1.7.0, relaxed mode:
    
        E-22   passed 4 failed 2  ->  passed 5 failed 1   (twice, to be sure)
        XdgToplevelStableTest + XdgToplevelV6Test: 11 passed either way, but
        four pointer_leaves_surface_during_interactive_* went from 11s
        timeouts to passing, and touch_can_not_steal_pointer_based_move went
        the other way. Same total, better composition.
    
    Still failing in E-22: ClientSurfaceEventsTest.surface_moves_while_under_pointer.
    
    The re-evaluation is inline rather than deferred to a job. That was tried
    - E_CLIENT_HOOK_MOVE_END does fire before _e_client_action_finish() tears
    down the event grabber, so a job looked correct - and it measures
    strictly worse, 5 passed down to 4, while fixing nothing. The comment
    says so at the call site.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
 src/bin/e_comp_wl.c                       | 114 ++++++++++++++++++++++++++----
 src/bin/e_comp_wl.h                       |   1 +
 src/modules/wl_desktop_shell/e_mod_main.c |  10 +++
 3 files changed, 112 insertions(+), 13 deletions(-)

diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index a0d6b8351..93c800d58 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -229,6 +229,10 @@ _e_comp_wl_mouse_in(E_Client *ec, Evas_Event_Mouse_In *ev)
 
         if (!e_comp_wl_input_pointer_check(res)) continue;
         if (wl_resource_get_client(res) != wc) continue;
+        /* Already inside. A second enter with no leave between is a protocol
+         * violation and clients treat it as one - wlcs fails the test outright
+         * rather than quietly re-entering. */
+        if (ptr->entered) continue;
         ptr->entered = 1;
         wl_pointer_send_enter(res, serial, ec->comp_data->surface,
                               wl_fixed_from_int(ev->canvas.x - ec->client.x),
@@ -256,14 +260,40 @@ _e_comp_wl_cb_ssd_mouse_in(void *data, Evas_Object *obj, void *event_info)
    _e_comp_wl_mouse_in(data, event_info);
 }
 
+/* The leave itself, with no policy attached. Split out because an interactive
+ * grab has to be able to send one at a moment when _e_comp_wl_mouse_out()
+ * would refuse. */
 static void
-_e_comp_wl_mouse_out(E_Client *ec)
+_e_comp_wl_pointer_leave_send(E_Client *ec)
 {
    struct wl_resource *res;
    struct wl_client *wc;
    Eina_List *l;
    uint32_t serial;
 
+   if (!ec->comp_data || !ec->comp_data->surface) return;
+   if (!eina_list_count(e_comp_wl->ptr.resources)) return;
+
+   wc = wl_resource_get_client(ec->comp_data->surface);
+   serial = wl_display_next_serial(e_comp_wl->wl.disp);
+   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
+     {
+        E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(res);
+        if (!e_comp_wl_input_pointer_check(res)) continue;
+        if (wl_resource_get_client(res) != wc) continue;
+        /* Never entered, or already told to leave. Sending leave anyway is
+         * where the "spurious wl_pointer.leave" failures came from: the client
+         * is asked to leave a surface it does not believe it is on. */
+        if (!ptr->entered) continue;
+        ptr->entered = 0;
+        wl_pointer_send_leave(res, serial, ec->comp_data->surface);
+        e_comp_wl_pointer_frame_send(res);
+     }
+}
+
+static void
+_e_comp_wl_mouse_out(E_Client *ec)
+{
    if ((ec == e_client_action_get()) && e_grabinput_mouse_win_get()) return;
    /* FIXME? this is a hack to just reset the cursor whenever we mouse out. not sure if accurate */
    {
@@ -280,19 +310,33 @@ _e_comp_wl_mouse_out(E_Client *ec)
         e_comp_wl_data_device_send_leave(ec);
         return;
      }
-   if (!eina_list_count(e_comp_wl->ptr.resources)) return;
 
-   wc = wl_resource_get_client(ec->comp_data->surface);
-   serial = wl_display_next_serial(e_comp_wl->wl.disp);
-   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
-     {
-        E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(res);
-        if (!e_comp_wl_input_pointer_check(res)) continue;
-        if (wl_resource_get_client(res) != wc) continue;
-        ptr->entered = 0;
-        wl_pointer_send_leave(res, serial, ec->comp_data->surface);
-        e_comp_wl_pointer_frame_send(res);
-     }
+   _e_comp_wl_pointer_leave_send(ec);
+}
+
+/* xdg_toplevel.move and .resize both say the surface "will lose the focus of
+ * the device (wl_pointer, wl_touch, etc) used for the move", and clients rely
+ * on it: they stop tracking hover the moment the grab is granted and wait for
+ * the leave rather than for pointer motion that may never come.
+ *
+ * E cannot arrive there by accident. The grab puts an evas event grabber over
+ * the canvas, which does stop events reaching the client, but Evas only
+ * re-evaluates what is under the pointer on the next motion - and a
+ * compositor-driven move need not produce one. So say it outright when the
+ * grab is granted.
+ *
+ * Deliberately not routed through _e_comp_wl_mouse_out(): by this point the
+ * client is the action client, and that function declines to send a leave for
+ * exactly that case. The guard is there to stop E churning focus during a move
+ * it is driving itself, which is a different question from the one the
+ * protocol asks here. */
+E_API void
+e_comp_wl_pointer_focus_drop(E_Client *ec)
+{
+   if (!ec || e_object_is_del(E_OBJECT(ec))) return;
+   if (e_comp_wl->drag) return;
+   if (e_comp_wl->ptr.ec == ec) e_comp_wl->ptr.ec = NULL;
+   _e_comp_wl_pointer_leave_send(ec);
 }
 
 static void
@@ -2759,6 +2803,46 @@ _e_comp_wl_client_cb_move_begin(void *data EINA_UNUSED, E_Client *ec EINA_UNUSED
    e_comp_wl->ptr.button_mask = 0;
 }
 
+/* Work out again what the pointer is over, and send whatever enter or leave
+ * that implies.
+ *
+ * Evas only recomputes the objects under the pointer when the pointer moves.
+ * That is a reasonable thing for a toolkit to do and the wrong thing for a
+ * compositor: an interactive move ends with the window somewhere new and the
+ * cursor exactly where it was, so nothing recomputes and the client is left
+ * believing it has no pointer. Feeding a move at the position the pointer is
+ * already at is enough - evas rebuilds its "in" list unconditionally and fires
+ * the difference - so there is no hit testing to duplicate here. */
+/* Inline, deliberately. Deferring this to a job was tried, on the theory that
+ * E_CLIENT_HOOK_MOVE_END fires before _e_client_action_finish() removes the
+ * event grabber and the synthetic move would be eaten by it. Measured: the job
+ * version scores strictly worse - ClientSurfaceEventsTest goes from 5 passed
+ * to 4 - and fixes nothing that the inline one does not. The reply to the
+ * client's roundtrip goes out before the job runs, so the test sees the old
+ * focus. Do not "clean this up" into a job without re-running E-22. */
+static void
+_e_comp_wl_pointer_focus_reeval(void)
+{
+   Evas_Coord x, y;
+
+   if ((!e_comp) || (!e_comp->evas)) return;
+   evas_pointer_canvas_xy_get(e_comp->evas, &x, &y);
+   evas_event_feed_mouse_move(e_comp->evas, x, y, 0, NULL);
+}
+
+/* The other half of e_comp_wl_pointer_focus_drop(). xdg-shell hedges here -
+ * "there is no guarantee that the device focus will return when the move is
+ * completed" - so handing it back is a choice, but it is the one a user
+ * expects, since as far as they are concerned the cursor never left the
+ * window. */
+static void
+_e_comp_wl_client_cb_move_end(void *data EINA_UNUSED, E_Client *ec)
+{
+   if (e_object_is_del(E_OBJECT(ec))) return;
+   if (e_client_has_xwindow(ec)) return;
+   _e_comp_wl_pointer_focus_reeval();
+}
+
 static void
 _e_comp_wl_client_cb_resize_begin(void *data EINA_UNUSED, E_Client *ec)
 {
@@ -2822,6 +2906,8 @@ _e_comp_wl_client_cb_resize_end(void *data EINA_UNUSED, E_Client *ec)
      }
 
    E_FREE_LIST(ec->pending_resize, free);
+
+   _e_comp_wl_pointer_focus_reeval();
 }
 
 static void
@@ -3146,6 +3232,8 @@ e_comp_wl_init(void)
 
    e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN,
                      _e_comp_wl_client_cb_move_begin, NULL);
+   e_client_hook_add(E_CLIENT_HOOK_MOVE_END,
+                     _e_comp_wl_client_cb_move_end, NULL);
    e_client_hook_add(E_CLIENT_HOOK_RESIZE_BEGIN,
                      _e_comp_wl_client_cb_resize_begin, NULL);
    e_client_hook_add(E_CLIENT_HOOK_RESIZE_END,
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index 77485a0a5..b01af21d3 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -415,6 +415,7 @@ EINTERN Eina_Bool e_comp_wl_key_down(Ecore_Event_Key *ev, E_Client *ec);
 EINTERN Eina_Bool e_comp_wl_key_up(Ecore_Event_Key *ev, E_Client *ec);
 E_API Eina_Bool e_comp_wl_evas_handle_mouse_button(E_Client *ec, uint32_t timestamp, uint32_t button_id, uint32_t state);
 EINTERN void e_comp_wl_pointer_frame_send(struct wl_resource *res);
+E_API void e_comp_wl_pointer_focus_drop(E_Client *ec);
 
 E_API extern int E_EVENT_WAYLAND_GLOBAL_ADD;
 
diff --git a/src/modules/wl_desktop_shell/e_mod_main.c b/src/modules/wl_desktop_shell/e_mod_main.c
index 9e760ab5d..c14b1cca0 100644
--- a/src/modules/wl_desktop_shell/e_mod_main.c
+++ b/src/modules/wl_desktop_shell/e_mod_main.c
@@ -123,6 +123,16 @@ e_shell_surface_mouse_down_helper(E_Client *ec, E_Binding_Event_Mouse_Button *ev
           e_object_ref(E_OBJECT(ec->cur_mouse_action));
      }
 
+   /* "If triggered, the surface will lose the focus of the device
+    * (wl_pointer, wl_touch, etc) used for the move" - xdg-shell.xml,
+    * xdg_toplevel.move, and the same sentence again under .resize.
+    *
+    * Only once the grab has actually been granted. e_client_act_*_begin
+    * declines for a fullscreen or already-moving client, and a surface whose
+    * request was refused must keep the pointer it had. */
+   if ((ec->moving) || (e_client_util_resizing_get(ec)))
+     e_comp_wl_pointer_focus_drop(ec);
+
    e_focus_event_mouse_down(ec);
 }
 

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

Reply via email to