touchDriver 触摸驱动
touchDriver 触摸驱动
touchDriver 是 ScriptX 里更底层的触摸驱动模块。和普通的 click() / swipe() 相比,它更像一套“你自己拿到一个触摸会话,然后手动控制 synthetic pointer”的能力。
它有两个主要用途:
- 主屏 Root 原生触摸驱动
- shell 型虚拟屏上的原始触摸注入
如果你只是要做普通自动化点击,优先看 coordinates 自动化操作-坐标。
如果你需要:
- 自己控制
down -> move -> up - 多个 synthetic pointer id
- 把原始触摸明确打到某块虚拟屏
那才更适合用 touchDriver。
先记住这 12 条
- 全局对象名是
touchDriver,兼容别名是$touchDriver。 touchDriver.open(...)返回的是一个“触摸会话对象”,不是立即执行一次点击。- 主屏模式底层后端叫
merged_uinput,依赖 Root。 - 虚拟屏模式底层后端叫
display_motion_event,不要求 Root,但目标虚拟屏必须是 shell 后端。 - 主屏同一时刻只能有一个活跃的主屏驱动会话。
- pointer id 只能是
0..9,最多 10 个 synthetic pointer。 move(id, ...)之前必须先down(id, ...)。up(id)如果这个 id 当前根本没按下,不会抛错,而是直接返回false。tap()默认时长60ms,press()默认350ms,swipe()默认260ms。- 所有坐标最终都会被裁进当前目标视口,不会因为轻微越界直接报错。
cancel()会清空当前会话里所有 synthetic pointer。closeAll()只会关掉当前脚本里开出来的会话,不会去动别的脚本的会话。
touchDriver.open(optionsOrTarget?)
打开一个触摸驱动会话。
const session = touchDriver.open();
const mainSession = touchDriver.open("main");
const vSession = touchDriver.open(screen);
支持的几种写法
1. 主屏默认写法
const session = touchDriver.open();
2. 显式主屏写法
const session = touchDriver.open("main");
const session2 = touchDriver.open("default");
const session3 = touchDriver.open(0);
3. 传配置对象
const session = touchDriver.open({
target: "main"
});
配置对象里当前会读取这几个字段来找目标:
targetscreendisplay
4. 传虚拟屏目标
const screen = vdisplay.create({ width: 720, height: 1280 });
const session = touchDriver.open(screen);
返回值
TouchDriverSession
主屏模式的要求
主屏模式会走 merged_uinput 后端,要求:
- Root 可用
- ScriptX APK 路径可取到
libscriptx_input.so存在- 当前没有别的活跃主屏
touchDriver会话
虚拟屏模式的要求
虚拟屏模式会走 display_motion_event 后端,要求:
- 目标必须能解析成一个现有虚拟屏 session
- 这个 session 的 backend 必须是
shell
如果你把一个本地虚拟屏或已销毁虚拟屏传进来,会直接报错。
什么时候会报错
主屏但没有 Root
会报:
touchDriver coexist mode requires Root
虚拟屏不是 shell 后端
会报:
touchDriver raw virtual-display input requires a shell-backed vdisplay session
目标值不合法
会报:
touchDriver target must be main or a vdisplay session
示例:主屏会话
const session = touchDriver.open("main");
log(session.backend);
示例:虚拟屏会话
const screen = vdisplay.create({ width: 720, height: 1280 });
const session = touchDriver.open(screen);
log(session.target);
touchDriver.isSupported(target?)
判断给定目标是否支持 touchDriver。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
target | 任意 | 主屏目标或虚拟屏目标 | 不传时默认检查主屏 |
返回值
boolean
主屏判断逻辑
主屏会检查:
- 当前是否能拿到 Root
libscriptx_input.so是否存在
虚拟屏判断逻辑
虚拟屏只要当前目标能解析成一个仍然存在的 shell 型虚拟屏,就会返回 true。
示例
log(`main supported=${touchDriver.isSupported()}`);
log(`screen supported=${touchDriver.isSupported(screen)}`);
touchDriver.backends()
列出当前模块公开的后端描述信息。
返回值
Array<object>
当前源码会固定返回两项:
主屏后端
{
name: "merged_uinput",
target: "main",
coexist: true,
requiresRoot: true,
maxSyntheticPointers: 10
}
虚拟屏后端
{
name: "display_motion_event",
target: "vdisplay",
coexist: true,
requiresRoot: false,
maxSyntheticPointers: 10
}
示例
log(JSON.stringify(touchDriver.backends(), null, 2));
touchDriver.closeAll()
关闭当前脚本里所有由 touchDriver.open(...) 打开的会话。
返回值
number
表示这次实际关闭了多少个会话。
示例
const count = touchDriver.closeAll();
log(`closed=${count}`);
touchDriver.api
touchDriver 自己的别名引用。
类型
object
示例
log(touchDriver.api === touchDriver);
session.id
当前触摸会话 id。
类型
string
典型值
touch-driver-1touch-driver-2
示例
const session = touchDriver.open();
log(session.id);
session.close();
session.backend
当前会话使用的底层后端名。
类型
string
可能值
| 值 | 含义 |
|---|---|
merged_uinput | 主屏 Root 原始触摸 |
display_motion_event | shell 型虚拟屏原始触摸 |
示例
log(session.backend);
session.target
当前会话目标名。
类型
string
典型值
| 场景 | 典型值 |
|---|---|
| 主屏会话 | main |
| 虚拟屏会话 | vdisplay-1 这类 session id |
session.width
当前目标视口宽度。
类型
number
session.height
当前目标视口高度。
类型
number
session.closed
当前会话是否已经关闭。
类型
boolean
session.down(id, x, y) / session.touchDown(id, x, y)
按下一个 synthetic pointer。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
id | number | 0..9 | synthetic pointer id |
x | number | 有限数字 | 目标坐标 X |
y | number | 有限数字 | 目标坐标 Y |
返回值
boolean
真实行为
- 坐标会先裁进当前视口
- 成功后这个 id 会进入活跃指针集合
- 如果会话已关闭,会直接报错
示例
session.down(0, 200, 600);
session.move(id, x, y) / session.touchMove(id, x, y)
移动一个已经按下的 synthetic pointer。
参数
和 down(...) 一样。
返回值
boolean
一个非常关键的限制
这个 id 必须已经先 down(...) 过,否则会直接报:
touchDriver pointer <id> is not down
示例
session.down(0, 200, 600);
session.move(0, 260, 600);
session.move(0, 320, 600);
session.up(0);
session.up(id) / session.touchUp(id)
释放一个 synthetic pointer。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
id | number | 0..9 | synthetic pointer id |
返回值
boolean
返回值细节
| 返回值 | 含义 |
|---|---|
true | 当前这个 id 原本处于按下状态,并且释放成功 |
false | 当前这个 id 根本没有按下 |
示例
session.down(0, 200, 600);
sleep(100);
session.up(0);
session.tap(x, y, duration?) / session.click(x, y, duration?)
用一个空闲 synthetic pointer 做一次点击。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x | number | 有限数字 | 必填 | 目标坐标 X |
y | number | 有限数字 | 必填 | 目标坐标 Y |
duration | number | 任意有限数字 | 60 | 点击持续时间,实际至少 1ms |
返回值
boolean
真实行为
- 会自动找一个当前空闲的 pointer id
- 如果后端原生支持 tap,会直接走后端
- 否则会模拟成
down -> sleep -> up
什么时候会报错
当 10 个 synthetic pointer 都还占着时,会直接报:
touchDriver has no free synthetic pointer slots
示例
session.tap(360, 640);
session.click(360, 700, 100);
session.press(x, y, duration?)
在一个点上长按。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x | number | 有限数字 | 必填 | 目标坐标 X |
y | number | 有限数字 | 必填 | 目标坐标 Y |
duration | number | 任意有限数字 | 350 | 持续时间 |
返回值
boolean
示例
session.press(360, 640, 800);
session.swipe(x1, y1, x2, y2, duration?)
做一次滑动。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x1 | number | 有限数字 | 必填 | 起点 X |
y1 | number | 有限数字 | 必填 | 起点 Y |
x2 | number | 有限数字 | 必填 | 终点 X |
y2 | number | 有限数字 | 必填 | 终点 Y |
duration | number | 任意有限数字 | 260 | 滑动时长 |
返回值
boolean
真实行为
- 会自动占用一个空闲 pointer id
- 如果后端支持原生 swipe,就直接走后端
- 否则会退化成若干步
move(...) - 总步数大致按
duration / 16ms估算,最多 120 步
示例
session.swipe(360, 1100, 360, 280, 320);
session.cancel() / session.cancelSynthetic()
取消当前会话里的所有 synthetic pointer。
返回值
boolean
它适合什么场景
- 脚本中途异常,怕有 pointer 没抬起
- 手势流程想整体中止
- 关闭前主动清场
示例
session.cancel();
session.status() / session.getState()
读取当前会话状态。
log(JSON.stringify(session.status(), null, 2));
返回值
object
通用字段
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 会话 id |
backend | string | 后端名 |
target | string | 目标名 |
width | number | 目标宽度 |
height | number | 目标高度 |
rotation | number | 目标旋转 |
closed | boolean | 会话是否已关闭 |
activeSyntheticPointers | number[] | 当前仍然按下的 synthetic pointer id 列表 |
主屏会额外带出的字段
| 字段 | 类型 | 说明 |
|---|---|---|
connected | boolean | Root helper 是否仍然连着 |
device | string | 底层设备名 |
physicalPointers | number | null | 当前真实物理指针数 |
syntheticPointers | number | null | 当前 synthetic 指针数 |
physicalSlots | number | null | 物理 slot 数 |
maxSyntheticPointers | number | null | synthetic slot 上限 |
虚拟屏会额外带出的字段
| 字段 | 类型 | 说明 |
|---|---|---|
connected | boolean | 目标虚拟屏是否仍存在 |
displayId | number | null | 虚拟屏 displayId |
maxSyntheticPointers | number | 当前固定 10 |
示例
const state = session.status();
log(JSON.stringify(state, null, 2));
session.close() / session.destroy()
关闭当前会话。
返回值
boolean
当前脚本侧会统一返回 true。
真实行为
- 会先尝试
cancelSynthetic() - 然后关闭底层后端
- 关闭后
session.closed会变成true
示例
session.close();
一段完整的新手示例
下面这段例子演示的是:在主屏打开一条原始触摸会话,先点击,再手动拖拽一次,最后关会话。
if (!touchDriver.isSupported()) {
throw new Error("当前设备不支持主屏 touchDriver,通常是 Root 或原生库不可用");
}
const session = touchDriver.open("main");
try {
session.tap(360, 640);
sleep(300);
session.down(0, 360, 1000);
sleep(60);
session.move(0, 360, 800);
sleep(60);
session.move(0, 360, 600);
sleep(60);
session.up(0);
log(JSON.stringify(session.status(), null, 2));
} finally {
session.close();
}
