Quick access terminal: Allow toggling the window to full screen and map using the standard kitty toggle_fullscreen shortcut

Fixes #8626
This commit is contained in:
Kovid Goyal 2025-05-12 15:20:48 +05:30
parent cf69385823
commit 9ed6be9272
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
12 changed files with 91 additions and 22 deletions

View file

@ -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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -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

View file

@ -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)

6
glfw/glfw3.h vendored
View file

@ -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.

1
glfw/internal.h vendored
View file

@ -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);

9
glfw/window.c vendored
View file

@ -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);

5
glfw/wl_window.c vendored
View file

@ -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; }

8
glfw/x11_window.c vendored
View file

@ -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);

6
kitty/glfw-wrapper.c generated
View file

@ -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

13
kitty/glfw-wrapper.h generated
View file

@ -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

View file

@ -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

View file

@ -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