- 桌面应用
【免费下载链接】Lunar
Intelligent adaptive brightness for your external monitors
本文基于 Lunar 仓库
ReleaseNotes/6.7.5b2.md版本说明展开,深入剖析该版本在 Sync Mode(同步模式)上的三项核心改动:亮度适应路径的平滑化与性能优化、新增隐藏设置syncModeBrightnessKeyPressedExpireSeconds对 OSD 展示时机的影响,以及 Nits 范围计算的直观化重构。结合仓库源码,帮助读者理解这些改动背后的实现机制,掌握在真实环境中调优 Sync Mode 行为的具体方法。
版本概览:beta 2 的三个核心变化
ReleaseNotes/6.7.5b2.md记录了 Lunar 6.7.5 的第二个 beta 版本,其改动高度聚焦于 Sync Mode 这一核心功能:
- Features(新特性):更平滑的 Sync Mode 亮度适应(Smoother Sync Mode brightness adaptation);
- Fixes(缺陷修复):修复 CLI 输出误打印
SentryCrashExceptionApplication的问题; - Improvements(改进):新增隐藏设置
syncModeBrightnessKeyPressedExpireSeconds、优化从 Sync 源显示器亮度变化到目标显示器亮度适应的代码路径、以及更直观的 Sync Mode Nits 范围计算。
Sync Mode 是 Lunar 的一项核心自适应亮度模式:它以一台显示器的亮度变化为信号源(通常是内置显示器),将这种变化"同步"到其他外接显示器上,从而让多显示器环境下所有屏幕的亮度保持一致。在 Lunar/Modes/AdaptiveMode.swift 中,Sync Mode 通过SyncMode.shared单例实现,与 Sensor、Location、Clock、Manual 等其他模式并列,由AdaptiveModeKey枚举统一管理(sync对应.sync分支,见 AdaptiveMode.swift)。
更平滑的 Sync Mode 亮度适应
改动背景
Sync Mode 的工作链路是:监听信号源显示器(sync source)的亮度变化 → 计算目标显示器(sync target)应有的亮度 → 通过 DDC/AppleNative 等控制方式写入目标显示器。这条链路中任何环节的延迟或抖动,都会表现为目标显示器亮度的"跳变"或"闪烁"。
beta 2 的"更平滑"改动,本质上是重排并精简了这条链路中的计算与调度逻辑,减少冗余计算和中间状态切换,使亮度写入更加连续、无感。
源码佐证:从按键到适应的完整链路
在 Lunar/Data/Hotkeys.swift 的adjust()方法中,当用户按下亮度调节快捷键(brightnessUp/brightnessDown)时,会执行两类操作:
- 标记按键事件:调用
DC.pressedBrightnessKey.true(for: DC.syncModeBrightnessKeyPressedExpireSeconds)(见 Hotkeys.swift),表明"刚发生过亮度键按压"这一事实,并让它在一段时间后自动失效; - 执行亮度增减:调用
increaseBrightness(...)/decreaseBrightness(...),将变化写入对应的显示器。
pressedBrightnessKey/pressedContrastKey是DisplayController中定义的ExpiringBool类型属性(见 Lunar/Utils/DisplayController.swift)。ExpiringBool定义在 Lunar/Data/Util.swift:它本质上是一个带"过期时间"的布尔值,调用true(for: time)时立即置为true,并安排一个延迟任务在指定秒数后自动翻转为false。这正是本版本改进的核心载体——按键"有效窗口"的时长,就由syncModeBrightnessKeyPressedExpireSeconds决定。
隐藏设置syncModeBrightnessKeyPressedExpireSeconds深度解析
为什么要展示 OSD 只考虑"按键后一段时间内的变化"
在 Sync Mode 下,OSD(屏幕亮度指示条)的展示逻辑与普通模式不同:只有发生在"亮度键按压之后的等待期内"的亮度变化,才需要触发目标显示器上的 OSD 展示。这样做的目的是避免无关的、系统自发的亮度波动(例如环境自适应、其他进程的写入)反复弹出 OSD 干扰用户。
版本说明原文明确指出:
Showing OSD on target monitors in Sync Mode requires only taking into account the changes that happen within a waiting period after the brightness key press.
也就是说,从按下亮度键的那一刻起,Lunar 打开一个"时间窗口",窗口内的亮度变化事件被认为是用户意图驱动的,据此决定是否在目标显示器上显示 OSD。
默认值从 0.5 秒提升到 1 秒
版本说明指出:
That period was previously 0.5 seconds, which was now increased to 1 second and made configurable via the
syncModeBrightnessKeyPressedExpireSecondshidden setting. Some systems take longer to propagate the brightness change events and this setting should help with that.
即该窗口原先固定为 0.5 秒,现在默认提升为 1 秒,并开放为可配置项。原因是:部分系统从按键到亮度变化事件传播完成需要更长的时间,0.5 秒的窗口过短,可能导致事件落在窗口之外,OSD 无法正常弹出,或者同步动作被判定为"非用户触发"而跳过。
在 LunarShortcuts/Shared.swift 中可以看到该设置的正式定义:
static let syncModeBrightnessKeyPressedExpireSeconds = Key<Double>("syncModeBrightnessKeyPressedExpireSeconds", default: 1.0)它属于Key<Double>类型,默认值为1.0,在代码中通过@Setting(.syncModeBrightnessKeyPressedExpireSeconds)注入到DisplayController(见 Lunar/Utils/DisplayController.swift)。
如何在终端中配置该隐藏设置
作为隐藏设置(hidden setting),它不会出现在 Lunar 的设置界面中,需要通过 macOS 的defaults命令写入。Lunar 的偏好设置域名为fyi.lunar.Lunar,仓库 CHANGELOG 中大量隐藏设置均使用该方式配置(例如 CHANGELOG.md 中的defaults write fyi.lunar.Lunar ignoreDisplaysWithMissingMetadata false)。
将该窗口调大(单位:秒):
defaults write fyi.lunar.Lunar syncModeBrightnessKeyPressedExpireSeconds 2恢复默认值(1 秒):
defaults delete fyi.lunar.Lunar syncModeBrightnessKeyPressedExpireSeconds说明:值为
Double类型,可以写入小数。例如 1.5 表示 1.5 秒。修改后需要重启 Lunar(或重新登录)使设置生效。如果遇到"按亮度键后 OSD 不显示"或"同步动作偶尔丢失"的问题,可以尝试逐步增大该值;反之若觉得 OSD 弹得太频繁,可适当调小。
底层实现:ExpiringBool 的过期机制
syncModeBrightnessKeyPressedExpireSeconds的值最终被传给ExpiringBool.true(for:),其内部实现(Lunar/Data/Util.swift)如下:
func `true`(for time: TimeInterval) { set(true, expireAfter: time) } func set(_ value: Bool, expireAfter: TimeInterval) { self.value = value expiresAt = .init(timeIntervalSinceNow: expireAfter) task = mainAsyncAfter(ms: (expireAfter * 1000).intround) { [self] in self.value = !value } }即:按键瞬间把pressedBrightnessKey置为true,同时记录过期时刻expiresAt,并调度一个主线程延迟任务,在expireAfter秒后把值翻转为false。Sync 目标显示器侧的逻辑只需读取该标志位:窗口内为true,窗口外为false,从而精确控制 OSD 展示与同步动作的判定。
从源码结构看,ExpiringBool采用"定时任务 + 过期时间戳"双保险机制:即便延迟任务因主线程繁忙被推迟,expiresAt依然可作为判断依据,避免窗口被意外延长。
OSD 展示优化与 CLI 输出修复
OSD 仅在按键窗口内展示
结合 Hotkeys.swift 附近的showOSD闭包可以看出,OSD 展示逻辑会依据当前是否处于按键状态(pressedBrightnessKey/pressedContrastKey是否为true)来决定是否在目标显示器上弹出指示条。beta 2 将窗口从 0.5 秒放宽到 1 秒,实质上是让 OSD 判定对"事件传播较慢的系统"更加宽容,同时保证非用户触发的亮度波动仍然不会误弹 OSD。
修复:CLI 输出误打印SentryCrashExceptionApplication
Lunar 内置了命令行接口(Lunar/Data/CLI.swift,基于 Swift ArgumentParser),可脱离 GUI 直接查询和控制显示器。此前在 CLI 输出中会误打印SentryCrashExceptionApplication—— 这是 Crash 监控框架 Sentry 相关的一个占位类名。
在 Lunar/AppDelegate.swift 中,SentryCrashExceptionApplication.self仅作为服务器模式下注册的异常处理器类型存在,它与显示器属性查询、亮度控制毫无关系。本次修复将其从 CLI 输出中剔除,避免在终端输出中暴露与业务无关的框架内部类名,属于输出清理类修复。该修复与 6.7.5 正式版说明(ReleaseNotes/6.7.5.md)一致。
更直观的 Nits 范围计算
Nits 与亮度百分比映射
在 Sync Mode 下,Lunar 需要把"源显示器的亮度值"映射为"目标显示器的亮度值"。由于不同显示器的亮度特性(最大 Nits、最小 Nits)不同,Lunar 使用 Nits(尼特,亮度单位)作为中间统一度量:先把源显示器的亮度百分比转换为 Nits,再把 Nits 转换为目标显示器对应的亮度百分比。
这个映射依赖每个显示器的minNits和maxNits(见 Lunar/Data/Display.swift 与 Lunar/Data/Display.swift):
minNits默认 0,maxNits默认 500,均带边界校验:minNits必须小于maxNits且不小于 0;maxNits必须大于minNits、大于 0 且不超过 3000;- 用户可通过
userMinNits/userMaxNits覆盖默认值(lazy 属性,见 Display.swift); - Nits 与 SDR 亮度百分比之间存在对数曲线关系:
sdrBrightnessToNits与nitsToSdrBrightness(见 Display.swift),低亮度段采用从 0 到 0.5 的线性映射、0.5 到 1.0 之间采用 140 Nits 到maxNits的对数曲线; - 映射表
nitsToPercentageMapping通过recomputeNitsMapping()重建(见 Display.swift),minNits/maxNits变化时通过nitsRecomputePublisher(500ms 节流)触发computeBrightnessSplines()/computeContrastSplines()重算(见 Display.swift)。
beta 2 的改进点
"更直观的 Nits 范围计算"指的是对上述映射与范围推算逻辑的简化:让用户通过界面(如 Lunar/SwiftUIViews/NitsTextField.swift,其中SyncMode.isUsingNits()决定是否展示 Nits 输入框)修改的 Min/Max Nits 值,能更直接、更可预期地反映到实际亮度映射结果上,避免中间换算产生反直觉的跳变。
在 NitsTextField.swift 中可以看到,用户输入后会写入display.minNits/display.maxNits(同时清空userMaxNits覆盖)并发送nitsEditPublisher事件,触发映射重算——这正是"范围计算"对用户可见的入口。beta 2 的优化使该入口产出的映射曲线更平滑、更贴近用户的直觉预期。
如何在你的环境中验证这些改动
如果你使用 Lunar 6.7.5b2(或更新版本)并启用 Sync Mode,可以通过以下方式验证与调优:
- 确认 Sync Mode 生效:在 Lunar 设置中切换到 Sync 模式,让一台显示器(通常为 Mac 内置屏)作为 Sync 源,外接显示器作为 Sync 目标;
- 验证 OSD 窗口:连续按内置屏亮度调节键,观察外接显示器 OSD 的弹出行为是否跟手、不闪烁;
- 调优隐藏设置:若外接显示器 OSD 经常不出现或同步滞后,执行
defaults write fyi.lunar.Lunar syncModeBrightnessKeyPressedExpireSeconds 1.5(按需增大)并重启 Lunar; - 验证 Nits 范围:在外接显示器的高级设置中调整 Min/Max Nits,观察亮度映射曲线(
BrightnessContrastChartView)的变化是否符合预期; - CLI 检查:通过 Lunar CLI 查询显示器状态,确认输出中不再包含
SentryCrashExceptionApplication等无关类名。
总结
Lunar 6.7.5b2 是一次围绕 Sync Mode 体验的精准打磨:
- 平滑化:重排并优化了从 Sync 源亮度变化到目标亮度适应的代码路径,减少跳变与抖动;
- 可配置窗口:新增
syncModeBrightnessKeyPressedExpireSeconds隐藏设置,将 OSD 展示判定窗口从固定的 0.5 秒提升为默认 1 秒,并允许用户按系统延迟情况自由调节,其实现依托ExpiringBool的过期标志机制(Lunar/Data/Util.swift); - 输出清理:修复 CLI 误打印
SentryCrashExceptionApplication,让终端输出更干净; - Nits 范围直观化:简化 Nits 与亮度百分比的映射计算,使用户对 Min/Max Nits 的调整更可预期(涉及 Lunar/Data/Display.swift 与 Lunar/SwiftUIViews/NitsTextField.swift)。
对于多显示器用户而言,理解syncModeBrightnessKeyPressedExpireSeconds这一隐藏设置尤其重要——它是解决"Sync 目标显示器 OSD 不跟随、同步不跟手"问题的第一抓手。
- 桌面应用
【免费下载链接】Lunar
Intelligent adaptive brightness for your external monitors
相关推荐
Lunar 6.7.2 beta 2 详解:CLI 主显示器设置、nits 化 Sync Mode 与参考预设亮度锁定的源码级实践
Lunar 6.7.2 beta 2 详解:CLI 主显示器设置、nits 化 Sync Mode 与参考预设亮度锁定的源码级实践 Lunar 是 macOS
桌面应用Lunar 6.7.2 更新深度解析:CLI 主显示器设置、Preset 亮度锁定与 nits 精度增强
Lunar 6.7.2 更新深度解析:CLI 主显示器设置、Preset 亮度锁定与 nits 精度增强 本文基于 Lunar 开源仓库 ReleaseNote
桌面应用Lunar显示器亮度优化终极指南:如何配置最适合你的亮度和对比度范围
Lunar显示器亮度优化终极指南:如何配置最适合你的亮度和对比度范围 Lunar是一款智能自适应亮度调节工具,专为外接显示器设计,能够根据环境光线自动调整屏幕亮
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考