176 lines
7.2 KiB
PowerShell
176 lines
7.2 KiB
PowerShell
|
|
# drive.ps1 - focus the Code Count window, optionally send keys, optionally capture a screenshot.
|
||
|
|
# Usage: powershell -File drive.ps1 -Keys "alt right right down enter" -Out shot.png -SettleMs 800
|
||
|
|
param(
|
||
|
|
[string]$Keys = "",
|
||
|
|
[string]$Out = "",
|
||
|
|
[string]$RClick = "", # "x,y" window-relative right click
|
||
|
|
[string]$LClick = "", # "x,y" window-relative left click
|
||
|
|
[string]$Move = "", # "x,y" window-relative hover (no click)
|
||
|
|
[string]$Wheel = "", # "x,y,ticks" wheel at point (positive ticks = up)
|
||
|
|
[int]$SettleMs = 700,
|
||
|
|
[int]$KeyGapMs = 150,
|
||
|
|
[long]$Hwnd = 0
|
||
|
|
)
|
||
|
|
$ErrorActionPreference = 'Stop'
|
||
|
|
Add-Type -ReferencedAssemblies System.Drawing @"
|
||
|
|
using System;
|
||
|
|
using System.Runtime.InteropServices;
|
||
|
|
public class Drv {
|
||
|
|
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h);
|
||
|
|
[DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint flags);
|
||
|
|
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
|
||
|
|
[DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint fl, UIntPtr ex);
|
||
|
|
[DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y);
|
||
|
|
[DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT p);
|
||
|
|
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; }
|
||
|
|
[DllImport("user32.dll")] public static extern void mouse_event(uint fl, int dx, int dy, uint data, UIntPtr ex);
|
||
|
|
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr FindWindow(string cls, string title);
|
||
|
|
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
||
|
|
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
|
||
|
|
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd);
|
||
|
|
[DllImport("user32.dll")] public static extern IntPtr WindowFromPoint(POINT p);
|
||
|
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
||
|
|
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassName(IntPtr h, System.Text.StringBuilder sb, int n);
|
||
|
|
[DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
|
||
|
|
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int L; public int T; public int R; public int B; }
|
||
|
|
}
|
||
|
|
"@
|
||
|
|
|
||
|
|
[Drv]::SetProcessDPIAware() | Out-Null
|
||
|
|
if ($Hwnd -ne 0) {
|
||
|
|
$h = [IntPtr]$Hwnd
|
||
|
|
} else {
|
||
|
|
# Window title is non-ASCII now; resolve via process main window handle to keep this file ASCII-safe.
|
||
|
|
$proc = Get-Process code-count | Where-Object { $_.MainWindowHandle -ne 0 } | Sort-Object StartTime -Descending | Select-Object -First 1
|
||
|
|
if (-not $proc) { throw "app window not found" }
|
||
|
|
$h = $proc.MainWindowHandle
|
||
|
|
}
|
||
|
|
if ([Drv]::IsIconic($h)) {
|
||
|
|
[Drv]::ShowWindow($h, 9) | Out-Null # SW_RESTORE
|
||
|
|
Start-Sleep -Milliseconds 600
|
||
|
|
Write-Output "restored from minimized"
|
||
|
|
}
|
||
|
|
[Drv]::SetForegroundWindow($h) | Out-Null
|
||
|
|
Start-Sleep -Milliseconds 300
|
||
|
|
if ([Drv]::GetForegroundWindow() -ne $h) {
|
||
|
|
# press Alt to satisfy the foreground-lock heuristic, then retry
|
||
|
|
[Drv]::keybd_event(0x12, 0, 0, [UIntPtr]::Zero)
|
||
|
|
[Drv]::SetForegroundWindow($h) | Out-Null
|
||
|
|
[Drv]::keybd_event(0x12, 0, 2, [UIntPtr]::Zero)
|
||
|
|
Start-Sleep -Milliseconds 300
|
||
|
|
}
|
||
|
|
Write-Output ("foreground=" + ([Drv]::GetForegroundWindow() -eq $h))
|
||
|
|
Start-Sleep -Milliseconds 200
|
||
|
|
|
||
|
|
$vk = @{ alt=0x12; ctrl=0x11; shift=0x10; enter=0x0D; esc=0x1B; tab=0x09; space=0x20; f10=0x79;
|
||
|
|
left=0x25; up=0x26; right=0x27; down=0x28; apps=0x5D }
|
||
|
|
$ext = @(0x25,0x26,0x27,0x28,0x5D)
|
||
|
|
|
||
|
|
function Press([int[]]$codes) {
|
||
|
|
foreach ($c in $codes) {
|
||
|
|
$f = if ($ext -contains $c) { 1 } else { 0 }
|
||
|
|
[Drv]::keybd_event([byte]$c, 0, [uint32]$f, [UIntPtr]::Zero)
|
||
|
|
Start-Sleep -Milliseconds 25
|
||
|
|
}
|
||
|
|
[array]::Reverse($codes)
|
||
|
|
foreach ($c in $codes) {
|
||
|
|
$f = if ($ext -contains $c) { 3 } else { 2 }
|
||
|
|
[Drv]::keybd_event([byte]$c, 0, [uint32]$f, [UIntPtr]::Zero)
|
||
|
|
Start-Sleep -Milliseconds 25
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Keys) {
|
||
|
|
foreach ($tok in $Keys.Split(' ')) {
|
||
|
|
if (-not $tok) { continue }
|
||
|
|
if ($tok.StartsWith('type:')) {
|
||
|
|
foreach ($ch in $tok.Substring(5).ToCharArray()) { Press @([int][char]::ToUpper($ch)) }
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
$codes = @()
|
||
|
|
foreach ($part in $tok.Split('+')) {
|
||
|
|
if ($vk.ContainsKey($part)) { $codes += [int]$vk[$part] }
|
||
|
|
elseif ($part.Length -eq 1) { $codes += [int][char]::ToUpper($part[0]) }
|
||
|
|
else { throw "unknown key: $part" }
|
||
|
|
}
|
||
|
|
Press $codes
|
||
|
|
Start-Sleep -Milliseconds $KeyGapMs
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Move) {
|
||
|
|
$xy = $Move.Split(',')
|
||
|
|
$rm = New-Object Drv+RECT
|
||
|
|
[Drv]::GetWindowRect($h, [ref]$rm) | Out-Null
|
||
|
|
[Drv]::SetCursorPos($rm.L + [int]$xy[0], $rm.T + [int]$xy[1]) | Out-Null
|
||
|
|
# nudge one pixel so the webview registers a mousemove event
|
||
|
|
Start-Sleep -Milliseconds 80
|
||
|
|
[Drv]::mouse_event(0x0001, 1, 0, 0, [UIntPtr]::Zero)
|
||
|
|
Start-Sleep -Milliseconds 200
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($RClick) {
|
||
|
|
$xy = $RClick.Split(',')
|
||
|
|
$r0 = New-Object Drv+RECT
|
||
|
|
[Drv]::GetWindowRect($h, [ref]$r0) | Out-Null
|
||
|
|
$px = $r0.L + [int]$xy[0]; $py = $r0.T + [int]$xy[1]
|
||
|
|
[Drv]::SetCursorPos($px, $py) | Out-Null
|
||
|
|
Start-Sleep -Milliseconds 150
|
||
|
|
[Drv]::mouse_event(0x0008, 0, 0, 0, [UIntPtr]::Zero) # right down
|
||
|
|
Start-Sleep -Milliseconds 60
|
||
|
|
[Drv]::mouse_event(0x0010, 0, 0, 0, [UIntPtr]::Zero) # right up
|
||
|
|
Start-Sleep -Milliseconds 300
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($LClick) {
|
||
|
|
$xy = $LClick.Split(',')
|
||
|
|
$r1 = New-Object Drv+RECT
|
||
|
|
[Drv]::GetWindowRect($h, [ref]$r1) | Out-Null
|
||
|
|
$tx = $r1.L + [int]$xy[0]; $ty = $r1.T + [int]$xy[1]
|
||
|
|
[Drv]::SetCursorPos($tx - 1, $ty) | Out-Null
|
||
|
|
Start-Sleep -Milliseconds 80
|
||
|
|
[Drv]::mouse_event(0x0001, 1, 0, 0, [UIntPtr]::Zero) # real WM_MOUSEMOVE for the webview
|
||
|
|
Start-Sleep -Milliseconds 120
|
||
|
|
$cp = New-Object Drv+POINT
|
||
|
|
[Drv]::GetCursorPos([ref]$cp) | Out-Null
|
||
|
|
$wfp = [Drv]::WindowFromPoint($cp)
|
||
|
|
$wpid = 0
|
||
|
|
[Drv]::GetWindowThreadProcessId($wfp, [ref]$wpid) | Out-Null
|
||
|
|
$wcls = New-Object System.Text.StringBuilder 128
|
||
|
|
[Drv]::GetClassName($wfp, $wcls, 128) | Out-Null
|
||
|
|
Write-Output "rect=($($r1.L),$($r1.T))-($($r1.R),$($r1.B)) target=($tx,$ty) cursor=($($cp.X),$($cp.Y)) under=$wfp cls=$($wcls.ToString()) pid=$wpid"
|
||
|
|
[Drv]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) # left down
|
||
|
|
Start-Sleep -Milliseconds 60
|
||
|
|
[Drv]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero) # left up
|
||
|
|
Start-Sleep -Milliseconds 300
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Wheel) {
|
||
|
|
$wp = $Wheel.Split(',')
|
||
|
|
$r2 = New-Object Drv+RECT
|
||
|
|
[Drv]::GetWindowRect($h, [ref]$r2) | Out-Null
|
||
|
|
[Drv]::SetCursorPos($r2.L + [int]$wp[0], $r2.T + [int]$wp[1]) | Out-Null
|
||
|
|
Start-Sleep -Milliseconds 120
|
||
|
|
$ticks = [int]$wp[2]
|
||
|
|
$dir = if ($ticks -gt 0) { 120 } else { -120 }
|
||
|
|
for ($i = 0; $i -lt [Math]::Abs($ticks); $i++) {
|
||
|
|
[Drv]::mouse_event(0x0800, 0, 0, [uint32]($dir -band 0xFFFFFFFF), [UIntPtr]::Zero)
|
||
|
|
Start-Sleep -Milliseconds 60
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($Out) {
|
||
|
|
Start-Sleep -Milliseconds $SettleMs
|
||
|
|
$r = New-Object Drv+RECT
|
||
|
|
[Drv]::GetWindowRect($h, [ref]$r) | Out-Null
|
||
|
|
$w = $r.R - $r.L; $ht = $r.B - $r.T
|
||
|
|
$bmp = New-Object System.Drawing.Bitmap($w, $ht)
|
||
|
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||
|
|
$dc = $g.GetHdc()
|
||
|
|
[Drv]::PrintWindow($h, $dc, 2) | Out-Null
|
||
|
|
$g.ReleaseHdc($dc); $g.Dispose()
|
||
|
|
$bmp.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||
|
|
$bmp.Dispose()
|
||
|
|
Write-Output "saved $Out ($w x $ht)"
|
||
|
|
}
|