使用中发现一个逻辑上的死循环:Dock 占满整条 Touch Bar,系统的亮度条被盖住了。如果屏幕亮度被调到最低,画面全黑,用户想把它调回来,却找不到亮度条;去菜单栏关掉插件,又要在一片漆黑里操作。
这一篇记录怎么给它做兜底,以及一个我认为更好、最后却没有做的方案,为什么被否掉。
问题:占满整条的代价
占满整条是一开始就做出的选择:系统设置成“展开的控制条”时,只占一小块的方式在 macOS 27 上根本不显示,所以固定用占满整条。代价一直写在 README 的局限里:开着的时候亮度和音量看不到,要用时到菜单里关掉 Dock。
平时这只是不方便,但亮度调到 0 时,它变成了一个死循环:要恢复亮度就得先关掉插件,而屏幕是黑的,关插件的入口看不见。这种“把用户锁在外面”的问题,比任何功能缺陷都严重,必须有一个不依赖屏幕的兜底。
方案:最右端固定一个齿轮
方案是用户提出来的,也是最简单的:在 Touch Bar 最右端固定一个齿轮按钮,点一下,插件暂时让位,系统原生的控制条(亮度、音量)就回来了;用户调好之后,插件自动恢复。相当于菜单里那个总开关的“临时版”。
按钮固定在最右端,不随图标滚动;Dock 图标区让出这 44pt,长按退出时的提示条也挪到它的左边。图标用系统自带的齿轮符号,不自己画。点击后调用的就是关闭插件时同一个接口,把我们的 Touch Bar 撤下来,系统的控制条自然显示。
怎么自动恢复:不能让用户又被锁住
“自动恢复”看起来简单,其实要考虑几种情况,否则很容易制造新的死循环。点的时候屏幕已经是黑的:一直等到亮度回到一个可见的值再恢复。点的时候屏幕是亮的(只是想调音量):过 20 秒恢复。最关键的一种:暂停期间用户又把亮度调到了最低:这时即使 20 秒到了也不能恢复,否则又盖住了亮度条,要一直等到亮度回来。
亮度是用系统私有的 DisplayServices 接口读的,和其他私有接口一样,读不到就退化:按 20 秒处理,功能不会失效。另外要保证暂停期间睡眠唤醒、解锁这些“自动把 Dock 挂回去”的流程不会把它顶回来,所以挂回去之前统一检查是否处于暂停状态。想提前恢复,也可以点系统控制条里我们留的那个入口图标。
被否掉的方案:调到 0 时自动让位
用户随后提出一个体验更好的想法:亮度一调到 0,插件自己关掉,不用点任何按钮。这个方案我认为确实更好,所以先实现了一版。但它要求程序每隔几秒读一次屏幕亮度。系统没有“亮度变化”的通知,只能轮询。
轮询意味着空闲时也要周期性醒来。这个项目对外写的一个数字是“空闲唤醒 0 次、CPU 0.0%”,这条会因此失效。哪怕把间隔放宽到 2 秒并允许系统合并唤醒,也不再是 0。用户的取舍很干脆:如果有开销就算了,用齿轮的被动方式,不要把开发复杂化。我把这一版完全移除,没有留在代码里,也没有给它加开关。
我觉得这个判断是对的:兜底功能的目标是“不会把用户锁死”,被动的齿轮已经做到了;主动检测只是把“点一下”省掉,却要用一条常驻的定时器去换。少一个机制,就少一个出问题的地方。
后来:齿轮改成小眼睛,菜单也跟着整理
用了一段时间之后,用户觉得齿轮太像“设置”,而点下去的实际效果是“暂时隐藏 Dock”,所以在 1.7 里改成了小眼睛,含义直接。同时把“暂时隐藏多久”做成了菜单里的选项(10、20、30、60 秒,默认 20 秒);屏幕已经被调黑时仍然不看时间,只等亮度回来。
这次还顺手整理了菜单:“显示 Dock 里固定的 App”改成了意思更直白的“只显示正在运行的 App”,默认不勾选;界面上的勾选状态和原来相反,但存储的设置没有变,所以已有用户不会被改掉设置。功能相近的选项放在一起,分成显示、切换与手势、通用、关于和退出四组。功能没有变,只是说法和位置变了,目的是减少误解。
验证:这次我能验证什么,不能验证什么
能验证的:按钮出现在最右端,Dock 布局没有变化(Touch Bar 截图确认);亮度读取接口在这台机器上能读到内置屏的亮度。不能验证的:点击暂停和自动恢复的整个流程。Touch Bar 上的触摸没法用脚本模拟,这部分逻辑只能靠人在真机上点。所以这一篇里,我不会说“测试通过”,只说逻辑按上面几种情况写了,并交给用户手动确认。
我原本还想用脚本把亮度真的调到 0 再恢复,来测试自动让位的那一版,这个测试在执行前被用户叫停了,因为整个方案随后被取消。这也算一个提醒:会改动用户屏幕亮度的测试,就算写了恢复逻辑,也应该先问一句。