fix(tray): optionally run tray in main event loop enabling support for macOS (#3818)
Co-authored-by: Lukas Senionis <22381748+FrogTheFrog@users.noreply.github.com>
This commit is contained in:
@@ -27,8 +27,12 @@
|
||||
#endif
|
||||
|
||||
// standard includes
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
// lib includes
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -47,38 +51,43 @@ using namespace std::literals;
|
||||
|
||||
// system_tray namespace
|
||||
namespace system_tray {
|
||||
static std::atomic<bool> tray_initialized = false;
|
||||
static std::atomic tray_initialized = false;
|
||||
|
||||
void tray_open_ui_cb(struct tray_menu *item) {
|
||||
// Threading variables for all platforms
|
||||
static std::thread tray_thread;
|
||||
static std::atomic tray_thread_running = false;
|
||||
static std::atomic tray_thread_should_exit = false;
|
||||
|
||||
void tray_open_ui_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
BOOST_LOG(info) << "Opening UI from system tray"sv;
|
||||
launch_ui();
|
||||
}
|
||||
|
||||
void tray_donate_github_cb(struct tray_menu *item) {
|
||||
void tray_donate_github_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
platf::open_url("https://github.com/sponsors/LizardByte");
|
||||
}
|
||||
|
||||
void tray_donate_patreon_cb(struct tray_menu *item) {
|
||||
void tray_donate_patreon_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
platf::open_url("https://www.patreon.com/LizardByte");
|
||||
}
|
||||
|
||||
void tray_donate_paypal_cb(struct tray_menu *item) {
|
||||
void tray_donate_paypal_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
platf::open_url("https://www.paypal.com/paypalme/ReenigneArcher");
|
||||
}
|
||||
|
||||
void tray_reset_display_device_config_cb(struct tray_menu *item) {
|
||||
void tray_reset_display_device_config_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
BOOST_LOG(info) << "Resetting display device config from system tray"sv;
|
||||
|
||||
std::ignore = display_device::reset_persistence();
|
||||
}
|
||||
|
||||
void tray_restart_cb(struct tray_menu *item) {
|
||||
void tray_restart_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
BOOST_LOG(info) << "Restarting from system tray"sv;
|
||||
|
||||
platf::restart();
|
||||
}
|
||||
|
||||
void tray_quit_cb(struct tray_menu *item) {
|
||||
void tray_quit_cb([[maybe_unused]] struct tray_menu *item) {
|
||||
BOOST_LOG(info) << "Quitting from system tray"sv;
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -123,7 +132,7 @@ namespace system_tray {
|
||||
.allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING},
|
||||
};
|
||||
|
||||
int system_tray() {
|
||||
int init_tray() {
|
||||
#ifdef _WIN32
|
||||
// If we're running as SYSTEM, Explorer.exe will not have permission to open our thread handle
|
||||
// to monitor for thread termination. If Explorer fails to open our thread, our tray icon
|
||||
@@ -189,39 +198,32 @@ namespace system_tray {
|
||||
if (tray_init(&tray) < 0) {
|
||||
BOOST_LOG(warning) << "Failed to create system tray"sv;
|
||||
return 1;
|
||||
} else {
|
||||
BOOST_LOG(info) << "System tray created"sv;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "System tray created"sv;
|
||||
tray_initialized = true;
|
||||
while (tray_loop(1) == 0) {
|
||||
BOOST_LOG(debug) << "System tray loop"sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_tray_events() {
|
||||
if (!tray_initialized) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Process one iteration of the tray loop with non-blocking mode (0)
|
||||
if (const int result = tray_loop(0); result != 0) {
|
||||
BOOST_LOG(warning) << "System tray loop failed"sv;
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void run_tray() {
|
||||
// create the system tray
|
||||
#if defined(__APPLE__) || defined(__MACH__)
|
||||
// macOS requires that UI elements be created on the main thread
|
||||
// creating tray using dispatch queue does not work, although the code doesn't actually throw any (visible) errors
|
||||
|
||||
// dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// system_tray();
|
||||
// });
|
||||
|
||||
BOOST_LOG(info) << "system_tray() is not yet implemented for this platform."sv;
|
||||
#else // Windows, Linux
|
||||
// create tray in separate thread
|
||||
std::thread tray_thread(system_tray);
|
||||
tray_thread.detach();
|
||||
#endif
|
||||
}
|
||||
|
||||
int end_tray() {
|
||||
tray_initialized = false;
|
||||
tray_exit();
|
||||
if (tray_initialized) {
|
||||
tray_initialized = false;
|
||||
tray_exit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -238,10 +240,10 @@ namespace system_tray {
|
||||
tray_update(&tray);
|
||||
tray.icon = TRAY_ICON_PLAYING;
|
||||
tray.notification_title = "Stream Started";
|
||||
char msg[256];
|
||||
snprintf(msg, std::size(msg), "Streaming started for %s", app_name.c_str());
|
||||
tray.notification_text = msg;
|
||||
tray.tooltip = msg;
|
||||
|
||||
static std::string msg = std::format("Streaming started for {}", app_name);
|
||||
tray.notification_text = msg.c_str();
|
||||
tray.tooltip = msg.c_str();
|
||||
tray.notification_icon = TRAY_ICON_PLAYING;
|
||||
tray_update(&tray);
|
||||
}
|
||||
@@ -257,12 +259,12 @@ namespace system_tray {
|
||||
tray.notification_icon = nullptr;
|
||||
tray.icon = TRAY_ICON_PAUSING;
|
||||
tray_update(&tray);
|
||||
char msg[256];
|
||||
snprintf(msg, std::size(msg), "Streaming paused for %s", app_name.c_str());
|
||||
|
||||
static std::string msg = std::format("Streaming paused for {}", app_name);
|
||||
tray.icon = TRAY_ICON_PAUSING;
|
||||
tray.notification_title = "Stream Paused";
|
||||
tray.notification_text = msg;
|
||||
tray.tooltip = msg;
|
||||
tray.notification_text = msg.c_str();
|
||||
tray.tooltip = msg.c_str();
|
||||
tray.notification_icon = TRAY_ICON_PAUSING;
|
||||
tray_update(&tray);
|
||||
}
|
||||
@@ -278,12 +280,12 @@ namespace system_tray {
|
||||
tray.notification_icon = nullptr;
|
||||
tray.icon = TRAY_ICON;
|
||||
tray_update(&tray);
|
||||
char msg[256];
|
||||
snprintf(msg, std::size(msg), "Application %s successfully stopped", app_name.c_str());
|
||||
|
||||
static std::string msg = std::format("Application {} successfully stopped", app_name);
|
||||
tray.icon = TRAY_ICON;
|
||||
tray.notification_icon = TRAY_ICON;
|
||||
tray.notification_title = "Application Stopped";
|
||||
tray.notification_text = msg;
|
||||
tray.notification_text = msg.c_str();
|
||||
tray.tooltip = PROJECT_NAME;
|
||||
tray_update(&tray);
|
||||
}
|
||||
@@ -310,5 +312,94 @@ namespace system_tray {
|
||||
tray_update(&tray);
|
||||
}
|
||||
|
||||
// Threading functions available on all platforms
|
||||
static void tray_thread_worker() {
|
||||
BOOST_LOG(info) << "System tray thread started"sv;
|
||||
|
||||
// Initialize the tray in this thread
|
||||
if (init_tray() != 0) {
|
||||
BOOST_LOG(error) << "Failed to initialize tray in thread"sv;
|
||||
tray_thread_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tray_thread_running = true;
|
||||
|
||||
// Main tray event loop
|
||||
while (!tray_thread_should_exit) {
|
||||
if (process_tray_events() != 0) {
|
||||
BOOST_LOG(warning) << "Tray event processing failed in thread"sv;
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep to avoid busy waiting
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
// Clean up the tray
|
||||
end_tray();
|
||||
tray_thread_running = false;
|
||||
BOOST_LOG(info) << "System tray thread ended"sv;
|
||||
}
|
||||
|
||||
int init_tray_threaded() {
|
||||
if (tray_thread_running) {
|
||||
BOOST_LOG(warning) << "Tray thread is already running"sv;
|
||||
return 1;
|
||||
}
|
||||
|
||||
tray_thread_should_exit = false;
|
||||
|
||||
try {
|
||||
tray_thread = std::thread(tray_thread_worker);
|
||||
|
||||
// Wait for the thread to start and initialize
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
while (!tray_thread_running && !tray_thread_should_exit) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// Timeout after 10 seconds
|
||||
if (std::chrono::steady_clock::now() - start_time > std::chrono::seconds(10)) {
|
||||
BOOST_LOG(error) << "Tray thread initialization timeout"sv;
|
||||
tray_thread_should_exit = true;
|
||||
if (tray_thread.joinable()) {
|
||||
tray_thread.join();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tray_thread_running) {
|
||||
BOOST_LOG(error) << "Tray thread failed to start"sv;
|
||||
if (tray_thread.joinable()) {
|
||||
tray_thread.join();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "System tray thread initialized successfully"sv;
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
BOOST_LOG(error) << "Failed to create tray thread: " << e.what();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int end_tray_threaded() {
|
||||
if (!tray_thread_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Stopping system tray thread"sv;
|
||||
tray_thread_should_exit = true;
|
||||
|
||||
if (tray_thread.joinable()) {
|
||||
tray_thread.join();
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "System tray thread stopped"sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace system_tray
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user