Skip to content

Commit a4321b0

Browse files
committed
fix: Windows screenshot only captured top-left on HiDPI displays
PowerShell.exe / pwsh.exe run as DPI-unaware by default, so Windows virtualizes coordinates for any WinForms call: on a 4K display at 200% scaling, PrimaryScreen.Bounds reported 1920x1080 and CopyFromScreen dutifully grabbed only the top-left quarter of the real framebuffer. Result: the screenshot 'could not fit full screen' even though nothing was technically wrong with the capture call. Declare per-monitor DPI awareness before touching any Screen API. Use SetProcessDpiAwarenessContext(PER_MONITOR_V2) on Windows 10+, fall back to SetProcessDPIAware on older Windows. After this, PrimaryScreen.Bounds returns real physical pixels and the capture covers the whole primary display. Also add a -VirtualDesktop switch so future multi-monitor callers can request the full joined virtual screen. Default stays on PrimaryScreen to keep the existing 'main_display' semantics.
1 parent 343e391 commit a4321b0

1 file changed

Lines changed: 64 additions & 2 deletions

File tree

scripts/windows/capture-screen.ps1

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,81 @@
11
param(
22
[Parameter(Mandatory = $true)]
3-
[string]$OutputPath
3+
[string]$OutputPath,
4+
[switch]$VirtualDesktop
45
)
56

67
$ErrorActionPreference = "Stop"
78

89
Add-Type -AssemblyName System.Windows.Forms
910
Add-Type -AssemblyName System.Drawing
1011

12+
# Declare per-monitor DPI awareness BEFORE touching any WinForms Screen
13+
# API. PowerShell.exe / pwsh.exe are DPI-unaware by default, so without
14+
# this call Windows virtualizes coordinates: on a 4K display at 200%
15+
# scaling, PrimaryScreen.Bounds would report 1920x1080 and CopyFromScreen
16+
# would grab only the top-left quarter of the real framebuffer. The
17+
# symptom looks like "the screenshot does not fit full screen".
18+
#
19+
# We try the Windows 10 per-monitor API first and fall back to the
20+
# older process-wide API that ships in every Windows version since 7.
21+
$dpiTypes = @"
22+
using System;
23+
using System.Runtime.InteropServices;
24+
25+
public static class ScreenPilotDpi
26+
{
27+
[DllImport("user32.dll")]
28+
private static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
29+
30+
[DllImport("user32.dll")]
31+
private static extern bool SetProcessDPIAware();
32+
33+
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
34+
private static readonly IntPtr PerMonitorV2 = new IntPtr(-4);
35+
36+
public static void Apply()
37+
{
38+
try
39+
{
40+
if (SetProcessDpiAwarenessContext(PerMonitorV2))
41+
{
42+
return;
43+
}
44+
}
45+
catch
46+
{
47+
}
48+
49+
try
50+
{
51+
SetProcessDPIAware();
52+
}
53+
catch
54+
{
55+
}
56+
}
57+
}
58+
"@
59+
60+
if (-not ("ScreenPilotDpi" -as [type])) {
61+
Add-Type -TypeDefinition $dpiTypes -Language CSharp
62+
}
63+
[ScreenPilotDpi]::Apply()
64+
1165
$outputDirectory = Split-Path -Parent $OutputPath
1266
if ($outputDirectory -and -not (Test-Path -LiteralPath $outputDirectory)) {
1367
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
1468
}
1569

16-
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
70+
# Default to the primary display, which matches the "main_display"
71+
# capture target semantics used by the agent. Pass -VirtualDesktop to
72+
# capture every monitor joined into one image.
73+
if ($VirtualDesktop) {
74+
$bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen
75+
} else {
76+
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
77+
}
78+
1779
$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
1880
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
1981

0 commit comments

Comments
 (0)