2020-04-21 10:30:12 +03:00
# include "pch.h"
2020-10-22 19:02:59 +03:00
# include "Generated Files/resource.h"
2021-06-14 12:55:59 +03:00
# include "ActionRunnerUtils.h"
2021-05-21 13:32:34 +03:00
# include "general_settings.h"
2026-01-08 15:57:49 +08:00
# include "trace.h"
Add update-available badge to system tray icon (#47030)
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).
Closes: #19222
Closes: #25497
## Changes
### Tray icon update badge
- Add 3 badged icon variants
- 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
### Fix: update icons not deployed (bug fix)
- Add APPICON_UPDATE icon resource to runner.base.rc (iconUpdate.ico was
missing)
- Add CopyFileToFolders entries for iconUpdate.ico,
PowerToysDarkUpdate.ico, and PowerToysWhiteUpdate.ico in runner.vcxproj
- Add all update icon files to installer Core.wxs so they ship in
releases
### UX improvements
- 'Update available' tray menu item now navigates to General page
(Overview) instead of opening Settings to Dashboard
- Update InfoBar severity changed from Success/Informational to Warning
across GeneralPage, LaunchPage, and CheckUpdateControl
- Dashboard update badge gradient and icon refreshed (orange theme,
exclamation glyph)
- AccentButtonStyle applied to 'Install Now' button
- Fixed casing: 'Update Available' to 'Update available'
- Added UpdateAvailableInfoBar.Title resource string
- Add orange update dot to the General navview item
### Screenshots
Before:
<img width="146" height="78" alt="image"
src="https://github.com/user-attachments/assets/c80b8b5f-da94-4cba-92c9-3fcca685653c"
/>
After:
<img width="184" height="104" alt="image"
src="https://github.com/user-attachments/assets/13fc6b34-6e2a-4060-a2f7-f0b6b0d15363"
/>
<img width="150" height="84" alt="image"
src="https://github.com/user-attachments/assets/2673239c-8ce3-437b-947a-1d66803a87ec"
/>
<img width="150" height="100" alt="image"
src="https://github.com/user-attachments/assets/c321deda-770d-47ff-9600-c395f466d444"
/>
<img width="189" height="104" alt="image"
src="https://github.com/user-attachments/assets/2c56d1b7-6615-4d85-80b9-a1cee6413b75"
/>
<img width="473" height="218" alt="image"
src="https://github.com/user-attachments/assets/b0fb59ed-f8bd-40a0-aefd-816a71fc231f"
/>
<img width="1048" height="288" alt="image"
src="https://github.com/user-attachments/assets/29d34e01-f6a9-46c3-a56e-2c50a07718a1"
/>
<img width="206" height="155" alt="image"
src="https://github.com/user-attachments/assets/80e9f77e-aae5-429a-b6be-f0e9f296e929"
/>
<img width="434" height="163" alt="image"
src="https://github.com/user-attachments/assets/7c9d6cd5-fdaa-4b70-a2c0-cff87f5fcf1c"
/>
<img width="379" height="270" alt="image"
src="https://github.com/user-attachments/assets/03e0f60d-a901-45e7-a03a-18be28ec87ed"
/>
## How to test
Since local dev builds use version `0.0.1` which blocks update checks,
you need to temporarily fake an older version:
1. In `src/Version.props`, change `<Version>0.0.1</Version>` to
`<Version>0.87.0</Version>`
2. Optionally, in `src/runner/UpdateUtils.cpp`, change both interval
constants to `1` (minute) for faster testing:
```cpp
constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 1;
constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 1;
```
3. Build and run the runner
4. Within ~1 minute (with the interval change) or after clicking 'Check
for updates' in Settings > General, the runner will query GitHub and
find a newer version
### Verify
- [ ] Tray icon changes to the update variant (badged with orange dot)
- [ ] Right-clicking the tray icon shows 'Update available' at the top
of the context menu
- [ ] Clicking 'Update available' opens Settings directly to the General
page
- [ ] Settings General page shows the update InfoBar with Warning
severity
- [ ] Dashboard shows the update badge with orange gradient and
exclamation icon
- [ ] Quick Access flyout shows update InfoBar with Warning severity
**Remember to revert Version.props and UpdateUtils.cpp before
committing!**
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 10:48:03 +02:00
# include "tray_icon.h"
2021-06-14 12:55:59 +03:00
# include "UpdateUtils.h"
2020-04-21 10:30:12 +03:00
2023-02-24 15:35:33 +01:00
# include <common/utils/gpo.h>
2021-05-21 13:32:34 +03:00
# include <common/logger/logger.h>
2021-06-14 12:55:59 +03:00
# include <common/notifications/notifications.h>
2020-11-20 11:34:34 +03:00
# include <common/updating/installer.h>
2020-04-21 10:30:12 +03:00
# include <common/updating/updating.h>
2021-05-21 13:32:34 +03:00
# include <common/updating/updateState.h>
2021-06-14 12:55:59 +03:00
# include <common/utils/HttpClient.h>
# include <common/utils/process_path.h>
2020-12-15 15:16:09 +03:00
# include <common/utils/resources.h>
# include <common/utils/timeutil.h>
2021-06-14 12:55:59 +03:00
# include <common/version/version.h>
2020-10-22 19:02:59 +03:00
2021-01-12 18:34:02 +03:00
namespace
{
constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 60 * 24 ;
constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 60 * 2 ;
2023-02-24 15:35:33 +01:00
// How many minor versions to suspend the toast notification (example: installed=0.60.0, suspend=2, next notification=0.63.*)
// Attention: When changing this value please update the ADML file to.
const int UPDATE_NOTIFICATION_TOAST_SUSPEND_MINOR_VERSION_COUNT = 2 ;
2021-01-12 18:34:02 +03:00
}
2021-06-14 12:55:59 +03:00
using namespace notifications ;
using namespace updating ;
2021-01-12 18:34:02 +03:00
2021-06-14 12:55:59 +03:00
std : : wstring CurrentVersionToNextVersion ( const new_version_download_info & info )
2020-04-21 10:30:12 +03:00
{
2021-06-14 12:55:59 +03:00
auto result = VersionHelper { VERSION_MAJOR , VERSION_MINOR , VERSION_REVISION } . toWstring ( ) ;
2023-10-18 11:42:18 -05:00
result + = L " \u2192 " ; // Right arrow
2021-06-14 12:55:59 +03:00
result + = info . version . toWstring ( ) ;
return result ;
}
2020-04-21 10:30:12 +03:00
2021-06-14 12:55:59 +03:00
void ShowNewVersionAvailable ( const new_version_download_info & info )
{
remove_toasts_by_tag ( UPDATING_PROCESS_TOAST_TAG ) ;
toast_params toast_params { UPDATING_PROCESS_TOAST_TAG , false } ;
std : : wstring contents = GET_RESOURCE_STRING ( IDS_GITHUB_NEW_VERSION_AVAILABLE ) ;
contents + = L ' \n ' ;
contents + = CurrentVersionToNextVersion ( info ) ;
show_toast_with_activations ( std : : move ( contents ) ,
GET_RESOURCE_STRING ( IDS_TOAST_TITLE ) ,
{ } ,
{ link_button { GET_RESOURCE_STRING ( IDS_GITHUB_NEW_VERSION_UPDATE_NOW ) ,
L " powertoys://update_now/ " } ,
link_button { GET_RESOURCE_STRING ( IDS_GITHUB_NEW_VERSION_MORE_INFO ) ,
2024-10-28 15:10:18 +03:00
L " powertoys://open_overview/ " } } ,
std : : move ( toast_params ) ,
L " powertoys://open_overview/ " ) ;
2021-06-14 12:55:59 +03:00
}
2020-04-21 10:30:12 +03:00
2021-06-14 12:55:59 +03:00
void ShowOpenSettingsForUpdate ( )
{
remove_toasts_by_tag ( UPDATING_PROCESS_TOAST_TAG ) ;
toast_params toast_params { UPDATING_PROCESS_TOAST_TAG , false } ;
std : : vector < action_t > actions = {
link_button { GET_RESOURCE_STRING ( IDS_GITHUB_NEW_VERSION_MORE_INFO ) ,
2024-10-28 15:10:18 +03:00
L " powertoys://open_overview/ " } ,
2021-06-14 12:55:59 +03:00
} ;
show_toast_with_activations ( GET_RESOURCE_STRING ( IDS_GITHUB_NEW_VERSION_AVAILABLE ) ,
GET_RESOURCE_STRING ( IDS_TOAST_TITLE ) ,
{ } ,
std : : move ( actions ) ,
2024-10-28 15:10:18 +03:00
std : : move ( toast_params ) ,
L " powertoys://open_overview/ " ) ;
2021-06-14 12:55:59 +03:00
}
2020-04-21 10:30:12 +03:00
2021-06-14 12:55:59 +03:00
SHELLEXECUTEINFOW LaunchPowerToysUpdate ( const wchar_t * cmdline )
{
std : : wstring powertoysUpdaterPath ;
powertoysUpdaterPath = get_module_folderpath ( ) ;
powertoysUpdaterPath + = L " \\ PowerToys.Update.exe " ;
SHELLEXECUTEINFOW sei { sizeof ( sei ) } ;
sei . fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS } ;
sei . lpFile = powertoysUpdaterPath . c_str ( ) ;
sei . nShow = SW_SHOWNORMAL ;
sei . lpParameters = cmdline ;
ShellExecuteExW ( & sei ) ;
return sei ;
2020-04-21 10:30:12 +03:00
}
2021-06-14 12:55:59 +03:00
bool IsMeteredConnection ( )
2021-05-21 13:32:34 +03:00
{
using namespace winrt : : Windows : : Networking : : Connectivity ;
ConnectionProfile internetConnectionProfile = NetworkInformation : : GetInternetConnectionProfile ( ) ;
2023-02-13 17:29:26 +01:00
if ( ! internetConnectionProfile )
{
return false ;
}
if ( internetConnectionProfile . IsWwanConnectionProfile ( ) )
{
return true ;
}
ConnectionCost connectionCost = internetConnectionProfile . GetConnectionCost ( ) ;
if ( connectionCost . Roaming ( )
| | connectionCost . OverDataLimit ( )
| | connectionCost . NetworkCostType ( ) = = NetworkCostType : : Fixed
| | connectionCost . NetworkCostType ( ) = = NetworkCostType : : Variable )
{
return true ;
}
return false ;
2021-05-21 13:32:34 +03:00
}
2021-06-14 12:55:59 +03:00
void ProcessNewVersionInfo ( const github_version_info & version_info ,
UpdateState & state ,
const bool download_update ,
2023-02-24 15:35:33 +01:00
bool show_notifications )
2021-05-21 13:32:34 +03:00
{
state . githubUpdateLastCheckedDate . emplace ( timeutil : : now ( ) ) ;
if ( std : : holds_alternative < version_up_to_date > ( version_info ) )
{
state . state = UpdateState : : upToDate ;
state . releasePageUrl = { } ;
state . downloadedInstallerFilename = { } ;
Logger : : trace ( L " Version is up to date " ) ;
Add update-available badge to system tray icon (#47030)
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).
Closes: #19222
Closes: #25497
## Changes
### Tray icon update badge
- Add 3 badged icon variants
- 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
### Fix: update icons not deployed (bug fix)
- Add APPICON_UPDATE icon resource to runner.base.rc (iconUpdate.ico was
missing)
- Add CopyFileToFolders entries for iconUpdate.ico,
PowerToysDarkUpdate.ico, and PowerToysWhiteUpdate.ico in runner.vcxproj
- Add all update icon files to installer Core.wxs so they ship in
releases
### UX improvements
- 'Update available' tray menu item now navigates to General page
(Overview) instead of opening Settings to Dashboard
- Update InfoBar severity changed from Success/Informational to Warning
across GeneralPage, LaunchPage, and CheckUpdateControl
- Dashboard update badge gradient and icon refreshed (orange theme,
exclamation glyph)
- AccentButtonStyle applied to 'Install Now' button
- Fixed casing: 'Update Available' to 'Update available'
- Added UpdateAvailableInfoBar.Title resource string
- Add orange update dot to the General navview item
### Screenshots
Before:
<img width="146" height="78" alt="image"
src="https://github.com/user-attachments/assets/c80b8b5f-da94-4cba-92c9-3fcca685653c"
/>
After:
<img width="184" height="104" alt="image"
src="https://github.com/user-attachments/assets/13fc6b34-6e2a-4060-a2f7-f0b6b0d15363"
/>
<img width="150" height="84" alt="image"
src="https://github.com/user-attachments/assets/2673239c-8ce3-437b-947a-1d66803a87ec"
/>
<img width="150" height="100" alt="image"
src="https://github.com/user-attachments/assets/c321deda-770d-47ff-9600-c395f466d444"
/>
<img width="189" height="104" alt="image"
src="https://github.com/user-attachments/assets/2c56d1b7-6615-4d85-80b9-a1cee6413b75"
/>
<img width="473" height="218" alt="image"
src="https://github.com/user-attachments/assets/b0fb59ed-f8bd-40a0-aefd-816a71fc231f"
/>
<img width="1048" height="288" alt="image"
src="https://github.com/user-attachments/assets/29d34e01-f6a9-46c3-a56e-2c50a07718a1"
/>
<img width="206" height="155" alt="image"
src="https://github.com/user-attachments/assets/80e9f77e-aae5-429a-b6be-f0e9f296e929"
/>
<img width="434" height="163" alt="image"
src="https://github.com/user-attachments/assets/7c9d6cd5-fdaa-4b70-a2c0-cff87f5fcf1c"
/>
<img width="379" height="270" alt="image"
src="https://github.com/user-attachments/assets/03e0f60d-a901-45e7-a03a-18be28ec87ed"
/>
## How to test
Since local dev builds use version `0.0.1` which blocks update checks,
you need to temporarily fake an older version:
1. In `src/Version.props`, change `<Version>0.0.1</Version>` to
`<Version>0.87.0</Version>`
2. Optionally, in `src/runner/UpdateUtils.cpp`, change both interval
constants to `1` (minute) for faster testing:
```cpp
constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 1;
constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 1;
```
3. Build and run the runner
4. Within ~1 minute (with the interval change) or after clicking 'Check
for updates' in Settings > General, the runner will query GitHub and
find a newer version
### Verify
- [ ] Tray icon changes to the update variant (badged with orange dot)
- [ ] Right-clicking the tray icon shows 'Update available' at the top
of the context menu
- [ ] Clicking 'Update available' opens Settings directly to the General
page
- [ ] Settings General page shows the update InfoBar with Warning
severity
- [ ] Dashboard shows the update badge with orange gradient and
exclamation icon
- [ ] Quick Access flyout shows update InfoBar with Warning severity
**Remember to revert Version.props and UpdateUtils.cpp before
committing!**
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 10:48:03 +02:00
dispatch_run_on_main_ui_thread ( [ ] ( PVOID ) { set_tray_icon_update_available ( false ) ; } , nullptr ) ;
2021-05-21 13:32:34 +03:00
return ;
}
const auto new_version_info = std : : get < new_version_download_info > ( version_info ) ;
state . releasePageUrl = new_version_info . release_page_uri . ToString ( ) . c_str ( ) ;
Logger : : trace ( L " Discovered new version {} " , new_version_info . version . toWstring ( ) ) ;
const bool already_downloaded = state . state = = UpdateState : : readyToInstall & & state . downloadedInstallerFilename = = new_version_info . installer_filename ;
if ( already_downloaded )
{
Logger : : trace ( L " New version is already downloaded " ) ;
return ;
}
2024-01-19 16:18:05 +01:00
// Check toast notification GPOs and settings. (We check only if notifications are allowed. This is the case if we are triggered by the periodic check.)
// Disable notification GPO or setting
bool disable_notification_setting = get_general_settings ( ) . showNewUpdatesToastNotification = = false ;
if ( show_notifications & & ( disable_notification_setting | | powertoys_gpo : : getDisableNewUpdateToastValue ( ) = = powertoys_gpo : : gpo_rule_configured_enabled ) )
{
Logger : : info ( L " There is a new update available or ready to install. But the toast notification is disabled by setting or GPO. " ) ;
show_notifications = false ;
}
// Suspend notification GPO
else if ( show_notifications & & powertoys_gpo : : getSuspendNewUpdateToastValue ( ) = = powertoys_gpo : : gpo_rule_configured_enabled )
2023-02-24 15:35:33 +01:00
{
Logger : : info ( L " GPO to suspend new update toast notification is enabled. " ) ;
if ( new_version_info . version . major < = VERSION_MAJOR & & new_version_info . version . minor - VERSION_MINOR < = UPDATE_NOTIFICATION_TOAST_SUSPEND_MINOR_VERSION_COUNT )
{
Logger : : info ( L " The difference between the installed version and the newer version is within the allowed period. The toast notification is not shown. " ) ;
show_notifications = false ;
}
else
{
Logger : : info ( L " The installed version is older than allowed for suspending the toast notification. The toast notification is shown. " ) ;
}
}
2021-05-21 13:32:34 +03:00
if ( download_update )
{
Logger : : trace ( L " Downloading installer for a new version " ) ;
2023-08-14 10:55:12 +02:00
// Cleanup old updates before downloading the latest
updating : : cleanup_updates ( ) ;
2026-02-11 22:31:46 +08:00
auto downloaded_installer = std : : move ( download_new_version_async ( new_version_info ) ) . get ( ) ;
if ( downloaded_installer )
2021-05-21 13:32:34 +03:00
{
state . state = UpdateState : : readyToInstall ;
state . downloadedInstallerFilename = new_version_info . installer_filename ;
2026-01-08 15:57:49 +08:00
Trace : : UpdateDownloadCompleted ( true , new_version_info . version . toWstring ( ) ) ;
Add update-available badge to system tray icon (#47030)
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).
Closes: #19222
Closes: #25497
## Changes
### Tray icon update badge
- Add 3 badged icon variants
- 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
### Fix: update icons not deployed (bug fix)
- Add APPICON_UPDATE icon resource to runner.base.rc (iconUpdate.ico was
missing)
- Add CopyFileToFolders entries for iconUpdate.ico,
PowerToysDarkUpdate.ico, and PowerToysWhiteUpdate.ico in runner.vcxproj
- Add all update icon files to installer Core.wxs so they ship in
releases
### UX improvements
- 'Update available' tray menu item now navigates to General page
(Overview) instead of opening Settings to Dashboard
- Update InfoBar severity changed from Success/Informational to Warning
across GeneralPage, LaunchPage, and CheckUpdateControl
- Dashboard update badge gradient and icon refreshed (orange theme,
exclamation glyph)
- AccentButtonStyle applied to 'Install Now' button
- Fixed casing: 'Update Available' to 'Update available'
- Added UpdateAvailableInfoBar.Title resource string
- Add orange update dot to the General navview item
### Screenshots
Before:
<img width="146" height="78" alt="image"
src="https://github.com/user-attachments/assets/c80b8b5f-da94-4cba-92c9-3fcca685653c"
/>
After:
<img width="184" height="104" alt="image"
src="https://github.com/user-attachments/assets/13fc6b34-6e2a-4060-a2f7-f0b6b0d15363"
/>
<img width="150" height="84" alt="image"
src="https://github.com/user-attachments/assets/2673239c-8ce3-437b-947a-1d66803a87ec"
/>
<img width="150" height="100" alt="image"
src="https://github.com/user-attachments/assets/c321deda-770d-47ff-9600-c395f466d444"
/>
<img width="189" height="104" alt="image"
src="https://github.com/user-attachments/assets/2c56d1b7-6615-4d85-80b9-a1cee6413b75"
/>
<img width="473" height="218" alt="image"
src="https://github.com/user-attachments/assets/b0fb59ed-f8bd-40a0-aefd-816a71fc231f"
/>
<img width="1048" height="288" alt="image"
src="https://github.com/user-attachments/assets/29d34e01-f6a9-46c3-a56e-2c50a07718a1"
/>
<img width="206" height="155" alt="image"
src="https://github.com/user-attachments/assets/80e9f77e-aae5-429a-b6be-f0e9f296e929"
/>
<img width="434" height="163" alt="image"
src="https://github.com/user-attachments/assets/7c9d6cd5-fdaa-4b70-a2c0-cff87f5fcf1c"
/>
<img width="379" height="270" alt="image"
src="https://github.com/user-attachments/assets/03e0f60d-a901-45e7-a03a-18be28ec87ed"
/>
## How to test
Since local dev builds use version `0.0.1` which blocks update checks,
you need to temporarily fake an older version:
1. In `src/Version.props`, change `<Version>0.0.1</Version>` to
`<Version>0.87.0</Version>`
2. Optionally, in `src/runner/UpdateUtils.cpp`, change both interval
constants to `1` (minute) for faster testing:
```cpp
constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 1;
constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 1;
```
3. Build and run the runner
4. Within ~1 minute (with the interval change) or after clicking 'Check
for updates' in Settings > General, the runner will query GitHub and
find a newer version
### Verify
- [ ] Tray icon changes to the update variant (badged with orange dot)
- [ ] Right-clicking the tray icon shows 'Update available' at the top
of the context menu
- [ ] Clicking 'Update available' opens Settings directly to the General
page
- [ ] Settings General page shows the update InfoBar with Warning
severity
- [ ] Dashboard shows the update badge with orange gradient and
exclamation icon
- [ ] Quick Access flyout shows update InfoBar with Warning severity
**Remember to revert Version.props and UpdateUtils.cpp before
committing!**
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 10:48:03 +02:00
dispatch_run_on_main_ui_thread ( [ ] ( PVOID ) { set_tray_icon_update_available ( true ) ; } , nullptr ) ;
2021-05-21 13:32:34 +03:00
if ( show_notifications )
{
2021-06-14 12:55:59 +03:00
ShowNewVersionAvailable ( new_version_info ) ;
2021-05-21 13:32:34 +03:00
}
}
else
{
state . state = UpdateState : : errorDownloading ;
state . downloadedInstallerFilename = { } ;
2026-01-08 15:57:49 +08:00
Trace : : UpdateDownloadCompleted ( false , new_version_info . version . toWstring ( ) ) ;
2021-05-21 13:32:34 +03:00
Logger : : error ( " Couldn't download new installer " ) ;
}
}
else
{
Logger : : trace ( L " New version is ready to download, showing notification " ) ;
state . state = UpdateState : : readyToDownload ;
state . downloadedInstallerFilename = { } ;
Add update-available badge to system tray icon (#47030)
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).
Closes: #19222
Closes: #25497
## Changes
### Tray icon update badge
- Add 3 badged icon variants
- 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
### Fix: update icons not deployed (bug fix)
- Add APPICON_UPDATE icon resource to runner.base.rc (iconUpdate.ico was
missing)
- Add CopyFileToFolders entries for iconUpdate.ico,
PowerToysDarkUpdate.ico, and PowerToysWhiteUpdate.ico in runner.vcxproj
- Add all update icon files to installer Core.wxs so they ship in
releases
### UX improvements
- 'Update available' tray menu item now navigates to General page
(Overview) instead of opening Settings to Dashboard
- Update InfoBar severity changed from Success/Informational to Warning
across GeneralPage, LaunchPage, and CheckUpdateControl
- Dashboard update badge gradient and icon refreshed (orange theme,
exclamation glyph)
- AccentButtonStyle applied to 'Install Now' button
- Fixed casing: 'Update Available' to 'Update available'
- Added UpdateAvailableInfoBar.Title resource string
- Add orange update dot to the General navview item
### Screenshots
Before:
<img width="146" height="78" alt="image"
src="https://github.com/user-attachments/assets/c80b8b5f-da94-4cba-92c9-3fcca685653c"
/>
After:
<img width="184" height="104" alt="image"
src="https://github.com/user-attachments/assets/13fc6b34-6e2a-4060-a2f7-f0b6b0d15363"
/>
<img width="150" height="84" alt="image"
src="https://github.com/user-attachments/assets/2673239c-8ce3-437b-947a-1d66803a87ec"
/>
<img width="150" height="100" alt="image"
src="https://github.com/user-attachments/assets/c321deda-770d-47ff-9600-c395f466d444"
/>
<img width="189" height="104" alt="image"
src="https://github.com/user-attachments/assets/2c56d1b7-6615-4d85-80b9-a1cee6413b75"
/>
<img width="473" height="218" alt="image"
src="https://github.com/user-attachments/assets/b0fb59ed-f8bd-40a0-aefd-816a71fc231f"
/>
<img width="1048" height="288" alt="image"
src="https://github.com/user-attachments/assets/29d34e01-f6a9-46c3-a56e-2c50a07718a1"
/>
<img width="206" height="155" alt="image"
src="https://github.com/user-attachments/assets/80e9f77e-aae5-429a-b6be-f0e9f296e929"
/>
<img width="434" height="163" alt="image"
src="https://github.com/user-attachments/assets/7c9d6cd5-fdaa-4b70-a2c0-cff87f5fcf1c"
/>
<img width="379" height="270" alt="image"
src="https://github.com/user-attachments/assets/03e0f60d-a901-45e7-a03a-18be28ec87ed"
/>
## How to test
Since local dev builds use version `0.0.1` which blocks update checks,
you need to temporarily fake an older version:
1. In `src/Version.props`, change `<Version>0.0.1</Version>` to
`<Version>0.87.0</Version>`
2. Optionally, in `src/runner/UpdateUtils.cpp`, change both interval
constants to `1` (minute) for faster testing:
```cpp
constexpr int64_t UPDATE_CHECK_INTERVAL_MINUTES = 1;
constexpr int64_t UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES = 1;
```
3. Build and run the runner
4. Within ~1 minute (with the interval change) or after clicking 'Check
for updates' in Settings > General, the runner will query GitHub and
find a newer version
### Verify
- [ ] Tray icon changes to the update variant (badged with orange dot)
- [ ] Right-clicking the tray icon shows 'Update available' at the top
of the context menu
- [ ] Clicking 'Update available' opens Settings directly to the General
page
- [ ] Settings General page shows the update InfoBar with Warning
severity
- [ ] Dashboard shows the update badge with orange gradient and
exclamation icon
- [ ] Quick Access flyout shows update InfoBar with Warning severity
**Remember to revert Version.props and UpdateUtils.cpp before
committing!**
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 10:48:03 +02:00
dispatch_run_on_main_ui_thread ( [ ] ( PVOID ) { set_tray_icon_update_available ( true ) ; } , nullptr ) ;
2021-05-21 13:32:34 +03:00
if ( show_notifications )
{
2021-06-14 12:55:59 +03:00
ShowOpenSettingsForUpdate ( ) ;
2021-05-21 13:32:34 +03:00
}
}
}
2021-06-14 12:55:59 +03:00
void PeriodicUpdateWorker ( )
2020-04-21 10:30:12 +03:00
{
for ( ; ; )
{
auto state = UpdateState : : read ( ) ;
2022-04-01 17:44:49 +01:00
int64_t sleep_minutes_till_next_update = UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES ;
2021-05-21 13:32:34 +03:00
if ( state . githubUpdateLastCheckedDate . has_value ( ) )
2020-04-21 10:30:12 +03:00
{
2021-05-21 13:32:34 +03:00
int64_t last_checked_minutes_ago = timeutil : : diff : : in_minutes ( timeutil : : now ( ) , * state . githubUpdateLastCheckedDate ) ;
2020-04-21 10:30:12 +03:00
if ( last_checked_minutes_ago < 0 )
{
2021-01-12 18:34:02 +03:00
last_checked_minutes_ago = UPDATE_CHECK_INTERVAL_MINUTES ;
2020-04-21 10:30:12 +03:00
}
2021-01-12 18:34:02 +03:00
sleep_minutes_till_next_update = max ( 0 , UPDATE_CHECK_INTERVAL_MINUTES - last_checked_minutes_ago ) ;
2020-04-21 10:30:12 +03:00
}
2021-01-12 18:34:02 +03:00
std : : this_thread : : sleep_for ( std : : chrono : : minutes { sleep_minutes_till_next_update } ) ;
2021-05-21 13:32:34 +03:00
2023-02-24 15:35:33 +01:00
// Auto download setting.
bool download_update = ! IsMeteredConnection ( ) & & get_general_settings ( ) . downloadUpdatesAutomatically ;
if ( powertoys_gpo : : getDisableAutomaticUpdateDownloadValue ( ) = = powertoys_gpo : : gpo_rule_configured_enabled )
{
Logger : : info ( L " Automatic download of updates is disabled by GPO. " ) ;
download_update = false ;
}
2021-05-21 13:32:34 +03:00
bool version_info_obtained = false ;
2020-04-21 10:30:12 +03:00
try
{
2026-02-11 22:31:46 +08:00
const auto new_version_info = std : : move ( get_github_version_info_async ( ) ) . get ( ) ;
2021-05-21 13:32:34 +03:00
if ( new_version_info . has_value ( ) )
{
version_info_obtained = true ;
2026-01-08 15:57:49 +08:00
bool updateAvailable = std : : holds_alternative < new_version_download_info > ( * new_version_info ) ;
std : : wstring fromVersion = get_product_version ( ) ;
std : : wstring toVersion = updateAvailable ? std : : get < new_version_download_info > ( * new_version_info ) . version . toWstring ( ) : L " " ;
Trace : : UpdateCheckCompleted ( true , updateAvailable , fromVersion , toVersion ) ;
2021-06-14 12:55:59 +03:00
ProcessNewVersionInfo ( * new_version_info , state , download_update , true ) ;
2021-05-21 13:32:34 +03:00
}
else
{
2026-01-08 15:57:49 +08:00
Trace : : UpdateCheckCompleted ( false , false , get_product_version ( ) , L " " ) ;
2021-05-21 13:32:34 +03:00
Logger : : error ( L " Couldn't obtain version info from github: {} " , new_version_info . error ( ) ) ;
}
2020-04-21 10:30:12 +03:00
}
catch ( . . . )
{
2021-05-21 13:32:34 +03:00
Logger : : error ( " periodic_update_worker: error while processing version info " ) ;
2021-01-12 18:34:02 +03:00
}
2021-05-21 13:32:34 +03:00
if ( version_info_obtained )
2021-01-12 18:34:02 +03:00
{
2021-05-21 13:32:34 +03:00
UpdateState : : store ( [ & ] ( UpdateState & v ) {
v = std : : move ( state ) ;
2021-01-12 18:34:02 +03:00
} ) ;
}
else
{
std : : this_thread : : sleep_for ( std : : chrono : : minutes { UPDATE_CHECK_AFTER_FAILED_INTERVAL_MINUTES } ) ;
2020-04-21 10:30:12 +03:00
}
}
}
2021-06-14 12:55:59 +03:00
void CheckForUpdatesCallback ( )
2020-04-21 10:30:12 +03:00
{
2021-05-21 13:32:34 +03:00
Logger : : trace ( L " Check for updates callback invoked " ) ;
auto state = UpdateState : : read ( ) ;
2020-04-21 10:30:12 +03:00
try
{
2026-02-11 22:31:46 +08:00
auto new_version_info = std : : move ( get_github_version_info_async ( ) ) . get ( ) ;
2021-05-21 13:32:34 +03:00
if ( ! new_version_info )
2020-04-21 10:30:12 +03:00
{
2023-06-14 11:55:55 +02:00
// We couldn't get a new version from github for some reason, log error
state . state = UpdateState : : networkError ;
2026-01-08 15:57:49 +08:00
Trace : : UpdateCheckCompleted ( false , false , get_product_version ( ) , L " " ) ;
2021-05-21 13:32:34 +03:00
Logger : : error ( L " Couldn't obtain version info from github: {} " , new_version_info . error ( ) ) ;
2020-04-21 10:30:12 +03:00
}
2023-06-14 11:55:55 +02:00
else
2023-02-24 15:35:33 +01:00
{
2023-06-14 11:55:55 +02:00
// Auto download setting
bool download_update = ! IsMeteredConnection ( ) & & get_general_settings ( ) . downloadUpdatesAutomatically ;
if ( powertoys_gpo : : getDisableAutomaticUpdateDownloadValue ( ) = = powertoys_gpo : : gpo_rule_configured_enabled )
{
Logger : : info ( L " Automatic download of updates is disabled by GPO. " ) ;
download_update = false ;
}
2026-01-08 15:57:49 +08:00
bool updateAvailable = std : : holds_alternative < new_version_download_info > ( * new_version_info ) ;
std : : wstring fromVersion = get_product_version ( ) ;
std : : wstring toVersion = updateAvailable ? std : : get < new_version_download_info > ( * new_version_info ) . version . toWstring ( ) : L " " ;
Trace : : UpdateCheckCompleted ( true , updateAvailable , fromVersion , toVersion ) ;
2023-06-14 11:55:55 +02:00
ProcessNewVersionInfo ( * new_version_info , state , download_update , false ) ;
2023-02-24 15:35:33 +01:00
}
2023-06-05 13:42:06 +03:00
2021-05-21 13:32:34 +03:00
UpdateState : : store ( [ & ] ( UpdateState & v ) {
v = std : : move ( state ) ;
} ) ;
2020-04-21 10:30:12 +03:00
}
catch ( . . . )
{
2021-06-14 12:55:59 +03:00
Logger : : error ( " CheckForUpdatesCallback: error while processing version info " ) ;
2020-04-21 10:30:12 +03:00
}
}