Skip to content

Commit f0a46d7

Browse files
fix(macOS): System tray icon disappears when restarting Sunshine (#5504)
1 parent 0a1c365 commit f0a46d7

1 file changed

Lines changed: 70 additions & 4 deletions

File tree

src/platform/macos/misc.mm

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,35 @@ void streaming_will_stop() {
269269
// Nothing to do
270270
}
271271

272+
static pid_t g_restart_child_pid = 0; ///< PID of the restarted child process for signal forwarding.
273+
274+
/**
275+
* @brief Forward a signal to the restarted child process.
276+
*
277+
* This handler is installed in the parent (supervisor) process after forking
278+
* the new Sunshine instance. It ensures that signals like SIGINT (Ctrl+C)
279+
* are delivered to the child.
280+
*
281+
* @param sig The signal number to forward.
282+
*/
283+
static void forward_signal_to_child(int sig) {
284+
if (g_restart_child_pid > 0) {
285+
kill(g_restart_child_pid, sig);
286+
}
287+
}
288+
289+
/**
290+
* @brief Request a Sunshine process restart on exit.
291+
*
292+
* This is registered as an atexit handler by restart(). It forks a child
293+
* process with a fresh PID so that macOS WindowServer treats it as a new
294+
* application (required for the system tray icon to reinitialize).
295+
*
296+
* The parent process stays alive as a transparent supervisor: it forwards
297+
* signals (SIGINT, SIGTERM, SIGHUP) to the child and blocks in waitpid().
298+
* This keeps the shell tracking the original PID as its foreground job,
299+
* preserving Ctrl+C and terminal log output.
300+
*/
272301
void restart_on_exit() {
273302
char executable[2048];
274303
uint32_t size = sizeof(executable);
@@ -277,17 +306,54 @@ void restart_on_exit() {
277306
return;
278307
}
279308

280-
// ASIO doesn't use O_CLOEXEC, so we have to close all fds ourselves
309+
// ASIO doesn't use O_CLOEXEC, so we have to close all fds ourselves.
281310
int openmax = (int) sysconf(_SC_OPEN_MAX);
282311
for (int fd = STDERR_FILENO + 1; fd < openmax; fd++) {
283312
close(fd);
284313
}
285314

286-
// Re-exec ourselves with the same arguments
287-
if (execv(executable, lifetime::get_argv()) < 0) {
288-
BOOST_LOG(fatal) << "execv() failed: "sv << errno;
315+
// Fork a child process to get a fresh PID.
316+
// A new PID is required on macOS because WindowServer associates GUI state
317+
// (tray icons, activation policy) with the PID. After execv with the same PID,
318+
// WindowServer retains stale state and silently refuses to show new tray icons.
319+
pid_t child = fork();
320+
if (child < 0) {
321+
BOOST_LOG(fatal) << "fork() failed: "sv << errno;
289322
return;
290323
}
324+
325+
if (child == 0) {
326+
// Child: create a new process group so that the parent's signal
327+
// forwarding targets only this child, not the parent itself.
328+
setpgid(0, 0);
329+
330+
// Replace this child with the new Sunshine instance
331+
execv(executable, lifetime::get_argv());
332+
333+
// If execv fails, exit the child immediately without running atexit handlers
334+
_exit(1);
335+
}
336+
337+
// Parent: become a transparent supervisor.
338+
// The parent stays alive so the shell continues to track it as the
339+
// foreground job, keeping Ctrl+C and terminal output working.
340+
g_restart_child_pid = child;
341+
342+
struct sigaction sa;
343+
memset(&sa, 0, sizeof(sa));
344+
sa.sa_handler = forward_signal_to_child;
345+
sigemptyset(&sa.sa_mask);
346+
sa.sa_flags = 0;
347+
sigaction(SIGINT, &sa, nullptr);
348+
sigaction(SIGTERM, &sa, nullptr);
349+
sigaction(SIGHUP, &sa, nullptr);
350+
351+
int status;
352+
waitpid(child, &status, 0);
353+
354+
// Exit immediately without running additional atexit handlers or
355+
// static destructors. The child has already taken over.
356+
_exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
291357
}
292358

293359
void restart() {

0 commit comments

Comments
 (0)