diff --git a/CHANGELOG.md b/CHANGELOG.md index 35fdd93..2ad5cd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +- The Windows tray icon (`rivet::system::TrayIcon`) can update its tooltip + (`SetTooltip`) and re-register itself after an Explorer restart + (`Readd`, called from the host's `TaskbarCreated` broadcast handler) — + state-aware tray apps no longer re-allocate the icon to relabel it and no + longer lose it the first time the shell restarts. `TrayIcon` owns an icon + copy so recovery does not impose a new lifetime requirement on callers. + - Bound and retry Ubuntu runner package-manager operations so a stalled mirror cannot consume an entire desktop validation job without a useful failure. - Removed the misleading `rivet-type` runtime descriptors from the pre-1.0 diff --git a/platform/windows/system/system_services.cpp b/platform/windows/system/system_services.cpp index 98f573a..39d41c0 100644 --- a/platform/windows/system/system_services.cpp +++ b/platform/windows/system/system_services.cpp @@ -123,24 +123,60 @@ void Autostart::SetEnabled(std::wstring const& application_id, TrayIcon::TrayIcon(HWND window, UINT identifier, UINT callback_message, std::wstring const& tooltip, HICON icon) - : window_(window), identifier_(identifier) { + : window_(window), + identifier_(identifier), + callback_message_(callback_message), + tooltip_(tooltip) { + icon_ = ::CopyIcon(icon); + if (icon_ == nullptr) ThrowLastError("CopyIcon"); + try { + Add(); + } catch (...) { + ::DestroyIcon(icon_); + icon_ = nullptr; + throw; + } +} + +TrayIcon::~TrayIcon() { + NOTIFYICONDATAW data{}; + data.cbSize = sizeof(data); + data.hWnd = window_; + data.uID = identifier_; + ::Shell_NotifyIconW(NIM_DELETE, &data); + if (icon_ != nullptr) ::DestroyIcon(icon_); +} + +void TrayIcon::Add() { NOTIFYICONDATAW data{}; data.cbSize = sizeof(data); - data.hWnd = window; - data.uID = identifier; + data.hWnd = window_; + data.uID = identifier_; data.uFlags = NIF_MESSAGE | NIF_TIP | NIF_ICON; - data.uCallbackMessage = callback_message; - data.hIcon = icon; - wcsncpy_s(data.szTip, tooltip.c_str(), _TRUNCATE); + data.uCallbackMessage = callback_message_; + data.hIcon = icon_; + wcsncpy_s(data.szTip, tooltip_.c_str(), _TRUNCATE); if (!::Shell_NotifyIconW(NIM_ADD, &data)) ThrowLastError("Shell_NotifyIconW"); } -TrayIcon::~TrayIcon() { +void TrayIcon::SetTooltip(std::wstring const& tooltip) { + tooltip_ = tooltip; + NOTIFYICONDATAW data{}; + data.cbSize = sizeof(data); + data.hWnd = window_; + data.uID = identifier_; + data.uFlags = NIF_TIP; + wcsncpy_s(data.szTip, tooltip_.c_str(), _TRUNCATE); + if (!::Shell_NotifyIconW(NIM_MODIFY, &data)) ThrowLastError("Shell_NotifyIconW"); +} + +void TrayIcon::Readd() { NOTIFYICONDATAW data{}; data.cbSize = sizeof(data); data.hWnd = window_; data.uID = identifier_; ::Shell_NotifyIconW(NIM_DELETE, &data); + Add(); } void TrayIcon::Notify(std::wstring const& title, diff --git a/platform/windows/system/system_services.hpp b/platform/windows/system/system_services.hpp index 1c26010..29f6a18 100644 --- a/platform/windows/system/system_services.hpp +++ b/platform/windows/system/system_services.hpp @@ -58,7 +58,10 @@ class Autostart final { }; // Uses the first-party Shell_NotifyIcon surface. The owning HWND receives -// callback_message for tray activation and must stay on the WinUI UI thread. +// callback_message for tray activation and must stay on the WinUI UI thread; +// the application intercepts callback_message (SetWindowSubclass) and also +// forwards the TaskbarCreated broadcast to Readd() so the icon survives an +// Explorer restart. class TrayIcon final { public: TrayIcon(HWND window, UINT identifier, UINT callback_message, @@ -67,11 +70,24 @@ class TrayIcon final { TrayIcon(TrayIcon const&) = delete; TrayIcon& operator=(TrayIcon const&) = delete; + // The constructor copies `icon`; the caller retains ownership of its input + // handle and may release it immediately after construction. + // State labels change with the application (running/paused, today's + // totals); NIM_MODIFY with NIF_TIP, no re-add needed. + void SetTooltip(std::wstring const& tooltip); + // TaskbarCreated handler: NIM_DELETE + NIM_ADD with the stored payload. + // Cheap enough to call unconditionally from the broadcast path. + void Readd(); void Notify(std::wstring const& title, std::wstring const& body) const; private: + void Add(); + HWND window_{}; UINT identifier_{}; + UINT callback_message_{}; + std::wstring tooltip_; + HICON icon_{}; }; using CrashCallback = void (*)(EXCEPTION_POINTERS const*) noexcept;