dllmain.cpp
1 #include "pch.h" 2 #include <interface/powertoy_module_interface.h> 3 #include <common/SettingsAPI/settings_objects.h> 4 #include <common/interop/shared_constants.h> 5 #include "Generated Files/resource.h" 6 #include <launcher\Microsoft.Launcher\LauncherConstants.h> 7 #include <common/logger/logger.h> 8 #include <common/SettingsAPI/settings_helpers.h> 9 10 #include <common/utils/elevation.h> 11 #include <common/utils/process_path.h> 12 #include <common/utils/resources.h> 13 #include <common/utils/winapi_error.h> 14 15 #include <filesystem> 16 #include <mutex> 17 18 namespace 19 { 20 const wchar_t POWER_LAUNCHER_PID_SHARED_FILE[] = L"Local\\PowerLauncherPidSharedFile-3cbfbad4-199b-4e2c-9825-942d5d3d3c74"; 21 const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; 22 const wchar_t JSON_KEY_WIN[] = L"win"; 23 const wchar_t JSON_KEY_ALT[] = L"alt"; 24 const wchar_t JSON_KEY_CTRL[] = L"ctrl"; 25 const wchar_t JSON_KEY_SHIFT[] = L"shift"; 26 const wchar_t JSON_KEY_CODE[] = L"code"; 27 const wchar_t JSON_KEY_OPEN_POWERLAUNCHER[] = L"open_powerlauncher"; 28 const wchar_t JSON_KEY_USE_CENTRALIZED_KEYBOARD_HOOK[] = L"use_centralized_keyboard_hook"; 29 } 30 31 BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) 32 { 33 switch (ul_reason_for_call) 34 { 35 case DLL_PROCESS_ATTACH: 36 break; 37 case DLL_THREAD_ATTACH: 38 case DLL_THREAD_DETACH: 39 break; 40 case DLL_PROCESS_DETACH: 41 break; 42 } 43 44 return TRUE; 45 } 46 47 // These are the properties shown in the Settings page. 48 struct ModuleSettings 49 { 50 } g_settings; 51 52 // Implement the PowerToy Module Interface and all the required methods. 53 class Microsoft_Launcher : public PowertoyModuleIface 54 { 55 private: 56 // The PowerToy state. 57 bool m_enabled = false; 58 59 // Load initial settings from the persisted values. 60 void init_settings(); 61 62 bool processStarting = false; 63 std::mutex processStartingMutex; 64 bool processStarted = false; 65 66 //contains the name of the powerToys 67 std::wstring app_name; 68 69 //contains the non localized key of the powertoy 70 std::wstring app_key; 71 72 // Time to wait for process to close after sending WM_CLOSE signal 73 static const int MAX_WAIT_MILLISEC = 10000; 74 75 // Hotkey to invoke the module 76 Hotkey m_hotkey = { .key = 0 }; 77 78 // If the centralized keyboard hook should be used to activate PowerToys Run 79 bool m_use_centralized_keyboard_hook = false; 80 81 // Helper function to extract the hotkey from the settings 82 void parse_hotkey(PowerToysSettings::PowerToyValues& settings); 83 84 // Handle to event used to invoke the Runner 85 HANDLE m_hEvent; 86 HANDLE m_hCentralizedKeyboardHookEvent; 87 88 HANDLE send_telemetry_event; 89 90 // Handle a case when a user started standalone PowerToys Run or for some reason the process is leaked 91 void TerminateRunningInstance() 92 { 93 auto exitEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::RUN_EXIT_EVENT); 94 if (!exitEvent) 95 { 96 Logger::warn(L"Failed to create exitEvent. {}", get_last_error_or_default(GetLastError())); 97 } 98 else 99 { 100 Logger::trace(L"Signaled exitEvent"); 101 if (!SetEvent(exitEvent)) 102 { 103 Logger::warn(L"Failed to signal exitEvent. {}", get_last_error_or_default(GetLastError())); 104 } 105 106 ResetEvent(exitEvent); 107 CloseHandle(exitEvent); 108 } 109 } 110 111 public: 112 // Constructor 113 Microsoft_Launcher() 114 { 115 app_name = GET_RESOURCE_STRING(IDS_LAUNCHER_NAME); 116 app_key = LauncherConstants::ModuleKey; 117 std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key)); 118 logFilePath.append(LogSettings::launcherLogPath); 119 Logger::init(LogSettings::launcherLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); 120 Logger::info("Launcher object is constructing"); 121 init_settings(); 122 123 m_hEvent = CreateDefaultEvent(CommonSharedConstants::POWER_LAUNCHER_SHARED_EVENT); 124 m_hCentralizedKeyboardHookEvent = CreateDefaultEvent(CommonSharedConstants::POWER_LAUNCHER_CENTRALIZED_HOOK_SHARED_EVENT); 125 126 send_telemetry_event = CreateDefaultEvent(CommonSharedConstants::RUN_SEND_SETTINGS_TELEMETRY_EVENT); 127 }; 128 129 ~Microsoft_Launcher() 130 { 131 Logger::info("Launcher object is destroying"); 132 m_enabled = false; 133 } 134 135 // Destroy the powertoy and free memory 136 virtual void destroy() override 137 { 138 delete this; 139 } 140 141 // Return the localized display name of the powertoy 142 virtual const wchar_t* get_name() override 143 { 144 return app_name.c_str(); 145 } 146 147 // Return the non localized key of the powertoy, this will be cached by the runner 148 virtual const wchar_t* get_key() override 149 { 150 return app_key.c_str(); 151 } 152 153 // Return the configured status for the gpo policy for the module 154 virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override 155 { 156 return powertoys_gpo::getConfiguredPowerLauncherEnabledValue(); 157 } 158 159 // Return JSON with the configuration options. 160 virtual bool get_config(wchar_t* buffer, int* buffer_size) override 161 { 162 HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase); 163 164 // Create a Settings object. 165 PowerToysSettings::Settings settings(hinstance, get_name()); 166 settings.set_description(GET_RESOURCE_STRING(IDS_LAUNCHER_SETTINGS_DESC)); 167 settings.set_overview_link(L"https://aka.ms/PowerToysOverview_PowerToysRun"); 168 169 return settings.serialize_to_buffer(buffer, buffer_size); 170 } 171 172 // Signal from the Settings editor to call a custom action. 173 // This can be used to spawn more complex editors. 174 virtual void call_custom_action(const wchar_t* action) override 175 { 176 static UINT custom_action_num_calls = 0; 177 try 178 { 179 // Parse the action values, including name. 180 PowerToysSettings::CustomActionObject action_object = 181 PowerToysSettings::CustomActionObject::from_json_string(action); 182 } 183 catch (std::exception&) 184 { 185 // Improper JSON. 186 } 187 } 188 189 // Called by the runner to pass the updated settings values as a serialized JSON. 190 virtual void set_config(const wchar_t* config) override 191 { 192 try 193 { 194 // Parse the input JSON string. 195 PowerToysSettings::PowerToyValues values = 196 PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); 197 198 parse_hotkey(values); 199 // If you don't need to do any custom processing of the settings, proceed 200 // to persists the values calling: 201 values.save_to_settings_file(); 202 // Otherwise call a custom function to process the settings before saving them to disk: 203 // save_settings(); 204 } 205 catch (std::exception&) 206 { 207 // Improper JSON. 208 } 209 } 210 211 // Enable the powertoy 212 virtual void enable() 213 { 214 Logger::info("Microsoft_Launcher::enable() begin"); 215 216 // This synchronization code is here since we've seen logs of this function being entered twice in the same process/thread pair. 217 // The theory here is that the call to ShellExecuteExW might be enabling some context switching that allows the low level keyboard hook to be run. 218 // Ref: https://github.com/microsoft/PowerToys/issues/12908#issuecomment-986995633 219 // We want only one instance to be started at the same time. 220 processStartingMutex.lock(); 221 if (processStarting) 222 { 223 processStartingMutex.unlock(); 224 Logger::warn(L"Two PowerToys Run processes were trying to get started at the same time."); 225 return; 226 } 227 else 228 { 229 processStarting = true; 230 processStartingMutex.unlock(); 231 } 232 233 ResetEvent(m_hCentralizedKeyboardHookEvent); 234 ResetEvent(send_telemetry_event); 235 236 unsigned long powertoys_pid = GetCurrentProcessId(); 237 TerminateRunningInstance(); 238 if (is_process_elevated(false)) 239 { 240 Logger::trace("Starting PowerToys Run from elevated process"); 241 const auto modulePath = get_module_folderpath(); 242 std::wstring runExecutablePath = modulePath; 243 std::wstring params; 244 params += L" -powerToysPid " + std::to_wstring(powertoys_pid) + L" "; 245 params += L"--started-from-runner "; 246 runExecutablePath += L"\\PowerToys.PowerLauncher.exe"; 247 processStarted = RunNonElevatedFailsafe(runExecutablePath, params, modulePath).has_value(); 248 } 249 else 250 { 251 Logger::trace("Starting PowerToys Run from not elevated process"); 252 std::wstring executable_args; 253 executable_args += L" -powerToysPid "; 254 executable_args += std::to_wstring(powertoys_pid); 255 executable_args += L" --started-from-runner"; 256 257 SHELLEXECUTEINFOW sei{ sizeof(sei) }; 258 sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; 259 sei.lpFile = L"PowerToys.PowerLauncher.exe"; 260 sei.nShow = SW_SHOWNORMAL; 261 sei.lpParameters = executable_args.data(); 262 263 if (ShellExecuteExW(&sei)) 264 { 265 processStarted = true; 266 Logger::trace("Started PowerToys Run"); 267 } 268 else 269 { 270 Logger::error("Launcher failed to start"); 271 } 272 } 273 processStarting = false; 274 m_enabled = true; 275 Logger::info("Microsoft_Launcher::enable() end"); 276 } 277 278 // Disable the powertoy 279 virtual void disable() 280 { 281 Logger::info("Launcher is disabling"); 282 if (m_enabled) 283 { 284 TerminateRunningInstance(); 285 processStarted = false; 286 ResetEvent(m_hEvent); 287 ResetEvent(m_hCentralizedKeyboardHookEvent); 288 ResetEvent(send_telemetry_event); 289 } 290 291 m_enabled = false; 292 } 293 294 // Returns if the powertoys is enabled 295 virtual bool is_enabled() override 296 { 297 return m_enabled; 298 } 299 300 // Return the invocation hotkey 301 virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override 302 { 303 if (m_hotkey.key) 304 { 305 if (hotkeys && buffer_size >= 1) 306 { 307 hotkeys[0] = m_hotkey; 308 } 309 310 return 1; 311 } 312 else 313 { 314 return 0; 315 } 316 } 317 318 // Process the hotkey event 319 virtual bool on_hotkey(size_t /*hotkeyId*/) override 320 { 321 // For now, hotkeyId will always be zero 322 if (m_enabled) 323 { 324 if (!processStarted) 325 { 326 Logger::warn("PowerToys Run hasn't been started. Starting PowerToys Run"); 327 enable(); 328 } 329 330 /* Now, PowerToys Run uses a global hotkey so that it can get focus. 331 * Activate it with the centralized keyboard hook only if the setting is on.*/ 332 if (m_use_centralized_keyboard_hook) 333 { 334 Logger::trace("Set POWER_LAUNCHER_SHARED_EVENT"); 335 SetEvent(m_hCentralizedKeyboardHookEvent); 336 return true; 337 } 338 } 339 340 return false; 341 } 342 343 // Callback to send WM_CLOSE signal to each top level window. 344 static BOOL CALLBACK requestMainWindowClose(HWND nextWindow, LPARAM closePid) 345 { 346 DWORD windowPid; 347 GetWindowThreadProcessId(nextWindow, &windowPid); 348 349 if (windowPid == static_cast<DWORD>(closePid)) 350 ::PostMessage(nextWindow, WM_CLOSE, 0, 0); 351 352 return true; 353 } 354 355 virtual void send_settings_telemetry() override 356 { 357 Logger::info("Send settings telemetry"); 358 SetEvent(send_telemetry_event); 359 } 360 }; 361 362 // Load the settings file. 363 void Microsoft_Launcher::init_settings() 364 { 365 try 366 { 367 // Load and parse the settings file for this PowerToy. 368 PowerToysSettings::PowerToyValues settings = 369 PowerToysSettings::PowerToyValues::load_from_settings_file(get_key()); 370 371 parse_hotkey(settings); 372 } 373 catch (std::exception&) 374 { 375 // Error while loading from the settings file. Let default values stay as they are. 376 } 377 } 378 379 void Microsoft_Launcher::parse_hotkey(PowerToysSettings::PowerToyValues& settings) 380 { 381 m_use_centralized_keyboard_hook = false; 382 auto settingsObject = settings.get_raw_json(); 383 if (settingsObject.GetView().Size()) 384 { 385 try 386 { 387 auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OPEN_POWERLAUNCHER); 388 m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); 389 m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); 390 m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); 391 m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL); 392 m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); 393 } 394 catch (...) 395 { 396 Logger::error("Failed to initialize PT Run start shortcut"); 397 } 398 try 399 { 400 auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); 401 m_use_centralized_keyboard_hook =jsonPropertiesObject.GetNamedBoolean(JSON_KEY_USE_CENTRALIZED_KEYBOARD_HOOK); 402 } 403 catch (...) 404 { 405 Logger::warn("Failed to get centralized keyboard hook setting"); 406 } 407 } 408 else 409 { 410 Logger::info("PT Run settings are empty"); 411 } 412 413 if (!m_hotkey.key) 414 { 415 Logger::info("PT Run is going to use default shortcut"); 416 m_hotkey.win = false; 417 m_hotkey.alt = true; 418 m_hotkey.shift = false; 419 m_hotkey.ctrl = false; 420 m_hotkey.key = VK_SPACE; 421 } 422 } 423 424 extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() 425 { 426 return new Microsoft_Launcher(); 427 }