From 9ed6be9272d1d3c647c3cc4e4f4834fbbf947e7d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 May 2025 15:20:48 +0530 Subject: [PATCH] Quick access terminal: Allow toggling the window to full screen and map using the standard kitty toggle_fullscreen shortcut Fixes #8626 --- docs/changelog.rst | 2 ++ glfw/cocoa_window.m | 5 ++++ glfw/glfw.py | 1 - glfw/glfw3.h | 6 +++++ glfw/internal.h | 1 + glfw/window.c | 9 +++++++ glfw/wl_window.c | 5 ++-- glfw/x11_window.c | 8 +++++- kitty/glfw-wrapper.c | 6 ++--- kitty/glfw-wrapper.h | 13 +++++++--- kitty/glfw.c | 49 ++++++++++++++++++++++++++++++------ kitty/rc/resize_os_window.py | 8 +++--- 12 files changed, 91 insertions(+), 22 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 41023721d..55278379e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -115,6 +115,8 @@ Detailed list of changes - Wayland: Fix an abort if the terminal program sets a window title longer than 2KB that contains CSI escape sequences and multibyte UTF-8 (:iss:`8619`) +- Quick access terminal: Allow toggling the window to full screen and map using the standard kitty :sc:`toggle_fullscreen` shortcut (:iss:`8626`) + 0.42.0 [2025-05-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 3a5d70609..5d2ff3f06 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1940,6 +1940,11 @@ screen_for_window_center(_GLFWwindow *window) { return NSScreen.mainScreen; } +const GLFWLayerShellConfig* +_glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { + return &window->ns.layer_shell.config; +} + bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { #define config window->ns.layer_shell.config diff --git a/glfw/glfw.py b/glfw/glfw.py index e8564c128..e5d7ab3d9 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -325,7 +325,6 @@ def generate_wrappers(glfw_header: str) -> None: void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) bool glfwWaylandBeep(GLFWwindow *handle) - GLFWLayerShellConfig* glfwWaylandLayerShellConfig(GLFWwindow *handle) pid_t glfwWaylandCompositorPID(void) unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) diff --git a/glfw/glfw3.h b/glfw/glfw3.h index a13341668..989e48b5b 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1309,6 +1309,11 @@ typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAN typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; + struct { + GLFWEdge edge; + int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; + } previous; + bool was_toggled_to_fullscreen; char output_name[64]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; @@ -2874,6 +2879,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, G GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window); +GLFWAPI const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle); GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value); /*! @brief Destroys the specified window and its context. diff --git a/glfw/internal.h b/glfw/internal.h index 543d81734..0d080204d 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -719,6 +719,7 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value); +const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow* window); void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos); void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos); void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height); diff --git a/glfw/window.c b/glfw/window.c index c4cb91ab0..be5bcfe4e 100644 --- a/glfw/window.c +++ b/glfw/window.c @@ -551,6 +551,15 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* handle, int value) window->shouldClose = value; } +GLFWAPI const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle) { + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(false); + return _glfwPlatformGetLayerShellConfig(window); +} + + GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 5413efb9a..4ff1c2266 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -2892,8 +2892,9 @@ GLFWAPI void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) { if (csd_change_title(window)) commit_window_surface_if_safe(window); } -GLFWAPI GLFWLayerShellConfig* glfwWaylandLayerShellConfig(GLFWwindow *handle) { - return &((_GLFWwindow*)handle)->wl.layer_shell.config; +const GLFWLayerShellConfig* +_glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { + return &window->wl.layer_shell.config; } GLFWAPI bool glfwIsLayerShellSupported(void) { return _glfw.wl.zwlr_layer_shell_v1 != NULL; } diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 37af23d65..90bbbbfa6 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -2092,7 +2092,13 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) XFlush(_glfw.x11.display); } -bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { +const GLFWLayerShellConfig* +_glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { + return &window->x11.layer_shell.config; +} + +bool +_glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { if (value) window->x11.layer_shell.config = *value; WindowGeometry wg = calculate_layer_geometry(window); update_wm_hints(window, &wg, NULL); diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 84e0a2696..ea14ef658 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -134,6 +134,9 @@ load_glfw(const char* path) { *(void **) (&glfwAreSwapsAllowed_impl) = dlsym(handle, "glfwAreSwapsAllowed"); if (glfwAreSwapsAllowed_impl == NULL) fail("Failed to load glfw function glfwAreSwapsAllowed with error: %s", dlerror()); + *(void **) (&glfwGetLayerShellConfig_impl) = dlsym(handle, "glfwGetLayerShellConfig"); + if (glfwGetLayerShellConfig_impl == NULL) fail("Failed to load glfw function glfwGetLayerShellConfig with error: %s", dlerror()); + *(void **) (&glfwSetLayerShellConfig_impl) = dlsym(handle, "glfwSetLayerShellConfig"); if (glfwSetLayerShellConfig_impl == NULL) fail("Failed to load glfw function glfwSetLayerShellConfig with error: %s", dlerror()); @@ -497,9 +500,6 @@ load_glfw(const char* path) { *(void **) (&glfwWaylandBeep_impl) = dlsym(handle, "glfwWaylandBeep"); if (glfwWaylandBeep_impl == NULL) dlerror(); // clear error indicator - *(void **) (&glfwWaylandLayerShellConfig_impl) = dlsym(handle, "glfwWaylandLayerShellConfig"); - if (glfwWaylandLayerShellConfig_impl == NULL) dlerror(); // clear error indicator - *(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID"); if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index a80096d47..8ce2a2733 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1047,6 +1047,11 @@ typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAN typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; + struct { + GLFWEdge edge; + int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; + } previous; + bool was_toggled_to_fullscreen; char output_name[64]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells, x_size_in_pixels; @@ -1872,6 +1877,10 @@ typedef bool (*glfwAreSwapsAllowed_func)(const GLFWwindow*); GFW_EXTERN glfwAreSwapsAllowed_func glfwAreSwapsAllowed_impl; #define glfwAreSwapsAllowed glfwAreSwapsAllowed_impl +typedef const GLFWLayerShellConfig* (*glfwGetLayerShellConfig_func)(GLFWwindow*); +GFW_EXTERN glfwGetLayerShellConfig_func glfwGetLayerShellConfig_impl; +#define glfwGetLayerShellConfig glfwGetLayerShellConfig_impl + typedef bool (*glfwSetLayerShellConfig_func)(GLFWwindow*, const GLFWLayerShellConfig*); GFW_EXTERN glfwSetLayerShellConfig_func glfwSetLayerShellConfig_impl; #define glfwSetLayerShellConfig glfwSetLayerShellConfig_impl @@ -2356,10 +2365,6 @@ typedef bool (*glfwWaylandBeep_func)(GLFWwindow*); GFW_EXTERN glfwWaylandBeep_func glfwWaylandBeep_impl; #define glfwWaylandBeep glfwWaylandBeep_impl -typedef GLFWLayerShellConfig* (*glfwWaylandLayerShellConfig_func)(GLFWwindow*); -GFW_EXTERN glfwWaylandLayerShellConfig_func glfwWaylandLayerShellConfig_impl; -#define glfwWaylandLayerShellConfig glfwWaylandLayerShellConfig_impl - typedef pid_t (*glfwWaylandCompositorPID_func)(void); GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl; #define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index f3d9edcd8..1f02d4f69 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1013,23 +1013,56 @@ do_toggle_fullscreen(OSWindow *w, unsigned int flags, bool restore_sizes) { static bool toggle_fullscreen_for_os_window(OSWindow *w) { - if (w && w->handle && !w->is_layer_shell) { + if (!w || !w->handle) return false; + if (!w->is_layer_shell) { #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) return do_toggle_fullscreen(w, 1, false); #endif return do_toggle_fullscreen(w, 0, true); } + const GLFWLayerShellConfig *prev = glfwGetLayerShellConfig(w->handle); + if (!prev) return false; + GLFWLayerShellConfig lsc; + memcpy(&lsc, prev, sizeof(lsc)); + if (prev->type == GLFW_LAYER_SHELL_OVERLAY || prev->type == GLFW_LAYER_SHELL_TOP) { + if (prev->was_toggled_to_fullscreen) { + lsc.edge = prev->previous.edge; + lsc.requested_bottom_margin = prev->previous.requested_bottom_margin; + lsc.requested_top_margin = prev->previous.requested_top_margin; + lsc.requested_left_margin = prev->requested_left_margin; + lsc.requested_right_margin = prev->requested_right_margin; + lsc.was_toggled_to_fullscreen = false; + glfwSetLayerShellConfig(w->handle, &lsc); + return true; + } + if (prev->edge == GLFW_EDGE_TOP || prev->edge == GLFW_EDGE_BOTTOM || prev->edge == GLFW_EDGE_LEFT || prev->edge == GLFW_EDGE_RIGHT) { + lsc.edge = GLFW_EDGE_CENTER; + lsc.previous.edge = prev->edge; + lsc.previous.requested_right_margin = prev->requested_right_margin; + lsc.previous.requested_left_margin = prev->requested_left_margin; + lsc.previous.requested_top_margin = prev->requested_top_margin; + lsc.previous.requested_bottom_margin = prev->requested_bottom_margin; + lsc.requested_bottom_margin = 0; lsc.requested_top_margin = 0; lsc.requested_left_margin = 0; lsc.requested_right_margin = 0; + lsc.was_toggled_to_fullscreen = true; + glfwSetLayerShellConfig(w->handle, &lsc); + return true; + } + } return false; } bool is_os_window_fullscreen(OSWindow *w) { unsigned int flags = 0; + if (!w || !w->handle) return false; + if (w->is_layer_shell) { + const GLFWLayerShellConfig *c = glfwGetLayerShellConfig(w->handle); + return c && c->was_toggled_to_fullscreen; + } #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) flags = 1; #endif - if (w && w->handle) return glfwIsFullscreen(w->handle, flags); - return false; + return glfwIsFullscreen(w->handle, flags); } static bool @@ -1048,20 +1081,20 @@ toggle_maximized_for_os_window(OSWindow *w) { static void change_state_for_os_window(OSWindow *w, int state) { - if (!w || !w->handle || w->is_layer_shell) return; + if (!w || !w->handle) return; switch (state) { case WINDOW_MAXIMIZED: - glfwMaximizeWindow(w->handle); + if (!w->is_layer_shell) glfwMaximizeWindow(w->handle); break; case WINDOW_MINIMIZED: - glfwIconifyWindow(w->handle); + if (!w->is_layer_shell) glfwIconifyWindow(w->handle); break; case WINDOW_FULLSCREEN: if (!is_os_window_fullscreen(w)) toggle_fullscreen_for_os_window(w); break; case WINDOW_NORMAL: if (is_os_window_fullscreen(w)) toggle_fullscreen_for_os_window(w); - else glfwRestoreWindow(w->handle); + else if (!w->is_layer_shell) glfwRestoreWindow(w->handle); break; case WINDOW_HIDDEN: glfwHideWindow(w->handle); break; @@ -2529,7 +2562,7 @@ layer_shell_config_for_os_window(PyObject *self UNUSED, PyObject *wid) { id_type id = PyLong_AsUnsignedLongLong(wid); OSWindow *w = os_window_for_id(id); if (!w || !w->handle) Py_RETURN_NONE; - const GLFWLayerShellConfig *c = glfwWaylandLayerShellConfig(w->handle); + const GLFWLayerShellConfig *c = glfwGetLayerShellConfig(w->handle); if (!c) Py_RETURN_NONE; return layer_shell_config_to_python(c); #endif diff --git a/kitty/rc/resize_os_window.py b/kitty/rc/resize_os_window.py index 296994fc5..da4dc2ebe 100644 --- a/kitty/rc/resize_os_window.py +++ b/kitty/rc/resize_os_window.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING -from kitty.fast_data_types import get_os_window_size, layer_shell_config_for_os_window, set_layer_shell_config, toggle_os_window_visibility +from kitty.fast_data_types import get_os_window_size, layer_shell_config_for_os_window, set_layer_shell_config, toggle_fullscreen, toggle_os_window_visibility from .base import ( MATCH_WINDOW_OPTION, @@ -139,6 +139,10 @@ using this option means that you will not be notified of failures. toggle_os_window_visibility(os_window_id, False) elif ac == 'show': toggle_os_window_visibility(os_window_id, True) + elif ac == 'toggle-fullscreen': + if not toggle_fullscreen(os_window_id): + raise RemoteControlErrorWithoutTraceback( + f'The OS Window {os_window_id} is a desktop panel that cannot be made fullscreen') elif is_panel: raise RemoteControlErrorWithoutTraceback( f'The OS Window {os_window_id} is a desktop panel, no actions other than resizing are supported for it') @@ -147,8 +151,6 @@ using this option means that you will not be notified of failures. os_window_id, width=payload_get('width'), height=payload_get('height'), unit=payload_get('unit'), incremental=payload_get('incremental'), metrics=metrics, ) - elif ac == 'toggle-fullscreen': - boss.toggle_fullscreen(os_window_id) elif ac == 'toggle-maximized': boss.toggle_maximized(os_window_id) return None