From 0eec911bdabb130415aeb2fc6eb4281b891a5d2f Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Wed, 14 Oct 2026 14:05:19 +0300 Subject: Fix Cocoa backend focus, close, live resize, popups and notifications - Undecorated windows can become key and main; popups never take focus. - Undecorated windows stay miniaturizable and resizable unless FIXED_SIZE. - The close button sends a CLOSE request (windowShouldClose:) and does not close; the app destroys the surface to comply. - Refresh runs during live resize: the modal flag is set between windowWillStartLiveResize: and windowDidEndLiveResize:, and CPU surfaces refresh before the frame is rendered. A destroyed window clears the flag. - presentsWithTransaction is only changed for CPU presenters. - Display open and surface creation return EPERM off the main thread. - Popups are always CPU surfaces with a software queue, also under a Metal parent. - Interactive move and drag start use the recorded mouse-down; without a press they set ESTALE. Interactive resize and window menu return ENOTSUP. - Notifications: ENOTSUP outside an app bundle; one authorization request, notifications queued until it is granted, EACCES after a denial. - Titles use the full string instead of a 159-byte copy that could split a UTF-8 sequence. - finishLaunching runs once per process, not once per display open. Co-Authored-By: Claude Haiku 5.5 --- src/platform/cocoa/desktop.m | 57 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 11 deletions(-) (limited to 'src/platform/cocoa/desktop.m') diff --git a/src/platform/cocoa/desktop.m b/src/platform/cocoa/desktop.m index 76aa551..25393d5 100644 --- a/src/platform/cocoa/desktop.m +++ b/src/platform/cocoa/desktop.m @@ -4,7 +4,9 @@ #include "common/desktop.h" #include "common/private_desktop.h" +#include #include +#include #include #include #include @@ -44,7 +46,12 @@ bool desktop_open_uri(const char *uri) @end static YaslNotificationDelegate *notification_delegate; +/* Main-thread state. Notifications made before the answer to the one + * authorization request wait in the queue and are posted when it is granted. */ +static NSMutableArray *notification_queue; static bool authorization_requested; +static bool authorization_granted; +static bool authorization_denied; /* UNUserNotificationCenter raises an exception outside an app bundle. */ static bool notifications_available(void) @@ -56,24 +63,26 @@ static bool notifications_available(void) bool desktop_notify(const char *app_id, const char *title, const char *body) { (void)app_id; - if (!notifications_available()) return false; + if (!notifications_available()) { + errno = ENOTSUP; + return false; + } + if (authorization_denied) { + errno = EACCES; + return false; + } @autoreleasepool { NSString *heading = [NSString stringWithUTF8String:title]; NSString *message = [NSString stringWithUTF8String:body]; - if (!heading || !message) return false; + if (!heading || !message) { + errno = EINVAL; + return false; + } UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; if (!notification_delegate) { notification_delegate = [[YaslNotificationDelegate alloc] init]; center.delegate = notification_delegate; } - if (!authorization_requested) { - authorization_requested = true; - [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert - completionHandler:^(BOOL granted, NSError *error) { - (void)granted; - (void)error; - }]; - } UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; content.title = heading; content.body = message; @@ -82,7 +91,33 @@ bool desktop_notify(const char *app_id, const char *title, const char *body) content:content trigger:nil]; [content release]; - [center addNotificationRequest:request withCompletionHandler:nil]; + if (authorization_granted) { + [center addNotificationRequest:request withCompletionHandler:nil]; + return true; + } + if (!notification_queue) + notification_queue = [[NSMutableArray alloc] init]; + [notification_queue addObject:request]; + if (!authorization_requested) { + authorization_requested = true; + [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert + completionHandler:^(BOOL granted, NSError *error) { + (void)error; + /* The handler runs on a background queue; the state is main-thread only. */ + dispatch_async(dispatch_get_main_queue(), ^{ + if (!granted) { + authorization_denied = true; + [notification_queue removeAllObjects]; + return; + } + authorization_granted = true; + UNUserNotificationCenter *granted_center = [UNUserNotificationCenter currentNotificationCenter]; + for (UNNotificationRequest *queued in notification_queue) + [granted_center addNotificationRequest:queued withCompletionHandler:nil]; + [notification_queue removeAllObjects]; + }); + }]; + } return true; } } -- cgit v1.2.3