我转过头去, 屏幕便起了一层雾
耳机戴在耳朵上, 通常是为了把声音送进来.
有一天, 我却想借它告诉电脑一件事: 我已经转过头去了.
这个念头来自别人分享的一个创意. 看过以后, 我很想在自己的 Mac 上也试一试: 正对屏幕时, 眼前的内容保持清晰; 头慢慢偏开, 离开朝向范围的部分便模糊起来; 再转回来, 画面重新清楚.
不需要另找一个开关. 一个原本就会发生的小动作, 成了电脑能够接住的信号.
后来, 我把自己的实现叫作 Glance Guard, 也把源码放到了 GitHub. 灵感来自 @bryllim_ 的分享, 项目由我独立实现, 仓库里保留了致谢.
先把一个小念头做出来
我喜欢这种尺度的项目.
它不必先回答能服务多少人, 也不必给自己安排一个庞大的未来. 只要那个画面确实发生, 我就已经很高兴: 戴着耳机, 转一下头, 屏幕跟着有了变化.
当然, 想象里只是一瞬间的事, 真做起来, 中间还有好几层.
Glance Guard 从兼容 AirPods 的运动数据里取得头部姿态, 再把相对校准方向的偏转, 映射到屏幕上的清晰区域. 它知道的是头朝哪里偏, 并不知道眼睛正盯着哪一个字. 只动眼球, 头没有转, 就不能指望它像眼动追踪那样理解视线.
我没有给它接摄像头. 第一版就围绕头部姿态做, 左右转, 抬头, 低头, 再回到正前方. 先让这几件事有一个清楚的对应关系.
为了不用每改一行代码就戴上耳机重试, 控制面板里还做了模拟模式. 拖动角度, 看屏幕范围怎样移动. 等规则大致走通, 再把它交回真实的动作.
但也正是在这里, 我逐渐发现, 模拟图里看着合理, 和坐在电脑前觉得自然, 还有一段距离.
我都转开了, 怎么还有一半是清楚的
最有记忆的一次反馈, 就这么直接.
头已经转开, 人都不再看着那块屏幕了, 它却还留着一片清晰区域. 最初在多屏环境里发现这个问题, 容易以为是屏幕排列出了差错. 后来只用笔记本内屏再试, 同样会发生.
问题便不能再交给副屏解释了.
早先的做法, 是假定一个视距与视野角度, 把头部朝向投影到连续的桌面上. 数学上可以画出一个范围, 可那并不意味着它知道我坐在哪里, 离屏幕多远, 又在什么时候已经完全转开.
系统能告诉程序屏幕的逻辑尺寸和排列. 桌上两块显示器之间真实的夹角, 我的坐姿, 耳机的佩戴偏差, 却不在那张排列图里.
后来, 我们把固定视距的估算拿掉, 换成了更贴近使用感受的校准: 正视参考屏幕, 记住正前方; 转到希望整屏模糊的位置, 再记下这个角度. 达到设定阈值时, 参考屏幕就完整模糊.
左右与上下分别有可调的阈值, 也给自然的小幅晃动留了一点余地. 换了坐姿, 移动了电脑, 就重新校准.
这次修改让我很喜欢. 程序终于不必假装知道一个标准的人应该怎样坐着. 它可以先问问眼前这个人, 再按他的动作来.
多屏仍然更复杂. 同一个清晰区域沿系统排列移动, 并不等于精确识别我正在看哪块副屏. 我把这部分保留为需要继续体验和验证的地方, 没有因为两块屏幕能显示出来, 就把所有摆放方式都算作解决了.
那层雾, 也不能随便起
最初的遮罩效果, 我并不满意.
屏幕像被盖了一层灰白的纸, 底下的内容固然不那么清楚了, 颜色和光也一并闷住. 我想要的高斯模糊, 应当还能看出原来画面的色调, 只是细节柔和地散开.
于是又回去改显示方式. 用 ScreenCaptureKit 取得屏幕画面, 交给 Core Image 做高斯模糊, 再让模糊图像与真实桌面沿边缘渐变衔接. 控制面板上的 Liquid Glass 是另一回事, 我没有拿它来代替桌面本身的模糊.
这里还有一个很容易忽略的小圈套: 如果把 App 自己的遮罩也截进去, 它就会不断处理自己的上一层结果. 所以捕获时要把自己的窗口排除, 让屏幕画面与覆盖在上面的那层雾各归各位.
为了这个效果, App 需要屏幕录制权限. 图像在本机内存里处理, 不保存, 不上传, 也不采集音频. 暂停以后, 停止捕获并清除图像.
我想做的只是桌面上的一个小变化. 它没有理由顺便替我攒下一份屏幕录像.
有时, 卡住的是系统对你的认识
做到这一步, 又碰到一个让人困惑的状况: 设置里的录屏权限明明开着, 程序却仍然被拒绝.
重启过, 开关也切过, 问题没有跟着消失. 后来对照日志与签名, 才发现旧的 ad-hoc 签名会随重新构建的可执行文件变化. 系统保存的授权, 与眼前这一版 App 的身份没有对上.
最后固定安装位置, 使用稳定的 Apple Development 签名, 再单独迁移这个 App 的录屏授权, 捕获才重新正常工作.
写功能的时候, 很容易把注意力都放在屏幕上发生了什么. 真正把一个 App 留在电脑里使用, 才会遇到这些不显眼的事: 它从哪里启动, 更新以后还是不是系统认识的那个程序, 授权还认不认它.
它们不在效果图里, 却决定了第二天还能不能照常打开.
后来那次安装和授权调整完成, 我终于说, 可以了. 那句话很短, 前面却跟着不少次构建, 检查与重试.
好玩之外, 还得留一条退出的路
我不希望耳机断连以后, 自己反而对着一块无法恢复的模糊屏幕.
所以数据中断, 手动暂停, 或显示器配置变化时, 都要恢复清晰. 运动数据超过一秒没有更新, 也不继续拿旧姿态维持遮罩. 重新开始, 则需要有效数据与校准.
控制面板和菜单栏里留着暂停入口. 不管正在试什么效果, 总要能把电脑交还给原来的使用方式.
这也决定了它的边界. Glance Guard 是一个跟随头部姿态变化的视觉工具, 模糊不能代替锁屏. 大字和图像仍可能被辨认, 它也不阻止其他程序截屏. 真要离开座位, 我还是会锁上电脑.
后来, 我们还试着减少捕获画面的尺寸和刷新频率, 降低图像处理的负担. 那部分先留在本地迭代里, 没有拿理论上的像素减少比例当作 CPU 实测成绩, 也没有把它写成公开版本已经兑现的性能承诺.
一个效果能出现, 是第一步. 愿不愿意让它一直开着, 还要留给更长一点的日常来回答.
把它放出来, 让别人也能试试
源码已经公开, 使用 MIT License:
它目前仍是一个原型, 适合愿意自己构建和调试的人. 构建条件与使用步骤以仓库 README 为准, 目前也不是已经公证、下载后即可面向所有人分发的成品安装包.
我尤其想知道, 换一副兼容耳机, 换一种坐姿, 换一套显示器摆法, 它会怎样. 这些真实的差异, 比我继续在自己的桌前猜测更有用. 若愿意尝试, 可以在仓库留下反馈, 分享时记得避开屏幕里的私人内容.
把一个小项目公开, 于我也有一份很朴素的快乐. 一个原本只在自己电脑上发生的动作, 从此有机会出现在别人的桌面上.
耳机仍然用来听歌. 只是偶尔, 当我转过头去, 它还会替我捎一句话给屏幕.
而屏幕轻轻起雾, 等我回来.