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 "trace.h"
  6  #include "generateSecurityDescriptor.h"
  7  
  8  #include <common/logger/logger.h>
  9  #include <common/SettingsAPI/settings_helpers.h>
 10  
 11  #include <common/utils/process_path.h>
 12  #include <common/utils/resources.h>
 13  #include <common/utils/winapi_error.h>
 14  #include <common/utils/processApi.h>
 15  #include <common/utils/elevation.h>
 16  #include <common/utils/logger_helper.h>
 17  
 18  HINSTANCE g_hInst_MouseWithoutBorders = 0;
 19  
 20  BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
 21  {
 22      switch (ul_reason_for_call)
 23      {
 24      case DLL_PROCESS_ATTACH:
 25          g_hInst_MouseWithoutBorders = hModule;
 26          Trace::MouseWithoutBorders::RegisterProvider();
 27          break;
 28      case DLL_THREAD_ATTACH:
 29      case DLL_THREAD_DETACH:
 30          break;
 31      case DLL_PROCESS_DETACH:
 32          Trace::MouseWithoutBorders::UnregisterProvider();
 33          break;
 34      }
 35      return TRUE;
 36  }
 37  
 38  bool GetUserSid(const wchar_t* username, PSID& sid)
 39  {
 40      DWORD sidSize = 0;
 41      DWORD domainNameSize = 0;
 42      SID_NAME_USE sidNameUse;
 43  
 44      LookupAccountName(nullptr, username, nullptr, &sidSize, nullptr, &domainNameSize, &sidNameUse);
 45      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
 46      {
 47          Logger::error("Failed to get buffer sizes");
 48          return false;
 49      }
 50  
 51      sid = LocalAlloc(LPTR, sidSize);
 52      LPWSTR domainName = static_cast<LPWSTR>(LocalAlloc(LPTR, domainNameSize * sizeof(wchar_t)));
 53  
 54      if (!LookupAccountNameW(nullptr, username, sid, &sidSize, domainName, &domainNameSize, &sidNameUse))
 55      {
 56          Logger::error("Failed to lookup account name");
 57          LocalFree(sid);
 58          LocalFree(domainName);
 59          return false;
 60      }
 61  
 62      LocalFree(domainName);
 63      return true;
 64  }
 65  
 66  std::wstring GetCurrentUserSid()
 67  {
 68      wchar_t username[UNLEN + 1];
 69      DWORD usernameSize = UNLEN + 1;
 70  
 71      std::wstring result;
 72      if (!GetUserNameW(username, &usernameSize))
 73      {
 74          Logger::error("Failed to get the current user name");
 75          return result;
 76      }
 77  
 78      PSID sid;
 79      if (GetUserSid(username, sid))
 80      {
 81          LPWSTR sidString;
 82          if (ConvertSidToStringSid(sid, &sidString))
 83          {
 84              result = sidString;
 85              LocalFree(sidString);
 86          }
 87          LocalFree(sid);
 88      }
 89      else
 90      {
 91          Logger::error(L"Failed to get SID for user \"");
 92      }
 93  
 94      return result;
 95  }
 96  
 97  std::wstring escapeDoubleQuotes(const std::wstring& input)
 98  {
 99      std::wstring output;
100      output.reserve(input.size());
101  
102      for (const wchar_t& ch : input)
103      {
104          if (ch == L'"')
105          {
106              output += L'\\';
107          }
108          output += ch;
109      }
110  
111      return output;
112  }
113  
114  const static wchar_t* MODULE_NAME = L"MouseWithoutBorders";
115  const static wchar_t* MODULE_DESC = L"A module to move your mouse across computers.";
116  const static wchar_t* SERVICE_NAME = L"PowerToys.MWB.Service";
117  const static std::wstring_view USE_SERVICE_PROPERTY_NAME = L"UseService";
118  
119  class MouseWithoutBorders : public PowertoyModuleIface
120  {
121      std::wstring app_name;
122      std::wstring app_key;
123  
124  private:
125      bool m_enabled = false;
126      bool run_in_service_mode = false;
127      PROCESS_INFORMATION p_info = {};
128  
129      bool is_enabled_by_default() const override
130      {
131          return false;
132      }
133  
134      bool is_process_running()
135      {
136          return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT;
137      }
138  
139      void launch_process()
140      {
141          Logger::trace(L"Launching PowerToys MouseWithoutBorders process");
142          const std::wstring application_path = L"PowerToys.MouseWithoutBorders.exe";
143          STARTUPINFO info = { sizeof(info) };
144          std::wstring full_command_path = application_path;
145          if (run_in_service_mode)
146          {
147              full_command_path += L" ";
148              full_command_path += USE_SERVICE_PROPERTY_NAME;
149          }
150  
151          if (!CreateProcessW(application_path.c_str(), full_command_path.data(), nullptr, nullptr, true, {}, nullptr, nullptr, &info, &p_info))
152          {
153              DWORD error = GetLastError();
154              std::wstring message = L"PowerToys MouseWithoutBorders failed to start with error: ";
155              message += std::to_wstring(error);
156              Logger::error(message);
157          }
158  
159          Trace::MouseWithoutBorders::Activate();
160      }
161  
162      void unregister_service()
163      {
164          SC_HANDLE schSCManager = OpenSCManagerW(nullptr, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
165  
166          SC_HANDLE hService = OpenServiceW(schSCManager, SERVICE_NAME, SERVICE_STOP | DELETE);
167          if (!hService)
168          {
169              Logger::error("Failed to open MWB service");
170              return;
171          }
172  
173          SERVICE_STATUS ss;
174          if (ControlService(hService, SERVICE_CONTROL_STOP, &ss))
175          {
176              Sleep(1000);
177              for (int i = 0; i < 5; ++i)
178              {
179                  while (QueryServiceStatus(hService, &ss))
180                  {
181                      if (ss.dwCurrentState == SERVICE_STOP_PENDING)
182                      {
183                          Sleep(1000);
184                      }
185                      else
186                      {
187                          goto outer;
188                      }
189                  }
190              }
191          }
192  
193      outer:
194          BOOL deleteResult = DeleteService(hService);
195          CloseServiceHandle(hService);
196  
197          if (!deleteResult)
198          {
199              Logger::error("Failed to delete MWB service");
200              return;
201          }
202  
203          Trace::MouseWithoutBorders::ToggleServiceRegistration(false);
204      }
205  
206      void
207      register_service()
208      {
209          SC_HANDLE schSCManager = OpenSCManagerW(nullptr, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
210          if (schSCManager == nullptr)
211          {
212              Logger::error(L"Couldn't open sc manager");
213              return;
214          }
215  
216          const auto closeSCM = wil::scope_exit([&] {
217              CloseServiceHandle(schSCManager);
218          });
219  
220          SC_HANDLE schService = OpenServiceW(schSCManager, SERVICE_NAME, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);
221  
222          const auto closeService = wil::scope_exit([&] {
223              CloseServiceHandle(schService);
224          });
225  
226          const auto servicePath = get_module_folderpath(g_hInst_MouseWithoutBorders) + L"/PowerToys.MouseWithoutBordersService.exe";
227  
228          // Check that the service doesn't exist already and is not disabled
229          DWORD bytesNeeded;
230          LPQUERY_SERVICE_CONFIGW pServiceConfig = nullptr;
231          if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded))
232          {
233              if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
234              {
235                  pServiceConfig = static_cast<LPQUERY_SERVICE_CONFIGW>(LocalAlloc(LMEM_FIXED, bytesNeeded));
236                  if (!QueryServiceConfigW(schService, pServiceConfig, bytesNeeded, &bytesNeeded))
237                  {
238                      LocalFree(pServiceConfig);
239                      pServiceConfig = nullptr;
240                      CloseServiceHandle(schService);
241                  }
242              }
243          }
244  
245          // Pass local app data of the current user to the service
246          wil::unique_cotaskmem_string cLocalAppPath;
247          winrt::check_hresult(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &cLocalAppPath));
248  
249          std::wstring localAppPath{ cLocalAppPath.get() };
250          std::wstring binaryWithArgsPath = L"\"";
251          binaryWithArgsPath += servicePath;
252          binaryWithArgsPath += L"\" ";
253          binaryWithArgsPath += escapeDoubleQuotes(localAppPath);
254  
255          bool alreadyRegistered = false;
256          bool isServicePathCorrect = true;
257          if (pServiceConfig)
258          {
259              std::wstring_view existingServicePath{ pServiceConfig->lpBinaryPathName };
260              alreadyRegistered = true;
261              isServicePathCorrect = (existingServicePath == binaryWithArgsPath);
262              if (isServicePathCorrect)
263              {
264                  Logger::warn(L"The service path is not correct. Current: {} Expected: {}", existingServicePath, binaryWithArgsPath);
265              }
266  
267              if (alreadyRegistered && pServiceConfig->dwStartType == SERVICE_DISABLED)
268              {
269                  if (!ChangeServiceConfigW(schService,
270                                            SERVICE_NO_CHANGE,
271                                            SERVICE_DEMAND_START,
272                                            SERVICE_NO_CHANGE,
273                                            nullptr,
274                                            nullptr,
275                                            nullptr,
276                                            nullptr,
277                                            nullptr,
278                                            nullptr,
279                                            nullptr))
280                  {
281                      const bool markedForDelete = GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE;
282                      // We cannot remove the mark for deletion from the service.
283                      if (markedForDelete)
284                      {
285                          alreadyRegistered = false;
286                          CloseServiceHandle(schService);
287                      }
288                  }
289              }
290              LocalFree(pServiceConfig);
291          }
292  
293          if (alreadyRegistered)
294          {
295              if (!isServicePathCorrect)
296              {
297                  if (!ChangeServiceConfigW(schService,
298                                            SERVICE_NO_CHANGE,
299                                            SERVICE_NO_CHANGE,
300                                            SERVICE_NO_CHANGE,
301                                            binaryWithArgsPath.c_str(),
302                                            nullptr,
303                                            nullptr,
304                                            nullptr,
305                                            nullptr,
306                                            nullptr,
307                                            nullptr))
308                  {
309                      Logger::error(L"Failed to update the service's path. ERROR: {}", GetLastError());
310                  }
311                  else
312                  {
313                      Logger::info(L"Updated the service's path.");
314                  }
315              }
316              return;
317          }
318  
319          schService = CreateServiceW(
320              schSCManager,
321              SERVICE_NAME,
322              SERVICE_NAME,
323              SERVICE_ALL_ACCESS,
324              SERVICE_WIN32_OWN_PROCESS,
325              SERVICE_DEMAND_START,
326              SERVICE_ERROR_NORMAL,
327              binaryWithArgsPath.c_str(),
328              nullptr,
329              nullptr,
330              nullptr,
331              nullptr,
332              nullptr);
333  
334          if (schService == nullptr)
335          {
336              Logger::error(L"Failed to create service");
337              return;
338          }
339  
340          // Set up the security descriptor to allow non-elevated users to start the service
341          PSECURITY_DESCRIPTOR pSD = nullptr;
342          ULONG szSD = 0;
343          std::wstring securityDescriptor = generateSecurityDescriptor(GetCurrentUserSid());
344  
345          if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
346                  securityDescriptor.c_str(),
347                  SDDL_REVISION_1,
348                  &pSD,
349                  &szSD))
350          {
351              Logger::error(L"Failed to convert security descriptor string");
352              CloseServiceHandle(schService);
353              return;
354          }
355  
356          if (!SetServiceObjectSecurity(schService, DACL_SECURITY_INFORMATION, pSD))
357          {
358              Logger::error("Failed to set service object security");
359          }
360  
361          LocalFree(pSD);
362          CloseServiceHandle(schService);
363      }
364  
365      void update_state_from_settings(const PowerToysSettings::PowerToyValues& values)
366      {
367          bool new_run_in_service_mode = values.get_bool_value(USE_SERVICE_PROPERTY_NAME).value_or(false);
368          if (powertoys_gpo::getConfiguredMwbAllowServiceModeValue() == powertoys_gpo::gpo_rule_configured_disabled)
369          {
370              new_run_in_service_mode = false;
371          }
372  
373          if (new_run_in_service_mode != run_in_service_mode)
374          {
375              run_in_service_mode = new_run_in_service_mode;
376  
377              shutdown_processes();
378  
379              if (new_run_in_service_mode)
380              {
381                  register_service();
382              }
383              // Wait until Settings -> MWB IPC Shutdown() call is completed
384              else
385              {
386                  const auto ps = getProcessHandlesByName(L"PowerToys.MouseWithoutBorders.exe", PROCESS_QUERY_LIMITED_INFORMATION);
387                  for (const auto& p : ps)
388                  {
389                      DWORD status = STILL_ACTIVE;
390                      do
391                      {
392                          GetExitCodeProcess(p.get(), &status);
393                      } while (status == STILL_ACTIVE);
394                  }
395  
396                  Sleep(1000);
397              }
398  
399              if (m_enabled)
400              {
401                  launch_process();
402              }
403  
404              Trace::MouseWithoutBorders::ToggleServiceRegistration(new_run_in_service_mode);
405          }
406      }
407  
408  public:
409      MouseWithoutBorders()
410      {
411          app_name = L"MouseWithoutBorders";
412          app_key = app_name;
413  
414          LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::mouseWithoutBordersLoggerName);
415  
416          std::filesystem::path oldLogPath(PTSettingsHelper::get_module_save_folder_location(app_key));
417          oldLogPath.append("LogsModuleInterface");
418          LoggerHelpers::delete_old_log_folder(oldLogPath);
419  
420          try
421          {
422              PowerToysSettings::PowerToyValues values =
423                  PowerToysSettings::PowerToyValues::load_from_settings_file(MODULE_NAME);
424              update_state_from_settings(values);
425          }
426          catch (std::exception&)
427          {
428              // Initial start/
429          }
430      };
431  
432      // Return the configured status for the gpo policy for the module
433      virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
434      {
435          return powertoys_gpo::getConfiguredMouseWithoutBordersEnabledValue();
436      }
437  
438      void shutdown_process(HANDLE handle)
439      {
440          auto cb = [](HWND hwnd, LPARAM lParam) -> BOOL {
441              DWORD processId;
442              GetWindowThreadProcessId(hwnd, &processId);
443  
444              if (processId == lParam)
445              {
446                  PostMessageW(hwnd, WM_CLOSE, 0, 0);
447                  return FALSE;
448              }
449  
450              return TRUE;
451          };
452  
453          DWORD processId = GetProcessId(handle);
454          EnumWindows(cb, processId);
455  
456          DWORD waitResult = WaitForSingleObject(handle, 3000);
457          if (waitResult != WAIT_OBJECT_0)
458          {
459              TerminateProcess(handle, 0);
460          }
461      }
462  
463      void shutdown_processes()
464      {
465          const auto services = getProcessHandlesByName(L"PowerToys.MouseWithoutBordersService.exe", PROCESS_TERMINATE);
466          for (const auto& svc : services)
467              TerminateProcess(svc.get(), 0);
468          wil::unique_process_handle s;
469  
470          std::array<std::wstring_view, 2> processes_names = { L"PowerToys.MouseWithoutBorders.exe",
471                                                               L"PowerToys.MouseWithoutBordersHelper.exe" };
472  
473          for (const auto process : processes_names)
474          {
475              const auto apps = getProcessHandlesByName(process, PROCESS_TERMINATE);
476              for (const auto& app : apps)
477                  shutdown_process(app.get());
478          }
479      }
480  
481      virtual void destroy() override
482      {
483          shutdown_processes();
484          TerminateProcess(p_info.hProcess, 1);
485          delete this;
486      }
487  
488      virtual const wchar_t* get_name() override
489      {
490          return MODULE_NAME;
491      }
492  
493      virtual bool get_config(wchar_t* buffer, int* buffer_size) override
494      {
495          HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
496  
497          PowerToysSettings::Settings settings(hinstance, get_name());
498          settings.set_description(MODULE_DESC);
499  
500          return settings.serialize_to_buffer(buffer, buffer_size);
501      }
502  
503      virtual const wchar_t* get_key() override
504      {
505          return app_key.c_str();
506      }
507  
508      virtual void set_config(const wchar_t* config) override
509      {
510          try
511          {
512              // Parse the input JSON string.
513              PowerToysSettings::PowerToyValues values =
514                  PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
515  
516              update_state_from_settings(values);
517              // If you don't need to do any custom processing of the settings, proceed
518              // to persists the values.
519              values.save_to_settings_file();
520          }
521          catch (std::exception&)
522          {
523              // Improper JSON.
524          }
525      }
526  
527      virtual void enable()
528      {
529          Trace::MouseWithoutBorders::Enable(true);
530  
531          launch_process();
532  
533          m_enabled = true;
534      };
535  
536      virtual void disable()
537      {
538          if (m_enabled)
539          {
540              Trace::MouseWithoutBorders::Enable(false);
541              Logger::trace(L"Disabling MouseWithoutBorders...");
542  
543              Logger::trace(L"Signaled exit event for PowerToys MouseWithoutBorders.");
544              TerminateProcess(p_info.hProcess, 1);
545  
546              shutdown_processes();
547  
548              CloseHandle(p_info.hProcess);
549          }
550  
551          m_enabled = false;
552      }
553  
554      virtual bool is_enabled() override
555      {
556          return m_enabled;
557      }
558  
559      virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
560      {
561          constexpr size_t num_hotkeys = 4; // We have 4 hotkeys
562  
563          if (hotkeys && buffer_size >= num_hotkeys)
564          {
565              PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(MODULE_NAME);
566  
567              // Cache the raw JSON object to avoid multiple parsing
568              json::JsonObject root_json = values.get_raw_json();
569              json::JsonObject properties_json = root_json.GetNamedObject(L"properties", json::JsonObject{});
570  
571              size_t hotkey_index = 0;
572  
573              // Helper lambda to extract hotkey from JSON properties
574              auto extract_hotkey = [&](const wchar_t* property_name) -> Hotkey {
575                  if (properties_json.HasKey(property_name))
576                  {
577                      try
578                      {
579                          json::JsonObject hotkey_json = properties_json.GetNamedObject(property_name);
580  
581                          // Extract hotkey properties directly from JSON
582                          bool win = hotkey_json.GetNamedBoolean(L"win", false);
583                          bool ctrl = hotkey_json.GetNamedBoolean(L"ctrl", false);
584                          bool alt = hotkey_json.GetNamedBoolean(L"alt", false);
585                          bool shift = hotkey_json.GetNamedBoolean(L"shift", false);
586                          unsigned char key = static_cast<unsigned char>(
587                              hotkey_json.GetNamedNumber(L"code", 0));
588  
589                          return { win, ctrl, shift, alt, key };
590                      }
591                      catch (...)
592                      {
593                          // If parsing individual hotkey fails, use defaults
594                          return { false, false, false, false, 0 };
595                      }
596                  }
597                  else
598                  {
599                      // Property doesn't exist, use defaults
600                      return { false, false, false, false, 0 };
601                  }
602              };
603  
604              // Extract all hotkeys using the optimized helper
605              hotkeys[hotkey_index++] = extract_hotkey(L"ToggleEasyMouseShortcut"); // [0] Toggle Easy Mouse
606              hotkeys[hotkey_index++] = extract_hotkey(L"LockMachineShortcut");     // [1] Lock Machine
607              hotkeys[hotkey_index++] = extract_hotkey(L"Switch2AllPCShortcut");    // [2] Switch to All PCs
608              hotkeys[hotkey_index++] = extract_hotkey(L"ReconnectShortcut");       // [3] Reconnect
609          }
610  
611          return num_hotkeys;
612      }
613  
614      void launch_add_firewall_process()
615      {
616          Logger::trace(L"Starting Process to add firewall rule");
617  
618          std::wstring executable_path = get_module_folderpath();
619          executable_path.append(L"\\PowerToys.MouseWithoutBorders.exe");
620  
621          std::wstring executable_args = L"";
622          executable_args.append(L"/S /c \"");
623          executable_args.append(L"echo \"Deleting existing inbound firewall rules for PowerToys.MouseWithoutBorders.exe\"");
624          executable_args.append(L" & netsh advfirewall firewall delete rule dir=in name=all program=\"");
625          executable_args.append(executable_path);
626          executable_args.append(L"\" & echo \"Adding an inbound firewall rule for PowerToys.MouseWithoutBorders.exe\"");
627          executable_args.append(L" & netsh advfirewall firewall add rule name=\"PowerToys.MouseWithoutBorders\" dir=in action=allow program=\"");
628          executable_args.append(executable_path);
629          executable_args.append(L"\" enable=yes remoteip=any profile=any protocol=tcp & pause\"");
630  
631          SHELLEXECUTEINFOW sei{ sizeof(sei) };
632          sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
633          sei.lpFile = L"cmd.exe";
634          sei.nShow = SW_SHOWNORMAL;
635          sei.lpParameters = executable_args.data();
636          sei.lpVerb = L"runas";
637  
638          if (ShellExecuteExW(&sei))
639          {
640              Logger::trace("Successfully started the firewall rule adding process");
641          }
642          else
643          {
644              Logger::error(L"The firewall rule adding process failed to start. {}", get_last_error_or_default(GetLastError()));
645          }
646      }
647  
648      virtual void call_custom_action(const wchar_t* action) override
649      {
650          try
651          {
652              PowerToysSettings::CustomActionObject action_object =
653                  PowerToysSettings::CustomActionObject::from_json_string(action);
654  
655              if (action_object.get_name() == L"add_firewall")
656              {
657                  launch_add_firewall_process();
658                  Trace::MouseWithoutBorders::AddFirewallRule();
659              }
660              else if (action_object.get_name() == L"uninstall_service")
661              {
662                  unregister_service();
663              }
664          }
665          catch (std::exception&)
666          {
667              Logger::error(L"Failed to parse action. {}", action);
668          }
669      }
670  };
671  
672  extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
673  {
674      return new MouseWithoutBorders();
675  }