CursorWrapCore.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 "CursorWrapCore.h" 7 #include "../../../common/logger/logger.h" 8 #include <sstream> 9 #include <iomanip> 10 #include <ctime> 11 12 CursorWrapCore::CursorWrapCore() 13 { 14 } 15 16 #ifdef _DEBUG 17 std::wstring CursorWrapCore::GenerateTopologyJSON() const 18 { 19 std::wostringstream json; 20 21 // Get current time 22 auto now = std::time(nullptr); 23 std::tm tm{}; 24 localtime_s(&tm, &now); 25 26 wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1] = {0}; 27 DWORD size = MAX_COMPUTERNAME_LENGTH + 1; 28 GetComputerNameW(computerName, &size); 29 30 wchar_t userName[256] = {0}; 31 size = 256; 32 GetUserNameW(userName, &size); 33 34 json << L"{\n"; 35 json << L" \"captured_at\": \"" << std::put_time(&tm, L"%Y-%m-%dT%H:%M:%S%z") << L"\",\n"; 36 json << L" \"computer_name\": \"" << computerName << L"\",\n"; 37 json << L" \"user_name\": \"" << userName << L"\",\n"; 38 json << L" \"monitor_count\": " << m_monitors.size() << L",\n"; 39 json << L" \"monitors\": [\n"; 40 41 for (size_t i = 0; i < m_monitors.size(); ++i) 42 { 43 const auto& monitor = m_monitors[i]; 44 45 // Get DPI for this monitor 46 UINT dpiX = 96, dpiY = 96; 47 POINT center = { 48 (monitor.rect.left + monitor.rect.right) / 2, 49 (monitor.rect.top + monitor.rect.bottom) / 2 50 }; 51 HMONITOR hMon = MonitorFromPoint(center, MONITOR_DEFAULTTONEAREST); 52 if (hMon) 53 { 54 // Try GetDpiForMonitor (requires linking Shcore.lib) 55 using GetDpiForMonitorFunc = HRESULT (WINAPI *)(HMONITOR, int, UINT*, UINT*); 56 HMODULE shcore = LoadLibraryW(L"Shcore.dll"); 57 if (shcore) 58 { 59 auto getDpi = reinterpret_cast<GetDpiForMonitorFunc>(GetProcAddress(shcore, "GetDpiForMonitor")); 60 if (getDpi) 61 { 62 getDpi(hMon, 0, &dpiX, &dpiY); // MDT_EFFECTIVE_DPI = 0 63 } 64 FreeLibrary(shcore); 65 } 66 } 67 68 int scalingPercent = static_cast<int>((dpiX / 96.0) * 100); 69 70 json << L" {\n"; 71 json << L" \"left\": " << monitor.rect.left << L",\n"; 72 json << L" \"top\": " << monitor.rect.top << L",\n"; 73 json << L" \"right\": " << monitor.rect.right << L",\n"; 74 json << L" \"bottom\": " << monitor.rect.bottom << L",\n"; 75 json << L" \"width\": " << (monitor.rect.right - monitor.rect.left) << L",\n"; 76 json << L" \"height\": " << (monitor.rect.bottom - monitor.rect.top) << L",\n"; 77 json << L" \"dpi\": " << dpiX << L",\n"; 78 json << L" \"scaling_percent\": " << scalingPercent << L",\n"; 79 json << L" \"primary\": " << (monitor.isPrimary ? L"true" : L"false") << L",\n"; 80 json << L" \"monitor_id\": " << monitor.monitorId << L"\n"; 81 json << L" }"; 82 if (i < m_monitors.size() - 1) 83 { 84 json << L","; 85 } 86 json << L"\n"; 87 } 88 89 json << L" ]\n"; 90 json << L"}"; 91 92 return json.str(); 93 } 94 #endif 95 96 void CursorWrapCore::UpdateMonitorInfo() 97 { 98 size_t previousMonitorCount = m_monitors.size(); 99 Logger::info(L"======= UPDATE MONITOR INFO START ======="); 100 Logger::info(L"Previous monitor count: {}", previousMonitorCount); 101 102 m_monitors.clear(); 103 104 EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hMonitor, HDC, LPRECT, LPARAM lParam) -> BOOL { 105 auto* self = reinterpret_cast<CursorWrapCore*>(lParam); 106 107 MONITORINFO mi{}; 108 mi.cbSize = sizeof(MONITORINFO); 109 if (GetMonitorInfo(hMonitor, &mi)) 110 { 111 MonitorInfo info{}; 112 info.hMonitor = hMonitor; // Store handle for direct comparison later 113 info.rect = mi.rcMonitor; 114 info.isPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) != 0; 115 info.monitorId = static_cast<int>(self->m_monitors.size()); 116 self->m_monitors.push_back(info); 117 118 Logger::info(L"Enumerated monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}", 119 info.monitorId, reinterpret_cast<uintptr_t>(hMonitor), 120 mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom, 121 info.isPrimary ? L"yes" : L"no"); 122 } 123 124 return TRUE; 125 }, reinterpret_cast<LPARAM>(this)); 126 127 if (previousMonitorCount != m_monitors.size()) 128 { 129 Logger::info(L"*** MONITOR CONFIGURATION CHANGED: {} -> {} monitors ***", 130 previousMonitorCount, m_monitors.size()); 131 } 132 133 m_topology.Initialize(m_monitors); 134 135 // Log monitor configuration summary 136 Logger::info(L"Monitor configuration updated: {} monitor(s)", m_monitors.size()); 137 for (size_t i = 0; i < m_monitors.size(); ++i) 138 { 139 const auto& m = m_monitors[i]; 140 int width = m.rect.right - m.rect.left; 141 int height = m.rect.bottom - m.rect.top; 142 Logger::info(L" Monitor {}: {}x{} at ({}, {}){}", 143 i, width, height, m.rect.left, m.rect.top, 144 m.isPrimary ? L" [PRIMARY]" : L""); 145 } 146 Logger::info(L" Detected {} outer edges for cursor wrapping", m_topology.GetOuterEdges().size()); 147 148 // Detect and log monitor gaps 149 auto gaps = m_topology.DetectMonitorGaps(); 150 if (!gaps.empty()) 151 { 152 Logger::warn(L"Monitor configuration has coordinate gaps that may prevent wrapping:"); 153 for (const auto& gap : gaps) 154 { 155 Logger::warn(L" Gap between Monitor {} and Monitor {}: {}px horizontal gap, {}px vertical overlap", 156 gap.monitor1Index, gap.monitor2Index, gap.horizontalGap, gap.verticalOverlap); 157 } 158 Logger::warn(L" If monitors appear snapped in Display Settings but show gaps here:"); 159 Logger::warn(L" 1. Try dragging monitors apart and snapping them back together"); 160 Logger::warn(L" 2. Update your GPU drivers"); 161 } 162 163 Logger::info(L"======= UPDATE MONITOR INFO END ======="); 164 } 165 166 POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor) 167 { 168 // Check if wrapping should be disabled on single monitor 169 if (disableOnSingleMonitor && m_monitors.size() <= 1) 170 { 171 #ifdef _DEBUG 172 static bool loggedOnce = false; 173 if (!loggedOnce) 174 { 175 OutputDebugStringW(L"[CursorWrap] Single monitor detected - cursor wrapping disabled\n"); 176 loggedOnce = true; 177 } 178 #endif 179 return currentPos; 180 } 181 182 // Check if wrapping should be disabled during drag 183 if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000)) 184 { 185 #ifdef _DEBUG 186 OutputDebugStringW(L"[CursorWrap] [DRAG] Left mouse button down - skipping wrap\n"); 187 #endif 188 return currentPos; 189 } 190 191 // Convert int wrapMode to WrapMode enum 192 WrapMode mode = static_cast<WrapMode>(wrapMode); 193 194 #ifdef _DEBUG 195 { 196 std::wostringstream oss; 197 oss << L"[CursorWrap] [MOVE] Cursor at (" << currentPos.x << L", " << currentPos.y << L")"; 198 199 // Get current monitor and identify which one 200 HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST); 201 RECT monitorRect; 202 if (m_topology.GetMonitorRect(currentMonitor, monitorRect)) 203 { 204 // Find monitor ID 205 int monitorId = -1; 206 for (const auto& monitor : m_monitors) 207 { 208 if (monitor.rect.left == monitorRect.left && 209 monitor.rect.top == monitorRect.top && 210 monitor.rect.right == monitorRect.right && 211 monitor.rect.bottom == monitorRect.bottom) 212 { 213 monitorId = monitor.monitorId; 214 break; 215 } 216 } 217 oss << L" on Monitor " << monitorId << L" [" << monitorRect.left << L".." << monitorRect.right 218 << L", " << monitorRect.top << L".." << monitorRect.bottom << L"]"; 219 } 220 else 221 { 222 oss << L" (beyond monitor bounds)"; 223 } 224 oss << L"\n"; 225 OutputDebugStringW(oss.str().c_str()); 226 } 227 #endif 228 229 // Get current monitor 230 HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST); 231 232 // Check if cursor is on an outer edge (filtered by wrap mode) 233 EdgeType edgeType; 234 if (!m_topology.IsOnOuterEdge(currentMonitor, currentPos, edgeType, mode)) 235 { 236 #ifdef _DEBUG 237 static bool lastWasNotOuter = false; 238 if (!lastWasNotOuter) 239 { 240 OutputDebugStringW(L"[CursorWrap] [MOVE] Not on outer edge - no wrapping\n"); 241 lastWasNotOuter = true; 242 } 243 #endif 244 return currentPos; // Not on an outer edge 245 } 246 247 #ifdef _DEBUG 248 { 249 const wchar_t* edgeStr = L"Unknown"; 250 switch (edgeType) 251 { 252 case EdgeType::Left: edgeStr = L"Left"; break; 253 case EdgeType::Right: edgeStr = L"Right"; break; 254 case EdgeType::Top: edgeStr = L"Top"; break; 255 case EdgeType::Bottom: edgeStr = L"Bottom"; break; 256 } 257 std::wostringstream oss; 258 oss << L"[CursorWrap] [EDGE] Detected outer " << edgeStr << L" edge at (" << currentPos.x << L", " << currentPos.y << L")\n"; 259 OutputDebugStringW(oss.str().c_str()); 260 } 261 #endif 262 263 // Calculate wrap destination 264 POINT newPos = m_topology.GetWrapDestination(currentMonitor, currentPos, edgeType); 265 266 #ifdef _DEBUG 267 if (newPos.x != currentPos.x || newPos.y != currentPos.y) 268 { 269 std::wostringstream oss; 270 oss << L"[CursorWrap] [WRAP] Position change: (" << currentPos.x << L", " << currentPos.y 271 << L") -> (" << newPos.x << L", " << newPos.y << L")\n"; 272 oss << L"[CursorWrap] [WRAP] Delta: (" << (newPos.x - currentPos.x) << L", " << (newPos.y - currentPos.y) << L")\n"; 273 OutputDebugStringW(oss.str().c_str()); 274 } 275 else 276 { 277 OutputDebugStringW(L"[CursorWrap] [WRAP] No position change (same-monitor wrap?)\n"); 278 } 279 #endif 280 281 return newPos; 282 }