/ src / modules / MouseUtils / FindMyMouse / dllmain.cpp
dllmain.cpp
  1  #include "pch.h"
  2  #include <interface/powertoy_module_interface.h>
  3  #include <common/SettingsAPI/settings_objects.h>
  4  #include "trace.h"
  5  #include "FindMyMouse.h"
  6  #include "WinHookEventIDs.h"
  7  #include <thread>
  8  #include <common/utils/logger_helper.h>
  9  #include <common/utils/color.h>
 10  #include <common/utils/string_utils.h>
 11  #include <common/utils/EventWaiter.h>
 12  #include <common/interop/shared_constants.h>
 13  
 14  namespace
 15  {
 16      const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
 17      const wchar_t JSON_KEY_VALUE[] = L"value";
 18      const wchar_t JSON_KEY_ACTIVATION_METHOD[] = L"activation_method";
 19      const wchar_t JSON_KEY_INCLUDE_WIN_KEY[] = L"include_win_key";
 20      const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode";
 21      const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color";
 22      const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color";
 23      const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; // legacy only (migrated into color alpha)
 24      const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius";
 25      const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms";
 26      const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom";
 27      const wchar_t JSON_KEY_EXCLUDED_APPS[] = L"excluded_apps";
 28      const wchar_t JSON_KEY_SHAKING_MINIMUM_DISTANCE[] = L"shaking_minimum_distance";
 29      const wchar_t JSON_KEY_SHAKING_INTERVAL_MS[] = L"shaking_interval_ms";
 30      const wchar_t JSON_KEY_SHAKING_FACTOR[] = L"shaking_factor";
 31      const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
 32  }
 33  
 34  extern "C" IMAGE_DOS_HEADER __ImageBase;
 35  
 36  HMODULE m_hModule;
 37  
 38  BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
 39  {
 40      m_hModule = hModule;
 41      switch (ul_reason_for_call)
 42      {
 43      case DLL_PROCESS_ATTACH:
 44          Trace::RegisterProvider();
 45          break;
 46      case DLL_THREAD_ATTACH:
 47      case DLL_THREAD_DETACH:
 48          break;
 49      case DLL_PROCESS_DETACH:
 50          Trace::UnregisterProvider();
 51          break;
 52      }
 53      return TRUE;
 54  }
 55  
 56  // The PowerToy name that will be shown in the settings.
 57  const static wchar_t* MODULE_NAME = L"FindMyMouse";
 58  // Add a description that will we shown in the module settings page.
 59  const static wchar_t* MODULE_DESC = L"Focus the mouse pointer";
 60  
 61  // Implement the PowerToy Module Interface and all the required methods.
 62  class FindMyMouse : public PowertoyModuleIface
 63  {
 64  private:
 65      // The PowerToy state.
 66      bool m_enabled = false;
 67  
 68      // Hotkey to invoke the module
 69      HotkeyEx m_hotkey;
 70  
 71      // Find My Mouse specific settings
 72      FindMyMouseSettings m_findMyMouseSettings;
 73  
 74      // Event-driven trigger support
 75      EventWaiter m_triggerEventWaiter;
 76  
 77      // Load initial settings from the persisted values.
 78      void init_settings();
 79  
 80      // Helper function to extract the settings
 81      void parse_settings(PowerToysSettings::PowerToyValues& settings);
 82  
 83  public:
 84      // Constructor
 85      FindMyMouse()
 86      {
 87          LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::findMyMouseLoggerName);
 88          init_settings();
 89      };
 90  
 91      // Destroy the powertoy and free memory
 92      virtual void destroy() override
 93      {
 94          // Ensure threads/handles are cleaned up before destruction
 95          disable();
 96          delete this;
 97      }
 98  
 99      // Return the localized display name of the powertoy
