mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-30 05:57:42 +00:00
runner: periodically check if there's a new version available on github and offer a visit
This commit is contained in:
parent
c543b7585a
commit
0016836022
@ -107,6 +107,7 @@
|
||||
<ClInclude Include="dpi_aware.h" />
|
||||
<ClInclude Include="com_object_factory.h" />
|
||||
<ClInclude Include="notifications.h" />
|
||||
<ClInclude Include="timeutil.h" />
|
||||
<ClInclude Include="window_helpers.h" />
|
||||
<ClInclude Include="icon_helpers.h" />
|
||||
<ClInclude Include="json.h" />
|
||||
|
@ -90,6 +90,9 @@
|
||||
<ClInclude Include="com_object_factory.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="timeutil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="d2d_svg.cpp">
|
||||
@ -148,4 +151,4 @@
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -3,15 +3,21 @@
|
||||
|
||||
#include <msi.h>
|
||||
#include <common/common.h>
|
||||
#include <common/json.h>
|
||||
|
||||
#include <common/winstore.h>
|
||||
#include <common/notifications.h>
|
||||
#include <MsiQuery.h>
|
||||
|
||||
#include <winrt/Windows.Web.Http.h>
|
||||
#include <winrt/Windows.Web.Http.Headers.h>
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
namespace localized_strings
|
||||
@ -82,3 +88,32 @@ bool uninstall_msi_version(const std::wstring& package_path)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async()
|
||||
{
|
||||
try
|
||||
{
|
||||
winrt::Windows::Web::Http::HttpClient client;
|
||||
auto headers = client.DefaultRequestHeaders();
|
||||
headers.UserAgent().TryParseAdd(USER_AGENT);
|
||||
|
||||
auto response = co_await client.GetAsync(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT });
|
||||
(void)response.EnsureSuccessStatusCode();
|
||||
const auto body = co_await response.Content().ReadAsStringAsync();
|
||||
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") };
|
||||
|
||||
const auto current_version = get_product_version();
|
||||
if (new_version == current_version)
|
||||
{
|
||||
co_return std::nullopt;
|
||||
}
|
||||
|
||||
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
co_return std::nullopt;
|
||||
}
|
||||
}
|
@ -2,7 +2,17 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <future>
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
|
||||
std::wstring get_msi_package_path();
|
||||
bool uninstall_msi_version(const std::wstring& package_path);
|
||||
bool offer_msi_uninstallation();
|
||||
bool offer_msi_uninstallation();
|
||||
|
||||
struct new_version_download_info
|
||||
{
|
||||
winrt::Windows::Foundation::Uri release_page_uri;
|
||||
std::wstring version_string;
|
||||
};
|
||||
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async();
|
||||
|
@ -97,6 +97,7 @@
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@ -134,6 +135,7 @@
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
|
@ -7,6 +7,7 @@
|
||||
namespace PTSettingsHelper {
|
||||
|
||||
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
|
||||
std::wstring get_root_save_folder_location();
|
||||
|
||||
void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings);
|
||||
json::JsonObject load_module_settings(std::wstring_view powertoy_name);
|
||||
|
57
src/common/timeutil.h
Normal file
57
src/common/timeutil.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include <winrt/base.h>
|
||||
|
||||
namespace timeutil
|
||||
{
|
||||
inline std::wstring to_string(const time_t time)
|
||||
{
|
||||
return std::to_wstring(static_cast<uint64_t>(time));
|
||||
}
|
||||
|
||||
inline std::optional<std::time_t> from_string(const std::wstring& s)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint64_t i = std::stoull(s);
|
||||
return static_cast<std::time_t>(i);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::time_t now()
|
||||
{
|
||||
return winrt::clock::to_time_t(winrt::clock::now());
|
||||
}
|
||||
|
||||
namespace diff
|
||||
{
|
||||
inline int64_t in_seconds(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from));
|
||||
}
|
||||
|
||||
inline int64_t in_minutes(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / 60);
|
||||
}
|
||||
|
||||
inline int64_t in_hours(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / 3600);
|
||||
}
|
||||
|
||||
inline int64_t in_days(const std::time_t to, const std::time_t from)
|
||||
{
|
||||
return static_cast<int64_t>(std::difftime(to, from) / (3600 * 24));
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,11 @@
|
||||
#include <common/msi_to_msix_upgrade_lib/msi_to_msix_upgrade.h>
|
||||
#include <common/winstore.h>
|
||||
#include <common/notifications.h>
|
||||
#include <common/timeutil.h>
|
||||
|
||||
#include "update_state.h"
|
||||
|
||||
#include <winrt/Windows.System.h>
|
||||
|
||||
#if _DEBUG && _WIN64
|
||||
#include "unhandled_exception_handler.h"
|
||||
@ -26,6 +31,8 @@ extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
namespace localized_strings
|
||||
{
|
||||
const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running.";
|
||||
const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to get ";
|
||||
const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit";
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -97,6 +104,46 @@ bool start_msi_uninstallation_sequence()
|
||||
return exit_code == 0;
|
||||
}
|
||||
|
||||
std::future<void> check_github_updates()
|
||||
{
|
||||
const auto new_version = co_await check_for_new_github_release_async();
|
||||
if (!new_version)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
using namespace localized_strings;
|
||||
|
||||
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
|
||||
contents += new_version->version_string;
|
||||
contents += L'.';
|
||||
notifications::show_toast_with_activations(contents, {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString() } });
|
||||
}
|
||||
|
||||
void github_update_checking_worker()
|
||||
{
|
||||
const int64_t update_check_period_minutes = 60 * 24;
|
||||
|
||||
auto state = UpdateState::load();
|
||||
for (;;)
|
||||
{
|
||||
int64_t sleep_minutes_till_next_update = 0;
|
||||
if (state.github_update_last_checked_date.has_value())
|
||||
{
|
||||
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date);
|
||||
if (last_checked_minutes_ago < 0)
|
||||
{
|
||||
last_checked_minutes_ago = update_check_period_minutes;
|
||||
}
|
||||
sleep_minutes_till_next_update = max(0, update_check_period_minutes - last_checked_minutes_ago);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::minutes(sleep_minutes_till_next_update));
|
||||
|
||||
check_github_updates().get();
|
||||
state.github_update_last_checked_date.emplace(timeutil::now());
|
||||
state.save();
|
||||
}
|
||||
}
|
||||
void alert_already_running()
|
||||
{
|
||||
MessageBoxW(nullptr,
|
||||
@ -271,12 +318,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
try
|
||||
{
|
||||
|
||||
std::thread{ [] {
|
||||
github_update_checking_worker();
|
||||
} }.detach();
|
||||
|
||||
if (winstore::running_as_packaged())
|
||||
{
|
||||
std::thread{ [] {
|
||||
start_msi_uninstallation_sequence();
|
||||
} }.detach();
|
||||
}
|
||||
|
||||
// Singletons initialization order needs to be preserved, first events and
|
||||
// then modules to guarantee the reverse destruction order.
|
||||
SystemMenuHelperInstace();
|
||||
|
@ -21,7 +21,7 @@
|
||||
<ImportGroup Label="Shared">
|
||||
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
@ -64,6 +64,7 @@
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>..\common\inc;..\common\Telemetry;..;..\modules;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<UACExecutionLevel>AsInvoker</UACExecutionLevel>
|
||||
@ -88,6 +89,7 @@
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>..\common\inc;..\common\Telemetry;..;..\modules;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
@ -117,6 +119,7 @@
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="tray_icon.cpp" />
|
||||
<ClCompile Include="unhandled_exception_handler.cpp" />
|
||||
<ClCompile Include="update_state.cpp" />
|
||||
<ClCompile Include="win_hook_event.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -124,6 +127,7 @@
|
||||
<ClInclude Include="general_settings.h" />
|
||||
<ClInclude Include="lowlevel_keyboard_event.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="update_state.h" />
|
||||
<ClInclude Include="powertoys_events.h" />
|
||||
<ClInclude Include="powertoy_module.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
|
@ -39,6 +39,9 @@
|
||||
<ClCompile Include="restart_elevated.cpp">
|
||||
<Filter>Utils</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="update_state.cpp">
|
||||
<Filter>Utils</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
@ -79,6 +82,9 @@
|
||||
<ClInclude Include="restart_elevated.h">
|
||||
<Filter>Utils</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="update_state.h">
|
||||
<Filter>Utils</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Utils">
|
||||
|
38
src/runner/update_state.cpp
Normal file
38
src/runner/update_state.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "pch.h"
|
||||
#include "update_state.h"
|
||||
|
||||
#include <common/json.h>
|
||||
#include <common/timeutil.h>
|
||||
#include <common/settings_helpers.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json";
|
||||
}
|
||||
|
||||
UpdateState UpdateState::load()
|
||||
{
|
||||
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
|
||||
auto json = json::from_file(file_name);
|
||||
UpdateState state;
|
||||
|
||||
if (!json)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
state.github_update_last_checked_date = timeutil::from_string(json->GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void UpdateState::save()
|
||||
{
|
||||
json::JsonObject json;
|
||||
if (github_update_last_checked_date.has_value())
|
||||
{
|
||||
json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*github_update_last_checked_date)));
|
||||
}
|
||||
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
|
||||
json::to_file(file_name, json);
|
||||
}
|
12
src/runner/update_state.h
Normal file
12
src/runner/update_state.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <optional>
|
||||
|
||||
struct UpdateState
|
||||
{
|
||||
std::optional<std::time_t> github_update_last_checked_date;
|
||||
|
||||
static UpdateState load();
|
||||
void save();
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user