309 lines
11 KiB
Markdown
309 lines
11 KiB
Markdown
---
|
||
tags: [computer-use, 架构, windows, 跨平台, SendMessage]
|
||
create time: 2026-06-09 22:30
|
||
---
|
||
|
||
# Computer Use 架构修正方案 v2
|
||
|
||
## 概述
|
||
|
||
针对 Computer Use v1 架构中的三个核心问题——平台代码混放、全局输入干扰用户、截图能力归属不清——提出的修正方案。核心思路是将跨平台抽象层 `platforms/` 从 `@ant/` 包中独立出来,Windows 使用 SendMessage 无焦点输入替代全局 SendInput。
|
||
|
||
> [!info]
|
||
> 更新时间:2026-04-04
|
||
|
||
## 正文
|
||
|
||
### 1. 当前架构的问题
|
||
|
||
#### 问题 A:平台代码混在错误的包里
|
||
|
||
`@ant/computer-use-swift` 是 macOS Swift 原生模块的包装器,但我们把 Windows(`backends/win32.ts`)和 Linux(`backends/linux.ts`)的截图/应用管理代码塞进了这个包。"swift" 在名字里就意味着 macOS,后期维护者无法区分。
|
||
|
||
`@ant/computer-use-input` 同样——原本是 macOS enigo Rust 模块,我们也往里面塞了 win32/linux 后端。
|
||
|
||
#### 问题 B:输入方式不对
|
||
|
||
当前 Windows 后端使用 `SetCursorPos` + `SendInput` + `keybd_event`——这是**全局输入**:
|
||
|
||
- 鼠标真的会移动到屏幕上
|
||
- 键盘真的打到当前前台窗口
|
||
- **会影响用户当前的操作**
|
||
|
||
绑定窗口句柄后,应该用 `SendMessage`/`PostMessage` 向目标 HWND 发送消息:
|
||
|
||
- `WM_CHAR` — 发送字符,不移动光标
|
||
- `WM_KEYDOWN`/`WM_KEYUP` — 发送按键
|
||
- `WM_LBUTTONDOWN`/`WM_LBUTTONUP` — 发送鼠标点击(窗口客户区相对坐标)
|
||
- `PrintWindow` — 截取窗口内容,不需要窗口在前台
|
||
- **不抢焦点、不影响用户当前操作**
|
||
|
||
> [!tip]
|
||
> 已验证:向记事本 `SendMessage(WM_CHAR)` 成功写入文字,记事本在后台,终端保持前台。
|
||
|
||
#### 问题 C:截图是公共能力,不属于 swift
|
||
|
||
截图(screenshot)、显示器枚举(display)、应用管理(apps)是所有平台都需要的公共能力,不应该放在 `@ant/computer-use-swift`(macOS 专属包名)里。
|
||
|
||
### 2. 修正后的架构
|
||
|
||
#### 2.1 分层原则
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "packages/@ant/ macOS 原生模块包装器"
|
||
A["computer-use-input: macOS enigo 键鼠 (仅 darwin)"]
|
||
B["computer-use-swift: macOS Swift 截图/应用 (仅 darwin)"]
|
||
C["computer-use-mcp: MCP server + 工具定义 (跨平台)"]
|
||
end
|
||
subgraph "src/utils/computerUse/ 跨平台抽象层"
|
||
D["platforms/types.ts: 公共接口"]
|
||
E["platforms/index.ts: 平台分发器"]
|
||
F["platforms/darwin.ts: 委托 @ant 包"]
|
||
G["platforms/win32.ts: SendMessage + PrintWindow + EnumWindows"]
|
||
H["platforms/linux.ts: xdotool + scrot + xrandr"]
|
||
end
|
||
subgraph "src/utils/computerUse/win32/ Windows专属增强"
|
||
I["windowCapture.ts: PrintWindow 窗口绑定截图"]
|
||
J["windowEnum.ts: EnumWindows 窗口枚举"]
|
||
K["windowMessage.ts: SendMessage 无焦点输入"]
|
||
L["uiAutomation.ts: UI 元素操作"]
|
||
M["ocr.ts: Windows.Media.Ocr 文字识别"]
|
||
end
|
||
E --> F
|
||
E --> G
|
||
E --> H
|
||
G --> I
|
||
G --> J
|
||
G --> K
|
||
G --> L
|
||
G --> M
|
||
```
|
||
|
||
#### 2.2 公共接口(`platforms/types.ts`)
|
||
|
||
```typescript
|
||
/** 窗口标识 — 跨平台 */
|
||
export interface WindowHandle {
|
||
id: string // macOS: bundleId, Windows: HWND string, Linux: window ID
|
||
pid: number
|
||
title: string
|
||
exePath?: string // Windows/Linux: 进程路径
|
||
}
|
||
|
||
/** 输入平台接口 — 两种模式 */
|
||
export interface InputPlatform {
|
||
// 模式 A: 全局输入(macOS/Linux 默认,向前台窗口发送)
|
||
moveMouse(x: number, y: number): Promise<void>
|
||
click(x: number, y: number, button: 'left' | 'right' | 'middle'): Promise<void>
|
||
typeText(text: string): Promise<void>
|
||
key(name: string, action: 'press' | 'release'): Promise<void>
|
||
keys(combo: string[]): Promise<void>
|
||
scroll(amount: number, direction: 'vertical' | 'horizontal'): Promise<void>
|
||
mouseLocation(): Promise<{ x: number; y: number }>
|
||
|
||
// 模式 B: 窗口绑定输入(Windows SendMessage,不抢焦点)
|
||
sendChar?(hwnd: string, char: string): Promise<void>
|
||
sendKey?(hwnd: string, vk: number, action: 'down' | 'up'): Promise<void>
|
||
sendClick?(hwnd: string, x: number, y: number, button: 'left' | 'right'): Promise<void>
|
||
sendText?(hwnd: string, text: string): Promise<void>
|
||
}
|
||
|
||
/** 截图平台接口 */
|
||
export interface ScreenshotPlatform {
|
||
captureScreen(displayId?: number): Promise<ScreenshotResult>
|
||
captureRegion(x: number, y: number, w: number, h: number): Promise<ScreenshotResult>
|
||
captureWindow?(hwnd: string): Promise<ScreenshotResult | null>
|
||
}
|
||
|
||
/** 显示器平台接口 */
|
||
export interface DisplayPlatform {
|
||
listAll(): DisplayInfo[]
|
||
getSize(displayId?: number): DisplayInfo
|
||
}
|
||
|
||
/** 应用管理平台接口 */
|
||
export interface AppsPlatform {
|
||
listRunning(): WindowHandle[]
|
||
listInstalled(): Promise<InstalledApp[]>
|
||
open(name: string): Promise<void>
|
||
getFrontmostApp(): FrontmostAppInfo | null
|
||
findWindowByTitle(title: string): WindowHandle | null
|
||
}
|
||
```
|
||
|
||
#### 2.3 平台分发器(`platforms/index.ts`)
|
||
|
||
```typescript
|
||
import type { InputPlatform, ScreenshotPlatform, DisplayPlatform, AppsPlatform } from './types.js'
|
||
|
||
export interface Platform {
|
||
input: InputPlatform
|
||
screenshot: ScreenshotPlatform
|
||
display: DisplayPlatform
|
||
apps: AppsPlatform
|
||
}
|
||
|
||
export function loadPlatform(): Platform {
|
||
switch (process.platform) {
|
||
case 'darwin':
|
||
return require('./darwin.js').platform
|
||
case 'win32':
|
||
return require('./win32.js').platform
|
||
case 'linux':
|
||
return require('./linux.js').platform
|
||
default:
|
||
throw new Error(`Computer Use not supported on ${process.platform}`)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.4 各平台实现
|
||
|
||
**`platforms/darwin.ts`** — 委托给 @ant 包(保持兼容):
|
||
|
||
```typescript
|
||
// macOS: 通过 @ant/computer-use-input 和 @ant/computer-use-swift
|
||
import { requireComputerUseInput } from '../inputLoader.js'
|
||
import { requireComputerUseSwift } from '../swiftLoader.js'
|
||
|
||
export const platform = {
|
||
input: { /* 委托给 requireComputerUseInput() */ },
|
||
screenshot: { /* 委托给 requireComputerUseSwift().screenshot */ },
|
||
display: { /* 委托给 requireComputerUseSwift().display */ },
|
||
apps: { /* 委托给 requireComputerUseSwift().apps */ },
|
||
}
|
||
```
|
||
|
||
**`platforms/win32.ts`** — 使用 `src/utils/computerUse/win32/` 模块:
|
||
|
||
```typescript
|
||
// Windows: SendMessage 输入 + PrintWindow 截图 + EnumWindows 应用
|
||
import { sendChar, sendKey, sendClick, sendText } from '../win32/windowMessage.js'
|
||
import { captureWindow } from '../win32/windowCapture.js'
|
||
import { listWindows } from '../win32/windowEnum.js'
|
||
|
||
export const platform = {
|
||
input: {
|
||
// 全局模式: PowerShell SetCursorPos/SendInput(fallback)
|
||
// 窗口模式: SendMessage(首选)
|
||
sendChar, sendKey, sendClick, sendText,
|
||
moveMouse, click, typeText, /* ... */
|
||
},
|
||
screenshot: {
|
||
captureScreen, // CopyFromScreen
|
||
captureRegion, // CopyFromScreen(rect)
|
||
captureWindow, // PrintWindow(不抢焦点)
|
||
},
|
||
display: { /* Screen.AllScreens */ },
|
||
apps: { /* EnumWindows */ },
|
||
}
|
||
```
|
||
|
||
**`platforms/linux.ts`** — 使用 xdotool/scrot:
|
||
|
||
```typescript
|
||
// Linux: xdotool + scrot + xrandr + wmctrl
|
||
export const platform = {
|
||
input: { /* xdotool mousemove/click/key/type */ },
|
||
screenshot: { /* scrot */ },
|
||
display: { /* xrandr */ },
|
||
apps: { /* wmctrl + ps */ },
|
||
}
|
||
```
|
||
|
||
#### 2.5 executor.ts 改造
|
||
|
||
```typescript
|
||
// 之前: 直接调 requireComputerUseSwift() 和 requireComputerUseInput()
|
||
// 之后: 通过 platforms/ 统一获取
|
||
|
||
import { loadPlatform } from './platforms/index.js'
|
||
|
||
const platform = loadPlatform()
|
||
|
||
// 截图
|
||
platform.screenshot.captureScreen()
|
||
platform.screenshot.captureWindow(hwnd) // 窗口绑定
|
||
|
||
// 输入(窗口绑定模式,不抢焦点)
|
||
platform.input.sendText?.(hwnd, 'Hello')
|
||
platform.input.sendClick?.(hwnd, 100, 200, 'left')
|
||
|
||
// 输入(全局模式,fallback)
|
||
platform.input.moveMouse(500, 500)
|
||
platform.input.click(500, 500, 'left')
|
||
```
|
||
|
||
### 3. Windows 输入模式对比
|
||
|
||
| 方式 | API | 抢焦点 | 移鼠标 | 窗口可最小化 | 适用场景 |
|
||
|------|-----|--------|--------|-------------|---------|
|
||
| **全局输入** | `SetCursorPos` + `SendInput` | ✅ 抢 | ✅ 动 | ❌ 不行 | 需要坐标点击(fallback) |
|
||
| **窗口消息** | `SendMessage(WM_CHAR/WM_KEYDOWN)` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 打字、按键(首选) |
|
||
| **窗口消息** | `SendMessage(WM_LBUTTONDOWN)` | ❌ 不抢 | ❌ 不动 | ⚠️ 部分 | 窗口内点击 |
|
||
| **窗口截图** | `PrintWindow(hwnd, PW_RENDERFULLCONTENT)` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 窗口截图 |
|
||
| **UI 操作** | `UIAutomation InvokePattern` | ❌ 不抢 | ❌ 不动 | ✅ 可以 | 按钮点击、文本写入 |
|
||
|
||
> [!tip]
|
||
> **策略**:优先用窗口消息 + UIAutomation(不干扰用户),全局输入作为 fallback。
|
||
|
||
### 4. 需要新增的文件
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `src/utils/computerUse/platforms/types.ts` | 公共接口定义 |
|
||
| `src/utils/computerUse/platforms/index.ts` | 平台分发器 |
|
||
| `src/utils/computerUse/platforms/darwin.ts` | macOS: 委托给 @ant 包 |
|
||
| `src/utils/computerUse/platforms/win32.ts` | Windows: 组合 win32/ 下各模块 |
|
||
| `src/utils/computerUse/platforms/linux.ts` | Linux: xdotool/scrot |
|
||
| `src/utils/computerUse/win32/windowMessage.ts` | **新增**: SendMessage 无焦点输入 |
|
||
|
||
### 5. 需要移除/清理的文件
|
||
|
||
| 文件 | 操作 | 原因 |
|
||
|------|------|------|
|
||
| `packages/@ant/computer-use-input/src/backends/win32.ts` | 删除 | Windows 代码不应在 macOS 包里 |
|
||
| `packages/@ant/computer-use-input/src/backends/linux.ts` | 删除 | Linux 代码不应在 macOS 包里 |
|
||
| `packages/@ant/computer-use-swift/src/backends/win32.ts` | 删除 | 同上 |
|
||
| `packages/@ant/computer-use-swift/src/backends/linux.ts` | 删除 | 同上 |
|
||
| `packages/@ant/computer-use-input/src/types.ts` | 删除 | 移到 platforms/types.ts |
|
||
| `packages/@ant/computer-use-swift/src/types.ts` | 删除 | 移到 platforms/types.ts |
|
||
|
||
### 6. 需要修改的文件
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `packages/@ant/computer-use-input/src/index.ts` | 恢复为仅 darwin dispatcher(去掉 win32/linux case) |
|
||
| `packages/@ant/computer-use-swift/src/index.ts` | 恢复为仅 darwin dispatcher(去掉 win32/linux case) |
|
||
| `src/utils/computerUse/executor.ts` | 通过 `platforms/` 获取平台实现,不直接调 @ant 包 |
|
||
| `src/utils/computerUse/swiftLoader.ts` | 仅 darwin 加载 |
|
||
| `src/utils/computerUse/inputLoader.ts` | 仅 darwin 加载 |
|
||
|
||
### 7. @ant 包的定位(修正后)
|
||
|
||
| 包 | 职责 | 平台 |
|
||
|---|------|------|
|
||
| `@ant/computer-use-input` | macOS enigo 键鼠原生模块包装 | **仅 darwin** |
|
||
| `@ant/computer-use-swift` | macOS Swift 截图/应用原生模块包装 | **仅 darwin** |
|
||
| `@ant/computer-use-mcp` | MCP Server + 工具定义 + 调用路由 | **跨平台**(不含平台代码) |
|
||
|
||
Windows/Linux 的平台实现全部在 `src/utils/computerUse/platforms/` 和 `src/utils/computerUse/win32/` 中。
|
||
|
||
### 8. 执行顺序
|
||
|
||
```mermaid
|
||
graph TD
|
||
A["Phase 1: 创建 platforms/ 抽象层"] --> B["Phase 2: 创建 Windows 平台实现"]
|
||
B --> C["Phase 3: 创建 Linux 平台实现"]
|
||
C --> D["Phase 4: 改造 executor.ts"]
|
||
D --> E["Phase 5: 清理 @ant 包"]
|
||
E --> F["Phase 6: 验证 + PR"]
|
||
```
|
||
|
||
## 关联笔记
|
||
|
||
- [[computer-use]]
|
||
- [[computer-use-windows-enhancement]]
|
||
- [[computer-use-tools-reference]]
|