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 381c6c692b66254090ee7c7b1194d58c37c09987
Author: Cedric BAIL <[email protected]>
AuthorDate: Tue Aug 18 23:54:15 2026 -0600
e_comp_wl - a dialog is a toplevel, and it gets the keyboard
xdg_toplevel.set_parent says only what a window belongs to: "this
toplevel should be stacked above its parent", and the parent is "usually
the toplevel that the dialog belongs to". Nothing in it says the child
declines keyboard input, and a modal dialog obviously cannot - it is the
window the user is being asked to answer.
An xdg_popup is the other thing. That one really does hand its keyboard
to the toplevel it hangs off unless it took an explicit grab, which is
why the two places below walk a popup up to its toplevel before deciding
where wl_keyboard.enter and .leave go.
E applied that walk to any xdg surface with a parent, which is both of
them. So a child toplevel never received focus: the walk arrived at the
parent, found it already focused, and returned. A Wayland dialog under E
could not be typed into at all - and from the compositor's own side it
looks fine, because the window is mapped, raised and apparently focused.
It reads as the application ignoring the keyboard.
The leave path had the mirror of it: it named the parent's surface in a
leave that was about the child's.
e_client_util_is_popup is the discriminator E already uses elsewhere for
this, and every shell sets it the same way - xdg.c, xdg6.c, xdg5.c and
wl_shell.c all give popups E_WINDOW_TYPE_POPUP_MENU and toplevels
E_WINDOW_TYPE_NORMAL.
Found while implementing text-input v3: the wlcs test that expects a
text input to follow focus into a child toplevel was measuring this, not
text input.
Full wlcs is unchanged by it - the failure set is identical, and the
in-tree suite including the three browsers stays green. The new test
asserts the surface named in wl_keyboard.enter, which is the only thing
a client can observe about who holds the keyboard, and it fails on a
build with this guard removed.
---
src/bin/e_comp_wl.c | 5 +-
src/tests/wayland/e_wl_testkit.c | 21 +++++-
src/tests/wayland/e_wl_testkit.h | 13 ++++
src/tests/wayland/meson.build | 1 +
src/tests/wayland/test_dialog_focus.c | 138 ++++++++++++++++++++++++++++++++++
5 files changed, 174 insertions(+), 4 deletions(-)
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index 89673dae3..727dbfd96 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -892,7 +892,8 @@ _e_comp_wl_evas_cb_focus_in(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj
/* A popup holding an xdg_popup.grab is the exception to the rule below:
* the grab is what gives a menu the keyboard, so focus stays on the popup
* itself rather than walking up to the toplevel it hangs off. */
- if (ec->comp_data->is_xdg_surface && (!ec->comp_data->grab))
+ if (ec->comp_data->is_xdg_surface && e_client_util_is_popup(ec) &&
+ (!ec->comp_data->grab))
{
/* We only send kbd focus to xdg top levels */
while (ec->parent)
@@ -928,7 +929,7 @@ _e_comp_wl_keyboard_leave(E_Client *ec)
if (!eina_list_count(e_comp_wl->kbd.resources)) return;
if (!ec->comp_data) return;
- if (ec->comp_data->is_xdg_surface)
+ if (ec->comp_data->is_xdg_surface && e_client_util_is_popup(ec))
{
/* If we left an xdg popup to enter some other (sub)surface
* of the same top level, we don't need to do anything.
diff --git a/src/tests/wayland/e_wl_testkit.c b/src/tests/wayland/e_wl_testkit.c
index 8e1b98f08..c1b3fad6b 100644
--- a/src/tests/wayland/e_wl_testkit.c
+++ b/src/tests/wayland/e_wl_testkit.c
@@ -403,8 +403,8 @@ tk_toplevel_maximize(Tk_Toplevel *top, int on)
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)
+static Tk_Toplevel *
+_toplevel_make(Tk *tk, const char *app_id, const char *title, int w, int h, int shadow, Tk_Toplevel *parent)
{
Tk_Toplevel *top;
@@ -422,6 +422,11 @@ tk_toplevel_new_shadowed(Tk *tk, const char *app_id, const char *title, int w, i
xdg_toplevel_add_listener(top->toplevel, &_toplevel_listener, top);
if (title) xdg_toplevel_set_title(top->toplevel, title);
if (app_id) xdg_toplevel_set_app_id(top->toplevel, app_id);
+ /* Before the first commit, because the relationship has to be in place by
+ * the time the compositor makes a window out of this. A dialog that names
+ * its parent only after it has already been mapped and focused is not the
+ * case worth testing. */
+ if (parent) xdg_toplevel_set_parent(top->toplevel, parent->toplevel);
/* The empty commit xdg-shell requires before any buffer. The configure it
* provokes is what paints us, from _xdg_surface_configure. */
@@ -434,6 +439,18 @@ tk_toplevel_new_shadowed(Tk *tk, const char *app_id, const char *title, int w, i
return top;
}
+Tk_Toplevel *
+tk_toplevel_new_shadowed(Tk *tk, const char *app_id, const char *title, int w, int h, int shadow)
+{
+ return _toplevel_make(tk, app_id, title, w, h, shadow, NULL);
+}
+
+Tk_Toplevel *
+tk_toplevel_new_child(Tk *tk, const char *app_id, const char *title, int w, int h, Tk_Toplevel *parent)
+{
+ return _toplevel_make(tk, app_id, title, w, h, 0, parent);
+}
+
uint32_t
tk_global_version(Tk *tk, const char *iface)
{
diff --git a/src/tests/wayland/e_wl_testkit.h b/src/tests/wayland/e_wl_testkit.h
index 71d5f54d4..b360d2a27 100644
--- a/src/tests/wayland/e_wl_testkit.h
+++ b/src/tests/wayland/e_wl_testkit.h
@@ -69,6 +69,19 @@ void tk_fail(Tk *tk, const char *fmt, ...) __attribute__((noreturn, format(print
Tk_Toplevel *tk_toplevel_new(Tk *tk, const char *app_id, const char *title,
int w, int h);
+/* A toplevel that names another as its parent - a dialog, in other words. Not
+ * a popup: an xdg_popup is a menu, positioned by the compositor and dismissed
+ * as a unit, whereas this is an ordinary window that happens to say what it
+ * belongs to. The distinction matters because a compositor that treats the two
+ * alike keeps keyboard focus on the parent, and a dialog nobody can type into
+ * reads as an application bug rather than a compositor one.
+ *
+ * The parent is set before the first commit, so the relationship is in place
+ * by the time the compositor makes a window out of it. */
+Tk_Toplevel *tk_toplevel_new_child(Tk *tk, const char *app_id,
+ const char *title, int w, int h,
+ Tk_Toplevel *parent);
+
/* The wl_surface behind a toplevel, for the protocols that take one. The kit
* owns it and destroys it with the connection. */
struct wl_surface *tk_toplevel_surface(Tk_Toplevel *top);
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 4271d6bb7..1d4e8cac8 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -91,6 +91,7 @@ wl_protocol_tests = [
['content-type', 'test_content_type.c'],
['toplevel-drag', 'test_toplevel_drag.c'],
['seat-capabilities', 'test_seat_capabilities.c'],
+ ['dialog-focus', 'test_dialog_focus.c'],
]
# Shared plumbing: registry binding, toplevel construction, enumeration and a
diff --git a/src/tests/wayland/test_dialog_focus.c b/src/tests/wayland/test_dialog_focus.c
new file mode 100644
index 000000000..4f0b55946
--- /dev/null
+++ b/src/tests/wayland/test_dialog_focus.c
@@ -0,0 +1,138 @@
+/* A dialog is a toplevel, not a popup, and it gets the keyboard.
+ *
+ * xdg_toplevel.set_parent says only what a window belongs to: "this toplevel
+ * should be stacked above its parent", and the parent is "usually the toplevel
+ * that the dialog belongs to". Nothing about it changes who receives keyboard
+ * input, and a modal dialog obviously has to receive it - it is the window the
+ * user is being asked to answer.
+ *
+ * An xdg_popup is the other thing. That one really does hand its keyboard to
+ * the toplevel it hangs off, unless it took an explicit grab, which is why E
+ * walks a popup up to its toplevel before deciding where wl_keyboard.enter
+ * goes.
+ *
+ * E used to apply that walk to any xdg surface with a parent, which is both of
+ * them. The result was a dialog that could never be typed into: focus stopped
+ * at the parent and stayed there. It is close to invisible from the
+ * compositor's own side - the window appears, it is raised, it looks focused -
+ * and it reads to a user as the application ignoring the keyboard.
+ *
+ * The test asserts the surface named in wl_keyboard.enter, because that is the
+ * one thing a client can observe about who has the keyboard, and it is what
+ * every toolkit routes key handling on.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "e_wl_testkit.h"
+
+#define PROG "test-dialog-focus"
+
+typedef struct
+{
+ struct wl_surface *entered; /* last surface named in enter */
+ int enters, leaves;
+} Kbd_Watch;
+
+static void
+_kbd_keymap(void *d, struct wl_keyboard *k, uint32_t f, int32_t fd, uint32_t s)
+{ (void)d; (void)k; (void)f; (void)s; if (fd >= 0) close(fd); }
+
+static void
+_kbd_enter(void *data, struct wl_keyboard *k, uint32_t serial,
+ struct wl_surface *surface, struct wl_array *keys)
+{
+ Kbd_Watch *w = data;
+
+ (void)k; (void)serial; (void)keys;
+ w->entered = surface;
+ w->enters++;
+}
+
+static void
+_kbd_leave(void *data, struct wl_keyboard *k, uint32_t serial,
+ struct wl_surface *surface)
+{
+ Kbd_Watch *w = data;
+
+ (void)k; (void)serial;
+ if (w->entered == surface) w->entered = NULL;
+ w->leaves++;
+}
+
+static void
+_kbd_key(void *d, struct wl_keyboard *k, uint32_t se, uint32_t t, uint32_t ke, uint32_t st)
+{ (void)d; (void)k; (void)se; (void)t; (void)ke; (void)st; }
+
+static void
+_kbd_mods(void *d, struct wl_keyboard *k, uint32_t se, uint32_t a, uint32_t b, uint32_t c, uint32_t g)
+{ (void)d; (void)k; (void)se; (void)a; (void)b; (void)c; (void)g; }
+
+static void
+_kbd_repeat(void *d, struct wl_keyboard *k, int32_t r, int32_t delay)
+{ (void)d; (void)k; (void)r; (void)delay; }
+
+static const struct wl_keyboard_listener _kbd_listener =
+{
+ _kbd_keymap, _kbd_enter, _kbd_leave, _kbd_key, _kbd_mods, _kbd_repeat
+};
+
+int
+main(void)
+{
+ Tk *tk;
+ Tk_Toplevel *parent, *dialog;
+ struct wl_seat *seat;
+ struct wl_keyboard *kbd;
+ Kbd_Watch w = { 0 };
+ uint32_t seat_version;
+
+ tk = tk_connect(PROG);
+
+ seat_version = tk_global_version(tk, "wl_seat");
+ if (!seat_version) tk_fail(tk, "no wl_seat");
+ seat = tk_bind(tk, &wl_seat_interface, seat_version);
+ if (!seat) tk_fail(tk, "wl_seat advertised but would not bind");
+ kbd = wl_seat_get_keyboard(seat);
+ if (!kbd) tk_fail(tk, "seat has no keyboard");
+ wl_keyboard_add_listener(kbd, &_kbd_listener, &w);
+ tk_sync(tk);
+
+ parent = tk_toplevel_new(tk, "dialog-focus-parent", "parent", 200, 200);
+ tk_settle(tk);
+ tk_sync(tk);
+
+ if (w.entered != tk_toplevel_surface(parent))
+ tk_fail(tk, "the parent toplevel did not get the keyboard when it mapped "
+ "(%d enters, %d leaves) - nothing after this means anything",
+ w.enters, w.leaves);
+
+ /* The dialog. Its parent is set before it is mapped, which is what a
+ * toolkit does. */
+ dialog = tk_toplevel_new_child(tk, "dialog-focus-dialog", "dialog",
+ 120, 80, parent);
+ tk_settle(tk);
+ tk_sync(tk);
+
+ if (!w.entered)
+ tk_fail(tk, "after the dialog mapped nothing holds the keyboard "
+ "(%d enters, %d leaves)", w.enters, w.leaves);
+
+ if (w.entered == tk_toplevel_surface(parent))
+ tk_fail(tk, "the keyboard stayed on the parent when a child toplevel "
+ "mapped - a dialog can never be typed into. set_parent says "
+ "what a window belongs to, not that it declines input; that "
+ "is what an xdg_popup without a grab does");
+
+ if (w.entered != tk_toplevel_surface(dialog))
+ tk_fail(tk, "the keyboard went to neither the parent nor the dialog "
+ "(%d enters, %d leaves)", w.enters, w.leaves);
+
+ printf(PROG ": ok - the dialog holds the keyboard (%d enters, %d leaves)\n",
+ w.enters, w.leaves);
+
+ tk_disconnect(tk);
+ return 0;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.