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 e6c00daa6afe23fa6eb88b2c1d46420f9b57e3e4
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 16 21:46:41 2026 -0600

    e_comp_wl - implement zwp_keyboard_shortcuts_inhibit_manager_v1
    
    Lets a client ask for the keys E would otherwise eat. A browser in
    fullscreen, a remote desktop in a tab, a terminal emulator inside a web
    page - all of them want Ctrl+Alt+something to reach them rather than
    switch desktops, and without this there is no way to ask and no way to be
    told whether the asking worked.
    
    Two halves. The bookkeeping half is one inhibitor per surface per seat,
    an error for a second, and active/inactive as the surface gains and loses
    the keyboard. Those events are not decoration: a client that believes it
    is inhibiting when it is not sits waiting for a key E has already
    swallowed, so it has to know each time the answer changes.
    
    The half that does the work is one condition in e_comp_canvas.c. A key is
    offered to e_bindings first and reaches the client only if no binding
    claimed it; while an inhibitor is in force the bindings are skipped
    rather than consulted. Both halves of the keystroke are skipped together,
    because swallowing only the press leaves the client with a key that went
    down and never came up.
    
    E has one seat, so the seat argument is accepted and otherwise unused.
    Per-seat inhibitors would want a resource per seat on the surface rather
    than the single slot in comp_data.
    
    The in-tree test covers the focus tracking and the duplicate error, and
    was checked against a build with the focus-unset notification removed to
    be sure it could fail. It deliberately does not test the binding
    suppression: wl_test's key request feeds evas directly, while bindings
    are evaluated on the ecore side, so a synthesised key never passes the
    code the inhibitor suspends and a test written against it would pass
    either way. Making that testable means teaching the harness to post a
    real ECORE_EVENT_KEY_DOWN, the same way _pointer_motion had to be taught
    to reach the ecore half for pointer-constraints and for the idle timer.
    
    wlcs has no test for this protocol.
---
 src/bin/e_comp_canvas.c                    |  20 ++-
 src/bin/e_comp_wl.h                        |   9 +
 src/bin/e_comp_wl_extensions.c             | 132 ++++++++++++++
 src/bin/generated/meson.build              |   1 +
 src/tests/wayland/globals.expected         |   1 +
 src/tests/wayland/meson.build              |   2 +
 src/tests/wayland/test_shortcuts_inhibit.c | 266 +++++++++++++++++++++++++++++
 7 files changed, 429 insertions(+), 2 deletions(-)

diff --git a/src/bin/e_comp_canvas.c b/src/bin/e_comp_canvas.c
index 215ae8d3a..e34d4b59e 100644
--- a/src/bin/e_comp_canvas.c
+++ b/src/bin/e_comp_canvas.c
@@ -1,5 +1,13 @@
 #include "e.h"
 
+/* zwp_keyboard_shortcuts_inhibit_manager_v1 is a wayland protocol, and this
+ * file is built either way. */
+#ifdef HAVE_WAYLAND
+# define SHORTCUTS_INHIBITED() e_comp_wl_shortcuts_inhibited_get()
+#else
+# define SHORTCUTS_INHIBITED() EINA_FALSE
+#endif
+
 static Eina_List *handlers;
 static Ecore_Timer *timer_post_screensaver_lock = NULL;
 static Ecore_Timer *timer_post_screensaver_on = NULL;
@@ -210,9 +218,14 @@ _key_down(int ctx, Ecore_Event_Key *ev)
          */
         if ((!ec) || (ev->event_window != e_comp->ee_win)) return ECORE_CALLBACK_RENEW;
      }
+   /* A client holding a zwp_keyboard_shortcuts_inhibitor_v1 asked for the keys
+    * E would otherwise swallow, so its bindings are skipped rather than
+    * consulted - a binding that claims the key stops the chain here and the
+    * client never sees it, which is exactly what the inhibitor is for. */
    return ((!e_comp->screen) ||
            (!e_comp->screen->key_down) || (!e_comp->screen->key_down(ev))) &&
-     !e_bindings_key_down_event_handle(ctx, E_OBJECT(e_comp), ev)
+     (SHORTCUTS_INHIBITED() ||
+      !e_bindings_key_down_event_handle(ctx, E_OBJECT(e_comp), ev))
 #ifdef HAVE_WAYLAND
        && !e_comp_wl_key_down(ev, NULL)
 #endif
