runner: initial automatic update (#2141)

This commit is contained in:
Andrey Nekrasov
2020-04-21 10:30:12 +03:00
committed by GitHub
parent e9ecdb3f56
commit 0354026292
37 changed files with 735 additions and 326 deletions

View File

@@ -0,0 +1,28 @@
#include "pch.h"
#include "action_runner_utils.h"
#include <common/common.h>
#include <common/winstore.h>
SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline)
{
std::wstring action_runner_path;
if (winstore::running_as_packaged())
{
action_runner_path = winrt::Windows::ApplicationModel::Package::Current().InstalledLocation().Path();
}
else
{
action_runner_path = get_module_folderpath();
}
action_runner_path += L"\\action_runner.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();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = cmdline;
ShellExecuteExW(&sei);
return sei;
}

View File

@@ -0,0 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
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";
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* UPDATE_REPORT_SUCCESS = L"-report_update_success";

View File

@@ -10,8 +10,10 @@
#include "trace.h"
// TODO: would be nice to get rid of these globals, since they're basically cached json settings
static std::wstring settings_theme = L"system";
static bool run_as_elevated = false;
static bool download_updates_automatically = true;
// TODO: add resource.rc for settings project and localize
namespace localized_strings
@@ -40,6 +42,7 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"is_elevated", json::value(isElevated));
result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
result.SetNamedValue(L"download_updates_automatically", json::value(downloadUpdatesAutomatically));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
@@ -57,19 +60,22 @@ json::JsonObject load_general_settings()
settings_theme = L"system";
}
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true);
return loaded;
}
GeneralSettings get_settings()
GeneralSettings get_general_settings()
{
GeneralSettings settings{
.isPackaged = winstore::running_as_packaged(),
.isElevated = is_process_elevated(),
.isRunElevated = run_as_elevated,
.isAdmin = check_user_is_admin(),
.downloadUpdatesAutomatically = download_updates_automatically,
.theme = settings_theme,
.systemTheme = WindowsColors::is_dark_mode() ? L"dark" : L"light",
.powerToysVersion = get_product_version(),
.powerToysVersion = get_product_version()
};
if (winstore::running_as_packaged())
@@ -107,16 +113,12 @@ GeneralSettings get_settings()
return settings;
}
json::JsonObject get_general_settings()
{
auto settings = get_settings();
return settings.to_json();
}
void apply_general_settings(const json::JsonObject& general_configs)
{
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
if (json::has(general_configs, L"startup", json::JsonValueType::Boolean))
{
const bool startup = general_configs.GetNamedBoolean(L"startup");
@@ -192,7 +194,7 @@ void apply_general_settings(const json::JsonObject& general_configs)
settings_theme = general_configs.GetNamedString(L"theme");
}
GeneralSettings save_settings = get_settings();
GeneralSettings save_settings = get_general_settings();
PTSettingsHelper::save_general_settings(save_settings.to_json());
Trace::SettingsChanged(save_settings);
}
@@ -217,7 +219,9 @@ void start_initial_powertoys()
}
}
}
catch (...) { }
catch (...)
{
}
if (powertoys_to_disable.empty())
{

View File

@@ -11,6 +11,7 @@ struct GeneralSettings
bool isElevated;
bool isRunElevated;
bool isAdmin;
bool downloadUpdatesAutomatically;
std::wstring theme;
std::wstring systemTheme;
std::wstring powerToysVersion;
@@ -19,6 +20,6 @@ struct GeneralSettings
};
json::JsonObject load_general_settings();
json::JsonObject get_general_settings();
GeneralSettings get_general_settings();
void apply_general_settings(const json::JsonObject& general_configs);
void start_initial_powertoys();

View File

