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.