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>
@@ -67,8 +67,11 @@
|
||||
<RegistryValue Type="string" Name="svgs_icons" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="icon.ico" Source="$(var.BinDir)svgs\icon.ico" />
|
||||
<File Id="iconUpdate.ico" Source="$(var.BinDir)svgs\iconUpdate.ico" />
|
||||
<File Id="PowerToysWhite.ico" Source="$(var.BinDir)svgs\PowerToysWhite.ico" />
|
||||
<File Id="PowerToysWhiteUpdate.ico" Source="$(var.BinDir)svgs\PowerToysWhiteUpdate.ico" />
|
||||
<File Id="PowerToysDark.ico" Source="$(var.BinDir)svgs\PowerToysDark.ico" />
|
||||
<File Id="PowerToysDarkUpdate.ico" Source="$(var.BinDir)svgs\PowerToysDarkUpdate.ico" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -197,4 +197,7 @@
|
||||
<value>Close</value>
|
||||
<comment>Close as a verb, as in Close the application</comment>
|
||||
</data>
|
||||
<data name="UPDATE_AVAILABLE_MENU_TEXT" xml:space="preserve">
|
||||
<value>Update available</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "ActionRunnerUtils.h"
|
||||
#include "general_settings.h"
|
||||
#include "trace.h"
|
||||
#include "tray_icon.h"
|
||||
#include "UpdateUtils.h"
|
||||
|
||||
#include <common/utils/gpo.h>
|
||||
@@ -130,6 +131,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
|
||||
state.releasePageUrl = {};
|
||||
state.downloadedInstallerFilename = {};
|
||||
Logger::trace(L"Version is up to date");
|
||||
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(false); }, nullptr);
|
||||
return;
|
||||
}
|
||||
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,
|
||||
state.state = UpdateState::readyToInstall;
|
||||
state.downloadedInstallerFilename = new_version_info.installer_filename;
|
||||
Trace::UpdateDownloadCompleted(true, new_version_info.version.toWstring());
|
||||
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(true); }, nullptr);
|
||||
if (show_notifications)
|
||||
{
|
||||
ShowNewVersionAvailable(new_version_info);
|
||||
@@ -197,6 +200,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
|
||||
Logger::trace(L"New version is ready to download, showing notification");
|
||||
state.state = UpdateState::readyToDownload;
|
||||
state.downloadedInstallerFilename = {};
|
||||
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(true); }, nullptr);
|
||||
if (show_notifications)
|
||||
{
|
||||
ShowOpenSettingsForUpdate();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#define APPICON 101
|
||||
#define ID_TRAY_MENU 102
|
||||
#define APPICON_UPDATE 103
|
||||
|
||||
#define ID_CLOSE_MENU_COMMAND 40001
|
||||
#define ID_SETTINGS_MENU_COMMAND 40002
|
||||
@@ -21,3 +22,4 @@
|
||||
#define ID_REPORT_BUG_COMMAND 40004
|
||||
#define ID_DOCUMENTATION_MENU_COMMAND 40005
|
||||
#define ID_QUICK_ACCESS_MENU_COMMAND 40006
|
||||
#define ID_UPDATE_MENU_COMMAND 40007
|
||||
|
||||
@@ -111,6 +111,11 @@
|
||||
<FileType>Document</FileType>
|
||||
<DestinationFolders>$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="svgs\iconUpdate.ico">
|
||||
<DeploymentContent>true</DeploymentContent>
|
||||
<FileType>Document</FileType>
|
||||
<DestinationFolders>$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\common\COMUtils\COMUtils.vcxproj">
|
||||
@@ -152,6 +157,16 @@
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="svgs\PowerToysDarkUpdate.ico">
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="svgs\PowerToysWhite.ico">
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</DeploymentContent>
|
||||
@@ -162,6 +177,16 @@
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="svgs\PowerToysWhiteUpdate.ico">
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
<DestinationFolders Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(OutDir)\svgs</DestinationFolders>
|
||||
</CopyFileToFolders>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 42 KiB |
BIN
src/runner/svgs/PowerToysDarkUpdate.ico
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 42 KiB |
BIN
src/runner/svgs/PowerToysWhiteUpdate.ico
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 42 KiB |
BIN
src/runner/svgs/iconUpdate.ico
Normal file
|
After Width: | Height: | Size: 42 KiB |
@@ -17,6 +17,7 @@
|
||||
#include <common/Themes/theme_listener.h>
|
||||
#include <common/Themes/theme_helpers.h>
|
||||
#include "bug_report.h"
|
||||
#include <common/updating/updateState.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -45,6 +46,7 @@ namespace
|
||||
|
||||
static ThemeListener theme_listener;
|
||||
static bool theme_adaptive_enabled = false;
|
||||
static bool update_available = false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
open_quick_access_flyout_window();
|
||||
break;
|
||||
}
|
||||
case ID_UPDATE_MENU_COMMAND:
|
||||
{
|
||||
open_settings_window(std::wstring{ L"Overview" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +267,24 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
|
||||
{
|
||||
h_sub_menu = GetSubMenu(h_menu, 0);
|
||||
}
|
||||
|
||||
// Dynamically add/remove "Update available" menu item and its separator
|
||||
DeleteMenu(h_sub_menu, ID_UPDATE_MENU_COMMAND, MF_BYCOMMAND);
|
||||
// Remove the separator right after the update item (position 0 after deletion)
|
||||
if (GetMenuItemCount(h_sub_menu) > 0)
|
||||
{
|
||||
MENUITEMINFOW mii = { .cbSize = sizeof(mii), .fMask = MIIM_FTYPE };
|
||||
if (GetMenuItemInfoW(h_sub_menu, 0, TRUE, &mii) && (mii.fType & MFT_SEPARATOR))
|
||||
{
|
||||
DeleteMenu(h_sub_menu, 0, MF_BYPOSITION);
|
||||
}
|
||||
}
|
||||
if (update_available)
|
||||
{
|
||||
InsertMenuW(h_sub_menu, 0, MF_BYPOSITION | MF_STRING, ID_UPDATE_MENU_COMMAND, GET_RESOURCE_STRING(IDS_UPDATE_AVAILABLE_MENU_TEXT).c_str());
|
||||
InsertMenuW(h_sub_menu, 1, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
|
||||
}
|
||||
|
||||
POINT mouse_pointer;
|
||||
GetCursorPos(&mouse_pointer);
|
||||
SetForegroundWindow(window); // Needed for the context menu to disappear.
|
||||
@@ -318,7 +343,14 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
|
||||
static HICON get_icon(Theme theme)
|
||||
{
|
||||
std::wstring icon_path = get_module_folderpath();
|
||||
icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico";
|
||||
if (theme == Theme::Dark)
|
||||
{
|
||||
icon_path += update_available ? L"\\svgs\\PowerToysWhiteUpdate.ico" : L"\\svgs\\PowerToysWhite.ico";
|
||||
}
|
||||
else
|
||||
{
|
||||
icon_path += update_available ? L"\\svgs\\PowerToysDarkUpdate.ico" : L"\\svgs\\PowerToysDark.ico";
|
||||
}
|
||||
Logger::trace(L"get_icon: Loading icon from path: {}", icon_path);
|
||||
|
||||
HICON icon = static_cast<HICON>(LoadImage(NULL,
|
||||
@@ -356,7 +388,14 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive)
|
||||
{
|
||||
theme_adaptive_enabled = theme_adaptive;
|
||||
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
HICON const icon = theme_adaptive ? get_icon(ThemeHelpers::GetSystemTheme()) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
|
||||
|
||||
// Check if an update is available at startup
|
||||
auto state = UpdateState::read();
|
||||
update_available = (state.state == UpdateState::readyToDownload || state.state == UpdateState::readyToInstall);
|
||||
|
||||
HICON const icon = theme_adaptive
|
||||
? get_icon(ThemeHelpers::GetSystemTheme())
|
||||
: LoadIcon(h_instance, MAKEINTRESOURCE(update_available ? APPICON_UPDATE : APPICON));
|
||||
if (icon)
|
||||
{
|
||||
UINT id_tray_icon = 1;
|
||||
@@ -425,6 +464,29 @@ void set_tray_icon_visible(bool shouldIconBeVisible)
|
||||
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
|
||||
}
|
||||
|
||||
void set_tray_icon_update_available(bool available)
|
||||
{
|
||||
if (update_available == available)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
update_available = available;
|
||||
Logger::info(L"set_tray_icon_update_available: update_available={}", update_available);
|
||||
|
||||
if (theme_adaptive_enabled)
|
||||
{
|
||||
tray_icon_data.hIcon = get_icon(ThemeHelpers::GetSystemTheme());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto h_instance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
tray_icon_data.hIcon = LoadIcon(h_instance, MAKEINTRESOURCE(available ? APPICON_UPDATE : APPICON));
|
||||
}
|
||||
|
||||
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
|
||||
}
|
||||
|
||||
void set_tray_icon_theme_adaptive(bool theme_adaptive)
|
||||
{
|
||||
Logger::info(L"set_tray_icon_theme_adaptive: Called with theme_adaptive={}, current theme_adaptive_enabled={}",
|
||||
@@ -445,7 +507,7 @@ void set_tray_icon_theme_adaptive(bool theme_adaptive)
|
||||
// If not requesting adaptive icon, or if adaptive icon failed to load, use default icon
|
||||
if (!icon)
|
||||
{
|
||||
icon = LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
|
||||
icon = LoadIcon(h_instance, MAKEINTRESOURCE(update_available ? APPICON_UPDATE : APPICON));
|
||||
if (theme_adaptive && icon)
|
||||
{
|
||||
// We requested adaptive but had to fall back, so update the flag
|
||||
|
||||
@@ -9,6 +9,8 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive);
|
||||
void set_tray_icon_visible(bool shouldIconBeVisible);
|
||||
// Enable or disable theme adaptive tray icon at runtime
|
||||
void set_tray_icon_theme_adaptive(bool theme_adaptive);
|
||||
// Show or hide the update-available badge on the tray icon
|
||||
void set_tray_icon_update_available(bool available);
|
||||
// Stop the Tray Icon
|
||||
void stop_tray_icon();
|
||||
// Open the Settings Window
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
x:Uid="UpdateAvailableInfoBar"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsUpdateAvailable, Mode=OneWay}"
|
||||
Severity="Success"
|
||||
Severity="Warning"
|
||||
Tapped="UpdateInfoBar_Tapped" />
|
||||
|
||||
<StackPanel
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
CornerRadius="10">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0.5,1">
|
||||
<GradientStop Offset="0.0" Color="#239DE0" />
|
||||
<GradientStop Offset="1.0" Color="#037CD6" />
|
||||
<GradientStop Offset="0.0" Color="#FFC328" />
|
||||
<GradientStop Offset="1.0" Color="#FC9A03" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
<FontIcon
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontSize="11"
|
||||
Foreground="White"
|
||||
Glyph="" />
|
||||
Foreground="Black"
|
||||
Glyph="" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" Orientation="Vertical">
|
||||
<TextBlock x:Uid="UpdateAvailableTextBlock" FontWeight="SemiBold" />
|
||||
|
||||
@@ -20,4 +20,21 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="UpdateInfoBadgeStyle" TargetType="InfoBadge">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Width" Value="8" />
|
||||
<Setter Property="Height" Value="8" />
|
||||
<Setter Property="Background" Value="#F09A2A" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="InfoBadge">
|
||||
<Border
|
||||
x:Name="RootGrid"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.InfoBadgeCornerRadius}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -109,7 +109,7 @@
|
||||
IsOpen="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToDownload}"
|
||||
IsTabStop="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToDownload}"
|
||||
Message="{x:Bind ViewModel.PowerToysNewAvailableVersion, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
Severity="Warning">
|
||||
|
||||
<InfoBar.Content>
|
||||
<StackPanel Spacing="16">
|
||||
@@ -150,13 +150,14 @@
|
||||
IsOpen="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToInstall}"
|
||||
IsTabStop="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToInstall}"
|
||||
Message="{x:Bind ViewModel.PowerToysNewAvailableVersion, Mode=OneWay}"
|
||||
Severity="Success">
|
||||
Severity="Warning">
|
||||
<InfoBar.Content>
|
||||
<Button
|
||||
x:Uid="General_InstallNow"
|
||||
Margin="0,0,0,16"
|
||||
Command="{Binding UpdateNowButtonEventHandler}"
|
||||
IsEnabled="{Binding IsDownloadAllowed}" />
|
||||
IsEnabled="{Binding IsDownloadAllowed}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</InfoBar.Content>
|
||||
<InfoBar.ActionButton>
|
||||
<HyperlinkButton
|
||||
|
||||
@@ -168,6 +168,13 @@
|
||||
</AnimatedIcon.FallbackIconSource>
|
||||
</AnimatedIcon>
|
||||
</NavigationViewItem.Icon>
|
||||
<NavigationViewItem.InfoBadge>
|
||||
<InfoBadge
|
||||
x:Name="UpdateInfoBadge"
|
||||
Margin="0,0,2,0"
|
||||
Style="{StaticResource UpdateInfoBadgeStyle}"
|
||||
Visibility="Collapsed" />
|
||||
</NavigationViewItem.InfoBadge>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItemSeparator />
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,6 +14,7 @@ using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
using Microsoft.UI.Windowing;
|
||||
@@ -100,6 +102,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
private CancellationTokenSource _searchDebounceCts;
|
||||
private const int SearchDebounceMs = 500;
|
||||
private bool _disposed;
|
||||
private IFileSystemWatcher _updateStateWatcher;
|
||||
|
||||
// Removed trace id counter per cleanup
|
||||
|
||||
@@ -137,6 +140,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
_searchSuggestions.Add(child.Content?.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
UpdateGeneralInfoBadge();
|
||||
_updateStateWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, () =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(UpdateGeneralInfoBadge);
|
||||
});
|
||||
}
|
||||
|
||||
public static int SendDefaultIPCMessage(string msg)
|
||||
@@ -629,11 +638,28 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
return;
|
||||
}
|
||||
|
||||
_updateStateWatcher?.Dispose();
|
||||
_searchDebounceCts?.Cancel();
|
||||
_searchDebounceCts?.Dispose();
|
||||
_searchDebounceCts = null;
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void UpdateGeneralInfoBadge()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = UpdatingSettings.LoadSettings();
|
||||
bool updateAvailable = config != null &&
|
||||
(config.State == UpdatingSettings.UpdatingState.ReadyToDownload ||
|
||||
config.State == UpdatingSettings.UpdatingState.ReadyToInstall);
|
||||
UpdateInfoBadge.Visibility = updateAvailable ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
UpdateInfoBadge.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5061,9 +5061,12 @@ The break timer font matches the text font.</value>
|
||||
<value>You're up to date</value>
|
||||
</data>
|
||||
<data name="UpdateAvailableTextBlock.Text" xml:space="preserve">
|
||||
<value>Update Available</value>
|
||||
<value>Update available</value>
|
||||
</data>
|
||||
<data name="GeneralVersion.Text" xml:space="preserve">
|
||||
<data name="UpdateAvailableInfoBar.Title" xml:space="preserve">
|
||||
<value>Update available</value>
|
||||
</data>
|
||||
<data name="GeneralVersion.Text" xml:space="preserve">
|
||||
<value>Version</value>
|
||||
</data>
|
||||
<data name="LearnWhatsNew.Text" xml:space="preserve">
|
||||
|
||||