Input 输入模拟¶
Input 模块提供了 HID (Human Interface Device) 输入模拟功能,可以模拟触摸手势、键盘输入和物理按键。基于 IOKit 私有 API 实现,需要 TrollStore 权限。
守护进程支持: 完全支持。所有 API 均可在 Daemon 模式下使用。
权限要求:
com.apple.private.hid.client.event-dispatch权限(TrollStore 签名自带)。
目录¶
快速开始¶
// 检查是否可用
if (!input.isAvailable()) {
console.error("HID 输入不可用");
return;
}
// 获取屏幕尺寸
const screen = input.getScreenSize();
const cx = screen.width / 2;
const cy = screen.height / 2;
// 点击屏幕中心
input.tap(cx, cy);
// 从下往上滑动
input.swipe(cx, screen.height * 0.8, cx, screen.height * 0.2, 0.3);
// 输入文本
input.typeText("hello world");
API 参考¶
状态检查¶
input.isAvailable()¶
检查 HID 输入模拟是否可用。返回: boolean
if (input.isAvailable()) {
console.log("HID 输入可用");
} else {
console.log("HID 输入不可用,请检查权限");
}
首次调用时会初始化 IOHIDEventSystemClient。如果设备不支持或权限不足,返回
false。
input.getScreenSize()¶
获取当前屏幕尺寸和缩放比例。返回: object
const screen = input.getScreenSize();
console.log(screen);
// { width: 393, height: 852, scale: 3 } // iPhone 14 Pro
返回对象结构:
{
width: 393, // 屏幕宽度(UIKit points)
height: 852, // 屏幕高度(UIKit points)
scale: 3 // 缩放比例(Retina 倍数)
}
坐标系与 UIKit 一致:左上角为原点 (0, 0),X 轴向右,Y 轴向下。
触摸模拟¶
所有触摸坐标使用 UIKit points(逻辑坐标),与屏幕显示一致。
input.tap(x, y)¶
在指定坐标处模拟单击。返回: boolean
// 点击坐标 (200, 400)
input.tap(200, 400);
// 点击屏幕中心
const s = input.getScreenSize();
input.tap(s.width / 2, s.height / 2);
input.doubleTap(x, y)¶
在指定坐标处模拟双击。返回: boolean
// 双击缩放
input.doubleTap(200, 400);
input.longPress(x, y, duration)¶
在指定坐标处模拟长按。参数:
| 参数 | 类型 | 说明 |
|---|---|---|
x |
number | X 坐标 |
y |
number | Y 坐标 |
duration |
number | 持续时间(秒),范围 0.1 ~ 30.0,默认 1.0 |
返回: boolean
// 长按 1.5 秒弹出菜单
input.longPress(200, 400, 1.5);
⚠️ 此方法会阻塞脚本执行直到长按结束。
input.swipe(fromX, fromY, toX, toY, duration?)¶
从起点滑动到终点。参数:
| 参数 | 类型 | 说明 |
|---|---|---|
fromX |
number | 起点 X |
fromY |
number | 起点 Y |
toX |
number | 终点 X |
toY |
number | 终点 Y |
duration |
number? | 持续时间(秒),范围 0.05 ~ 10.0,默认 0.3 |
返回: boolean
const s = input.getScreenSize();
// 向上滑动(下拉刷新反向)
input.swipe(s.width / 2, s.height * 0.7, s.width / 2, s.height * 0.3, 0.3);
// 向左滑动
input.swipe(s.width * 0.8, 400, s.width * 0.2, 400, 0.25);
// 快速滑动
input.swipe(200, 600, 200, 200, 0.1);
内部以 60fps(16ms 间隔)生成连续触摸点,确保手势流畅。
input.drag(fromX, fromY, toX, toY, duration?)¶
从起点拖拽到终点(先按住 100ms 再移动)。参数: 同 swipe。返回: boolean
// 拖拽排序列表项
input.drag(200, 300, 200, 500, 0.5);
drag与swipe的区别:drag在起点会先按住 100ms 再开始移动,模拟真实的拖拽操作。
键盘输入¶
input.pressKey(keyName)¶
模拟按下键盘按键(key down + key up)。参数: keyName (string) 返回: boolean | object
input.pressKey("return"); // 回车
input.pressKey("space"); // 空格
input.pressKey("delete"); // 删除
input.pressKey("a"); // 字母 a
input.pressKey("up"); // 方向上
// 未知按键返回错误
const result = input.pressKey("unknown");
// { error: "Unknown key: unknown" }
input.typeText(text, delay?)¶
模拟键盘逐字输入文本。参数:
| 参数 | 类型 | 说明 |
|---|---|---|
text |
string | 要输入的文本 |
delay |
number? | 每字符间隔(秒),默认 0.05,最小 0.01 |
返回: boolean
// 正常速度输入
input.typeText("hello world");
// 快速输入
input.typeText("hello", 0.02);
// 慢速输入(模拟真人)
input.typeText("hello", 0.15);
⚠️ 仅支持 ASCII 字符(a-z, 0-9, 空格, 常见符号)。中文、Emoji 等非 ASCII 字符会被静默跳过。如需输入中文/Emoji,请使用
pasteText()。
input.pasteText(text)¶
通过剪贴板粘贴文本,支持中文、Emoji 等所有 Unicode 字符。参数:
| 参数 | 类型 | 说明 |
|---|---|---|
text |
string | 要粘贴的文本(支持任意 Unicode 字符) |
返回: boolean
// 粘贴中文
input.pasteText("你好世界");
// 粘贴 Emoji
input.pasteText("Hello 🌍🎉");
// 粘贴混合文本
input.pasteText("用户名:张三 ID:12345");
原理:将文本写入系统剪贴板 → 模拟 Cmd+V 硬件键盘粘贴快捷键。
⚠️ 此方法会覆盖当前剪贴板内容。需要目标输入框处于焦点状态。
物理按键¶
input.pressButton(buttonName)¶
模拟按下物理设备按键。参数: buttonName (string) 返回: boolean | object
input.pressButton("volumeUp"); // 音量+
input.pressButton("volumeDown"); // 音量-
input.pressButton("mute"); // 静音
input.pressButton("power"); // 电源键
input.pressButton("play"); // 播放/暂停
input.pressButtons(buttonNames)¶
同时按下多个物理按键(组合键)。参数:
| 参数 | 类型 | 说明 |
|---|---|---|
buttonNames |
string[] | 按键名称数组 |
返回: boolean | object
// 模拟截屏(电源键 + 音量上同时按下)
input.pressButtons(["power", "volumeUp"]);
// 也可以组合其他按键
input.pressButtons(["volumeUp", "volumeDown"]);
内部实现:按序发送所有 key down 事件 → 保持 150ms → 反序发送所有 key up 事件。
触摸录制¶
录制真实触摸事件并生成可回放的 JS 脚本。录制期间,所有真实的触摸事件(down/move/up/cancel)会被捕获并记录坐标、时间戳等信息。模拟事件(如 input.tap() 产生的触摸)会被自动过滤,不会被录制。
input.startRecording()¶
开始录制触摸事件。清空之前的录制数据,开始捕获新的触摸事件。
返回: boolean - 始终返回 true
input.startRecording();
console.log("开始录制,请在屏幕上操作...");
input.stopRecording()¶
停止录制并返回录制结果。返回: object
返回对象结构:
{
events: [
{ type: "down", x: 100, y: 200, pointerId: 1, timestamp: 0 },
{ type: "move", x: 105, y: 210, pointerId: 1, timestamp: 16 },
{ type: "up", x: 110, y: 220, pointerId: 1, timestamp: 150 }
],
script: "input.tap(100, 200);\n..."
}
events 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
type |
string | 事件类型: "down", "move", "up", "cancel" |
x |
number | X 坐标(UIKit points) |
y |
number | Y 坐标(UIKit points) |
pointerId |
number | 触摸点 ID(多指触控时用于区分手指) |
timestamp |
number | 相对录制开始时间的毫秒数 |
script 字段说明:
自动分析录制的事件,识别手势模式并生成对应的 JS 回放脚本:
| 手势条件 | 生成代码 |
|---|---|
| 位移 < 10pt 且时间 < 300ms | input.tap(x, y) |
| 位移 < 10pt 且时间 >= 500ms | input.longPress(x, y, duration) |
| 位移 >= 10pt | input.swipe(fromX, fromY, toX, toY, duration) |
| 手势间隔 > 50ms | 插入 util.sleep(interval) |
// 录制并回放示例
input.startRecording();
util.sleep(5000); // 录制 5 秒
const result = input.stopRecording();
console.log(`录制了 ${result.events.length} 个事件`);
console.log("生成的脚本:");
console.log(result.script);
// 回放录制的操作
eval(result.script);
input.isRecording()¶
查询当前是否正在录制。返回: boolean
if (input.isRecording()) {
console.log("正在录制中...");
} else {
console.log("未在录制");
}
按键名称速查表¶
键盘按键¶
| keyName | 说明 | HID Usage |
|---|---|---|
a - z |
字母键 | 0x04 - 0x1D |
0 - 9 |
数字键 | 0x1E - 0x27 |
return / enter |
回车键 | 0x28 |
escape / esc |
ESC 键 | 0x29 |
delete / backspace |
删除键 | 0x2A |
tab |
Tab 键 | 0x2B |
space |
空格键 | 0x2C |
minus / - |
减号 | 0x2D |
equal / = |
等号 | 0x2E |
up |
方向上 | 0x52 |
down |
方向下 | 0x51 |
left |
方向左 | 0x50 |
right |
方向右 | 0x4F |
媒体/物理按键¶
| buttonName | 说明 | HID Usage Page |
|---|---|---|
volumeUp |
音量+ | Consumer (0x0C) |
volumeDown |
音量- | Consumer (0x0C) |
mute |
静音 | Consumer (0x0C) |
play / playpause |
播放/暂停 | Consumer (0x0C) |
next |
下一曲 | Consumer (0x0C) |
previous / prev |
上一曲 | Consumer (0x0C) |
power / lock |
电源/锁屏 | Consumer (0x0C) |
完整示例¶
示例 1: 自动化 App 操作¶
// 打开 Safari → 输入网址 → 访问
app.launch("com.apple.mobilesafari");
util.sleep(2000);
// 点击地址栏(大约位置)
const s = input.getScreenSize();
input.tap(s.width / 2, 50);
util.sleep(500);
// 输入网址
input.typeText("example.com", 0.03);
util.sleep(300);
// 按回车访问
input.pressKey("return");
示例 2: 滑动浏览内容¶
const s = input.getScreenSize();
const cx = s.width / 2;
// 连续向上滑动 5 次
for (let i = 0; i < 5; i++) {
input.swipe(cx, s.height * 0.7, cx, s.height * 0.3, 0.4);
util.sleep(1000); // 等待加载
}
示例 3: 自动调节音量¶
// 音量调到最低
for (let i = 0; i < 16; i++) {
input.pressButton("volumeDown");
util.sleep(100);
}
// 再调高 5 格
for (let i = 0; i < 5; i++) {
input.pressButton("volumeUp");
util.sleep(100);
}
示例 4: 触摸录制与回放¶
if (!input.isAvailable()) {
console.error("HID 输入不可用");
return;
}
// 开始录制
input.startRecording();
notification.send("录制", "开始录制触摸操作,5秒后自动停止");
// 等待用户操作
util.sleep(5000);
// 停止录制
const result = input.stopRecording();
notification.send("录制完成", `共录制 ${result.events.length} 个事件`);
// 查看生成的脚本
console.log("=== 生成的回放脚本 ===");
console.log(result.script);
// 等待 2 秒后回放
util.sleep(2000);
notification.send("回放", "开始回放录制的操作");
eval(result.script);
notification.send("完成", "回放结束");
示例 5: 录制到文件保存¶
// 录制操作
input.startRecording();
util.sleep(10000); // 录制 10 秒
const result = input.stopRecording();
// 保存原始事件数据
file.write("recordings/events.json", JSON.stringify(result.events, null, 2));
// 保存回放脚本
file.write("recordings/replay.js", result.script);
console.log("录制数据已保存到 recordings/ 目录");
示例 6: 配合 HUD 的自动点击器¶
if (!input.isAvailable()) {
notification.send("错误", "HID 输入不可用");
return;
}
const s = input.getScreenSize();
let running = true;
let count = 0;
// 在屏幕中心自动点击
while (running && count < 100) {
input.tap(s.width / 2, s.height / 2);
count++;
util.sleep(500);
}
notification.send("完成", `共点击 ${count} 次`);
最佳实践¶
1. 始终检查可用性¶
// ✅ 正确
if (input.isAvailable()) {
input.tap(200, 400);
}
// ❌ 错误 - 直接调用可能失败
input.tap(200, 400);
2. 使用相对坐标¶
// ✅ 正确 - 适配不同屏幕尺寸
const s = input.getScreenSize();
input.tap(s.width / 2, s.height * 0.1); // 屏幕顶部 10%
// ❌ 错误 - 硬编码坐标不通用
input.tap(196, 85);
3. 操作间加入延迟¶
// ✅ 正确 - 等待 UI 响应
input.tap(200, 400);
util.sleep(300); // 等待动画完成
input.tap(200, 500);
// ❌ 错误 - 连续操作太快可能丢失
input.tap(200, 400);
input.tap(200, 500);
4. 限制循环次数¶
// ✅ 正确 - 有上限的循环
let maxTaps = 50;
for (let i = 0; i < maxTaps; i++) {
input.tap(200, 400);
util.sleep(200);
}
// ❌ 错误 - 无限循环会阻塞
while (true) {
input.tap(200, 400);
}
注意事项¶
- 权限要求: 需要
com.apple.private.hid.client.event-dispatch权限,仅 TrollStore 签名可用 - 坐标系: 使用 UIKit points(逻辑坐标),左上角为原点。通过
getScreenSize()获取屏幕尺寸 - 阻塞行为:
longPress、swipe、drag会阻塞脚本执行直到手势完成 - 字符限制:
typeText仅支持 ASCII 字符,中文/Emoji 请使用pasteText - 剪贴板:
pasteText会覆盖当前剪贴板内容 - 设备兼容:
IOHIDEventSystemClientCreate在某些设备上可能返回 NULL,请始终用isAvailable()检查 - 线程安全: HID 事件通过 IOKit 系统级管道派发,不需要主线程
- 系统限制: 模拟触摸无法绕过 Face ID / Touch ID 等生物识别
- 性能: swipe/drag 以 60fps 生成触摸点,高频操作时注意 CPU 消耗
- 录制过滤:
startRecording只录制真实触摸事件,input.tap()等模拟触摸不会被录制 - 多指手势: 录制支持多指触摸(通过 pointerId 区分),但脚本生成暂不合并多指手势