From 3494b23e20b13d27b88fa08b97d07cb6d917f919 Mon Sep 17 00:00:00 2001 From: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:45:21 -0400 Subject: [PATCH] fix(tray): continue when system tray is unavailable (#5968) --- src/system_tray.cpp | 6 ++++++ src/system_tray.h | 3 +++ tests/unit/test_system_tray.cpp | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/system_tray.cpp b/src/system_tray.cpp index b553d4f48..8fcdf67ca 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -820,6 +820,12 @@ namespace system_tray { } void run_tray_until_exit(const std::shared_ptr> &shutdown_event) { + if (!tray_initialized_state().load()) { + BOOST_LOG(info) << "System tray is unavailable; continuing without it"sv; + shutdown_event->view(); + return; + } + while (process_tray_events() == 0); shutdown_event->raise(true); } diff --git a/src/system_tray.h b/src/system_tray.h index e0eaf6e64..6721e4300 100644 --- a/src/system_tray.h +++ b/src/system_tray.h @@ -95,6 +95,9 @@ namespace system_tray { /** * @brief Process tray events until exit and notify the application's workers. * + * If tray initialization failed, wait for the application's shutdown event instead + * of stopping its workers. An initialized tray still requests shutdown when its loop exits. + * * @param shutdown_event Event used to stop Sunshine's server threads. */ void run_tray_until_exit(const std::shared_ptr> &shutdown_event); diff --git a/tests/unit/test_system_tray.cpp b/tests/unit/test_system_tray.cpp index 3bb4bddd7..c3043fd05 100644 --- a/tests/unit/test_system_tray.cpp +++ b/tests/unit/test_system_tray.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -670,6 +671,40 @@ TEST_F(SystemTrayTest, ShowsMacBrokerLicenseAndAvailabilityWithOptionalStartupNo } #endif +/** + * @brief Verify the tray fallback waits for shutdown using an explicitly owned worker thread. + */ +TEST_F(SystemTrayTest, KeepsWorkersRunningWhenTrayIsUnavailable) { + ASSERT_FALSE(system_tray::tray_initialized_for_testing()); + auto shutdown_event = std::make_shared>(); + std::promise tray_loop_exited; + auto tray_loop = tray_loop_exited.get_future(); + std::jthread tray_worker([shutdown_event, &tray_loop_exited]() { + system_tray::run_tray_until_exit(shutdown_event); + tray_loop_exited.set_value(); + }); + + EXPECT_EQ(tray_loop.wait_for(100ms), std::future_status::timeout); + EXPECT_FALSE(shutdown_event->view(0ms)); + + shutdown_event->raise(true); + EXPECT_EQ(tray_loop.wait_for(5s), std::future_status::ready); + tray_worker.join(); + EXPECT_TRUE(shutdown_event->peek()); + EXPECT_FALSE(system_tray::tray_initialized_for_testing()); +} + +TEST_F(SystemTrayTest, HonorsPendingShutdownWhenTrayIsUnavailable) { + ASSERT_FALSE(system_tray::tray_initialized_for_testing()); + auto shutdown_event = std::make_shared>(); + shutdown_event->raise(true); + + system_tray::run_tray_until_exit(shutdown_event); + + EXPECT_TRUE(shutdown_event->peek()); + EXPECT_FALSE(system_tray::tray_initialized_for_testing()); +} + #ifndef _WIN32 TEST_F(SystemTrayTest, LifecycleMenuAndStateTransitions) { if (const int result = initialize_tray(); result != 0) {