100      virtual const wchar_t* get_name() override
101      {
102          return MODULE_NAME;
103      }
104  
105      // Return the non localized key of the powertoy, this will be cached by the runner
106      virtual const wchar_t* get_key() override
107      {
108          return MODULE_NAME;
109      }
110  
111      // Return the configured status for the gpo policy for the module
112      virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
113      {
114          return powertoys_gpo::getConfiguredFindMyMouseEnabledValue();
115      }
116  
117      // Return JSON with the configuration options.
118      virtual bool get_config(wchar_t* buffer, int* buffer_size) override
119      {
120          HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
121  
122          // Create a Settings object.
123          PowerToysSettings::Settings settings(hinstance, get_name());
124          settings.set_description(MODULE_DESC);
125  
126          return settings.serialize_to_buffer(buffer, buffer_size);
127      }
128  
129      // Signal from the Settings editor to call a custom action.
130      // This can be used to spawn more complex editors.
131      virtual void call_custom_action(const wchar_t* action) override
132      {
133      }
134  
135      // Called by the runner to pass the updated settings values as a serialized JSON.
136      virtual void set_config(const wchar_t* config) override
137      {
138          try
139          {
140              // Parse the input JSON string.
141              PowerToysSettings::PowerToyValues values =
142                  PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
143  
144              parse_settings(values);
145  
146              FindMyMouseApplySettings(m_findMyMouseSettings);
147          }
148          catch (std::exception&)
149          {
150              // Improper JSON.
151          }
152      }
153  
154      // Enable the powertoy
155      virtual void enable()
156      {
157          m_enabled = true;
158          Trace::EnableFindMyMouse(true);
159          std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach();
160  
161          // Start listening for external trigger event so we can invoke the same logic as the hotkey.
162          m_triggerEventWaiter.start(CommonSharedConstants::FIND_MY_MOUSE_TRIGGER_EVENT, [this](DWORD) {
163              OnHotkeyEx();
164          });
165      }
166  
167      // Disable the powertoy
168      virtual void disable()
169      {
170          m_enabled = false;
171          Trace::EnableFindMyMouse(false);
172          FindMyMouseDisable();
173  
174          m_triggerEventWaiter.stop();
175      }
176  
177      // Returns if the powertoys is enabled
178      virtual bool is_enabled() override
179      {
180          return m_enabled;
181      }
182  
183      virtual std::optional<HotkeyEx> GetHotkeyEx() override
184      {
185          Logger::trace("GetHotkeyEx()");
186          if (m_findMyMouseSettings.activationMethod == FindMyMouseActivationMethod::Shortcut)
187          {
188              return m_hotkey;
189          }
190  
191          return std::nullopt;
192      }
193  
194      virtual void OnHotkeyEx() override
195      {
196          Logger::trace("OnHotkeyEx()");
197          HWND hwnd = GetSonarHwnd();
198          if (hwnd != nullptr)
199          {
200              PostMessageW(hwnd, WM_PRIV_SHORTCUT, NULL, NULL);
201          }
202      }
203  };
204  
205  // Load the settings file.
206  void FindMyMouse::init_settings()
207  {
208      try
209      {
210          // Load and parse the settings file for this PowerToy.
211          PowerToysSettings::PowerToyValues settings =
212              PowerToysSettings::PowerToyValues::load_from_settings_file(FindMyMouse::get_key());
213          parse_settings(settings);
214      }
215      catch (std::exception&)
216      {
217          // Error while loading from the settings file. Let default values stay as they are.
218      }
219  }
220  
221  inline static uint8_t LegacyOpacityToAlpha(int overlayOpacityPercent)
222  {
223      if (overlayOpacityPercent < 0)
224      {
225          return 255; // fallback: fully opaque
226      }
227  
228      if (overlayOpacityPercent > 100)
229      {
230          overlayOpacityPercent = 100;
231      }
232  
233      // Round to nearest integer (0–255)
234      return static_cast<uint8_t>((overlayOpacityPercent * 255 + 50) / 100);
235  }
236  
237  void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
238  {
239      auto settingsObject = settings.get_raw_json();
240      FindMyMouseSettings findMyMouseSettings;
241  
242      if (!settingsObject.GetView().Size())
243      {
244          Logger::info("Find My Mouse settings are empty");
245          m_findMyMouseSettings = findMyMouseSettings;
246          return;
247      }
248  
249      // Early exit if no properties object exists
250      if (!settingsObject.HasKey(JSON_KEY_PROPERTIES))
251      {
252          Logger::info("Find My Mouse settings have no properties");
253          m_findMyMouseSettings = findMyMouseSettings;
254          return;
255      }
256  
257      auto properties = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
258  
259      // Parse Activation Method
260      if (properties.HasKey(JSON_KEY_ACTIVATION_METHOD))
261      {
262          try
263          {
264              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_ACTIVATION_METHOD);
265              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
266              if (value < static_cast<int>(FindMyMouseActivationMethod::EnumElements) && value >= 0)
267              {
268                  std::wstring version = (std::wstring)settingsObject.GetNamedString(L"version");
269                  if (version == L"1.0" && value == 1)
270                  {
271                      findMyMouseSettings.activationMethod = FindMyMouseActivationMethod::ShakeMouse;
272                  }
273                  else
274                  {
275                      findMyMouseSettings.activationMethod = static_cast<FindMyMouseActivationMethod>(value);
276                  }
277              }
278              else
279              {
280                  throw std::runtime_error("Invalid Activation Method value");
281              }
282          }
283          catch (...)
284          {
285              Logger::warn("Failed to initialize Activation Method from settings. Will use default value");
286          }
287      }
288  
289      // Parse Include Win Key
290      if (properties.HasKey(JSON_KEY_INCLUDE_WIN_KEY))
291      {
292          try
293          {
294              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_INCLUDE_WIN_KEY);
295              findMyMouseSettings.includeWinKey = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
296          }
297          catch (...)
298          {
299              Logger::warn("Failed to get 'include windows key with ctrl' setting");
300          }
301      }
302  
303      // Parse Do Not Activate On Game Mode
304      if (properties.HasKey(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE))
305      {
306          try
307          {
308              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE);
309              findMyMouseSettings.doNotActivateOnGameMode = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
310          }
311          catch (...)
312          {
313              Logger::warn("Failed to get 'do not activate on game mode' setting");
314          }
315      }
316  
317      // Colors + legacy overlay opacity migration
318      // Desired behavior:
319      //  - Old schema: colors stored as RGB (no alpha) + separate overlay_opacity (0-100). We should migrate by applying that opacity as alpha.
320      //  - New schema: colors stored as ARGB (alpha embedded). Ignore overlay_opacity even if still present.
321      int legacyOverlayOpacity = -1;
322      bool backgroundColorHadExplicitAlpha = false;
323      bool spotlightColorHadExplicitAlpha = false;
324  
325      // Parse Legacy Overlay Opacity (may not exist in newer settings)
326      if (properties.HasKey(JSON_KEY_OVERLAY_OPACITY))
327      {
328          try
329          {
330              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
331              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
332              if (value >= 0 && value <= 100)
333              {
334                  legacyOverlayOpacity = value;
335              }
336          }
337          catch (...)
338          {
339              // overlay_opacity may have invalid data
340          }
341      }
342  
343      // Parse Background Color
344      if (properties.HasKey(JSON_KEY_BACKGROUND_COLOR))
345      {
346          try
347          {
348              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
349              auto backgroundColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
350              uint8_t a = 255, r, g, b;
351              bool parsed = false;
352              if (checkValidARGB(backgroundColorStr, &a, &r, &g, &b))
353              {
354                  parsed = true;
355                  backgroundColorHadExplicitAlpha = true; // New schema with alpha present
356              }
357              else if (checkValidRGB(backgroundColorStr, &r, &g, &b))
358              {
359                  a = LegacyOpacityToAlpha(legacyOverlayOpacity);
360                  parsed = true; // Old schema (no alpha component)
361              }
362              if (parsed)
363              {
364                  findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
365              }
366              else
367              {
368                  Logger::error("Background color value is invalid. Will use default");
369              }
370          }
371          catch (...)
372          {
373              Logger::warn("Failed to initialize background color from settings. Will use default value");
374          }
375      }
376  
377      // Parse Spotlight Color
378      if (properties.HasKey(JSON_KEY_SPOTLIGHT_COLOR))
379      {
380          try
381          {
382              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR);
383              auto spotlightColorStr = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
384              uint8_t a = 255, r, g, b;
385              bool parsed = false;
386              if (checkValidARGB(spotlightColorStr, &a, &r, &g, &b))
387              {
388                  parsed = true;
389                  spotlightColorHadExplicitAlpha = true;
390              }
391              else if (checkValidRGB(spotlightColorStr, &r, &g, &b))
392              {
393                  a = LegacyOpacityToAlpha(legacyOverlayOpacity);
394                  parsed = true;
395              }
396              if (parsed)
397              {
398                  findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
399              }
400              else
401              {
402                  Logger::error("Spotlight color value is invalid. Will use default");
403              }
404          }
405          catch (...)
406          {
407              Logger::warn("Failed to initialize spotlight color from settings. Will use default value");
408          }
409      }
410  
411      // Parse Spotlight Radius
412      if (properties.HasKey(JSON_KEY_SPOTLIGHT_RADIUS))
413      {
414          try
415          {
416              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS);
417              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
418              if (value >= 0)
419              {
420                  findMyMouseSettings.spotlightRadius = value;
421              }
422              else
423              {
424                  throw std::runtime_error("Invalid Spotlight Radius value");
425              }
426          }
427          catch (...)
428          {
429              Logger::warn("Failed to initialize Spotlight Radius from settings. Will use default value");
430          }
431      }
432  
433      // Parse Animation Duration
434      if (properties.HasKey(JSON_KEY_ANIMATION_DURATION_MS))
435      {
436          try
437          {
438              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_ANIMATION_DURATION_MS);
439              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
440              if (value >= 0)
441              {
442                  findMyMouseSettings.animationDurationMs = value;
443              }
444              else
445              {
446                  throw std::runtime_error("Invalid Animation Duration value");
447              }
448          }
449          catch (...)
450          {
451              Logger::warn("Failed to initialize Animation Duration from settings. Will use default value");
452          }
453      }
454  
455      // Parse Spotlight Initial Zoom
456      if (properties.HasKey(JSON_KEY_SPOTLIGHT_INITIAL_ZOOM))
457      {
458          try
459          {
460              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SPOTLIGHT_INITIAL_ZOOM);
461              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
462              if (value >= 0)
463              {
464                  findMyMouseSettings.spotlightInitialZoom = value;
465              }
466              else
467              {
468                  throw std::runtime_error("Invalid Spotlight Initial Zoom value");
469              }
470          }
471          catch (...)
472          {
473              Logger::warn("Failed to initialize Spotlight Initial Zoom from settings. Will use default value");
474          }
475      }
476  
477      // Parse Excluded Apps
478      if (properties.HasKey(JSON_KEY_EXCLUDED_APPS))
479      {
480          try
481          {
482              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_EXCLUDED_APPS);
483              std::wstring apps = jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE).c_str();
484              std::vector<std::wstring> excludedApps;
485              auto excludedUppercase = apps;
486              CharUpperBuffW(excludedUppercase.data(), static_cast<DWORD>(excludedUppercase.length()));
487              std::wstring_view view(excludedUppercase);
488              view = left_trim<wchar_t>(trim<wchar_t>(view));
489  
490              while (!view.empty())
491              {
492                  auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
493                  excludedApps.emplace_back(view.substr(0, pos));
494                  view.remove_prefix(pos);
495                  view = left_trim<wchar_t>(trim<wchar_t>(view));
496              }
497  
498              findMyMouseSettings.excludedApps = excludedApps;
499          }
500          catch (...)
501          {
502              Logger::warn("Failed to initialize Excluded Apps from settings. Will use default value");
503          }
504      }
505  
506      // Parse Shaking Minimum Distance
507      if (properties.HasKey(JSON_KEY_SHAKING_MINIMUM_DISTANCE))
508      {
509          try
510          {
511              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SHAKING_MINIMUM_DISTANCE);
512              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
513              if (value >= 0)
514              {
515                  findMyMouseSettings.shakeMinimumDistance = value;
516              }
517              else
518              {
519                  throw std::runtime_error("Invalid Shaking Minimum Distance value");
520              }
521          }
522          catch (...)
523          {
524              Logger::warn("Failed to initialize Shaking Minimum Distance from settings. Will use default value");
525          }
526      }
527  
528      // Parse Shaking Interval Milliseconds
529      if (properties.HasKey(JSON_KEY_SHAKING_INTERVAL_MS))
530      {
531          try
532          {
533              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SHAKING_INTERVAL_MS);
534              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
535              if (value >= 0)
536              {
537                  findMyMouseSettings.shakeIntervalMs = value;
538              }
539              else
540              {
541                  throw std::runtime_error("Invalid Shaking Interval Milliseconds value");
542              }
543          }
544          catch (...)
545          {
546              Logger::warn("Failed to initialize Shaking Interval Milliseconds from settings. Will use default value");
547          }
548      }
549  
550      // Parse Shaking Factor
551      if (properties.HasKey(JSON_KEY_SHAKING_FACTOR))
552      {
553          try
554          {
555              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_SHAKING_FACTOR);
556              int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
557              if (value >= 0)
558              {
559                  findMyMouseSettings.shakeFactor = value;
560              }
561              else
562              {
563                  throw std::runtime_error("Invalid Shaking Factor value");
564              }
565          }
566          catch (...)
567          {
568              Logger::warn("Failed to initialize Shaking Factor from settings. Will use default value");
569          }
570      }
571  
572      // Parse HotKey
573      if (properties.HasKey(JSON_KEY_ACTIVATION_SHORTCUT))
574      {
575          try
576          {
577              auto jsonPropertiesObject = properties.GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
578              auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
579              m_hotkey = HotkeyEx();
580              if (hotkey.win_pressed())
581              {
582                  m_hotkey.modifiersMask |= MOD_WIN;
583              }
584  
585              if (hotkey.ctrl_pressed())
586              {
587                  m_hotkey.modifiersMask |= MOD_CONTROL;
588              }
589  
590              if (hotkey.shift_pressed())
591              {
592                  m_hotkey.modifiersMask |= MOD_SHIFT;
593              }
594  
595              if (hotkey.alt_pressed())
596              {
597                  m_hotkey.modifiersMask |= MOD_ALT;
598              }
599  
600              m_hotkey.vkCode = static_cast<WORD>(hotkey.get_code());
601          }
602          catch (...)
603          {
604              Logger::warn("Failed to initialize Activation Shortcut from settings. Will use default value");
605          }
606      }
607  
608      if (!m_hotkey.modifiersMask)
609      {
610          Logger::info("Using default Activation Shortcut");
611          m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
612          m_hotkey.vkCode = 0x46; // F key
613      }
614  
615      m_findMyMouseSettings = findMyMouseSettings;
616  }
617  
618  extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
619  {
620      return new FindMyMouse();
621  }