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 22121668e264ed41a11096e96bbd6701fbaa8520
Author: Cedric BAIL <[email protected]>
AuthorDate: Wed Aug 19 09:01:54 2026 -0600

    wl_test - press keys through ecore, not evas
    
    wl_test.key called evas_event_feed_key_down, and evas is the wrong end
    of the keyboard. Nothing a Wayland client can see hangs off it:
    wl_keyboard.key is sent from e_comp_wl_key_down, bindings are evaluated
    by e_bindings_key_down_event_handle, and the idle timer is re-armed by
    e_comp_canvas_notidle - all three from _key_down in e_comp_canvas.c,
    which is an ECORE_EVENT_KEY_DOWN handler. So no key this suite pressed
    had ever reached a client, and no assertion about one could fail.
    
    The mouse half of this was done in wl/test-ecore-input; keys were
    missed. Same shape as that change, and the same reason for it: a harness
    that patches around a gap eventually hides a bug behind it.
    
    The existing caller shows what it was costing. test_idle_inhibit presses
    a key to prove an inhibitor survives input, and the key reached nothing
    that could have cancelled the inhibit - half of that assertion could not
    fail. It still passes, now for the right reason.
    
    Two handlers feed the canvas, in the same position and for the same
    reason as the mouse ones: E's own widgets take keys as evas events, and
    on a real backend ecore_evas carries them across. The buffer backend has
    no such bridge, so without these an internal window could never be typed
    into. At ordinary priority they sit after E's E_LIST_HANDLER_APPEND_PRE
    handlers, which is where ecore_evas would be - so a binding that claims
    a key stops it reaching the canvas, as in a real session.
    
    The new test is the one this suite most needed and did not have: that a
    key pressed by the harness arrives at the focused client with the right
    keycode. It checks the evdev number rather than the xkb one - "a" is 30,
    not 38 - because the off-by-eight is invisible from inside the
    compositor and only a client can report it. It also checks that an
    unfocused client gets nothing, without which a compositor that
    broadcast every key to every window would pass the rest.
    
    Measured: full wlcs 780 passed / 14 failed, failure set identical to
    b8f413638. wlcs never injects keys - e_wlcs.c has no key path at all -
    so that was expected, and it is the check that says so. In-tree 30 -> 31,
    all green including the browser tier. Reverting the one line makes
    wl-keyboard fail with "delivered 0 wl_keyboard.key events", checked
    against a build with it reverted.
---
 src/modules/wl_test/e_mod_main.c  |  60 ++++++++++-
 src/tests/wayland/e_wl_testkit.c  |   7 ++
 src/tests/wayland/e_wl_testkit.h  |  12 +++
 src/tests/wayland/meson.build     |   1 +
 src/tests/wayland/test_keyboard.c | 213 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 289 insertions(+), 4 deletions(-)

diff --git a/src/modules/wl_test/e_mod_main.c b/src/modules/wl_test/e_mod_main.c
index b3d60eb68..f1624a5e8 100644
--- a/src/modules/wl_test/e_mod_main.c
+++ b/src/modules/wl_test/e_mod_main.c
@@ -544,13 +544,29 @@ _wl_test_cb_touch_up(struct wl_client *client EINA_UNUSED, struct wl_resource *r
                             EVAS_BUTTON_NONE, 0, NULL);
 }
 
+/* Post a real ecore key event, the same way E's own keyboard_send action does.
+ *
+ * This used to call evas_event_feed_key_down directly, and evas is the wrong
+ * end of the keyboard entirely: nothing a Wayland client can see hangs off it.
+ * wl_keyboard.key is sent from e_comp_wl_key_down, key bindings are evaluated
+ * by e_bindings_key_down_event_handle, and the idle timer is re-armed by
+ * e_comp_canvas_notidle - all three from _key_down in e_comp_canvas.c, which
+ * is an ECORE_EVENT_KEY_DOWN handler. A test that fed evas reached none of
+ * them, so every key assertion in this suite was measuring the canvas and
+ * calling it the keyboard.
+ *
+ * The one existing caller shows the shape of the problem: test_idle_inhibit
+ * presses a key to prove an inhibitor survives input, and the key never
+ * reached the idle path, so that half of the assertion could not fail.
+ *
+ * e_comp_wl_input_keyboard_event_generate is E's own generator - it maps a
+ * name to a keycode through the active xkb keymap and posts the event. Using
+ * it rather than building an Ecore_Event_Key here is the point: the harness
+ * should not hold a second copy of how E makes a key event. */
 static void
 _wl_test_cb_key(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, const char *keyname, uint32_t pressed)
 {
-   if (pressed)
-     evas_event_feed_key_down(e_comp->evas, keyname, keyname, NULL, NULL, 0, NULL);
-   else
-     evas_event_feed_key_up(e_comp->evas, keyname, keyname, NULL, NULL, 0, NULL);
+   e_comp_wl_input_keyboard_event_generate(keyname, 0, !pressed);
 }
 
 static void
