2026-10-21 · Codex 教程 ⑩ · 技巧

把温度搬到菜单栏
让它一直看得见

最初,温度条浮在 Codex 窗口右上角。窗口一遮住,数字就失去意义; Codex 一更新,原来的临时浮层还可能失效。这次把它搬进 macOS 菜单栏, 留下一个始终可见、只读、离线的小工具。

现在菜单栏上的读数很克制:CPU 48° S 40°。 CPU 是 Apple Silicon 的综合温度,S 是外接存储盘; 左边的小圆点在正常、偏热、高温时分别变绿、橙、红。点开后才有完整小数、 更新时间和手动刷新,不把菜单栏挤成一块仪表盘。

窗口内是好原型,不是最终位置

第一版把温度条挂在 Codex 的界面内,开发快、样式自由,也能直接拖动。 但它把显示层绑在 Codex 窗口里:Codex 关闭或界面结构变化, 温度条都会跟着受影响。监控工具不该依赖被监控的窗口。

迁移后的四层职责
层级实现职责
数据源macmon、smartctl只读本机传感器
采样后台进程 + 定时器CPU 每 3 秒、外接盘每 15 秒更新
显示AppKit NSStatusItem菜单栏读数、详情和阈值颜色
生命周期LaunchAgent登录后启动,异常退出后恢复
先把展示层搬出去
数据仍然来自本机,但菜单栏程序不再依赖 Codex 窗口,也不修改应用文件。 之后 Codex 怎样升级,都与这枚温度读数没有直接依赖。

先定义两个真正有用的数

可用的数据不少,菜单栏却只适合放两项。这里选择 CPU/SoC 综合温度和外接盘温度: 前者能看出处理器是否持续高负载,后者能发现外置 SSD 长时间读写后的积热。 两项数据先在本机确认:

/opt/homebrew/bin/macmon pipe -s 1 -i 100
/usr/sbin/diskutil info /Volumes/1TB
/opt/homebrew/bin/smartctl -a /dev/disk6

macmon 最后一行 JSON 里的 temp.cpu_temp_avg 是 CPU 综合值; smartctl 的 Temperature 行给出外接盘温度。磁盘编号不要永远写死: 先从 diskutil 输出里找 APFS Physical Store 或 Part of Whole,再拼成 /dev/diskN。盘换接口、重新编号时, 热插拔才不会继续读旧设备。

状态阈值沿用这次实机的判断:低于 68°C 为正常,68–81°C 为偏热,82°C 起标红。 颜色只是辅助,菜单里同时写着“正常”“偏热”或“较高”,不让色觉差异成为唯一信息来源。

菜单栏只做三件事

原生状态栏项目由 NSStatusItem 创建。标题使用等宽数字,温度变化时不会左右抖动; 小圆点负责快速扫一眼状态;点击后的菜单才显示两位小数、更新时间、刷新和退出。 程序启动时把激活策略设为 .accessory,所以只在菜单栏出现,不占 Dock。

let item = NSStatusBar.system.statusItem(
    withLength: NSStatusItem.variableLength
)
item.button?.attributedTitle = NSAttributedString(
    string: "CPU 48°  S 40°",
    attributes: [
        .font: NSFont.monospacedDigitSystemFont(
            ofSize: 11.5, weight: .medium
        )
    ]
)
item.button?.toolTip = "CPU / SoC 47.6 °C,S · ZHITAI 40.0 °C"

读数更新时只改标题、圆点和提示文字,不做动画,也不弹出通知。 温度是持续变化的环境信息,安静地待在那里比每一次变化都抢注意力更有用。

采样要短,失败也要能收尾

macmon 和 smartctl 都是外部进程,不能直接卡在主线程上跑。 采样放到后台队列,分别设置 6 秒和 8 秒超时;同一类采样尚未结束时直接跳过下一轮, 防止慢命令越积越多。任一项失败时只把该数字显示成 --°, 另一项仍可继续工作。

显示器休眠、外接盘拔掉都属于正常运行状态,不需要报警弹窗。温度工具的价值是提供上下文, 不是替用户做故障判断。真要延长排查,菜单里的更新时间和“不可用”状态已经够用。

用 LaunchAgent 管生命周期

小工具编译成一个本机可执行文件后,由 LaunchAgent 在登录时启动。 RunAtLoad 保证登录后出现,SuccessfulExit=false 让异常退出可以被重新拉起;用户在菜单里主动选择退出则算正常结束,不会立刻复活。

/usr/bin/xcrun swiftc -O TemperatureMenu.swift \
    -o CodexTemperatureMenu

launchctl bootstrap "gui/$(id -u)" \
    "$HOME/Library/LaunchAgents/com.dola.codex-temperature.plist"

验收不看“编译成功”,而看四件事:状态栏项目真的出现、读数来自真实传感器、 点击菜单能刷新、登录启动项处于运行状态。这次实机读到 CPU 48°、外接盘 40°,菜单详情为 47.6°C 和 40.0°C,进程由 LaunchAgent 保持运行。

迁移时真正踩到的坑

一、不要靠重载主界面清理旧浮层。 旧温度条属于窗口内的临时层,直接重载 Codex 主界面可能得到一个空白窗口, 还会连带丢失当前界面状态。先停用旧浮层,再用应用自身的重启流程清理更稳。

二、硬盘编号不会永远不变。 第一版默认读取 /dev/disk6,短期能用,换接口后就会失效。 现在先从已挂载卷定位物理盘,再调用 smartctl。

三、短标签比完整名称重要。 菜单栏空间有限,外接盘没有再写“外接盘”四个字,而是用大写 S; 点开菜单后才写清 S · ZHITAI。信息没有丢,只是分层了。

结论

这次最重要的变化不是“多了一个温度条”,而是把数据源、采样、显示和生命周期拆开。 菜单栏负责一直可见,后台采样负责稳定更新,LaunchAgent 负责启动和恢复; 它们都不再依赖 Codex 窗口是否开着。

一个监控组件最好的状态,是平时安静、需要时立刻能读到, 而且被监控的应用更新后,它仍然活着。

实测环境:Mac mini M2、macOS 27、macmon 与 smartmontools 均在本机运行;无网络请求、无模型调用、无 Codex 应用文件修改。 上一篇 ⑨ 什么该自己跑,什么必须停一下, 继续读 AI 能生成界面,但质感藏在交互里, 或回到 全部笔记。 回到首页