VM 运行时:窗口、输入与控制面
VM 运行时:窗口、输入与控制面
本文面向日常使用:VM 启动后如何与其交互、如何用 HostControl socket 做脚本化控制,以及两条底层控制通道的基本概念。
启动与窗口
vphone-cli boot 启动后宿主进程完成整条链路:读取 VM manifest 组装 VZVirtualMachineConfiguration(PV=3 私有硬件模型)→ validate() → 启动虚拟机 → 连接与 guest 内 vphoned 的各条 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 照常监听,截图直接读虚拟机的显示输出,触摸注入、电池同步等服务全部保留。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。
控制面:两条通道
运行时控制经宿主侧 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;远程 VM 经节点 agent 的 TCP proxy 中继到控制端本机(见「集群」篇) |
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 |
| 推流 | screen_stream(mjpeg/h264 帧推送,唯一推模式命令) |
| 转发 | forward_add / forward_remove / forward_list |
guest 缺少某项能力时,响应会附 capability 字段提示;通常意味着需要 make vphoned 重建并推送更新后再试。
安全边界
vphone.sock权限 0600,服务端用getpeereid校验对端 UID 必须与本进程一致;并发客户端上限 8,超限返回busy。- 命令走显式 allowlist;截图等落盘操作用
O_CREAT|O_EXCL|O_NOFOLLOW防覆盖与 symlink 攻击。 - guest 侧零 TCP 监听——SSH 与端口转发全部经 vsock,guest 内 App 扫端口不可见。
挂起与恢复
echo '{"t":"vm_suspend"}' | nc -U vm/vphone.sock # 保存状态并关机
vphone-cli boot --config vm/config.plist --resume # 秒级恢复到现场
.vz-state 保存 CPU/内存/设备状态(不含磁盘),格式与宿主机绑定且不随 VM 导出离开本机;恢复失败(如宿主 macOS 升级)会自动降级为冷启动。