@@ -676,6 +692,38 @@ _input_cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
    return ECORE_CALLBACK_RENEW;
 }
 
+/* Keys need the canvas half too, for the same reason and in the same position.
+ *
+ * E's own widgets - dialogs, entries, the file manager - take keys as evas
+ * events, and on a real backend ecore_evas is what carries an ecore key event
+ * across to the canvas. The buffer backend has no such bridge, so without
+ * these two an internal window could never be typed into. Registering at
+ * ordinary priority puts them after E's own E_LIST_HANDLER_APPEND_PRE
+ * handlers, which is where ecore_evas would sit: a binding that claims the key
+ * answers ECORE_CALLBACK_CANCEL and the canvas never hears about it, which is
+ * the behaviour a real session has. */
+static Eina_Bool
+_input_cb_key_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
+{
+   Ecore_Event_Key *ev = event;
+
+   if (ev->window != e_comp->ee_win) return ECORE_CALLBACK_RENEW;
+   evas_event_feed_key_down(e_comp->evas, ev->keyname, ev->key, ev->string,
+                            ev->compose, ev->timestamp, NULL);
+   return ECORE_CALLBACK_RENEW;
+}
+
+static Eina_Bool
+_input_cb_key_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
+{
+   Ecore_Event_Key *ev = event;
+
+   if (ev->window != e_comp->ee_win) return ECORE_CALLBACK_RENEW;
+   evas_event_feed_key_up(e_comp->evas, ev->keyname, ev->key, ev->string,
+                          ev->compose, ev->timestamp, NULL);
+   return ECORE_CALLBACK_RENEW;
+}
+
 E_API void *
 e_modapi_init(E_Module *m)
 {
@@ -706,6 +754,10 @@ e_modapi_init(E_Module *m)
                          _input_cb_mouse_down, NULL);
    E_LIST_HANDLER_APPEND(_input_handlers, ECORE_EVENT_MOUSE_BUTTON_UP,
                          _input_cb_mouse_up, NULL);
+   E_LIST_HANDLER_APPEND(_input_handlers, ECORE_EVENT_KEY_DOWN,
+                         _input_cb_key_down, NULL);
+   E_LIST_HANDLER_APPEND(_input_handlers, ECORE_EVENT_KEY_UP,
+                         _input_cb_key_up, NULL);
 
    INF("wl_test: test interface active - this build is not for production");
 
diff --git a/src/tests/wayland/e_wl_testkit.c b/src/tests/wayland/e_wl_testkit.c
index c1b3fad6b..1e3c9876f 100644
--- a/src/tests/wayland/e_wl_testkit.c
+++ b/src/tests/wayland/e_wl_testkit.c
@@ -773,3 +773,10 @@ tk_pointer_button(Tk *tk, unsigned int button, int pressed)
    wl_test_pointer_button(tk->tester, button, pressed ? 1 : 0);
    tk_settle(tk);
 }
+
+void
+tk_key(Tk *tk, const char *keyname, int pressed)
+{
+   wl_test_key(tk->tester, keyname, pressed ? 1 : 0);
+   tk_settle(tk);
+}
diff --git a/src/tests/wayland/e_wl_testkit.h b/src/tests/wayland/e_wl_testkit.h
index b360d2a27..1c7170d4f 100644
--- a/src/tests/wayland/e_wl_testkit.h
+++ b/src/tests/wayland/e_wl_testkit.h
@@ -241,4 +241,16 @@ void tk_pointer_move(Tk *tk, int dx, int dy);
 void tk_pointer_button(Tk *tk, unsigned int button, int pressed);
 #define TK_BTN_LEFT 0x110
 
