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 }