From 8c32f093bb2d29959fdd69ea040dcdb2b4363eb9 Mon Sep 17 00:00:00 2001 From: Aleksei Gmitron Date: Sun, 21 Jun 2026 22:31:03 +0400 Subject: [PATCH] wip: MacOS-specific config settings --- glfw/cocoa_window.m | 77 +++++++++++++++++++++++++++++++-- glfw/glfw3.h | 7 +-- glfw/wl_window.c | 3 +- glfw/x11_window.c | 4 +- kittens/panel/main.py | 6 +-- kitty/glfw-wrapper.c | 3 ++ kitty/glfw-wrapper.h | 11 +++-- kitty/glfw.c | 12 +++-- kitty/options/definition.py | 21 +++++++++ kitty/options/parse.py | 6 +++ kitty/options/to-c-generated.h | 30 +++++++++++++ kitty/options/to-c.h | 5 ++- kitty/options/types.py | 4 ++ kitty/simple_cli_definitions.py | 14 ++---- kitty/state.h | 4 +- kitty/types.py | 1 - 16 files changed, 171 insertions(+), 37 deletions(-) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index d2a24592e..e50c7ce88 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -2577,6 +2577,71 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) window->ns.object = nil; } +static bool +ns_window_level_constant(const char *name, long *val) { +#define C(x) if (strcmp(name, #x) == 0) { *val = (long)x; return true; } + C(NSNormalWindowLevel); C(NSFloatingWindowLevel); C(NSSubmenuWindowLevel); C(NSTornOffMenuWindowLevel); + C(NSMainMenuWindowLevel); C(NSStatusWindowLevel); C(NSModalPanelWindowLevel); C(NSPopUpMenuWindowLevel); + C(NSScreenSaverWindowLevel); + C(kCGBaseWindowLevel); C(kCGMinimumWindowLevel); C(kCGDesktopWindowLevel); C(kCGBackstopMenuLevel); + C(kCGNormalWindowLevel); C(kCGFloatingWindowLevel); C(kCGTornOffMenuWindowLevel); C(kCGDockWindowLevel); + C(kCGMainMenuWindowLevel); C(kCGStatusWindowLevel); C(kCGModalPanelWindowLevel); C(kCGPopUpMenuWindowLevel); + C(kCGDraggingWindowLevel); C(kCGScreenSaverWindowLevel); C(kCGMaximumWindowLevel); C(kCGOverlayWindowLevel); + C(kCGHelpWindowLevel); C(kCGUtilityWindowLevel); C(kCGDesktopIconWindowLevel); C(kCGAssistiveTechHighWindowLevel); + C(kCGCursorWindowLevel); C(kCGNumberOfWindowLevelKeys); +#undef C + return false; +} + +static bool +parse_ns_window_level_term(const char **spec, long *val) { + const char *s = *spec; + while (isspace(*s)) s++; + if (!*s) return false; + if (*s == '+' || *s == '-' || isdigit(*s)) { + errno = 0; + char *end = NULL; + long v = strtol(s, &end, 0); + if (end == s || errno) return false; + *val = v; *spec = end; + return true; + } + char name[96]; size_t i = 0; + while ((isalnum(*s) || *s == '_') && i + 1 < sizeof(name)) name[i++] = *s++; + name[i] = '\0'; + if (!i || !ns_window_level_constant(name, val)) return false; + *spec = s; + return true; +} + +static bool +parse_ns_window_level(const char *spec, NSWindowLevel *level) { + const char *original = spec; + if (!spec) return false; + while (isspace(*spec)) spec++; + if (!*spec || strcmp(spec, "unset") == 0) return false; + long total = 0; + if (!parse_ns_window_level_term(&spec, &total)) goto invalid; + while (true) { + while (isspace(*spec)) spec++; + if (!*spec) { *level = (NSWindowLevel)total; return true; } + char op = *spec++; + if (op != '+' && op != '-') goto invalid; + long term = 0; + if (!parse_ns_window_level_term(&spec, &term)) goto invalid; + total = op == '+' ? total + term : total - term; + } +invalid: + _glfwInputError(GLFW_INVALID_VALUE, "Cocoa: Invalid macOS NSWindow layer expression: %s", original); + return false; +} + +static void +apply_ns_window_layer(_GLFWwindow *window, const char *spec) { + NSWindowLevel level = 0; + if (parse_ns_window_level(spec, &level)) [window->ns.object setLevel:level]; +} + static NSScreen* screen_for_window_center(_GLFWwindow *window) { NSRect windowFrame = [window->ns.object frame]; @@ -2681,9 +2746,9 @@ _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig double spacing_y = top_edge_spacing + bottom_edge_spacing; const unsigned xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells); const unsigned ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells); - NSRect placement_frame = config.use_physical_screen_frame ? screen.frame : screen.visibleFrame; + NSRect placement_frame = config.related.use_physical_screen_frame ? screen.frame : screen.visibleFrame; CGFloat dock_height = NSMinY(screen.visibleFrame) - NSMinY(screen.frame); - CGFloat menubar_height = config.use_physical_screen_frame ? 0 : NSHeight(screen.frame) - NSHeight(screen.visibleFrame) - dock_height; + CGFloat menubar_height = config.related.use_physical_screen_frame ? 0 : NSHeight(screen.frame) - NSHeight(screen.visibleFrame) - dock_height; CGFloat x = NSMinX(placement_frame), y = NSMinY(placement_frame) - 1, width = NSWidth(placement_frame), height = NSHeight(placement_frame) + 2; if (config.type == GLFW_LAYER_SHELL_BACKGROUND || config.edge == GLFW_EDGE_CENTER) { x = NSMinX(screen.frame); height = NSHeight(screen.frame) - menubar_height + 1; y = NSMinY(screen.frame); width = NSWidth(screen.frame); @@ -2698,7 +2763,6 @@ _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig // See: https://stackoverflow.com/questions/4982584/how-do-i-draw-the-desktop-on-mac-os-x/4982619#4982619 level = kCGDesktopWindowLevel; break; - case GLFW_LAYER_SHELL_DESKTOP_SHELL: level = kCGBackstopMenuLevel; break; case GLFW_LAYER_SHELL_OVERLAY: case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_PANEL: level = NSNormalWindowLevel - 1; break; case GLFW_LAYER_SHELL_TOP: level--; break; @@ -2737,6 +2801,7 @@ _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig [nswindow setAnimationBehavior:animation_behavior]; [nswindow setLevel:level]; + apply_ns_window_layer(window, config.related.ns_window_layer); [nswindow setCollectionBehavior: (NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorIgnoresCycle)]; [nswindow setFrame:NSMakeRect(x, y, width, height) display:YES]; return true; @@ -4036,6 +4101,12 @@ apply_window_corner_curve(_GLFWwindow *window) { +GLFWAPI void glfwCocoaSetWindowLevel(GLFWwindow *w, const char *level_spec) { @autoreleasepool { + _GLFWwindow* window = (_GLFWwindow*)w; + apply_ns_window_layer(window, level_spec); +}} + + GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool use_system_color, unsigned int system_color, int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable) { @autoreleasepool { _GLFWwindow* window = (_GLFWwindow*)w; if (window->ns.layer_shell.is_active) return; diff --git a/glfw/glfw3.h b/glfw/glfw3.h index e9ee195e9..2e99fb2e5 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1349,7 +1349,7 @@ typedef struct GLFWkeyevent bool fake_event_on_focus_change; } GLFWkeyevent; -typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_DESKTOP_SHELL, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; +typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT, GLFW_EDGE_CENTER, GLFW_EDGE_NONE, GLFW_EDGE_CENTER_SIZED } GLFWEdge; @@ -1368,12 +1368,13 @@ typedef struct GLFWLayerShellConfig { unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; - int requested_exclusive_zone, hide_on_focus_loss, use_physical_screen_frame; + int requested_exclusive_zone, hide_on_focus_loss; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing); struct { float xscale, yscale; } expected; struct { - float background_opacity; int background_blur, color_space; + float background_opacity; int background_blur, color_space, use_physical_screen_frame; + char ns_window_layer[128]; } related; } GLFWLayerShellConfig; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index af5e3accc..d8cc01a6a 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1020,7 +1020,7 @@ get_layer_shell_layer(const _GLFWwindow *window) { case GLFW_LAYER_SHELL_BACKGROUND: case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_PANEL: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; break; case GLFW_LAYER_SHELL_TOP: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; break; - case GLFW_LAYER_SHELL_DESKTOP_SHELL: case GLFW_LAYER_SHELL_OVERLAY: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; break; + case GLFW_LAYER_SHELL_OVERLAY: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; break; } return which_layer; } @@ -1043,7 +1043,6 @@ layer_set_properties(const _GLFWwindow *window, bool during_creation, uint32_t w case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_BACKGROUND: exclusive_zone = -1; break; case GLFW_LAYER_SHELL_TOP: - case GLFW_LAYER_SHELL_DESKTOP_SHELL: case GLFW_LAYER_SHELL_OVERLAY: case GLFW_LAYER_SHELL_PANEL: switch (config.edge) { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index b3ea14e07..8fa76f53b 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -644,7 +644,7 @@ calculate_layer_geometry(_GLFWwindow *window) { double xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells); double ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells); ans.width = (int)(1. + spacing_x + xsz); ans.height = (int)(1. + spacing_y + ysz); - GeometryRect m = config.type == GLFW_LAYER_SHELL_TOP || config.type == GLFW_LAYER_SHELL_DESKTOP_SHELL || config.type == GLFW_LAYER_SHELL_OVERLAY ? mg.workarea : mg.full; + GeometryRect m = config.type == GLFW_LAYER_SHELL_TOP || config.type == GLFW_LAYER_SHELL_OVERLAY ? mg.workarea : mg.full; static const struct { unsigned left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x; } s = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; @@ -762,7 +762,7 @@ update_wm_hints(_GLFWwindow *window, const WindowGeometry *wg, const _GLFWwndcon // i3 does not support NET_WM_STATE_BELOW but panels work without it if (_glfw.x11.NET_WM_STATE_BELOW) { S(NET_WM_STATE_BELOW); } break; - case GLFW_LAYER_SHELL_TOP: case GLFW_LAYER_SHELL_DESKTOP_SHELL: case GLFW_LAYER_SHELL_OVERLAY: S(NET_WM_STATE_ABOVE); break; + case GLFW_LAYER_SHELL_TOP: case GLFW_LAYER_SHELL_OVERLAY: S(NET_WM_STATE_ABOVE); break; } #undef S } else if (wndconfig) { diff --git a/kittens/panel/main.py b/kittens/panel/main.py index 895800385..451239deb 100644 --- a/kittens/panel/main.py +++ b/kittens/panel/main.py @@ -26,7 +26,6 @@ from kitty.fast_data_types import ( GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_OVERLAY, GLFW_LAYER_SHELL_PANEL, - GLFW_LAYER_SHELL_DESKTOP_SHELL, GLFW_LAYER_SHELL_TOP, layer_shell_config_for_os_window, set_layer_shell_config, @@ -70,7 +69,6 @@ def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: 'background': GLFW_LAYER_SHELL_BACKGROUND, 'bottom': GLFW_LAYER_SHELL_PANEL, 'top': GLFW_LAYER_SHELL_TOP, - 'shell': GLFW_LAYER_SHELL_DESKTOP_SHELL, 'overlay': GLFW_LAYER_SHELL_OVERLAY }.get(opts.layer, GLFW_LAYER_SHELL_PANEL) ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else ltype @@ -96,7 +94,6 @@ def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: requested_exclusive_zone=opts.exclusive_zone, override_exclusive_zone=opts.override_exclusive_zone, hide_on_focus_loss=opts.hide_on_focus_loss, - use_physical_screen_frame=opts.use_physical_screen_frame, output_name=opts.output_name or '') @@ -115,8 +112,7 @@ def cli_option_to_lsc_configs_map() -> MappingProxyType[str, tuple[str, ...]]: 'focus_policy': ('focus_policy',), 'exclusive_zone': ('requested_exclusive_zone',), 'override_exclusive_zone': ('override_exclusive_zone',), - 'hide_on_focus_loss': ('hide_on_focus_loss',), - 'use_physical_screen_frame': ('use_physical_screen_frame',) + 'hide_on_focus_loss': ('hide_on_focus_loss',) }) diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 37a35891a..91d925a6d 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -497,6 +497,9 @@ load_glfw(const char* path) { *(void **) (&glfwCocoaCycleThroughOSWindows_impl) = dlsym(handle, "glfwCocoaCycleThroughOSWindows"); if (glfwCocoaCycleThroughOSWindows_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwCocoaSetWindowLevel_impl) = dlsym(handle, "glfwCocoaSetWindowLevel"); + if (glfwCocoaSetWindowLevel_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwCocoaSetWindowChrome_impl) = dlsym(handle, "glfwCocoaSetWindowChrome"); if (glfwCocoaSetWindowChrome_impl == NULL) dlerror(); // clear error indicator diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index ed1149ae1..ec4d42933 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1077,7 +1077,7 @@ typedef struct GLFWkeyevent bool fake_event_on_focus_change; } GLFWkeyevent; -typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_DESKTOP_SHELL, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; +typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT, GLFW_EDGE_CENTER, GLFW_EDGE_NONE, GLFW_EDGE_CENTER_SIZED } GLFWEdge; @@ -1096,12 +1096,13 @@ typedef struct GLFWLayerShellConfig { unsigned x_size_in_cells, x_size_in_pixels; unsigned y_size_in_cells, y_size_in_pixels; int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin; - int requested_exclusive_zone, hide_on_focus_loss, use_physical_screen_frame; + int requested_exclusive_zone, hide_on_focus_loss; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing); struct { float xscale, yscale; } expected; struct { - float background_opacity; int background_blur, color_space; + float background_opacity; int background_blur, color_space, use_physical_screen_frame; + char ns_window_layer[128]; } related; } GLFWLayerShellConfig; @@ -2478,6 +2479,10 @@ typedef void (*glfwCocoaCycleThroughOSWindows_func)(bool); GFW_EXTERN glfwCocoaCycleThroughOSWindows_func glfwCocoaCycleThroughOSWindows_impl; #define glfwCocoaCycleThroughOSWindows glfwCocoaCycleThroughOSWindows_impl +typedef void (*glfwCocoaSetWindowLevel_func)(GLFWwindow*, const char*); +GFW_EXTERN glfwCocoaSetWindowLevel_func glfwCocoaSetWindowLevel_impl; +#define glfwCocoaSetWindowLevel glfwCocoaSetWindowLevel_impl + typedef void (*glfwCocoaSetWindowChrome_func)(GLFWwindow*, unsigned int, bool, unsigned int, int, unsigned int, bool, int, float, bool); GFW_EXTERN glfwCocoaSetWindowChrome_func glfwCocoaSetWindowChrome_impl; #define glfwCocoaSetWindowChrome glfwCocoaSetWindowChrome_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 362f766ee..e6e8199ed 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -182,6 +182,9 @@ set_layer_shell_config_for(OSWindow *w, GLFWLayerShellConfig *lsc) { lsc->related.background_opacity = effective_os_window_alpha(w); lsc->related.background_blur = OPT(background_blur); lsc->related.color_space = OPT(macos_colorspace); + lsc->related.use_physical_screen_frame = OPT(macos_use_physical_screen_frame); + if (OPT(macos_ns_window_layer)) snprintf(lsc->related.ns_window_layer, sizeof(lsc->related.ns_window_layer), "%s", OPT(macos_ns_window_layer)); + else lsc->related.ns_window_layer[0] = '\0'; w->hide_on_focus_loss = lsc->hide_on_focus_loss; } return glfwSetLayerShellConfig(w->handle, lsc); @@ -1372,7 +1375,7 @@ toggle_fullscreen_for_os_window(OSWindow *w) { if (!prev) return false; GLFWLayerShellConfig lsc; memcpy(&lsc, prev, sizeof(lsc)); - if (prev->type == GLFW_LAYER_SHELL_OVERLAY || prev->type == GLFW_LAYER_SHELL_DESKTOP_SHELL || prev->type == GLFW_LAYER_SHELL_TOP) { + 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; @@ -1642,7 +1645,6 @@ layer_shell_config_to_python(const GLFWLayerShellConfig *c) { A(requested_right_margin, fl); A(requested_exclusive_zone, fl); A(hide_on_focus_loss, b) - A(use_physical_screen_frame, b) A(override_exclusive_zone, b); #undef A #undef fl @@ -1670,7 +1672,6 @@ layer_shell_config_from_python(PyObject *p, GLFWLayerShellConfig *ans) { A(requested_exclusive_zone, PyLong_Check, PyLong_AsLong); A(override_exclusive_zone, PyBool_Check, PyLong_AsLong); A(hide_on_focus_loss, PyBool_Check, PyLong_AsLong); - A(use_physical_screen_frame, PyBool_Check, PyLong_AsLong); #undef A #define A(attr) { \ RAII_PyObject(attr, PyObject_GetAttrString(p, #attr)); if (attr == NULL) return false; \ @@ -1924,6 +1925,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (global_state.is_apple) set_layer_shell_config_for(w, lsc); } else apply_window_chrome_state( w->handle, w->last_window_chrome, width, height, global_state.is_apple ? OPT(hide_window_decorations) != 0 : false); +#ifdef __APPLE__ + if (!w->is_layer_shell) glfwCocoaSetWindowLevel(w->handle, OPT(macos_ns_window_layer)); +#endif // Update window state // We do not call glfwWindowHint to set GLFW_MAXIMIZED before the window is created. // That would cause the window to be set to maximize immediately after creation and use the wrong initial size when restored. @@ -3262,7 +3266,7 @@ init_glfw(PyObject *m) { ADDC(GLFW_REPEAT); ADDC(true); ADDC(false); ADDC(GLFW_PRIMARY_SELECTION); ADDC(GLFW_CLIPBOARD); - ADDC(GLFW_LAYER_SHELL_NONE); ADDC(GLFW_LAYER_SHELL_PANEL); ADDC(GLFW_LAYER_SHELL_BACKGROUND); ADDC(GLFW_LAYER_SHELL_TOP); ADDC(GLFW_LAYER_SHELL_DESKTOP_SHELL); ADDC(GLFW_LAYER_SHELL_OVERLAY); + ADDC(GLFW_LAYER_SHELL_NONE); ADDC(GLFW_LAYER_SHELL_PANEL); ADDC(GLFW_LAYER_SHELL_BACKGROUND); ADDC(GLFW_LAYER_SHELL_TOP); ADDC(GLFW_LAYER_SHELL_OVERLAY); ADDC(GLFW_FOCUS_NOT_ALLOWED); ADDC(GLFW_FOCUS_EXCLUSIVE); ADDC(GLFW_FOCUS_ON_DEMAND); ADDC(GLFW_EDGE_TOP); ADDC(GLFW_EDGE_BOTTOM); ADDC(GLFW_EDGE_LEFT); ADDC(GLFW_EDGE_RIGHT); ADDC(GLFW_EDGE_CENTER); ADDC(GLFW_EDGE_NONE); ADDC(GLFW_EDGE_CENTER_SIZED); diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 66545faf4..95183aeaf 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2724,6 +2724,27 @@ full display frame instead of leaving space for the notch area. ''' ) +opt('macos_use_physical_screen_frame', 'no', + option_type='to_bool', ctype='bool', + long_text=''' +Use the physical screen frame instead of the visible frame when placing macOS +desktop panels such as those created by :code:`kitty +kitten panel`. This allows +panels to draw in areas normally reserved for the native menu bar or Dock. +''' + ) + +opt('macos_ns_window_layer', 'unset', + option_type='str', ctype='!macos_ns_window_layer', + long_text=''' +Set the macOS NSWindow level for newly created OS windows. The default value +:code:`unset` leaves kitty's normal window-level handling unchanged. Values can +be integer window levels, AppKit/CoreGraphics window-level constant names, or +simple arithmetic expressions combining them with integers. For example: +:code:`NSFloatingWindowLevel`, :code:`kCGBackstopMenuLevel`, +:code:`NSPopUpMenuWindowLevel - 1`. +''' + ) + opt('macos_show_window_title_in', 'all', choices=('all', 'menubar', 'none', 'window'), ctype='window_title_in', long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index cf2870707..380de4b55 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -1112,6 +1112,9 @@ class Parser: def macos_menubar_title_max_length(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_menubar_title_max_length'] = positive_int(val) + def macos_ns_window_layer(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['macos_ns_window_layer'] = str(val) + def macos_option_as_alt(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_option_as_alt'] = macos_option_as_alt(val) @@ -1135,6 +1138,9 @@ class Parser: def macos_traditional_fullscreen(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_traditional_fullscreen'] = to_bool(val) + def macos_use_physical_screen_frame(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['macos_use_physical_screen_frame'] = to_bool(val) + def macos_window_resizable(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_window_resizable'] = to_bool(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index eb149fb61..bb1db7927 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -1435,6 +1435,32 @@ convert_from_opts_macos_fullscreen_ignore_safe_area_insets(PyObject *py_opts, Op Py_DECREF(ret); } +static void +convert_from_python_macos_use_physical_screen_frame(PyObject *val, Options *opts) { + opts->macos_use_physical_screen_frame = PyObject_IsTrue(val); +} + +static void +convert_from_opts_macos_use_physical_screen_frame(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "macos_use_physical_screen_frame"); + if (ret == NULL) return; + convert_from_python_macos_use_physical_screen_frame(ret, opts); + Py_DECREF(ret); +} + +static void +convert_from_python_macos_ns_window_layer(PyObject *val, Options *opts) { + macos_ns_window_layer(val, opts); +} + +static void +convert_from_opts_macos_ns_window_layer(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "macos_ns_window_layer"); + if (ret == NULL) return; + convert_from_python_macos_ns_window_layer(ret, opts); + Py_DECREF(ret); +} + static void convert_from_python_macos_show_window_title_in(PyObject *val, Options *opts) { opts->macos_show_window_title_in = window_title_in(val); @@ -1709,6 +1735,10 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_macos_fullscreen_ignore_safe_area_insets(py_opts, opts); if (PyErr_Occurred()) return false; + convert_from_opts_macos_use_physical_screen_frame(py_opts, opts); + if (PyErr_Occurred()) return false; + convert_from_opts_macos_ns_window_layer(py_opts, opts); + if (PyErr_Occurred()) return false; convert_from_opts_macos_show_window_title_in(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_menubar_title_max_length(py_opts, opts); diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 84ec7475c..dd1ea8399 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -206,6 +206,9 @@ bell_path(PyObject *src, Options *opts) { STR_SETTER(bell_path); } static inline void bell_theme(PyObject *src, Options *opts) { STR_SETTER(bell_theme); } +static inline void +macos_ns_window_layer(PyObject *src, Options *opts) { STR_SETTER(macos_ns_window_layer); } + static inline void window_logo_path(PyObject *src, Options *opts) { STR_SETTER(default_window_logo); } @@ -604,6 +607,6 @@ free_allocs_in_options(Options *opts) { free_background_images(opts); #define F(x) free(opts->x); opts->x = NULL; F(select_by_word_characters); F(url_excluded_characters); F(select_by_word_characters_forward); - F(bell_path); F(bell_theme); F(default_window_logo); + F(bell_path); F(bell_theme); F(macos_ns_window_layer); F(default_window_logo); #undef F } diff --git a/kitty/options/types.py b/kitty/options/types.py index 2766de82d..fb7d7f196 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -391,12 +391,14 @@ option_names = ( 'macos_fullscreen_ignore_safe_area_insets', 'macos_hide_from_tasks', 'macos_menubar_title_max_length', + 'macos_ns_window_layer', 'macos_option_as_alt', 'macos_quit_when_last_window_closed', 'macos_show_window_title_in', 'macos_thicken_font', 'macos_titlebar_color', 'macos_traditional_fullscreen', + 'macos_use_physical_screen_frame', 'macos_window_resizable', 'map', 'map_timeout', @@ -605,12 +607,14 @@ class Options: macos_fullscreen_ignore_safe_area_insets: bool = False macos_hide_from_tasks: bool = False macos_menubar_title_max_length: int = 0 + macos_ns_window_layer: str = 'unset' macos_option_as_alt: int = 0 macos_quit_when_last_window_closed: bool = False macos_show_window_title_in: choices_for_macos_show_window_title_in = 'all' macos_thicken_font: float = 0 macos_titlebar_color: int = 0 macos_traditional_fullscreen: bool = False + macos_use_physical_screen_frame: bool = False macos_window_resizable: bool = True map_timeout: float = 0 mark1_background: Color = Color(152, 211, 203) diff --git a/kitty/simple_cli_definitions.py b/kitty/simple_cli_definitions.py index b5b41d46b..bcb82b59e 100644 --- a/kitty/simple_cli_definitions.py +++ b/kitty/simple_cli_definitions.py @@ -573,7 +573,7 @@ panel_defaults = { 'edge': 'top', 'layer': 'bottom', 'override': '', 'cls': f'{appname}-panel', 'focus_policy': 'not-allowed', 'exclusive_zone': '-1', 'override_exclusive_zone': 'no', 'single_instance': 'no', 'instance_group': '', 'toggle_visibility': 'no', - 'start_as_hidden': 'no', 'detach': 'no', 'detached_log': '', 'use_physical_screen_frame': 'no', + 'start_as_hidden': 'no', 'detach': 'no', 'detached_log': '', } def build_panel_cli_spec(defaults: dict[str, str]) -> str: @@ -639,20 +639,12 @@ that the panel is centered instead of in the top left corner and the margins hav --layer -choices=background,bottom,top,shell,overlay +choices=background,bottom,top,overlay default={layer} On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer on which the panel should be drawn. This parameter is ignored and set to :code:`background` if :option:`--edge` is set to :code:`background`. On macOS, maps -these to appropriate NSWindow *levels*. On macOS, :code:`shell` places the panel -above normal application windows but below native system UI such as the menu bar. - - ---use-physical-screen-frame -type=bool-set -default={use_physical_screen_frame} -On macOS, use the physical screen frame rather than the visible frame when placing the panel. -This allows panels to draw in areas reserved for the native menu bar or dock. Ignored on other platforms. +these to appropriate NSWindow *levels*. --config -c diff --git a/kitty/state.h b/kitty/state.h index ce0307e70..fa88d9b28 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -78,11 +78,11 @@ typedef struct Options { bool on_cross, on_drop; } focus_follows_mouse; unsigned int hide_window_decorations; - bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen, macos_fullscreen_ignore_safe_area_insets; + bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen, macos_fullscreen_ignore_safe_area_insets, macos_use_physical_screen_frame; unsigned int macos_option_as_alt; float macos_thicken_font; WindowTitleIn macos_show_window_title_in; - char *bell_path, *bell_theme; + char *bell_path, *bell_theme, *macos_ns_window_layer; float background_opacity, dim_opacity; ScrollbarVisibilityPolicy scrollbar; diff --git a/kitty/types.py b/kitty/types.py index b7df467a6..d3fb4603d 100644 --- a/kitty/types.py +++ b/kitty/types.py @@ -85,7 +85,6 @@ class LayerShellConfig(NamedTuple): requested_exclusive_zone: int = -1 override_exclusive_zone: bool = False hide_on_focus_loss: bool = False - use_physical_screen_frame: bool = False def mod_to_names(mods: int, has_kitty_mod: bool = False, kitty_mod: int = 0) -> Iterator[str]: