openpencil/tests/helpers/tauri/windows-input.ts

104 lines
3.8 KiB
TypeScript

import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
const execFileAsync = promisify(execFile)
interface ClientPoint {
x: number
y: number
}
type ElementClientGeometry = Pick<DOMRect, 'height' | 'width' | 'x' | 'y'>
const USER32_DECLARATION = String.raw`
using System;
using System.Runtime.InteropServices;
public static class NativeInput {
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int X;
public int Y;
}
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr window, ref Point point);
[DllImport("user32.dll")]
public static extern uint GetDpiForWindow(IntPtr window);
[DllImport("user32.dll")]
public static extern bool SetProcessDpiAwarenessContext(IntPtr value);
[DllImport("user32.dll")]
public static extern bool SetCursorPos(int x, int y);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr window);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr window, int command);
[DllImport("user32.dll")]
public static extern void mouse_event(uint flags, uint dx, uint dy, uint data, UIntPtr extraInfo);
}`
const FOREGROUND_SCRIPT = String.raw`
$process = Get-Process OpenPencil -ErrorAction Stop |
Where-Object { $_.MainWindowTitle -eq 'OpenPencil Native Test' } |
Select-Object -First 1
if (-not $process) { throw 'OpenPencil Native Test window not found' }
[NativeInput]::SetProcessDpiAwarenessContext([IntPtr](-4)) | Out-Null
[NativeInput]::ShowWindow($process.MainWindowHandle, 9) | Out-Null
[NativeInput]::SetForegroundWindow($process.MainWindowHandle) | Out-Null
Start-Sleep -Milliseconds 150
$clientOrigin = New-Object NativeInput+Point
if (-not [NativeInput]::ClientToScreen($process.MainWindowHandle, [ref]$clientOrigin)) {
throw 'ClientToScreen failed'
}
$dpi = [NativeInput]::GetDpiForWindow($process.MainWindowHandle)
if ($dpi -eq 0) { throw 'GetDpiForWindow failed' }
$scale = $dpi / 96.0
`
async function runPowerShell(script: string): Promise<void> {
if (process.platform !== 'win32') throw new Error('Win32 input is only available on Windows')
const encoded = Buffer.from(script, 'utf16le').toString('base64')
await execFileAsync('powershell.exe', ['-NoProfile', '-EncodedCommand', encoded])
}
export async function readElementClientGeometry(selector: string): Promise<ElementClientGeometry> {
return browser.execute((targetSelector) => {
const element = document.querySelector(targetSelector)
if (!(element instanceof HTMLElement)) throw new Error(`Element not found: ${targetSelector}`)
const rect = element.getBoundingClientRect()
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
}
}, selector)
}
export async function nativeDrag(from: ClientPoint, to: ClientPoint): Promise<void> {
const steps = Array.from({ length: 20 }, (_, index) => {
const progress = (index + 1) / 20
return {
x: Math.round(from.x + (to.x - from.x) * progress),
y: Math.round(from.y + (to.y - from.y) * progress)
}
})
const moves = steps
.map(
(point) =>
`$x = [int][Math]::Round($clientOrigin.X + (${point.x} * $scale))\n$y = [int][Math]::Round($clientOrigin.Y + (${point.y} * $scale))\n[NativeInput]::SetCursorPos($x, $y) | Out-Null\nStart-Sleep -Milliseconds 35`
)
.join('\n')
await runPowerShell(`
Add-Type -TypeDefinition '${USER32_DECLARATION}'
${FOREGROUND_SCRIPT}
$x = [int][Math]::Round($clientOrigin.X + (${from.x} * $scale))
$y = [int][Math]::Round($clientOrigin.Y + (${from.y} * $scale))
[NativeInput]::SetCursorPos($x, $y) | Out-Null
Start-Sleep -Milliseconds 100
[NativeInput]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero)
Start-Sleep -Milliseconds 250
${moves}
Start-Sleep -Milliseconds 250
[NativeInput]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero)
`)
}