summaryrefslogtreecommitdiff
path: root/src/platform/cocoa/window.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/cocoa/window.m')
-rw-r--r--src/platform/cocoa/window.m102
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];
}
}