/ src / modules / MouseUtils / CursorWrap / CursorWrapCore.cpp
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  }