Skip to content

Commit 3550af5

Browse files
niels9001Copilot
andcommitted
Add update-available badge to system tray icon
When an update is available (readyToDownload or readyToInstall), the tray icon switches to a badged variant with an orange dot. Works for both default mode (color icon.ico) and theme-adaptive mode (light/dark variants). Changes: - Add 3 badged icon variants (placeholder assets, to be replaced with designs) - Extend get_icon() to select badged variant based on update_available state - Add set_tray_icon_update_available() for runtime icon switching - Hook into UpdateUtils.cpp state transitions via dispatch_run_on_main_ui_thread - Check UpdateState at startup to show badge immediately if update pending - Add 'Update available' context menu item at top of tray menu when active Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 578554d commit 3550af5

File tree

11 files changed

+66
-2
lines changed

11 files changed

+66
-2
lines changed

src/runner/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,7 @@
197197
<value>Close</value>
198198
<comment>Close as a verb, as in Close the application</comment>
199199
</data>
200+
<data name="UPDATE_AVAILABLE_MENU_TEXT" xml:space="preserve">
201+
<value>Update available</value>
202+
</data>
200203
</root>

src/runner/UpdateUtils.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "ActionRunnerUtils.h"
66
#include "general_settings.h"
77
#include "trace.h"
8+
#include "tray_icon.h"
89
#include "UpdateUtils.h"
910

1011
#include <common/utils/gpo.h>
@@ -130,6 +131,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
130131
state.releasePageUrl = {};
131132
state.downloadedInstallerFilename = {};
132133
Logger::trace(L"Version is up to date");
134+
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(false); }, nullptr);
133135
return;
134136
}
135137
const auto new_version_info = std::get<new_version_download_info>(version_info);
@@ -179,6 +181,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
179181
state.state = UpdateState::readyToInstall;
180182
state.downloadedInstallerFilename = new_version_info.installer_filename;
181183
Trace::UpdateDownloadCompleted(true, new_version_info.version.toWstring());
184+
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(true); }, nullptr);
182185
if (show_notifications)
183186
{
184187
ShowNewVersionAvailable(new_version_info);
@@ -197,6 +200,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
197200
Logger::trace(L"New version is ready to download, showing notification");
198201
state.state = UpdateState::readyToDownload;
199202
state.downloadedInstallerFilename = {};
203+
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(true); }, nullptr);
200204
if (show_notifications)
201205
{
202206
ShowOpenSettingsForUpdate();

src/runner/resource.base.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515
#define APPICON 101
1616
#define ID_TRAY_MENU 102
17+
#define APPICON_UPDATE 103
1718

1819
#define ID_CLOSE_MENU_COMMAND 40001
1920
#define ID_SETTINGS_MENU_COMMAND 40002
2021
#define ID_ABOUT_MENU_COMMAND 40003
2122
#define ID_REPORT_BUG_COMMAND 40004
2223
#define ID_DOCUMENTATION_MENU_COMMAND 40005
2324
#define ID_QUICK_ACCESS_MENU_COMMAND 40006
25+
#define ID_UPDATE_MENU_COMMAND 40007

src/runner/svgs/PowerToysDark.ico

4.97 KB
Binary file not shown.
42 KB
Binary file not shown.

src/runner/svgs/PowerToysWhite.ico

5.06 KB
Binary file not shown.
42 KB
Binary file not shown.

src/runner/svgs/icon.ico

-9.87 KB
Binary file not shown.

src/runner/svgs/iconUpdate.ico

42 KB
Binary file not shown.

src/runner/tray_icon.cpp

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <common/Themes/theme_listener.h>
1818
#include <common/Themes/theme_helpers.h>
1919
#include "bug_report.h"
20+
#include <common/updating/updateState.h>
2021

2122
namespace
2223
{
@@ -45,6 +46,7 @@ namespace
4546

4647
static ThemeListener theme_listener;
4748
static bool theme_adaptive_enabled = false;
49+
static bool update_available = false;
4850
}
4951

5052
// Struct to fill with callback and the data. The window_proc is responsible for cleaning it.
@@ -124,6 +126,11 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
124126
open_quick_access_flyout_window();
125127
break;
126128
}
129+
case ID_UPDATE_MENU_COMMAND:
130+
{
131+
open_settings_window(std::nullopt);
132+
break;
133+
}
127134
}
128135
}
129136

