VM 运行时:窗口、输入与控制面

虚拟 iPhone 启动后的日常操作指南——窗口交互、触摸/键盘/硬件键注入,以及经 vphone.sock 的一行 JSON 控制面用法。

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 露出软键盘),优先级从高到低:

  1. --hardware-keyboard / --no-hardware-keyboard 显式 flag
  2. config.plist 中的 hardwareKeyboard 键
  3. 默认关

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 不打断输入分发。经 HostControl record_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 扫端口不可见。