2019-09-05 00:26:26 +08:00
|
|
|
#include "pch.h"
|
|
|
|
#include <ShellScalingApi.h>
|
|
|
|
#include <lmcons.h>
|
|
|
|
#include <filesystem>
|
|
|
|
#include "tray_icon.h"
|
|
|
|
#include "powertoy_module.h"
|
|
|
|
#include "lowlevel_keyboard_event.h"
|
|
|
|
#include "trace.h"
|
|
|
|
#include "general_settings.h"
|
2019-12-17 01:36:52 +08:00
|
|
|
#include "restart_elevated.h"
|
|
|
|
#include "resource.h"
|
2019-09-05 00:26:26 +08:00
|
|
|
|
2019-12-17 16:21:46 +08:00
|
|
|
#include <common/common.h>
|
2019-11-08 02:56:32 +08:00
|
|
|
#include <common/dpi_aware.h>
|
|
|
|
|
2020-02-18 23:11:01 +08:00
|
|
|
#include <common/msi_to_msix_upgrade_lib/msi_to_msix_upgrade.h>
|
2020-02-05 00:41:00 +08:00
|
|
|
#include <common/winstore.h>
|
|
|
|
#include <common/notifications.h>
|
|
|
|
|
2019-09-05 00:26:26 +08:00
|
|
|
#if _DEBUG && _WIN64
|
|
|
|
#include "unhandled_exception_handler.h"
|
|
|
|
#endif
|
|
|
|
|
2019-12-17 01:36:52 +08:00
|
|
|
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
|
|
|
|
2020-02-18 23:11:01 +08:00
|
|
|
namespace localized_strings
|
|
|
|
{
|
|
|
|
const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running.";
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
const wchar_t MSI_VERSION_MUTEX_NAME[] = L"Local\\PowerToyRunMutex";
|
|
|
|
const wchar_t MSIX_VERSION_MUTEX_NAME[] = L"Local\\PowerToyMSIXRunMutex";
|
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
}
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
|
|
|
|
2020-02-18 23:11:01 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-02-21 18:12:04 +08:00
|
|
|
wil::unique_mutex_nothrow create_msi_mutex()
|
|
|
|
{
|
|
|
|
return create_runner_mutex(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
wil::unique_mutex_nothrow create_msix_mutex()
|
|
|
|
{
|
|
|
|
return create_runner_mutex(true);
|
|
|
|
}
|
|
|
|
|
2020-02-18 23:11:01 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-02-21 18:12:04 +08:00
|
|
|
void alert_already_running()
|
|
|
|
{
|
|
|
|
MessageBoxW(nullptr,
|
|
|
|
GET_RESOURCE_STRING(IDS_ANOTHER_INSTANCE_RUNNING).c_str(),
|
|
|
|
GET_RESOURCE_STRING(IDS_POWERTOYS).c_str(),
|
|
|
|
MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
|
|
|
|
}
|
|
|
|
|
2020-02-12 18:03:40 +08:00
|
|
|
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();
|
2020-02-18 23:11:01 +08:00
|
|
|
|
|
|
|
int result = -1;
|
2019-12-27 00:26:11 +08:00
|
|
|
try
|
|
|
|
{
|
2020-02-26 04:04:19 +08:00
|
|
|
notifications::register_background_toast_handler();
|
2020-02-18 23:11:01 +08:00
|
|
|
|
2019-12-27 00:26:11 +08:00
|
|
|
chdir_current_executable();
|
|
|
|
// Load Powertyos DLLS
|
|
|
|
// For now only load known DLLs
|
|
|
|
std::unordered_set<std::wstring> known_dlls = {
|
|
|
|
L"shortcut_guide.dll",
|
|
|
|
L"fancyzones.dll",
|
|
|
|
L"PowerRenameExt.dll"
|
|
|
|
};
|
|
|
|
for (auto& file : std::filesystem::directory_iterator(L"modules/"))
|
|
|
|
{
|
|
|
|
if (file.path().extension() != L".dll")
|
|
|
|
continue;
|
|
|
|
if (known_dlls.find(file.path().filename()) == known_dlls.end())
|
|
|
|
continue;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
auto module = load_powertoy(file.path().wstring());
|
|
|
|
modules().emplace(module.get_name(), std::move(module));
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Start initial powertoys
|
|
|
|
start_initial_powertoys();
|
2019-09-05 00:26:26 +08:00
|
|
|
|
2020-02-12 18:03:40 +08:00
|
|
|
Trace::EventLaunch(get_product_version(), isProcessElevated);
|
2019-09-05 00:26:26 +08:00
|
|
|
|
2019-12-27 00:26:11 +08:00
|
|
|
result = run_message_loop();
|
|
|
|
}
|
|
|
|
catch (std::runtime_error& err)
|
|
|
|
{
|
|
|
|
std::string err_what = err.what();
|
2020-01-10 01:17:42 +08:00
|
|
|
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;
|
2019-09-05 00:26:26 +08:00
|
|
|
}
|
2019-12-17 01:36:52 +08:00
|
|
|
|
2020-02-26 04:04:19 +08:00
|
|
|
// 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
|
|
|
|
};
|
|
|
|
|
|
|
|
SpecialMode should_run_in_special_mode()
|
|
|
|
{
|
|
|
|
int nArgs;
|
|
|
|
LPWSTR* szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
|
|
|
|
for (size_t i = 1; i < nArgs; ++i)
|
|
|
|
{
|
|
|
|
if (!wcscmp(notifications::TOAST_ACTIVATED_LAUNCH_ARG, szArglist[i]))
|
|
|
|
return SpecialMode::Win32ToastNotificationCOMServer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SpecialMode::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
int win32_toast_notification_COM_server_mode()
|
|
|
|
{
|
|
|
|
notifications::run_desktop_app_activator_loop();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-12-27 00:26:11 +08:00
|
|
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
|
|
{
|
2020-02-26 04:04:19 +08:00
|
|
|
winrt::init_apartment();
|
|
|
|
|
|
|
|
switch (should_run_in_special_mode())
|
|
|
|
{
|
|
|
|
case SpecialMode::Win32ToastNotificationCOMServer:
|
|
|
|
return win32_toast_notification_COM_server_mode();
|
|
|
|
case SpecialMode::None:
|
|
|
|
// continue as usual
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-02-21 18:12:04 +08:00
|
|
|
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
|
|
|
{
|
2020-02-21 18:12:04 +08:00
|
|
|
msix_mutex = create_msix_mutex();
|
|
|
|
if (!msix_mutex)
|
|
|
|
{
|
|
|
|
// The MSIX version is already running.
|
|
|
|
alert_already_running();
|
|
|
|
return 0;
|
|
|
|
}
|
2020-02-18 23:11:01 +08:00
|
|
|
|
2020-02-21 18:12:04 +08:00
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
alert_already_running();
|
|
|
|
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
|
2020-02-18 23:11:01 +08:00
|
|
|
{
|
2020-02-21 18:12:04 +08:00
|
|
|
// 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.
|
|
|
|
alert_already_running();
|
|
|
|
return 0;
|
|
|
|
}
|
2020-02-18 23:11:01 +08:00
|
|
|
|
2020-02-21 18:12:04 +08:00
|
|
|
// 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)
|
2020-02-18 23:11:01 +08:00
|
|
|
{
|
2020-02-21 18:12:04 +08:00
|
|
|
// The MSIX version is already running.
|
|
|
|
alert_already_running();
|
2020-02-18 23:11:01 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2020-02-21 18:12:04 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// The MSIX version isn't running, release the mutex.
|
|
|
|
msix_mutex.reset(nullptr);
|
|
|
|
}
|
2020-02-18 23:11:01 +08:00
|
|
|
}
|
|
|
|
|
2019-12-27 00:26:11 +08:00
|
|
|
int result = 0;
|
|
|
|
try
|
|
|
|
{
|
2020-02-18 23:11:01 +08:00
|
|
|
|
|
|
|
if (winstore::running_as_packaged())
|
|
|
|
{
|
|
|
|
std::thread{ [] {
|
|
|
|
start_msi_uninstallation_sequence();
|
|
|
|
} }.detach();
|
|
|
|
}
|
2019-12-27 00:26:11 +08:00
|
|
|
// Singletons initialization order needs to be preserved, first events and
|
|
|
|
// then modules to guarantee the reverse destruction order.
|
|
|
|
SystemMenuHelperInstace();
|
|
|
|
powertoys_events();
|
|
|
|
modules();
|
2019-12-17 01:36:52 +08:00
|
|
|
|
2019-12-27 00:26:11 +08:00
|
|
|
auto general_settings = load_general_settings();
|
|
|
|
int rvalue = 0;
|
2020-02-12 18:03:40 +08:00
|
|
|
bool isProcessElevated = is_process_elevated();
|
|
|
|
if (isProcessElevated ||
|
2019-12-27 00:26:11 +08:00
|
|
|
general_settings.GetNamedBoolean(L"run_elevated", false) == false ||
|
|
|
|
strcmp(lpCmdLine, "--dont-elevate") == 0)
|
|
|
|
{
|
2020-02-12 18:03:40 +08:00
|
|
|
result = runner(isProcessElevated);
|
2019-12-27 00:26:11 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
schedule_restart_as_elevated();
|
|
|
|
result = 0;
|
|
|
|
}
|
2019-12-17 01:36:52 +08:00
|
|
|
}
|
2019-12-27 00:26:11 +08:00
|
|
|
catch (std::runtime_error& err)
|
|
|
|
{
|
|
|
|
std::string err_what = err.what();
|
2020-01-10 01:17:42 +08:00
|
|
|
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;
|
2019-12-17 01:36:52 +08:00
|
|
|
}
|
2020-02-21 18:12:04 +08:00
|
|
|
|
|
|
|
// 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);
|
2020-01-10 01:17:42 +08:00
|
|
|
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-17 01:36:52 +08:00
|
|
|
}
|
2019-12-27 00:26:11 +08:00
|
|
|
stop_tray_icon();
|
|
|
|
return result;
|
2019-12-17 01:36:52 +08:00
|
|
|
}
|