diff --git a/glfw/cocoa_platform.h b/glfw/cocoa_platform.h index e90ba4050..1c78ce170 100644 --- a/glfw/cocoa_platform.h +++ b/glfw/cocoa_platform.h @@ -150,6 +150,11 @@ typedef struct _GLFWwindowNS GLFWcocoatogglefullscreenfun toggleFullscreenCallback; // Dead key state UInt32 deadKeyState; + + // Layer shell windows + bool is_layer_shell; + GLFWLayerShellConfig layer_shell_config; + // Whether a render frame has been requested for this window bool renderFrameRequested; GLFWcocoarenderframefun renderFrameCallback; diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index ecfdda066..be8e172f5 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1652,6 +1652,13 @@ 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) { + case GLFW_FOCUS_NOT_ALLOWED: return NO; + case GLFW_FOCUS_EXCLUSIVE: return YES; + case GLFW_FOCUS_ON_DEMAND: return YES; + } + } // Required for NSWindowStyleMaskBorderless windows return YES; } @@ -1820,8 +1827,11 @@ static bool createNativeWindow(_GLFWwindow* window, ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, const GLFWLayerShellConfig *lsc) { - (void)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; if (!_glfw.ns.finishedLaunching) { [NSApp run]; @@ -1956,6 +1966,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->monitor) { if (window->monitor->window == window) @@ -2992,8 +3003,67 @@ 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; } const bool is_transparent = ![window->ns.object isOpaque]; if (!is_transparent) { background_opacity = 1.0; background_blur = 0; } NSColor *background = nil; diff --git a/kittens/panel/main.py b/kittens/panel/main.py index 67cda68b2..e3ce2a98b 100644 --- a/kittens/panel/main.py +++ b/kittens/panel/main.py @@ -326,8 +326,6 @@ def handle_single_instance_command(boss: BossType, sys_args: Sequence[str], envi def main(sys_args: list[str]) -> None: global args - if is_macos: - raise SystemExit('Currently the panel kitten is not supported on macOS') args, items = parse_panel_args(sys_args[1:]) if not items: raise SystemExit('You must specify the program to run') @@ -336,12 +334,14 @@ def main(sys_args: list[str]) -> None: sys.argv.append('--debug-rendering') for config in args.config: sys.argv.extend(('--config', config)) - sys.argv.extend(('--class', args.cls)) + if not is_macos: + sys.argv.extend(('--class', args.cls)) if args.name: sys.argv.extend(('--name', args.name)) for override in args.override: sys.argv.extend(('--override', override)) sys.argv.append('--override=linux_display_server=auto') + sys.argv.append('--override=macos_quit_when_last_window_closed=yes') if args.single_instance: sys.argv.append('--single-instance') if args.listen_on: @@ -352,8 +352,9 @@ def main(sys_args: list[str]) -> None: from kitty.main import run_app run_app.cached_values_name = 'panel' run_app.layer_shell_config = layer_shell_config(args) - run_app.first_window_callback = setup_x11_window - run_app.initial_window_size_func = initial_window_size_func + if not is_macos: + run_app.first_window_callback = setup_x11_window + run_app.initial_window_size_func = initial_window_size_func real_main() diff --git a/kitty/glfw.c b/kitty/glfw.c index 2a3c3d2f9..7938a2b29 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1134,7 +1134,7 @@ calculate_layer_shell_window_size( } float xscale = (float)config->expected.xscale, yscale = (float)config->expected.yscale; double xdpi = config->expected.xdpi, ydpi = config->expected.ydpi; - if (glfwWaylandIsWindowFullyCreated(window)) { + if (global_state.is_apple || (global_state.is_wayland && glfwWaylandIsWindowFullyCreated(window))) { glfwGetWindowContentScale(window, &xscale, &yscale); dpi_from_scale(xscale, yscale, &xdpi, &ydpi); } @@ -1238,12 +1238,19 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|OOOOpO", (char**)kwlist, &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &optional_window_state, &load_programs, &optional_x, &optional_y, &disallow_override_title, &layer_shell_config)) return NULL; GLFWLayerShellConfig *lsc = NULL, lsc_stack = {0}; - if (layer_shell_config && layer_shell_config != Py_None && global_state.is_wayland) { - if (!glfwWaylandIsLayerShellSupported()) { - PyErr_SetString(PyExc_RuntimeError, "The Wayland compositor does not support the layer shell protocol."); - return NULL; - } + + if (layer_shell_config && layer_shell_config != Py_None ) { +#ifdef __APPLE__ lsc = &lsc_stack; +#else + if (global_state.is_wayland) { + if (!glfwWaylandIsLayerShellSupported()) { + PyErr_SetString(PyExc_RuntimeError, "The Wayland compositor does not support the layer shell protocol."); + return NULL; + } + lsc = &lsc_stack; + } +#endif } else { if (optional_window_state && optional_window_state != Py_None) { if (!PyLong_Check(optional_window_state)) { PyErr_SetString(PyExc_TypeError, "window_state must be an int"); return NULL; } window_state = (int) PyLong_AsLong(optional_window_state); } if (optional_x && optional_x != Py_None) { if (!PyLong_Check(optional_x)) { PyErr_SetString(PyExc_TypeError, "x must be an int"); return NULL;} x = (int)PyLong_AsLong(optional_x); } @@ -1274,7 +1281,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { // See https://github.com/kovidgoyal/kitty/issues/7174#issuecomment-2000033873 if (!global_state.is_wayland) glfwWindowHint(GLFW_SRGB_CAPABLE, true); #ifdef __APPLE__ - cocoa_set_activation_policy(OPT(macos_hide_from_tasks)); + cocoa_set_activation_policy(OPT(macos_hide_from_tasks) || lsc != NULL); glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true); glfwSetApplicationShouldHandleReopen(on_application_reopen); glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu); @@ -1358,12 +1365,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (pret == NULL) return NULL; Py_DECREF(pret); if (x != INT_MIN && y != INT_MIN) glfwSetWindowPos(glfw_window, x, y); - bool is_apple = true; -#ifndef __APPLE__ - is_apple = false; - if (!global_state.is_wayland) glfwShowWindow(glfw_window); -#endif - if (global_state.is_wayland || is_apple) { + if (!global_state.is_apple && !global_state.is_wayland) glfwShowWindow(glfw_window); + if (global_state.is_wayland || global_state.is_apple) { float n_xscale, n_yscale; double n_xdpi, n_ydpi; get_window_content_scale(glfw_window, &n_xscale, &n_yscale, &n_xdpi, &n_ydpi); @@ -1391,7 +1394,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { w->disallow_title_changes = disallow_override_title; w->is_layer_shell = lsc != NULL; update_os_window_references(); - if (!w->is_layer_shell) { + if (!w->is_layer_shell || (global_state.is_apple && w->is_layer_shell && lsc->focus_policy == GLFW_FOCUS_EXCLUSIVE)) { for (size_t i = 0; i < global_state.num_os_windows; i++) { // On some platforms (macOS) newly created windows don't get the initial focus in event OSWindow *q = global_state.os_windows + i; diff --git a/kitty/state.c b/kitty/state.c index 0db978292..7935a3a46 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -769,10 +769,11 @@ PYWRAP1(set_options) { Py_CLEAR(global_state.options_object); Py_RETURN_NONE; } - global_state.is_wayland = is_wayland ? true : false; #ifdef __APPLE__ + global_state.is_apple = true; global_state.has_render_frames = true; #endif + global_state.is_wayland = is_wayland ? true : false; if (global_state.is_wayland) global_state.has_render_frames = true; global_state.debug_rendering = debug_rendering ? true : false; global_state.debug_font_fallback = debug_font_fallback ? true : false; diff --git a/kitty/state.h b/kitty/state.h index 954b760e1..3272374d7 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -330,7 +330,7 @@ typedef struct { OSWindow *os_windows; size_t num_os_windows, capacity; OSWindow *callback_os_window; - bool is_wayland; + bool is_wayland, is_apple; bool has_render_frames; bool debug_rendering, debug_font_fallback; bool has_pending_resizes, has_pending_closes;