Factor out Cocoa layer shell code into the proper function

This commit is contained in:
Kovid Goyal 2025-04-22 22:52:01 +05:30
parent e93338a81c
commit c9bf7e4236
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 76 additions and 77 deletions

View file

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

View file

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

3
glfw/glfw3.h vendored
View file

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

3
kitty/glfw-wrapper.h generated
View file

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

View file

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