Skip to content
kafeifeiPublic

About

Keep a Mac awake with its lid closed on AC power, with a compact battery menu.

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

LidAwake(合盖守护)

LidAwake 是一个原生 macOS 菜单栏工具:接通电源时保持 Mac 合盖运行,切换到电池后恢复正常睡眠,同时替代系统电池图标显示电量、充放电状态和功率信息。

Important

LidAwake 会安装一个 root LaunchDaemon,并在接通电源时执行 pmset disablesleep 1。这是一项系统级设置,不只影响合盖睡眠。请先阅读“工作方式”和“卸载”部分。

功能

  • 监听 IOKit 电源变化事件,插电时保持运行,拔电时恢复正常睡眠。
  • 每 10 秒重新核对一次,修正漏掉的事件或外部状态漂移。
  • 后台服务在系统启动时运行,不依赖用户登录或桌面会话。
  • 菜单栏电池图标区分充电、接通电源但未充电、使用电池等状态。
  • 显示电量、预计充电/续航时间、整机负载、外部输入、电池净功率和适配器能力。
  • 在支持的系统上直接读写 macOS 原生 80%–100% 充电上限。
  • 提供“插电时合盖保持运行”开关;关闭后仅保留电池显示。
  • 提供“电池下也保持运行”限时会话(30 分钟 / 1 小时 / 2 小时),到期、电量低于 20% 或手动停止后自动结束。
  • 接入 RME ADI-2 DAC 时,菜单里显示当前输出(线路 / 耳机 / IEM)的 DAC 音量和滑块,并可开启“用音量键控制 ADI-2 DAC”,详见“ADI-2 DAC 音量”。
  • 通过 Sparkle 提供应用内自动更新:默认每天在后台检查一次,发现新版本后自行下载、安装并重新启动应用,全程不弹窗、不需要点击(安装只在菜单未打开时进行)。只有在关闭自动安装或系统不允许自动安装时,才会退回到提示方式:菜单栏电池图标右上角显示一个红点,菜单项变成“有新版本 X.Y.Z…”,点击它打开更新对话框,看过之后红点消失。自动安装可以在菜单里的“自动安装更新”中开关;手动“检查更新…”仍然直接弹出对话框。

ADI-2 DAC 音量

RME ADI-2 DAC 不向 macOS 提供系统音量,菜单栏音量和键盘音量键对它无效。LidAwake 通过 DAC 的 USB MIDI 端口发送 RME 公开的 ADI-2 协议指令(与官方 ADI-2 Remote 相同的通道),在 DAC 内部调节音量,等同于转动机身旋钮;音频数据流不经过 LidAwake,也不做任何数字衰减。

  • 菜单中的 ADI-2 DAC 一栏只在 DAC 连接时出现,显示旋钮当前控制的输出及其音量,滑块以 0.5 dB 为步进。滑块和音量提示条按音量曲线分布,每按一下音量键,条走的长度相同。直接转动或按下 DAC 上的旋钮时,也会弹出同样的音量提示。
  • 打开“用音量键控制 ADI-2 DAC”后,只要默认输出设备是 ADI-2 DAC,音量加减键沿音量曲线一格一格走,默认音量越大步进越细,从最小到 -25 dB 共 25 下(按住 ⌥⇧ 走四分之一格),并对齐到步进刻度;静音键切换 DAC 静音,并显示一个小的音量提示;默认输出是其他设备时,音量键保持系统原样。DAC 上锁定了音量(Lock Volume)时不会改动音量。
  • 菜单中的“ADI-2 音量曲线…”打开一张以按键次数为横轴、dB 为纵轴的曲线图,每一下都标在曲线上:拖动圆点改变曲线形状,拖动右端的方块,上下改最大音量,左右改从最小到最大一共按几下。曲线越陡,那一段每按一下跳得越多;拖动时立即生效。
  • 曲线右端的高度就是音量键和滑块能调到的最大音量(-60 dB 到 +6 dB 之间的整数 dB,默认 -25 dB),滑块和音量提示条到这里即为满格。DAC 上的旋钮不受此限制,超过最大音量时音量键只会往下调。
  • 截获音量键需要“辅助功能”权限:首次开启时系统会提示,在 系统设置 › 隐私与安全性 › 辅助功能 中允许 LidAwake 即可,授权后无需重启。
  • DAC 设置里的 MIDI Control 需保持开启(出厂即开启);若菜单里看不到 ADI-2 DAC 一栏,先检查这一项。

