dllmain.cpp
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 #include "pch.h" 6 #include "../../../interface/powertoy_module_interface.h" 7 #include "../../../common/SettingsAPI/settings_objects.h" 8 #include "trace.h" 9 #include "../../../common/utils/process_path.h" 10 #include "../../../common/utils/resources.h" 11 #include "../../../common/logger/logger.h" 12 #include "../../../common/utils/logger_helper.h" 13 #include "../../../common/interop/shared_constants.h" 14 #include <atomic> 15 #include <thread> 16 #include <vector> 17 #include <map> 18 #include <string> 19 #include <algorithm> 20 #include <windows.h> 21 #include <dbt.h> 22 #include <sstream> 23 #include "resource.h" 24 #include "CursorWrapCore.h" 25 26 // Disable C26451 arithmetic overflow warning for this file since the operations are safe in this context 27 #pragma warning(disable: 26451) 28 29 extern "C" IMAGE_DOS_HEADER __ImageBase; 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 Trace::RegisterProvider(); 37 break; 38 case DLL_THREAD_ATTACH: 39 case DLL_THREAD_DETACH: 40 break; 41 case DLL_PROCESS_DETACH: 42 Trace::UnregisterProvider(); 43 break; 44 } 45 return TRUE; 46 } 47 48 // Non-Localizable strings 49 namespace 50 { 51 const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; 52 const wchar_t JSON_KEY_VALUE[] = L"value"; 53 const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut"; 54 const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate"; 55 const wchar_t JSON_KEY_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag"; 56 const wchar_t JSON_KEY_WRAP_MODE[] = L"wrap_mode"; 57 const wchar_t JSON_KEY_DISABLE_ON_SINGLE_MONITOR[] = L"disable_cursor_wrap_on_single_monitor"; 58 } 59 60 // The PowerToy name that will be shown in the settings. 61 const static wchar_t* MODULE_NAME = L"CursorWrap"; 62 // Add a description that will we shown in the module settings page. 63 const static wchar_t* MODULE_DESC = L"<no description>"; 64 65 // Monitor device interface GUID for RegisterDeviceNotification 66 // {e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} 67 static const GUID GUID_DEVINTERFACE_MONITOR = 68 { 0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 } }; 69 70 // Forward declaration 71 class CursorWrap; 72 73 // Global instance pointer for the mouse hook 74 static CursorWrap* g_cursorWrapInstance = nullptr; 75 76 // Implement the PowerToy Module Interface and all the required methods. 77 class CursorWrap : public PowertoyModuleIface 78 { 79 private: 80 // The PowerToy state. 81 bool m_enabled = false; 82 bool m_autoActivate = false; 83 bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag 84 bool m_disableOnSingleMonitor = false; // Default to false 85 int m_wrapMode = 0; // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly 86 87 // Mouse hook 88 HHOOK m_mouseHook = nullptr; 89 std::atomic<bool> m_hookActive{ false }; 90 91 // Core wrapping engine (edge-based polygon model) 92 CursorWrapCore m_core; 93 94 // Hotkey 95 Hotkey m_activationHotkey{}; 96 97 // Event-driven trigger support (for CmdPal/automation) 98 HANDLE m_triggerEventHandle = nullptr; 99 HANDLE m_terminateEventHandle = nullptr; 100 std::thread m_eventThread; 101 std::atomic_bool m_listening{ false }; 102 103 // Display change notification 104 HWND m_messageWindow = nullptr; 105 HDEVNOTIFY m_deviceNotify = nullptr; 106 static constexpr UINT_PTR TIMER_UPDATE_MONITORS = 1; 107 static constexpr UINT DEBOUNCE_DELAY_MS = 500; 108 109 public: 110 // Constructor 111 CursorWrap() 112 { 113 LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::cursorWrapLoggerName); 114 init_settings(); 115 m_core.UpdateMonitorInfo(); 116 g_cursorWrapInstance = this; // Set global instance pointer 117 }; 118 119 // Destroy the powertoy and free memory 120 virtual void destroy() override 121 { 122 // Ensure hooks/threads/handles are torn down before deletion 123 disable(); 124 g_cursorWrapInstance = nullptr; // Clear global instance pointer 125 delete this; 126 } 127 128 // Return the localized display name of the powertoy 129 virtual const wchar_t* get_name() override 130 { 131 return MODULE_NAME; 132 } 133 134 // Return the non localized key of the powertoy, this will be cached by the runner 135 virtual const wchar_t* get_key() override 136 { 137 return MODULE_NAME; 138 } 139 140 // Return the configured status for the gpo policy for the module 141 virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override 142 { 143 return powertoys_gpo::getConfiguredCursorWrapEnabledValue(); 144 } 145 146 // Return JSON with the configuration options. 147 virtual bool get_config(wchar_t* buffer, int* buffer_size) override 148 { 149 HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase); 150 151 PowerToysSettings::Settings settings(hinstance, get_name()); 152 153 settings.set_description(IDS_CURSORWRAP_NAME); 154 settings.set_icon_key(L"pt-cursor-wrap"); 155 156 // Create HotkeyObject from the Hotkey struct for the settings 157 auto hotkey_object = PowerToysSettings::HotkeyObject::from_settings( 158 m_activationHotkey.win, 159 m_activationHotkey.ctrl, 160 m_activationHotkey.alt, 161 m_activationHotkey.shift, 162 m_activationHotkey.key); 163 164 settings.add_hotkey(JSON_KEY_ACTIVATION_SHORTCUT, IDS_CURSORWRAP_NAME, hotkey_object); 165 settings.add_bool_toggle(JSON_KEY_AUTO_ACTIVATE, IDS_CURSORWRAP_NAME, m_autoActivate); 166 settings.add_bool_toggle(JSON_KEY_DISABLE_WRAP_DURING_DRAG, IDS_CURSORWRAP_NAME, m_disableWrapDuringDrag); 167 168 return settings.serialize_to_buffer(buffer, buffer_size); 169 } 170 171 // Signal from the Settings editor to call a custom action. 172 // This can be used to spawn more complex editors. 173 virtual void call_custom_action(const wchar_t* /*action*/) override {} 174 175 // Called by the runner to pass the updated settings values as a serialized JSON. 176 virtual void set_config(const wchar_t* config) override 177 { 178 try 179 { 180 // Parse the input JSON string. 181 PowerToysSettings::PowerToyValues values = 182 PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); 183 184 parse_settings(values); 185 } 186 catch (std::exception&) 187 { 188 Logger::error("Invalid json when trying to parse CursorWrap settings json."); 189 } 190 } 191 192 // Enable the powertoy 193 virtual void enable() 194 { 195 m_enabled = true; 196 Trace::EnableCursorWrap(true); 197 198 // Start listening for external trigger event so we can invoke the same logic as the activation hotkey. 199 m_triggerEventHandle = CreateEventW(nullptr, false, false, CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT); 200 m_terminateEventHandle = CreateEventW(nullptr, false, false, nullptr); 201 if (m_triggerEventHandle) 202 { 203 ResetEvent(m_triggerEventHandle); 204 } 205 if (m_triggerEventHandle && m_terminateEventHandle) 206 { 207 m_listening = true; 208 m_eventThread = std::thread([this]() { 209 HANDLE handles[2] = { m_triggerEventHandle, m_terminateEventHandle }; 210 211 // WH_MOUSE_LL callbacks are delivered to the thread that installed the hook. 212 // Ensure this thread has a message queue and pumps messages while the hook is active. 213 MSG msg; 214 PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); 215 216 // Create message window for display change notifications 217 RegisterForDisplayChanges(); 218 219 // Only start the mouse hook automatically if auto-activate is enabled 220 if (m_autoActivate) 221 { 222 StartMouseHook(); 223 Logger::info("CursorWrap enabled - mouse hook started (auto-activate on)"); 224 } 225 else 226 { 227 Logger::info("CursorWrap enabled - waiting for activation hotkey (auto-activate off)"); 228 } 229 230 while (m_listening) 231 { 232 auto res = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT); 233 if (!m_listening) 234 { 235 break; 236 } 237 238 if (res == WAIT_OBJECT_0) 239 { 240 ToggleMouseHook(); 241 } 242 else if (res == WAIT_OBJECT_0 + 1) 243 { 244 break; 245 } 246 else 247 { 248 while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 249 { 250 TranslateMessage(&msg); 251 DispatchMessage(&msg); 252 } 253 } 254 } 255 256 // Cleanup display change notifications 257 UnregisterDisplayChanges(); 258 259 StopMouseHook(); 260 Logger::info("CursorWrap event listener stopped"); 261 }); 262 } 263 } 264 265 // Disable the powertoy 266 virtual void disable() 267 { 268 m_enabled = false; 269 Trace::EnableCursorWrap(false); 270 271 m_listening = false; 272 if (m_terminateEventHandle) 273 { 274 SetEvent(m_terminateEventHandle); 275 } 276 if (m_eventThread.joinable()) 277 { 278 m_eventThread.join(); 279 } 280 if (m_triggerEventHandle) 281 { 282 CloseHandle(m_triggerEventHandle); 283 m_triggerEventHandle = nullptr; 284 } 285 if (m_terminateEventHandle) 286 { 287 CloseHandle(m_terminateEventHandle); 288 m_terminateEventHandle = nullptr; 289 } 290 } 291 292 // Returns if the powertoys is enabled 293 virtual bool is_enabled() override 294 { 295 return m_enabled; 296 } 297 298 // Returns whether the PowerToys should be enabled by default 299 virtual bool is_enabled_by_default() const override 300 { 301 return false; 302 } 303 304 // Legacy hotkey support 305 virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override 306 { 307 if (buffer && buffer_size >= 1) 308 { 309 buffer[0] = m_activationHotkey; 310 } 311 return 1; 312 } 313 314 virtual bool on_hotkey(size_t hotkeyId) override 315 { 316 if (!m_enabled || hotkeyId != 0) 317 { 318 return false; 319 } 320 321 // Toggle on the thread that owns the WH_MOUSE_LL hook (the event listener thread). 322 if (m_triggerEventHandle) 323 { 324 return SetEvent(m_triggerEventHandle); 325 } 326 327 return false; 328 } 329 330 // Called when display configuration changes - update monitor topology 331 void OnDisplayChange() 332 { 333 #ifdef _DEBUG 334 OutputDebugStringW(L"[CursorWrap] Display configuration changed, updating monitor topology\n"); 335 #endif 336 Logger::info("Display configuration changed, updating monitor topology"); 337 m_core.UpdateMonitorInfo(); 338 } 339 340 private: 341 void ToggleMouseHook() 342 { 343 // Toggle cursor wrapping. 344 if (m_hookActive) 345 { 346 StopMouseHook(); 347 } 348 else 349 { 350 StartMouseHook(); 351 } 352 } 353 354 // Load the settings file. 355 void init_settings() 356 { 357 try 358 { 359 // Load and parse the settings file for this PowerToy. 360 PowerToysSettings::PowerToyValues settings = 361 PowerToysSettings::PowerToyValues::load_from_settings_file(CursorWrap::get_key()); 362 parse_settings(settings); 363 } 364 catch (std::exception&) 365 { 366 Logger::error("Invalid json when trying to load the CursorWrap settings json from file."); 367 } 368 } 369 370 void parse_settings(PowerToysSettings::PowerToyValues& settings) 371 { 372 auto settingsObject = settings.get_raw_json(); 373 if (settingsObject.GetView().Size()) 374 { 375 try 376 { 377 // Parse activation HotKey 378 auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT); 379 auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject); 380 381 m_activationHotkey.win = hotkey.win_pressed(); 382 m_activationHotkey.ctrl = hotkey.ctrl_pressed(); 383 m_activationHotkey.shift = hotkey.shift_pressed(); 384 m_activationHotkey.alt = hotkey.alt_pressed(); 385 m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code()); 386 } 387 catch (...) 388 { 389 Logger::warn("Failed to initialize CursorWrap activation shortcut"); 390 } 391 392 try 393 { 394 // Parse auto activate 395 auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_AUTO_ACTIVATE); 396 m_autoActivate = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); 397 } 398 catch (...) 399 { 400 Logger::warn("Failed to initialize CursorWrap auto activate from settings. Will use default value"); 401 } 402 403 try 404 { 405 // Parse disable wrap during drag 406 auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); 407 if (propertiesObject.HasKey(JSON_KEY_DISABLE_WRAP_DURING_DRAG)) 408 { 409 auto disableDragObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_WRAP_DURING_DRAG); 410 m_disableWrapDuringDrag = disableDragObject.GetNamedBoolean(JSON_KEY_VALUE); 411 } 412 } 413 catch (...) 414 { 415 Logger::warn("Failed to initialize CursorWrap disable wrap during drag from settings. Will use default value (true)"); 416 } 417 418 try 419 { 420 // Parse wrap mode 421 auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); 422 if (propertiesObject.HasKey(JSON_KEY_WRAP_MODE)) 423 { 424 auto wrapModeObject = propertiesObject.GetNamedObject(JSON_KEY_WRAP_MODE); 425 m_wrapMode = static_cast<int>(wrapModeObject.GetNamedNumber(JSON_KEY_VALUE)); 426 } 427 } 428 catch (...) 429 { 430 Logger::warn("Failed to initialize CursorWrap wrap mode from settings. Will use default value (0=Both)"); 431 } 432 433 try 434 { 435 // Parse disable on single monitor 436 auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); 437 if (propertiesObject.HasKey(JSON_KEY_DISABLE_ON_SINGLE_MONITOR)) 438 { 439 auto disableOnSingleMonitorObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_ON_SINGLE_MONITOR); 440 m_disableOnSingleMonitor = disableOnSingleMonitorObject.GetNamedBoolean(JSON_KEY_VALUE); 441 } 442 } 443 catch (...) 444 { 445 Logger::warn("Failed to initialize CursorWrap disable on single monitor from settings. Will use default value (false)"); 446 } 447 } 448 else 449 { 450 Logger::info("CursorWrap settings are empty"); 451 } 452 453 // Set default hotkey if not configured 454 if (m_activationHotkey.key == 0) 455 { 456 m_activationHotkey.win = true; 457 m_activationHotkey.alt = true; 458 m_activationHotkey.ctrl = false; 459 m_activationHotkey.shift = false; 460 m_activationHotkey.key = 'U'; // Win+Alt+U 461 } 462 } 463 464 void StartMouseHook() 465 { 466 if (m_mouseHook || m_hookActive) 467 { 468 Logger::info("CursorWrap mouse hook already active"); 469 return; 470 } 471 472 // Refresh monitor info before starting hook 473 m_core.UpdateMonitorInfo(); 474 475 m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, GetModuleHandle(nullptr), 0); 476 if (m_mouseHook) 477 { 478 m_hookActive = true; 479 Logger::info("CursorWrap mouse hook started successfully"); 480 #ifdef _DEBUG 481 Logger::info(L"CursorWrap DEBUG: Hook installed"); 482 #endif 483 } 484 else 485 { 486 DWORD error = GetLastError(); 487 Logger::error(L"Failed to install CursorWrap mouse hook, error: {}", error); 488 } 489 } 490 491 void StopMouseHook() 492 { 493 if (m_mouseHook) 494 { 495 UnhookWindowsHookEx(m_mouseHook); 496 m_mouseHook = nullptr; 497 m_hookActive = false; 498 Logger::info("CursorWrap mouse hook stopped"); 499 #ifdef _DEBUG 500 Logger::info("CursorWrap DEBUG: Mouse hook stopped"); 501 #endif 502 } 503 } 504 505 void RegisterForDisplayChanges() 506 { 507 if (m_messageWindow) 508 { 509 return; // Already registered 510 } 511 512 // Create a hidden top-level window to receive broadcast messages 513 // NOTE: Message-only windows (HWND_MESSAGE parent) do NOT receive 514 // WM_DISPLAYCHANGE, WM_SETTINGCHANGE, or WM_DEVICECHANGE broadcasts. 515 // We must use a real (hidden) top-level window instead. 516 WNDCLASSEXW wc = { sizeof(WNDCLASSEXW) }; 517 wc.lpfnWndProc = MessageWindowProc; 518 wc.hInstance = GetModuleHandle(nullptr); 519 wc.lpszClassName = L"CursorWrapDisplayChangeWindow"; 520 521 RegisterClassExW(&wc); 522 523 // Create a hidden top-level window (not message-only) 524 // WS_EX_TOOLWINDOW prevents taskbar button, WS_POPUP with no size makes it invisible 525 m_messageWindow = CreateWindowExW( 526 WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, 527 L"CursorWrapDisplayChangeWindow", 528 nullptr, 529 WS_POPUP, // Minimal window style 530 0, 0, 0, 0, // Zero size = invisible 531 nullptr, // No parent - top-level window to receive broadcasts 532 nullptr, 533 GetModuleHandle(nullptr), 534 nullptr); 535 536 if (m_messageWindow) 537 { 538 #ifdef _DEBUG 539 OutputDebugStringW(L"[CursorWrap] Registered for display change notifications\n"); 540 #endif 541 Logger::info("Registered for display change notifications"); 542 543 // Register for device notifications (monitor hardware add/remove) 544 DEV_BROADCAST_DEVICEINTERFACE filter = {}; 545 filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); 546 filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; 547 filter.dbcc_classguid = GUID_DEVINTERFACE_MONITOR; 548 549 m_deviceNotify = RegisterDeviceNotificationW( 550 m_messageWindow, 551 &filter, 552 DEVICE_NOTIFY_WINDOW_HANDLE); 553 554 if (m_deviceNotify) 555 { 556 #ifdef _DEBUG 557 OutputDebugStringW(L"[CursorWrap] Registered for device notifications (monitor hardware changes)\n"); 558 #endif 559 Logger::info("Registered for device notifications (monitor hardware changes)"); 560 } 561 else 562 { 563 DWORD error = GetLastError(); 564 #ifdef _DEBUG 565 std::wostringstream oss; 566 oss << L"[CursorWrap] Failed to register device notifications. Error: " << error << L"\n"; 567 OutputDebugStringW(oss.str().c_str()); 568 #endif 569 Logger::warn("Failed to register device notifications. Error: {}", error); 570 } 571 } 572 else 573 { 574 DWORD error = GetLastError(); 575 Logger::error(L"Failed to create message window for display changes, error: {}", error); 576 } 577 } 578 579 void UnregisterDisplayChanges() 580 { 581 if (m_deviceNotify) 582 { 583 #ifdef _DEBUG 584 OutputDebugStringW(L"[CursorWrap] Unregistering device notifications...\n"); 585 #endif 586 UnregisterDeviceNotification(m_deviceNotify); 587 m_deviceNotify = nullptr; 588 Logger::info("Unregistered device notifications"); 589 } 590 591 if (m_messageWindow) 592 { 593 KillTimer(m_messageWindow, TIMER_UPDATE_MONITORS); 594 DestroyWindow(m_messageWindow); 595 m_messageWindow = nullptr; 596 UnregisterClassW(L"CursorWrapDisplayChangeWindow", GetModuleHandle(nullptr)); 597 #ifdef _DEBUG 598 OutputDebugStringW(L"[CursorWrap] Unregistered display change notifications\n"); 599 #endif 600 Logger::info("Unregistered display change notifications"); 601 } 602 } 603 604 static LRESULT CALLBACK MessageWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 605 { 606 if (!g_cursorWrapInstance) 607 { 608 return DefWindowProcW(hwnd, msg, wParam, lParam); 609 } 610 611 switch (msg) 612 { 613 case WM_DISPLAYCHANGE: 614 #ifdef _DEBUG 615 OutputDebugStringW(L"[CursorWrap] WM_DISPLAYCHANGE received - monitor resolution/DPI changed\n"); 616 #endif 617 Logger::info("WM_DISPLAYCHANGE received - resolution/DPI changed"); 618 // Debounce: Wait for multiple changes to settle 619 KillTimer(hwnd, TIMER_UPDATE_MONITORS); 620 SetTimer(hwnd, TIMER_UPDATE_MONITORS, DEBOUNCE_DELAY_MS, nullptr); 621 break; 622 623 case WM_SETTINGCHANGE: 624 if (wParam == SPI_SETWORKAREA) 625 { 626 #ifdef _DEBUG 627 OutputDebugStringW(L"[CursorWrap] WM_SETTINGCHANGE (SPI_SETWORKAREA) received - taskbar changed\n"); 628 #endif 629 Logger::info("WM_SETTINGCHANGE (SPI_SETWORKAREA) received"); 630 // Taskbar position/size changed 631 KillTimer(hwnd, TIMER_UPDATE_MONITORS); 632 SetTimer(hwnd, TIMER_UPDATE_MONITORS, DEBOUNCE_DELAY_MS, nullptr); 633 } 634 break; 635 636 case WM_DEVICECHANGE: 637 // Handle monitor hardware add/remove 638 if (wParam == DBT_DEVNODES_CHANGED) 639 { 640 #ifdef _DEBUG 641 OutputDebugStringW(L"[CursorWrap] DBT_DEVNODES_CHANGED received - monitor hardware change detected\n"); 642 #endif 643 Logger::info("DBT_DEVNODES_CHANGED received - monitor hardware change detected"); 644 // Debounce: Wait for multiple changes to settle 645 KillTimer(hwnd, TIMER_UPDATE_MONITORS); 646 SetTimer(hwnd, TIMER_UPDATE_MONITORS, DEBOUNCE_DELAY_MS, nullptr); 647 return TRUE; 648 } 649 break; 650 651 case WM_TIMER: 652 if (wParam == TIMER_UPDATE_MONITORS) 653 { 654 #ifdef _DEBUG 655 OutputDebugStringW(L"[CursorWrap] Debounce timer expired - triggering topology update\n"); 656 #endif 657 KillTimer(hwnd, TIMER_UPDATE_MONITORS); 658 g_cursorWrapInstance->OnDisplayChange(); 659 } 660 break; 661 } 662 663 return DefWindowProcW(hwnd, msg, wParam, lParam); 664 } 665 666 static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) 667 { 668 if (nCode >= 0 && wParam == WM_MOUSEMOVE) 669 { 670 auto* pMouseStruct = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam); 671 POINT currentPos = { pMouseStruct->pt.x, pMouseStruct->pt.y }; 672 673 if (g_cursorWrapInstance && g_cursorWrapInstance->m_hookActive) 674 { 675 POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove( 676 currentPos, 677 g_cursorWrapInstance->m_disableWrapDuringDrag, 678 g_cursorWrapInstance->m_wrapMode, 679 g_cursorWrapInstance->m_disableOnSingleMonitor); 680 681 if (newPos.x != currentPos.x || newPos.y != currentPos.y) 682 { 683 #ifdef _DEBUG 684 Logger::info(L"CursorWrap DEBUG: Wrapping cursor from ({}, {}) to ({}, {})", 685 currentPos.x, currentPos.y, newPos.x, newPos.y); 686 #endif 687 SetCursorPos(newPos.x, newPos.y); 688 return 1; // Suppress the original message 689 } 690 } 691 } 692 693 return CallNextHookEx(nullptr, nCode, wParam, lParam); 694 } 695 }; 696 697 extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() 698 { 699 return new CursorWrap(); 700 }