PowerToys/src/runner/main.cpp

427 lines
14 KiB
C++
Raw Normal View History

#include "pch.h"
#include <ShellScalingApi.h>
#include <lmcons.h>
#include <filesystem>
#include "tray_icon.h"
#include "powertoy_module.h"
#include "trace.h"
#include "general_settings.h"
#include "restart_elevated.h"
#include "resource.h"
#include <common/common.h>
#include <common/dpi_aware.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <common/updating/updating.h>
#include <common/RestartManagement.h>
#include "update_state.h"
#include "update_utils.h"
#include "action_runner_utils.h"
#include <winrt/Windows.System.h>
#include <Psapi.h>
#include <RestartManager.h>
#if _DEBUG && _WIN64
#include "unhandled_exception_handler.h"
#endif
#include <common/notifications/fancyzones_notifications.h>
extern "C" IMAGE_DOS_HEADER __ImageBase;
// Window Explorer process name should not be localized.
const wchar_t EXPLORER_PROCESS_NAME[] = L"explorer.exe";
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 PT_UPDATE_MESSAGE_BOX_TITLE[] = L"PowerToys";
const wchar_t PT_UPDATE_MESSAGE_BOX_TEXT[] = L"PowerToys was updated and some components require Windows Explorer to restart. Do you want to restart Windows Explorer now?";
}
namespace
{
const wchar_t MSI_VERSION_MUTEX_NAME[] = L"Local\\PowerToyRunMutex";
const wchar_t MSIX_VERSION_MUTEX_NAME[] = L"Local\\PowerToyMSIXRunMutex";
const wchar_t PT_URI_PROTOCOL_SCHEME[] = L"powertoys://";
}
2019-12-27 00:26:11 +08:00
void chdir_current_executable()
{
// Change current directory to the path of the executable.
WCHAR executable_path[MAX_PATH];
GetModuleFileName(NULL, executable_path, MAX_PATH);
PathRemoveFileSpec(executable_path);
if (!SetCurrentDirectory(executable_path))
{
show_last_error_message(L"Change Directory to Executable Path", GetLastError());
}
}
wil::unique_mutex_nothrow create_runner_mutex(const bool msix_version)
{
wchar_t username[UNLEN + 1];
DWORD username_length = UNLEN + 1;
GetUserNameW(username, &username_length);
wil::unique_mutex_nothrow result{ CreateMutexW(nullptr, TRUE, (std::wstring(msix_version ? MSIX_VERSION_MUTEX_NAME : MSI_VERSION_MUTEX_NAME) + username).c_str()) };
return GetLastError() == ERROR_ALREADY_EXISTS ? wil::unique_mutex_nothrow{} : std::move(result);
}
wil::unique_mutex_nothrow create_msi_mutex()
{
return create_runner_mutex(false);
}
wil::unique_mutex_nothrow create_msix_mutex()
{
return create_runner_mutex(true);
}
void open_menu_from_another_instance()
{
HWND hwnd_main = FindWindow(L"PToyTrayIconWindow", NULL);
PostMessage(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, NULL);
}
int runner(bool isProcessElevated)
2019-12-27 00:26:11 +08:00
{
DPIAware::EnableDPIAwarenessForThisProcess();
#if _DEBUG && _WIN64
//Global error handlers to diagnose errors.
//We prefer this not not show any longer until there's a bug to diagnose.
//init_global_error_handlers();
#endif
Trace::RegisterProvider();
start_tray_icon();
int result = -1;
2019-12-27 00:26:11 +08:00
try
{
std::thread{ [] {
github_update_worker();
} }.detach();
if (winstore::running_as_packaged())
{
std::thread{ [] {
start_msi_uninstallation_sequence();
} }.detach();
}
else
{
std::thread{ [] {
if (updating::uninstall_previous_msix_version_async().get())
{
notifications::show_toast(localized_strings::OLDER_MSIX_UNINSTALLED);
}
} }.detach();
}
notifications::register_background_toast_handler();
2019-12-27 00:26:11 +08:00
chdir_current_executable();
// Load Powertoys DLLs
const std::array<std::wstring_view, 7> knownModules = {
L"modules/FancyZones/fancyzones.dll",
L"modules/FileExplorerPreview/powerpreview.dll",
L"modules/ImageResizer/ImageResizerExt.dll",
L"modules/KeyboardManager/KeyboardManager.dll",
L"modules/Launcher/Microsoft.Launcher.dll",
L"modules/PowerRename/PowerRenameExt.dll",
L"modules/ShortcutGuide/ShortcutGuide.dll",
2019-12-27 00:26:11 +08:00
};
for (const auto & moduleSubdir : knownModules)
2019-12-27 00:26:11 +08:00
{
try
{
auto module = load_powertoy(moduleSubdir);
modules().emplace(module->get_name(), std::move(module));
}
catch (...)
2019-12-27 00:26:11 +08:00
{
}
}
// Start initial powertoys
start_initial_powertoys();
Trace::EventLaunch(get_product_version(), isProcessElevated);
2019-12-27 00:26:11 +08:00
result = run_message_loop();
}
catch (std::runtime_error& err)
{
std::string err_what = err.what();
MessageBoxW(nullptr, std::wstring(err_what.begin(), err_what.end()).c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
2019-12-27 00:26:11 +08:00
result = -1;
}
Trace::UnregisterProvider();
return result;
}
// If the PT runner is launched as part of some action and manually by a user, e.g. being activated as a COM server
// for background toast notification handling, we should execute corresponding code flow instead of the main code flow.
enum class SpecialMode
{
None,
Win32ToastNotificationCOMServer,
ToastNotificationHandler,
ReportSuccessfulUpdate
};
SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_list)
{
for (size_t i = 1; i < n_cmd_args; ++i)
{
if (!wcscmp(notifications::TOAST_ACTIVATED_LAUNCH_ARG, cmd_arg_list[i]))
{
return SpecialMode::Win32ToastNotificationCOMServer;
}
else if (n_cmd_args == 2 && !wcsncmp(PT_URI_PROTOCOL_SCHEME, cmd_arg_list[i], wcslen(PT_URI_PROTOCOL_SCHEME)))
{
return SpecialMode::ToastNotificationHandler;
}
else if (n_cmd_args == 2 && !wcscmp(UPDATE_REPORT_SUCCESS, cmd_arg_list[i]))
{
return SpecialMode::ReportSuccessfulUpdate;
}
}
return SpecialMode::None;
}
int win32_toast_notification_COM_server_mode()
{
notifications::run_desktop_app_activator_loop();
return 0;
}
enum class toast_notification_handler_result
{
exit_success,
exit_error
};
toast_notification_handler_result toast_notification_handler(const std::wstring_view param)
{
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)
{
return disable_cant_drag_elevated_warning() ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
}
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;
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))
{
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;
}
}
void RequestExplorerRestart()
{
if (MessageBox(nullptr,
localized_strings::PT_UPDATE_MESSAGE_BOX_TEXT,
localized_strings::PT_UPDATE_MESSAGE_BOX_TITLE,
MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1) == IDYES)
{
2020-06-11 23:03:02 +08:00
RestartProcess(EXPLORER_PROCESS_NAME);
}
}
2019-12-27 00:26:11 +08:00
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
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))
{
case SpecialMode::Win32ToastNotificationCOMServer:
return win32_toast_notification_COM_server_mode();
case SpecialMode::ToastNotificationHandler:
switch (toast_notification_handler(cmd_arg_list[1] + wcslen(PT_URI_PROTOCOL_SCHEME)))
{
case toast_notification_handler_result::exit_error:
return 1;
case toast_notification_handler_result::exit_success:
return 0;
}
case SpecialMode::ReportSuccessfulUpdate:
RequestExplorerRestart();
break;
case SpecialMode::None:
// continue as usual
break;
}
wil::unique_mutex_nothrow msi_mutex;
wil::unique_mutex_nothrow msix_mutex;
if (winstore::running_as_packaged())
2019-12-27 00:26:11 +08:00
{
msix_mutex = create_msix_mutex();
if (!msix_mutex)
{
// The MSIX version is already running.
open_menu_from_another_instance();
return 0;
}
// Check if the MSI version is running, if not, hold the
// mutex to prevent the old MSI versions to start.
msi_mutex = create_msi_mutex();
if (!msi_mutex)
{
// The MSI version is running, warn the user and offer to uninstall it.
const bool declined_uninstall = !start_msi_uninstallation_sequence();
if (declined_uninstall)
{
// Check again if the MSI version is still running.
msi_mutex = create_msi_mutex();
if (!msi_mutex)
{
open_menu_from_another_instance();
return 0;
}
}
}
else
{
// Older MSI versions are not aware of the MSIX mutex, therefore
// hold the MSI mutex to prevent an old instance to start.
}
}
else
{
// Check if another instance of the MSI version is already running.
msi_mutex = create_msi_mutex();
if (!msi_mutex)
{
// The MSI version is already running.
open_menu_from_another_instance();
return 0;
}
// Check if an instance of the MSIX version is already running.
// Note: this check should always be negative since the MSIX version
// is holding both mutexes.
msix_mutex = create_msix_mutex();
if (!msix_mutex)
{
// The MSIX version is already running.
open_menu_from_another_instance();
return 0;
}
else
{
// The MSIX version isn't running, release the mutex.
msix_mutex.reset(nullptr);
}
}
2019-12-27 00:26:11 +08:00
int result = 0;
try
{
// Singletons initialization order needs to be preserved, first events and
// then modules to guarantee the reverse destruction order.
SystemMenuHelperInstance();
2019-12-27 00:26:11 +08:00
powertoys_events();
modules();
2019-12-27 00:26:11 +08:00
auto general_settings = load_general_settings();
apply_general_settings(general_settings);
2019-12-27 00:26:11 +08:00
int rvalue = 0;
const bool elevated = is_process_elevated();
if ((elevated ||
general_settings.GetNamedBoolean(L"run_elevated", false) == false ||
strcmp(lpCmdLine, "--dont-elevate") == 0))
2019-12-27 00:26:11 +08:00
{
result = runner(elevated);
2019-12-27 00:26:11 +08:00
}
else
{
schedule_restart_as_elevated();
result = 0;
}
}
2019-12-27 00:26:11 +08:00
catch (std::runtime_error& err)
{
std::string err_what = err.what();
MessageBoxW(nullptr, std::wstring(err_what.begin(), err_what.end()).c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR);
2019-12-27 00:26:11 +08:00
result = -1;
}
// We need to release the mutexes to be able to restart the application
if (msi_mutex)
{
msi_mutex.reset(nullptr);
}
if (msix_mutex)
{
msix_mutex.reset(nullptr);
}
2019-12-27 00:26:11 +08:00
if (is_restart_scheduled())
{
if (restart_if_scheduled() == false)
{
auto text = is_process_elevated() ? GET_RESOURCE_STRING(IDS_COULDNOT_RESTART_NONELEVATED) :
GET_RESOURCE_STRING(IDS_COULDNOT_RESTART_ELEVATED);
MessageBoxW(nullptr, text.c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
restart_same_elevation();
2019-12-27 00:26:11 +08:00
result = -1;
}
}
2019-12-27 00:26:11 +08:00
stop_tray_icon();
return result;
}