From c9bf7e42365a679ee552cb20214e6c3680f19ff6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 22 Apr 2025 22:52:01 +0530 Subject: [PATCH] Factor out Cocoa layer shell code into the proper function --- glfw/cocoa_platform.h | 6 ++- glfw/cocoa_window.m | 120 ++++++++++++++++++------------------------ glfw/glfw3.h | 3 ++ kitty/glfw-wrapper.h | 3 ++ kitty/glfw.c | 21 +++++--- 5 files changed, 76 insertions(+), 77 deletions(-) diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index 1c78ce170..df33821c7 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -152,8 +152,10 @@ typedef struct _GLFWwindowNS UInt32 deadKeyState; // Layer shell windows - bool is_layer_shell; - GLFWLayerShellConfig layer_shell_config; + struct { + bool is_active; + GLFWLayerShellConfig config; + } layer_shell; // Whether a render frame has been requested for this window bool renderFrameRequested; diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index be8e172f5..1e5b18fe4 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1652,8 +1652,8 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { - (BOOL)canBecomeKeyWindow { - if (glfw_window && glfw_window->ns.is_layer_shell) { - switch(glfw_window->ns.layer_shell_config.focus_policy) { + if (glfw_window && glfw_window->ns.layer_shell.is_active) { + switch(glfw_window->ns.layer_shell.config.focus_policy) { case GLFW_FOCUS_NOT_ALLOWED: return NO; case GLFW_FOCUS_EXCLUSIVE: return YES; case GLFW_FOCUS_ON_DEMAND: return YES; @@ -1829,9 +1829,9 @@ static bool createNativeWindow(_GLFWwindow* window, int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, const GLFWLayerShellConfig *lsc) { window->ns.deadKeyState = 0; if (lsc) { - window->ns.is_layer_shell = true; - window->ns.layer_shell_config = *lsc; - } else window->ns.is_layer_shell = false; + window->ns.layer_shell.is_active = true; + window->ns.layer_shell.config = *lsc; + } else window->ns.layer_shell.is_active = false; if (!_glfw.ns.finishedLaunching) { [NSApp run]; @@ -1913,9 +1913,51 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) window->ns.object = nil; } -bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { - (void)window; (void)value; - return false; +static NSScreen* +screen_for_window_center(_GLFWwindow *window) { + NSRect windowFrame = [window->ns.object frame]; + NSPoint windowCenter = NSMakePoint(NSMidX(windowFrame), NSMidY(windowFrame)); + for (NSScreen *screen in [NSScreen screens]) { + if (NSPointInRect(windowCenter, [screen frame])) { + return screen; + } + } + return NSScreen.mainScreen; +} + +bool +_glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) { + window->resizable = false; + const bool is_transparent = ![window->ns.object isOpaque]; + int background_blur = value->related.background_blur; + if (!is_transparent || value->related.background_opacity >= 1.f) { background_blur = 0; } + [window->ns.object setBackgroundColor:nil]; + _glfwPlatformSetWindowBlur(window, background_blur); + window->ns.titlebar_hidden = true; + window->decorated = false; + [window->ns.object setTitlebarAppearsTransparent:false]; + [window->ns.object setHasShadow:false]; + [window->ns.object setTitleVisibility:NSWindowTitleHidden]; + NSColorSpace *cs = nil; + switch (value->related.color_space) { + case SRGB_COLORSPACE: cs = [NSColorSpace sRGBColorSpace]; break; + case DISPLAY_P3_COLORSPACE: cs = [NSColorSpace displayP3ColorSpace]; break; + case DEFAULT_COLORSPACE: cs = nil; break; // using deviceRGBColorSpace causes a hang when transitioning to fullscreen + } + [window->ns.object setColorSpace:cs]; + [[window->ns.object standardWindowButton: NSWindowCloseButton] setHidden:true]; + [[window->ns.object standardWindowButton: NSWindowMiniaturizeButton] setHidden:true]; + [[window->ns.object standardWindowButton: NSWindowZoomButton] setHidden:true]; + [window->ns.object setStyleMask:NSWindowStyleMaskBorderless]; + // HACK: Changing the style mask can cause the first responder to be cleared + [window->ns.object makeFirstResponder:window->ns.view]; + uint32_t width = 0, height = 0; + NSScreen *screen = screen_for_window_center(window); + CGFloat monitor_width = NSWidth(screen.frame), monitor_height = NSHeight(screen.frame); + window->ns.layer_shell.config.size_callback((GLFWwindow*)window, &window->ns.layer_shell.config, (unsigned)monitor_width, (unsigned)monitor_height, &width, &height); + CGFloat x = NSMinX(screen.visibleFrame), y = NSMinY(screen.visibleFrame); + [window->ns.object setFrame:NSMakeRect(x, y, (CGFloat)width, (CGFloat)height) display:YES]; + return true; } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) @@ -1966,7 +2008,7 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { - if (window->ns.is_layer_shell) return; + if (window->ns.layer_shell.is_active) return; if (window->monitor) { if (window->monitor->window == window) @@ -3003,67 +3045,9 @@ GLFWAPI GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, return current; } -static NSScreen* -screen_for_window_center(_GLFWwindow *window) { - NSRect windowFrame = [window->ns.object frame]; - NSPoint windowCenter = NSMakePoint(NSMidX(windowFrame), NSMidY(windowFrame)); - for (NSScreen *screen in [NSScreen screens]) { - if (NSPointInRect(windowCenter, [screen frame])) { - return screen; - } - } - return NSScreen.mainScreen; -} - -static void -apply_layer_shell_properties(_GLFWwindow *window, int background_blur, unsigned system_color, int color_space) { - window->resizable = false; - const bool is_transparent = ![window->ns.object isOpaque]; - if (!is_transparent) { background_blur = 0; } - NSAppearance *appearance = nil; - NSAppearance *light_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight] : [NSAppearance appearanceNamed:NSAppearanceNameAqua]; - NSAppearance *dark_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark] : [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; - NSColor *background = nil; - switch (system_color) { - case 1: - appearance = light_appearance; break; - case 2: - appearance = dark_appearance; break; - } - [window->ns.object setBackgroundColor:background]; - [window->ns.object setAppearance:appearance]; - _glfwPlatformSetWindowBlur(window, background_blur); - window->ns.titlebar_hidden = true; - window->decorated = false; - [window->ns.object setTitlebarAppearsTransparent:false]; - [window->ns.object setHasShadow:false]; - [window->ns.object setTitleVisibility:NSWindowTitleHidden]; - NSColorSpace *cs = nil; - switch (color_space) { - case SRGB_COLORSPACE: cs = [NSColorSpace sRGBColorSpace]; break; - case DISPLAY_P3_COLORSPACE: cs = [NSColorSpace displayP3ColorSpace]; break; - case DEFAULT_COLORSPACE: cs = nil; break; // using deviceRGBColorSpace causes a hang when transitioning to fullscreen - } - [window->ns.object setColorSpace:cs]; - [[window->ns.object standardWindowButton: NSWindowCloseButton] setHidden:true]; - [[window->ns.object standardWindowButton: NSWindowMiniaturizeButton] setHidden:true]; - [[window->ns.object standardWindowButton: NSWindowZoomButton] setHidden:true]; - [window->ns.object setStyleMask:NSWindowStyleMaskBorderless]; - // HACK: Changing the style mask can cause the first responder to be cleared - [window->ns.object makeFirstResponder:window->ns.view]; - uint32_t width = 0, height = 0; - NSScreen *screen = screen_for_window_center(window); - NSRect frame = screen.frame; - CGFloat monitor_width = NSWidth(frame), monitor_height = NSHeight(frame); - window->ns.layer_shell_config.size_callback((GLFWwindow*)window, &window->ns.layer_shell_config, (unsigned)monitor_width, (unsigned)monitor_height, &width, &height); - NSRect window_frame = [window->ns.object frame]; - CGFloat x = 0, y = NSMinY(window_frame); - [window->ns.object setFrame:NSMakeRect(x, y, (CGFloat)width, (CGFloat)height) display:YES]; -} - 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.is_layer_shell) { apply_layer_shell_properties(window, background_blur, system_color, color_space); return; } + if (window->ns.layer_shell.is_active) return; const bool is_transparent = ![window->ns.object isOpaque]; if (!is_transparent) { background_opacity = 1.0; background_blur = 0; } NSColor *background = nil; diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 54556b943..8d3d4574a 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1321,6 +1321,9 @@ typedef struct GLFWLayerShellConfig { unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height); struct { double xdpi, ydpi, xscale, yscale; } expected; + struct { + float background_opacity; int background_blur, color_space; + } related; } GLFWLayerShellConfig; typedef struct GLFWDBUSNotificationData { diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 841065fbe..f37adadcf 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1059,6 +1059,9 @@ typedef struct GLFWLayerShellConfig { unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height); struct { double xdpi, ydpi, xscale, yscale; } expected; + struct { + float background_opacity; int background_blur, color_space; + } related; } GLFWLayerShellConfig; typedef struct GLFWDBUSNotificationData { diff --git a/kitty/glfw.c b/kitty/glfw.c index 7938a2b29..12db0848c 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1067,7 +1067,7 @@ apply_window_chrome_state(GLFWwindow *w, WindowChromeState new_state, int width, void set_os_window_chrome(OSWindow *w) { - if (!w->handle) return; + if (!w->handle || w->is_layer_shell) return; color_type bg = OPT(background); if (w->num_tabs > w->active_tab) { Tab *tab = w->tabs + w->active_tab; @@ -1229,6 +1229,14 @@ layer_shell_config_from_python(PyObject *p, GLFWLayerShellConfig *ans) { #undef A } +static bool +set_layer_shell_config_for(OSWindow *w, GLFWLayerShellConfig *lsc) { + lsc->related.background_opacity = w->background_opacity; + lsc->related.background_blur = OPT(background_blur); + lsc->related.color_space = OPT(macos_colorspace); + return glfwSetLayerShellConfig(w->handle, lsc); +} + static PyObject* create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { int x = INT_MIN, y = INT_MIN, window_state = WINDOW_NORMAL, disallow_override_title = 0; @@ -1446,11 +1454,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { } } init_window_chrome_state(&w->last_window_chrome, OPT(background), w->is_semi_transparent, w->background_opacity); -#ifdef __APPLE__ - apply_window_chrome_state(w->handle, w->last_window_chrome, width, height, OPT(hide_window_decorations) != 0); -#else - apply_window_chrome_state(w->handle, w->last_window_chrome, width, height, false); -#endif + if (w->is_layer_shell) { + 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); // 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. @@ -2514,7 +2521,7 @@ set_layer_shell_config(PyObject *self UNUSED, PyObject *args) { if (!window || !window->handle || !window->is_layer_shell) Py_RETURN_FALSE; GLFWLayerShellConfig lsc = {0}; if (!layer_shell_config_from_python(pylsc, &lsc)) return NULL; - return Py_NewRef(glfwSetLayerShellConfig(window->handle, &lsc) ? Py_True : Py_False); + return Py_NewRef(set_layer_shell_config_for(window, &lsc) ? Py_True : Py_False); } // Boilerplate {{{