mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
## Summary of the Pull Request Cursor Wrap makes it simple to move the mouse from one edge of a display (or set of displays) to the opposite edge of the display stack - on a single display Cursor Wrap will wrap top/bottom and left/right edges. https://github.com/user-attachments/assets/3feb606c-142b-4dab-9824-7597833d3ba4 ## PR Checklist - [x] Closes: CursorWrap #41759 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments PR adds a new mouse utils module, this is 'Cursor Wrap' - Cursor Wrap works with 1-9 monitors based on the logical monitor layout of the PC - for a single monitor device the cursor is wrapped for the top/bottom and left/right edges of the display - for a multi-monitor setup the cursor is wrapped on the top/bottom left/right of the displays in the logical display layout. ## Validation Steps Performed Validation has been performed on a Surface Laptop 7 Pro (Intel) with a single display and with an HDMI USB-C second display configured to be a second monitor in top/left/right/bottom configuration - there are also tests that run as part of the build to validate logical monitor layout and cursor positioning. --------- Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Kai Tao (from Dev Box) <kaitao@microsoft.com> Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
1046 lines
41 KiB
C++
1046 lines
41 KiB
C++
#include "pch.h"
|
|
#include "../../../interface/powertoy_module_interface.h"
|
|
#include "../../../common/SettingsAPI/settings_objects.h"
|
|
#include "trace.h"
|
|
#include "../../../common/utils/process_path.h"
|
|
#include "../../../common/utils/resources.h"
|
|
#include "../../../common/logger/logger.h"
|
|
#include "../../../common/utils/logger_helper.h"
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <windows.h>
|
|
#include "resource.h"
|
|
#include "CursorWrapTests.h"
|
|
|
|
// Disable C26451 arithmetic overflow warning for this file since the operations are safe in this context
|
|
#pragma warning(disable: 26451)
|
|
|
|
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
|
{
|
|
switch (ul_reason_for_call)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
Trace::RegisterProvider();
|
|
break;
|
|
case DLL_THREAD_ATTACH:
|
|
case DLL_THREAD_DETACH:
|
|
break;
|
|
case DLL_PROCESS_DETACH:
|
|
Trace::UnregisterProvider();
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// Non-Localizable strings
|
|
namespace
|
|
{
|
|
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
|
const wchar_t JSON_KEY_VALUE[] = L"value";
|
|
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
|
|
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
|
|
const wchar_t JSON_KEY_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag";
|
|
}
|
|
|
|
// The PowerToy name that will be shown in the settings.
|
|
const static wchar_t* MODULE_NAME = L"CursorWrap";
|
|
// Add a description that will we shown in the module settings page.
|
|
const static wchar_t* MODULE_DESC = L"<no description>";
|
|
|
|
// Mouse hook data structure
|
|
struct MonitorInfo
|
|
{
|
|
RECT rect;
|
|
bool isPrimary;
|
|
int monitorId; // Add monitor ID for easier debugging
|
|
};
|
|
|
|
// Add structure for logical monitor grid position
|
|
struct LogicalPosition
|
|
{
|
|
int row;
|
|
int col;
|
|
bool isValid;
|
|
};
|
|
|
|
// Add monitor topology helper
|
|
struct MonitorTopology
|
|
{
|
|
std::vector<std::vector<HMONITOR>> grid; // 3x3 grid of monitors
|
|
std::map<HMONITOR, LogicalPosition> monitorToPosition;
|
|
std::map<std::pair<int, int>, HMONITOR> positionToMonitor;
|
|
|
|
void Initialize(const std::vector<MonitorInfo>& monitors);
|
|
LogicalPosition GetPosition(HMONITOR monitor) const;
|
|
HMONITOR GetMonitorAt(int row, int col) const;
|
|
HMONITOR FindAdjacentMonitor(HMONITOR current, int deltaRow, int deltaCol) const;
|
|
};
|
|
|
|
// Forward declaration
|
|
class CursorWrap;
|
|
|
|
// Global instance pointer for the mouse hook
|
|
static CursorWrap* g_cursorWrapInstance = nullptr;
|
|
|
|
// Implement the PowerToy Module Interface and all the required methods.
|
|
class CursorWrap : public PowertoyModuleIface
|
|
{
|
|
private:
|
|
// The PowerToy state.
|
|
bool m_enabled = false;
|
|
bool m_autoActivate = false;
|
|
bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag
|
|
|
|
// Mouse hook
|
|
HHOOK m_mouseHook = nullptr;
|
|
std::atomic<bool> m_hookActive{ false };
|
|
|
|
// Monitor information
|
|
std::vector<MonitorInfo> m_monitors;
|
|
MonitorTopology m_topology;
|
|
|
|
// Hotkey
|
|
Hotkey m_activationHotkey{};
|
|
|
|
public:
|
|
// Constructor
|
|
CursorWrap()
|
|
{
|
|
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::cursorWrapLoggerName);
|
|
init_settings();
|
|
UpdateMonitorInfo();
|
|
g_cursorWrapInstance = this; // Set global instance pointer
|
|
};
|
|
|
|
// Destroy the powertoy and free memory
|
|
virtual void destroy() override
|
|
{
|
|
StopMouseHook();
|
|
g_cursorWrapInstance = nullptr; // Clear global instance pointer
|
|
delete this;
|
|
}
|
|
|
|
// Return the localized display name of the powertoy
|
|
virtual const wchar_t* get_name() override
|
|
{
|
|
return MODULE_NAME;
|
|
}
|
|
|
|
// Return the non localized key of the powertoy, this will be cached by the runner
|
|
virtual const wchar_t* get_key() override
|
|
{
|
|
return MODULE_NAME;
|
|
}
|
|
|
|
// Return the configured status for the gpo policy for the module
|
|
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
|
{
|
|
return powertoys_gpo::getConfiguredCursorWrapEnabledValue();
|
|
}
|
|
|
|
// Return JSON with the configuration options.
|
|
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
|
{
|
|
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
|
|
|
PowerToysSettings::Settings settings(hinstance, get_name());
|
|
|
|
settings.set_description(IDS_CURSORWRAP_NAME);
|
|
settings.set_icon_key(L"pt-cursor-wrap");
|
|
|
|
// Create HotkeyObject from the Hotkey struct for the settings
|
|
auto hotkey_object = PowerToysSettings::HotkeyObject::from_settings(
|
|
m_activationHotkey.win,
|
|
m_activationHotkey.ctrl,
|
|
m_activationHotkey.alt,
|
|
m_activationHotkey.shift,
|
|
m_activationHotkey.key);
|
|
|
|
settings.add_hotkey(JSON_KEY_ACTIVATION_SHORTCUT, IDS_CURSORWRAP_NAME, hotkey_object);
|
|
settings.add_bool_toggle(JSON_KEY_AUTO_ACTIVATE, IDS_CURSORWRAP_NAME, m_autoActivate);
|
|
settings.add_bool_toggle(JSON_KEY_DISABLE_WRAP_DURING_DRAG, IDS_CURSORWRAP_NAME, m_disableWrapDuringDrag);
|
|
|
|
return settings.serialize_to_buffer(buffer, buffer_size);
|
|
}
|
|
|
|
// Signal from the Settings editor to call a custom action.
|
|
// This can be used to spawn more complex editors.
|
|
virtual void call_custom_action(const wchar_t* /*action*/) override {}
|
|
|
|
// Called by the runner to pass the updated settings values as a serialized JSON.
|
|
virtual void set_config(const wchar_t* config) override
|
|
{
|
|
try
|
|
{
|
|
// Parse the input JSON string.
|
|
PowerToysSettings::PowerToyValues values =
|
|
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
|
|
|
parse_settings(values);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
Logger::error("Invalid json when trying to parse CursorWrap settings json.");
|
|
}
|
|
}
|
|
|
|
// Enable the powertoy
|
|
virtual void enable()
|
|
{
|
|
m_enabled = true;
|
|
Trace::EnableCursorWrap(true);
|
|
|
|
if (m_autoActivate)
|
|
{
|
|
StartMouseHook();
|
|
}
|
|
}
|
|
|
|
// Disable the powertoy
|
|
virtual void disable()
|
|
{
|
|
m_enabled = false;
|
|
Trace::EnableCursorWrap(false);
|
|
StopMouseHook();
|
|
}
|
|
|
|
// Returns if the powertoys is enabled
|
|
virtual bool is_enabled() override
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
// Returns whether the PowerToys should be enabled by default
|
|
virtual bool is_enabled_by_default() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Legacy hotkey support
|
|
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
|
|
{
|
|
if (buffer && buffer_size >= 1)
|
|
{
|
|
buffer[0] = m_activationHotkey;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual bool on_hotkey(size_t hotkeyId) override
|
|
{
|
|
if (!m_enabled || hotkeyId != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Toggle cursor wrapping
|
|
if (m_hookActive)
|
|
{
|
|
StopMouseHook();
|
|
}
|
|
else
|
|
{
|
|
StartMouseHook();
|
|
#ifdef _DEBUG
|
|
// Run comprehensive tests when hook is started in debug builds
|
|
RunComprehensiveTests();
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
// Load the settings file.
|
|
void init_settings()
|
|
{
|
|
try
|
|
{
|
|
// Load and parse the settings file for this PowerToy.
|
|
PowerToysSettings::PowerToyValues settings =
|
|
PowerToysSettings::PowerToyValues::load_from_settings_file(CursorWrap::get_key());
|
|
parse_settings(settings);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
Logger::error("Invalid json when trying to load the CursorWrap settings json from file.");
|
|
}
|
|
}
|
|
|
|
void parse_settings(PowerToysSettings::PowerToyValues& settings)
|
|
{
|
|
auto settingsObject = settings.get_raw_json();
|
|
if (settingsObject.GetView().Size())
|
|
{
|
|
try
|
|
{
|
|
// Parse activation HotKey
|
|
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
|
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
|
|
|
|
m_activationHotkey.win = hotkey.win_pressed();
|
|
m_activationHotkey.ctrl = hotkey.ctrl_pressed();
|
|
m_activationHotkey.shift = hotkey.shift_pressed();
|
|
m_activationHotkey.alt = hotkey.alt_pressed();
|
|
m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code());
|
|
}
|
|
catch (...)
|
|
{
|
|
Logger::warn("Failed to initialize CursorWrap activation shortcut");
|
|
}
|
|
|
|
try
|
|
{
|
|
// Parse auto activate
|
|
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_AUTO_ACTIVATE);
|
|
m_autoActivate = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
|
|
}
|
|
catch (...)
|
|
{
|
|
Logger::warn("Failed to initialize CursorWrap auto activate from settings. Will use default value");
|
|
}
|
|
|
|
try
|
|
{
|
|
// Parse disable wrap during drag
|
|
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
|
if (propertiesObject.HasKey(JSON_KEY_DISABLE_WRAP_DURING_DRAG))
|
|
{
|
|
auto disableDragObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_WRAP_DURING_DRAG);
|
|
m_disableWrapDuringDrag = disableDragObject.GetNamedBoolean(JSON_KEY_VALUE);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
Logger::warn("Failed to initialize CursorWrap disable wrap during drag from settings. Will use default value (true)");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::info("CursorWrap settings are empty");
|
|
}
|
|
|
|
// Set default hotkey if not configured
|
|
if (m_activationHotkey.key == 0)
|
|
{
|
|
m_activationHotkey.win = true;
|
|
m_activationHotkey.alt = true;
|
|
m_activationHotkey.ctrl = false;
|
|
m_activationHotkey.shift = false;
|
|
m_activationHotkey.key = 'U'; // Win+Alt+U
|
|
}
|
|
}
|
|
|
|
void UpdateMonitorInfo()
|
|
{
|
|
m_monitors.clear();
|
|
|
|
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hMonitor, HDC, LPRECT, LPARAM lParam) -> BOOL {
|
|
auto* self = reinterpret_cast<CursorWrap*>(lParam);
|
|
|
|
MONITORINFO mi{};
|
|
mi.cbSize = sizeof(MONITORINFO);
|
|
if (GetMonitorInfo(hMonitor, &mi))
|
|
{
|
|
MonitorInfo info{};
|
|
info.rect = mi.rcMonitor;
|
|
info.isPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) != 0;
|
|
info.monitorId = static_cast<int>(self->m_monitors.size());
|
|
self->m_monitors.push_back(info);
|
|
}
|
|
|
|
return TRUE;
|
|
}, reinterpret_cast<LPARAM>(this));
|
|
|
|
// Initialize monitor topology
|
|
m_topology.Initialize(m_monitors);
|
|
}
|
|
|
|
void StartMouseHook()
|
|
{
|
|
if (m_mouseHook || m_hookActive)
|
|
{
|
|
Logger::info("CursorWrap mouse hook already active");
|
|
return;
|
|
}
|
|
|
|
UpdateMonitorInfo();
|
|
|
|
m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, GetModuleHandle(nullptr), 0);
|
|
if (m_mouseHook)
|
|
{
|
|
m_hookActive = true;
|
|
Logger::info("CursorWrap mouse hook started successfully");
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Hook installed");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
DWORD error = GetLastError();
|
|
Logger::error(L"Failed to install CursorWrap mouse hook, error: {}", error);
|
|
}
|
|
}
|
|
|
|
void StopMouseHook()
|
|
{
|
|
if (m_mouseHook)
|
|
{
|
|
UnhookWindowsHookEx(m_mouseHook);
|
|
m_mouseHook = nullptr;
|
|
m_hookActive = false;
|
|
Logger::info("CursorWrap mouse hook stopped");
|
|
#ifdef _DEBUG
|
|
Logger::info("CursorWrap DEBUG: Mouse hook stopped");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (nCode >= 0 && wParam == WM_MOUSEMOVE)
|
|
{
|
|
auto* pMouseStruct = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
|
|
POINT currentPos = { pMouseStruct->pt.x, pMouseStruct->pt.y };
|
|
|
|
if (g_cursorWrapInstance && g_cursorWrapInstance->m_hookActive)
|
|
{
|
|
POINT newPos = g_cursorWrapInstance->HandleMouseMove(currentPos);
|
|
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Wrapping cursor from ({}, {}) to ({}, {})",
|
|
currentPos.x, currentPos.y, newPos.x, newPos.y);
|
|
#endif
|
|
SetCursorPos(newPos.x, newPos.y);
|
|
return 1; // Suppress the original message
|
|
}
|
|
}
|
|
}
|
|
|
|
return CallNextHookEx(nullptr, nCode, wParam, lParam);
|
|
}
|
|
|
|
// *** COMPLETELY REWRITTEN CURSOR WRAPPING LOGIC ***
|
|
// Implements vertical scrolling to bottom/top of vertical stack as requested
|
|
POINT HandleMouseMove(const POINT& currentPos)
|
|
{
|
|
POINT newPos = currentPos;
|
|
|
|
// Check if we should skip wrapping during drag if the setting is enabled
|
|
if (m_disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Left mouse button is down and disable_wrap_during_drag is enabled - skipping wrap");
|
|
#endif
|
|
return currentPos; // Return unchanged position (no wrapping)
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= HANDLE MOUSE MOVE START =======");
|
|
Logger::info(L"CursorWrap DEBUG: Input position ({}, {})", currentPos.x, currentPos.y);
|
|
#endif
|
|
|
|
// Find which monitor the cursor is currently on
|
|
HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
|
|
MONITORINFO currentMonitorInfo{};
|
|
currentMonitorInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(currentMonitor, ¤tMonitorInfo);
|
|
|
|
LogicalPosition currentLogicalPos = m_topology.GetPosition(currentMonitor);
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Current monitor bounds: Left={}, Top={}, Right={}, Bottom={}",
|
|
currentMonitorInfo.rcMonitor.left, currentMonitorInfo.rcMonitor.top,
|
|
currentMonitorInfo.rcMonitor.right, currentMonitorInfo.rcMonitor.bottom);
|
|
Logger::info(L"CursorWrap DEBUG: Logical position: Row={}, Col={}, Valid={}",
|
|
currentLogicalPos.row, currentLogicalPos.col, currentLogicalPos.isValid);
|
|
#endif
|
|
|
|
bool wrapped = false;
|
|
|
|
// *** VERTICAL WRAPPING LOGIC - CONFIRMED WORKING ***
|
|
// Move to bottom of vertical stack when hitting top edge
|
|
if (currentPos.y <= currentMonitorInfo.rcMonitor.top)
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: TOP EDGE DETECTED =======");
|
|
#endif
|
|
|
|
// Find the bottom-most monitor in the vertical stack (same column)
|
|
HMONITOR bottomMonitor = nullptr;
|
|
|
|
if (currentLogicalPos.isValid) {
|
|
// Search down from current position to find the bottom-most monitor in same column
|
|
for (int row = 2; row >= 0; row--) { // Start from bottom and work up
|
|
HMONITOR candidateMonitor = m_topology.GetMonitorAt(row, currentLogicalPos.col);
|
|
if (candidateMonitor) {
|
|
bottomMonitor = candidateMonitor;
|
|
break; // Found the bottom-most monitor
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bottomMonitor && bottomMonitor != currentMonitor) {
|
|
// *** MOVE TO BOTTOM OF VERTICAL STACK ***
|
|
MONITORINFO bottomInfo{};
|
|
bottomInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(bottomMonitor, &bottomInfo);
|
|
|
|
// Calculate relative X position to maintain cursor X alignment
|
|
double relativeX = static_cast<double>(currentPos.x - currentMonitorInfo.rcMonitor.left) /
|
|
(currentMonitorInfo.rcMonitor.right - currentMonitorInfo.rcMonitor.left);
|
|
|
|
int targetWidth = bottomInfo.rcMonitor.right - bottomInfo.rcMonitor.left;
|
|
newPos.x = bottomInfo.rcMonitor.left + static_cast<int>(relativeX * targetWidth);
|
|
newPos.y = bottomInfo.rcMonitor.bottom - 1; // Bottom edge of bottom monitor
|
|
|
|
// Clamp X to target monitor bounds
|
|
newPos.x = max(bottomInfo.rcMonitor.left, min(newPos.x, bottomInfo.rcMonitor.right - 1));
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP SUCCESS - Moved to bottom of vertical stack");
|
|
Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
|
|
#endif
|
|
} else {
|
|
// *** NO OTHER MONITOR IN VERTICAL STACK - WRAP WITHIN CURRENT MONITOR ***
|
|
newPos.y = currentMonitorInfo.rcMonitor.bottom - 1;
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP - No other monitor in stack, wrapping within current monitor");
|
|
#endif
|
|
}
|
|
}
|
|
else if (currentPos.y >= currentMonitorInfo.rcMonitor.bottom - 1)
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: BOTTOM EDGE DETECTED =======");
|
|
#endif
|
|
|
|
// Find the top-most monitor in the vertical stack (same column)
|
|
HMONITOR topMonitor = nullptr;
|
|
|
|
if (currentLogicalPos.isValid) {
|
|
// Search up from current position to find the top-most monitor in same column
|
|
for (int row = 0; row <= 2; row++) { // Start from top and work down
|
|
HMONITOR candidateMonitor = m_topology.GetMonitorAt(row, currentLogicalPos.col);
|
|
if (candidateMonitor) {
|
|
topMonitor = candidateMonitor;
|
|
break; // Found the top-most monitor
|
|
}
|
|
}
|
|
}
|
|
|
|
if (topMonitor && topMonitor != currentMonitor) {
|
|
// *** MOVE TO TOP OF VERTICAL STACK ***
|
|
MONITORINFO topInfo{};
|
|
topInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(topMonitor, &topInfo);
|
|
|
|
// Calculate relative X position to maintain cursor X alignment
|
|
double relativeX = static_cast<double>(currentPos.x - currentMonitorInfo.rcMonitor.left) /
|
|
(currentMonitorInfo.rcMonitor.right - currentMonitorInfo.rcMonitor.left);
|
|
|
|
int targetWidth = topInfo.rcMonitor.right - topInfo.rcMonitor.left;
|
|
newPos.x = topInfo.rcMonitor.left + static_cast<int>(relativeX * targetWidth);
|
|
newPos.y = topInfo.rcMonitor.top; // Top edge of top monitor
|
|
|
|
// Clamp X to target monitor bounds
|
|
newPos.x = max(topInfo.rcMonitor.left, min(newPos.x, topInfo.rcMonitor.right - 1));
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP SUCCESS - Moved to top of vertical stack");
|
|
Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
|
|
#endif
|
|
} else {
|
|
// *** NO OTHER MONITOR IN VERTICAL STACK - WRAP WITHIN CURRENT MONITOR ***
|
|
newPos.y = currentMonitorInfo.rcMonitor.top;
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP - No other monitor in stack, wrapping within current monitor");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// *** FIXED HORIZONTAL WRAPPING LOGIC ***
|
|
// Move to opposite end of horizontal stack when hitting left/right edge
|
|
// Only handle horizontal wrapping if we haven't already wrapped vertically
|
|
if (!wrapped && currentPos.x <= currentMonitorInfo.rcMonitor.left)
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: LEFT EDGE DETECTED =======");
|
|
#endif
|
|
|
|
// Find the right-most monitor in the horizontal stack (same row)
|
|
HMONITOR rightMonitor = nullptr;
|
|
|
|
if (currentLogicalPos.isValid) {
|
|
// Search right from current position to find the right-most monitor in same row
|
|
for (int col = 2; col >= 0; col--) { // Start from right and work left
|
|
HMONITOR candidateMonitor = m_topology.GetMonitorAt(currentLogicalPos.row, col);
|
|
if (candidateMonitor) {
|
|
rightMonitor = candidateMonitor;
|
|
break; // Found the right-most monitor
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rightMonitor && rightMonitor != currentMonitor) {
|
|
// *** MOVE TO RIGHT END OF HORIZONTAL STACK ***
|
|
MONITORINFO rightInfo{};
|
|
rightInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(rightMonitor, &rightInfo);
|
|
|
|
// Calculate relative Y position to maintain cursor Y alignment
|
|
double relativeY = static_cast<double>(currentPos.y - currentMonitorInfo.rcMonitor.top) /
|
|
(currentMonitorInfo.rcMonitor.bottom - currentMonitorInfo.rcMonitor.top);
|
|
|
|
int targetHeight = rightInfo.rcMonitor.bottom - rightInfo.rcMonitor.top;
|
|
newPos.y = rightInfo.rcMonitor.top + static_cast<int>(relativeY * targetHeight);
|
|
newPos.x = rightInfo.rcMonitor.right - 1; // Right edge of right monitor
|
|
|
|
// Clamp Y to target monitor bounds
|
|
newPos.y = max(rightInfo.rcMonitor.top, min(newPos.y, rightInfo.rcMonitor.bottom - 1));
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP SUCCESS - Moved to right end of horizontal stack");
|
|
Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
|
|
#endif
|
|
} else {
|
|
// *** NO OTHER MONITOR IN HORIZONTAL STACK - WRAP WITHIN CURRENT MONITOR ***
|
|
newPos.x = currentMonitorInfo.rcMonitor.right - 1;
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP - No other monitor in stack, wrapping within current monitor");
|
|
#endif
|
|
}
|
|
}
|
|
else if (!wrapped && currentPos.x >= currentMonitorInfo.rcMonitor.right - 1)
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: RIGHT EDGE DETECTED =======");
|
|
#endif
|
|
|
|
// Find the left-most monitor in the horizontal stack (same row)
|
|
HMONITOR leftMonitor = nullptr;
|
|
|
|
if (currentLogicalPos.isValid) {
|
|
// Search left from current position to find the left-most monitor in same row
|
|
for (int col = 0; col <= 2; col++) { // Start from left and work right
|
|
HMONITOR candidateMonitor = m_topology.GetMonitorAt(currentLogicalPos.row, col);
|
|
if (candidateMonitor) {
|
|
leftMonitor = candidateMonitor;
|
|
break; // Found the left-most monitor
|
|
}
|
|
}
|
|
}
|
|
|
|
if (leftMonitor && leftMonitor != currentMonitor) {
|
|
// *** MOVE TO LEFT END OF HORIZONTAL STACK ***
|
|
MONITORINFO leftInfo{};
|
|
leftInfo.cbSize = sizeof(MONITORINFO);
|
|
GetMonitorInfo(leftMonitor, &leftInfo);
|
|
|
|
// Calculate relative Y position to maintain cursor Y alignment
|
|
double relativeY = static_cast<double>(currentPos.y - currentMonitorInfo.rcMonitor.top) /
|
|
(currentMonitorInfo.rcMonitor.bottom - currentMonitorInfo.rcMonitor.top);
|
|
|
|
int targetHeight = leftInfo.rcMonitor.bottom - leftInfo.rcMonitor.top;
|
|
newPos.y = leftInfo.rcMonitor.top + static_cast<int>(relativeY * targetHeight);
|
|
newPos.x = leftInfo.rcMonitor.left; // Left edge of left monitor
|
|
|
|
// Clamp Y to target monitor bounds
|
|
newPos.y = max(leftInfo.rcMonitor.top, min(newPos.y, leftInfo.rcMonitor.bottom - 1));
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP SUCCESS - Moved to left end of horizontal stack");
|
|
Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
|
|
#endif
|
|
} else {
|
|
// *** NO OTHER MONITOR IN HORIZONTAL STACK - WRAP WITHIN CURRENT MONITOR ***
|
|
newPos.x = currentMonitorInfo.rcMonitor.left;
|
|
wrapped = true;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP - No other monitor in stack, wrapping within current monitor");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if (wrapped)
|
|
{
|
|
Logger::info(L"CursorWrap DEBUG: ======= WRAP RESULT =======");
|
|
Logger::info(L"CursorWrap DEBUG: Original: ({}, {}) -> New: ({}, {})",
|
|
currentPos.x, currentPos.y, newPos.x, newPos.y);
|
|
}
|
|
else
|
|
{
|
|
Logger::info(L"CursorWrap DEBUG: No wrapping performed - cursor not at edge");
|
|
}
|
|
Logger::info(L"CursorWrap DEBUG: ======= HANDLE MOUSE MOVE END =======");
|
|
#endif
|
|
|
|
return newPos;
|
|
}
|
|
|
|
// Add test method for monitor topology validation
|
|
void RunMonitorTopologyTests()
|
|
{
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap: Running monitor topology tests...");
|
|
|
|
// Test all 9 possible monitor positions in 3x3 grid
|
|
const char* gridNames[3][3] = {
|
|
{"TL", "TC", "TR"}, // Top-Left, Top-Center, Top-Right
|
|
{"ML", "MC", "MR"}, // Middle-Left, Middle-Center, Middle-Right
|
|
{"BL", "BC", "BR"} // Bottom-Left, Bottom-Center, Bottom-Right
|
|
};
|
|
|
|
for (int row = 0; row < 3; row++)
|
|
{
|
|
for (int col = 0; col < 3; col++)
|
|
{
|
|
HMONITOR monitor = m_topology.GetMonitorAt(row, col);
|
|
if (monitor)
|
|
{
|
|
std::string gridName(gridNames[row][col]);
|
|
std::wstring wGridName(gridName.begin(), gridName.end());
|
|
Logger::info(L"CursorWrap TEST: Monitor at [{}][{}] ({}) exists",
|
|
row, col, wGridName.c_str());
|
|
|
|
// Test adjacent monitor finding
|
|
HMONITOR up = m_topology.FindAdjacentMonitor(monitor, -1, 0);
|
|
HMONITOR down = m_topology.FindAdjacentMonitor(monitor, 1, 0);
|
|
HMONITOR left = m_topology.FindAdjacentMonitor(monitor, 0, -1);
|
|
HMONITOR right = m_topology.FindAdjacentMonitor(monitor, 0, 1);
|
|
|
|
Logger::info(L"CursorWrap TEST: Adjacent monitors - Up: {}, Down: {}, Left: {}, Right: {}",
|
|
up ? L"YES" : L"NO", down ? L"YES" : L"NO",
|
|
left ? L"YES" : L"NO", right ? L"YES" : L"NO");
|
|
}
|
|
}
|
|
}
|
|
|
|
Logger::info(L"CursorWrap: Monitor topology tests completed.");
|
|
#endif
|
|
}
|
|
|
|
// Add method to trigger test suite (can be called via hotkey in debug builds)
|
|
void RunComprehensiveTests()
|
|
{
|
|
#ifdef _DEBUG
|
|
RunMonitorTopologyTests();
|
|
|
|
// Test cursor wrapping scenarios
|
|
Logger::info(L"CursorWrap: Testing cursor wrapping scenarios...");
|
|
|
|
// Simulate cursor positions at each monitor edge and verify expected behavior
|
|
for (const auto& monitor : m_monitors)
|
|
{
|
|
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
LogicalPosition pos = m_topology.GetPosition(hMonitor);
|
|
|
|
if (pos.isValid)
|
|
{
|
|
Logger::info(L"CursorWrap TEST: Testing monitor at position [{}][{}]", pos.row, pos.col);
|
|
|
|
// Test top edge
|
|
POINT topEdge = {(monitor.rect.left + monitor.rect.right) / 2, monitor.rect.top};
|
|
POINT newPos = HandleMouseMove(topEdge);
|
|
Logger::info(L"CursorWrap TEST: Top edge ({}, {}) -> ({}, {})",
|
|
topEdge.x, topEdge.y, newPos.x, newPos.y);
|
|
|
|
// Test bottom edge
|
|
POINT bottomEdge = {(monitor.rect.left + monitor.rect.right) / 2, monitor.rect.bottom - 1};
|
|
newPos = HandleMouseMove(bottomEdge);
|
|
Logger::info(L"CursorWrap TEST: Bottom edge ({}, {}) -> ({}, {})",
|
|
bottomEdge.x, bottomEdge.y, newPos.x, newPos.y);
|
|
|
|
// Test left edge
|
|
POINT leftEdge = {monitor.rect.left, (monitor.rect.top + monitor.rect.bottom) / 2};
|
|
newPos = HandleMouseMove(leftEdge);
|
|
Logger::info(L"CursorWrap TEST: Left edge ({}, {}) -> ({}, {})",
|
|
leftEdge.x, leftEdge.y, newPos.x, newPos.y);
|
|
|
|
// Test right edge
|
|
POINT rightEdge = {monitor.rect.right - 1, (monitor.rect.top + monitor.rect.bottom) / 2};
|
|
newPos = HandleMouseMove(rightEdge);
|
|
Logger::info(L"CursorWrap TEST: Right edge ({}, {}) -> ({}, {})",
|
|
rightEdge.x, rightEdge.y, newPos.x, newPos.y);
|
|
}
|
|
}
|
|
|
|
Logger::info(L"CursorWrap: Comprehensive tests completed.");
|
|
#endif
|
|
}
|
|
};
|
|
|
|
// Implementation of MonitorTopology methods
|
|
void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors)
|
|
{
|
|
// Clear existing data
|
|
grid.assign(3, std::vector<HMONITOR>(3, nullptr));
|
|
monitorToPosition.clear();
|
|
positionToMonitor.clear();
|
|
|
|
if (monitors.empty()) return;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: ======= TOPOLOGY INITIALIZATION START =======");
|
|
Logger::info(L"CursorWrap DEBUG: Initializing topology for {} monitors", monitors.size());
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {}: bounds=({},{},{},{}), isPrimary={}",
|
|
monitor.monitorId, monitor.rect.left, monitor.rect.top,
|
|
monitor.rect.right, monitor.rect.bottom, monitor.isPrimary);
|
|
}
|
|
#endif
|
|
|
|
// Special handling for 2 monitors - use physical position, not discovery order
|
|
if (monitors.size() == 2)
|
|
{
|
|
// Determine if arrangement is horizontal or vertical by comparing centers
|
|
POINT center0 = {(monitors[0].rect.left + monitors[0].rect.right) / 2,
|
|
(monitors[0].rect.top + monitors[0].rect.bottom) / 2};
|
|
POINT center1 = {(monitors[1].rect.left + monitors[1].rect.right) / 2,
|
|
(monitors[1].rect.top + monitors[1].rect.bottom) / 2};
|
|
|
|
int xDiff = abs(center0.x - center1.x);
|
|
int yDiff = abs(center0.y - center1.y);
|
|
|
|
bool isHorizontal = xDiff > yDiff;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Monitor centers: M0=({}, {}), M1=({}, {})",
|
|
center0.x, center0.y, center1.x, center1.y);
|
|
Logger::info(L"CursorWrap DEBUG: Differences: X={}, Y={}, IsHorizontal={}",
|
|
xDiff, yDiff, isHorizontal);
|
|
#endif
|
|
|
|
if (isHorizontal)
|
|
{
|
|
// Horizontal arrangement - place in middle row [1,0] and [1,2]
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
POINT center = {(monitor.rect.left + monitor.rect.right) / 2,
|
|
(monitor.rect.top + monitor.rect.bottom) / 2};
|
|
|
|
int row = 1; // Middle row
|
|
int col = (center.x < (center0.x + center1.x) / 2) ? 0 : 2; // Left or right based on center
|
|
|
|
grid[row][col] = hMonitor;
|
|
monitorToPosition[hMonitor] = {row, col, true};
|
|
positionToMonitor[{row, col}] = hMonitor;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {} (horizontal) placed at grid[{}][{}]",
|
|
monitor.monitorId, row, col);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// *** VERTICAL ARRANGEMENT - CRITICAL LOGIC ***
|
|
// Sort monitors by Y coordinate to determine vertical order
|
|
std::vector<std::pair<int, MonitorInfo>> sortedMonitors;
|
|
for (int i = 0; i < 2; i++) {
|
|
sortedMonitors.push_back({i, monitors[i]});
|
|
}
|
|
|
|
// Sort by Y coordinate (top to bottom)
|
|
std::sort(sortedMonitors.begin(), sortedMonitors.end(),
|
|
[](const std::pair<int, MonitorInfo>& a, const std::pair<int, MonitorInfo>& b) {
|
|
int centerA = (a.second.rect.top + a.second.rect.bottom) / 2;
|
|
int centerB = (b.second.rect.top + b.second.rect.bottom) / 2;
|
|
return centerA < centerB; // Top first
|
|
});
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: VERTICAL ARRANGEMENT DETECTED");
|
|
Logger::info(L"CursorWrap DEBUG: Top monitor: ID={}, Y-center={}",
|
|
sortedMonitors[0].second.monitorId,
|
|
(sortedMonitors[0].second.rect.top + sortedMonitors[0].second.rect.bottom) / 2);
|
|
Logger::info(L"CursorWrap DEBUG: Bottom monitor: ID={}, Y-center={}",
|
|
sortedMonitors[1].second.monitorId,
|
|
(sortedMonitors[1].second.rect.top + sortedMonitors[1].second.rect.bottom) / 2);
|
|
#endif
|
|
|
|
// Place monitors in grid based on sorted order
|
|
for (int i = 0; i < 2; i++) {
|
|
const auto& monitorPair = sortedMonitors[i];
|
|
const auto& monitor = monitorPair.second;
|
|
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
|
|
int col = 1; // Middle column for vertical arrangement
|
|
int row = (i == 0) ? 0 : 2; // Top monitor at row 0, bottom at row 2
|
|
|
|
grid[row][col] = hMonitor;
|
|
monitorToPosition[hMonitor] = {row, col, true};
|
|
positionToMonitor[{row, col}] = hMonitor;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {} (vertical) placed at grid[{}][{}] - {} position",
|
|
monitor.monitorId, row, col, (i == 0) ? L"TOP" : L"BOTTOM");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For more than 2 monitors, use the general algorithm
|
|
RECT totalBounds = monitors[0].rect;
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
totalBounds.left = min(totalBounds.left, monitor.rect.left);
|
|
totalBounds.top = min(totalBounds.top, monitor.rect.top);
|
|
totalBounds.right = max(totalBounds.right, monitor.rect.right);
|
|
totalBounds.bottom = max(totalBounds.bottom, monitor.rect.bottom);
|
|
}
|
|
|
|
int totalWidth = totalBounds.right - totalBounds.left;
|
|
int totalHeight = totalBounds.bottom - totalBounds.top;
|
|
int gridWidth = max(1, totalWidth / 3);
|
|
int gridHeight = max(1, totalHeight / 3);
|
|
|
|
// Place monitors in the 3x3 grid based on their center points
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
|
|
// Calculate center point of monitor
|
|
int centerX = (monitor.rect.left + monitor.rect.right) / 2;
|
|
int centerY = (monitor.rect.top + monitor.rect.bottom) / 2;
|
|
|
|
// Map to grid position
|
|
int col = (centerX - totalBounds.left) / gridWidth;
|
|
int row = (centerY - totalBounds.top) / gridHeight;
|
|
|
|
// Ensure we stay within bounds
|
|
col = max(0, min(2, col));
|
|
row = max(0, min(2, row));
|
|
|
|
grid[row][col] = hMonitor;
|
|
monitorToPosition[hMonitor] = {row, col, true};
|
|
positionToMonitor[{row, col}] = hMonitor;
|
|
|
|
#ifdef _DEBUG
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}], center=({}, {})",
|
|
monitor.monitorId, row, col, centerX, centerY);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
// *** CRITICAL: Print topology map using OutputDebugString for debug builds ***
|
|
Logger::info(L"CursorWrap DEBUG: ======= FINAL TOPOLOGY MAP =======");
|
|
OutputDebugStringA("CursorWrap TOPOLOGY MAP:\n");
|
|
for (int r = 0; r < 3; r++)
|
|
{
|
|
std::string rowStr = " ";
|
|
for (int c = 0; c < 3; c++)
|
|
{
|
|
if (grid[r][c])
|
|
{
|
|
// Find monitor ID for this handle
|
|
int monitorId = -1;
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
HMONITOR handle = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
if (handle == grid[r][c])
|
|
{
|
|
monitorId = monitor.monitorId + 1; // Convert to 1-based for display
|
|
break;
|
|
}
|
|
}
|
|
rowStr += std::to_string(monitorId) + " ";
|
|
}
|
|
else
|
|
{
|
|
rowStr += ". ";
|
|
}
|
|
}
|
|
rowStr += "\n";
|
|
OutputDebugStringA(rowStr.c_str());
|
|
|
|
// Also log to PowerToys logger
|
|
std::wstring wRowStr(rowStr.begin(), rowStr.end());
|
|
Logger::info(wRowStr.c_str());
|
|
}
|
|
OutputDebugStringA("======= END TOPOLOGY MAP =======\n");
|
|
|
|
// Additional validation logging
|
|
Logger::info(L"CursorWrap DEBUG: ======= GRID POSITION VALIDATION =======");
|
|
for (const auto& monitor : monitors)
|
|
{
|
|
HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
|
|
LogicalPosition pos = GetPosition(hMonitor);
|
|
if (pos.isValid)
|
|
{
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {} -> grid[{}][{}]", monitor.monitorId, pos.row, pos.col);
|
|
OutputDebugStringA(("Monitor " + std::to_string(monitor.monitorId) + " -> grid[" + std::to_string(pos.row) + "][" + std::to_string(pos.col) + "]\n").c_str());
|
|
|
|
// Test adjacent finding
|
|
HMONITOR up = FindAdjacentMonitor(hMonitor, -1, 0);
|
|
HMONITOR down = FindAdjacentMonitor(hMonitor, 1, 0);
|
|
HMONITOR left = FindAdjacentMonitor(hMonitor, 0, -1);
|
|
HMONITOR right = FindAdjacentMonitor(hMonitor, 0, 1);
|
|
|
|
Logger::info(L"CursorWrap DEBUG: Monitor {} adjacents - Up: {}, Down: {}, Left: {}, Right: {}",
|
|
monitor.monitorId, up ? L"YES" : L"NO", down ? L"YES" : L"NO",
|
|
left ? L"YES" : L"NO", right ? L"YES" : L"NO");
|
|
}
|
|
}
|
|
Logger::info(L"CursorWrap DEBUG: ======= TOPOLOGY INITIALIZATION COMPLETE =======");
|
|
#endif
|
|
}
|
|
|
|
LogicalPosition MonitorTopology::GetPosition(HMONITOR monitor) const
|
|
{
|
|
auto it = monitorToPosition.find(monitor);
|
|
if (it != monitorToPosition.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
return {-1, -1, false};
|
|
}
|
|
|
|
HMONITOR MonitorTopology::GetMonitorAt(int row, int col) const
|
|
{
|
|
if (row >= 0 && row < 3 && col >= 0 && col < 3)
|
|
{
|
|
return grid[row][col];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
HMONITOR MonitorTopology::FindAdjacentMonitor(HMONITOR current, int deltaRow, int deltaCol) const
|
|
{
|
|
LogicalPosition currentPos = GetPosition(current);
|
|
if (!currentPos.isValid) return nullptr;
|
|
|
|
int newRow = currentPos.row + deltaRow;
|
|
int newCol = currentPos.col + deltaCol;
|
|
|
|
return GetMonitorAt(newRow, newCol);
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
|
{
|
|
return new CursorWrap();
|
|
}
|