系统要求

  • macOS 13 或更高版本。
  • 从源码构建需要 Xcode 16.4 或兼容 Swift 6 的工具链。
  • 合盖策略可在 Intel 和 Apple Silicon Mac 上构建运行。
  • 原生充电上限仅在 Apple Silicon 和 macOS Tahoe 26.4 或更高版本显示。

充电上限通过运行时检测系统 PowerUI 客户端实现。它不是公开开发者 API,因此未来 macOS 更新可能使此区域暂时不可用;失败时应用会隐藏该控件,不会自行模拟断充。有关系统原生充电上限的行为,请参阅 Apple 支持文档。

工作方式

应用由两部分组成:

  1. LidAwakeHelper 是 root LaunchDaemon。它在开机阶段监听电源事件并控制 SleepDisabled。
  2. LidAwake 是登录后的菜单栏应用。它显示状态、保存开关设置,并在首次运行时注册系统登录项、安装后台服务。

策略如下:

状态 SleepDisabled 行为
接通电源且开关开启 1 保持运行
使用电池 0 正常睡眠
电池会话生效(到期/低于 20% 自动结束) 1 保持运行
开关关闭 0 正常睡眠
无法判断电源 0 安全回退

睡眠被禁用期间合上盖子时,若没有外接显示器,菜单栏应用会立即让内建显示器进入睡眠(pmset displaysleepnow),避免合盖后屏幕持续点亮直到显示器睡眠计时器到期;退出菜单栏应用后此行为不再生效。

Warning

电池供电时合盖运行会让机器散热受阻、温度升高,并快速消耗电量。因此“电池下也保持运行”只提供限时会话,不提供常开模式:会话到期、电量降到 20% 或手动停止后立即恢复正常睡眠,且低电量结束的会话不会因为重新充电而自动恢复。

配置保存在当前安装用户的 ~/Library/Application Support/LidAwake/config.json。后台状态保存在 /Library/Application Support/LidAwake/status.json。一台 Mac 同时只应由一个用户管理这项系统级策略;后安装的用户会成为控制用户。

功率数据

  • 电源:标题显示适配器能力,例如 电源 100W;下方显示 SMC PDTR 实时 DC 输入。
  • 电脑:Apple SMC PSTR(System Total)整机功率传感器。
  • 电池:优先使用 SMC PPBR 电池电源轨的实时读数;无法读取时才回退到电池包电流或功率平衡估算。

同一份遥测快照中近似满足:

电脑与电源功率直接读取 SMC 传感器,不由其他栏位计算。

电脑功率来自 SMC PSTR,电源输入来自 SMC PDTR,电池功率来自 SMC PPBR;这些传感器和菜单通常约每秒更新。由于 macOS 只向 AppleSmartBattery 暴露聚合缓存,BatteryPower 只作为回退,不作为实时主数据源。这些数值适合观察功率方向和量级,不应当作精密功率计。

充电时间使用 macOS 提供的估算,再按当前充电上限对剩余百分比做近似换算。电池充电并非严格线性,显示值仅供参考。

安装发布版

  1. 解压发布包。
  2. 先把 LidAwake.app 移到 /Applications,再首次打开。
  3. 按提示授权安装后台服务。

如果忘了移动就直接打开,应用会提示“请将 LidAwake 移到「应用程序」文件夹”,点“移动并重新打开”即可由它自己完成搬家(/Applications 不可写时会请求一次管理员授权),随后从新位置重新启动。

应用会为当前用户注册登录时启动;root 后台服务安装完成后会在系统启动阶段运行,不需要用户进入桌面。登录项和后台服务都按应用所在路径注册,因此发布版只支持 /Applications/LidAwake.app 这一个位置;应用在其他位置启动时会拒绝运行,并提供上面的自动移动。