@@ -248,9 +261,12 @@ _key_up(int ctx, Ecore_Event_Key *ev)
           }
      }
    if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root)) return ECORE_CALLBACK_PASS_ON;
+   /* The release has to follow the press: swallowing one half of a shortcut
+    * leaves the client with a key that went down and never came up. */
    return ((!e_comp->screen) ||
            (!e_comp->screen->key_up) || (!e_comp->screen->key_up(ev))) &&
-     !e_bindings_key_up_event_handle(ctx, E_OBJECT(e_comp), ev)
+     (SHORTCUTS_INHIBITED() ||
+      !e_bindings_key_up_event_handle(ctx, E_OBJECT(e_comp), ev))
 #ifdef HAVE_WAYLAND
        && !e_comp_wl_key_up(ev, NULL)
 #endif
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index e959505b2..d0a210b5c 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -190,6 +190,10 @@ typedef struct E_Comp_Wl_Extension_Data
      {
         struct wl_global *global;
      } zwp_idle_inhibit_manager_v1;
+   struct
+     {
+        struct wl_global *global;
+     } zwp_keyboard_shortcuts_inhibit_manager_v1;
    struct
      {
         struct wl_global *global;
@@ -414,6 +418,10 @@ struct _E_Comp_Wl_Client_Data
     * exactly one per surface and has an error for the second, so the surface
     * is where the answer to "is there already one" lives. */
    struct wl_resource *fractional_scale_resource;
+   /* The one zwp_keyboard_shortcuts_inhibitor_v1 this surface is allowed for
+    * E's single seat. The protocol has an already_inhibited error for the
+    * second, so the surface is where "is there one" has to live. */
+   struct wl_resource *shortcuts_inhibitor_resource;
    /* wl_surface.set_buffer_scale and set_buffer_transform currently in
     * effect. The scale is never below 1; the transform is the value the
     * client says it has ALREADY applied, so displaying the buffer means
@@ -578,6 +586,7 @@ E_API void e_comp_wl_notidle(void);
 E_API void e_comp_wl_idle_inhibit_add(void);
 E_API void e_comp_wl_idle_inhibit_del(void);
 E_API Eina_Bool e_comp_wl_idle_inhibited_get(void);
+E_API Eina_Bool e_comp_wl_shortcuts_inhibited_get(void);
 E_API void e_comp_wl_screensaver_activate(void);
 E_API void e_comp_wl_screensaver_inhibit(Eina_Bool inhibit);
 
diff --git a/src/bin/e_comp_wl_extensions.c b/src/bin/e_comp_wl_extensions.c
index 58ee0ae47..dc6f93dc8 100644
--- a/src/bin/e_comp_wl_extensions.c
+++ b/src/bin/e_comp_wl_extensions.c
@@ -12,6 +12,7 @@
 #include "xdg-output-unstable-v1-server-protocol.h"
 #include "fractional-scale-v1-server-protocol.h"
 #include "idle-inhibit-unstable-v1-server-protocol.h"
+#include "keyboard-shortcuts-inhibit-unstable-v1-server-protocol.h"
 
 /* mutter uses 32, seems reasonable */
 #define HANDLE_LEN 32
@@ -117,16 +118,22 @@ _constraints_deactivate(E_Client *ec)
      }
 }
 
+/* Defined with the rest of zwp_keyboard_shortcuts_inhibit_manager_v1 below;
+ * the focus hooks are up here with the pointer constraints they share. */
+static void _e_shortcuts_inhibit_update(E_Client *ec, Eina_Bool active);
+
 static void
 _e_comp_wl_extensions_client_focus_set(void *d EINA_UNUSED, E_Client *ec)
 {
    _constraints_activate(ec);
+   _e_shortcuts_inhibit_update(ec, EINA_TRUE);
 }
 
 static void
 _e_comp_wl_extensions_client_focus_unset(void *d EINA_UNUSED, E_Client *ec)
 {
    _constraints_deactivate(ec);
+   _e_shortcuts_inhibit_update(ec, EINA_FALSE);
 }
 
 static void
@@ -1637,6 +1644,129 @@ static const struct zwp_idle_inhibit_manager_v1_interface _e_zwp_idle_inhibit_ma
    _e_zwp_idle_inhibit_manager_v1_cb_inhibitor_create,
 };
 