@@ -13,12 +13,14 @@
#include <common/common.h>
#include <common/dpi_aware.h>
#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 <common/updating/updating.h>
#include "update_state.h"
#include "update_utils.h"
#include "action_runner_utils.h"
#include <winrt/Windows.System.h>
@@ -33,9 +35,6 @@ namespace localized_strings
{
const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running.";
const wchar_t OLDER_MSIX_UNINSTALLED[] = L"An older MSIX version of PowerToys was uninstalled.";
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
@@ -78,78 +77,6 @@ wil::unique_mutex_nothrow create_msix_mutex()
return create_runner_mutex(true);
}
bool start_msi_uninstallation_sequence()
{
const auto package_path = get_msi_package_path();
if (package_path.empty())
{
// No MSI version detected
return true;
}
if (!offer_msi_uninstallation())
{
// User declined to uninstall or opted for "Don't show again"
return false;
}
std::wstring action_runner_path{ winrt::Windows::ApplicationModel::Package::Current().InstalledLocation().Path() };
action_runner_path += L"\\action_runner.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();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = L"-uninstall_msi";
ShellExecuteExW(&sei);
WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(sei.hProcess, &exit_code);
CloseHandle(sei.hProcess);
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(std::move(contents), {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString().c_str() } });
}
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 open_menu_from_another_instance()
{
HWND hwnd_main = FindWindow(L"PToyTrayIconWindow", NULL);
@@ -172,7 +99,7 @@ int runner(bool isProcessElevated)
try
{
std::thread{ [] {
github_update_checking_worker();
github_update_worker();
} }.detach();
if (winstore::running_as_packaged())
@@ -183,13 +110,12 @@ int runner(bool isProcessElevated)
}
else
{
std::thread{[] {
if(uninstall_previous_msix_version_async().get())
std::thread{ [] {
if (updating::uninstall_previous_msix_version_async().get())
{
notifications::show_toast(localized_strings::OLDER_MSIX_UNINSTALLED);
}
}}.detach();
} }.detach();
}
notifications::register_background_toast_handler();
@@ -243,7 +169,8 @@ enum class SpecialMode
{
None,
Win32ToastNotificationCOMServer,
ToastNotificationHandler
ToastNotificationHandler,
ReportSuccessfulUpdate
};
SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_list)
@@ -258,6 +185,10 @@ 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]))
{
return SpecialMode::ReportSuccessfulUpdate;
}
}
return SpecialMode::None;
@@ -281,6 +212,19 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_
{
return disable_cant_drag_elevated_warning() ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
}
else if (param == L"update_now/")
{
launch_action_runner(UPDATE_NOW_LAUNCH_STAGE1_CMDARG);
return toast_notification_handler_result::exit_success;
}
else if (param == L"schedule_update/")
{
UpdateState::store([](UpdateState& state) {
state.pending_update = true;
});
return toast_notification_handler_result::exit_success;
}
else
{
return toast_notification_handler_result::exit_error;
@@ -291,6 +235,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
{
winrt::init_apartment();
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))
@@ -305,6 +254,10 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
case toast_notification_handler_result::exit_success:
return 0;
}
case SpecialMode::ReportSuccessfulUpdate:
notifications::show_toast(GET_RESOURCE_STRING(IDS_AUTOUPDATE_SUCCESS));
break;
case SpecialMode::None:
// continue as usual
break;

View File

@@ -6,6 +6,7 @@
#define IDS_COULDNOT_RESTART_NONELEVATED 105
#define IDS_COULDNOT_RESTART_ELEVATED 106
#define IDS_ANOTHER_INSTANCE_RUNNING 107
#define IDS_AUTOUPDATE_SUCCESS 108
#define ID_EXIT_MENU_COMMAND 40001
#define ID_SETTINGS_MENU_COMMAND 40002

Binary file not shown.

View File

@@ -105,6 +105,7 @@
</Manifest>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="action_runner_utils.cpp" />
<ClCompile Include="auto_start_helper.cpp" />
<ClCompile Include="general_settings.cpp" />
<ClCompile Include="lowlevel_keyboard_event.cpp" />
@@ -121,14 +122,17 @@
<ClCompile Include="trace.cpp" />
<ClCompile Include="tray_icon.cpp" />
<ClCompile Include="unhandled_exception_handler.cpp" />
<ClCompile Include="update_utils.cpp" />
<ClCompile Include="update_state.cpp" />
<ClCompile Include="win_hook_event.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="action_runner_utils.h" />
<ClInclude Include="auto_start_helper.h" />
<ClInclude Include="general_settings.h" />
<ClInclude Include="lowlevel_keyboard_event.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="update_utils.h" />
<ClInclude Include="update_state.h" />
<ClInclude Include="powertoys_events.h" />
<ClInclude Include="powertoy_module.h" />
@@ -234,7 +238,7 @@
<ProjectReference Include="..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
<ProjectReference Include="..\common\msi_to_msix_upgrade_lib\msi_to_msix_upgrade_lib.vcxproj">
<ProjectReference Include="..\common\updating\updating.vcxproj">
<Project>{17da04df-e393-4397-9cf0-84dabe11032e}</Project>
</ProjectReference>
</ItemGroup>

View File

