想象一个普通的周三早上:你打开Mac,超宽屏上散落着八个终端窗口,它们随机出现在屏幕上,有的叠在一起,有的只露出一角。你得花整整一分钟,用鼠标把它们一个个拖到合适位置,摆成一个整齐的4×2网格——这才能开始一天的多会话Claude Code工作。这件事看起来只缺一行AppleScript,但真的动手写下去,才发现坐标转换、异步行为、行舍入……每一步都可能踩坑。

脚本的初始思路很直接:用osascript给每个窗口设定position和size。然而一旦开始写,三个问题立刻暴露出来。第一,macOS内部存在两套坐标系:Cocoa的Y轴向上,屏幕坐标的Y轴向下。第二,在某些情况下,AppleScript的set position指令会被悄悄忽略,窗口纹丝不动。第三,即便位置对了,窗口最终的高度也常常不是你设定的那个值。这三个问题只是冰山一角,后面还藏着两个更隐蔽的坑。

要解决坐标混乱,作者把转换全部交给了Swift。核心代码通过NSScreen的visibleFrame拿到不含Dock的区域,再将其从Cocoa坐标(左下原点)转成全局坐标(左上原点)。转换公式很明确:主屏高度减去(visibleFrame.origin.y + height),就得到全局的topY。这样,x、y、宽、高四个值可以干净地导出为GX GY GW GH四个shell变量,后续AppleScript直接引用,不再需要人脑心算两种坐标系。

但就算坐标系转换正确,第四个陷阱还是在实测中冒了出来。对于非主屏的显示器,visibleFrame有时会直接返回和frame完全一样的高度,没有自动扣除顶部30像素的菜单栏。如果代码不加处理,所有窗口都会整体上移,压住菜单栏区域。为此,Swift里加了一个fallback:当visibleFrame高度等于屏幕总高度时,手动减去30像素,并相应地调整topY。这个修正只能从实际测量中得出,文档上并未写明。

第五个陷阱与AppleScript的异步特性有关。给窗口设定position和size的调用可能不会按顺序立即生效,多个窗口的布局如果并发执行,会彼此干扰,导致部分窗口仍留在默认位置。一个稳妥的做法是在每设置完一个窗口后加入短暂的延迟,或把整个操作串行化。虽然原文没有展开异步细节,但这一层隐患确实是将窗口排列变成可靠一键操作的最终障碍。

经历这五个陷阱后,一行命令终于能可靠地把八个终端窗口吸附到4×2网格上。脚本的另一个设计核心是将坐标计算全部外置到Swift,让AppleScript只负责应用数值,职责单一、出错也容易定位。这种“把脏活交给Swift,把界面指令还给AppleScript”的分工,在macOS自动化里是个很实用的模式,其他需要批量控制窗口的场景也能复用。

整个过程揭示了一个有趣的真相:哪怕是最简单的窗口排列,背后也横亘着API设计的时代断层、文档盲区和异步行为。开发者从“一天拖一次窗口”到“敲一下命令就搞定”,节省的每一分钟,都来自对这些陷阱的溯源与打补丁。而将这个过程分享出来,恰好让下一个人不必再把同样的坑重新踩一遍。