六月份我发了一篇关于Tungsten Edge 钨极的文章,那篇说的是这个东西长什么样、和原生 Dock 比性能怎么样。这篇我想说说我对这个产品的一些想法,我一个不会写代码的人和 AI 是怎么把它做出来的,它为什么要这么设计,以后怎么收费。
起因
我很多年前从 Windows 转到 Mac。
macOS 真的有很多优点,其中最重要的一点是 Adobe 系列软件是我工作的软件,而 Adobe 软件对于 Mac 的支持要比 Windows 好不少,所以 Mac 一下子就变成了我的主力电脑,这是毫不犹豫的事情。
用了一段时间后,真的发现 macOS 有很多优秀的地方。但是 Mac 的任务条 dock 栏一直让我不满意。因为我之前接触过 Windows 的系统,所以我觉得 Windows 系统虽然有很多不如 Mac 系统的地方,但是它的窗口管理确实做的比 mac 要好很多。
在 Windows 上,我开了哪些窗口,任务栏上一目了然,想要唤醒一个窗口,鼠标一点窗口就出来了。但是到了 Mac 上这件事就变了。比如我桌面上开着四个访达窗口,我没有办法一眼看出来我开了哪四个,也没有办法一下选中其中一个。要么右键点访达图标去选,要么三指上滑,但其实我仍然看不到这个窗口的名字。
然后我就找了一些类似 uBar 的替代原生 Dock 的方案。这类的软件我几乎都用了一遍,可是各有各的问题。没有一款我是觉得可以到日常使用的程度。最后还是换回了原生 Dock。一直到今年我接触了 AI 编程。我发现我可以借助 AI 去自己写一个软件来解决我的痛点。这是我做的第一款应用,我把它当做我的孩子一样对待,我也希望他能够越来越好。
这就是我做出来的东西,Tungsten Edge 钨极。
原生 Dock 的痛点
核心的问题就一句话:Mac 的 Dock 是以应用为单位的,而我们每天在操作的,是窗口。
如果一个应用只开一个窗口,这完全没问题。可现实是很多应用都是多窗口的,访达、浏览器、设计软件、终端,而我们经常要在同一个应用的好几个窗口之间来回切。这时候 Dock 就不够用了。

点击 Dock 图标,所有窗口一起出来。 我只想去操作访达的其中一个窗口,可是我点击访达图标,它把所有的访达窗口一起弹出来,而我要找的那个窗口,可能压在好几个窗口下面,我无法去选中它。

想精确唤醒一个窗口,都得至少两步。 macOS 不是没意识到这个问题,它给的解决方案其实不少:右键 Dock 图标有窗口列表,菜单栏「窗口」菜单里也有;三指上滑是 Mission Control,三指下滑是 App Exposé;再加上台前调度。
这些方法可以大致分成几类。列表类,你主动调出来才看得到,而且那个列表的顺序还在变,你没法记住你想要的窗口在第几个。摊开类,你得先进入一个全屏界面,用眼睛扫一遍(而且并不容易找到 ),选完再跳回来,手上正在做的事情会被打断。台前调度提供的是另一种思路,它整理的是桌面,而不是你的切换动作。同一个应用的几个窗口仍然是一摞一摞的出来,想要打开其中一个窗口还是得再选一次。
我觉得这些做法都是把一团乱麻先遮起来,而不是把它理顺。没有哪一种方法是「那扇窗口一直在那儿,你看一眼,点一下就到」。
钨极 Dock 和 Windows 的区别在哪
Tungsten Edge 钨极做的事情,就是把「以窗口为单位」搬到 macOS 的底边。每一个窗口在底下这条栏上有自己的一张卡片,卡片上写着窗口的名字,点一下就到。

但如果只做到这里,那就是把 Windows 任务栏搬过来了。
我用过很多类似的产品,有一些产品的思路是:「macOS 的 Dock 不好用,Windows 的任务栏挺好用,那我就做一个尽可能像 Windows 的东西。」当然有一些用户就是想在 Mac 上找到 Windows 的感觉。但我不希望钨极是这样。我自己是做设计的,我认为 macOS 的系统界面和交互是非常优秀的;而且 Windows 和 macOS 本来就是两套视觉系统,把 Dock 做得很像 Windows,在用 Mac 的时候会有非常强的割裂感。
而且我也认为 Windows 的任务栏有它自己的短板。在 Windows 上,底下那条栏会被排得非常满。比如 PS,它其实只有一个窗口,但 Windows 任务栏设成不合并的时候,还是用一个图标加一个名称的方式去显示它,这样整条栏就占满了。可是像 PS 这种单窗口的应用,我们基本上不需要知道它的窗口名。你看到 PS 的图标,就知道那是 PS,没有第二种可能。
所以钨极的做法是:只开了一个窗口的应用,折叠成一个图标;开了多个窗口的应用,才拆成一张一张带名字的卡片。