+
+/* zwp_keyboard_shortcuts_inhibit_manager_v1.
+ *
+ * Lets a client ask for the keys E would otherwise eat. A browser in
+ * fullscreen, a remote desktop or a terminal emulator inside a web page all
+ * want Ctrl+Alt+something to reach them rather than switch desktops, and
+ * without this there is no way for them to say so and no way to be told
+ * whether they were heard.
+ *
+ * Two halves. The protocol half is bookkeeping: one inhibitor per surface per
+ * seat, an error for a second, and active/inactive events as the surface gains
+ * and loses keyboard focus - a client has to know whether its request is in
+ * force, because if it is not then the key it is waiting for is never coming.
+ *
+ * The half that does the work is in e_comp_canvas.c, where a key event is
+ * offered to e_bindings first and reaches the client only if no binding claimed
+ * it. That is the swallow this protocol exists to suspend.
+ *
+ * E has one seat, so the seat argument is accepted and not otherwise used;
+ * per-seat inhibitors would need the surface to hold one resource per seat
+ * instead of the single slot in comp_data. */
+
+static void
+_e_zwp_keyboard_shortcuts_inhibitor_v1_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+   wl_resource_destroy(resource);
+}
+
+static const struct zwp_keyboard_shortcuts_inhibitor_v1_interface _e_zwp_keyboard_shortcuts_inhibitor_v1_interface =
+{
+   _e_zwp_keyboard_shortcuts_inhibitor_v1_cb_destroy,
+};
+
+/* called by wl_resource_destroy */
+static void
+_e_zwp_keyboard_shortcuts_inhibitor_v1_res_destroy(struct wl_resource *resource)
+{
+   E_Client *ec = wl_resource_get_user_data(resource);
+
+   if ((!ec) || e_object_is_del(E_OBJECT(ec)) || (!ec->comp_data)) return;
+   if (ec->comp_data->shortcuts_inhibitor_resource == resource)
+     ec->comp_data->shortcuts_inhibitor_resource = NULL;
+}
+
+/* An inhibitor is in force exactly while its surface has the keyboard. */
+static void
+_e_shortcuts_inhibit_update(E_Client *ec, Eina_Bool active)
+{
+   struct wl_resource *res;
+
+   if ((!ec) || (!ec->comp_data)) return;
+   res = ec->comp_data->shortcuts_inhibitor_resource;
+   if (!res) return;
+
+   if (active)
+     zwp_keyboard_shortcuts_inhibitor_v1_send_active(res);
+   else
+     zwp_keyboard_shortcuts_inhibitor_v1_send_inactive(res);
+}
+
+E_API Eina_Bool
+e_comp_wl_shortcuts_inhibited_get(void)
+{
+   E_Client *ec = e_client_focused_get();
+
+   if ((!ec) || e_object_is_del(E_OBJECT(ec)) || (!ec->comp_data))
+     return EINA_FALSE;
+   return !!ec->comp_data->shortcuts_inhibitor_resource;
+}
+
+static void
+_e_zwp_keyboard_shortcuts_inhibit_manager_v1_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+   wl_resource_destroy(resource);
+}
+
+static void
+_e_zwp_keyboard_shortcuts_inhibit_manager_v1_cb_inhibit_shortcuts(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface, struct wl_resource *seat EINA_UNUSED)
+{
+   struct wl_resource *res;
+   E_Client *ec;
+
+   ec = wl_resource_get_user_data(surface);
+   if ((!ec) || e_object_is_del(E_OBJECT(ec)) || (!ec->comp_data))
+     {
+        wl_resource_post_error(resource,
+                               ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED,
+                               "wl_surface is gone");
+        return;
+     }
+   if (ec->comp_data->shortcuts_inhibitor_resource)
+     {
+        wl_resource_post_error(resource,
+                               ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED,
+                               "wl_surface already inhibits shortcuts on this seat");
+        return;
+     }
+
+   res = wl_resource_create(client,
+                            &zwp_keyboard_shortcuts_inhibitor_v1_interface,
+                            wl_resource_get_version(resource), id);
+   if (!res)
+     {
+        wl_client_post_no_memory(client);
+        return;
+     }
+   wl_resource_set_implementation(res,
+                                  &_e_zwp_keyboard_shortcuts_inhibitor_v1_interface, ec,
+                                  _e_zwp_keyboard_shortcuts_inhibitor_v1_res_destroy);
+   ec->comp_data->shortcuts_inhibitor_resource = res;
+
+   /* Asked for by a surface that already has the keyboard, so it is in force
+    * from this moment and the client is entitled to know without waiting for
+    * a focus change that may never come. */
+   _e_shortcuts_inhibit_update(ec, ec == e_client_focused_get());
+}
+
+static const struct zwp_keyboard_shortcuts_inhibit_manager_v1_interface _e_zwp_keyboard_shortcuts_inhibit_manager_v1_interface =
+{
+   _e_zwp_keyboard_shortcuts_inhibit_manager_v1_cb_destroy,
+   _e_zwp_keyboard_shortcuts_inhibit_manager_v1_cb_inhibit_shortcuts,
+};
+
 #define GLOBAL_BIND_CB(NAME, IFACE, ...) \
 static void \
 _e_comp_wl_##NAME##_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id) \
