diff --git a/src/action_runner/action_runner.cpp b/src/action_runner/action_runner.cpp index 0b4cb6bc4e..fe1ea4ac73 100644 --- a/src/action_runner/action_runner.cpp +++ b/src/action_runner/action_runner.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -164,7 +165,8 @@ bool install_dotnet() { try { - updating::try_download_file(dotnet_download_path, download_link).wait(); + http::HttpClient client; + client.download(download_link, dotnet_download_path).wait(); download_success = true; break; } diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp index de55ba5334..06e9d52643 100644 --- a/src/common/notifications.cpp +++ b/src/common/notifications.cpp @@ -36,6 +36,10 @@ namespace namespace localized_strings { constexpr std::wstring_view SNOOZE_BUTTON = L"Snooze"; + + constexpr std::wstring_view PT_UPDATE = L"PowerToys update"; + constexpr std::wstring_view DOWNLOAD_IN_PROGRESS = L"Downloading..."; + constexpr std::wstring_view DOWNLOAD_COMPLETE = L"Download complete"; } static DWORD loop_thread_id() @@ -204,7 +208,19 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri toast_xml += title; toast_xml += L""; toast_xml += message; - toast_xml += L""; + toast_xml += L""; + if (params.progress) + { + toast_xml += LR"()"; + } + toast_xml += L""; for (size_t i = 0; i < size(actions); ++i) { std::visit(overloaded{ @@ -294,6 +310,17 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri toast_xml_doc.LoadXml(toast_xml); ToastNotification notification{ toast_xml_doc }; + if (params.progress) + { + float progress = std::clamp(params.progress.value(), 0.0f, 1.0f); + winrt::Windows::Foundation::Collections::StringMap map; + map.Insert(L"progressValue", std::to_wstring(progress)); + map.Insert(L"progressValueString", std::to_wstring(static_cast(progress * 100)) + std::wstring(L"%")); + map.Insert(L"progressStatus", localized_strings::DOWNLOAD_IN_PROGRESS); + winrt::Windows::UI::Notifications::NotificationData data(map); + notification.Data(data); + } + const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() : ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID); @@ -315,3 +342,30 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri notifier.Show(notification); } + +void notifications::update_progress_bar_toast(std::wstring plaintext_message, toast_params params) +{ + if (!params.progress.has_value()) + { + return; + } + + const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() : + ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID); + + float progress = std::clamp(params.progress.value(), 0.0f, 1.0f); + winrt::Windows::Foundation::Collections::StringMap map; + map.Insert(L"progressValue", std::to_wstring(progress)); + map.Insert(L"progressValueString", std::to_wstring(static_cast(progress * 100)) + std::wstring(L"%")); + map.Insert(L"progressStatus", progress < 1 ? localized_strings::DOWNLOAD_IN_PROGRESS : localized_strings::DOWNLOAD_COMPLETE); + + + winrt::Windows::UI::Notifications::NotificationData data(map); + std::wstring tag = L""; + if (params.tag.has_value() && params.tag->length() < 64) + { + tag = *params.tag; + } + + winrt::Windows::UI::Notifications::NotificationUpdateResult res = notifier.Update(data, tag); +} diff --git a/src/common/notifications.h b/src/common/notifications.h index b4f57e5398..5e2b57918d 100644 --- a/src/common/notifications.h +++ b/src/common/notifications.h @@ -43,10 +43,13 @@ namespace notifications { std::optional tag; bool resend_if_scheduled = true; + std::optional progress; + std::optional subtitle; }; using action_t = std::variant; void show_toast(std::wstring plaintext_message, toast_params params = {}); void show_toast_with_activations(std::wstring plaintext_message, std::wstring_view background_handler_id, std::vector actions, toast_params params = {}); + void update_progress_bar_toast(std::wstring plaintext_message, toast_params params); } diff --git a/src/common/updating/http_client.cpp b/src/common/updating/http_client.cpp new file mode 100644 index 0000000000..951d841d8b --- /dev/null +++ b/src/common/updating/http_client.cpp @@ -0,0 +1,72 @@ +#include "pch.h" +#include "http_client.h" + +#include +#include +#include + +namespace http +{ + using namespace winrt::Windows::Web::Http; + namespace storage = winrt::Windows::Storage; + + const wchar_t USER_AGENT[] = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; + + HttpClient::HttpClient() + { + auto headers = m_client.DefaultRequestHeaders(); + headers.UserAgent().TryParseAdd(USER_AGENT); + } + + std::future HttpClient::request(const winrt::Windows::Foundation::Uri& url) + { + auto response = co_await m_client.GetAsync(url); + (void)response.EnsureSuccessStatusCode(); + auto body = co_await response.Content().ReadAsStringAsync(); + co_return std::wstring(body); + } + + std::future HttpClient::download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath) + { + auto response = co_await m_client.GetAsync(url); + (void)response.EnsureSuccessStatusCode(); + auto file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(dstFilePath.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); + co_await response.Content().WriteToStreamAsync(file_stream); + file_stream.Close(); + } + + std::future HttpClient::download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath, const std::function& progressUpdateCallback) + { + auto response = co_await m_client.GetAsync(url, HttpCompletionOption::ResponseHeadersRead); + response.EnsureSuccessStatusCode(); + + uint64_t totalBytes = response.Content().Headers().ContentLength().GetUInt64(); + auto contentStream = co_await response.Content().ReadAsInputStreamAsync(); + + uint64_t totalBytesRead = 0; + storage::Streams::Buffer buffer(8192); + auto fileStream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(dstFilePath.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); + + co_await contentStream.ReadAsync(buffer, buffer.Capacity(), storage::Streams::InputStreamOptions::None); + while (buffer.Length() > 0) + { + co_await fileStream.WriteAsync(buffer); + totalBytesRead += buffer.Length(); + if (progressUpdateCallback) + { + float percentage = (float)totalBytesRead / totalBytes; + progressUpdateCallback(percentage); + } + + co_await contentStream.ReadAsync(buffer, buffer.Capacity(), storage::Streams::InputStreamOptions::None); + } + + if (progressUpdateCallback) + { + progressUpdateCallback(1); + } + + fileStream.Close(); + contentStream.Close(); + } +} \ No newline at end of file diff --git a/src/common/updating/http_client.h b/src/common/updating/http_client.h new file mode 100644 index 0000000000..396c61271b --- /dev/null +++ b/src/common/updating/http_client.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace http +{ + class HttpClient + { + public: + HttpClient(); + std::future request(const winrt::Windows::Foundation::Uri& url); + std::future download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFle); + std::future download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFle, const std::function& progressUpdateCallback); + + private: + winrt::Windows::Web::Http::HttpClient m_client; + }; +} diff --git a/src/common/updating/toast_notifications_helper.cpp b/src/common/updating/toast_notifications_helper.cpp new file mode 100644 index 0000000000..c6d6de2fce --- /dev/null +++ b/src/common/updating/toast_notifications_helper.cpp @@ -0,0 +1,127 @@ +#include "pch.h" + +#include "toast_notifications_helper.h" + +#include + +#include "updating.h" + +#include "VersionHelper.h" +#include "version.h" + +namespace +{ + const wchar_t UPDATE_NOTIFY_TOAST_TAG[] = L"PTUpdateNotifyTag"; + const wchar_t UPDATE_READY_TOAST_TAG[] = L"PTUpdateReadyTag"; +} + +namespace localized_strings +{ + const wchar_t GITHUB_NEW_VERSION_AVAILABLE[] = L"An update to PowerToys is available.\n"; + const wchar_t GITHUB_NEW_VERSION_DOWNLOAD_STARTED[] = L"PowerToys download started.\n"; + const wchar_t GITHUB_NEW_VERSION_READY_TO_INSTALL[] = L"An update to PowerToys is ready to install.\n"; + const wchar_t GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR[] = L"Error: couldn't download PowerToys installer. Visit our GitHub page to update.\n"; + const wchar_t GITHUB_NEW_VERSION_UPDATE_NOW[] = L"Update now"; + const wchar_t GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART[] = L"At next launch"; + + const wchar_t UNINSTALLATION_SUCCESS[] = L"Previous version of PowerToys was uninstalled successfully."; + const wchar_t UNINSTALLATION_UNKNOWN_ERROR[] = L"Error: please uninstall the previous version of PowerToys manually."; + + const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to update.\n"; + const wchar_t GITHUB_NEW_VERSION_UNAVAILABLE[] = L"PowerToys is up to date.\n"; + const wchar_t GITHUB_NEW_VERSION_VISIT[] = L"Visit"; + const wchar_t GITHUB_NEW_VERSION_MORE_INFO[] = L"More info..."; + const wchar_t GITHUB_NEW_VERSION_ABORT[] = L"Abort"; + const wchar_t GITHUB_NEW_VERSION_SNOOZE_TITLE[] = L"Click Snooze to be reminded in:"; + const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D[] = L"1 day"; + const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D[] = L"5 days"; +} + +namespace updating +{ + namespace notifications + { + using namespace localized_strings; + + std::wstring current_version_to_next_version(const updating::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" -> "; + current_version_to_next_version += info.version_string; + return current_version_to_next_version; + } + + void show_unavailable() + { + ::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; + std::wstring contents = GITHUB_NEW_VERSION_UNAVAILABLE; + ::notifications::show_toast(std::move(contents), std::move(toast_params)); + } + + void show_available(const updating::new_version_download_info& info) + { + ::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; + std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE; + contents += current_version_to_next_version(info); + + ::notifications::show_toast_with_activations(std::move(contents), {}, + { + ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://download_and_install_update/" }, + ::notifications::link_button{ GITHUB_NEW_VERSION_MORE_INFO, info.release_page_uri.ToString().c_str() } + }, + std::move(toast_params)); + } + + void show_download_start(const updating::new_version_download_info& info) + { + ::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false, 0.0f, info.version_string }; + ::notifications::show_toast_with_activations(localized_strings::GITHUB_NEW_VERSION_DOWNLOAD_STARTED, {}, {}, std::move(toast_params)); + } + + void show_visit_github(const updating::new_version_download_info& info) + { + ::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; + std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT; + contents += current_version_to_next_version(info); + ::notifications::show_toast_with_activations(std::move(contents), {}, { ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } }, std::move(toast_params)); + } + + void show_install_error(const updating::new_version_download_info& info) + { + ::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; + std::wstring contents = GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR; + contents += current_version_to_next_version(info); + ::notifications::show_toast_with_activations(std::move(contents), {}, { ::notifications::link_button{ 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) + { + ::notifications::toast_params toast_params{ UPDATE_READY_TOAST_TAG, false }; + std::wstring new_version_ready{ GITHUB_NEW_VERSION_READY_TO_INSTALL }; + new_version_ready += current_version_to_next_version(info); + + ::notifications::show_toast_with_activations(std::move(new_version_ready), + {}, + { ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" + info.installer_filename }, + ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" + info.installer_filename }, + ::notifications::snooze_button{ GITHUB_NEW_VERSION_SNOOZE_TITLE, { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } }, + std::move(toast_params)); + } + + void show_uninstallation_success() + { + ::notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS); + } + + void show_uninstallation_error() + { + ::notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR); + } + + void update_download_progress(float progress) + { + ::notifications::toast_params toast_params { UPDATE_NOTIFY_TOAST_TAG, false, progress }; + ::notifications::update_progress_bar_toast(localized_strings::GITHUB_NEW_VERSION_DOWNLOAD_STARTED, std::move(toast_params)); + } + } +} \ No newline at end of file diff --git a/src/common/updating/toast_notifications_helper.h b/src/common/updating/toast_notifications_helper.h new file mode 100644 index 0000000000..f582d36e1f --- /dev/null +++ b/src/common/updating/toast_notifications_helper.h @@ -0,0 +1,20 @@ +#pragma once + +namespace updating +{ + struct new_version_download_info; + + namespace notifications + { + void show_unavailable(); + void show_available(const updating::new_version_download_info& info); + void show_download_start(const updating::new_version_download_info& info); + void show_visit_github(const updating::new_version_download_info& info); + void show_install_error(const updating::new_version_download_info& info); + void show_version_ready(const updating::new_version_download_info& info); + void show_uninstallation_success(); + void show_uninstallation_error(); + + void update_download_progress(float progress); + } +} \ No newline at end of file diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index e929fba9d5..d2587fa7e0 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -2,7 +2,9 @@ #include "version.h" +#include "http_client.h" #include "updating.h" +#include "toast_notifications_helper.h" #include #include @@ -11,9 +13,6 @@ #include #include -#include -#include -#include #include #include @@ -23,13 +22,10 @@ namespace { const wchar_t POWER_TOYS_UPGRADE_CODE[] = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; const wchar_t DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH[] = L"delete_previous_powertoys_confirm"; - const wchar_t USER_AGENT[] = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest"; const wchar_t MSIX_PACKAGE_NAME[] = L"Microsoft.PowerToys"; const wchar_t MSIX_PACKAGE_PUBLISHER[] = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; - const wchar_t UPDATE_NOTIFY_TOAST_TAG[] = L"PTUpdateNotifyTag"; - const wchar_t UPDATE_READY_TOAST_TAG[] = L"PTUpdateReadyTag"; const size_t MAX_DOWNLOAD_ATTEMPTS = 3; } @@ -37,30 +33,10 @@ namespace localized_strings { const wchar_t OFFER_UNINSTALL_MSI[] = L"We've detected a previous installation of PowerToys. Would you like to remove it?"; const wchar_t OFFER_UNINSTALL_MSI_TITLE[] = L"PowerToys: uninstall previous version?"; - const wchar_t UNINSTALLATION_SUCCESS[] = L"Previous version of PowerToys was uninstalled successfully."; - const wchar_t UNINSTALLATION_UNKNOWN_ERROR[] = L"Error: please uninstall the previous version of PowerToys manually."; - - const wchar_t GITHUB_NEW_VERSION_READY_TO_INSTALL[] = L"An update to PowerToys is ready to install.\n"; - const wchar_t GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR[] = L"Error: couldn't download PowerToys installer. Visit our GitHub page to update.\n"; - const wchar_t GITHUB_NEW_VERSION_UPDATE_NOW[] = L"Update now"; - const wchar_t GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART[] = L"At next launch"; - - const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to update.\n"; - const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit"; - const wchar_t GITHUB_NEW_VERSION_SNOOZE_TITLE[] = L"Click Snooze to be reminded in:"; - const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D[] = L"1 day"; - const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D[] = L"5 days"; } + namespace updating { - inline winrt::Windows::Web::Http::HttpClient create_http_client() - { - winrt::Windows::Web::Http::HttpClient client; - auto headers = client.DefaultRequestHeaders(); - headers.UserAgent().TryParseAdd(USER_AGENT); - return client; - } - std::wstring get_msi_package_path() { std::wstring package_path; @@ -93,8 +69,8 @@ namespace updating return package_path; } - bool offer_msi_uninstallation() + bool offer_msi_uninstallation() { const auto selection = SHMessageBoxCheckW(nullptr, localized_strings::OFFER_UNINSTALL_MSI, localized_strings::OFFER_UNINSTALL_MSI_TITLE, MB_ICONQUESTION | MB_YESNO, IDNO, DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH); return selection == IDYES; @@ -105,18 +81,18 @@ namespace updating const auto uninstall_result = MsiInstallProductW(package_path.c_str(), L"REMOVE=ALL"); if (ERROR_SUCCESS == uninstall_result) { - notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS); + notifications::show_uninstallation_success(); return true; } else if (auto system_message = get_last_error_message(uninstall_result); system_message.has_value()) { try { - notifications::show_toast(*system_message); + ::notifications::show_toast(*system_message); } catch (...) { - notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR); + updating::notifications::show_uninstallation_error(); } } return false; @@ -126,10 +102,8 @@ namespace updating { try { - auto client = create_http_client(); - auto response = co_await client.GetAsync(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT }); - (void)response.EnsureSuccessStatusCode(); - const auto body = co_await response.Content().ReadAsStringAsync(); + http::HttpClient client; + const auto body = co_await client.request(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT }); auto json_body = json::JsonValue::Parse(body).GetObjectW(); auto new_version = json_body.GetNamedString(L"tag_name"); winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") }; @@ -213,16 +187,12 @@ namespace updating return { std::move(path_str) }; } - std::future try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url) + std::filesystem::path create_download_path() { - namespace storage = winrt::Windows::Storage; - - auto client = create_http_client(); - auto response = co_await client.GetAsync(url); - (void)response.EnsureSuccessStatusCode(); - auto file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(destination.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); - co_await response.Content().WriteToStreamAsync(file_stream); - file_stream.Close(); + auto installer_download_dst = get_pending_updates_path(); + std::error_code _; + std::filesystem::create_directories(installer_download_dst, _); + return installer_download_dst; } std::future try_autoupdate(const bool download_updates_automatically) @@ -232,24 +202,17 @@ namespace updating { co_return; } - using namespace localized_strings; - auto current_version_to_next_version = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring(); - current_version_to_next_version += L" -> "; - current_version_to_next_version += new_version->version_string; - + if (download_updates_automatically && !could_be_costly_connection()) { - auto installer_download_dst = get_pending_updates_path(); - std::error_code _; - std::filesystem::create_directories(installer_download_dst, _); - installer_download_dst /= new_version->installer_filename; - + 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 { - co_await try_download_file(installer_download_dst, new_version->installer_download_url); + http::HttpClient client; + co_await client.download(new_version->installer_download_url, installer_download_dst); download_success = true; break; } @@ -260,32 +223,56 @@ namespace updating } if (!download_success) { - notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; - std::wstring contents = GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR; - contents += current_version_to_next_version; - - notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } }, std::move(toast_params)); - + updating::notifications::show_install_error(new_version.value()); co_return; } - notifications::toast_params toast_params{ UPDATE_READY_TOAST_TAG, false }; - std::wstring new_version_ready{ GITHUB_NEW_VERSION_READY_TO_INSTALL }; - new_version_ready += current_version_to_next_version; - - notifications::show_toast_with_activations(std::move(new_version_ready), - {}, - { notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" + new_version->installer_filename }, - notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" + new_version->installer_filename }, - notifications::snooze_button{ GITHUB_NEW_VERSION_SNOOZE_TITLE, { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } }, - std::move(toast_params)); + updating::notifications::show_version_ready(new_version.value()); } else { - notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false }; - std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT; - contents += current_version_to_next_version; - notifications::show_toast_with_activations(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } }, std::move(toast_params)); + updating::notifications::show_visit_github(new_version.value()); } } + + std::future check_new_version_available() + { + const auto new_version = co_await get_new_github_version_info_async(); + if (!new_version) + { + updating::notifications::show_unavailable(); + co_return; + } + + updating::notifications::show_available(new_version.value()); + } + + std::future download_update() + { + const auto new_version = co_await get_new_github_version_info_async(); + if (!new_version) + { + co_return L""; + } + + auto installer_download_dst = create_download_path() / new_version->installer_filename; + updating::notifications::show_download_start(new_version.value()); + + try + { + auto progressUpdateHandle = [](float progress) { + updating::notifications::update_download_progress(progress); + }; + + http::HttpClient client; + co_await client.download(new_version->installer_download_url, installer_download_dst, progressUpdateHandle); + } + catch (...) + { + updating::notifications::show_install_error(new_version.value()); + co_return L""; + } + + co_return new_version->installer_filename; + } } \ No newline at end of file diff --git a/src/common/updating/updating.h b/src/common/updating/updating.h index 2f0716e1f2..53ae0e6b3e 100644 --- a/src/common/updating/updating.h +++ b/src/common/updating/updating.h @@ -9,8 +9,6 @@ namespace updating { - std::future try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url); - std::wstring get_msi_package_path(); bool uninstall_msi_version(const std::wstring& package_path); bool offer_msi_uninstallation(); @@ -29,5 +27,8 @@ namespace updating std::future try_autoupdate(const bool download_updates_automatically); std::filesystem::path get_pending_updates_path(); + std::future check_new_version_available(); + std::future download_update(); + constexpr inline std::wstring_view installer_filename_pattern = L"powertoyssetup"; } \ No newline at end of file diff --git a/src/common/updating/updating.vcxproj b/src/common/updating/updating.vcxproj index cc8da9aa30..796e5275e6 100644 --- a/src/common/updating/updating.vcxproj +++ b/src/common/updating/updating.vcxproj @@ -169,10 +169,14 @@ + + + + Create diff --git a/src/common/updating/updating.vcxproj.filters b/src/common/updating/updating.vcxproj.filters index 6523291f59..0dc2139e62 100644 --- a/src/common/updating/updating.vcxproj.filters +++ b/src/common/updating/updating.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -21,6 +21,12 @@ Header Files + + Header Files + + + Header Files + @@ -29,6 +35,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs index ab6628d978..6155c83b3a 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs @@ -50,6 +50,12 @@ namespace Microsoft.PowerToys.Settings.UI.Runner System.Windows.Application.Current.Shutdown(); // close application }); + // send IPC Message + shellPage.SetCheckForUpdatesMessageCallback(msg => + { + Program.GetTwoWayIPCManager().Send(msg); + }); + shellPage.SetElevationStatus(Program.IsElevated); shellPage.SetIsUserAnAdmin(Program.IsUserAnAdmin); shellPage.Refresh(); diff --git a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/GeneralViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/GeneralViewModel.cs index 9d3e7219d9..f63c341e2d 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/GeneralViewModel.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/ViewModels/GeneralViewModel.cs @@ -352,7 +352,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // callback function to launch the URL to check for updates. private async void CheckForUpdates_Click() { - await Launcher.LaunchUriAsync(new Uri("https://github.com/microsoft/PowerToys/releases")); + GeneralSettings settings = SettingsUtils.GetSettings(string.Empty); + settings.CustomActionName = "check_for_updates"; + + OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(settings); + GeneralSettingsCustomAction customaction = new GeneralSettingsCustomAction(outsettings); + + if (ShellPage.CheckForUpdatesMsgCallback != null) + { + ShellPage.CheckForUpdatesMsgCallback(customaction.ToString()); + } } public void Restart_Elevated() diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs index 5ec69bb06d..140b2b3f31 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs @@ -33,6 +33,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// public static IPCMessageCallback SndRestartAsAdminMsgCallback { get; set; } + /// + /// Gets or sets iPC callback function for checking updates. + /// + public static IPCMessageCallback CheckForUpdatesMsgCallback { get; set; } + /// /// Gets view model. /// @@ -74,6 +79,15 @@ namespace Microsoft.PowerToys.Settings.UI.Views SndRestartAsAdminMsgCallback = implementation; } + /// + /// Set check for updates IPC callback function. + /// + /// delegate function implementation. + public void SetCheckForUpdatesMessageCallback(IPCMessageCallback implementation) + { + CheckForUpdatesMsgCallback = implementation; + } + public void SetElevationStatus(bool isElevated) { IsElevated = isElevated; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 0504df2661..bd6ef5000e 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -216,6 +216,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 update_now = L"update_now/"; const std::wstring_view schedule_update = L"schedule_update/"; + const std::wstring_view download_and_install_update = L"download_and_install_update/"; if (param == cant_drag_elevated_disable) { @@ -240,6 +241,17 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ return toast_notification_handler_result::exit_success; } + else if (param.starts_with(download_and_install_update)) + { + std::wstring installer_filename = updating::download_update().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; + } else { return toast_notification_handler_result::exit_error; diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 21d25e581b..a9649b09c0 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -11,6 +11,7 @@ #include "common/windows_colors.h" #include "common/common.h" #include "restart_elevated.h" +#include "update_utils.h" #include #include @@ -56,15 +57,32 @@ void dispatch_json_action_to_module(const json::JsonObject& powertoys_configs) // so it has to be the "restart as (non-)elevated" button. if (name == L"general") { - if (is_process_elevated()) + try { - schedule_restart_as_non_elevated(); - PostQuitMessage(0); + const auto value = powertoy_element.Value().GetObjectW(); + const auto action = value.GetNamedString(L"action_name"); + if (action == L"restart_elevation") + { + if (is_process_elevated()) + { + schedule_restart_as_non_elevated(); + PostQuitMessage(0); + } + else + { + schedule_restart_as_elevated(); + PostQuitMessage(0); + } + } + else if (action == L"check_for_updates") + { + std::thread{ [] { + check_for_updates(); + } }.detach(); + } } - else + catch (...) { - schedule_restart_as_elevated(); - PostQuitMessage(0); } } else if (modules().find(name) != modules().end()) diff --git a/src/runner/update_utils.cpp b/src/runner/update_utils.cpp index 266782afe1..db42b56fcc 100644 --- a/src/runner/update_utils.cpp +++ b/src/runner/update_utils.cpp @@ -66,6 +66,18 @@ void github_update_worker() } } +void check_for_updates() +{ + try + { + updating::check_new_version_available(); + } + catch (...) + { + // Couldn't autoupdate + } +} + bool launch_pending_update() { try diff --git a/src/runner/update_utils.h b/src/runner/update_utils.h index 1fc90eb966..fa0cb3d6a1 100644 --- a/src/runner/update_utils.h +++ b/src/runner/update_utils.h @@ -2,4 +2,5 @@ bool start_msi_uninstallation_sequence(); void github_update_worker(); +void check_for_updates(); bool launch_pending_update(); \ No newline at end of file