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  }