- 桌面应用
【免费下载链接】Lunar
Intelligent adaptive brightness for your external monitors
本篇基于当前仓库中的版本说明文件 ReleaseNotes/6.7.9.md,逐条解读 Lunar 6.7.9 的四项变更:音量键处理器在系统睡眠时的自动禁用机制、CLI 启动速度优化、LED Cinema 与 Thunderbolt 显示器在特定亮度值(39 和 78)下黑屏的长期遗留缺陷,以及手动坐标下 Location Mode 不可选的修复。读完你可以理解每项变更背后的实现路径,并知道如何在源码中验证对应行为。
版本概览
6.7.9 是一个以稳定性修复为主的版本,全部变更来自 ReleaseNotes/6.7.9.md:
| 类别 | 变更 |
|---|---|
| Improvements | 睡眠时禁用音量键处理器、唤醒时重新启用,防止 CoreAudio 挂起导致系统卡顿 |
| Improvements | 缩短 CLI 启动时间 |
| Fixes | 修复LED Cinema与Thunderbolt显示器亮度值溢出、在特定亮度(39和78)下显示器变黑屏的长期遗留问题 |
| Fixes | 修复使用手动坐标时 Location Mode 无法被选择的问题 |
从 Releases/appcast.xml 中的 Sparkle 更新源条目可以看到,该版本发布于 2024 年 4 月 23 日,minimumSystemVersion为11.0,最低自动更新版本为 5.0.0,分发包大小约 22 MB。同一版本在 CHANGELOG.md 的# 6.7.9小节中留有相同内容的记录,可用于对照阅读。
改进一:睡眠时禁用音量键处理器,避免 CoreAudio 挂起
问题背景
Lunar 通过拦截(tap)系统媒体键事件来实现亮度键和音量键的自定义行为。音量键处理路径会触碰 CoreAudio 框架:在决定是否接管音量键之前,Lunar 需要查询当前默认音频输出设备是否支持设置虚拟主音量。
在 Lunar/Data/Hotkeys.swift 的startOrRestartMediaKeyTap中可以看到这一步:
lazy var audioDeviceCantSetVolume = withTimeout(3.seconds, name: "CoreAudio") { defaultAudioOutputDevice?.canSetVirtualMainVolume(scope: .output) } onTimeout: { if Defaults[.autoRestartOnCoreAudioHang] { restart() } }这里已经用 3 秒超时包裹了对 CoreAudio 的查询——从源码结构看,正是因为 CoreAudio 在屏幕睡眠状态下可能长时间不响应(挂起),而挂起的查询会拖住整个主线程、造成系统级卡顿。超时后还有autoRestartOnCoreAudioHang兜底:检测到挂起就自动重启应用。
6.7.9 的解法:睡前进、唤醒退
与其在挂起发生后抢救,不如在睡眠期间直接不启动音量键 tap。这个行为由应用层监听屏幕睡眠/唤醒通知驱动,位于 Lunar/AppDelegate.swift:
// 屏幕睡眠 case NSWorkspace.screensDidSleepNotification: if Defaults[.disableVolumeKeysOnSleep], Defaults[.volumeKeysEnabled] { log.debug("Disabling volume keys") DC.volumeKeysDisabledTemporarily = true } // ... // 屏幕唤醒 case NSWorkspace.screensDidWakeNotification where !DC.loggedOut, NSWorkspace.sessionDidBecomeActiveNotification: // ... if Defaults[.disableVolumeKeysOnSleep], Defaults[.volumeKeysEnabled] { log.debug("Re-enabling volume keys") DC.volumeKeysDisabledTemporarily = false }两个要点:
- 该行为受隐藏设置
disableVolumeKeysOnSleep与用户设置volumeKeysEnabled双重门控,只有开启了音量键功能才生效; - 唤醒分支同时合并了
screensDidWakeNotification与sessionDidBecomeActiveNotification,并做了 2 秒 debounce(见 Lunar/AppDelegate.swift),避免合盖/解锁瞬间的通知抖动导致 tap 反复启停。
中间状态volumeKeysDisabledTemporarily定义在 Lunar/Utils/DisplayController.swift:
@Atomic var volumeKeysDisabledTemporarily = false { didSet { appDelegate!.startOrRestartMediaKeyTap(volumeKeysEnabled: volumeKeysDisabledTemporarily && Defaults[.volumeKeysEnabled]) } }它是一个@Atomic属性,任何线程写入都会立即触发startOrRestartMediaKeyTap重新评估:当volumeKeysDisabledTemporarily为true时,mediaKeyTapAudio会被stop(),即睡眠期间音量键 tap 完全停止运行,CoreAudio 查询根本不会发起;唤醒后恢复。这样从源头消除了睡眠期间 CoreAudio 挂起的可能。
用户侧对应的是高级设置中的 "Disable volume keys on display sleep" 开关,见 Lunar/SwiftUIViews/AdvancedSettingsView.swift,其说明文案明确写道音量键处理器会在睡眠时自动禁用。
改进二:缩短 CLI 启动时间
6.7.9 的另一项改进是加快 CLI 启动速度。Lunar 的命令行工具由 Lunar/Data/CLI.swift 实现,基于 swift-argument-parser 组织大量子命令(亮度/对比度读写、display信息、gamma、blackout、location、自适应模式设置等)。CLI 同时承担两种角色:
- 独立启动一个新实例执行命令;
- 通过 IPC 把命令转发给已在运行的 Lunar 实例(源码中
--remote与--spawn两个 flag 即用于控制这一行为,见 Lunar/Data/CLI.swift)。
启动时间的收益对脚本化场景(如在 shell 中批量调整外接显示器亮度)最为直接。本仓库的 Lunar/ALS/install.sh 等外围脚本也会调用 CLI,启动变快会缩短这类自动化流程的整体耗时。需要说明的是,release notes 只给出了结论性描述,未披露具体优化手段,源码层面也不应过度推断具体改了哪条启动路径。
修复一:LED Cinema / Thunderbolt 显示器的亮度溢出黑屏
这是 6.7.9 的核心修复。release notes 原文:
Fix a long-standing issue whereLED CinemaandThunderboltdisplays would overflow the brightness value, causing the display to go dark at specific brightness levels (
39and78)
涉及型号如何识别
Lunar 通过 EDID 中的显示器名称识别这些老款 Apple 显示器,常量定义在 Lunar/Data/Display.swift:
let LED_CINEMA_NAME = "LED Cinema" let CINEMA_NAME = "Cinema" let CINEMA_HD_NAME = "Cinema HD"对应的判定属性在 Lunar/Data/Display.swift:
lazy var isThunderbolt: Bool = edidName.contains(THUNDERBOLT_NAME) lazy var isLEDCinema: Bool = edidName.contains(LED_CINEMA_NAME) lazy var isCinema: Bool = edidName == CINEMA_NAME || edidName == CINEMA_HD_NAME lazy var isAppleDisplay: Bool = isStudioDisplay || isUltraFine || isThunderbolt || isLEDCinema || isCinema || isAppleVendorID这些老显示器(如 2011 年发布的 Apple LED Cinema Display、Apple Thunderbolt Display)需要通过 USB 附加连接走 DDC 控制,界面上也有相应提示,见 Lunar/Controllers/ControlChoiceViewController.swift。
溢出问题的成因
这类显示器的特殊之处在于它们的 DDC 亮度量程与普通 0–100 的显示器不同。围绕量程上限,CHANGELOG 中存在一次反复的拉锯:
- 早期版本曾 "Correctly set max DDC brightness to 255 on LED Cinema displays"(见 CHANGELOG.md 3023、3050 附近的历史条目);
- 6.7.8 之前一度 "Revert the max DDC brightness for LED Cinema and Thunderbolt displays back to 100 instead of 255"(见 CHANGELOG.md 278 行);
- 6.7.8 又 "Move max DDC brightness back to
255for Cinema and Thunderbolt displays"(见 CHANGELOG.md 234 行)。
这说明 100 与 255 两种量程在这些显示器上都会暴露边界问题。6.7.9 修复的是其中的值溢出:当内部亮度值被映射/截断为显示器无法接受的字节值时,面板在特定刻度上直接黑屏——release notes 点名的就是39和78这两个亮度值。可以推断,这两个值恰好落在量程换算的临界点附近,写入后触发了面板侧的异常状态;修复后写入路径对溢出值做了正确处理,黑屏不再复现。
相关参数在源码中的承载者是maxDDCBrightness,定义于 Lunar/Data/Display.swift,每个显示器独立保存:
@Published @objc dynamic var maxDDCBrightness: NSNumber = 100 { didSet { save() readapt(newValue: maxDDCBrightness, oldValue: oldValue) } }显示器初始化、重置路径(如 Lunar/Data/Display.swift 与 Lunar/Data/Display.swift)都会按defaultMaxDDCBrightness恢复该上限。对仍在使用 LED Cinema / Thunderbolt Display 的用户,升级到 6.7.9 后如果该显示器仍被误识别或量程配置异常,可尝试在显示器设置中重置 DDC 限值(resetLimits逻辑见 Lunar/Data/Display.swift)。
修复二:手动坐标下 Location Mode 不可选
Location Mode 根据地理坐标计算日出日落,再据此映射亮度曲线。默认情况下 Lunar 依赖系统定位,但用户可以在设置中手动填写经纬度,此时会置位manualLocation标记。
手动坐标的输入在 Lunar/Controllers/ConfigurationViewController.swift:纬度字段限定在-90.00 ~ 90.00、经度在-180.00 ~ 180.00,每次修改都会:
- 置
CachedDefaults[.manualLocation] = true; - 构造
Geolocation对象并store()持久化; - 更新
LocationMode.specific.geolocation,让 Location Mode 立即使用新坐标。
反向的恢复动作是 "重置位置" 按钮,见 Lunar/Controllers/ConfigurationViewController.swift:清除manualLocation标记后重新开启系统定位。
应用层在多处用该标记决定行为:例如 Lunar/AppDelegate.swift 中if !CachedDefaults[.manualLocation]才会更新系统定位;而 Lunar/AppDelegate.swift 附近则是在manualLocation为真时直接使用存储的坐标计算太阳位置。修复前,一旦manualLocation被置位,模式选择界面会把 Location Mode 判定为不可用;6.7.9 修正了这一逻辑,使使用手动坐标的用户也能正常选择并停留在 Location Mode——这对处于无法使用系统定位(或不想授权定位)环境中的用户是关键修复。
变更落点速查
| release notes 条目 | 主要相关源码 |
|---|---|
| 睡眠禁用音量键处理器 | Lunar/AppDelegate.swift、Lunar/Utils/DisplayController.swift、Lunar/Data/Hotkeys.swift、Lunar/SwiftUIViews/AdvancedSettingsView.swift |
| CLI 启动加速 | Lunar/Data/CLI.swift |
| Cinema/Thunderbolt 亮度溢出 | Lunar/Data/Display.swift、Lunar/Data/Display.swift、CHANGELOG.md 6.7.8/历史条目 |
| Location Mode 手动坐标 | Lunar/Controllers/ConfigurationViewController.swift、Lunar/AppDelegate.swift |
适用前提与说明
- 以上行为描述均以当前仓库源码为准,适用于 Lunar 6.7.9 及之后的构建;版本发布元数据(日期、最低系统版本 macOS 11.0)来自 Releases/appcast.xml。
- 睡眠/唤醒的音量键行为由
disableVolumeKeysOnSleep与volumeKeysEnabled两个设置共同控制,未开启音量键功能时不受影响。 - LED Cinema / Thunderbolt Display 需要额外的 USB 连接才能通过 DDC 控制亮度(见 Lunar/Controllers/ControlChoiceViewController.swift 的提示文案),无 USB 连接时亮度控制本身不可用。
- CLI 启动加速为结论性优化,仓库内未记录具体实现手段,本文不作进一步推断。
- 桌面应用
【免费下载链接】Lunar
Intelligent adaptive brightness for your external monitors
相关推荐
Lunar 修复 LED Cinema 显示器 DDC 控制:从识别逻辑到亮度区间映射的完整解读
Lunar 修复 LED Cinema 显示器 DDC 控制:从识别逻辑到亮度区间映射的完整解读 导读 Lunar 是一款为外接显示器提供智能自适应亮度调节的开
桌面应用RStudio 测试体系迁移实战:从 Python Selenium/Selene 到 TypeScript/Playwright 的系统化迁移指南
RStudio 测试体系迁移实战:从 Python Selenium/Selene 到 TypeScript/Playwright 的系统化迁移指南 本篇技术指
桌面应用3.1.2 版本解析:Lunar 如何修复重复 UUID 崩溃,并扩展显示器亮度上限至 255
3.1.2 版本解析:Lunar 如何修复重复 UUID 崩溃,并扩展显示器亮度上限至 255 版本导读 Lunar 是一款面向 macOS 外接显示器的智能自
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考