结果就是,你其实开了非常多的窗口,但底下这条栏看起来仍然是简洁的。同时借鉴 Windows 的效率和 macOS 的简洁,这句话是我做这个东西的时候一直在想的。「Windows 的高效,Mac 的优雅」,也是我追求的。
我把这种状态叫做「和谐」。不是让两个系统互相覆盖,也不是把不同的设计元素简单地拼在一起。我希望用户可以感受到 Windows 的效率,也能享受 macOS 原本的秩序感和美感。不觉得自己换了一套系统,而是在熟悉的 macOS 里,多了一种更高效的用法。

我想把它做成什么样
第一,一个简单的工具。
我信奉少即是多。刚开始做这款软件的时候,我脑子里想的就是一款简单、只做它分内工作的应用,并且把分内的工作做好。
功能丰富本身当然是好事。但它不可避免地会带来一些负面的东西。就算更多的功能占用的电脑资源可以少到忽略不计,对用户的理解成本和接受度,仍然是负面的影响。
第二,体验为先。
既然把功能做得简单,那就要把简单的功能做到极致。
1. 窗口逻辑
我在窗口逻辑上投入了很多精力。一个窗口被点击后,怎么准确地完成唤起、激活或者最小化;同一个应用下的多个窗口,要保持正确的层级和切换关系;应用里的标签页,不能被拆成独立的窗口显示在栏上。
这部分逻辑看起来简单,实际上涉及窗口的创建、销毁、激活、隐藏、最小化,以及多个窗口之间的关联关系。
坦白说,到今天为止,钨极的窗口逻辑仍然有一些边界上的问题。但我会把它当作这个产品最重要的敌人去重视、去优化。因为窗口逻辑一旦出错,用户感受到的不只是某个功能不对,而是整个产品「是一个半成品」。
2. 流畅度
一款每天要被最频繁点击的应用,如果做不到流畅丝滑,那我认为不会有人想去用它。
所以在我看来,流畅度是钨极的生命。流畅度做不好,再完美的窗口逻辑、再精妙的交互设计,都无济于事。
流畅度不只是动画顺不顺、页面卡不卡的问题,更重要的是你每一次操作都能得到及时、明确的反馈,从而带来点击的信心。你点击一个图标,反馈是立刻发生的;你拖动卡片调整顺序,卡片是跟手的;抽屉窗口弹开的时候不掉帧;鼠标在图标上面划过,图标就给反馈。这些都是很小的事,但它们每天都在发生,只要有一次不跟手,用户就会开始不信任它。

你不操作它时,它要全天挂在屏幕的底边,安静地等待着,不占资源,也不打扰你。
好的体验不应该让用户意识到产品的存在,而是让一切的操作都顺理成章。窗口逻辑解决的是「能不能用」的问题,而流畅度解决的是「愿不愿意一直用」。这两者缺一不可。
难的不是写代码,是规划和协作
一、这个产品最终会长成什么样子。
钨极测试版发出去之后,我陆续收到用户的建议。呼声最高、但我一直没做的是这两条:把同一个应用的窗口合并成一个图标;鼠标停在卡片上,出一张窗口的缩略图。
不是因为它们不合理。它们都很合理。
缩略图这一条最简单。但它需要「屏幕录制」的权限,我不想因为这个功能要一个那么重的权限。而且我也不认为缩略图是选窗口这个流程里重要的一环,卡片上本身有名字,名字比缩略图好认。
合并图标,是「加个开关」的问题。加一个开关,默认不动,想要的人自己去开,这什么都不影响。但是我用过太多这样的产品,设置页面内容一大堆,每一个选项都有一个非常合理的理由,可是当他们全加起来,就成为了我用不下去的原因。
所以我觉得判断不能一条一条地做,我划了一条线:默认必须一眼能懂,开关只在没有默认答案的时候才出现。守住这条线的代价很明显,现在就有人因为少了那个开关而选择不用它;但是我从这个产品应该是什么样子出发,我会挡住这些开关。