@@ -260,6 +267,15 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
260267
{
261268
h_sub_menu = GetSubMenu(h_menu, 0);
262269
}
270+
271+
// Dynamically add/remove "Update available" menu item
272+
DeleteMenu(h_sub_menu, ID_UPDATE_MENU_COMMAND, MF_BYCOMMAND);
273+
if (update_available)
274+
{
275+
InsertMenuW(h_sub_menu, 0, MF_BYPOSITION | MF_STRING, ID_UPDATE_MENU_COMMAND, GET_RESOURCE_STRING(IDS_UPDATE_AVAILABLE_MENU_TEXT).c_str());
276+
InsertMenuW(h_sub_menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
277+
}
278+
263279
POINT mouse_pointer;
264280
GetCursorPos(&mouse_pointer);
265281
SetForegroundWindow(window); // Needed for the context menu to disappear.
@@ -318,7 +334,14 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
318334
static HICON get_icon(Theme theme)
319335
{
320336
std::wstring icon_path = get_module_folderpath();
321-
icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico";
337+
if (theme == Theme::Dark)
338+
{
339+
icon_path += update_available ? L"\\svgs\\PowerToysWhiteUpdate.ico" : L"\\svgs\\PowerToysWhite.ico";
340+
}
341+
else
342+
{
343+
icon_path += update_available ? L"\\svgs\\PowerToysDarkUpdate.ico" : L"\\svgs\\PowerToysDark.ico";
344+
}
322345
Logger::trace(L"get_icon: Loading icon from path: {}", icon_path);
323346

324347
HICON icon = static_cast<HICON>(LoadImage(NULL,
@@ -356,7 +379,14 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive)
356379
{
357380
theme_adaptive_enabled = theme_adaptive;
358381
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
359-
HICON const icon = theme_adaptive ? get_icon(ThemeHelpers::GetSystemTheme()) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
382+
383+
// Check if an update is available at startup
384+
auto state = UpdateState::read();
385+
update_available = (state.state == UpdateState::readyToDownload || state.state == UpdateState::readyToInstall);
386+
387+
HICON const icon = theme_adaptive
388+
? get_icon(ThemeHelpers::GetSystemTheme())
389+
: LoadIcon(h_instance, MAKEINTRESOURCE(update_available ? APPICON_UPDATE : APPICON));
360390
if (icon)
361391
{
362392
UINT id_tray_icon = 1;
@@ -425,6 +455,29 @@ void set_tray_icon_visible(bool shouldIconBeVisible)
425455
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
426456
}
427457

458+
void set_tray_icon_update_available(bool available)
459+
{
460+
if (update_available == available)
461+
{
462+
return;
463+
}
464+
465+
update_available = available;
466+
Logger::info(L"set_tray_icon_update_available: update_available={}", update_available);
467+
468+
if (theme_adaptive_enabled)
469+
{
470+
tray_icon_data.hIcon = get_icon(ThemeHelpers::GetSystemTheme());
471+
}
472+
else
473+
{
474+
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
475+
tray_icon_data.hIcon = LoadIcon(h_instance, MAKEINTRESOURCE(available ? APPICON_UPDATE : APPICON));
476+
}
477+
478+
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
479+
}
480+
428481
void set_tray_icon_theme_adaptive(bool theme_adaptive)
429482
{
430483
Logger::info(L"set_tray_icon_theme_adaptive: Called with theme_adaptive={}, current theme_adaptive_enabled={}",

0 commit comments

Comments
 (0)