diff --git a/glfw/cocoa_init.m b/glfw/cocoa_init.m index fb4479528..5762b01a2 100644 --- a/glfw/cocoa_init.m +++ b/glfw/cocoa_init.m @@ -828,7 +828,7 @@ int _glfwPlatformInit(bool *supports_window_occlusion) { debug_key("---------------- key down -------------------\n"); debug_key("%s\n", [[event description] UTF8String]); - if (!_glfw.ignoreOSKeyboardProcessing) { + if (!_glfw.ignoreOSKeyboardProcessing && !_glfw.keyboard_grabbed) { // first check if there is a global menu bar shortcut if ([[NSApp mainMenu] performKeyEquivalent:event]) { debug_key("keyDown triggered global menu bar action ignoring\n"); @@ -1144,3 +1144,4 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, } void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { } +bool _glfwPlatformGrabKeyboard(bool grab UNUSED) { return true; /* directly uses _glfw.keyboard_grabbed */ } diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 9ed81564d..b3e9518ee 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -4247,6 +4247,7 @@ GLFWAPI void glfwPostEmptyEvent(void); GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void); GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled); +GLFWAPI bool glfwGrabKeyboard(int grab); /*! @brief Returns the value of an input option for the specified window. * diff --git a/glfw/input.c b/glfw/input.c index a7a3cc7d4..a6d09dda9 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -684,6 +684,13 @@ GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) { _glfw.ignoreOSKeyboardProcessing = enabled; } +GLFWAPI bool glfwGrabKeyboard(int grab) { + if (grab == 0 || grab == 1) { + if (_glfwPlatformGrabKeyboard(grab)) _glfw.keyboard_grabbed = grab; + } + return _glfw.keyboard_grabbed; +} + GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/glfw/internal.h b/glfw/internal.h index 69d4ab963..18710f642 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -616,7 +616,7 @@ struct _GLFWlibrary _GLFWtls contextSlot; _GLFWmutex errorLock; - bool ignoreOSKeyboardProcessing; + bool ignoreOSKeyboardProcessing, keyboard_grabbed; struct { bool available; @@ -884,6 +884,7 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, void _glfwPlatformRemoveTimer(unsigned long long timer_id); int _glfwPlatformSetWindowBlur(_GLFWwindow* handle, int value); MonitorGeometry _glfwPlatformGetMonitorGeometry(_GLFWmonitor* monitor); +bool _glfwPlatformGrabKeyboard(bool grab); char* _glfw_strdup(const char* source); diff --git a/glfw/source-info.json b/glfw/source-info.json index 766ca1015..31a3e096a 100644 --- a/glfw/source-info.json +++ b/glfw/source-info.json @@ -84,6 +84,7 @@ "staging/fractional-scale/fractional-scale-v1.xml", "staging/single-pixel-buffer/single-pixel-buffer-v1.xml", "unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", + "unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml", "staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml", "staging/xdg-system-bell/xdg-system-bell-v1.xml", "staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml", diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 122ed879f..eb07d5813 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -600,6 +600,9 @@ static void registryHandleGlobal(void* data UNUSED, else if (is(zwp_idle_inhibit_manager_v1)) { _glfw.wl.idle_inhibit_manager = wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); } + else if (is(zwp_keyboard_shortcuts_inhibit_manager_v1)) { + _glfw.wl.keyboard_shortcuts_inhibit_manager = wl_registry_bind(registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); + } else if (is(xdg_toplevel_icon_manager_v1)) { _glfw.wl.xdg_toplevel_icon_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, 1); } @@ -718,7 +721,7 @@ get_compositor_missing_capabilities(void) { C(cursor_shape, wp_cursor_shape_manager_v1); C(layer_shell, zwlr_layer_shell_v1); C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale); C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); C(bell, xdg_system_bell_v1); - C(window-tag, xdg_toplevel_tag_manager_v1); + C(window-tag, xdg_toplevel_tag_manager_v1); C(keyboard_shortcuts_inhibit, keyboard_shortcuts_inhibit_manager); if (_glfw.wl.xdg_wm_base_version < 6) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-state-suspended"); if (_glfw.wl.xdg_wm_base_version < 5) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-capabilities"); #undef C @@ -913,6 +916,8 @@ void _glfwPlatformTerminate(void) zwlr_layer_shell_v1_destroy(_glfw.wl.zwlr_layer_shell_v1); if (_glfw.wl.idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idle_inhibit_manager); + if (_glfw.wl.keyboard_shortcuts_inhibit_manager) + zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(_glfw.wl.keyboard_shortcuts_inhibit_manager); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index ddf763794..78a7a9fc8 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -66,6 +66,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h" #include "wayland-single-pixel-buffer-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" +#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" #include "wayland-xdg-toplevel-icon-v1-client-protocol.h" #include "wayland-xdg-system-bell-v1-client-protocol.h" #include "wayland-xdg-toplevel-tag-v1-client-protocol.h" @@ -290,6 +291,7 @@ typedef struct _GLFWwindowWayland WaylandWindowState toplevel_states; uint32_t decoration_mode; } current, pending; + struct zwp_keyboard_shortcuts_inhibitor_v1 *keyboard_shortcuts_inhibitor; } _GLFWwindowWayland; typedef enum _GLFWWaylandOfferType @@ -350,6 +352,7 @@ typedef struct _GLFWlibraryWayland struct zwlr_layer_shell_v1* zwlr_layer_shell_v1; uint32_t zwlr_layer_shell_v1_version; struct wp_single_pixel_buffer_manager_v1 *wp_single_pixel_buffer_manager_v1; struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager; + struct zwp_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit_manager; int compositorVersion; int seatVersion; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index faf0a221f..bfbc07c7c 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1469,6 +1469,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) if (window->id == _glfw.wl.keyRepeatInfo.keyboardFocusId) { _glfw.wl.keyRepeatInfo.keyboardFocusId = 0; } + if (window->wl.keyboard_shortcuts_inhibitor) + zwp_keyboard_shortcuts_inhibitor_v1_destroy(window->wl.keyboard_shortcuts_inhibitor); if (window->wl.temp_buffer_used_during_window_creation) wl_buffer_destroy(window->wl.temp_buffer_used_during_window_creation); @@ -2820,6 +2822,22 @@ _glfwPlatformSetWindowBlur(_GLFWwindow *window, int blur_radius) { return has_blur ? 1 : 0; } +bool +_glfwPlatformGrabKeyboard(bool grab) { + if (!_glfw.wl.keyboard_shortcuts_inhibit_manager) return false; + for (_GLFWwindow* window = _glfw.windowListHead; window; window = window->next) { + if (grab) { + if (window->wl.keyboard_shortcuts_inhibitor) break; + window->wl.keyboard_shortcuts_inhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(_glfw.wl.keyboard_shortcuts_inhibit_manager, window->wl.surface, _glfw.wl.seat); + } else { + if (!window->wl.keyboard_shortcuts_inhibitor) break; + zwp_keyboard_shortcuts_inhibitor_v1_destroy(window->wl.keyboard_shortcuts_inhibitor); + window->wl.keyboard_shortcuts_inhibitor = NULL; + } + } + return true; +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 6f16ee686..e8473c22d 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -3384,6 +3384,12 @@ _glfwPlatformSetWindowBlur(_GLFWwindow *window, int blur_radius) { } +bool +_glfwPlatformGrabKeyboard(bool grab) { + if (grab) _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Grabbing of keyboard is not currently supported"); + return false; +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// diff --git a/kitty/boss.py b/kitty/boss.py index fd2475d79..babb6c89e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -87,6 +87,7 @@ from .fast_data_types import ( get_os_window_size, glfw_get_monitor_workarea, global_font_size, + grab_keyboard, is_layer_shell_supported, last_focused_os_window_id, mark_os_window_for_close, @@ -3202,3 +3203,15 @@ class Boss: if wid == exception: continue window.screen.clear_selection() + + @ac('misc', ''' + Grab the keyboard. This means global shortcuts defined in the OS will be handled in kitty instead. How well this + works depends on the OS/window manager/desktop environment. On Wayland it works only if the compositor implements + the :link:`inhibit-keyboard-shortcuts protocol `. + ''') + def grab_keyboard(self) -> None: + grab_keyboard(True) + + @ac('misc', 'Ungrab the keyboard if it was previously grabbed') + def ungrab_keyboard(self) -> None: + grab_keyboard(False) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index afd361de3..a165d21ef 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1748,6 +1748,7 @@ def buffer_keys_in_window(os_window_id: int, tab_id: int, window_id: int, enable def sprite_idx_to_pos(idx: int, xnum: int, ynum: int) -> tuple[int, int, int]: ... def render_box_char(ch: int, width: int, height: int, scale: float = 1.0, dpi_x: float = 96.0, dpi_y: float = 96.0) -> bytes: ... def run_at_exit_cleanup_functions() -> None: ... +def grab_keyboard(grab: bool | None) -> bool: ... DecorationTypes = Literal[ 'curl', 'dashed', 'dotted', 'double', 'straight', 'strikethrough', 'beam_cursor', 'underline_cursor', 'hollow_cursor', 'missing'] def render_decoration( diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 2ff52d057..fdc2f427d 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -293,6 +293,9 @@ load_glfw(const char* path) { *(void **) (&glfwSetIgnoreOSKeyboardProcessing_impl) = dlsym(handle, "glfwSetIgnoreOSKeyboardProcessing"); if (glfwSetIgnoreOSKeyboardProcessing_impl == NULL) fail("Failed to load glfw function glfwSetIgnoreOSKeyboardProcessing with error: %s", dlerror()); + *(void **) (&glfwGrabKeyboard_impl) = dlsym(handle, "glfwGrabKeyboard"); + if (glfwGrabKeyboard_impl == NULL) fail("Failed to load glfw function glfwGrabKeyboard with error: %s", dlerror()); + *(void **) (&glfwGetInputMode_impl) = dlsym(handle, "glfwGetInputMode"); if (glfwGetInputMode_impl == NULL) fail("Failed to load glfw function glfwGetInputMode with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 83866e713..428c8497c 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -794,6 +794,7 @@ typedef enum { #define GLFW_WAYLAND_APP_ID 0x00025001 #define GLFW_WAYLAND_BGCOLOR 0x00025002 +#define GLFW_WAYLAND_WINDOW_TAG 0x00025003 /*! @} */ #define GLFW_NO_API 0 @@ -2089,6 +2090,10 @@ typedef void (*glfwSetIgnoreOSKeyboardProcessing_func)(bool); GFW_EXTERN glfwSetIgnoreOSKeyboardProcessing_func glfwSetIgnoreOSKeyboardProcessing_impl; #define glfwSetIgnoreOSKeyboardProcessing glfwSetIgnoreOSKeyboardProcessing_impl +typedef bool (*glfwGrabKeyboard_func)(int); +GFW_EXTERN glfwGrabKeyboard_func glfwGrabKeyboard_impl; +#define glfwGrabKeyboard glfwGrabKeyboard_impl + typedef int (*glfwGetInputMode_func)(GLFWwindow*, int); GFW_EXTERN glfwGetInputMode_func glfwGetInputMode_impl; #define glfwGetInputMode glfwGetInputMode_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 60787bd11..a73b6f0c5 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2612,6 +2612,11 @@ set_layer_shell_config(PyObject *self UNUSED, PyObject *args) { return Py_NewRef(set_layer_shell_config_for(window, &lsc) ? Py_True : Py_False); } +static PyObject* +grab_keyboard(PyObject *self UNUSED, PyObject *action) { + if (action == Py_None) return Py_NewRef(glfwGrabKeyboard(2) ? Py_True : Py_False); + return Py_NewRef(glfwGrabKeyboard(PyObject_IsTrue(action)) ? Py_True : Py_False); +} // Boilerplate {{{ @@ -2621,6 +2626,7 @@ static PyMethodDef module_methods[] = { METHODB(toggle_os_window_visibility, METH_VARARGS), METHODB(layer_shell_config_for_os_window, METH_O), METHODB(set_layer_shell_config, METH_VARARGS), + METHODB(grab_keyboard, METH_O), METHODB(pointer_name_to_css_name, METH_O), {"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(set_default_window_icon, METH_VARARGS),