二、把问题讲成我听得懂的例子。
有一阵子切标签页卡片会跳。访达、Ghostty 这类应用,在标签之间切一下,底下的卡片先消失,再冒出来一张排到队尾;把一个开了好几个标签的窗口最小化,卡片分裂成好几张。 AI 改了很多次,每次修好一种情况,另一种又坏了,怎么也修不好。
然后我急了,我跟它说:你把问题描述成我能听懂的语言,我来帮你想想办法。
它给我举了停车场的例子,我们把停车场变成了技术沟通的环境:
- 车:一个窗口。
- 车位:底下这条栏上的一张卡片,代码里叫 seat。进场时发的票是 seat token,钨极自己发的身份,和车牌无关。
- 车牌:CGWindowID,窗口服务器给每个窗口面发的编号。原生标签页在 macOS 底下每个标签都是一个独立窗口,切标签时同一个位置上的 CGWindowID 会换。
- 走到车跟前问司机:Accessibility API,向应用本身要 AXWindows,拿到的是标题、位置、大小、有没有最小化。
- 门卫亭的登记簿:CGWindowList,窗口服务器手里所有窗口面的名单,每一条有 CGWindowID、所属进程、坐标、图层。
- 「别看车牌,看车位」:用 frame 做窗口的连续性判断。同一个 frame 上出现新的 CGWindowID,接进同一个 seat。
- 「两辆车抢一个车位就是新车」:tear-out。只有恰好一个 seat 声称这个 frame 时才允许接管;被拖出的 CGWindowID 拿一张新票,旧 frame 的 seat 留给原窗口。
窗口是车,底下这条栏上每张卡片是一个车位。每辆车有一个车牌,车牌是系统发的。一个开着标签的窗口是一个机械车位,像立体车库,几辆车叠着停,一次只升一辆到面上。要知道一辆车是谁有两个办法,一个是走到车跟前问司机,一个是查门卫亭的登记簿。它卡住的地方是:切标签的时候,车牌换了,司机的说法也换了,登记簿上也是新登记的一辆。它拿到的所有信息都在说「这是一辆新车」,它就没法不把它当新车。
听懂了这个,我反而想到一件它想不到的事:窗口一动没动,还在那个位置、还是那个大小,只有里面的内容换了。对用户来说那就是同一个窗口,没有第二种理解。所以问题不是认车,是守位。我跟它说:别看车牌,看车位。车位没动过,卡片就不动。上面换了哪辆车,不管。
它照这个改了,问题解决了大半。然后我又想到一个它没想到的情况:那我把标签拖出来变成一个独立窗口呢?老车位上还停着一辆,新车位又多了一辆,两辆车都说自己是原来那辆。我跟它说:那就是真的来了新车。老车位留给原来那辆,新车位发一张新票。这一条也写进去以后,标签页的问题才算彻底修好,到今天这条规则没再动过。

