PushSense 前九个版本只有一个手势:按住键,说话,松开,文字落在光标所在的位置。知道文字要去哪的时候,这个顺序没问题。想先说出来、之后再决定放哪的时候,它就别扭;而光标在错误的输入框里的时候,它会悄无声息地出错。对我来说,后一种情况大约占一半。
PushSense 0.2 加了第二个手势。屏幕上有一颗小珍珠。点一下,说话,转写停在旁边的玻璃气泡里。然后你把文字拖进它该去的窗口。我叫它桌宠。这篇是它的构建日志:哪里难,哪里不难,以及发版流程抓到了什么。
每个听写 App 都有的隐藏步骤
我用过的所有语音转文字工具,包括我自己的,都是在光标处打字。这藏了一个你说话之前要做的动作:先点进正确的输入框。漏了这一步,你那句话就落进了搜索框、终端或者错误的聊天窗口。付费的 App 把转写打磨得很好,边说边出字,但它们全都保留了这个隐藏步骤。
解法不是更好的光标,而是默认不替用户打字。说出来,看一眼,再放到它该去的地方。这意味着转写得变成屏幕上一个能拿起来的东西。
原生拖放是最简单的部分
我以为拖拽会是最难的一块。结果就是一个 NSDraggingSession。气泡里的文字是一个提供纯文本的拖拽源;任何接受拖放文字的窗口都能接:编辑器、聊天框、浏览器输入框、终端。拖到桌面上,macOS 会把它变成一个文本剪辑。这些我一行都没写,AppKit 做的。
有意思的是拒收的窗口。有些 App 接受键入但不接受拖放。所以当拖拽结束、没有任何东西接收时,PushSense 会在你松手的位置点一下,然后粘贴。从用户这边看,手势是一样的:拖,松手,文字出现。
自动停止的图标选了四次
点珍珠说话后,停顿一秒半这一段会自动结束;关掉自动停止,就要再点一下结束。这个开关放在悬停珍珠时展开的一圈小玻璃按钮上,旁边是历史和菜单。它需要一个图标。
- 计时器。第一版。看起来像倒计时或秒表,而且和历史那个时钟离得太近。一圈上两个钟。
- 魔法棒。和时钟区分开了,但魔法棒暗示「把它变好」,那是 Smart mode 的活,不是这个开关的。
- 带斜线的波形。准确,但 18 像素下看不清。
- 带波形的耳朵。听你什么时候停,这就是这个模式做的事;形状也和时钟、菜单的三个点都不像。这个上线了。
可以推广的教训:三个一圈的图标,先要和邻居不一样,然后才谈准不准确。
待机的珍珠是一张静态图
第一版珍珠一直在动,一个缓慢的液态呼吸循环。看着是活的,代价是什么都不做也吃掉大约 5% 的一个 CPU 核。待机 5% 的听写工具,是会被用户退出的听写工具。
现在待机的珍珠是一张静态图。液态金属的球只在你点下去那一刻开始动,这一段结束就停。待机 CPU 从大约 5% 降到 0。热键模式里跟着光标走的光环也一样处理:第一次真正需要时才创建,所以打开桌宠模式根本不会启动光环的 WebKit 进程。
转写变慢的那一天,以及原因
做到一半,转写开始要三四秒才回来,平时远不到一秒。我以为是桌宠的代码在做什么昂贵的事。不是。同一台 Mac 上跑着一个视频生成任务,GPU 被它占了。Whisper 的 encoder 跑在 Metal 上,排在它后面:负载下 encode 一步要 3.5 秒,GPU 空出来后大约 0.3 秒。
提这个是因为它正是用户会报成「新版本变慢了」的那种事,而诚实的回答是:看看还有什么在用你的 GPU。0.2 没有任何改动让转写变慢。同一个模型,同一条路径。
用脚本点 App 抓到的三个 bug
切 release 之前,我用合成的点击和按键事件驱动 App:打开菜单、切换桌宠、开始一段录音、录到一半切模式、打开历史、切换界面语言,反复。不是单元测试,是一个脚本在点真实的 App。它找到三件我手动没看到的事。
- 滚动条压住文字。转写长到需要滚动时,气泡的滚动条盖在每一行最后几个字上。修法:文字排版宽度减去滚动条的宽度。
- 残留的光环。热键录音进行中打开桌宠模式,光标光环会永远留在屏幕上,因为 idle 处理里桌宠那个分支从来没有把它藏起来。修法:每次 idle 都隐藏光环,并把指示器移到和当前录音状态匹配的那一个(球或光环)上。
- 一个重复的键让中文界面崩溃。本地化表是一个 Swift 字典字面量。两个条目用了同一个英文键。英文下什么都不会发生;界面语言设成中文时,字典初始化遇到重复键直接 trap,App 启动即死。修法:去重,再加一个重复就让构建失败的检查。
三个真 bug,其中一个是所有中文用户的启动崩溃,大约二十分钟的脚本点击找出来的。这是我做过的最便宜的测试。
把语言检测器听到的亮出来
Whisper 每一段只用一个语言 token 解码。中英混说的句子它必须选一个,所以整句偶尔会变成那一种语言。PushSense 已经在中文有可能出现时偏向中文。它永远不可能每次都对。
所以现在它告诉你。气泡的 meta 行和每一条历史都带着检测器听到的比例,English 59% · 中文 39%,领先的在前。混说的句子变成一种语言时,那一行就是原因。它不修正这个猜测,它只是不再藏起来。
0.2 里有什么
- 桌宠模式,默认关闭:点珍珠说话,停顿自动结束,转写在可拖拽的气泡里。
- 悬停珍珠:历史(最近 50 条,存在你 Mac 上的一个 JSON 文件里)、自动停止开关、菜单。
- 每一段都显示识别到的语言比例。
- 桌宠输出:留在气泡里,或者像热键一样粘贴到当前应用。
- 热键的用法完全不变。
免费,已签名并公证,macOS 13 或更高,Apple Silicon。下载最新版,或者先看演示。哪个窗口拒收了你的拖拽,告诉我是哪个,我在记一个列表。