/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/MouseUtils/CursorWrap/CursorWrapTests/Capture-MonitorLayout.ps1
327 строк
12 KB
Mike Hall
add script parameters to add user, machine, and devices names, default is to not include. (#44880)
21 янв 2026, 15:58
Не верифицирован
21 янв 2026, 15:58
7cfa4d4
Код
Авторство
О чём код?
#!/usr/bin/env pwsh <# .SYNOPSIS Captures the current monitor layout configuration for CursorWrap testing. .DESCRIPTION Queries Windows for all connected monitors and saves their configuration (position, size, DPI, primary status) to a JSON file that can be used for testing the CursorWrap module. By default, potentially identifying information (computer name, user name, device names) is anonymized to protect privacy when sharing layout files. .PARAMETER OutputPath Path where the JSON file will be saved. Default: cursorwrap_monitor_layout.json .PARAMETER AddUserMachineNames Include computer name and user name in the output. By default these are blank to protect privacy when sharing layout files. .PARAMETER AddDeviceNames Include device names (e.g., \\.\DISPLAY1) in the output. By default these are anonymized to "DISPLAY1", "DISPLAY2", etc. to reduce fingerprinting. .PARAMETER Help Show this help message and exit. .EXAMPLE .\Capture-MonitorLayout.ps1 Captures layout with privacy-safe defaults (no user/machine names). .EXAMPLE .\Capture-MonitorLayout.ps1 -OutputPath "my_setup.json" Saves to a custom filename. .EXAMPLE .\Capture-MonitorLayout.ps1 -AddUserMachineNames Includes computer name and user name in the output. .EXAMPLE .\Capture-MonitorLayout.ps1 -AddUserMachineNames -AddDeviceNames Includes all identifying information (useful for personal debugging). #> param( [Parameter(Mandatory=$false)] [string]$OutputPath = "cursorwrap_monitor_layout.json", [Parameter(Mandatory=$false)] [switch]$AddUserMachineNames, [Parameter(Mandatory=$false)] [switch]$AddDeviceNames, [Parameter(Mandatory=$false)] [Alias("h", "?")] [switch]$Help ) # Show help if requested if ($Help) { Get-Help $MyInvocation.MyCommand.Path -Detailed exit 0 } # Add Windows Forms for screen enumeration Add-Type -AssemblyName System.Windows.Forms function Get-MonitorDPI { param([System.Windows.Forms.Screen]$Screen) # Try to get DPI using P/Invoke with multiple methods Add-Type @" using System; using System.Runtime.InteropServices; public class DisplayConfig { [DllImport("user32.dll")] public static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags); [DllImport("shcore.dll")] public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY); [DllImport("user32.dll")] public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); [DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); [DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr hdc, int nIndex); [DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct MONITORINFOEX { public int cbSize; public RECT rcMonitor; public RECT rcWork; public uint dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szDevice; } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } public const uint MONITOR_DEFAULTTOPRIMARY = 1; public const int MDT_EFFECTIVE_DPI = 0; public const int MDT_ANGULAR_DPI = 1; public const int MDT_RAW_DPI = 2; public const int LOGPIXELSX = 88; public const int LOGPIXELSY = 90; } "@ -ErrorAction SilentlyContinue try { $point = New-Object DisplayConfig+POINT $point.X = $Screen.Bounds.Left + ($Screen.Bounds.Width / 2) $point.Y = $Screen.Bounds.Top + ($Screen.Bounds.Height / 2) $hMonitor = [DisplayConfig]::MonitorFromPoint($point, 1) # Method 1: Try GetDpiForMonitor (Windows 8.1+) [uint]$dpiX = 0 [uint]$dpiY = 0 $result = [DisplayConfig]::GetDpiForMonitor($hMonitor, 0, [ref]$dpiX, [ref]$dpiY) if ($result -eq 0 -and $dpiX -gt 0) { Write-Verbose "DPI detected via GetDpiForMonitor: $dpiX" return $dpiX } # Method 2: Try RAW DPI $result = [DisplayConfig]::GetDpiForMonitor($hMonitor, 2, [ref]$dpiX, [ref]$dpiY) if ($result -eq 0 -and $dpiX -gt 0) { Write-Verbose "DPI detected via RAW DPI: $dpiX" return $dpiX } # Method 3: Try getting device context DPI (legacy method) $hdc = [DisplayConfig]::GetDC([IntPtr]::Zero) if ($hdc -ne [IntPtr]::Zero) { $dpiValue = [DisplayConfig]::GetDeviceCaps($hdc, 88) # LOGPIXELSX [DisplayConfig]::ReleaseDC([IntPtr]::Zero, $hdc) if ($dpiValue -gt 0) { Write-Verbose "DPI detected via GetDeviceCaps: $dpiValue" return $dpiValue } } } catch { Write-Verbose "DPI detection error: $($_.Exception.Message)" } Write-Warning "Could not detect DPI for $($Screen.DeviceName), using default 96 DPI" return 96 # Standard 96 DPI (100% scaling) } function Capture-MonitorLayout { Write-Host "Capturing monitor layout..." -ForegroundColor Cyan Write-Host "=" * 80 $screens = [System.Windows.Forms.Screen]::AllScreens $monitors = @() $monitorIndex = 1 foreach ($screen in $screens) { $isPrimary = $screen.Primary $bounds = $screen.Bounds $dpi = Get-MonitorDPI -Screen $screen # Anonymize device name by default to reduce fingerprinting $deviceName = if ($AddDeviceNames) { $screen.DeviceName } else { "DISPLAY$monitorIndex" } $monitor = [ordered]@{ left = $bounds.Left top = $bounds.Top right = $bounds.Right bottom = $bounds.Bottom width = $bounds.Width height = $bounds.Height dpi = $dpi scaling_percent = [math]::Round(($dpi / 96.0) * 100, 0) primary = $isPrimary device_name = $deviceName } $monitors += $monitor $monitorIndex++ # Display info $primaryTag = if ($isPrimary) { " [PRIMARY]" } else { "" } $scaling = [math]::Round(($dpi / 96.0) * 100, 0) Write-Host "`nMonitor $($monitors.Count)$primaryTag" -ForegroundColor Green Write-Host " Device: $($screen.DeviceName)" Write-Host " Position: ($($bounds.Left), $($bounds.Top))" Write-Host " Size: $($bounds.Width)x$($bounds.Height)" Write-Host " DPI: $dpi ($scaling% scaling)" Write-Host " Bounds: [$($bounds.Left), $($bounds.Top), $($bounds.Right), $($bounds.Bottom)]" } # Create output object with privacy-safe defaults $output = [ordered]@{ captured_at = (Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz") computer_name = if ($AddUserMachineNames) { $env:COMPUTERNAME } else { "" } user_name = if ($AddUserMachineNames) { $env:USERNAME } else { "" } monitor_count = $monitors.Count monitors = $monitors } # Save to JSON $json = $output | ConvertTo-Json -Depth 10 Set-Content -Path $OutputPath -Value $json -Encoding UTF8 Write-Host "`n" + ("=" * 80) Write-Host "Monitor layout saved to: $OutputPath" -ForegroundColor Green Write-Host "Total monitors captured: $($monitors.Count)" -ForegroundColor Cyan Write-Host "`nYou can now use this file with the test script:" -ForegroundColor Yellow Write-Host " python monitor_layout_tests.py --layout-file $OutputPath" -ForegroundColor White return $output } # Main execution try { $layout = Capture-MonitorLayout # Display summary Write-Host "`n" + ("=" * 80) Write-Host "SUMMARY" -ForegroundColor Cyan Write-Host ("=" * 80) if ($layout.computer_name) { Write-Host "Configuration Name: $($layout.computer_name)" } Write-Host "Captured: $($layout.captured_at)" Write-Host "Monitors: $($layout.monitor_count)" # Privacy notice if (-not $AddUserMachineNames -or -not $AddDeviceNames) { Write-Host "`nPrivacy: " -NoNewline -ForegroundColor Yellow $privacyNotes = @() if (-not $AddUserMachineNames) { $privacyNotes += "user/machine names excluded" } if (-not $AddDeviceNames) { $privacyNotes += "device names anonymized" } Write-Host ($privacyNotes -join ", ") -ForegroundColor Yellow Write-Host " Use -AddUserMachineNames and/or -AddDeviceNames to include." -ForegroundColor DarkGray } # Calculate desktop dimensions $widths = @($layout.monitors | ForEach-Object { $_.width }) $heights = @($layout.monitors | ForEach-Object { $_.height }) $totalWidth = ($widths | Measure-Object -Sum).Sum $maxHeight = ($heights | Measure-Object -Maximum).Maximum Write-Host "Total Desktop Width: $totalWidth pixels" Write-Host "Max Desktop Height: $maxHeight pixels" # Analyze potential coordinate issues Write-Host "`n" + ("=" * 80) Write-Host "COORDINATE ANALYSIS" -ForegroundColor Cyan Write-Host ("=" * 80) # Check for gaps between monitors if ($layout.monitor_count -gt 1) { $hasGaps = $false for ($i = 0; $i -lt $layout.monitor_count - 1; $i++) { $m1 = $layout.monitors[$i] for ($j = $i + 1; $j -lt $layout.monitor_count; $j++) { $m2 = $layout.monitors[$j] # Check horizontal gap $hGap = [Math]::Min([Math]::Abs($m1.right - $m2.left), [Math]::Abs($m2.right - $m1.left)) # Check vertical overlap $vOverlapStart = [Math]::Max($m1.top, $m2.top) $vOverlapEnd = [Math]::Min($m1.bottom, $m2.bottom) $vOverlap = $vOverlapEnd - $vOverlapStart if ($hGap -gt 50 -and $vOverlap -gt 0) { Write-Host "⚠ Gap detected between Monitor $($i+1) and Monitor $($j+1): ${hGap}px horizontal gap" -ForegroundColor Yellow Write-Host " Vertical overlap: ${vOverlap}px" -ForegroundColor Yellow Write-Host " This may indicate a Windows coordinate bug if monitors appear snapped in Display Settings" -ForegroundColor Yellow $hasGaps = $true } } } if (-not $hasGaps) { Write-Host "✓ No unexpected gaps detected" -ForegroundColor Green } } # DPI/Scaling notes Write-Host "`nDPI/Scaling Impact on Coordinates:" -ForegroundColor Cyan Write-Host "• Coordinate values (left, top, right, bottom) are in LOGICAL PIXELS" Write-Host "• These are DPI-independent virtual coordinates" Write-Host "• Physical pixels = Logical pixels × (DPI / 96)" Write-Host "• Example: 1920 logical pixels at 150% scaling = 1920 × 1.5 = 2880 physical pixels" Write-Host "• Windows snaps monitors using logical pixel coordinates" Write-Host "• If monitors appear snapped but coordinates show gaps, this is a Windows bug" exit 0 } catch { Write-Host "`nError capturing monitor layout:" -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor DarkGray exit 1 }