VM 运行时:窗口、输入与控制面
VM 运行时:窗口、输入与控制面
本文面向日常使用:VM 启动后如何与其交互、如何用 HostControl socket 做脚本化控制,以及各底层控制通道的基本概念。
架构概览
启动与窗口
vphone-cli boot 启动后完成整条链路:读取 VM manifest 组装 VZVirtualMachineConfiguration(PV=3 私有硬件模型)→ validate() → 启动虚拟机 → 连接 vsock 各通道 → 建立可交互窗口。
vphone-cli boot --config vm/config.plist --variant dev
vphone-cli boot --config vm/config.plist --resume # 从 .vz-state 恢复
窗口行为要点:
- 窗口尺寸 = 屏幕像素 ÷
screenScale,纵横比锁定,不可自由拉伸。 - toolbar 仅一个 Home 按钮(走 HID 注入);右键点击窗口任意位置或按
Cmd+H也发 Home。 - 标题栏显示连接状态:
[connected]/[disconnected],副标题显示 ECID 与 guest IP。 - 关掉最后一个窗口即退出进程并关机 VM。
- 把
.ipa/.tipa文件直接拖到窗口上即可安装到 guest。
无头模式
普通 boot 加 --no-graphics 进入无头模式:不建窗口与菜单,但 HostControl socket 照常监听,截图直接读 VM 的 graphics display,触摸注入、电池同步等服务全部保留。DFU 启动(--dfu)天然无头并额外裁掉控制面。
输入路径一览
| 输入类型 | 路径 | 备注 |
|---|---|---|
| 触摸 | vphoned guest HID 注入 | iOS major < 26 的 base 默认路径 |
| 触摸(回退) | VZ 私有多点触控设备 | 26.x base 的原生路径 |
| 硬件键 | vphoned HID | Home / 电源 / 音量 / Spotlight(Cmd+Space) |
| 打字 | _VZKeyboard.sendKeyEvents |
仅 ASCII(US 布局) |
| Unicode 文本 | 自定义键盘扩展 | 所有非 less 变体默认安装(可关) |
| Touch ID | BiometricKit 手指监听 | 单击 = Home,双击 = App Switcher |
| 拖拽安装 | vsock install_ipa |
拖 IPA 到窗口 |
| 位置/电池 | CoreLocation / IOKit 转发 | 经 vphoned 注入 guest |
触摸在窗口边缘 32pt 内会转换为边缘手势(swipe aim)。无头模式下触摸注入优先走 guest HID,能力不足时自动回退 VZ multitouch。
硬件键盘三级开关
USB 键盘默认不挂载(iOS 露出软键盘),优先级从高到低:
--hardware-keyboard/--no-hardware-keyboard显式 flag- config.plist 中的
hardwareKeyboard键 - 默认关
make boot HARDWARE_KEYBOARD=1 可单次开启;--install-ipa .vphone-keyboard.ipa 时强制关闭。键盘菜单的 Home/Power/Spotlight 走 guest HID,type 命令走 guest keyboard 扩展,均不依赖 _VZKeyboard。
控制面:两条通道
运行时控制经宿主侧 VPhoneGuestControl / VPhoneHostControl 两个组件暴露,外部用户只需接触后者。
| 通道 | 端点 | 用途 |
|---|---|---|
| HostControl socket | <bundle>/vphone.sock(AF_UNIX,0600) |
自动化/脚本驱动 VM 的唯一入口 |
| vphoned 控制通道 | host → guest vsock 1337 |
对 guest 的全部运行时控制(内部实现) |
| SSH 桥 | 127.0.0.1:<sshPort> ↔ vsock 2222 |
ssh -p <sshPort> root@localhost 进 guest |
| 端口转发 | host listener ↔ vsock 1339 |
把 guest 内 TCP/unix 服务映射到 host |
socket 路径解析顺序:--socket 参数 → VPHONE_HOST_CONTROL_SOCKET 环境变量 → vm/vphone.sock。
HostControl socket 用法
请求为"一行 JSON,换行结尾",响应也是一行 JSON(必含 ok 字段)。默认每个命令执行后附带一张压缩截图(base64 JPEG,可设 "screen":false 关闭;"delay" 控制截图前等待毫秒数)。
# 推送 vphoned 更新(详见「vphoned」篇)
echo '{"t":"vphoned_update"}' | nc -U "$(VM_DIR)/vphone.sock"
# 触摸注入
echo '{"t":"tap","x":645,"y":400}' | nc -U "$(VM_DIR)/vphone.sock"
echo '{"t":"swipe","x1":645,"y1":2000,"x2":645,"y2":800,"ms":300}' | nc -U "$(VM_DIR)/vphone.sock"
echo '{"t":"long_press","x":645,"y":400,"ms":600}' | nc -U "$(VM_DIR)/vphone.sock"
# 硬件键与文本
echo '{"t":"key","name":"home"}' | nc -U "$(VM_DIR)/vphone.sock"
echo '{"t":"type","text":"hello"}' | nc -U "$(VM_DIR)/vphone.sock"
# 截图与应用管理
echo '{"t":"screenshot"}' | nc -U "$(VM_DIR)/vphone.sock"
echo '{"t":"app_launch","bundle_id":"com.apple.Preferences"}' | nc -U "$(VM_DIR)/vphone.sock"
echo '{"t":"open_url","url":"https://example.com"}' | nc -U "$(VM_DIR)/vphone.sock"
常用命令族速查(坐标为 1290×2796 像素空间,左上角原点):
| 族 | 命令示例 |
|---|---|
| 输入 | tap / swipe / long_press / key / type / keyboard_delete / keyboard_clear |
| 应用 | app_list / app_launch / app_terminate / app_foreground / open_url / install_ipa |
| 剪贴板/设置 | clipboard_get / clipboard_set / settings_get / settings_set |
| 文件 | file_list / file_download / file_upload / file_delete / file_rename |
| 位置/电池/相机 | location_set / battery_set / camera_source / touchid |
| 网络观测 | network_status / network_flows / network_set_direct_rules |
| 录制 | record_start/stop(输入事件 JSONL)、record_screen_start/stop(.mov 录屏) |
| 挂起/恢复 | vm_save_state / vm_resume / vm_suspend(配合 boot --resume) |
| vphoned | ping / version / vphoned_update / guest_health |
| 推流 | screen_stream(mjpeg/h264 帧推送) |
| 转发 | forward_add / forward_remove / forward_list |
guest 缺少某项能力时,响应会附 capability 字段提示;通常意味着需要 make vphoned 重建并推送更新后再试。
Guest 无响应看门狗
GuestHealthTracker 跨断连周期监测 guest 健康状态,仅在检测到异常时记录日志,不会自动 kill/force-stop VM。
- 判定逻辑:首次 hello 握手成功后开始监测。连续出现 ≥ 3 个失败周期(connect 失败 / 断连 / 握手超时)且静默 ≥ 90 秒 → 标记为
unresponsive;任何成功响应(心跳 ping 成功 / 重连握手成功)立即恢复并清零。 - 防误报:boot 期间未 arm 不计数;关机路径(SIGINT/SIGTERM、vm_suspend 等)统一暂停看门狗;guest 正常内核重启也会判无响应,但重新握手成功后自动恢复。
- 状态查询走 HostControl
guest_health命令。
机器状态保存与恢复
.vz-state 保存 CPU/内存/设备状态(不含磁盘),格式与宿主机绑定且不随 VM 导出离开本机。恢复失败(如宿主 macOS 升级)会自动降级为冷启动。
echo '{"t":"vm_suspend"}' | nc -U vm/vphone.sock # 保存状态并关机
vphone-cli boot --config vm/config.plist --resume # 秒级恢复到现场
HostControl 三命令:
| 命令 | 语义 | 失败行为 |
|---|---|---|
vm_save_state |
pause + save,VM 保持 paused | best-effort resume,不留冻结 guest |
vm_resume |
resume 并删除失效 state 文件 | 直接报错 |
vm_suspend |
pause + save + 优雅关机 | 响应 flush 后约 0.5s 退出进程 |
live 快照:vm_save_state(pause + save)→ clonefile 整包复制 bundle → vm_resume,内存状态与磁盘落在同一暂停瞬间。主入口在 vphone-manager 的快照 tab。
磁盘预检:vm_save_state / vm_suspend 在 pause 前预检 .vz-state 所在卷可用空间(需求 ≈ VM 内存大小),不足直接拒绝,不把 guest 暂停了才在 save 中途失败。
输入录制与自动化 overlay
- 输入录制(
VPhoneInputRecorder):白名单命令(tap/swipe/key/type/keyboard_delete/keyboard_clear)写入 JSONL 文件,best-effort 不打断输入分发。经 HostControlrecord_start/stop控制。 - 自动化 overlay(
VPhoneAutomationOverlayView):hit-test 透传,按来源着色绘制元素框(template 绿 / akaze 蓝 / ocr 橙 / 稳定哈希色)与 AUTO 徽标,适合脚本调试时可视化识别结果。
安全边界
vphone.sock权限 0600,服务端用getpeereid校验对端 UID 必须与本进程一致;并发客户端上限 8,超限返回busy。- 命令走显式 allowlist;截图等落盘操作用
O_CREAT|O_EXCL|O_NOFOLLOW防覆盖与 symlink 攻击。 - guest 侧零 TCP 监听——SSH 与端口转发全部经 vsock,guest 内 App 扫端口不可见。