| name | run-maui-app |
| description | Build, launch, screenshot, and drive the WinPrint.Maui Windows app (winprint.exe) — including UIA automation of the File button and native Open dialog, and pixel capture that works for WinUI3 content. Use when asked to run, verify, or screenshot the MAUI app. |
Run & drive the WinPrint MAUI app (Windows)
Build and launch
dotnet build src/WinPrint.Maui/WinPrint.Maui.csproj -f net10.0-windows10.0.19041.0
The app is unpackaged (WindowsPackageType=None), assembly name winprint:
$exe = "src\WinPrint.Maui\bin\Debug\net10.0-windows10.0.19041.0\win-x64\winprint.exe"
# No args — empty preview; or pass a file to load it on startup:
$proc = Start-Process -FilePath $exe -ArgumentList '"C:\full\path\to\file.cs"' -PassThru
Give it ~6-8 seconds to start. Command-line options mirror the CLI (--landscape,
--sheet, --printer, --paper-size; files are positional). Relative file paths are
resolved against the launch CWD (MauiProgram.cs captures it before switching CWD to
the exe dir).
Verify state (works even on a locked session)
Screenshots lie or fail when the session is locked — UI Automation doesn't. Use:
& .claude\skills\run-maui-app\scripts\Get-UIState.ps1 -ProcessId $proc.Id
Signals:
- File loaded? The
🖨 Print... button is enabled only when IsFileLoaded && !IsBusy
(load failure clears ActiveFile, disabling it). This is the reliable check.
- Title:
(Get-Process -Id $proc.Id).MainWindowTitle becomes WinPrint - <name>
when the title sync fires. (Historically this did NOT fire for command-line-loaded
files because the load starts before the page is attached to its Window — don't
treat a stale title alone as "load failed"; check the Print button.)
- The page indicator ("Page x of y") and status text are drawn inside the preview
drawable — invisible to UIA. Pixel capture is the only way to see them.
Screenshot (WinUI3 needs PrintWindow + PW_RENDERFULLCONTENT)
& .claude\skills\run-maui-app\scripts\Capture-Window.ps1 -ProcessId $proc.Id -OutFile out.png
Graphics.CopyFromScreen returns wallpaper/black if the session is locked
(Get-Process LogonUI running ⇒ locked) — don't trust it.
PrintWindow flag must be 2 (PW_RENDERFULLCONTENT) or WinUI3 composition content
comes back black. Even then, captures stay black until the composition surface is
poked — a UIA SetFocus on any element in the window reliably unsticks it
(waiting and resizing do NOT). The script detects an all-black result, pokes focus,
and recaptures automatically.
- Look at the screenshot: a loaded file shows the rendered page with header
(date | filename | language), line numbers, and footer ("Page x of y").
Inject keyboard input (works on a locked session)
SendInput/SendKeys can't reach a locked desktop, but posting WM_KEYDOWN/WM_KEYUP
to the app's InputSiteWindowClass child hwnd does enter the WinUI3 keyboard
pipeline (posting to the top-level hwnd or DesktopChildSiteBridge does NOT). Keys
route only if some XAML element has focus — use UIA SetFocus first if needed.
# Find the input-site child hwnd via EnumChildWindows (class "InputSiteWindowClass"),
# then e.g. PageDown (VK_NEXT=0x22, scan 0x51, extended):
$lpDown = [IntPtr](0x1 -bor (0x51 -shl 16) -bor 0x01000000)
$lpUp = [IntPtr]([long]0x1 -bor (0x51 -shl 16) -bor 0x01000000 -bor 0xC0000000)
[Win32]::PostMessage($inputSite, 0x0100, [IntPtr]0x22, $lpDown) # WM_KEYDOWN
[Win32]::PostMessage($inputSite, 0x0101, [IntPtr]0x22, $lpUp) # WM_KEYUP
Verify paging took effect by capturing and reading the drawn footer ("Page x of y").
Note: GetKeyStateForCurrentThread-based modifier checks (Ctrl/Shift) won't see
modifiers this way — posted messages don't update the thread keyboard state.
Drive the File button + Open dialog
& .claude\skills\run-maui-app\scripts\Drive-FileOpen.ps1 -ProcessId $proc.Id -FilePath "C:\full\path\to\README.md"
This UIA-invokes the 📂 File... button, waits for the native Open dialog (a child
window of the app window), sets the filename edit (automation id 1148), and clicks
Open (automation id 1). Works on a locked session — no real mouse/keyboard input.
Cleanup
$proc.CloseMainWindow() # polite close; prompts only if sheet settings were edited
# fall back to Stop-Process -Force if it lingers
Closing instances matters: the app persists window state and settings next to the exe
on exit, and a leftover instance holds the bin directory, breaking the next build.