summaryrefslogtreecommitdiff
path: root/src/platform/cocoa/desktop.m
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-14 14:05:19 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-14 14:06:16 +0300
commit0eec911bdabb130415aeb2fc6eb4281b891a5d2f (patch)
tree0ab5dfa8ee08d1fc5ecc7092fd43d2cda723d2fa /src/platform/cocoa/desktop.m
parent87279f6903343e621c0446b20d1e7d6a495f1125 (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'src/platform/cocoa/desktop.m')
-rw-r--r--src/platform/cocoa/desktop.m57
1 files changed, 46 insertions, 11 deletions
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 <dispatch/dispatch.h>
#include <CoreFoundation/CoreFoundation.h>
+#include <errno.h>
#include <limits.h>
#include <mach-o/dyld.h>
#include <stdio.h>
@@ -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<UNNotificationRequest *> *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;
}
}