summaryrefslogtreecommitdiff
path: root/src/platform/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/cocoa')
-rw-r--r--src/platform/cocoa/desktop.m57
-rw-r--r--src/platform/cocoa/window.m167
2 files changed, 176 insertions, 48 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;
}
}
diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m
index 8f01365..a78c2ce 100644
--- a/src/platform/cocoa/window.m
+++ b/src/platform/cocoa/window.m
@@ -118,7 +118,8 @@ struct cocoa_surface {
uint64_t pen_device_id, pen_tool_id;
uint32_t pen_buttons;
- char title[160];
+ /* Retained mouse-down of the press in progress, for interactive moves. */
+ NSEvent *mouse_down;
struct yasl_private_frame_clock clock;
};
@@ -150,7 +151,6 @@ struct cocoa_display {
} drag;
int depth;
bool open;
- bool launched;
bool pumping;
bool owns_loop;
};
@@ -275,7 +275,9 @@ static void sync_size(struct cocoa_surface *w, bool force)
yasl_private_surface_notify_configure(&w->base, (struct yasl_private_geometry){
.size = size, .scale_120 = scale_120,
});
- if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU)
+ /* During a live resize the app draws the new size here, before the frame is
+ * rendered and committed below. */
+ if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->view.inLiveResize)
yasl_private_surface_notify_refresh(&w->base);
if (!w->destroyed && !w->closed && w->view.inLiveResize)
render_and_commit(w);
@@ -851,7 +853,8 @@ static struct yasl_private_vector2i dragging_pos(YaslView *view, id<NSDraggingIn
{
[super viewWillStartLiveResize];
struct cocoa_surface *w = self.surface;
- if (w)
+ /* Layers that the app owns through Metal or Vulkan keep their own setting. */
+ if (w && w->base.presenter == YASL_PRIVATE_PRESENTER_CPU)
w->layer.presentsWithTransaction = YES;
}
@@ -861,7 +864,8 @@ static struct yasl_private_vector2i dragging_pos(YaslView *view, id<NSDraggingIn
struct cocoa_surface *w = self.surface;
if (!w)
return;
- w->layer.presentsWithTransaction = NO;
+ if (w->base.presenter == YASL_PRIVATE_PRESENTER_CPU)
+ w->layer.presentsWithTransaction = NO;
cocoa_surface_schedule_redraw(w);
}
@@ -933,6 +937,13 @@ static struct yasl_private_vector2i dragging_pos(YaslView *view, id<NSDraggingIn
- (void)mouseDown:(NSEvent *)event
{
[self.window makeFirstResponder:self];
+ /* Right and other buttons also arrive here. Keep the press for interactive moves. */
+ struct cocoa_surface *w = self.surface;
+ if (w) {
+ [event retain];
+ [w->mouse_down release];
+ w->mouse_down = event;
+ }
push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_DOWN, cocoa_button(event.buttonNumber));
}
- (void)rightMouseDown:(NSEvent *)event { [self mouseDown:event]; }
@@ -940,6 +951,11 @@ static struct yasl_private_vector2i dragging_pos(YaslView *view, id<NSDraggingIn
- (void)mouseUp:(NSEvent *)event
{
+ struct cocoa_surface *w = self.surface;
+ if (w) {
+ [w->mouse_down release];
+ w->mouse_down = nil;
+ }
push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_UP, cocoa_button(event.buttonNumber));
}
- (void)rightMouseUp:(NSEvent *)event { [self mouseUp:event]; }
@@ -1226,6 +1242,18 @@ static void push_text_edit(struct cocoa_surface *w, const char *text, size_t len
}
}
+/* Borderless windows cannot take focus unless they say so. Toplevels may; popups
+ * never take focus from their parent. */
+- (BOOL)canBecomeKeyWindow
+{
+ return self.surface && !self.surface->is_popup;
+}
+
+- (BOOL)canBecomeMainWindow
+{
+ return self.surface && !self.surface->is_popup;
+}
+
@end
@implementation YaslWindowController
@@ -1303,6 +1331,32 @@ static void push_text_edit(struct cocoa_surface *w, const char *text, size_t len
sync_size(self.surface, false);
}
+/* The close button is a request, as on Wayland and X11: the app destroys the
+ * surface to comply. Programmatic closes come through windowWillClose instead. */
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+ (void)sender;
+ struct cocoa_surface *w = self.surface;
+ if (w && !w->closed) {
+ darwin_ctx.depth++;
+ yasl_private_surface_notify_close(&w->base);
+ darwin_ctx.depth--;
+ }
+ return NO;
+}
+
+- (void)windowWillStartLiveResize:(NSNotification *)notification
+{
+ (void)notification;
+ darwin_ctx.base.modal = true;
+}
+
+- (void)windowDidEndLiveResize:(NSNotification *)notification
+{
+ (void)notification;
+ darwin_ctx.base.modal = false;
+}
+
- (void)windowWillClose:(NSNotification *)notification
{
(void)notification;
@@ -1584,7 +1638,8 @@ void cocoa_surface_schedule_redraw(struct cocoa_surface *w)
static void app_init(const char *app_name)
{
- if (darwin_ctx.launched) return;
+ /* finishLaunching runs once per process, even when the display reopens. */
+ static bool finished_launching;
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
NSWindow.allowsAutomaticWindowTabbing = NO;
@@ -1616,8 +1671,10 @@ static void app_init(const char *app_name)
[event.window sendEvent:event];
return nil;
}];
- [NSApp finishLaunching];
- darwin_ctx.launched = true;
+ if (!finished_launching) {
+ [NSApp finishLaunching];
+ finished_launching = true;
+ }
}
static void place_window(NSWindow *window)
@@ -1633,7 +1690,10 @@ static void place_window(NSWindow *window)
struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec)
{
- assert([NSThread isMainThread]);
+ if (![NSThread isMainThread]) {
+ errno = EPERM;
+ return NULL;
+ }
if (darwin_ctx.open) return NULL;
const char *name = spec && spec->app_id ? spec->app_id : "YASL";
@autoreleasepool { app_init(name); }
@@ -1691,7 +1751,10 @@ uint32_t cocoa_display_caps(const struct cocoa_display *d)
struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
const struct yasl_private_window_spec *spec, void *graphics_device)
{
- assert([NSThread isMainThread]);
+ if (![NSThread isMainThread]) {
+ errno = EPERM;
+ return NULL;
+ }
if (!d || !d->open || !spec)
return NULL;
@autoreleasepool {
@@ -1715,14 +1778,15 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
cocoa_surface_set_callbacks(w, NULL, NULL);
w->clock.refresh_ns = YASL_PRIVATE_REFRESH_DEFAULT_NS;
const char *t = spec->title ? spec->title : "YASL";
- strncpy(w->title, t, sizeof(w->title) - 1);
int win_w = spec->size.x > 0 ? spec->size.x : 800;
int win_h = spec->size.y > 0 ? spec->size.y : 480;
+ /* Undecorated windows stay minimizable and edge-resizable; only FIXED_SIZE
+ * removes resizing. */
NSWindowStyleMask style = (spec->flags & YASL_PRIVATE_SURFACE_UNDECORATED) ?
- NSWindowStyleMaskBorderless : NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
- NSWindowStyleMaskMiniaturizable;
- if (!(spec->flags & (YASL_PRIVATE_SURFACE_UNDECORATED | YASL_PRIVATE_SURFACE_FIXED_SIZE)))
+ NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable :
+ NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+ if (!(spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE))
style |= NSWindowStyleMaskResizable;
w->window = [[YaslWindow alloc] initWithContentRect:NSMakeRect(0, 0, win_w, win_h)
styleMask:style
@@ -1738,7 +1802,8 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
w->window.acceptsMouseMovedEvents = YES;
w->window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
w->window.titlebarAppearsTransparent = YES;
- w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL";
+ /* The full title: a byte-limited copy could split a UTF-8 sequence. */
+ w->window.title = [NSString stringWithUTF8String:t] ?: @"YASL";
w->window.colorSpace = [NSColorSpace sRGBColorSpace];
w->view = [[YaslView alloc] initWithFrame:w->window.contentView.bounds];
@@ -1791,6 +1856,7 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d,
static void surface_free(struct cocoa_surface *w)
{
+ [w->mouse_down release];
[w->link release];
[w->software_buffer release];
[w->software_queue release];
@@ -1843,6 +1909,10 @@ void cocoa_surface_destroy(struct cocoa_surface *w)
w->cbs = &no_callbacks;
w->ud = NULL;
[w->link invalidate];
+ /* The delegate is removed before the window can end its live resize, so the
+ * modal flag would never be cleared there. */
+ if (w->window.inLiveResize)
+ darwin_ctx.base.modal = false;
w->window.delegate = nil;
w->controller.surface = NULL;
w->window.surface = NULL;
@@ -1873,8 +1943,8 @@ void cocoa_surface_destroy(struct cocoa_surface *w)
void cocoa_surface_set_title(struct cocoa_surface *w, const char *title)
{
if (!w || w->closed || !title) return;
- strncpy(w->title, title, sizeof(w->title) - 1);
- w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL";
+ /* The full title: a byte-limited copy could split a UTF-8 sequence. */
+ w->window.title = [NSString stringWithUTF8String:title] ?: @"YASL";
}
static bool impl_surface_set_size(struct yasl_surface *surface, struct yasl_private_vector2i size)
@@ -2207,7 +2277,10 @@ static NSRect popup_calc_frame(struct cocoa_surface *parent, const struct yasl_p
struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent,
const struct yasl_private_popup_spec *spec)
{
- assert([NSThread isMainThread]);
+ if (![NSThread isMainThread]) {
+ errno = EPERM;
+ return NULL;
+ }
if (!parent || parent->closed || !parent->window || !parent->view ||
!popup_spec_valid(parent, spec))
return NULL;
@@ -2223,9 +2296,10 @@ struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent,
struct cocoa_surface *w = calloc(1, sizeof(*w));
if (!w) return NULL;
+ /* Popups are always CPU surfaces, whatever the parent draws with. */
w->base.display = parent->base.display;
- w->base.presenter = parent->base.presenter;
- w->base.creation_flags = parent->base.creation_flags;
+ w->base.presenter = YASL_PRIVATE_PRESENTER_CPU;
+ w->base.creation_flags = parent->base.creation_flags | YASL_PRIVATE_SURFACE_CPU;
cocoa_surface_set_callbacks(w, NULL, NULL);
w->clock.refresh_ns = parent->clock.refresh_ns ? parent->clock.refresh_ns : YASL_PRIVATE_REFRESH_DEFAULT_NS;
w->parent = parent;
@@ -2256,21 +2330,19 @@ struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent,
w->layer = (CAMetalLayer *)w->view.layer;
w->layer.opaque = NO;
w->layer.device = parent->layer.device;
- if (parent->software_queue) {
- w->software_queue = [w->layer.device newCommandQueue];
- if (!w->software_queue) {
- w->window.delegate = nil;
- w->controller.surface = NULL;
- [w->view release];
- [w->controller release];
- [w->window release];
- free(w);
- return NULL;
- }
- w->layer.framebufferOnly = NO;
- } else {
- w->layer.framebufferOnly = parent->layer.framebufferOnly;
+ /* A CPU popup needs the same software queue as a CPU toplevel, even when
+ * the parent presents through Metal. */
+ w->software_queue = [w->layer.device newCommandQueue];
+ if (!w->software_queue) {
+ w->window.delegate = nil;
+ w->controller.surface = NULL;
+ [w->view release];
+ [w->controller release];
+ [w->window release];
+ free(w);
+ return NULL;
}
+ w->layer.framebufferOnly = NO;
w->link = [[w->view displayLinkWithTarget:w->view
selector:@selector(displayLinkFired:)] retain];
@@ -2307,8 +2379,17 @@ bool cocoa_surface_popup_reposition(struct cocoa_surface *popup,
bool cocoa_surface_start_interactive_move(struct cocoa_surface *s)
{
- if (!s || !s->window) return false;
- [s->window performWindowDragWithEvent:[NSApp currentEvent]];
+ if (!s || !s->window || s->closed) {
+ errno = EINVAL;
+ return false;
+ }
+ /* The drag must start from a press still in progress. NSApp's current event
+ * need not be that press, so use the mouse-down recorded for this window. */
+ if (!s->mouse_down || [NSEvent pressedMouseButtons] == 0) {
+ errno = ESTALE;
+ return false;
+ }
+ [s->window performWindowDragWithEvent:s->mouse_down];
return true;
}
@@ -2344,8 +2425,18 @@ bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *ori
const struct yasl_private_drag_image *image,
const struct yasl_private_drag_listener *l, void *ud)
{
- if (!d || !origin || d->drag.active || !l)
+ if (!d || !origin || !l) {
+ errno = EINVAL;
+ return false;
+ }
+ if (d->drag.active) {
+ errno = EBUSY;
return false;
+ }
+ if ([NSEvent pressedMouseButtons] == 0) {
+ errno = ESTALE;
+ return false;
+ }
d->drag.active = true;
d->drag.origin = origin;
d->drag.carried = NULL;
@@ -2412,12 +2503,14 @@ bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_private_vecto
bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_private_resize_edge edge)
{
(void)s; (void)edge;
+ errno = ENOTSUP;
return false;
}
bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_private_vector2i pos)
{
(void)s; (void)pos;
+ errno = ENOTSUP;
return false;
}