☰
Lunar 6.7.5b2 技术解析:更平滑的 Sync Mode 亮度适应、OSD 展示窗口与 Nits 范围计算优化
2026/10/15 8:05:03 网站建设 项目流程
  • 桌面应用

【免费下载链接】Lunar

Intelligent adaptive brightness for your external monitors

项目地址:https://gitcode.com/gh_mirrors/lu/Lunar
点击查看免费下载

本文基于 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)时,会执行两类操作:

  1. 标记按键事件:调用DC.pressedBrightnessKey.true(for: DC.syncModeBrightnessKeyPressedExpireSeconds)(见 Hotkeys.swift),表明"刚发生过亮度键按压"这一事实,并让它在一段时间后自动失效;
  2. 执行亮度增减:调用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 thesyncModeBrightnessKeyPressedExpireSecondshidden 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,可以通过以下方式验证与调优:

  1. 确认 Sync Mode 生效:在 Lunar 设置中切换到 Sync 模式,让一台显示器(通常为 Mac 内置屏)作为 Sync 源,外接显示器作为 Sync 目标;
  2. 验证 OSD 窗口:连续按内置屏亮度调节键,观察外接显示器 OSD 的弹出行为是否跟手、不闪烁;
  3. 调优隐藏设置:若外接显示器 OSD 经常不出现或同步滞后,执行defaults write fyi.lunar.Lunar syncModeBrightnessKeyPressedExpireSeconds 1.5(按需增大)并重启 Lunar;
  4. 验证 Nits 范围:在外接显示器的高级设置中调整 Min/Max Nits,观察亮度映射曲线(BrightnessContrastChartView)的变化是否符合预期;
  5. 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

项目地址:https://gitcode.com/gh_mirrors/lu/Lunar
点击查看免费下载

相关推荐

上一篇:Windows系统依赖问题终极解决方案:Visual C++运行库一键修复全攻略
下一篇:Logstash 已知问题全解析:9.3.0 / 9.2.5 捆绑 JDK 平台不兼容与 9.2.0 三大缺陷的识别与规避

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询