它不是不聪明,它的思路是从手里的数据出发,把矛盾的证据一条一条理顺,理不顺就再加一条规则,所以它反复修不好,一直在原来的问题里打转。我没有它聪明,可是我的思维方式跟它不同,从我的角度看过去,问题本身就变了,「认车」变成了「守位」,一条规则代替了一堆规则。
三、是把「感觉不对」磨成「什么时候一定会错」。
有一个闪烁问题,查了很久。点底下的卡片切到另一个应用的窗口,大概三分之一的时候,上一个应用的窗口会在屏幕上浮回来一下,再沉下去。就一下,一两百毫秒。但卡片是每天要点几百次的东西,这一下会让你觉得它不可靠。
这个问题的麻烦在于不好复现。AI 写探针程序,但是点出来全是好的,因为「闪」只在真人从一个真正在前台的应用点过去的时候才发生,程序模拟的点击走的不是同一条路。所以只能它在程序里埋上日志,我在真机上点:每次隔三秒,在不同的应用之间换着点,每一下编号,点完把日志给它读。一轮二三十下,前后九轮。
第一次的结论:我们能控制的都对了,但是这一下发生在 macOS 的合成层,压不掉,无解。 AI 建议接受现状。我不太甘心,就又来了几轮。
真正把问题定住的是两个观察。一个是它从日志里看出来的:闪的那几次,切走的应用都是 Ghostty;不是 Ghostty 的十八次,一次没闪。「什么情况下不发生」比「什么情况下发生」更能定位问题。另一个是我点出来的:Ghostty 的窗口在最前面还不够,得先多点它一下子,让它真正处于聚焦状态,再从它切走,闪的次数一下子就上去了。「大概三分之一、随机」其实是两个条件同时成立:要切过去的那个应用当时正在打盹,而切走的那个应用是 Ghostty、Chrome 这类会抢到顶层的应用窗口,并且它那会正处于聚焦。这两个条件写清楚以后,闪就不是随机的了,想让它闪就能让它闪。
条件定了以后,病根很快就找到了。不在系统里,而在我们自己的流程里:点击之后到真正切前台之间,程序先去问了几次打盹的那个应用,最长能等上个大半秒,这个空档里前面那个应用已经把自己的窗口浮回来了。调整的办法是点击的瞬间先切前台,把要问的事放到后面。问题解决了。
这件事没有捷径。需要精准地找到窗口逻辑出错时候的复现条件,一遍一遍地跟 AI 去调去测试。
现在一共 1,198 项单元测试,其中 375 项专门守窗口识别和切换。那 375 项不是「我很努力」的证明,它是每一次查明白的复现条件被钉住之后的样子。一个边界只要查清楚一次,就不会再丢了。
所以这一部分我也没法用视频演示。「准」是一个行为契约,不是画面。连点十次都正常的录屏,看起来和普通演示没有任何区别,所以拍不出「没出错」。
权限、开源,和我为什么不上 App Store
这一节可能比功能更重要,因为你要装的是一个能读你所有窗口的东西。
它要辅助功能权限。 因为它得知道你打开了哪些窗口,还得在你点卡片的时候把那扇窗口切到前面来。没有这个权限,这个产品就不存在。我知道这个权限很敏感。
所以源码是全开的,GPL-3.0-or-later,在 GitHub 上,谁都可以去看。这是我主动的选择。钨极要辅助功能权限,还挂了全局输入监听,我觉得源码公开是对「它会不会偷看我」这个问题唯一有说服力的回答。别的说法都建立在你信任我的基础上,只有这个是透明地让你自己看。
全局输入监听只认四种手势,普通输入不会被记录、写入日志、修改,或者发送到任何地方。应用本体没有任何统计上报,唯一会自己联网的是检查更新,只连我们自己的域名、不带系统信息。这一条我不会因为想要数据而让步。
签名和公证是有的。 从 v0.9.0 起用的是苹果的开发者 ID 签名并且过了公证,双击就能打开,不用右键「打开」那一套。背后是一家注册的公司,不是匿名开发者。
为什么不上 App Store:上不了。辅助功能权限和 App Store 要求的沙盒是互斥的,做这类工具的应用全都在 App Store 外面。不是不想上。
关于收费,现在就说清楚
目前是免费的。
1.0 之后会改成一次性买断,不做订阅。价格和日期我现在都还没定,定了会说。
在 1.0 之前完成邮箱确认的用户,永久免费。 这句话不是一句营销话术。产品没成熟之前我不应该收费,而早期用户愿意在半成品的状态下试用、给我反馈 bug,是他们让这个产品一点点变成熟的。我想给他们一些回馈。如果以后钨系列有别的产品,这批最早的用户我也会记着。
目前名单上已经有一千多个确认过的邮箱。关闭这个名单的时候我会提前至少两周宣布,写清楚时间。
有人可能会问,开源的东西凭什么收费。GPL 之下任何人都可以合法地自己拉下来编译,这一条不会变,已经发布的每个版本永远是 GPL。收费的是官方签名公证的安装包、自动更新和售后。
版本、下载和反馈
当前版本 v0.11.2,还在 0.11.x,没到 1.0。
- macOS 12.0 (Monterey) 及以上,Intel 和 Apple 芯片通用二进制
- 界面有简体中文和英文,跟随系统语言,也可以在设置里手选
- 安装包 10.4 MB,空闲的时候 CPU 占用在 0.7% 以下
官网:tungstenedge.app
源码:github.com/moonbai-studio/tungsten-edge
这个产品本来是我为了解决自己的问题做的,现在我把它拿出来给其他人用,所以我尽量把它做扎实。应用内开放了 bug 和建议的反馈入口,GitHub 上开 issue 也行,我都会认真去看。
关于 AI 的使用说明:本文正文全部由我自己写。AI只用来整理结构,也就是章节顺序和段落归并,没有生成或润色任何正文。文中的事实、数字和截图都由我自己核对过。
