summaryrefslogtreecommitdiff
path: root/src/platform/cocoa
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 16:55:45 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 16:55:45 +0300
commitefca84e8b5204c4d5ce28113474df0bf0b67dbf3 (patch)
tree8ab6ed74f1e29b3a9dd3f1b2cb031c81891a5902 /src/platform/cocoa
parent62717de20d2a0c8d3596a9223e56e734971e3ec5 (diff)
Wake and post to loops from any thread, and watch signals
wsap_term runs its own eventfds to wake the loop from its pty worker and accessibility threads, and its own signalfd or self-pipe per platform. yasl_loop_kick() is now documented as safe from any thread and from a signal handler. yasl_loop_post() queues a function to run on the owning thread during a later step. yasl_loop_watch_signals() installs a process-wide SA_RESTART handler per signal that records the signal and kicks the loop, so callbacks run on the loop thread on every platform without touching thread masks. Cocoa dispatch waited only for AppKit events, so kicks, posts, and fd watches could not end an indefinite wait, and yasl_post_event() did not wake yasl_run(). While waiting, Cocoa now mirrors the loop's descriptors in a kqueue whose run loop source posts a wake event. If kqueue rejects a descriptor, the wait is bounded to 10 ms. The Cocoa change is not built or run here; it needs a macOS check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/platform/cocoa')
-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];
}
}