From 91e29f65a0b143472159aff79fb2d88d3794fd82 Mon Sep 17 00:00:00 2001 From: jrtxio Date: Mon, 12 Oct 2026 19:30:09 +0800 Subject: [PATCH 1/2] windows: TrayIcon learns SetTooltip and Readd (TaskbarCreated recovery) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State-aware tray apps (movebit: running/paused + today's totals) currently have to destroy and re-allocate the icon just to relabel the tooltip, and lose the icon entirely the first time Explorer restarts under them — the TaskbarCreated broadcast reaches the host window, but nothing re-registers the icon. SetTooltip issues the NIM_MODIFY/NIF_TIP update; Readd issues the NIM_DELETE + NIM_ADD pair with the stored payload so the host's TaskbarCreated handler is one call. The callback message is now stored so Readd re-registers the same activation contract. Hosts forward TaskbarCreated from their subclass procedure; documented on the class. --- CHANGELOG.md | 6 +++ platform/windows/system/system_services.cpp | 42 +++++++++++++++++---- platform/windows/system/system_services.hpp | 16 +++++++- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cd5441..cee0ad63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 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. + - Removed the misleading `rivet-type` runtime descriptors from the pre-1.0 public surface. Schema type identifiers are declaration grammar, while Keel is the canonical runtime-inspection model. diff --git a/platform/windows/system/system_services.cpp b/platform/windows/system/system_services.cpp index 98f573a4..cd634775 100644 --- a/platform/windows/system/system_services.cpp +++ b/platform/windows/system/system_services.cpp @@ -123,24 +123,52 @@ 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_(icon) { + Add(); +} + +TrayIcon::~TrayIcon() { + NOTIFYICONDATAW data{}; + data.cbSize = sizeof(data); + data.hWnd = window_; + data.uID = identifier_; + ::Shell_NotifyIconW(NIM_DELETE, &data); +} + +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 1c260100..a3766089 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,22 @@ class TrayIcon final { TrayIcon(TrayIcon const&) = delete; TrayIcon& operator=(TrayIcon const&) = delete; + // 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; From 7ecb2c88ce4a25931d72597bc8d821f83e566a6f Mon Sep 17 00:00:00 2001 From: "ren.ji" Date: Mon, 12 Oct 2026 19:34:20 +0800 Subject: [PATCH 2/2] fix(windows): own the tray icon used for recovery --- CHANGELOG.md | 3 ++- platform/windows/system/system_services.cpp | 14 +++++++++++--- platform/windows/system/system_services.hpp | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee0ad63..199ba67d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ (`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. + 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. - Removed the misleading `rivet-type` runtime descriptors from the pre-1.0 public surface. Schema type identifiers are declaration grammar, while Keel diff --git a/platform/windows/system/system_services.cpp b/platform/windows/system/system_services.cpp index cd634775..39d41c02 100644 --- a/platform/windows/system/system_services.cpp +++ b/platform/windows/system/system_services.cpp @@ -126,9 +126,16 @@ TrayIcon::TrayIcon(HWND window, UINT identifier, UINT callback_message, : window_(window), identifier_(identifier), callback_message_(callback_message), - tooltip_(tooltip), - icon_(icon) { - Add(); + tooltip_(tooltip) { + icon_ = ::CopyIcon(icon); + if (icon_ == nullptr) ThrowLastError("CopyIcon"); + try { + Add(); + } catch (...) { + ::DestroyIcon(icon_); + icon_ = nullptr; + throw; + } } TrayIcon::~TrayIcon() { @@ -137,6 +144,7 @@ TrayIcon::~TrayIcon() { data.hWnd = window_; data.uID = identifier_; ::Shell_NotifyIconW(NIM_DELETE, &data); + if (icon_ != nullptr) ::DestroyIcon(icon_); } void TrayIcon::Add() { diff --git a/platform/windows/system/system_services.hpp b/platform/windows/system/system_services.hpp index a3766089..29f6a185 100644 --- a/platform/windows/system/system_services.hpp +++ b/platform/windows/system/system_services.hpp @@ -60,7 +60,7 @@ 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; // the application intercepts callback_message (SetWindowSubclass) and also -// forwards the TaskbarCreated broadcast to readd() so the icon survives an +// forwards the TaskbarCreated broadcast to Readd() so the icon survives an // Explorer restart. class TrayIcon final { public: @@ -70,6 +70,8 @@ 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);