@@ -42,6 +42,12 @@
<ClCompile Include="update_state.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="update_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="action_runner_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -85,6 +91,12 @@
<ClInclude Include="update_state.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="update_utils.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="action_runner_utils.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -40,7 +40,7 @@ json::JsonObject get_all_settings()
{
json::JsonObject result;
result.SetNamedValue(L"general", get_general_settings());
result.SetNamedValue(L"general", get_general_settings().to_json());
result.SetNamedValue(L"powertoys", get_power_toys_settings());
return result;
}
@@ -243,7 +243,7 @@ void run_settings_window()
DWORD powertoys_pid = GetCurrentProcessId();
// Arg 4: settings theme.
const std::wstring settings_theme_setting{ get_general_settings().GetNamedString(L"theme").c_str() };
const std::wstring settings_theme_setting{ get_general_settings().theme };
std::wstring settings_theme;
if (settings_theme_setting == L"dark" || (settings_theme_setting == L"system" && WindowsColors::is_dark_mode()))
{

View File

@@ -55,6 +55,7 @@ void Trace::SettingsChanged(const GeneralSettings& settings)
TraceLoggingWideString(settings.startupDisabledReason.c_str(), "StartupDisabledReason"),
TraceLoggingWideString(enabledModules.c_str(), "ModulesEnabled"),
TraceLoggingBoolean(settings.isRunElevated, "AlwaysRunElevated"),
TraceLoggingBoolean(settings.downloadUpdatesAutomatically, "DownloadUpdatesAutomatically"),
TraceLoggingWideString(settings.theme.c_str(), "Theme"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),

View File

@@ -164,17 +164,16 @@ void start_tray_icon()
{
UINT id_tray_icon = wm_icon_notify = RegisterWindowMessageW(L"WM_PowerToysIconNotify");
static LPCWSTR class_name = L"PToyTrayIconWindow";
WNDCLASS wc = {};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = h_instance;
wc.lpszClassName = class_name;
wc.lpszClassName = pt_tray_icon_window_class;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = tray_icon_window_proc;
wc.hIcon = icon;
RegisterClass(&wc);
auto hwnd = CreateWindowW(wc.lpszClassName,
L"PToyTrayIconWindow",
pt_tray_icon_window_class,
WS_OVERLAPPEDWINDOW | WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,

View File

@@ -9,3 +9,5 @@ void open_settings_window();
typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback
bool dispatch_run_on_main_ui_thread(main_loop_callback_function _callback, PVOID data);
const inline wchar_t* pt_tray_icon_window_class = L"PToyTrayIconWindow";

View File

@@ -8,31 +8,59 @@
namespace
{
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json";
const wchar_t UPDATE_STATE_MUTEX[] = L"PTUpdateStateMutex";
}
UpdateState UpdateState::load()
UpdateState deserialize(const json::JsonObject& json)
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
auto json = json::from_file(file_name);
UpdateState state;
UpdateState result;
if (!json)
{
return state;
}
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);
state.github_update_last_checked_date = timeutil::from_string(json->GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
return state;
return result;
}
void UpdateState::save()
json::JsonObject serialize(const UpdateState& state)
{
json::JsonObject json;
if (github_update_last_checked_date.has_value())
if (state.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)));
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));
return json;
}
UpdateState UpdateState::read()
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> 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<void(UpdateState&)> state_modifier)
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> 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);
}
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
json::to_file(file_name, json);
}

View File

@@ -2,11 +2,16 @@
#include <ctime>
#include <optional>
#include <functional>
// All fields must be default-initialized
struct UpdateState
{
std::optional<std::time_t> github_update_last_checked_date;
bool pending_update = false;
static UpdateState load();
void save();
// 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<void(UpdateState&)> state_modifier);
static UpdateState read();
};

View File

@@ -0,0 +1,90 @@
#include "pch.h"
#include "action_runner_utils.h"
#include "update_state.h"
#include "update_utils.h"
#include <common/timeutil.h>
#include <common/updating/updating.h>
#include <runner/general_settings.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
bool start_msi_uninstallation_sequence()
{
const auto package_path = updating::get_msi_package_path();
if (package_path.empty())
{
// No MSI version detected
return true;
}
if (!updating::offer_msi_uninstallation())
{
// User declined to uninstall or opted for "Don't show again"
return false;
}
auto sei = launch_action_runner(L"-uninstall_msi");
WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(sei.hProcess, &exit_code);
CloseHandle(sei.hProcess);
return exit_code == 0;
}
void github_update_worker()
{
const int64_t update_check_period_minutes = 60 * 24;
for (;;)
{
auto state = UpdateState::read();
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));
const bool download_updates_automatically = get_general_settings().downloadUpdatesAutomatically;
try
{
updating::try_autoupdate(download_updates_automatically).get();
}
catch (...)
{
// Couldn't autoupdate
}
UpdateState::store([](UpdateState& state) {
state.github_update_last_checked_date.emplace(timeutil::now());
});
}
}
bool launch_pending_update()
{
try
{
auto update_state = UpdateState::read();
if (update_state.pending_update)
{
UpdateState::store([](UpdateState& state) {
state.pending_update = false;
});
launch_action_runner(UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG);
return true;
}
}
catch (...)
{
}
return false;
}

View File

@@ -0,0 +1,5 @@
#pragma once
bool start_msi_uninstallation_sequence();
void github_update_worker();
bool launch_pending_update();