diff --git a/keyboard-shortcuts-inhibit-unstable-v1.xml b/keyboard-shortcuts-inhibit-unstable-v1.xml
new file mode 100644
index 0000000..2774876
--- /dev/null
+++ b/keyboard-shortcuts-inhibit-unstable-v1.xml
@@ -0,0 +1,143 @@
+
+
+
+
+ Copyright © 2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+ This protocol specifies a way for a client to request the compositor
+ to ignore its own keyboard shortcuts for a given seat, so that all
+ key events from that seat get forwarded to a surface.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible
+ changes may be added together with the corresponding interface
+ version bump.
+ Backward incompatible changes are done by bumping the version
+ number in the protocol and interface names and resetting the
+ interface version. Once the protocol is to be declared stable,
+ the 'z' prefix and the version number in the protocol and
+ interface names are removed and the interface version number is
+ reset.
+
+
+
+
+ A global interface used for inhibiting the compositor keyboard shortcuts.
+
+
+
+
+ Destroy the keyboard shortcuts inhibitor manager.
+
+
+
+
+
+ Create a new keyboard shortcuts inhibitor object associated with
+ the given surface for the given seat.
+
+ If shortcuts are already inhibited for the specified seat and surface,
+ a protocol error "already_inhibited" is raised by the compositor.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A keyboard shortcuts inhibitor instructs the compositor to ignore
+ its own keyboard shortcuts when the associated surface has keyboard
+ focus. As a result, when the surface has keyboard focus on the given
+ seat, it will receive all key events originating from the specified
+ seat, even those which would normally be caught by the compositor for
+ its own shortcuts.
+
+ The Wayland compositor is however under no obligation to disable
+ all of its shortcuts, and may keep some special key combo for its own
+ use, including but not limited to one allowing the user to forcibly
+ restore normal keyboard events routing in the case of an unwilling
+ client. The compositor may also use the same key combo to reactivate
+ an existing shortcut inhibitor that was previously deactivated on
+ user request.
+
+ When the compositor restores its own keyboard shortcuts, an
+ "inactive" event is emitted to notify the client that the keyboard
+ shortcuts inhibitor is not effectively active for the surface and
+ seat any more, and the client should not expect to receive all
+ keyboard events.
+
+ When the keyboard shortcuts inhibitor is inactive, the client has
+ no way to forcibly reactivate the keyboard shortcuts inhibitor.
+
+ The user can chose to re-enable a previously deactivated keyboard
+ shortcuts inhibitor using any mechanism the compositor may offer,
+ in which case the compositor will send an "active" event to notify
+ the client.
+
+ If the surface is destroyed, unmapped, or loses the seat's keyboard
+ focus, the keyboard shortcuts inhibitor becomes irrelevant and the
+ compositor will restore its own keyboard shortcuts but no "inactive"
+ event is emitted in this case.
+
+
+
+
+ Remove the keyboard shortcuts inhibitor from the associated wl_surface.
+
+
+
+
+
+ This event indicates that the shortcut inhibitor is active.
+
+ The compositor sends this event every time compositor shortcuts
+ are inhibited on behalf of the surface. When active, the client
+ may receive input events normally reserved by the compositor
+ (see zwp_keyboard_shortcuts_inhibitor_v1).
+
+ This occurs typically when the initial request "inhibit_shortcuts"
+ first becomes active or when the user instructs the compositor to
+ re-enable and existing shortcuts inhibitor using any mechanism
+ offered by the compositor.
+
+
+
+
+
+ This event indicates that the shortcuts inhibitor is inactive,
+ normal shortcuts processing is restored by the compositor.
+
+
+
+
diff --git a/keyboard_shortcuts_inhibit.c b/keyboard_shortcuts_inhibit.c
new file mode 100644
index 0000000..d997875
--- /dev/null
+++ b/keyboard_shortcuts_inhibit.c
@@ -0,0 +1,379 @@
+/* Wayland compositor running on top of an X server.
+
+Copyright (C) 2022 to various contributors.
+
+This file is part of 12to11.
+
+12to11 is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+12to11 is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with 12to11. If not, see . */
+
+#include
+
+#include "compositor.h"
+#include "keyboard-shortcuts-inhibit-unstable-v1.h"
+
+typedef struct _ShortcutInhibitDataRecord ShortcutInhibitDataRecord;
+typedef struct _KeyboardShortcutInhibitor KeyboardShortcutInhibitor;
+
+enum
+ {
+ IsGrabbed = 1,
+ };
+
+struct _KeyboardShortcutInhibitor
+{
+ /* The surface to which the inhibitor applies. */
+ Surface *surface;
+
+ /* The associated struct wl_resource. */
+ struct wl_resource *resource;
+
+ /* The next and last shortcut inhibitors in this list. Not valid if
+ surface is NULL. */
+ KeyboardShortcutInhibitor *next, *last;
+
+ /* The seat. */
+ Seat *seat;
+
+ /* The seat destruction key. */
+ void *seat_key;
+
+ /* Some flags. */
+ int flags;
+};
+
+struct _ShortcutInhibitDataRecord
+{
+ /* List of all keyboard shortcut inhibitors. */
+ KeyboardShortcutInhibitor inhibitors;
+};
+
+/* The zwp_keyboard_shortcuts_inhibit_manager_v1 global. */
+struct wl_global *inhibit_manager_global;
+
+static void
+Destroy (struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+FreeShortcutInhibitData (void *data)
+{
+ ShortcutInhibitDataRecord *record;
+ KeyboardShortcutInhibitor *inhibitor;
+
+ record = data;
+
+ /* Clear the surface of every attached keyboard shortcut
+ inhibitor. */
+ inhibitor = record->inhibitors.next;
+ XLAssert (inhibitor != NULL);
+
+ while (inhibitor != &record->inhibitors)
+ {
+ inhibitor->surface = NULL;
+
+ /* Move to the next inhibitor. */
+ inhibitor = inhibitor->next;
+ }
+}
+
+static void
+InitShortcutInhibitData (ShortcutInhibitDataRecord *data)
+{
+ /* If data is already initialized, do nothing. */
+ if (data->inhibitors.next)
+ return;
+
+ /* Otherwise, initialize the list of inhibitors. */
+ data->inhibitors.next = &data->inhibitors;
+ data->inhibitors.last = &data->inhibitors;
+}
+
+static KeyboardShortcutInhibitor *
+FindKeyboardShortcutInhibitor (Surface *surface, Seat *seat)
+{
+ ShortcutInhibitDataRecord *data;
+ KeyboardShortcutInhibitor *inhibitor;
+
+ data = XLSurfaceFindClientData (surface, ShortcutInhibitData);
+
+ if (!data)
+ return NULL;
+
+ inhibitor = data->inhibitors.next;
+ while (inhibitor != &data->inhibitors)
+ {
+ if (inhibitor->seat == seat)
+ return inhibitor;
+
+ inhibitor = data->inhibitors.next;
+ }
+
+ /* There is no inhibitor for this seat on the given surface. */
+ return NULL;
+}
+
+static void
+DestroyKeyboardShortcutsInhibitor (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static struct zwp_keyboard_shortcuts_inhibitor_v1_interface inhibitor_impl =
+ {
+ .destroy = DestroyKeyboardShortcutsInhibitor,
+ };
+
+static void
+HandleResourceDestroy (struct wl_resource *resource)
+{
+ KeyboardShortcutInhibitor *inhibitor;
+
+ inhibitor = wl_resource_get_user_data (resource);
+
+ if (inhibitor->surface)
+ {
+ /* Unlink the inhibitor from its surroundings. */
+ inhibitor->next->last = inhibitor->last;
+ inhibitor->last->next = inhibitor->next;
+ }
+
+ if (inhibitor->seat)
+ {
+ /* Cancel the seat destruction callback. */
+ XLSeatCancelDestroyListener (inhibitor->seat_key);
+
+ /* Ungrab the keyboard if it is grabbed. */
+ if (inhibitor->flags & IsGrabbed)
+ XLSeatCancelExternalGrab (inhibitor->seat);
+ }
+
+ /* Free the inhibitor. */
+ XLFree (inhibitor);
+}
+
+static void
+HandleSeatDestroy (void *data)
+{
+ KeyboardShortcutInhibitor *inhibitor;
+
+ inhibitor = data;
+
+ /* The seat was destroyed. Unlink the inhibitor, then remove the
+ seat. */
+ if (inhibitor->surface)
+ {
+ /* Unlink the inhibitor from its surroundings. */
+ inhibitor->next->last = inhibitor->last;
+ inhibitor->last->next = inhibitor->next;
+ }
+
+ /* Clear the seat. */
+ inhibitor->seat = NULL;
+ inhibitor->seat_key = NULL;
+}
+
+
+
+static void
+InhibitShortcuts (struct wl_client *client, struct wl_resource *resource,
+ uint32_t id, struct wl_resource *surface_resource,
+ struct wl_resource *seat_resource)
+{
+ ShortcutInhibitDataRecord *record;
+ Surface *surface;
+ Seat *seat;
+ KeyboardShortcutInhibitor *inhibitor;
+ struct wl_resource *dummy_resource;
+
+ surface = wl_resource_get_user_data (surface_resource);
+ seat = wl_resource_get_user_data (seat_resource);
+
+ /* If the seat is inert, return an empty inhibitor. */
+ if (XLSeatIsInert (seat))
+ {
+ dummy_resource
+ = wl_resource_create (client,
+ &zwp_keyboard_shortcuts_inhibitor_v1_interface,
+ wl_resource_get_version (resource), id);
+
+ if (!dummy_resource)
+ wl_resource_post_no_memory (resource);
+ else
+ wl_resource_set_implementation (dummy_resource, &inhibitor_impl,
+ NULL, NULL);
+
+ return;
+ }
+
+ /* Check that there is no keyboard shortcut inhibitor already
+ present. */
+
+#define AlreadyInhibited \
+ ZWP_KEYBOARD_SHORTCUTS_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED
+
+ if (FindKeyboardShortcutInhibitor (surface, seat))
+ {
+ wl_resource_post_error (resource, AlreadyInhibited,
+ "inhibitor already attached to surface and seat");
+ return;
+ }
+
+#undef AlreadyInhibited
+
+ record = XLSurfaceGetClientData (surface, ShortcutInhibitData,
+ sizeof *record,
+ FreeShortcutInhibitData);
+ InitShortcutInhibitData (record);
+
+ /* Allocate a new keyboard shortcut inhibitor. */
+ inhibitor = XLSafeMalloc (sizeof *inhibitor);
+
+ if (!inhibitor)
+ {
+ wl_resource_post_no_memory (resource);
+ return;
+ }
+
+ memset (inhibitor, 0, sizeof *inhibitor);
+ inhibitor->resource
+ = wl_resource_create (client,
+ &zwp_keyboard_shortcuts_inhibitor_v1_interface,
+ wl_resource_get_version (resource), id);
+
+ if (!inhibitor->resource)
+ {
+ wl_resource_post_no_memory (resource);
+ XLFree (inhibitor);
+ return;
+ }
+
+ /* Link the inhibitor onto the list. */
+ inhibitor->next = record->inhibitors.next;
+ inhibitor->last = &record->inhibitors;
+ record->inhibitors.next->last = inhibitor;
+ record->inhibitors.next = inhibitor;
+
+ /* Attach the surface. */
+ inhibitor->surface = surface;
+
+ /* And the seat. */
+ inhibitor->seat = seat;
+ inhibitor->seat_key
+ = XLSeatRunOnDestroy (seat, HandleSeatDestroy, inhibitor);
+
+ /* Attach the resource implementation. */
+ wl_resource_set_implementation (inhibitor->resource, &inhibitor_impl,
+ inhibitor, HandleResourceDestroy);
+
+ /* If the given surface is the seat's focus, try to apply the grab
+ now. */
+ if (surface == XLSeatGetFocus (seat)
+ && XLSeatApplyExternalGrab (seat, surface))
+ {
+ /* The external grab is now active, so send the active
+ signal. */
+ zwp_keyboard_shortcuts_inhibitor_v1_send_active (inhibitor->resource);
+
+ /* Mark the inhibitor as active. */
+ inhibitor->flags |= IsGrabbed;
+ }
+}
+
+static struct zwp_keyboard_shortcuts_inhibit_manager_v1_interface manager_impl =
+ {
+ .inhibit_shortcuts = InhibitShortcuts,
+ .destroy = Destroy,
+ };
+
+static void
+HandleBind (struct wl_client *client, void *data, uint32_t version,
+ uint32_t id)
+{
+ struct wl_resource *resource;
+
+ resource
+ = wl_resource_create (client,
+ &zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
+ version, id);
+
+ if (!resource)
+ {
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ wl_resource_set_implementation (resource, &manager_impl, NULL, NULL);
+}
+
+void
+XLInitKeyboardShortcutsInhibit (void)
+{
+ inhibit_manager_global
+ = wl_global_create (compositor.wl_display,
+ &zwp_keyboard_shortcuts_inhibit_manager_v1_interface,
+ 1, NULL, HandleBind);
+}
+
+void
+XLCheckShortcutInhibition (Seat *seat, Surface *surface)
+{
+ KeyboardShortcutInhibitor *inhibitor;
+
+ /* If SURFACE has a shortcut inhibitor, inhibit shortcuts and send
+ the active signal. */
+
+ inhibitor = FindKeyboardShortcutInhibitor (surface, seat);
+
+ if (!inhibitor)
+ return;
+
+ /* Try to apply an external grab. */
+ if (XLSeatApplyExternalGrab (seat, surface))
+ {
+ /* The external grab is now active, so send the active
+ signal. */
+ zwp_keyboard_shortcuts_inhibitor_v1_send_active (inhibitor->resource);
+
+ /* Mark the inhibitor as active. */
+ inhibitor->flags |= IsGrabbed;
+ }
+ else if (inhibitor->flags & IsGrabbed)
+ {
+ /* The grab failed, and inhibitor was already grabbed (can that
+ even happen?) */
+ inhibitor->flags &= ~IsGrabbed;
+ zwp_keyboard_shortcuts_inhibitor_v1_send_inactive (inhibitor->resource);
+ }
+}
+
+void
+XLReleaseShortcutInhibition (Seat *seat, Surface *surface)
+{
+ KeyboardShortcutInhibitor *inhibitor;
+
+ inhibitor = FindKeyboardShortcutInhibitor (surface, seat);
+
+ if (!inhibitor || !(inhibitor->flags & IsGrabbed))
+ return;
+
+ /* Cancel the grab. */
+ XLSeatCancelExternalGrab (seat);
+
+ /* Mark the inhibitor as no longer holding a grab. */
+ inhibitor->flags &= IsGrabbed;
+ zwp_keyboard_shortcuts_inhibitor_v1_send_inactive (inhibitor->resource);
+}