@@ -1664,6 +1794,7 @@ GLOBAL_BIND_CB(xdg_activation_v1, xdg_activation_v1_interface)
 GLOBAL_BIND_CB(zxdg_output_manager_v1, zxdg_output_manager_v1_interface)
 GLOBAL_BIND_CB(wp_fractional_scale_manager_v1, wp_fractional_scale_manager_v1_interface)
 GLOBAL_BIND_CB(zwp_idle_inhibit_manager_v1, zwp_idle_inhibit_manager_v1_interface)
+GLOBAL_BIND_CB(zwp_keyboard_shortcuts_inhibit_manager_v1, zwp_keyboard_shortcuts_inhibit_manager_v1_interface)
 GLOBAL_BIND_CB(action_route, action_route_interface,
      e_binding_key_list_cb = _action_route_key_list_cb;
      key_bindings = eina_hash_string_superfast_new(NULL);
@@ -1767,6 +1898,7 @@ e_comp_wl_extensions_init(void)
    GLOBAL_CREATE_OR_RETURN(zxdg_output_manager_v1, zxdg_output_manager_v1_interface, 3);
    GLOBAL_CREATE_OR_RETURN(wp_fractional_scale_manager_v1, wp_fractional_scale_manager_v1_interface, 1);
    GLOBAL_CREATE_OR_RETURN(zwp_idle_inhibit_manager_v1, zwp_idle_inhibit_manager_v1_interface, 1);
+   GLOBAL_CREATE_OR_RETURN(zwp_keyboard_shortcuts_inhibit_manager_v1, zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
 
    ecore_event_handler_add(ECORE_WL2_EVENT_SYNC_DONE, _dmabuf_add, NULL);
    ecore_event_handler_add(E_EVENT_CLIENT_ZONE_SET,
diff --git a/src/bin/generated/meson.build b/src/bin/generated/meson.build
index 9999fe9a6..a8c4790cf 100644
--- a/src/bin/generated/meson.build
+++ b/src/bin/generated/meson.build
@@ -12,6 +12,7 @@ protos = [
   '@0@/unstable/primary-selection/primary-selection-unstable-v1.xml'.format(dir_wayland_protocols),
   '@0@/staging/fractional-scale/fractional-scale-v1.xml'.format(dir_wayland_protocols),
   '@0@/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'.format(dir_wayland_protocols),
+  '@0@/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'.format(dir_wayland_protocols),
 ]
 
 proto_c = []
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 7bfc6230f..e374ea9f2 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -13,6 +13,7 @@ xdg_activation_v1	1
 xdg_wm_base	6
 zwp_e_session_recovery	1
 zwp_idle_inhibit_manager_v1	1
+zwp_keyboard_shortcuts_inhibit_manager_v1	1
 zwp_pointer_constraints_v1	1
 zwp_primary_selection_device_manager_v1	1
 zwp_relative_pointer_manager_v1	1
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 8cd8ead51..6485b05d8 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -33,6 +33,7 @@ foreach p: [
   '@0@/staging/xdg-activation/xdg-activation-v1.xml'.format(dir_wayland_protocols),
   '@0@/staging/fractional-scale/fractional-scale-v1.xml'.format(dir_wayland_protocols),
   '@0@/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'.format(dir_wayland_protocols),
+  '@0@/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'.format(dir_wayland_protocols),
 ]
   test_proto_src += gen_scanner_client.process(p)
   test_proto_src += gen_scanner_impl.process(p)
@@ -60,6 +61,7 @@ wl_protocol_tests = [
   ['buffer-scale', 'test_buffer_scale.c'],
   ['fractional-scale', 'test_fractional_scale.c'],
   ['idle-inhibit', 'test_idle_inhibit.c'],
+  ['shortcuts-inhibit', 'test_shortcuts_inhibit.c'],
 ]
 
 foreach t: wl_protocol_tests
diff --git a/src/tests/wayland/test_shortcuts_inhibit.c b/src/tests/wayland/test_shortcuts_inhibit.c
new file mode 100644
index 000000000..b2f753345
--- /dev/null
+++ b/src/tests/wayland/test_shortcuts_inhibit.c
@@ -0,0 +1,266 @@
+/* Does a shortcuts inhibitor track keyboard focus, and refuse a second one?
+ *
+ * zwp_keyboard_shortcuts_inhibit_manager_v1 has two halves. This test covers
+ * the one that is observable from a client: an inhibitor is in force exactly
+ * while its surface holds the keyboard, and the client is told each time that
+ * changes. It matters because a client that believes it is inhibiting when it
+ * is not sits waiting for a key that E has already swallowed.
+ *
+ * The other half - E skipping its own bindings while an inhibitor is active -
+ * is NOT tested here, and cannot be from this harness. wl_test's key request
+ * feeds evas directly (see _wl_test_cb_key), while bindings are evaluated in
+ * _key_down() in e_comp_canvas.c, on the ecore side. So a synthesised key
+ * never passes the code the inhibitor suspends, and a test written against it
+ * would pass whether or not the suppression existed. Making it testable means
+ * teaching the harness to post a real ECORE_EVENT_KEY_DOWN, the same way
+ * _pointer_motion had to be taught to reach the ecore half for
+ * pointer-constraints and, later, for the idle timer.
+ *
+ * wlcs has no test for this protocol at all.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <wayland-client.h>
+#include "wl-test-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
+
+#define W 200
+#define H 150
+
+#define FAIL(fmt, ...) \
+  do { fprintf(stderr, "test-shortcuts-inhibit: " fmt "\n", ##__VA_ARGS__); return 1; } while (0)
+
+static struct wl_compositor *compositor;
+static struct wl_shm *shm;
+static struct xdg_wm_base *wm_base;
+static struct wl_seat *seat;
+static struct wl_test *tester;
+static struct zwp_keyboard_shortcuts_inhibit_manager_v1 *sc_manager;
+static struct wl_registry *reg;
+static struct wl_display *disp;
+static int sync_done;
+
+static int n_active, n_inactive;
+
+static void _sc_active(void *d, struct zwp_keyboard_shortcuts_inhibitor_v1 *i)
+{ (void)d; (void)i; n_active++; }
+static void _sc_inactive(void *d, struct zwp_keyboard_shortcuts_inhibitor_v1 *i)
+{ (void)d; (void)i; n_inactive++; }
+static const struct zwp_keyboard_shortcuts_inhibitor_v1_listener _sc_listener =
+{ _sc_active, _sc_inactive };
+
+static void _wm_ping(void *d, struct xdg_wm_base *b, uint32_t serial)
+{ (void)d; xdg_wm_base_pong(b, serial); }
+static const struct xdg_wm_base_listener _wm_listener = { _wm_ping };
+
+static int configured;
+static void _xdg_conf(void *d, struct xdg_surface *s, uint32_t serial)
+{ (void)d; xdg_surface_ack_configure(s, serial); configured = 1; }
+static const struct xdg_surface_listener _xdg_listener = { _xdg_conf };
+
+static void _top_conf(void *d, struct xdg_toplevel *t, int32_t w, int32_t h, struct wl_array *st)
+{ (void)d; (void)t; (void)w; (void)h; (void)st; }
+static void _top_close(void *d, struct xdg_toplevel *t) { (void)d; (void)t; }
+static const struct xdg_toplevel_listener _top_listener = { _top_conf, _top_close };
+
+static void _sync_done(void *d, struct wl_test *t) { (void)d; (void)t; sync_done = 1; }
+static void _client_serial(void *d, struct wl_test *t, uint32_t s) { (void)d; (void)t; (void)s; }
+static void _surface_info(void *d, struct wl_test *t, struct wl_surface *s, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t vis, uint32_t foc)
+{ (void)d; (void)t; (void)s; (void)x; (void)y; (void)w; (void)h; (void)vis; (void)foc; }
+static void _surface_unknown(void *d, struct wl_test *t, struct wl_surface *s)
+{ (void)d; (void)t; (void)s; }
+static void _surface_buffer_info(void *d, struct wl_test *t, struct wl_surface *s, int32_t bs, int32_t bt, int32_t os)
+{ (void)d; (void)t; (void)s; (void)bs; (void)bt; (void)os; }
+static void _idle_inhibit(void *d, struct wl_test *t, uint32_t v)
+{ (void)d; (void)t; (void)v; }
+static const struct wl_test_listener _tester_listener =
+{
+   _client_serial, _surface_info, _surface_unknown, _sync_done,
+   _surface_buffer_info, _idle_inhibit
+};
+
+static void
+_global(void *data, struct wl_registry *r, uint32_t id, const char *iface, uint32_t ver)
+{
+   (void)data; (void)r; (void)ver;
+   if (!strcmp(iface, "wl_compositor"))
+     compositor = wl_registry_bind(reg, id, &wl_compositor_interface, 4);
+   else if (!strcmp(iface, "wl_shm"))
+     shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
+   else if (!strcmp(iface, "wl_seat"))
+     seat = wl_registry_bind(reg, id, &wl_seat_interface, 5);
+   else if (!strcmp(iface, "xdg_wm_base"))
+     {
+        wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, 1);
+        xdg_wm_base_add_listener(wm_base, &_wm_listener, NULL);
+     }
+   else if (!strcmp(iface, "zwp_keyboard_shortcuts_inhibit_manager_v1"))
+     sc_manager = wl_registry_bind(reg, id,
+                &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
+   else if (!strcmp(iface, "wl_test"))
+     tester = wl_registry_bind(reg, id, &wl_test_interface, 3);
+}
+
+static void _global_rm(void *d, struct wl_registry *r, uint32_t id) { (void)d; (void)r; (void)id; }
+static const struct wl_registry_listener _reg_listener = { _global, _global_rm };
+
+static int
+csync(void)
+{
+   sync_done = 0;
+   wl_test_sync(tester);
+   while (!sync_done)
+     if (wl_display_dispatch(disp) < 0) return -1;
+   return wl_display_roundtrip(disp) < 0 ? -1 : 0;
+}
+
+static struct wl_buffer *
+make_buffer(const char *name)
+{
+   int fd, stride = W * 4, size = stride * H;
+   void *map;
+   struct wl_shm_pool *pool;
+   struct wl_buffer *buf;
+
+   fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+   if (fd < 0) return NULL;
+   shm_unlink(name);
+   if (ftruncate(fd, size) < 0) { close(fd); return NULL; }
+   map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+   if (map == MAP_FAILED) { close(fd); return NULL; }
+   memset(map, 0xff, size);
+   pool = wl_shm_create_pool(shm, fd, size);
+   buf = wl_shm_pool_create_buffer(pool, 0, W, H, stride, WL_SHM_FORMAT_ARGB8888);
+   wl_shm_pool_destroy(pool);
+   close(fd);
+   return buf;
+}
+
+/* A mapped, focusable toplevel. */
+static struct wl_surface *
+make_toplevel(const char *title, const char *shmname, struct xdg_toplevel **out_top,
+              struct xdg_surface **out_xdg)
+{
+   struct wl_surface *surface;
+   struct xdg_surface *xs;
+   struct xdg_toplevel *tl;
+   struct wl_buffer *buffer;
+
+   surface = wl_compositor_create_surface(compositor);
+   xs = xdg_wm_base_get_xdg_surface(wm_base, surface);
+   xdg_surface_add_listener(xs, &_xdg_listener, NULL);
+   tl = xdg_surface_get_toplevel(xs);
+   xdg_toplevel_add_listener(tl, &_top_listener, NULL);
+   xdg_toplevel_set_title(tl, title);
+   wl_surface_commit(surface);
+
+   configured = 0;
+   while (!configured)
+     if (wl_display_dispatch(disp) < 0) return NULL;
+
+   buffer = make_buffer(shmname);
+   if (!buffer) return NULL;
+   wl_surface_attach(surface, buffer, 0, 0);
+   wl_surface_damage(surface, 0, 0, W, H);
+   wl_surface_commit(surface);
+
+   *out_top = tl;
+   *out_xdg = xs;
+   return surface;
+}
+
+int
+main(void)
+{
+   struct wl_surface *sa, *sb;
+   struct xdg_toplevel *ta, *tb;
+   struct xdg_surface *xa, *xb;
+   struct zwp_keyboard_shortcuts_inhibitor_v1 *inhibitor;
+   uint32_t ecode;
+   const struct wl_interface *eiface;
+   int eid;
+
+   disp = wl_display_connect(NULL);
+   if (!disp) FAIL("cannot connect to WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY") ?: "(unset)");
+
+   reg = wl_display_get_registry(disp);
+   wl_registry_add_listener(reg, &_reg_listener, NULL);
+   if (wl_display_roundtrip(disp) < 0) FAIL("registry roundtrip failed");
+
+   if (!compositor || !shm || !wm_base || !seat) FAIL("missing core globals");
+   if (!tester) FAIL("no wl_test -- built without -Dtests=true?");
+   if (!sc_manager)
+     FAIL("zwp_keyboard_shortcuts_inhibit_manager_v1 is not advertised, so a "
+          "fullscreen web app cannot ask for the keys E swallows");
+   wl_test_add_listener(tester, &_tester_listener, NULL);
+
+   sa = make_toplevel("shortcuts-a", "/e-test-sc-a", &ta, &xa);
+   if (!sa) FAIL("could not map the first toplevel");
+   if (csync() < 0) FAIL("sync failed after mapping a");
+
+   /* Asked for by the surface that already holds the keyboard, so it is in
+    * force at once and the client should not have to wait for a focus change
+    * that may never come. */
+   n_active = n_inactive = 0;
+   inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(
+                 sc_manager, sa, seat);
+   zwp_keyboard_shortcuts_inhibitor_v1_add_listener(inhibitor, &_sc_listener, NULL);
+   if (csync() < 0) FAIL("sync failed after inhibit_shortcuts");
+
+   if (!n_active)
+     FAIL("no active event for an inhibitor taken by the focused surface. A "
+          "client that is never told cannot know whether the keys it is "
+          "waiting for will arrive");
+   if (n_inactive) FAIL("inhibitor went inactive immediately");
+   printf("test-shortcuts-inhibit: active on the focused surface\n");
+
+   /* Focus elsewhere: the inhibit lapses and the client has to hear so. */
+   n_active = n_inactive = 0;
+   sb = make_toplevel("shortcuts-b", "/e-test-sc-b", &tb, &xb);
+   if (!sb) FAIL("could not map the second toplevel");
+   if (csync() < 0) FAIL("sync failed after mapping b");
+
+   if (!n_inactive)
+     FAIL("another surface took the keyboard and the inhibitor never went "
+          "inactive, so the client still believes it is inhibiting keys that "
+          "E is now swallowing");
+   printf("test-shortcuts-inhibit: inactive when focus leaves\n");
+
+   /* And back. */
+   n_active = n_inactive = 0;
+   xdg_toplevel_destroy(tb);
+   xdg_surface_destroy(xb);
+   wl_surface_destroy(sb);
+   if (csync() < 0) FAIL("sync failed after destroying b");
+
+   if (!n_active)
+     FAIL("focus returned to the inhibiting surface and it was never told it "
+          "was in force again");
+   printf("test-shortcuts-inhibit: active again when focus returns\n");
+
+   /* A second inhibitor for the same surface and seat is a protocol error.
+    * This kills the connection, so it goes last. */
+   zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(sc_manager, sa, seat);
+   wl_display_roundtrip(disp);
+
+   if (wl_display_get_error(disp) != EPROTO)
+     FAIL("a second inhibitor on the same surface and seat was accepted; the "
+          "protocol says already_inhibited");
+   ecode = wl_display_get_protocol_error(disp, &eiface, &eid);
+   if (eiface != &zwp_keyboard_shortcuts_inhibit_manager_v1_interface)
+     FAIL("already_inhibited was posted against %s, expected the manager",
+          eiface ? eiface->name : "(nothing)");
+   if (ecode != ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED)
+     FAIL("duplicate inhibitor raised error %u, expected already_inhibited (%u)",
+          ecode, ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED);
+   printf("test-shortcuts-inhibit: a second inhibitor is already_inhibited\n");
+
+   printf("test-shortcuts-inhibit: ok\n");
+   return 0;
+}

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

Reply via email to