把 macOS 的 Cmd+Tab / Cmd+` 变成液态玻璃选择面板 —— 一天迭代 20+ 轮换来的教训
一个 app 同一时刻只有一个 key window,第二个 makeKey 必然抢走第一个的 key。多屏面板想让玻璃同时聚焦?做不到——这是后面一串坑的总根源。
NSGlassEffectView 的聚焦/失焦样式由系统按窗口 key 状态渲染,SDK 头文件确认没有「强制聚焦样式」的属性。所有「给玻璃切换加过渡」的尝试都在对抗这一点。
纯 AX(setFocused)对后台 app 无激活效果,必须 app.activate();而 activate 自带窗口决策,激活后还得 refocus 纠正——决策窗口「闪现后消失」由此而来,窗口级路径无法根除。
系统 cmd+tab 会把应用全部窗口带到前台,但直接调 activate() 在 macOS 26 上只提升一个显示器的窗口,副屏被盖的窗口不会浮上来。
「点击面板外取消」用 addGlobalMonitorForEvents 实现,但面板窗口内的透明区域点击被本 app 窗口吞掉——不取消也不透传,成了点击死区,只能自己垫透明点击层。
makeKeyAndOrderFront 实测必然隐式激活(yieldActivation 也拦不住),面板生命周期内 app 短暂 active,关闭即失活。
① 全量 raise:打乱其他窗口相对层序,每次切换非选中窗口互换位置。② CG z 序保序 raise:被完全盖住的窗口「提到顶再被压回」闪现。③ 遮挡感知 raise(只提被其他 app 盖住的):仍闪屏。根因:AXRaise 没有原子组操作,中间态必然可见。
① 恒定压暗层:难看(用户不要常驻背景)。② 玻璃淡到 0 再切换:玻璃完全消失露出纯模糊底,闪帧。③ 淡到 30%:alpha 稀释了玻璃的黑色 tint,「先亮一下再慢慢变暗」的脉冲。④ 瞬时高光补偿:也没能留住。结论:直接切换是接受态。
一个窗口横跨所有屏、一个 key 全屏聚焦——思路正确,但 .screenSaver 级窗口跨屏只在主屏渲染(WindowServer 不合成第二屏),空白区还有点击死区;降级窗口级修复后仍被要求回退双窗口方案。
兜底「modifier 状态丢失」的超时把长时间按住浏览的面板杀掉了。改成每秒轮询 CGEventSource.flagsState:只有 cmd 物理已松开(且松开事件丢失)才收起,按住多久都不取消。
iPhone 靠近时 Dock 出现 Handoff 条目(AXHandoffDockItem),title 与真实 app 同名(如「提醒事项」),AXStatusLabel 竟是设备标识 com.apple.iphone-13-pro-1——把真实未读数 4 覆盖成了一行字符串。按 subrole 过滤解决。
行视图被裁剪视口的 edgeInset 裁掉两侧,点击落在容器上无效。改成行全宽接收点击、高亮块用内缩子层绘制——点击区域与视觉宽度解耦。
未同意 Xcode license 时 swift build 直接报错。DEVELOPER_DIR=/Library/Developer/CommandLineTools 指向 CLT 工具链即可绕过。
① 参数声明为 NSPanel 时看不到 NonKeyPanel 子类的方法;② 覆写 init(frame:) 忘写 override;③ CGRect.union 是实例方法,不能作为函数值传给 reduce,要写 { $0.union($1) }。
发版跳号时删了 tag,稍后 tag 又出现了——CI 在被取消前已创建 Draft release,GitHub 会为 draft release 自动重建关联 tag。必须先删 release 再删 tag;取消 CI 要赶在 release 步骤之前。