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 "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-05 00:41:00 +08:00
# include <common/winstore.h>
# include <common/notifications.h>
2020-04-21 15:30:12 +08:00
# include <common/updating/updating.h>
2020-06-11 16:09:06 +08:00
# include <common/RestartManagement.h>
2020-02-20 22:04:56 +08:00
# include "update_state.h"
2020-04-21 15:30:12 +08:00
# include "update_utils.h"
# include "action_runner_utils.h"
2020-02-20 22:04:56 +08:00
# include <winrt/Windows.System.h>
2020-02-05 00:41:00 +08:00
2020-06-11 16:09:06 +08:00
# include <Psapi.h>
# include <RestartManager.h>
2019-09-05 00:26:26 +08:00
# if _DEBUG && _WIN64
# include "unhandled_exception_handler.h"
# endif
2020-03-24 22:17:25 +08:00
# include <common/notifications/fancyzones_notifications.h>
2019-09-05 00:26:26 +08:00
2019-12-17 01:36:52 +08:00
extern " C " IMAGE_DOS_HEADER __ImageBase ;
2020-06-11 16:09:06 +08:00
// Window Explorer process name should not be localized.
const wchar_t EXPLORER_PROCESS_NAME [ ] = L " explorer.exe " ;
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. " ;
2020-04-04 00:51:28 +08:00
const wchar_t OLDER_MSIX_UNINSTALLED [ ] = L " An older MSIX version of PowerToys was uninstalled. " ;
2020-06-11 16:09:06 +08:00
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? " ;
2020-02-18 23:11:01 +08:00
}
namespace
{
const wchar_t MSI_VERSION_MUTEX_NAME [ ] = L " Local \\ PowerToyRunMutex " ;
const wchar_t MSIX_VERSION_MUTEX_NAME [ ] = L " Local \\ PowerToyMSIXRunMutex " ;
2020-03-24 22:17:25 +08:00
const wchar_t PT_URI_PROTOCOL_SCHEME [ ] = L " powertoys:// " ;
2020-02-18 23:11:01 +08:00
}
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-04-07 18:17:18 +08:00
void open_menu_from_another_instance ( )
2020-02-21 18:12:04 +08:00
{
2020-04-07 18:17:18 +08:00
HWND hwnd_main = FindWindow ( L " PToyTrayIconWindow " , NULL ) ;
PostMessage ( hwnd_main , WM_COMMAND , ID_SETTINGS_MENU_COMMAND , NULL ) ;
2020-02-21 18:12:04 +08:00
}
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-03-12 15:59:10 +08:00
std : : thread { [ ] {
2020-04-21 15:30:12 +08:00
github_update_worker ( ) ;
2020-03-12 15:59:10 +08:00
} } . detach ( ) ;
if ( winstore : : running_as_packaged ( ) )
{
std : : thread { [ ] {
start_msi_uninstallation_sequence ( ) ;
} } . detach ( ) ;
}
2020-04-04 00:51:28 +08:00
else
{
2020-04-21 15:30:12 +08:00
std : : thread { [ ] {
if ( updating : : uninstall_previous_msix_version_async ( ) . get ( ) )
2020-04-04 00:51:28 +08:00
{
notifications : : show_toast ( localized_strings : : OLDER_MSIX_UNINSTALLED ) ;
}
2020-04-21 15:30:12 +08:00
} } . detach ( ) ;
2020-04-04 00:51:28 +08:00
}
2020-03-12 15:59:10 +08:00
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 ( ) ;
2020-06-22 18:01:33 +08:00
// 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
} ;
2020-05-08 23:23:18 +08:00
2020-06-22 18:01:33 +08:00
for ( const auto & moduleSubdir : knownModules )
2019-12-27 00:26:11 +08:00
{
2020-06-22 18:01:33 +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 ( ) ;
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 ,
2020-03-24 22:17:25 +08:00
Win32ToastNotificationCOMServer ,
2020-04-21 15:30:12 +08:00
ToastNotificationHandler ,
ReportSuccessfulUpdate
2020-02-26 04:04:19 +08:00
} ;
2020-03-24 22:17:25 +08:00
SpecialMode should_run_in_special_mode ( const int n_cmd_args , LPWSTR * cmd_arg_list )
2020-02-26 04:04:19 +08:00
{
2020-03-24 22:17:25 +08:00
for ( size_t i = 1 ; i < n_cmd_args ; + + i )
2020-02-26 04:04:19 +08:00
{
2020-03-24 22:17:25 +08:00
if ( ! wcscmp ( notifications : : TOAST_ACTIVATED_LAUNCH_ARG , cmd_arg_list [ i ] ) )
{
2020-02-26 04:04:19 +08:00
return SpecialMode : : Win32ToastNotificationCOMServer ;
2020-03-24 22:17:25 +08:00
}
else if ( n_cmd_args = = 2 & & ! wcsncmp ( PT_URI_PROTOCOL_SCHEME , cmd_arg_list [ i ] , wcslen ( PT_URI_PROTOCOL_SCHEME ) ) )
{
return SpecialMode : : ToastNotificationHandler ;
}
2020-04-21 15:30:12 +08:00
else if ( n_cmd_args = = 2 & & ! wcscmp ( UPDATE_REPORT_SUCCESS , cmd_arg_list [ i ] ) )
{
return SpecialMode : : ReportSuccessfulUpdate ;
}
2020-02-26 04:04:19 +08:00
}
return SpecialMode : : None ;
}
int win32_toast_notification_COM_server_mode ( )
{
notifications : : run_desktop_app_activator_loop ( ) ;
return 0 ;
}
2020-03-24 22:17:25 +08:00
enum class toast_notification_handler_result
{
exit_success ,
exit_error
} ;
2020-06-18 18:43:09 +08:00
2020-03-24 22:17:25 +08:00
toast_notification_handler_result toast_notification_handler ( const std : : wstring_view param )
{
2020-06-18 18:43:09 +08:00
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/ " ;
2020-06-23 20:53:02 +08:00
const std : : wstring_view download_and_install_update = L " download_and_install_update/ " ;
2020-06-18 18:43:09 +08:00
if ( param = = cant_drag_elevated_disable )
2020-03-24 22:17:25 +08:00
{
return disable_cant_drag_elevated_warning ( ) ? toast_notification_handler_result : : exit_success : toast_notification_handler_result : : exit_error ;
}
2020-06-18 18:43:09 +08:00
else if ( param . starts_with ( update_now ) )
2020-04-21 15:30:12 +08:00
{
2020-06-18 18:43:09 +08:00
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 ( ) ) ;
2020-04-21 15:30:12 +08:00
return toast_notification_handler_result : : exit_success ;
}
2020-06-18 18:43:09 +08:00
else if ( param . starts_with ( schedule_update ) )
2020-04-21 15:30:12 +08:00
{
2020-06-18 18:43:09 +08:00
const auto installerFilename = param . data ( ) + size ( schedule_update ) ;
UpdateState : : store ( [ = ] ( UpdateState & state ) {
2020-04-21 15:30:12 +08:00
state . pending_update = true ;
2020-06-18 18:43:09 +08:00
state . pending_installer_filename = installerFilename ;
2020-04-21 15:30:12 +08:00
} ) ;
return toast_notification_handler_result : : exit_success ;
}
2020-06-23 20:53:02 +08:00
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 ;
}
2020-03-24 22:17:25 +08:00
else
{
return toast_notification_handler_result : : exit_error ;
}
}
2020-06-11 16:09:06 +08:00
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 ) ;
2020-06-11 16:09:06 +08:00
}
}
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 ( ) ;
2020-04-21 15:30:12 +08:00
if ( launch_pending_update ( ) )
{
return 0 ;
}
2020-03-24 22:17:25 +08:00
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 ) )
2020-02-26 04:04:19 +08:00
{
case SpecialMode : : Win32ToastNotificationCOMServer :
return win32_toast_notification_COM_server_mode ( ) ;
2020-03-24 22:17:25 +08:00
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 ;
}
2020-04-21 15:30:12 +08:00
case SpecialMode : : ReportSuccessfulUpdate :
2020-06-11 16:09:06 +08:00
RequestExplorerRestart ( ) ;
2020-04-21 15:30:12 +08:00
break ;
2020-02-26 04:04:19 +08:00
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.
2020-04-07 18:17:18 +08:00
open_menu_from_another_instance ( ) ;
2020-02-21 18:12:04 +08:00
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 )
{
2020-04-07 18:17:18 +08:00
open_menu_from_another_instance ( ) ;
2020-02-21 18:12:04 +08:00
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.
2020-04-07 18:17:18 +08:00
open_menu_from_another_instance ( ) ;
2020-02-21 18:12:04 +08:00
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.
2020-04-07 18:17:18 +08:00
open_menu_from_another_instance ( ) ;
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
{
// Singletons initialization order needs to be preserved, first events and
// then modules to guarantee the reverse destruction order.
2020-05-27 23:06:50 +08:00
SystemMenuHelperInstance ( ) ;
2019-12-27 00:26:11 +08:00
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 ( ) ;
2020-06-27 05:46:47 +08:00
// Apply the general settings but don't save it as the modules() variable has not been loaded yet
apply_general_settings ( general_settings , false ) ;
2019-12-27 00:26:11 +08:00
int rvalue = 0 ;
2020-03-24 22:17:25 +08:00
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
{
2020-03-24 22:17:25 +08:00
result = runner ( elevated ) ;
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
}