+/* Press or release a key, named the way E's own keyboard_send action names
+ * them: a single character for the printable ones ("a", "1", " ") and an X
+ * keysym name for the rest ("Escape", "Control_L"). The compositor turns the
+ * name into a keycode through the active xkb keymap, so a test never has to
+ * know what keycode this machine's layout uses.
+ *
+ * This posts a real ecore key event. Worth stating because it used to feed
+ * evas instead, and evas is the wrong end of the keyboard - wl_keyboard, key
+ * bindings and the idle timer all hang off the ecore half, so a test that
+ * pressed a key reached none of them. */
+void tk_key(Tk *tk, const char *keyname, int pressed);
+
 #endif
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 5ac8ca09a..1bbcf91b2 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -92,6 +92,7 @@ wl_protocol_tests = [
   ['xdg-foreign', 'test_xdg_foreign.c'],
   ['content-type', 'test_content_type.c'],
   ['toplevel-drag', 'test_toplevel_drag.c'],
+  ['keyboard', 'test_keyboard.c'],
   ['seat-capabilities', 'test_seat_capabilities.c'],
   ['dialog-focus', 'test_dialog_focus.c'],
   ['text-input', 'test_text_input.c'],
diff --git a/src/tests/wayland/test_keyboard.c b/src/tests/wayland/test_keyboard.c
new file mode 100644
index 000000000..be732e8a9
--- /dev/null
+++ b/src/tests/wayland/test_keyboard.c
@@ -0,0 +1,213 @@
+/* A key pressed by the harness reaches the focused client as wl_keyboard.key.
+ *
+ * That sounds like a test of the obvious, and it is the test this suite most
+ * needed. `wl_test.key` used to call evas_event_feed_key_down, and evas is the
+ * wrong end of the keyboard: nothing a Wayland client can see hangs off it.
+ * wl_keyboard.key is sent from e_comp_wl_key_down, bindings are evaluated by
+ * e_bindings_key_down_event_handle, and the idle timer is re-armed by
+ * e_comp_canvas_notidle - all three from an ECORE_EVENT_KEY_DOWN handler in
+ * e_comp_canvas.c. So no key this suite pressed had ever been delivered to a
+ * client at all, and no assertion about one could fail.
+ *
+ * What is checked here, in the order the protocol says it happens:
+ *
+ *   * a focused client gets a keycode and a state for a press and a release,
+ *     and they are the same keycode;
+ *   * the keycode is the evdev one, which is the xkb keycode minus 8. This is
+ *     the single most common off-by-eight in a compositor, and it is invisible
+ *     from inside: the compositor knows what it meant, and only the client can
+ *     say what arrived. "a" is evdev 30;
+ *   * the serial advances between the press and the release, because a client
+ *     that wants to start a drag or open a popup has to quote one;
+ *   * a key pressed while the client is *not* focused does not arrive.
+ *
+ * The last one is the reason this is worth having beyond the harness change:
+ * without it a compositor that broadcast every key to every client would pass
+ * everything above.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "e_wl_testkit.h"
+
+#define PROG "test-keyboard"
+
+/* linux/input-event-codes.h KEY_A. The compositor works in xkb keycodes, which
+ * are these plus 8, and wl_keyboard.key carries the evdev number. */
+#define EVDEV_KEY_A 30
+
+typedef struct
+{
+   struct wl_surface *entered;
+   int enters, leaves;
+
+   uint32_t last_key, last_state, last_serial;
+   int keys;
+   int mods;
+} Kbd;
+
+static void
+_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
+_enter(void *data, struct wl_keyboard *k, uint32_t serial,
+       struct wl_surface *surface, struct wl_array *keys)
+{
+   Kbd *w = data;
+
+   (void)k; (void)serial; (void)keys;
+   w->entered = surface;
+   w->enters++;
+}
+
+static void
+_leave(void *data, struct wl_keyboard *k, uint32_t serial,
+       struct wl_surface *surface)
+{
+   Kbd *w = data;
+
+   (void)k; (void)serial;
+   if (w->entered == surface) w->entered = NULL;
+   w->leaves++;
+}
+
+static void
+_key(void *data, struct wl_keyboard *k, uint32_t serial, uint32_t time,
+     uint32_t key, uint32_t state)
+{
+   Kbd *w = data;
+
+   (void)k; (void)time;
+   w->last_serial = serial;
+   w->last_key = key;
+   w->last_state = state;
+   w->keys++;
+}
+
+static void
+_mods(void *data, struct wl_keyboard *k, uint32_t se, uint32_t a, uint32_t b,
+      uint32_t c, uint32_t g)
+{
+   Kbd *w = data;
+
+   (void)k; (void)se; (void)a; (void)b; (void)c; (void)g;
+   w->mods++;
+}
+
+static void
+_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 _listener =
+{ _keymap, _enter, _leave, _key, _mods, _repeat };
+
+int
+main(void)
+{
+   Tk *tk, *other;
+   Tk_Toplevel *top, *thief;
+   struct wl_seat *seat;
+   struct wl_keyboard *kbd;
+   Kbd w = { 0 };
+   uint32_t version, down_serial, down_key;
+   int keys_before;
+
+   tk = tk_connect(PROG);
+
+   version = tk_global_version(tk, "wl_seat");
+   if (!version) tk_fail(tk, "no wl_seat");
+   seat = tk_bind(tk, &wl_seat_interface, 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 capability");
+   wl_keyboard_add_listener(kbd, &_listener, &w);
+   tk_sync(tk);
+
+   top = tk_toplevel_new(tk, "keyboard", "keyboard", 200, 200);
+   tk_settle(tk);
+   tk_sync(tk);
+
+   if (w.entered != tk_toplevel_surface(top))
+     tk_fail(tk, "the window did not get keyboard focus when it mapped "
+                 "(%d enters, %d leaves); nothing below would mean anything",
+             w.enters, w.leaves);
+
+   /* ------------------------------------------------------------- press */
+
+   tk_key(tk, "a", 1);
+   tk_sync(tk);
+
+   if (w.keys != 1)
+     tk_fail(tk, "pressing a key delivered %d wl_keyboard.key events, expected "
+                 "1. If this is 0 the key never left the compositor - check "
+                 "that the harness posts an ecore event rather than feeding "
+                 "evas, because only the ecore path reaches "
+                 "e_comp_wl_key_down", w.keys);
+   if (w.last_state != WL_KEYBOARD_KEY_STATE_PRESSED)
+     tk_fail(tk, "the press arrived with state %u, expected %d",
+             w.last_state, WL_KEYBOARD_KEY_STATE_PRESSED);
+   if (w.last_key != EVDEV_KEY_A)
+     tk_fail(tk, "the press arrived as keycode %u, expected %d (evdev KEY_A). "
+                 "%u is what you get by sending the xkb keycode without "
+                 "subtracting 8, and a client that trusts it reads every key "
+                 "as a different one", w.last_key, EVDEV_KEY_A,
+             w.last_key);
+   down_serial = w.last_serial;
+   down_key = w.last_key;
+
+   /* ----------------------------------------------------------- release */
+
+   tk_key(tk, "a", 0);
+   tk_sync(tk);
+
+   if (w.keys != 2)
+     tk_fail(tk, "releasing the key delivered no event (%d in total) - a key "
+                 "that goes down and never comes up leaves the client "
+                 "repeating it for ever", w.keys);
+   if (w.last_state != WL_KEYBOARD_KEY_STATE_RELEASED)
+     tk_fail(tk, "the release arrived with state %u, expected %d",
+             w.last_state, WL_KEYBOARD_KEY_STATE_RELEASED);
+   if (w.last_key != down_key)
+     tk_fail(tk, "the release named keycode %u but the press named %u",
+             w.last_key, down_key);
+   if (w.last_serial == down_serial)
+     tk_fail(tk, "press and release carried the same serial %u - serials are "
+                 "how a client proves which event it is acting on when it "
+                 "starts a drag or opens a popup", down_serial);
+
+   /* -------------------------------------------- not focused, not yours */
+
+   keys_before = w.keys;
+
+   other = tk_connect(PROG "-other");
+   thief = tk_toplevel_new(other, "keyboard-thief", "thief", 200, 200);
+   tk_settle(other);
+   tk_sync(other);
+   tk_sync(tk);
+
+   if (w.entered)
+     tk_fail(tk, "the first client still holds keyboard focus after another "
+                 "client mapped a window over it (%d enters, %d leaves)",
+             w.enters, w.leaves);
+
+   tk_key(other, "a", 1);
+   tk_key(other, "a", 0);
+   tk_sync(tk);
+
+   if (w.keys != keys_before)
+     tk_fail(tk, "an unfocused client received %d key events - keys go to the "
+                 "focused surface, and a compositor that broadcasts them hands "
+                 "every password to every window on the screen",
+             w.keys - keys_before);
+
+   printf(PROG ": ok - keycode %u down and up on the focused window, %d "
+          "serials, nothing to the unfocused one\n", down_key, w.keys);
+
+   (void)thief;
+   tk_disconnect(other);
+   tk_disconnect(tk);
+   return 0;
+}

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

Reply via email to