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); +}