/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/MouseUtils/CursorWrap/MonitorTopology.cpp
825 строк
27 KB
Michael Jolley
CmdPal: Adding a lock around perf monitor updates (#46061)
12 мар 2026, 00:06
Не верифицирован
12 мар 2026, 00:06
b81ea23
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #include "pch.h" #include "MonitorTopology.h" #include "CursorWrapCore.h" // For CursorDirection struct #include "../../../common/logger/logger.h" #include <algorithm> #include <cmath> void MonitorTopology::Initialize(const std::vector<MonitorInfo>& monitors) { Logger::info(L"======= TOPOLOGY INITIALIZATION START ======="); Logger::info(L"Initializing edge-based topology for {} monitors", monitors.size()); m_monitors = monitors; m_outerEdges.clear(); m_edgeMap.clear(); if (monitors.empty()) { Logger::warn(L"No monitors provided to Initialize"); return; } // Log monitor details for (size_t i = 0; i < monitors.size(); ++i) { const auto& m = monitors[i]; Logger::info(L"Monitor {}: hMonitor={}, rect=({},{},{},{}), primary={}", i, reinterpret_cast<uintptr_t>(m.hMonitor), m.rect.left, m.rect.top, m.rect.right, m.rect.bottom, m.isPrimary ? L"yes" : L"no"); } BuildEdgeMap(); IdentifyOuterEdges(); Logger::info(L"Found {} outer edges", m_outerEdges.size()); for (const auto& edge : m_outerEdges) { const wchar_t* typeStr = L"Unknown"; switch (edge.type) { case EdgeType::Left: typeStr = L"Left"; break; case EdgeType::Right: typeStr = L"Right"; break; case EdgeType::Top: typeStr = L"Top"; break; case EdgeType::Bottom: typeStr = L"Bottom"; break; } Logger::info(L"Outer edge: Monitor {} {} at position {}, range [{}, {}]", edge.monitorIndex, typeStr, edge.position, edge.start, edge.end); } Logger::info(L"======= TOPOLOGY INITIALIZATION COMPLETE ======="); } void MonitorTopology::BuildEdgeMap() { // Create edges for each monitor using monitor index (not HMONITOR) // This is important because HMONITOR handles can change when monitors are // added/removed dynamically, but indices remain stable within a single // topology configuration for (size_t idx = 0; idx < m_monitors.size(); ++idx) { const auto& monitor = m_monitors[idx]; int monitorIndex = static_cast<int>(idx); // Left edge MonitorEdge leftEdge; leftEdge.monitorIndex = monitorIndex; leftEdge.type = EdgeType::Left; leftEdge.position = monitor.rect.left; leftEdge.start = monitor.rect.top; leftEdge.end = monitor.rect.bottom; leftEdge.isOuter = true; // Will be updated in IdentifyOuterEdges m_edgeMap[{monitorIndex, EdgeType::Left}] = leftEdge; // Right edge MonitorEdge rightEdge; rightEdge.monitorIndex = monitorIndex; rightEdge.type = EdgeType::Right; rightEdge.position = monitor.rect.right - 1; rightEdge.start = monitor.rect.top; rightEdge.end = monitor.rect.bottom; rightEdge.isOuter = true; m_edgeMap[{monitorIndex, EdgeType::Right}] = rightEdge; // Top edge MonitorEdge topEdge; topEdge.monitorIndex = monitorIndex; topEdge.type = EdgeType::Top; topEdge.position = monitor.rect.top; topEdge.start = monitor.rect.left; topEdge.end = monitor.rect.right; topEdge.isOuter = true; m_edgeMap[{monitorIndex, EdgeType::Top}] = topEdge; // Bottom edge MonitorEdge bottomEdge; bottomEdge.monitorIndex = monitorIndex; bottomEdge.type = EdgeType::Bottom; bottomEdge.position = monitor.rect.bottom - 1; bottomEdge.start = monitor.rect.left; bottomEdge.end = monitor.rect.right; bottomEdge.isOuter = true; m_edgeMap[{monitorIndex, EdgeType::Bottom}] = bottomEdge; } } void MonitorTopology::IdentifyOuterEdges() { const int tolerance = 50; // Check each edge against all other edges to find adjacent ones for (auto& [key1, edge1] : m_edgeMap) { for (const auto& [key2, edge2] : m_edgeMap) { if (edge1.monitorIndex == edge2.monitorIndex) { continue; // Same monitor } // Check if edges are adjacent if (EdgesAreAdjacent(edge1, edge2, tolerance)) { edge1.isOuter = false; break; // This edge has an adjacent monitor } } if (edge1.isOuter) { m_outerEdges.push_back(edge1); } } } bool MonitorTopology::EdgesAreAdjacent(const MonitorEdge& edge1, const MonitorEdge& edge2, int tolerance) const { // Edges must be opposite types to be adjacent bool oppositeTypes = false; if ((edge1.type == EdgeType::Left && edge2.type == EdgeType::Right) || (edge1.type == EdgeType::Right && edge2.type == EdgeType::Left) || (edge1.type == EdgeType::Top && edge2.type == EdgeType::Bottom) || (edge1.type == EdgeType::Bottom && edge2.type == EdgeType::Top)) { oppositeTypes = true; } if (!oppositeTypes) { return false; } // Check if positions are within tolerance if (abs(edge1.position - edge2.position) > tolerance) { return false; } // Check if perpendicular ranges overlap int overlapStart = max(edge1.start, edge2.start); int overlapEnd = min(edge1.end, edge2.end); return overlapEnd > overlapStart + tolerance; } EdgeType MonitorTopology::PrioritizeEdgeByDirection(const std::vector<EdgeType>& candidates, const CursorDirection* direction) const { if (candidates.empty()) { return EdgeType::Left; // Should not happen, but return a default } if (candidates.size() == 1 || direction == nullptr) { return candidates[0]; } // Prioritize based on movement direction // If moving primarily horizontally, prefer horizontal edges (Left/Right) // If moving primarily vertically, prefer vertical edges (Top/Bottom) if (direction->IsPrimarilyHorizontal()) { // Prefer Left if moving left, Right if moving right if (direction->IsMovingLeft()) { for (EdgeType edge : candidates) { if (edge == EdgeType::Left) return edge; } } else if (direction->IsMovingRight()) { for (EdgeType edge : candidates) { if (edge == EdgeType::Right) return edge; } } // Fall back to any horizontal edge for (EdgeType edge : candidates) { if (edge == EdgeType::Left || edge == EdgeType::Right) return edge; } } else { // Prefer Top if moving up, Bottom if moving down if (direction->IsMovingUp()) { for (EdgeType edge : candidates) { if (edge == EdgeType::Top) return edge; } } else if (direction->IsMovingDown()) { for (EdgeType edge : candidates) { if (edge == EdgeType::Bottom) return edge; } } // Fall back to any vertical edge for (EdgeType edge : candidates) { if (edge == EdgeType::Top || edge == EdgeType::Bottom) return edge; } } // Default to first candidate return candidates[0]; } bool MonitorTopology::IsOnOuterEdge(HMONITOR monitor, const POINT& cursorPos, EdgeType& outEdgeType, WrapMode wrapMode, const CursorDirection* direction) const { RECT monitorRect; if (!GetMonitorRect(monitor, monitorRect)) { Logger::warn(L"IsOnOuterEdge: GetMonitorRect failed for monitor handle {}", reinterpret_cast<uintptr_t>(monitor)); return false; } // Get monitor index for edge map lookup int monitorIndex = GetMonitorIndex(monitor); if (monitorIndex < 0) { Logger::warn(L"IsOnOuterEdge: Monitor index not found for handle {} at cursor ({}, {})", reinterpret_cast<uintptr_t>(monitor), cursorPos.x, cursorPos.y); return false; // Monitor not found in our list } // Check each edge type const int edgeThreshold = 1; // At corners, multiple edges may match - collect all candidates and try each // to find one with a valid wrap destination std::vector<EdgeType> candidateEdges; // Left edge - only if mode allows horizontal wrapping if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) && cursorPos.x <= monitorRect.left + edgeThreshold) { auto it = m_edgeMap.find({monitorIndex, EdgeType::Left}); if (it != m_edgeMap.end() && it->second.isOuter) { candidateEdges.push_back(EdgeType::Left); } } // Right edge - only if mode allows horizontal wrapping if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::HorizontalOnly) && cursorPos.x >= monitorRect.right - 1 - edgeThreshold) { auto it = m_edgeMap.find({monitorIndex, EdgeType::Right}); if (it != m_edgeMap.end()) { if (it->second.isOuter) { candidateEdges.push_back(EdgeType::Right); } // Debug: Log why right edge isn't outer else { Logger::trace(L"IsOnOuterEdge: Monitor {} right edge is NOT outer (inner edge)", monitorIndex); } } } // Top edge - only if mode allows vertical wrapping if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) && cursorPos.y <= monitorRect.top + edgeThreshold) { auto it = m_edgeMap.find({monitorIndex, EdgeType::Top}); if (it != m_edgeMap.end() && it->second.isOuter) { candidateEdges.push_back(EdgeType::Top); } } // Bottom edge - only if mode allows vertical wrapping if ((wrapMode == WrapMode::Both || wrapMode == WrapMode::VerticalOnly) && cursorPos.y >= monitorRect.bottom - 1 - edgeThreshold) { auto it = m_edgeMap.find({monitorIndex, EdgeType::Bottom}); if (it != m_edgeMap.end() && it->second.isOuter) { candidateEdges.push_back(EdgeType::Bottom); } } if (candidateEdges.empty()) { return false; } // Prioritize candidates by movement direction at corners EdgeType prioritizedEdge = PrioritizeEdgeByDirection(candidateEdges, direction); // Get the source edge info auto sourceIt = m_edgeMap.find({monitorIndex, prioritizedEdge}); if (sourceIt == m_edgeMap.end()) { return false; } // Use the new FindNearestOppositeEdge which handles non-overlapping regions int cursorCoord = (prioritizedEdge == EdgeType::Left || prioritizedEdge == EdgeType::Right) ? cursorPos.y : cursorPos.x; OppositeEdgeResult result = FindNearestOppositeEdge(prioritizedEdge, cursorCoord, sourceIt->second); if (result.found) { outEdgeType = prioritizedEdge; return true; } // If prioritized edge didn't work, try other candidates for (EdgeType candidate : candidateEdges) { if (candidate == prioritizedEdge) continue; auto it = m_edgeMap.find({monitorIndex, candidate}); if (it == m_edgeMap.end()) continue; int coord = (candidate == EdgeType::Left || candidate == EdgeType::Right) ? cursorPos.y : cursorPos.x; OppositeEdgeResult altResult = FindNearestOppositeEdge(candidate, coord, it->second); if (altResult.found) { outEdgeType = candidate; return true; } } return false; } POINT MonitorTopology::GetWrapDestination(HMONITOR fromMonitor, const POINT& cursorPos, EdgeType edgeType) const { // Get monitor index for edge map lookup int monitorIndex = GetMonitorIndex(fromMonitor); if (monitorIndex < 0) { return cursorPos; // Monitor not found } auto it = m_edgeMap.find({monitorIndex, edgeType}); if (it == m_edgeMap.end()) { return cursorPos; // Edge not found } const MonitorEdge& fromEdge = it->second; // Get cursor coordinate perpendicular to the edge int cursorCoord = (edgeType == EdgeType::Left || edgeType == EdgeType::Right) ? cursorPos.y : cursorPos.x; // Use the new FindNearestOppositeEdge which handles non-overlapping regions OppositeEdgeResult oppositeResult = FindNearestOppositeEdge(edgeType, cursorCoord, fromEdge); if (!oppositeResult.found) { // No opposite edge found, wrap within same monitor RECT monitorRect; if (GetMonitorRect(fromMonitor, monitorRect)) { POINT result = cursorPos; switch (edgeType) { case EdgeType::Left: result.x = monitorRect.right - 2; break; case EdgeType::Right: result.x = monitorRect.left + 1; break; case EdgeType::Top: result.y = monitorRect.bottom - 2; break; case EdgeType::Bottom: result.y = monitorRect.top + 1; break; } return result; } return cursorPos; } // Calculate target position on opposite edge POINT result; if (edgeType == EdgeType::Left || edgeType == EdgeType::Right) { // Horizontal wrapping (Left<->Right edges) result.x = oppositeResult.edge.position; if (oppositeResult.requiresProjection) { // Use the pre-calculated projected coordinate for non-overlapping regions result.y = oppositeResult.projectedCoordinate; } else { // Overlapping region - preserve Y coordinate result.y = cursorPos.y; } } else { // Vertical wrapping (Top<->Bottom edges) result.y = oppositeResult.edge.position; if (oppositeResult.requiresProjection) { // Use the pre-calculated projected coordinate for non-overlapping regions result.x = oppositeResult.projectedCoordinate; } else { // Overlapping region - preserve X coordinate result.x = cursorPos.x; } } return result; } MonitorEdge MonitorTopology::FindOppositeOuterEdge(EdgeType fromEdge, int relativePosition) const { EdgeType targetType; bool findMax; // true = find max position, false = find min position switch (fromEdge) { case EdgeType::Left: targetType = EdgeType::Right; findMax = true; break; case EdgeType::Right: targetType = EdgeType::Left; findMax = false; break; case EdgeType::Top: targetType = EdgeType::Bottom; findMax = true; break; case EdgeType::Bottom: targetType = EdgeType::Top; findMax = false; break; default: return { .monitorIndex = -1 }; // Invalid edge type } MonitorEdge result = { .monitorIndex = -1 }; // -1 indicates not found int extremePosition = findMax ? INT_MIN : INT_MAX; for (const auto& edge : m_outerEdges) { if (edge.type != targetType) { continue; } // Check if this edge overlaps with the relative position if (relativePosition >= edge.start && relativePosition <= edge.end) { if ((findMax && edge.position > extremePosition) || (!findMax && edge.position < extremePosition)) { extremePosition = edge.position; result = edge; } } } return result; } OppositeEdgeResult MonitorTopology::FindNearestOppositeEdge(EdgeType fromEdge, int cursorCoordinate, const MonitorEdge& sourceEdge) const { OppositeEdgeResult result; result.found = false; result.requiresProjection = false; result.projectedCoordinate = 0; result.edge.monitorIndex = -1; EdgeType targetType; bool findMax; // true = find max position (furthest right/bottom), false = find min (furthest left/top) switch (fromEdge) { case EdgeType::Left: targetType = EdgeType::Right; findMax = true; break; case EdgeType::Right: targetType = EdgeType::Left; findMax = false; break; case EdgeType::Top: targetType = EdgeType::Bottom; findMax = true; break; case EdgeType::Bottom: targetType = EdgeType::Top; findMax = false; break; default: return result; // Invalid edge type } // First, try to find an edge that directly overlaps the cursor coordinate MonitorEdge directMatch = FindOppositeOuterEdge(fromEdge, cursorCoordinate); if (directMatch.monitorIndex >= 0) { result.found = true; result.requiresProjection = false; result.edge = directMatch; result.projectedCoordinate = cursorCoordinate; // Not used, but set for completeness return result; } // No direct overlap - find the nearest opposite edge by coordinate distance // This handles the "dead zone" case where cursor is in a non-overlapping region int bestDistance = INT_MAX; MonitorEdge bestEdge = { .monitorIndex = -1 }; int bestProjectedCoord = 0; for (const auto& edge : m_outerEdges) { if (edge.type != targetType) { continue; } // Calculate distance from cursor coordinate to this edge's range int distance = 0; int projectedCoord = 0; if (cursorCoordinate < edge.start) { // Cursor is before the edge's start - project to edge start with offset distance = edge.start - cursorCoordinate; projectedCoord = edge.start; // Clamp to edge start } else if (cursorCoordinate > edge.end) { // Cursor is after the edge's end - project to edge end with offset distance = cursorCoordinate - edge.end; projectedCoord = edge.end; // Clamp to edge end } else { // Cursor overlaps - this shouldn't happen since we checked direct match distance = 0; projectedCoord = cursorCoordinate; } // Choose the best edge: prefer closer edges, and among equals prefer extreme position bool isBetter = false; if (distance < bestDistance) { isBetter = true; } else if (distance == bestDistance && bestEdge.monitorIndex >= 0) { // Same distance - prefer the extreme position (furthest in wrap direction) if ((findMax && edge.position > bestEdge.position) || (!findMax && edge.position < bestEdge.position)) { isBetter = true; } } if (isBetter) { bestDistance = distance; bestEdge = edge; bestProjectedCoord = projectedCoord; } } if (bestEdge.monitorIndex >= 0) { result.found = true; result.requiresProjection = true; result.edge = bestEdge; // Calculate projected position using offset-from-boundary approach result.projectedCoordinate = CalculateProjectedPosition(cursorCoordinate, sourceEdge, bestEdge); Logger::trace(L"FindNearestOppositeEdge: Non-overlapping wrap from {} to Mon {} edge, cursor={}, projected={}", static_cast<int>(fromEdge), bestEdge.monitorIndex, cursorCoordinate, result.projectedCoordinate); } return result; } int MonitorTopology::CalculateProjectedPosition(int cursorCoordinate, const MonitorEdge& sourceEdge, const MonitorEdge& targetEdge) const { // Windows behavior for non-overlapping regions: // When cursor is in a region that doesn't overlap with the target edge, // clamp to the nearest boundary of the target edge. // This matches observed Windows cursor transition behavior. // Find the shared boundary region between source and target edges int sharedStart = max(sourceEdge.start, targetEdge.start); int sharedEnd = min(sourceEdge.end, targetEdge.end); if (cursorCoordinate >= sharedStart && cursorCoordinate <= sharedEnd) { // Cursor is in shared region - preserve the coordinate exactly return cursorCoordinate; } // For non-overlapping regions, clamp to the nearest boundary of the target edge // This matches Windows behavior where the cursor is projected to the closest // valid point on the destination edge int projectedCoord; if (cursorCoordinate < sharedStart) { // Cursor is BEFORE the shared region (e.g., above shared area) // Clamp to the start of the target edge (with small offset to stay within bounds) projectedCoord = targetEdge.start + 1; } else { // Cursor is AFTER the shared region (e.g., below shared area) // Clamp to the end of the target edge (with small offset to stay within bounds) projectedCoord = targetEdge.end - 1; } // Final bounds check projectedCoord = max(targetEdge.start, min(projectedCoord, targetEdge.end - 1)); return projectedCoord; } double MonitorTopology::GetRelativePosition(const MonitorEdge& edge, int coordinate) const { if (edge.end == edge.start) { return 0.5; // Avoid division by zero } int clamped = max(edge.start, min(coordinate, edge.end)); // Use int64_t to avoid overflow warning C26451 int64_t numerator = static_cast<int64_t>(clamped) - static_cast<int64_t>(edge.start); int64_t denominator = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start); return static_cast<double>(numerator) / static_cast<double>(denominator); } int MonitorTopology::GetAbsolutePosition(const MonitorEdge& edge, double relativePosition) const { // Use int64_t to prevent arithmetic overflow during subtraction and multiplication int64_t range = static_cast<int64_t>(edge.end) - static_cast<int64_t>(edge.start); int64_t offset = static_cast<int64_t>(relativePosition * static_cast<double>(range)); // Clamp result to int range before returning int64_t result = static_cast<int64_t>(edge.start) + offset; return static_cast<int>(result); } std::vector<MonitorTopology::GapInfo> MonitorTopology::DetectMonitorGaps() const { std::vector<GapInfo> gaps; const int gapThreshold = 50; // Same as ADJACENCY_TOLERANCE // Check each pair of monitors for (size_t i = 0; i < m_monitors.size(); ++i) { for (size_t j = i + 1; j < m_monitors.size(); ++j) { const auto& m1 = m_monitors[i]; const auto& m2 = m_monitors[j]; // Check vertical overlap int vOverlapStart = max(m1.rect.top, m2.rect.top); int vOverlapEnd = min(m1.rect.bottom, m2.rect.bottom); int vOverlap = vOverlapEnd - vOverlapStart; if (vOverlap <= 0) { continue; // No vertical overlap, skip } // Check horizontal gap int hGap = min(abs(m1.rect.right - m2.rect.left), abs(m2.rect.right - m1.rect.left)); if (hGap > gapThreshold) { GapInfo gap; gap.monitor1Index = static_cast<int>(i); gap.monitor2Index = static_cast<int>(j); gap.horizontalGap = hGap; gap.verticalOverlap = vOverlap; gaps.push_back(gap); } } } return gaps; } HMONITOR MonitorTopology::GetMonitorFromPoint(const POINT& pt) const { return MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); } bool MonitorTopology::GetMonitorRect(HMONITOR monitor, RECT& rect) const { // First try direct HMONITOR comparison for (const auto& monitorInfo : m_monitors) { if (monitorInfo.hMonitor == monitor) { rect = monitorInfo.rect; return true; } } // Fallback: If direct comparison fails, try matching by current monitor info MONITORINFO mi{}; mi.cbSize = sizeof(MONITORINFO); if (GetMonitorInfo(monitor, &mi)) { for (const auto& monitorInfo : m_monitors) { if (monitorInfo.rect.left == mi.rcMonitor.left && monitorInfo.rect.top == mi.rcMonitor.top && monitorInfo.rect.right == mi.rcMonitor.right && monitorInfo.rect.bottom == mi.rcMonitor.bottom) { rect = monitorInfo.rect; return true; } } } return false; } HMONITOR MonitorTopology::GetMonitorFromRect(const RECT& rect) const { return MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST); } int MonitorTopology::GetMonitorIndex(HMONITOR monitor) const { // First try direct HMONITOR comparison (fast and accurate) for (size_t i = 0; i < m_monitors.size(); ++i) { if (m_monitors[i].hMonitor == monitor) { return static_cast<int>(i); } } // Fallback: If direct comparison fails (e.g., handle changed after display reconfiguration), // try matching by position. Get the monitor's current rect and find matching stored rect. MONITORINFO mi{}; mi.cbSize = sizeof(MONITORINFO); if (GetMonitorInfo(monitor, &mi)) { for (size_t i = 0; i < m_monitors.size(); ++i) { // Match by rect bounds if (m_monitors[i].rect.left == mi.rcMonitor.left && m_monitors[i].rect.top == mi.rcMonitor.top && m_monitors[i].rect.right == mi.rcMonitor.right && m_monitors[i].rect.bottom == mi.rcMonitor.bottom) { Logger::trace(L"GetMonitorIndex: Found monitor {} via rect fallback (handle changed from {} to {})", i, reinterpret_cast<uintptr_t>(m_monitors[i].hMonitor), reinterpret_cast<uintptr_t>(monitor)); return static_cast<int>(i); } } // Log all stored monitors vs the requested one for debugging Logger::warn(L"GetMonitorIndex: No match found. Requested monitor rect=({},{},{},{})", mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom); for (size_t i = 0; i < m_monitors.size(); ++i) { Logger::warn(L" Stored monitor {}: rect=({},{},{},{})", i, m_monitors[i].rect.left, m_monitors[i].rect.top, m_monitors[i].rect.right, m_monitors[i].rect.bottom); } } else { Logger::warn(L"GetMonitorIndex: GetMonitorInfo failed for handle {}", reinterpret_cast<uintptr_t>(monitor)); } return -1; // Not found }