From f3fdc21850d7a4d10966e9de6655ff53ede02314 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 4 Sep 2026 21:04:24 +0530 Subject: [PATCH] Fix #10436 --- docs/changelog.rst | 6 ++++++ glfw/backend_utils.c | 13 +++++++++++-- kitty/child-monitor.c | 4 ++-- kitty/loop-utils.c | 6 ++++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3cf8c362f..5df3f6623 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -308,6 +308,12 @@ Detailed list of changes files that no longer exist. The dropped files are now kept alive for ten minutes and removed when kitty quits (:iss:`10430`) +- macOS: Fix all signal handling being permanently disabled after the first + window that used the graphics protocol is closed. This caused exited window + shells to accumulate as zombie processes and also broke reloading the config + with ``SIGUSR1`` and quitting on ``SIGINT``/``SIGTERM``/``SIGHUP`` + (:iss:`10436`) + 0.48.2 [2026-07-30] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index f4467dd58..87d018091 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -329,6 +329,15 @@ finalizePollData(EventLoopData *eld) { #endif } +static int +display_fd_ready(EventLoopData *eld) { + // watches[0] is always the display fd. Errors count as ready: the display + // callback must complete the read rather than cancel it, so that a + // compositor disconnect is turned into a display error instead of a poll() + // that keeps returning immediately forever. + return eld->fds[0].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL); +} + int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func display_callback) { int read_ok = 0; @@ -344,7 +353,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ errno = 0; result = pollWithTimeout(eld->fds, eld->watches_count, timeout); int saved_errno = errno; - if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL); + if (display_callback) display_callback(result, display_fd_ready(eld), NULL); dispatchTimers(eld); if (result > 0) { dispatchEvents(eld); @@ -359,7 +368,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ errno = 0; result = poll(eld->fds, eld->watches_count, -1); int saved_errno = errno; - if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL); + if (display_callback) display_callback(result, display_fd_ready(eld), NULL); dispatchTimers(eld); if (result > 0) { dispatchEvents(eld); diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 949a30e9f..4d0a76b53 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -1826,8 +1826,8 @@ io_loop(void *data) { ret = poll(children_fds, self->count + EXTRA_FDS, -1); } if (ret > 0) { - if (children_fds[0].revents && POLLIN) drain_fd(children_fds[0].fd); // wakeup - if (children_fds[1].revents && POLLIN) { + if (children_fds[0].revents & POLLIN) drain_fd(children_fds[0].fd); // wakeup + if (children_fds[1].revents & POLLIN) { SignalSet ss = {0}; data_received = true; read_signals(children_fds[1].fd, handle_signal, &ss); diff --git a/kitty/loop-utils.c b/kitty/loop-utils.c index c82325ab8..ff2a08cd5 100644 --- a/kitty/loop-utils.c +++ b/kitty/loop-utils.c @@ -88,7 +88,9 @@ init_loop_data(LoopData *ld, ...) { static void remove_signal_handlers(LoopData *ld) { #ifndef HAS_SIGNAL_FD - signal_write_fd = -1; + // Only clear the global write fd if it is owned by this loop. Loops that + // handle no signals must not disable signal delivery for the loop that does. + if (ld->signal_fds[1] > -1 && signal_write_fd == ld->signal_fds[1]) signal_write_fd = -1; CLOSE(signal_fds, 0); CLOSE(signal_fds, 1); #endif @@ -97,7 +99,7 @@ remove_signal_handlers(LoopData *ld) { safe_close(ld->signal_read_fd, __FILE__, __LINE__); sigprocmask(SIG_UNBLOCK, &ld->signals, NULL); #endif - for (size_t i = 0; i < ld->num_handled_signals; i++) signal(ld->num_handled_signals, SIG_DFL); + for (size_t i = 0; i < ld->num_handled_signals; i++) signal(ld->handled_signals[i], SIG_DFL); } ld->signal_read_fd = -1; ld->num_handled_signals = 0;