From b96b4fcb0f697df90276f2ef34c638f77ec60278 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Fri, 21 May 2021 13:32:34 +0300 Subject: [PATCH] [Auto-update] Auto-update improvements (#11356) * [Updating] Refactor autoupdate mechanism to use Settings window buttons * [Updating] Don't use underscores in update_state (#11029) * [Updating] Rename action_runner to be consisent with accepted format * [Updating] Make UpdateState values explicit * [Setup] Set default bootstrapper log severity to debug * [BugReport] Include all found bootstrapper logs * [Setup] Use capital letter for ActionRunner * [Updating] Simple UI to test UpdateState * [Action Runner] cleanup and coding style * [BugReportTool] fix coding convension * [Auto-update][PT Settings] Updated general page in the Settings (#11227) * [Auto-update][PT Settings] File watcher monitoring UpdateState.json (#11282) * Handle button clicks (#11288) * [Updating] Document ActionRunner cmd flags * [Auto-update][PT Settings] Updated UI (#11335) * [Updating] Do not reset update state when msi cancellation detected * [Updating] Directly launch update_now PT action instead of using custom URI scheme * Checking for updates UI (#11354) * [Updating] Fix cannotDownload state in action runner * [Updating] Reset update state to CannotDownload if action runner encountered an error * [Updating][PT Settings] downloading label, disable button in error state * Changed error message * [Updating rename CannotDownload to ErrorDownloading * [Updating] Add trace logging for Check for updates callback * [Updating][PT Settings] simplify downloading checks * [Updating][PT Settings] Updated text labels * [Updating][PT Settings] Retry to load settings if failed * [Updating][PT Settings] Text fix * [Updating][PT Settings] Installed version links removed * [Updating][PT Settings] Error text updated * [Updating][PT Settings] Show label after version checked * [Updating][PT Settings] Text foreground fix * [Updating][PT Settings] Clean up * [Updating] Do not reset releasePageUrl in case of error/cancellation * [Updating][PT Settings] fixed missing string * [Updating][PT Settings] checked for updates state fix Co-authored-by: yuyoyuppe Co-authored-by: Andrey Nekrasov Co-authored-by: Enrico Giordani --- .pipelines/pipeline.user.windows.yml | 2 +- PowerToys.sln | 2 +- .../bootstrapper/Resources.resx | 46 ----- .../bootstrapper/bootstrapper.cpp | 9 +- installer/PowerToysSetup/Product.wxs | 8 +- .../LocProject.json | 0 .../Resources.resx | 46 ----- .../actionRunner.base.rc} | 0 .../actionRunner.cpp} | 141 ++++++++++--- .../actionRunner.vcxproj} | 26 +-- .../cs/src/action_runner/Resources.resx.lcl | 0 .../de/src/action_runner/Resources.resx.lcl | 0 .../es/src/action_runner/Resources.resx.lcl | 0 .../fr/src/action_runner/Resources.resx.lcl | 0 .../hu/src/action_runner/Resources.resx.lcl | 0 .../it/src/action_runner/Resources.resx.lcl | 0 .../ja/src/action_runner/Resources.resx.lcl | 0 .../ko/src/action_runner/Resources.resx.lcl | 0 .../nl/src/action_runner/Resources.resx.lcl | 0 .../pl/src/action_runner/Resources.resx.lcl | 0 .../src/action_runner/Resources.resx.lcl | 0 .../src/action_runner/Resources.resx.lcl | 0 .../ru/src/action_runner/Resources.resx.lcl | 0 .../sv/src/action_runner/Resources.resx.lcl | 0 .../tr/src/action_runner/Resources.resx.lcl | 0 .../src/action_runner/Resources.resx.lcl | 0 .../src/action_runner/Resources.resx.lcl | 0 .../packages.config | 0 .../resource.base.h | 6 +- src/common/notifications/dont_show_again.cpp | 1 - src/common/updating/installer.cpp | 2 +- src/common/updating/notifications.cpp | 112 ++--------- src/common/updating/notifications.h | 48 +---- src/common/updating/pch.h | 4 +- src/common/updating/updateState.cpp | 70 +++++++ src/common/updating/updateState.h | 25 +++ src/common/updating/updating.cpp | 123 ++++-------- src/common/updating/updating.h | 11 +- src/common/updating/updating.vcxproj | 2 + .../launcher/Microsoft.Launcher/dllmain.cpp | 2 +- src/runner/Resources.resx | 46 ----- src/runner/action_runner_utils.cpp | 2 +- src/runner/action_runner_utils.h | 24 ++- src/runner/main.cpp | 53 +---- src/runner/pch.h | 1 + src/runner/runner.vcxproj | 2 - src/runner/runner.vcxproj.filters | 6 - src/runner/settings_window.cpp | 34 +--- src/runner/update_state.cpp | 68 ------- src/runner/update_state.h | 18 -- src/runner/update_utils.cpp | 158 ++++++++++----- src/runner/update_utils.h | 5 +- .../UpdatingSettings.cs | 126 ++++++++++++ .../Utilities/Helper.cs | 6 + .../ViewModels/GeneralViewModel.cs | 168 +++++++++++++++- ...tateCannotDownloadToVisibilityConverter.cs | 24 +++ ...ateReadyToDownloadToVisibilityConverter.cs | 24 +++ ...tateReadyToInstallToVisibilityConverter.cs | 24 +++ ...atingStateUpToDateToVisibilityConverter.cs | 24 +++ .../Microsoft.PowerToys.Settings.UI.csproj | 4 + .../Strings/en-us/Resources.resw | 37 +++- .../Views/GeneralPage.xaml | 190 +++++++++++++++--- .../Views/GeneralPage.xaml.cs | 55 +---- tools/BugReportTool/BugReportTool/Main.cpp | 27 ++- .../BugReportTool/RegistryUtils.cpp | 3 + .../BugReportTool/RegistryUtils.h | 2 +- .../BugReportTool/ReportMonitorInfo.cpp | 7 +- .../BugReportTool/ReportMonitorInfo.h | 2 +- 68 files changed, 1065 insertions(+), 761 deletions(-) rename src/{action_runner => ActionRunner}/LocProject.json (100%) rename src/{action_runner => ActionRunner}/Resources.resx (68%) rename src/{action_runner/action_runner.base.rc => ActionRunner/actionRunner.base.rc} (100%) rename src/{action_runner/action_runner.cpp => ActionRunner/actionRunner.cpp} (62%) rename src/{action_runner/action_runner.vcxproj => ActionRunner/actionRunner.vcxproj} (74%) rename src/{action_runner => ActionRunner}/loc/cs/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/de/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/es/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/fr/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/hu/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/it/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/ja/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/ko/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/nl/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/pl/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/pt-BR/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/pt-PT/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/ru/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/sv/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/tr/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/zh-Hans/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/loc/zh-Hant/src/action_runner/Resources.resx.lcl (100%) rename src/{action_runner => ActionRunner}/packages.config (100%) rename src/{action_runner => ActionRunner}/resource.base.h (55%) create mode 100644 src/common/updating/updateState.cpp create mode 100644 src/common/updating/updateState.h delete mode 100644 src/runner/update_state.cpp delete mode 100644 src/runner/update_state.h create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/UpdatingSettings.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateCannotDownloadToVisibilityConverter.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToDownloadToVisibilityConverter.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToInstallToVisibilityConverter.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateUpToDateToVisibilityConverter.cs diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 0e72e144eb..b435e4258d 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -64,7 +64,7 @@ build: - from: 'x64/Release' to: 'Build_Output' include: - - 'action_runner.exe' + - 'PowerToys.ActionRunner.exe' - 'BugReportTool\BugReportTool.exe' - 'modules\ColorPicker\ColorPicker.dll' - 'modules\ColorPicker\ColorPickerUI.dll' diff --git a/PowerToys.sln b/PowerToys.sln index cd31c81bc7..c0799f590d 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -100,7 +100,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageResizerUITest", "src\modules\imageresizer\tests\ImageResizerUITest.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action_runner\action_runner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ActionRunner", "src\ActionRunner\ActionRunner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}" ProjectSection(ProjectDependencies) = postProject {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} EndProjectSection diff --git a/installer/PowerToysBootstrapper/bootstrapper/Resources.resx b/installer/PowerToysBootstrapper/bootstrapper/Resources.resx index 0687da7399..ce29162af4 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/Resources.resx +++ b/installer/PowerToysBootstrapper/bootstrapper/Resources.resx @@ -70,54 +70,18 @@ An update to PowerToys is available. - - PowerToys download started. - - - An update to PowerToys is ready to install. - - - Error: couldn't download PowerToys installer. Visit our GitHub page to update. - Update now - - At next launch - Error: please uninstall the previous version of PowerToys manually. An update to PowerToys is available. Visit our GitHub page to update. - - PowerToys is up to date. - - - Visit - More info... - - Abort - - - Click Snooze to be reminded in: - - - 1 day - - - 5 days - - - Downloading... - - - Download complete - PowerToys Update @@ -144,16 +108,6 @@ Couldn't install new PowerToys version. - - - Snooze - - - Updating from a local build is not supported. - User cannot autoupdate from a locally-built PowerToys version - - - Failed to connect to the server. Check your network connection or retry later. A newer version is already installed. diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp index f2d996c30e..02eb38102f 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp @@ -207,12 +207,8 @@ int Bootstrapper(HINSTANCE hInstance) { } - spdlog::level::level_enum severity = spdlog::level::off; - if (logLevel == "debug") - { - severity = spdlog::level::debug; - } - else if (logLevel == "error") + spdlog::level::level_enum severity = spdlog::level::debug; + if (logLevel == "error") { severity = spdlog::level::err; } @@ -359,6 +355,7 @@ int Bootstrapper(HINSTANCE hInstance) { spdlog::error("Couldn't install the existing MSI package ({})", GetLastError()); ShowMessageBoxError(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR); + return 1; } const bool installDotnet = !skipDotnetInstall; diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index b706692f8e..9c45963e4f 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -71,7 +71,7 @@ - + @@ -324,8 +324,8 @@ - - + + @@ -792,7 +792,7 @@ - + diff --git a/src/action_runner/LocProject.json b/src/ActionRunner/LocProject.json similarity index 100% rename from src/action_runner/LocProject.json rename to src/ActionRunner/LocProject.json diff --git a/src/action_runner/Resources.resx b/src/ActionRunner/Resources.resx similarity index 68% rename from src/action_runner/Resources.resx rename to src/ActionRunner/Resources.resx index 5662ceb353..7e2feb1971 100644 --- a/src/action_runner/Resources.resx +++ b/src/ActionRunner/Resources.resx @@ -67,54 +67,18 @@ An update to PowerToys is available. - - PowerToys download started. - - - An update to PowerToys is ready to install. - - - Error: couldn't download PowerToys installer. Visit our GitHub page to update. - Update now - - At next launch - Error: please uninstall the previous version of PowerToys manually. An update to PowerToys is available. Visit our GitHub page to update. - - PowerToys is up to date. - - - Visit - More info... - - Abort - - - Click Snooze to be reminded in: - - - 1 day - - - 5 days - - - Downloading... - - - Download complete - PowerToys Update @@ -124,14 +88,4 @@ PowerToys: uninstall previous version? - - Snooze - - - Updating from a local build is not supported. - User cannot autoupdate from a locally-built PowerToys version - - - Failed to connect to the server. Check your network connection or retry later. - diff --git a/src/action_runner/action_runner.base.rc b/src/ActionRunner/actionRunner.base.rc similarity index 100% rename from src/action_runner/action_runner.base.rc rename to src/ActionRunner/actionRunner.base.rc diff --git a/src/action_runner/action_runner.cpp b/src/ActionRunner/actionRunner.cpp similarity index 62% rename from src/action_runner/action_runner.cpp rename to src/ActionRunner/actionRunner.cpp index 144d57b3e8..bfd54f816b 100644 --- a/src/action_runner/action_runner.cpp +++ b/src/ActionRunner/actionRunner.cpp @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + #define WIN32_LEAN_AND_MEAN #include "Generated Files/resource.h" @@ -8,6 +12,7 @@ #include #include +#include #include #include #include @@ -15,6 +20,7 @@ #include #include #include +#include #include @@ -29,13 +35,16 @@ auto Strings = create_notifications_strings(); -int uninstall_msi_action() +using namespace cmdArg; + +int UninstallMsiAction() { const auto package_path = updating::get_msi_package_path(); if (package_path.empty()) { return 0; } + if (!updating::uninstall_msi_version(package_path, Strings)) { return -1; @@ -55,44 +64,92 @@ int uninstall_msi_action() namespace fs = std::filesystem; -std::optional copy_self_to_temp_dir() +std::optional CopySelfToTempDir() { std::error_code error; - auto dst_path = fs::temp_directory_path() / "action_runner.exe"; + auto dst_path = fs::temp_directory_path() / "PowerToys.ActionRunner.exe"; fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error); if (error) { return std::nullopt; } + return std::move(dst_path); } -bool install_new_version_stage_1(const std::wstring_view installer_filename, const bool must_restart = false) +std::optional ObtainInstallerPath() { - const fs::path installer{ updating::get_pending_updates_path() / installer_filename }; + using namespace updating; - if (!fs::is_regular_file(installer)) + auto state = UpdateState::read(); + if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading) + { + const auto new_version_info = get_github_version_info_async(Strings).get(); + if (!new_version_info) + { + Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error()); + return std::nullopt; + } + + if (!std::holds_alternative(*new_version_info)) + { + Logger::error("Invoked with -update_now argument, but no update was available"); + return std::nullopt; + } + + auto downloaded_installer = download_new_version(std::get(*new_version_info)).get(); + if (!downloaded_installer) + { + Logger::error("Couldn't download new installer"); + } + + return downloaded_installer; + } + else if (state.state == UpdateState::readyToInstall) + { + fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename }; + if (fs::is_regular_file(installer)) + { + return std::move(installer); + } + else + { + Logger::error(L"Couldn't find a downloaded installer {}", installer.native()); + return std::nullopt; + } + } + else + { + Logger::error("Invoked with -update_now argument, but update state was invalid"); + return std::nullopt; + } +} + +bool InstallNewVersionStage1() +{ + const auto installer = ObtainInstallerPath(); + if (!installer) { return false; } - if (auto copy_in_temp = copy_self_to_temp_dir()) + if (auto copy_in_temp = CopySelfToTempDir()) { - // detect if PT was running + // Detect if PT was running const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr); - const bool launch_powertoys = must_restart || pt_main_window != nullptr; + const bool launch_powertoys = pt_main_window != nullptr; if (pt_main_window != nullptr) { SendMessageW(pt_main_window, WM_CLOSE, 0, 0); } - std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2_CMDARG }; + std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 }; arguments += L" \""; - arguments += installer.c_str(); + arguments += installer->c_str(); arguments += L"\" \""; arguments += get_module_folderpath(); arguments += L"\" "; - arguments += launch_powertoys ? UPDATE_STAGE2_RESTART_PT_CMDARG : UPDATE_STAGE2_DONT_START_PT_CMDARG; + arguments += launch_powertoys ? UPDATE_STAGE2_RESTART_PT : UPDATE_STAGE2_DONT_START_PT; SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC }; sei.lpFile = copy_in_temp->c_str(); @@ -107,7 +164,7 @@ bool install_new_version_stage_1(const std::wstring_view installer_filename, con } } -bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view install_path, bool launch_powertoys) +bool InstallNewVersionStage2(std::wstring installer_path, std::wstring_view install_path, bool launch_powertoys) { std::transform(begin(installer_path), end(installer_path), begin(installer_path), ::towlower); @@ -134,25 +191,36 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view { parameters += L"--no_start_pt"; } + sei.lpParameters = parameters.c_str(); success = ShellExecuteExW(&sei) == TRUE; + // Wait for the install completion if (success) { WaitForSingleObject(sei.hProcess, INFINITE); + DWORD exitCode = 0; + GetExitCodeProcess(sei.hProcess, &exitCode); + success = exitCode == 0; CloseHandle(sei.hProcess); } } - std::error_code _; - fs::remove(installer_path, _); - if (!success) { return false; } + std::error_code _; + fs::remove(installer_path, _); + + UpdateState::store([&](UpdateState& state) { + state = {}; + state.githubUpdateLastCheckedDate.emplace(timeutil::now()); + state.state = UpdateState::upToDate; + }); + if (launch_powertoys) { std::wstring new_pt_path{ install_path }; @@ -164,6 +232,7 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view sei.lpParameters = UPDATE_REPORT_SUCCESS; return ShellExecuteExW(&sei) == TRUE; } + return true; } @@ -175,13 +244,14 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return 1; } + std::wstring_view action{ args[1] }; std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location()); logFilePath.append(LogSettings::actionRunnerLogPath); Logger::init(LogSettings::actionRunnerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); - if (action == RUN_NONELEVATED_CMDARG) + if (action == RUN_NONELEVATED) { int nextArg = 2; @@ -227,7 +297,6 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) run_same_elevation(target.data(), params, pidBuffer); - // cleanup if (!pidFile.empty()) { if (pidBuffer) @@ -243,24 +312,36 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) } } } - else if (action == UNINSTALL_MSI_CMDARG) + else if (action == UNINSTALL_MSI) { - return uninstall_msi_action(); + return UninstallMsiAction(); } - else if (action == UPDATE_NOW_LAUNCH_STAGE1_CMDARG) + else if (action == UPDATE_NOW_LAUNCH_STAGE1) { - std::wstring_view installerFilename{ args[2] }; - return !install_new_version_stage_1(installerFilename); + const bool failed = !InstallNewVersionStage1(); + if (failed) + { + UpdateState::store([&](UpdateState& state) { + state.downloadedInstallerFilename = {}; + state.githubUpdateLastCheckedDate.emplace(timeutil::now()); + state.state = UpdateState::errorDownloading; + }); + } + return failed; } - else if (action == UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG) - { - std::wstring_view installerFilename{ args[2] }; - return !install_new_version_stage_1(installerFilename, true); - } - else if (action == UPDATE_NOW_LAUNCH_STAGE2_CMDARG) + else if (action == UPDATE_NOW_LAUNCH_STAGE2) { using namespace std::string_view_literals; - return !install_new_version_stage_2(args[2], args[3], args[4] == std::wstring_view{ UPDATE_STAGE2_RESTART_PT_CMDARG }); + const bool failed = !InstallNewVersionStage2(args[2], args[3], args[4] == std::wstring_view{ UPDATE_STAGE2_RESTART_PT }); + if (failed) + { + UpdateState::store([&](UpdateState& state) { + state.downloadedInstallerFilename = {}; + state.githubUpdateLastCheckedDate.emplace(timeutil::now()); + state.state = UpdateState::errorDownloading; + }); + } + return failed; } return 0; diff --git a/src/action_runner/action_runner.vcxproj b/src/ActionRunner/actionRunner.vcxproj similarity index 74% rename from src/action_runner/action_runner.vcxproj rename to src/ActionRunner/actionRunner.vcxproj index 89133a9928..126508090e 100644 --- a/src/action_runner/action_runner.vcxproj +++ b/src/ActionRunner/actionRunner.vcxproj @@ -2,13 +2,13 @@ - + 16.0 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} - action_runner - action_runner + actionRunner + PowerToys.ActionRunner @@ -20,16 +20,7 @@ - - - - - - - - - - + @@ -44,7 +35,7 @@ - + @@ -61,12 +52,11 @@ - - - + + @@ -83,4 +73,4 @@ - + \ No newline at end of file diff --git a/src/action_runner/loc/cs/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/cs/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/cs/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/cs/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/de/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/de/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/de/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/de/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/es/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/es/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/es/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/es/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/fr/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/fr/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/fr/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/fr/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/hu/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/hu/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/hu/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/hu/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/it/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/it/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/it/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/it/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/ja/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/ja/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/ja/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/ja/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/ko/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/ko/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/ko/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/ko/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/nl/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/nl/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/nl/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/nl/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/pl/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/pl/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/pl/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/pl/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/pt-BR/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/pt-BR/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/pt-BR/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/pt-BR/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/pt-PT/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/pt-PT/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/pt-PT/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/pt-PT/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/ru/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/ru/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/ru/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/ru/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/sv/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/sv/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/sv/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/sv/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/tr/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/tr/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/tr/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/tr/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/zh-Hans/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/zh-Hans/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/zh-Hans/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/zh-Hans/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/loc/zh-Hant/src/action_runner/Resources.resx.lcl b/src/ActionRunner/loc/zh-Hant/src/action_runner/Resources.resx.lcl similarity index 100% rename from src/action_runner/loc/zh-Hant/src/action_runner/Resources.resx.lcl rename to src/ActionRunner/loc/zh-Hant/src/action_runner/Resources.resx.lcl diff --git a/src/action_runner/packages.config b/src/ActionRunner/packages.config similarity index 100% rename from src/action_runner/packages.config rename to src/ActionRunner/packages.config diff --git a/src/action_runner/resource.base.h b/src/ActionRunner/resource.base.h similarity index 55% rename from src/action_runner/resource.base.h rename to src/ActionRunner/resource.base.h index cadb2738ed..2d2c6d7cd6 100644 --- a/src/action_runner/resource.base.h +++ b/src/ActionRunner/resource.base.h @@ -1,11 +1,11 @@ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. -// Used by action_runner.rc +// Used by PowerToys.ActionRunner.rc ////////////////////////////// // Non-localizable #define FILE_DESCRIPTION "PowerToys ActionRunner" -#define INTERNAL_NAME "action_runner" -#define ORIGINAL_FILENAME "action_runner.exe" +#define INTERNAL_NAME "PowerToys.ActionRunner" +#define ORIGINAL_FILENAME "PowerToys.ActionRunner.exe" diff --git a/src/common/notifications/dont_show_again.cpp b/src/common/notifications/dont_show_again.cpp index 4d9d8d91a8..64f927f646 100644 --- a/src/common/notifications/dont_show_again.cpp +++ b/src/common/notifications/dont_show_again.cpp @@ -57,6 +57,5 @@ namespace notifications } RegCloseKey(key); return timeutil::diff::in_days(timeutil::now(), last_disabled_time) < disable_interval_in_days; - return false; } } \ No newline at end of file diff --git a/src/common/updating/installer.cpp b/src/common/updating/installer.cpp index f7b4a673a3..91572d0dde 100644 --- a/src/common/updating/installer.cpp +++ b/src/common/updating/installer.cpp @@ -81,7 +81,7 @@ namespace updating } catch (...) { - updating::notifications::show_uninstallation_error(strings); + MessageBoxW(nullptr, strings.UNINSTALLATION_UNKNOWN_ERROR.c_str(), strings.NOTIFICATION_TITLE.c_str(), MB_OK | MB_ICONERROR); } } return false; diff --git a/src/common/updating/notifications.cpp b/src/common/updating/notifications.cpp index 853fc4f130..7a08a1031f 100644 --- a/src/common/updating/notifications.cpp +++ b/src/common/updating/notifications.cpp @@ -14,7 +14,7 @@ namespace updating namespace notifications { using namespace ::notifications; - std::wstring current_version_to_next_version(const updating::new_version_download_info& info) + std::wstring current_version_to_next_version(const new_version_download_info& info) { auto current_version_to_next_version = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring(); current_version_to_next_version += L" -> "; @@ -22,15 +22,7 @@ namespace updating return current_version_to_next_version; } - void show_unavailable(const notifications::strings& strings, std::wstring reason) - { - remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); - - toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false }; - show_toast(std::move(reason), strings.TOAST_TITLE, std::move(toast_params)); - } - - void show_available(const updating::new_version_download_info& info, const notifications::strings& strings) + void show_new_version_available(const new_version_download_info& info, const strings& strings) { remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); @@ -40,106 +32,30 @@ namespace updating contents += current_version_to_next_version(info); show_toast_with_activations(std::move(contents), - strings.TOAST_TITLE, + strings.NOTIFICATION_TITLE, {}, { link_button{ strings.GITHUB_NEW_VERSION_UPDATE_NOW, - L"powertoys://download_and_install_update/" }, + L"powertoys://update_now/" }, link_button{ strings.GITHUB_NEW_VERSION_MORE_INFO, - info.release_page_uri.ToString().c_str() } }, + L"powertoys://open_settings/" } }, std::move(toast_params)); } - void show_download_start(const updating::new_version_download_info& info, const notifications::strings& strings) - { - remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); - - progress_bar_params progress_bar_params; - std::wstring progress_title{ info.version.toWstring() }; - progress_title += L' '; - progress_title += strings.DOWNLOAD_IN_PROGRESS; - - progress_bar_params.progress_title = progress_title; - progress_bar_params.progress = .0f; - toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false, std::move(progress_bar_params) }; - show_toast_with_activations(strings.GITHUB_NEW_VERSION_DOWNLOAD_STARTED, - strings.TOAST_TITLE, - {}, - {}, - std::move(toast_params)); - } - - void show_visit_github(const updating::new_version_download_info& info, const notifications::strings& strings) + void show_open_settings_for_update(const strings& strings) { remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false }; - std::wstring contents = strings.GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT; - contents += L'\n'; - contents += current_version_to_next_version(info); - show_toast_with_activations(std::move(contents), - strings.TOAST_TITLE, + + std::vector actions = { + link_button{ strings.GITHUB_NEW_VERSION_MORE_INFO, + L"powertoys://open_settings/" }, + }; + show_toast_with_activations(strings.GITHUB_NEW_VERSION_AVAILABLE, + strings.NOTIFICATION_TITLE, {}, - { link_button{ strings.GITHUB_NEW_VERSION_VISIT, - info.release_page_uri.ToString().c_str() } }, + std::move(actions), std::move(toast_params)); } - - void show_install_error(const updating::new_version_download_info& info, const notifications::strings& strings) - { - remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); - - toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false }; - std::wstring contents = strings.GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR; - contents += L'\n'; - contents += current_version_to_next_version(info); - show_toast_with_activations(std::move(contents), - strings.TOAST_TITLE, - {}, - { link_button{ strings.GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } }, - std::move(toast_params)); - } - - void show_version_ready(const updating::new_version_download_info& info, const notifications::strings& strings) - { - remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); - - toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false }; - std::wstring new_version_ready{ strings.GITHUB_NEW_VERSION_READY_TO_INSTALL }; - new_version_ready += L'\n'; - new_version_ready += current_version_to_next_version(info); - - show_toast_with_activations(std::move(new_version_ready), - strings.TOAST_TITLE, - {}, - { link_button{ strings.GITHUB_NEW_VERSION_UPDATE_NOW, - L"powertoys://update_now/" + info.installer_filename }, - link_button{ strings.GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, - L"powertoys://schedule_update/" + info.installer_filename }, - snooze_button{ - strings.GITHUB_NEW_VERSION_SNOOZE_TITLE, - { { strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, - { strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } }, - strings.SNOOZE_BUTTON } }, - std::move(toast_params)); - } - - void show_uninstallation_error(const notifications::strings& strings) - { - remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG); - - show_toast(strings.UNINSTALLATION_UNKNOWN_ERROR, strings.TOAST_TITLE); - } - - void update_download_progress(const updating::new_version_download_info& info, float progress, const notifications::strings& strings) - { - progress_bar_params progress_bar_params; - - std::wstring progress_title{ info.version.toWstring() }; - progress_title += L' '; - progress_title += progress < 1 ? strings.DOWNLOAD_IN_PROGRESS : strings.DOWNLOAD_COMPLETE; - progress_bar_params.progress_title = progress_title; - progress_bar_params.progress = progress; - update_toast_progress_bar(UPDATING_PROCESS_TOAST_TAG, progress_bar_params); - } } } diff --git a/src/common/updating/notifications.h b/src/common/updating/notifications.h index 1862671d9a..421a32d716 100644 --- a/src/common/updating/notifications.h +++ b/src/common/updating/notifications.h @@ -10,71 +10,31 @@ namespace updating { struct strings { - std::wstring DOWNLOAD_COMPLETE; - std::wstring DOWNLOAD_IN_PROGRESS; - - std::wstring GITHUB_NEW_VERSION_ABORT; std::wstring GITHUB_NEW_VERSION_AVAILABLE; - std::wstring GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT; - std::wstring GITHUB_NEW_VERSION_CHECK_ERROR; - std::wstring GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR; - std::wstring GITHUB_NEW_VERSION_DOWNLOAD_STARTED; std::wstring GITHUB_NEW_VERSION_MORE_INFO; - std::wstring GITHUB_NEW_VERSION_READY_TO_INSTALL; - std::wstring GITHUB_NEW_VERSION_SNOOZE_TITLE; - std::wstring GITHUB_NEW_VERSION_UP_TO_DATE; std::wstring GITHUB_NEW_VERSION_UPDATE_NOW; - std::wstring GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART; - std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D; - std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D; - std::wstring GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR; - std::wstring GITHUB_NEW_VERSION_VISIT; std::wstring OFFER_UNINSTALL_MSI; std::wstring OFFER_UNINSTALL_MSI_TITLE; - std::wstring SNOOZE_BUTTON; - std::wstring TOAST_TITLE; + std::wstring NOTIFICATION_TITLE; std::wstring UNINSTALLATION_UNKNOWN_ERROR; }; - void show_unavailable(const notifications::strings& strings, std::wstring reason); - void show_available(const updating::new_version_download_info& info, const strings&); - void show_download_start(const updating::new_version_download_info& info, const strings&); - void show_visit_github(const updating::new_version_download_info& info, const strings&); - void show_install_error(const updating::new_version_download_info& info, const strings&); - void show_version_ready(const updating::new_version_download_info& info, const strings&); - void show_uninstallation_error(const notifications::strings& strings); - - void update_download_progress(const updating::new_version_download_info& info, float progress, const notifications::strings& strings); + void show_new_version_available(const new_version_download_info& info, const strings& strings); + void show_open_settings_for_update(const strings& strings); } } #define create_notifications_strings() \ ::updating::notifications::strings \ { \ - .DOWNLOAD_COMPLETE = GET_RESOURCE_STRING(IDS_DOWNLOAD_COMPLETE), \ - .DOWNLOAD_IN_PROGRESS = GET_RESOURCE_STRING(IDS_DOWNLOAD_IN_PROGRESS), \ - .GITHUB_NEW_VERSION_ABORT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_ABORT), \ .GITHUB_NEW_VERSION_AVAILABLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE), \ - .GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT), \ - .GITHUB_NEW_VERSION_CHECK_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_CHECK_ERROR), \ - .GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR), \ - .GITHUB_NEW_VERSION_DOWNLOAD_STARTED = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_STARTED), \ .GITHUB_NEW_VERSION_MORE_INFO = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_MORE_INFO), \ - .GITHUB_NEW_VERSION_READY_TO_INSTALL = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_READY_TO_INSTALL), \ - .GITHUB_NEW_VERSION_SNOOZE_TITLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_SNOOZE_TITLE), \ - .GITHUB_NEW_VERSION_UP_TO_DATE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UP_TO_DATE), \ .GITHUB_NEW_VERSION_UPDATE_NOW = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_NOW), \ - .GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART), \ - .GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D), \ - .GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D), \ - .GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR), \ - .GITHUB_NEW_VERSION_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_VISIT), \ .OFFER_UNINSTALL_MSI = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI), \ .OFFER_UNINSTALL_MSI_TITLE = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI_TITLE), \ - .SNOOZE_BUTTON = GET_RESOURCE_STRING(IDS_SNOOZE_BUTTON), \ - .TOAST_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \ + .NOTIFICATION_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \ .UNINSTALLATION_UNKNOWN_ERROR = GET_RESOURCE_STRING(IDS_UNINSTALLATION_UNKNOWN_ERROR) \ } diff --git a/src/common/updating/pch.h b/src/common/updating/pch.h index 3e2b83fbff..6b484c4eea 100644 --- a/src/common/updating/pch.h +++ b/src/common/updating/pch.h @@ -27,9 +27,11 @@ #include #include -#include #include #include +#include + +#include #endif //PCH_H diff --git a/src/common/updating/updateState.cpp b/src/common/updating/updateState.cpp new file mode 100644 index 0000000000..40e6330576 --- /dev/null +++ b/src/common/updating/updateState.cpp @@ -0,0 +1,70 @@ +#include "pch.h" +#include "updateState.h" + +#include +#include +#include + +namespace +{ + const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\UpdateState.json"; + const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToysRunnerUpdateStateMutex"; +} + +UpdateState deserialize(const json::JsonObject& json) +{ + UpdateState result; + + result.state = static_cast(json.GetNamedNumber(L"state", UpdateState::upToDate)); + result.releasePageUrl = json.GetNamedString(L"releasePageUrl", L""); + result.githubUpdateLastCheckedDate = timeutil::from_string(json.GetNamedString(L"githubUpdateLastCheckedDate", L"invalid").c_str()); + result.downloadedInstallerFilename = json.GetNamedString(L"downloadedInstallerFilename", L""); + return result; +} + +json::JsonObject serialize(const UpdateState& state) +{ + json::JsonObject json; + + if (state.githubUpdateLastCheckedDate.has_value()) + { + json.SetNamedValue(L"githubUpdateLastCheckedDate", json::value(timeutil::to_string(*state.githubUpdateLastCheckedDate))); + } + json.SetNamedValue(L"releasePageUrl", json::value(state.releasePageUrl)); + json.SetNamedValue(L"state", json::value(static_cast(state.state))); + json.SetNamedValue(L"downloadedInstallerFilename", json::value(state.downloadedInstallerFilename)); + + return json; +} + +UpdateState UpdateState::read() +{ + const auto filename = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME; + std::optional json; + { + wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; + auto lock = mutex.acquire(); + json = json::from_file(filename); + } + return json ? deserialize(*json) : UpdateState{}; +} + +void UpdateState::store(std::function stateModifier) +{ + const auto filename = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME; + + std::optional json; + { + wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; + auto lock = mutex.acquire(); + json = json::from_file(filename); + UpdateState state; + if (json) + { + state = deserialize(*json); + } + stateModifier(state); + json.emplace(serialize(state)); + json::to_file(filename, *json); + } +} diff --git a/src/common/updating/updateState.h b/src/common/updating/updateState.h new file mode 100644 index 0000000000..09cb0c2f41 --- /dev/null +++ b/src/common/updating/updateState.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +// All fields must be default-initialized +struct UpdateState +{ + enum State + { + upToDate = 0, + errorDownloading = 1, + readyToDownload = 2, + readyToInstall = 3 + } state = upToDate; + std::wstring releasePageUrl; + std::optional githubUpdateLastCheckedDate; + std::wstring downloadedInstallerFilename; + + // To prevent concurrent modification of the file, we enforce this interface, which locks the file while + // the state_modifier is active. + static void store(std::function stateModifier); + static UpdateState read(); +}; \ No newline at end of file diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index 2c478ae582..a80017ed4d 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -15,12 +15,27 @@ namespace // Strings in this namespace should not be localized { const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest"; const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases"; + + const wchar_t LOCAL_BUILD_ERROR[] = L"Local build cannot be updated"; + const wchar_t NETWORK_ERROR[] = L"Network error"; const size_t MAX_DOWNLOAD_ATTEMPTS = 3; } namespace updating { + Uri extract_release_page_url(const json::JsonObject& release_object) + { + try + { + return Uri{ release_object.GetNamedString(L"html_url") }; + } + catch (...) + { + } + return nullptr; + } + std::optional extract_version_from_release_object(const json::JsonObject& release_object) { try @@ -66,7 +81,7 @@ namespace updating // If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates. if (VERSION_MAJOR == 0 && VERSION_MINOR == 0) { - co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR); + co_return nonstd::make_unexpected(LOCAL_BUILD_ERROR); } try @@ -109,24 +124,16 @@ namespace updating co_return version_up_to_date{}; } - Uri release_page_url{ release_object.GetNamedString(L"html_url") }; - auto installer_download_url = extract_installer_asset_download_info(release_object); - co_return new_version_download_info{ std::move(release_page_url), + auto [installer_download_url, installer_filename] = extract_installer_asset_download_info(release_object); + co_return new_version_download_info{ extract_release_page_url(release_object), std::move(github_version), - std::move(installer_download_url.first), - std::move(installer_download_url.second) }; + std::move(installer_download_url), + std::move(installer_filename) }; } catch (...) { } - co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_CHECK_ERROR); - } - - bool could_be_costly_connection() - { - using namespace winrt::Windows::Networking::Connectivity; - ConnectionProfile internetConnectionProfile = NetworkInformation::GetInternetConnectionProfile(); - return internetConnectionProfile.IsWwanConnectionProfile(); + co_return nonstd::make_unexpected(NETWORK_ERROR); } std::filesystem::path get_pending_updates_path() @@ -136,86 +143,40 @@ namespace updating return { std::move(path_str) }; } - std::filesystem::path create_download_path() + std::optional create_download_path() { - auto installer_download_dst = get_pending_updates_path(); - std::error_code _; - std::filesystem::create_directories(installer_download_dst, _); - return installer_download_dst; + auto installer_download_path = get_pending_updates_path(); + std::error_code ec; + std::filesystem::create_directories(installer_download_path, ec); + return !ec ? std::optional{ installer_download_path } : std::nullopt; } - std::future try_autoupdate(const bool download_updates_automatically, const notifications::strings& strings) + std::future> download_new_version(const new_version_download_info& new_version) { - const auto version_check_result = co_await get_github_version_info_async(strings); - if (!version_check_result) + auto installer_download_path = create_download_path(); + if (!installer_download_path) { - co_return false; + co_return std::nullopt; } - if (std::holds_alternative(*version_check_result)) - { - co_return true; - } - const auto new_version = std::get(*version_check_result); - if (download_updates_automatically && !could_be_costly_connection()) + *installer_download_path /= new_version.installer_filename; + + bool download_success = false; + for (size_t i = 0; i < MAX_DOWNLOAD_ATTEMPTS; ++i) { - auto installer_download_dst = create_download_path() / new_version.installer_filename; - bool download_success = false; - for (size_t i = 0; i < MAX_DOWNLOAD_ATTEMPTS; ++i) + try { - try - { - http::HttpClient client; - co_await client.download(new_version.installer_download_url, installer_download_dst); - download_success = true; - break; - } - catch (...) - { - // reattempt to download or do nothing - } + http::HttpClient client; + co_await client.download(new_version.installer_download_url, *installer_download_path); + download_success = true; + break; } - if (!download_success) + catch (...) { - updating::notifications::show_install_error(new_version, strings); - co_return false; + // reattempt to download or do nothing } - - updating::notifications::show_version_ready(new_version, strings); } - else - { - updating::notifications::show_visit_github(new_version, strings); - } - co_return true; + co_return download_success ? installer_download_path : std::nullopt; } - std::future download_update(const notifications::strings& strings) - { - const auto version_check_result = co_await get_github_version_info_async(strings); - if (!version_check_result || std::holds_alternative(*version_check_result)) - { - co_return L""; - } - const auto new_version = std::get(*version_check_result); - auto installer_download_dst = create_download_path() / new_version.installer_filename; - updating::notifications::show_download_start(new_version, strings); - - try - { - auto progressUpdateHandle = [&](float progress) { - updating::notifications::update_download_progress(new_version, progress, strings); - }; - - http::HttpClient client; - co_await client.download(new_version.installer_download_url, installer_download_dst, progressUpdateHandle); - } - catch (...) - { - updating::notifications::show_install_error(new_version, strings); - co_return L""; - } - - co_return new_version.installer_filename; - } } diff --git a/src/common/updating/updating.h b/src/common/updating/updating.h index dcde36eb7c..1aac92d712 100644 --- a/src/common/updating/updating.h +++ b/src/common/updating/updating.h @@ -14,9 +14,9 @@ namespace updating { using winrt::Windows::Foundation::Uri; - struct version_up_to_date {}; - using github_version_info = std::variant; - + struct version_up_to_date + { + }; struct new_version_download_info { Uri release_page_uri = nullptr; @@ -24,11 +24,10 @@ namespace updating Uri installer_download_url = nullptr; std::wstring installer_filename; }; + using github_version_info = std::variant; - // Returns whether the update check has succeeded - std::future try_autoupdate(const bool download_updates_automatically, const notifications::strings&); + std::future> download_new_version(const new_version_download_info& new_version); std::filesystem::path get_pending_updates_path(); - std::future download_update(const notifications::strings&); std::future> get_github_version_info_async(const notifications::strings& strings, const bool prerelease = false); // non-localized diff --git a/src/common/updating/updating.vcxproj b/src/common/updating/updating.vcxproj index 5f0935569f..c9caaba5c4 100644 --- a/src/common/updating/updating.vcxproj +++ b/src/common/updating/updating.vcxproj @@ -40,6 +40,7 @@ + @@ -49,6 +50,7 @@ + Create diff --git a/src/modules/launcher/Microsoft.Launcher/dllmain.cpp b/src/modules/launcher/Microsoft.Launcher/dllmain.cpp index 4524ff2557..430dbecb22 100644 --- a/src/modules/launcher/Microsoft.Launcher/dllmain.cpp +++ b/src/modules/launcher/Microsoft.Launcher/dllmain.cpp @@ -225,7 +225,7 @@ public: params += L" -powerToysPid " + std::to_wstring(powertoys_pid) + L" "; params += L"--centralized-kb-hook "; - action_runner_path += L"\\action_runner.exe"; + action_runner_path += L"\\PowerToys.ActionRunner.exe"; // Set up the shared file from which to retrieve the PID of PowerLauncher HANDLE hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(DWORD), POWER_LAUNCHER_PID_SHARED_FILE); if (!hMapFile) diff --git a/src/runner/Resources.resx b/src/runner/Resources.resx index bc0c6d1130..801a308b3f 100644 --- a/src/runner/Resources.resx +++ b/src/runner/Resources.resx @@ -94,54 +94,18 @@ An update to PowerToys is available. - - PowerToys download started. - - - An update to PowerToys is ready to install. - - - Error: couldn't download PowerToys installer. Visit our GitHub page to update. - Update now - - At next launch - Error: please uninstall the previous version of PowerToys manually. An update to PowerToys is available. Visit our GitHub page to update. - - PowerToys is up to date. - - - Visit - More info... - - Abort - - - Click Snooze to be reminded in: - - - 1 day - - - 5 days - - - Downloading... - - - Download complete - PowerToys Update @@ -151,9 +115,6 @@ PowerToys: uninstall previous version? - - Snooze - Settings @@ -167,11 +128,4 @@ Bug report .zip file has been created on your Desktop. - - Updating from a local build is not supported. - User cannot autoupdate from a locally-built PowerToys version - - - Failed to connect to the server. Check your network connection or retry later. - diff --git a/src/runner/action_runner_utils.cpp b/src/runner/action_runner_utils.cpp index 64f3ee0400..e4d7344166 100644 --- a/src/runner/action_runner_utils.cpp +++ b/src/runner/action_runner_utils.cpp @@ -17,7 +17,7 @@ SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline) action_runner_path = get_module_folderpath(); } - action_runner_path += L"\\action_runner.exe"; + action_runner_path += L"\\PowerToys.ActionRunner.exe"; SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS }; sei.lpFile = action_runner_path.c_str(); diff --git a/src/runner/action_runner_utils.h b/src/runner/action_runner_utils.h index 13e5353869..c1b5b795a8 100644 --- a/src/runner/action_runner_utils.h +++ b/src/runner/action_runner_utils.h @@ -5,15 +5,19 @@ SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline); -const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG = L"-update_now_and_start_pt"; -const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_CMDARG = L"-update_now"; +namespace cmdArg +{ + // Starts first stage of the PowerToys auto-update process, which involves copying action runner to a temp path and + // restarting it from there, so it doesn't interfere with the installation process. + const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1 = L"-update_now"; + // Stage 2 consists of starting the installer and optionally launching newly installed PowerToys binary. + // That's indicated by the following 2 flags. + const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE2 = L"-update_now_stage_2"; + const inline wchar_t* UPDATE_STAGE2_RESTART_PT = L"restart"; + const inline wchar_t* UPDATE_STAGE2_DONT_START_PT = L"dont_start"; -const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE2_CMDARG = L"-update_now_stage_2"; -const inline wchar_t* UPDATE_STAGE2_RESTART_PT_CMDARG = L"restart"; -const inline wchar_t* UPDATE_STAGE2_DONT_START_PT_CMDARG = L"dont_start"; - -const inline wchar_t * UNINSTALL_MSI_CMDARG = L"-uninstall_msi"; -const inline wchar_t * RUN_NONELEVATED_CMDARG = L"-run-non-elevated"; - -const inline wchar_t* UPDATE_REPORT_SUCCESS = L"-report_update_success"; + const inline wchar_t* UNINSTALL_MSI = L"-uninstall_msi"; + const inline wchar_t* RUN_NONELEVATED = L"-run-non-elevated"; + const inline wchar_t* UPDATE_REPORT_SUCCESS = L"-report_update_success"; +} diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 5c9b34fe3b..bcf37d72c5 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -17,13 +17,13 @@ #include #include #include +#include #include #include #include #include #include -#include "update_state.h" #include "update_utils.h" #include "action_runner_utils.h" @@ -96,7 +96,7 @@ void debug_verify_launcher_assets() const auto assetPath = powertoysRoot / asset; if (!fs::is_regular_file(assetPath)) { - Logger::error("{} couldn't be found.", assetPath.string()); + Logger::error("{} couldn't be found.", assetPath.string()); } } } @@ -125,7 +125,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe) debug_verify_launcher_assets(); std::thread{ [] { - github_update_worker(); + periodic_update_worker(); } }.detach(); if (winstore::running_as_packaged()) @@ -227,7 +227,7 @@ SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_lis { return SpecialMode::ToastNotificationHandler; } - else if (n_cmd_args == 2 && !wcscmp(UPDATE_REPORT_SUCCESS, cmd_arg_list[i])) + else if (n_cmd_args == 2 && !wcscmp(cmdArg::UPDATE_REPORT_SUCCESS, cmd_arg_list[i])) { return SpecialMode::ReportSuccessfulUpdate; } @@ -252,9 +252,7 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ { const std::wstring_view cant_drag_elevated_disable = L"cant_drag_elevated_disable/"; const std::wstring_view couldnt_toggle_powerpreview_modules_disable = L"couldnt_toggle_powerpreview_modules_disable/"; - const std::wstring_view download_and_install_update = L"download_and_install_update/"; const std::wstring_view open_settings = L"open_settings/"; - const std::wstring_view schedule_update = L"schedule_update/"; const std::wstring_view update_now = L"update_now/"; if (param == cant_drag_elevated_disable) @@ -263,46 +261,10 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ } else if (param.starts_with(update_now)) { - std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_CMDARG }; - const auto installerFilename = param.data() + size(update_now); - args += L' '; - args += installerFilename; + std::wstring args{ cmdArg::UPDATE_NOW_LAUNCH_STAGE1 }; launch_action_runner(args.c_str()); return toast_notification_handler_result::exit_success; } - else if (param.starts_with(schedule_update)) - { - const auto installerFilename = param.data() + size(schedule_update); - UpdateState::store([=](UpdateState& state) { - state.pending_update = true; - state.pending_installer_filename = installerFilename; - }); - - return toast_notification_handler_result::exit_success; - } - else if (param.starts_with(download_and_install_update)) - { - try - { - std::wstring installer_filename = updating::download_update(Strings).get(); - - std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_CMDARG }; - args += L' '; - args += installer_filename; - launch_action_runner(args.c_str()); - - return toast_notification_handler_result::exit_success; - } - catch (...) - { - MessageBoxW(nullptr, - GET_RESOURCE_STRING(IDS_DOWNLOAD_UPDATE_ERROR).c_str(), - L"PowerToys", - MB_ICONWARNING | MB_OK); - - return toast_notification_handler_result::exit_error; - } - } else if (param == couldnt_toggle_powerpreview_modules_disable) { return notifications::disable_toast(notifications::PreviewModulesDontShowAgainRegistryPath) ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error; @@ -334,11 +296,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine L"(ML;;NX;;;LW)"; // Integrity label on No execute up for Low mandatory level initializeCOMSecurity(securityDescriptor); - if (launch_pending_update()) - { - return 0; - } - int n_cmd_args = 0; LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args); switch (should_run_in_special_mode(n_cmd_args, cmd_arg_list)) diff --git a/src/runner/pch.h b/src/runner/pch.h index 907a4901a2..06d67b3c8f 100644 --- a/src/runner/pch.h +++ b/src/runner/pch.h @@ -28,6 +28,7 @@ #include #include +#include #include #include diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 9d5ffe7b49..052fe88b61 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -63,7 +63,6 @@ - @@ -74,7 +73,6 @@ - diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters index d532e62952..65337bf1d0 100644 --- a/src/runner/runner.vcxproj.filters +++ b/src/runner/runner.vcxproj.filters @@ -27,9 +27,6 @@ Utils - - Utils - Utils @@ -75,9 +72,6 @@ Utils - - Utils - Utils diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 685b078b5d..84d99cdb59 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -8,9 +8,7 @@ #include #include "tray_icon.h" #include "general_settings.h" -#include #include "restart_elevated.h" -#include "update_state.h" #include "update_utils.h" #include "centralized_kb_hook.h" @@ -23,6 +21,8 @@ #include #include #include +#include +#include #define BUFSIZE 1024 @@ -84,34 +84,16 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec } else if (action == L"check_for_updates") { - if (auto update_check_result = check_for_updates()) - { - VersionHelper latestVersion{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }; - bool isVersionLatest = true; - if (auto new_version = std::get_if(&*update_check_result)) - { - latestVersion = new_version->version; - isVersionLatest = false; - } - json::JsonObject json; - json.SetNamedValue(L"version", json::value(latestVersion.toWstring())); - json.SetNamedValue(L"isVersionLatest", json::value(isVersionLatest)); - - result.emplace(json.Stringify()); - - UpdateState::store([](UpdateState& state) { - state.github_update_last_checked_date.emplace(timeutil::now()); - }); - } + check_for_updates_settings_callback(); } else if (action == L"request_update_state_date") { json::JsonObject json; auto update_state = UpdateState::read(); - if (update_state.github_update_last_checked_date) + if (update_state.githubUpdateLastCheckedDate) { - const time_t date = *update_state.github_update_last_checked_date; + const time_t date = *update_state.githubUpdateLastCheckedDate; json.SetNamedValue(L"updateStateDate", json::value(std::to_wstring(date))); } @@ -336,7 +318,7 @@ void run_settings_window(bool showOobeWindow) // Arg 6: elevated status bool isElevated{ get_general_settings().isElevated }; std::wstring settings_elevatedStatus = isElevated ? L"true" : L"false"; - + // Arg 7: is user an admin bool isAdmin{ get_general_settings().isAdmin }; std::wstring settings_isUserAnAdmin = isAdmin ? L"true" : L"false"; @@ -364,14 +346,14 @@ void run_settings_window(bool showOobeWindow) executable_args.append(settings_isUserAnAdmin); executable_args.append(L" "); executable_args.append(settings_showOobe); - + BOOL process_created = false; if (is_process_elevated()) { // TODO: Revisit this after switching to .NET 5 // Due to a bug in .NET, running the Settings process as non-elevated - // from an elevated process sometimes results in a crash. + // from an elevated process sometimes results in a crash. // process_created = run_settings_non_elevated(executable_path.c_str(), executable_args.data(), &process_info); } diff --git a/src/runner/update_state.cpp b/src/runner/update_state.cpp deleted file mode 100644 index d4ea01aaff..0000000000 --- a/src/runner/update_state.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "pch.h" -#include "update_state.h" - -#include -#include -#include - -namespace -{ - const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json"; - const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToys_Runner_UpdateStateMutex"; -} - -UpdateState deserialize(const json::JsonObject& json) -{ - UpdateState result; - - result.github_update_last_checked_date = timeutil::from_string(json.GetNamedString(L"github_update_last_checked_date", L"invalid").c_str()); - result.pending_update = json.GetNamedBoolean(L"pending_update", false); - result.pending_installer_filename = json.GetNamedString(L"pending_installer_filename", L""); - - return result; -} - -json::JsonObject serialize(const UpdateState& state) -{ - json::JsonObject json; - if (state.github_update_last_checked_date.has_value()) - { - json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*state.github_update_last_checked_date))); - } - json.SetNamedValue(L"pending_update", json::value(state.pending_update)); - json.SetNamedValue(L"pending_installer_filename", json::value(state.pending_installer_filename)); - - return json; -} - -UpdateState UpdateState::read() -{ - const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME; - std::optional json; - { - wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; - auto lock = mutex.acquire(); - json = json::from_file(file_name); - } - return json ? deserialize(*json) : UpdateState{}; -} - -void UpdateState::store(std::function state_modifier) -{ - const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME; - - std::optional json; - { - wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) }; - auto lock = mutex.acquire(); - json = json::from_file(file_name); - UpdateState state; - if (json) - { - state = deserialize(*json); - } - state_modifier(state); - json.emplace(serialize(state)); - json::to_file(file_name, *json); - } -} diff --git a/src/runner/update_state.h b/src/runner/update_state.h deleted file mode 100644 index 8f24de3a7c..0000000000 --- a/src/runner/update_state.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -// All fields must be default-initialized -struct UpdateState -{ - std::optional github_update_last_checked_date; - bool pending_update = false; - std::wstring pending_installer_filename; - - // To prevent concurrent modification of the file, we enforce this interface, which locks the file while - // the state_modifier is active. - static void store(std::function state_modifier); - static UpdateState read(); -}; \ No newline at end of file diff --git a/src/runner/update_utils.cpp b/src/runner/update_utils.cpp index 5a5cec9c54..4c49592dde 100644 --- a/src/runner/update_utils.cpp +++ b/src/runner/update_utils.cpp @@ -3,14 +3,16 @@ #include "Generated Files/resource.h" #include "action_runner_utils.h" -#include "update_state.h" +#include "general_settings.h" #include "update_utils.h" +#include #include +#include #include +#include #include #include -#include auto Strings = create_notifications_strings(); @@ -44,15 +46,80 @@ bool start_msi_uninstallation_sequence() return exit_code == 0; } -void github_update_worker() +using namespace updating; + +bool could_be_costly_connection() +{ + using namespace winrt::Windows::Networking::Connectivity; + ConnectionProfile internetConnectionProfile = NetworkInformation::GetInternetConnectionProfile(); + return internetConnectionProfile && internetConnectionProfile.IsWwanConnectionProfile(); +} + +void process_new_version_info(const github_version_info& version_info, + UpdateState& state, + const bool download_update, + const bool show_notifications) +{ + state.githubUpdateLastCheckedDate.emplace(timeutil::now()); + if (std::holds_alternative(version_info)) + { + state.state = UpdateState::upToDate; + state.releasePageUrl = {}; + state.downloadedInstallerFilename = {}; + Logger::trace(L"Version is up to date"); + return; + } + const auto new_version_info = std::get(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; + } + + if (download_update) + { + Logger::trace(L"Downloading installer for a new version"); + if (download_new_version(new_version_info).get()) + { + state.state = UpdateState::readyToInstall; + state.downloadedInstallerFilename = new_version_info.installer_filename; + if (show_notifications) + { + notifications::show_new_version_available(new_version_info, Strings); + } + } + else + { + state.state = UpdateState::errorDownloading; + state.downloadedInstallerFilename = {}; + 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 = {}; + if (show_notifications) + { + notifications::show_open_settings_for_update(Strings); + } + } +} + +void periodic_update_worker() { for (;;) { auto state = UpdateState::read(); int64_t sleep_minutes_till_next_update = 0; - if (state.github_update_last_checked_date.has_value()) + if (state.githubUpdateLastCheckedDate.has_value()) { - int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date); + int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.githubUpdateLastCheckedDate); if (last_checked_minutes_ago < 0) { last_checked_minutes_ago = UPDATE_CHECK_INTERVAL_MINUTES; @@ -61,22 +128,31 @@ void github_update_worker() } std::this_thread::sleep_for(std::chrono::minutes{ sleep_minutes_till_next_update }); - const bool download_updates_automatically = get_general_settings().downloadUpdatesAutomatically; - bool update_check_ok = false; + + const bool download_update = !could_be_costly_connection() && get_general_settings().downloadUpdatesAutomatically; + bool version_info_obtained = false; try { - update_check_ok = updating::try_autoupdate(download_updates_automatically, Strings).get(); + const auto new_version_info = get_github_version_info_async(Strings).get(); + if (new_version_info.has_value()) + { + version_info_obtained = true; + process_new_version_info(*new_version_info, state, download_update, true); + } + else + { + Logger::error(L"Couldn't obtain version info from github: {}", new_version_info.error()); + } } catch (...) { - // Couldn't autoupdate - update_check_ok = false; + Logger::error("periodic_update_worker: error while processing version info"); } - if (update_check_ok) + if (version_info_obtained) { - UpdateState::store([](UpdateState& state) { - state.github_update_last_checked_date.emplace(timeutil::now()); + UpdateState::store([&](UpdateState& v) { + v = std::move(state); }); } else @@ -86,55 +162,27 @@ void github_update_worker() } } -std::optional check_for_updates() +void check_for_updates_settings_callback() { + Logger::trace(L"Check for updates callback invoked"); + auto state = UpdateState::read(); try { - auto version_check_result = updating::get_github_version_info_async(Strings).get(); - if (!version_check_result) + auto new_version_info = get_github_version_info_async(Strings).get(); + if (!new_version_info) { - updating::notifications::show_unavailable(Strings, std::move(version_check_result.error())); - return std::nullopt; + // If we couldn't get a new version from github for some reason, assume we're up to date, but also log error + new_version_info = version_up_to_date{}; + Logger::error(L"Couldn't obtain version info from github: {}", new_version_info.error()); } - - if (std::holds_alternative(*version_check_result)) - { - updating::notifications::show_unavailable(Strings, Strings.GITHUB_NEW_VERSION_UP_TO_DATE); - return std::move(*version_check_result); - } - - auto new_version = std::get(*version_check_result); - updating::notifications::show_available(new_version, Strings); - return std::move(new_version); + const bool download_update = !could_be_costly_connection() && get_general_settings().downloadUpdatesAutomatically; + process_new_version_info(*new_version_info, state, download_update, false); + UpdateState::store([&](UpdateState& v) { + v = std::move(state); + }); } catch (...) { - // Couldn't autoupdate + Logger::error("check_for_updates_settings_callback: error while processing version info"); } - return std::nullopt; -} - -bool launch_pending_update() -{ - try - { - auto update_state = UpdateState::read(); - if (update_state.pending_update) - { - UpdateState::store([](UpdateState& state) { - state.pending_update = false; - state.pending_installer_filename = {}; - }); - std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG }; - args += L' '; - args += update_state.pending_installer_filename; - - launch_action_runner(args.c_str()); - return true; - } - } - catch (...) - { - } - return false; } diff --git a/src/runner/update_utils.h b/src/runner/update_utils.h index 72e4119853..e1632c7b21 100644 --- a/src/runner/update_utils.h +++ b/src/runner/update_utils.h @@ -3,6 +3,5 @@ #include bool start_msi_uninstallation_sequence(); -void github_update_worker(); -std::optional check_for_updates(); -bool launch_pending_update(); \ No newline at end of file +void periodic_update_worker(); +void check_for_updates_settings_callback(); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/UpdatingSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/UpdatingSettings.cs new file mode 100644 index 0000000000..585bbb972d --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/UpdatingSettings.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.IO; +using System.IO.Abstractions; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class UpdatingSettings + { + public enum UpdatingState + { + UpToDate = 0, + ErrorDownloading, + ReadyToDownload, + ReadyToInstall, + } + + // Gets or sets a value of the updating state + [JsonPropertyName("state")] + public UpdatingState State { get; set; } + + // Gets or sets a value of the release page url + [JsonPropertyName("releasePageUrl")] + public string ReleasePageLink { get; set; } + + // Gets or sets a value of the github last checked date + [JsonPropertyName("githubUpdateLastCheckedDate")] + public string LastCheckedDate { get; set; } + + // Gets or sets a value of the updating state + [JsonPropertyName("downloadedInstallerFilename")] + public string DownloadedInstallerFilename { get; set; } + + // Non-localizable strings: Files + public const string SettingsFilePath = "\\Microsoft\\PowerToys\\"; + public const string SettingsFile = "UpdateState.json"; + + public string NewVersion + { + get + { + if (ReleasePageLink == null) + { + return string.Empty; + } + + try + { + string version = ReleasePageLink.Substring(ReleasePageLink.LastIndexOf('/') + 1); + return version.Trim(); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + } + + return string.Empty; + } + } + + public string LastCheckedDateLocalized + { + get + { + try + { + long seconds = long.Parse(LastCheckedDate, CultureInfo.CurrentCulture); + var date = DateTimeOffset.FromUnixTimeSeconds(seconds).UtcDateTime; + return date.ToLocalTime().ToString(CultureInfo.CurrentCulture); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + } + + return string.Empty; + } + } + + public UpdatingSettings() + { + State = UpdatingState.UpToDate; + } + + public static UpdatingSettings LoadSettings() + { + FileSystem fileSystem = new FileSystem(); + var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var file = localAppDataDir + SettingsFilePath + SettingsFile; + + if (fileSystem.File.Exists(file)) + { + try + { + Stream inputStream = fileSystem.File.Open(file, FileMode.Open); + StreamReader reader = new StreamReader(inputStream); + string data = reader.ReadToEnd(); + inputStream.Close(); + reader.Dispose(); + + return JsonSerializer.Deserialize(data); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + } + } + + return null; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Utilities/Helper.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Utilities/Helper.cs index 22b2964592..b577dc2102 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Utilities/Helper.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Utilities/Helper.cs @@ -75,6 +75,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); } + public static string GetPowerToysInstallationFolder() + { + var settingsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + return Directory.GetParent(settingsPath).FullName; + } + private static readonly interop.LayoutMapManaged LayoutMap = new interop.LayoutMapManaged(); public static string GetKeyName(uint key) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs index 62a6b6133c..bbe68746c4 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/GeneralViewModel.cs @@ -1,9 +1,11 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; using System.Runtime.CompilerServices; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; @@ -16,10 +18,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { private GeneralSettings GeneralSettingsConfig { get; set; } + private UpdatingSettings UpdatingSettingsConfig { get; set; } + public ButtonClickCommand CheckForUpdatesEventHandler { get; set; } public ButtonClickCommand RestartElevatedButtonEventHandler { get; set; } + public ButtonClickCommand UpdateNowButtonEventHandler { get; set; } + public Func UpdateUIThemeCallBack { get; } public Func SendConfigMSG { get; } @@ -34,10 +40,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private string _settingsConfigFileFolder = string.Empty; - public GeneralViewModel(ISettingsRepository settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func updateTheme, Func ipcMSGCallBackFunc, Func ipcMSGRestartAsAdminMSGCallBackFunc, Func ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "") + private IFileSystemWatcher _fileWatcher; + + public GeneralViewModel(ISettingsRepository settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func updateTheme, Func ipcMSGCallBackFunc, Func ipcMSGRestartAsAdminMSGCallBackFunc, Func ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "", Action dispatcherAction = null) { CheckForUpdatesEventHandler = new ButtonClickCommand(CheckForUpdatesClick); RestartElevatedButtonEventHandler = new ButtonClickCommand(RestartElevated); + UpdateNowButtonEventHandler = new ButtonClickCommand(UpdateNowClick); // To obtain the general settings configuration of PowerToys if it exists, else to create a new file and return the default configurations. if (settingsRepository == null) @@ -46,6 +55,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } GeneralSettingsConfig = settingsRepository.SettingsConfig; + UpdatingSettingsConfig = UpdatingSettings.LoadSettings(); + if (UpdatingSettingsConfig == null) + { + UpdatingSettingsConfig = new UpdatingSettings(); + } // set the callback functions value to hangle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; @@ -86,6 +100,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels RunningAsAdminDefaultText = runAsAdminText; _isAdmin = isAdmin; + + _updatingState = UpdatingSettingsConfig.State; + _newAvailableVersion = UpdatingSettingsConfig.NewVersion; + _newAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink; + _updateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized; + + if (dispatcherAction != null) + { + _fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction); + } } private bool _packaged; @@ -98,9 +122,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private bool _isSystemThemeRadioButtonChecked; private bool _autoDownloadUpdates; - private string _latestAvailableVersion = string.Empty; + private UpdatingSettings.UpdatingState _updatingState = UpdatingSettings.UpdatingState.UpToDate; + private string _newAvailableVersion = string.Empty; + private string _newAvailableVersionLink = string.Empty; private string _updateCheckedDate = string.Empty; + private bool _isNewVersionDownloading; + private bool _isNewVersionChecked; + // Gets or sets a value indicating whether packaged. public bool Packaged { @@ -360,24 +389,90 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } - // Temp string. Appears when a user clicks "Check for updates" button and shows latest version available on the Github. - public string LatestAvailableVersion + public UpdatingSettings.UpdatingState PowerToysUpdatingState { get { - return _latestAvailableVersion; + return _updatingState; + } + + private set + { + if (value != _updatingState) + { + _updatingState = value; + NotifyPropertyChanged(); + } + } + } + + public string PowerToysNewAvailableVersion + { + get + { + return _newAvailableVersion; + } + + private set + { + if (value != _newAvailableVersion) + { + _newAvailableVersion = value; + NotifyPropertyChanged(); + } + } + } + + public string PowerToysNewAvailableVersionLink + { + get + { + return _newAvailableVersionLink; + } + + private set + { + if (value != _newAvailableVersionLink) + { + _newAvailableVersionLink = value; + NotifyPropertyChanged(); + } + } + } + + public bool IsNewVersionDownloading + { + get + { + return _isNewVersionDownloading; } set { - if (_latestAvailableVersion != value) + if (value != _isNewVersionDownloading) { - _latestAvailableVersion = value; + _isNewVersionDownloading = value; NotifyPropertyChanged(); } } } + public bool IsNewVersionCheckedAndUpToDate + { + get + { + return _isNewVersionChecked; + } + } + + public bool IsDownloadAllowed + { + get + { + return AutoUpdatesEnabled && !IsNewVersionDownloading; + } + } + public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { // Notify UI of property change @@ -390,6 +485,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels // callback function to launch the URL to check for updates. private void CheckForUpdatesClick() { + IsNewVersionDownloading = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename); + NotifyPropertyChanged(nameof(IsDownloadAllowed)); + + if (_isNewVersionChecked) + { + _isNewVersionChecked = !IsNewVersionDownloading; + NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate)); + } + GeneralSettingsConfig.CustomActionName = "check_for_updates"; OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfig); @@ -398,6 +502,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels SendCheckForUpdatesConfigMSG(customaction.ToString()); } + private void UpdateNowClick() + { + IsNewVersionDownloading = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename); + NotifyPropertyChanged(nameof(IsDownloadAllowed)); + + Process.Start(new ProcessStartInfo(Helper.GetPowerToysInstallationFolder() + "\\PowerToys.exe") { Arguments = "powertoys://update_now/" }); + } + public void RequestUpdateCheckedDate() { GeneralSettingsConfig.CustomActionName = "request_update_state_date"; @@ -417,5 +529,45 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels SendRestartAsAdminConfigMSG(customaction.ToString()); } + + public void RefreshUpdatingState() + { + var config = UpdatingSettings.LoadSettings(); + + // Retry loading if failed + for (int i = 0; i < 3 && config == null; i++) + { + System.Threading.Thread.Sleep(100); + config = UpdatingSettings.LoadSettings(); + } + + if (config == null || config.ToJsonString() == UpdatingSettingsConfig.ToJsonString()) + { + return; + } + + UpdatingSettingsConfig = config; + + if (PowerToysUpdatingState != config.State) + { + IsNewVersionDownloading = false; + } + else + { + bool dateChanged = UpdateCheckedDate == UpdatingSettingsConfig.LastCheckedDateLocalized; + bool fileDownloaded = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename); + IsNewVersionDownloading = !(dateChanged || fileDownloaded); + } + + PowerToysUpdatingState = UpdatingSettingsConfig.State; + PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion; + PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink; + UpdateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized; + + _isNewVersionChecked = PowerToysUpdatingState == UpdatingSettings.UpdatingState.UpToDate && !IsNewVersionDownloading; + NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate)); + + NotifyPropertyChanged(nameof(IsDownloadAllowed)); + } } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateCannotDownloadToVisibilityConverter.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateCannotDownloadToVisibilityConverter.cs new file mode 100644 index 0000000000..26748b6a3f --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateCannotDownloadToVisibilityConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed class UpdatingStateCannotDownloadToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ErrorDownloading ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToDownloadToVisibilityConverter.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToDownloadToVisibilityConverter.cs new file mode 100644 index 0000000000..d809338119 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToDownloadToVisibilityConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed class UpdatingStateReadyToDownloadToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ReadyToDownload ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToInstallToVisibilityConverter.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToInstallToVisibilityConverter.cs new file mode 100644 index 0000000000..c9ab831811 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateReadyToInstallToVisibilityConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed class UpdatingStateReadyToInstallToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ReadyToInstall ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateUpToDateToVisibilityConverter.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateUpToDateToVisibilityConverter.cs new file mode 100644 index 0000000000..c60e261d7a --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Converters/UpdatingStateUpToDateToVisibilityConverter.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.PowerToys.Settings.UI.Library; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.PowerToys.Settings.UI.Converters +{ + public sealed class UpdatingStateUpToDateToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.UpToDate ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 2d865ca040..f2d65009ad 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -106,6 +106,10 @@ ShortcutVisualControl.xaml + + + + Code diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 0e29cf5da1..e70452007d 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -400,6 +400,9 @@ Check for updates + + Update now + Privacy statement @@ -706,10 +709,10 @@ Updates - Version: + Installed version: - Last successfully checked: + Last checked: Version @@ -1194,4 +1197,34 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and Open Shortcut Guide + + An error occurred trying to update to + + + Install now + + + Read more + + + A new version is available: + + + Downloading... + + + Try again to download and install + + + Checking for updates... + + + A new version is ready to install: + + + PowerToys is up to date + + + Download and install + \ No newline at end of file diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml index 0103991272..5e83eac8b2 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" + xmlns:localConverters="using:Microsoft.PowerToys.Settings.UI.Converters" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" @@ -14,6 +15,10 @@ + + + + @@ -62,7 +67,7 @@ AutomationProperties.HeadingLevel="Level2" Style="{StaticResource SubtitleTextBlockStyle}"/> - @@ -116,45 +121,176 @@ IsOn="{Binding Mode=TwoWay, Path=Startup}"/> - - + + + + + + + + + + + + + + + + +