param([string]$Executable = "$PSScriptRoot\..\build\staged\KSwitcher.Screenshot.exe") $ErrorActionPreference='Stop' if (Get-Process KSwitcher.Screenshot -ErrorAction SilentlyContinue) { throw 'Close existing screenshot editors before this test.' } Add-Type @' using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; public static class ScreenshotProcessTest { public delegate bool EnumProc(IntPtr w, IntPtr p); [DllImport("user32.dll")] static extern bool EnumWindows(EnumProc callback, IntPtr param); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr w, out uint pid); [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetClassName(IntPtr w, StringBuilder name, int count); [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr w, StringBuilder name, int count); [DllImport("user32.dll")] public static extern bool PostMessage(IntPtr w, uint msg, IntPtr wp, IntPtr lp); public static IntPtr[] Windows(int process, string cls) { var windows=new List(); EnumWindows((w,p)=>{ uint pid; GetWindowThreadProcessId(w,out pid); var name=new StringBuilder(256); GetClassName(w,name,256); if(pid==process && name.ToString()==cls) windows.Add(w); return true; },IntPtr.Zero); return windows.ToArray(); } public static string Title(IntPtr w) { var s=new StringBuilder(256); GetWindowText(w,s,256); return s.ToString(); } } '@ function Wait-For([scriptblock]$Check,[string]$Message) { for($attempt=0;$attempt -lt 100;$attempt++) { if (& $Check) { return }; Start-Sleep -Milliseconds 50 } throw $Message } function Send($Window,$Message,$Wparam=0,$Lparam=0) { [ScreenshotProcessTest]::PostMessage($Window,$Message,[IntPtr]$Wparam,[IntPtr]$Lparam) | Out-Null } function Capture-Windows { @([ScreenshotProcessTest]::Windows($script:process.Id,'KSwitcher.Capture')) } function Select-Region($Window) { Send $Window 0x201 1 ((40 -shl 16) -bor 40) Send $Window 0x200 1 ((240 -shl 16) -bor 360) Send $Window 0x202 0 ((240 -shl 16) -bor 360) Wait-For { (Capture-Windows) -notcontains $Window } 'Selection did not close after copying (notifications may be disabled).' } function Close-Editor($Window) { Send $Window 0x10 Wait-For { (Capture-Windows) -notcontains $Window } 'Unedited copied screenshot did not close.' } function Latest-Token { (Get-ChildItem -LiteralPath (Join-Path $env:LOCALAPPDATA 'KSwitcher/ScreenshotNotifications') -Filter '*.capture' | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1).BaseName } $process=$null try { $process=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru Wait-For { (Capture-Windows).Count -eq 1 } 'First selection did not open.' Select-Region (Capture-Windows)[0] if (!$process.WaitForExit(5000)) { throw 'Process remained alive after notification.' } $token=Latest-Token $process=Start-Process -FilePath $Executable -ArgumentList @('--open',"kswitcher-screenshot:$token") -WindowStyle Hidden -PassThru Wait-For { (Capture-Windows).Count -eq 1 } 'Notification activation did not open an editor.' $first=(Capture-Windows)[0] $secondLaunch=Start-Process -FilePath $Executable -ArgumentList @('--open',"kswitcher-screenshot:$token") -WindowStyle Hidden -PassThru if (!$secondLaunch.WaitForExit(10000) -or $secondLaunch.ExitCode -ne 0) { throw 'Second activation did not hand off.' } Wait-For { (Capture-Windows).Count -eq 2 } 'Second editor did not open in same process.' $second=(Capture-Windows | Where-Object { $_ -ne $first })[0] if ([ScreenshotProcessTest]::Title($first) -eq [ScreenshotProcessTest]::Title($second)) { throw 'Editor indices collide.' } Close-Editor $first if ($process.HasExited) { throw 'Process exited with an editor open.' } Close-Editor $second if (!$process.WaitForExit(5000) -or $process.ExitCode -ne 0) { throw 'Last editor did not exit process.' } $process=Start-Process -FilePath $Executable -WindowStyle Hidden -PassThru Wait-For { (Capture-Windows).Count -eq 1 } 'New process did not open selection.' Send (Capture-Windows)[0] 0x100 27 # Esc cancels a selection only. if (!$process.WaitForExit(5000) -or $process.ExitCode -ne 0) { throw 'Cancelling selection did not exit process.' } 'Passed screenshot process lifecycle checks' } finally { if ($process -and !$process.HasExited) { Stop-Process -Id $process.Id } }