diff options
Diffstat (limited to 'src/platform/cocoa/window.m')
| -rw-r--r-- | src/platform/cocoa/window.m | 102 |
1 files changed, 93 insertions, 9 deletions
diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index f1f0f03..811a7c6 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -13,11 +13,18 @@ #include <assert.h> #include <math.h> +#include <poll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/event.h> +#include <unistd.h> #define LINK_IDLE_FRAMES 8 +/* Application-defined event subtype that only wakes a waiting dispatch. */ +#define LOOP_WAKE_SUBTYPE 0x5953 +/* Wait bound while some loop descriptor cannot be watched with kqueue. */ +#define LOOP_FALLBACK_MS 10 #define SCROLL_POINTS_PER_LINE 10.0 enum { @@ -1119,6 +1126,54 @@ static void pump_window(struct cocoa_surface *w) @autoreleasepool { render_and_commit(w); } } +static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) +{ + (void)fd; + (void)flags; + (void)info; + NSEvent *wake = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:LOOP_WAKE_SUBTYPE + data1:0 + data2:0]; + [NSApp postEvent:wake atStart:YES]; +} + +/* A kqueue mirroring the loop's poll set, or -1. complete is false when some + * descriptor could not be registered. */ +static int loop_kqueue(struct yasl_loop *loop, bool *complete) +{ + *complete = true; + int kq = kqueue(); + if (kq < 0) { + *complete = false; + return -1; + } + const struct pollfd *fds; + int n = yasl_loop_poll_set(loop, &fds); + for (int i = 0; i < n; i++) { + struct kevent change; + if (fds[i].events & POLLIN) { + EV_SET(&change, fds[i].fd, EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(kq, &change, 1, NULL, 0, NULL) < 0) *complete = false; + } + if (fds[i].events & POLLOUT) { + EV_SET(&change, fds[i].fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + if (kevent(kq, &change, 1, NULL, 0, NULL) < 0) *complete = false; + } + } + return kq; +} + +static bool is_loop_wake(NSEvent *ev) +{ + return ev.type == NSEventTypeApplicationDefined && ev.subtype == LOOP_WAKE_SUBTYPE; +} + int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) { assert([NSThread isMainThread]); @@ -1143,6 +1198,30 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) } } + /* AppKit waits on Mach ports, not on loop descriptors. While waiting, + * a run loop source on a kqueue of the loop's descriptors posts a wake + * event, so kicks, posts, signals, and watches end the wait. */ + CFFileDescriptorRef wake_fd = NULL; + CFRunLoopSourceRef wake_source = NULL; + if (timeout_ms != 0 && d->loop) { + bool complete; + int kq = loop_kqueue(d->loop, &complete); + if (kq >= 0) { + wake_fd = CFFileDescriptorCreate(kCFAllocatorDefault, kq, true, loop_ready, NULL); + if (!wake_fd) close(kq); + } + if (wake_fd) { + CFFileDescriptorEnableCallBacks(wake_fd, kCFFileDescriptorReadCallBack); + wake_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, wake_fd, 0); + } + if (wake_source) + CFRunLoopAddSource(CFRunLoopGetCurrent(), wake_source, kCFRunLoopDefaultMode); + else + complete = false; + if (!complete && (timeout_ms < 0 || timeout_ms > LOOP_FALLBACK_MS)) + timeout_ms = LOOP_FALLBACK_MS; + } + NSDate *limitDate; if (timeout_ms == 0) limitDate = [NSDate distantPast]; @@ -1156,19 +1235,24 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) untilDate:limitDate inMode:NSDefaultRunLoopMode dequeue:YES]; - if (ev) { - count++; - [NSApp sendEvent:ev]; - [NSApp updateWindows]; - - while ((ev = [NSApp nextEventMatchingMask:NSEventMaskAny - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES])) { + if (wake_source) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), wake_source, kCFRunLoopDefaultMode); + CFRelease(wake_source); + } + if (wake_fd) { + CFFileDescriptorInvalidate(wake_fd); + CFRelease(wake_fd); + } + while (ev) { + if (!is_loop_wake(ev)) { count++; [NSApp sendEvent:ev]; [NSApp updateWindows]; } + ev = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; } } |
