3707c3c0ba
New Windows-native capabilities: - windowCapture.ts: PrintWindow API for per-window screenshot (works on occluded/background windows) - windowEnum.ts: EnumWindows for precise window enumeration with HWND - uiAutomation.ts: IUIAutomation for UI tree reading, element clicking, text input, and coordinate-based element identification - ocr.ts: Windows.Media.Ocr for screen text recognition (en-US + zh-CN) Updated win32.ts backend to use EnumWindows for listRunning() and added captureWindowTarget() for window-specific screenshots. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
/**
|
|
* Window enumeration using Win32 EnumWindows API.
|
|
* Returns visible windows with their HWND, PID, and title.
|
|
*/
|
|
|
|
export interface WindowInfo {
|
|
hwnd: number
|
|
pid: number
|
|
title: string
|
|
}
|
|
|
|
const ENUM_WINDOWS_PS = `
|
|
Add-Type @'
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
public class WinEnum {
|
|
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
[DllImport("user32.dll")]
|
|
public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
|
|
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
|
[DllImport("user32.dll")]
|
|
public static extern int GetWindowTextLength(IntPtr hWnd);
|
|
[DllImport("user32.dll")]
|
|
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
|
|
|
public static List<string> results = new List<string>();
|
|
|
|
public static void Run() {
|
|
results.Clear();
|
|
EnumWindows(delegate(IntPtr hWnd, IntPtr lParam) {
|
|
if (!IsWindowVisible(hWnd)) return true;
|
|
int len = GetWindowTextLength(hWnd);
|
|
if (len == 0) return true;
|
|
StringBuilder sb = new StringBuilder(len + 1);
|
|
GetWindowText(hWnd, sb, sb.Capacity);
|
|
string title = sb.ToString();
|
|
if (string.IsNullOrWhiteSpace(title)) return true;
|
|
uint pid = 0;
|
|
GetWindowThreadProcessId(hWnd, out pid);
|
|
results.Add(hWnd.ToInt64() + "|" + pid + "|" + title);
|
|
return true;
|
|
}, IntPtr.Zero);
|
|
}
|
|
}
|
|
'@
|
|
[WinEnum]::Run()
|
|
[WinEnum]::results | ForEach-Object { $_ }
|
|
`
|
|
|
|
/**
|
|
* List all visible windows with non-empty titles.
|
|
* Returns HWND, PID, and window title for each.
|
|
*/
|
|
export function listWindows(): WindowInfo[] {
|
|
const result = Bun.spawnSync({
|
|
cmd: ['powershell', '-NoProfile', '-NonInteractive', '-Command', ENUM_WINDOWS_PS],
|
|
stdout: 'pipe',
|
|
stderr: 'pipe',
|
|
})
|
|
const raw = new TextDecoder().decode(result.stdout).trim()
|
|
if (!raw) return []
|
|
|
|
return raw
|
|
.split('\n')
|
|
.filter(Boolean)
|
|
.map(line => {
|
|
const trimmed = line.trim()
|
|
const firstPipe = trimmed.indexOf('|')
|
|
const secondPipe = trimmed.indexOf('|', firstPipe + 1)
|
|
if (firstPipe === -1 || secondPipe === -1) return null
|
|
|
|
const hwnd = Number(trimmed.slice(0, firstPipe))
|
|
const pid = Number(trimmed.slice(firstPipe + 1, secondPipe))
|
|
const title = trimmed.slice(secondPipe + 1)
|
|
|
|
if (isNaN(hwnd) || isNaN(pid) || !title) return null
|
|
return { hwnd, pid, title }
|
|
})
|
|
.filter((item): item is WindowInfo => item !== null)
|
|
}
|