应用内更新由 Sparkle 完成:更新下载安装后会自动重启应用。如果某次发布同时提升了后台服务版本,更新后的第一次启动会再请求一次管理员授权,用于重新安装 LidAwakeHelper;之后的启动不再提示。

从源码安装

git clone https://github.com/kafeifei/LidAwake.git
cd LidAwake
./Scripts/install-app.sh

脚本会构建应用,将其安装到 /Applications/LidAwake.app 并打开(/Applications 不可写时脚本会直接报错,请换用管理员账户运行一次,而不是用 sudo 安装)。应用通过 macOS SMAppService 注册登录时启动;如果用户曾在系统设置中禁止该登录项,菜单中会显示“允许登录时启动…”。首次运行还会请求一次管理员授权,用于把后台服务安装到:

  • /Library/PrivilegedHelperTools/com.kafeifei.LidAwake.helper
  • /Library/LaunchDaemons/com.kafeifei.LidAwake.helper.plist

安装完成后可检查:

pmset -g | grep SleepDisabled
cat '/Library/Application Support/LidAwake/status.json'

合盖行为仍应现场 A/B 验证:不连接外接显示器和输入设备,插电合盖确认任务继续运行;保持合盖并拔电,确认系统恢复睡眠。

卸载

不要只删除 .app,否则系统后台服务仍可能继续运行。推荐直接用菜单里的最后一项:

  • 点击菜单栏图标 → 卸载 LidAwake… → 确认,全过程不需要终端,只会请求一次管理员授权。

也可以在终端执行源码仓库中的脚本:

./Scripts/uninstall.sh

如果只保留了发布版应用,同一个脚本也在应用包里:

"/Applications/LidAwake.app/Contents/Resources/uninstall.sh"

卸载会先执行 pmset disablesleep 0,再移除 LaunchDaemon、系统登录项、旧版 LaunchAgent、配置、状态和应用本体。应用从任意目录运行时,包内卸载脚本都会验证 bundle identifier 后再删除对应的 LidAwake.app;当前用户无权删除应用本体时,会在同一次管理员授权里一并删除。

开发与验证

运行完整检查:

./Scripts/check.sh

它会执行警告即错误的单元测试、脚本和 plist 校验、Release 构建以及代码签名结构验证。GitHub Actions 使用同一入口。

正式发布包

普通源码构建使用 ad-hoc 签名,只适合在本机运行。公开分发 .app 需要 Developer ID、Hardened Runtime 和 Apple notarization。准备好证书和 notarytool 钥匙串配置后运行:

LIDAWAKE_SIGN_IDENTITY='Developer ID Application: Your Name (TEAMID)' \
LIDAWAKE_NOTARY_PROFILE='LidAwake-notary' \
./Scripts/release.sh

发布脚本会生成 arm64 + x86_64 Universal Binary,签名所有可执行文件,提交公证、装订 ticket、通过 Gatekeeper 检查,并在 dist/ 生成 ZIP 和 SHA-256 文件(SHA-256 文件中记录的是 ZIP 的文件名,不含目录)。它还会调用 Sparkle 的 generate_appcast(EdDSA 私钥从登录钥匙串读取)生成 dist/appcast.xml;扫描的是只包含本次归档的临时目录,因此 dist/ 中遗留的旧版本 ZIP 不会混进 appcast。该文件必须和 ZIP 一起上传到同一个 GitHub Release,因为应用的更新源地址指向最新 Release 的 appcast.xml 资源。Apple 的要求见 Distributing software on macOS 和 Notarizing macOS software。

隐私与网络

LidAwake 唯一的网络访问是 Sparkle 的更新检查(ADI-2 DAC 控制只走本机 USB MIDI):向 GitHub Releases 上托管的 appcast 拉取版本信息,默认每天一次,可以在 Sparkle 首次弹出的提示中选择不自动检查,也可以只用菜单中的“检查更新…”手动触发。除此之外没有遥测、分析或用户账号,所有状态都保存在本机。

许可证

项目使用 MIT License。电池图标部分改编自 MIT 许可的 Stats,详情见 THIRD_PARTY_NOTICES.md。

About

Keep a Mac awake with its lid closed on AC power, with a compact battery menu.

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages