From 09404baa4bc55dc44716fbb48130a65c71d23fb3 Mon Sep 17 00:00:00 2001 From: Andy Grundman Date: Thu, 12 Feb 2026 15:07:50 -0500 Subject: [PATCH] feat(macos/tray): allow tray icon paths to be set at runtime from .app bundle --- src/system_tray.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 781437043b5..1799b2c668d 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -23,7 +23,9 @@ #define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing-16.png" #define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing-16.png" #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked-16.png" + #include #include + #include #endif // standard includes @@ -127,6 +129,58 @@ namespace system_tray { .allIconPaths = {TRAY_ICON, TRAY_ICON_LOCKED, TRAY_ICON_PLAYING, TRAY_ICON_PAUSING}, }; + const char *GetResourcePath(const char *relativePath) { + #ifdef __APPLE__ + if (!relativePath || !*relativePath) { + return nullptr; + } + + // Simple cache ensures our string pointers live forever + static std::unordered_map g_cache; + auto search = g_cache.find(relativePath); + if (search != g_cache.end()) { + return search->second.c_str(); + } + + // If we're running from an .app bundle, get the internal Resources dir + CFBundleRef bundle = CFBundleGetMainBundle(); + if (!bundle) { + return relativePath; + } + + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); + if (!resourcesURL) { + return relativePath; + } + + char resourcesPath[PATH_MAX]; + bool ok = CFURLGetFileSystemRepresentation( + resourcesURL, + true, + reinterpret_cast(resourcesPath), + sizeof(resourcesPath) + ); + CFRelease(resourcesURL); + if (!ok) { + return relativePath; + } + + std::string full; + if (relativePath && relativePath[0] == '/') { + full = relativePath; + } else { + full = std::string(resourcesPath) + "/" + relativePath; + } + + BOOST_LOG(debug) << "System Tray: using " << full << " for icon path"; + + auto [it, inserted] = g_cache.emplace(relativePath, std::move(full)); + return it->second.c_str(); + #else + return relativePath; + #endif + } + int init_tray() { #ifdef _WIN32 // If we're running as SYSTEM, Explorer.exe will not have permission to open our thread handle @@ -190,6 +244,16 @@ namespace system_tray { } #endif + #ifdef __APPLE__ + // if these icon paths are relative, resolve to internal .app Resources path + tray.allIconPaths[0] = GetResourcePath(TRAY_ICON); + tray.allIconPaths[1] = GetResourcePath(TRAY_ICON_LOCKED); + tray.allIconPaths[2] = GetResourcePath(TRAY_ICON_PLAYING); + tray.allIconPaths[3] = GetResourcePath(TRAY_ICON_PAUSING); + + tray.icon = tray.allIconPaths[0]; + #endif + if (tray_init(&tray) < 0) { BOOST_LOG(warning) << "Failed to create system tray"sv; return 1;