为什么phone-harness不抢你的屏幕:iPhone Mirroring后台输入与SkyLight私有API揭秘
【免费下载链接】phone-harnesslet your agent control your phone项目地址: https://gitcode.com/gh_mirrors/ph/phone-harness
phone-harness 是一个让 AI Agent 直接操控你 iPhone 的开源工具:它把 macOS 的 iPhone Mirroring 镜像窗口当作唯一"传输层",用系统截图 + Vision OCR 做眼睛,用 SkyLight 私有 API 投递后台输入事件做双手。全程无需越狱、无需 Xcode、无需 WebDriverAgent——而本文要揭秘的重点是:Agent 干活时为什么不会把镜像窗口抢到你眼前,你的焦点和屏幕始终留在你手里。
传统自动化为何总"抢屏幕":先看懂镜像窗口的真相
iPhone Mirroring(macOS Sequoia+)把手机渲染成 Mac 上的一个窗口,并把鼠标键盘输入转发为触摸。但这个项目踩过不少坑,总结出几条决定性的事实:
- 窗口内部是一段视频流:macOS 辅助功能在里面什么都看不到,AppleScript 的
click at会被静默忽略; - 只有HID 层级的 CGEvent能驱动它,且窗口必须处于最前,否则事件直接被系统吞掉;
- 镜像只转发原始 HID 键码并丢弃修饰键标志位——所以
Cmd+V发进 iOS 文本框,收到的只是一个光秃秃的v。
于是经典后端(src/phone_harness/mirror.py)的做法是:每次动作前把镜像窗口activate()到最前,再screencapture截图、CGEventPost注入事件。副作用显而易见——整个任务期间你的屏幕被"占用"了。
不抢屏幕的秘诀:SkyLight 私有 API 事件记录
真正的答案藏在src/phone_harness/background.py。macOS 窗口服务背后有一个未公开的内部框架SkyLight,它接受一种"合成事件记录"(event record),可以直接投递给指定进程、指定窗口——这正是知名窗口管理工具 yabai 实现"聚焦但不置顶"所用的机制。
phone-harness 三步走:
| 步骤 | 做了什么 | 关键符号 |
|---|---|---|
| 1 | 由镜像应用的 PID 换出进程句柄 ProcessSerialNumber | GetProcessForPID |
| 2 | 向窗口服务声明该进程+窗口的操作来自用户 | _SLPSSetFrontProcessWithOptions |
| 3 | 把带真实坐标的鼠标事件记录直投给该进程 | SLPSPostEventRecordTo |
事件记录是一个 0xf8 字节的固定布局缓冲区,字段含义如下:
| 偏移 | 内容 |
|---|---|
| 0x04 | 记录长度(0xf8) |
| 0x08 | CGSEventType:1=按下,2=抬起,6=拖动 |
| 0x10 | 全局坐标(屏幕点) |
| 0x20 | 窗口内局部坐标 |
| 0x3c | 目标窗口 ID |
yabai 会把坐标置空来表示"仅聚焦";phone-harness 则写入真实坐标,同一条记录就变成了一次"带位置的点击"——落在 iPhone Mirroring 窗口里,而你最前面的应用全程纹丝不动。所有写入都限制在缓冲区之内,即使某个值写错也只是无效操作,不会崩掉窗口服务。
键盘输入走另一条小路:先用 yabai 风格的 "make-key" 记录(坐标置空的按下+抬起对)让窗口成为 key window,再经CGEventPostToPid把按键事件按 PID 直投进程——输入文字时焦点同样不切换。
后台"眼睛":不激活窗口也能截图
输入解决了,视觉呢?background.py用CGWindowListCreateImage按窗口 ID抓取镜像窗口:即使窗口不活动、甚至被其他窗口遮挡,也能拿到画面;取不到时退回screencapture -l兜底。
截图随后交给 Apple 的 Vision 框架做 OCR(src/phone_harness/ocr.py):每一段可见文字都带一个可直接点击的屏幕坐标,作者称之为"穷人的 DOM"。Agent 的典型闭环只有一句话:
OCR 找到目标文字坐标 → tap 点下去 → 再截图验证 → 完成。
滚动列表也有讲究:滚轮事件只路由给聚焦中的窗口(后台实测 0% 位移),慢速触摸拖动在 iOS 列表上几乎不动,于是后台后端改用快速惯性 flick模拟手指甩动,实测每次可滚动约一屏的 29%。
两条输入路径,一个环境变量开关
src/phone_harness/ios.py里有一行小开关决定走哪条路:
- 默认(后台模式):鼠标、滚动全程不抢焦点;键盘也尽量"make-key + 按 PID 投递";
PHONE_HARNESS_BACKGROUND=0(经典模式):每次动作前激活窗口,依赖更少的私有机制,兼容性更好;- 后台模式依赖 SkyLight 私有符号,跨 macOS 版本不做保证——加载失败时自动回落经典后端,保证工具始终可用。
快速上手:clone 仓库到 --doctor 体检
1️⃣ 克隆并安装(需 Python 3.12+ 与 pyobjc):
git clone https://gitcode.com/gh_mirrors/ph/phone-harness ~/.phone-harness cd ~/.phone-harness pip install pyobjc-framework-Quartz pyobjc-framework-Vision pyobjc-framework-Cocoa pip install -e . --no-deps2️⃣ 在"系统设置 > 隐私与安全性"给终端授予辅助功能(点击/按键)与屏幕录制(看屏幕)权限,细节见仓库的install.md。
3️⃣ 运行phone-harness --doctor,它会沿"权限天梯"逐项检查:pyobjc → 辅助功能 → 屏幕录制 → 镜像应用已装 → 正在运行 → 找到窗口 → 截图成功 → OCR 成功(逻辑在src/phone_harness/admin.py),第一个 FAIL 就是你要修的。
边界与常见疑问
- 手机解锁 = 会话暂停("iPhone in Use"):恢复镜像是物理动作,工具只提示,不代点 Connect;
- 无多点触控、无相机/Face ID 流程,DRM 视频渲染为黑屏,一机一会话;
- OCR 看到的是文字而非语义:无文字标签的图标需要视觉模型看截图再算坐标;
- 日常用法看
SKILL.md,Agent 可自由扩展的辅助函数在agent-workspace/agent_helpers.py。
小结
不抢屏幕的 phone-harness,本质只做了两件事:眼睛用"按窗口 ID 截图",双手用"SkyLight 事件记录直投进程"。私有 API 自带不稳定风险,但项目用一个优雅的回退策略兜住了它——默认后台、失败回落经典路径。想让 AI Agent 安静、不打扰地操控真机 iPhone,这套"眼睛 + 双手"的设计就是最值得抄的作业。
【免费下载链接】phone-harnesslet your agent control your phone项目地址: https://gitcode.com/gh_mirrors/ph/phone-harness
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考