Lunar 6.3.0 beta 1 技术解析:外部显示器环境光传感器接入与 Auto XDR 稳定性优化
Lunar 6.3.0 beta 1 技术解析:外部显示器环境光传感器接入与 Auto XDR 稳定性优化
本篇技术指南围绕 Lunar 6.3.0 beta 1(ReleaseNotes/6.3.0b1.md)的完整改动清单展开,逐项解读该版本引入的三大方向:读取 Apple 自家外部显示器的 lux 传感器、Auto XDR 传感器响应的窗口平均化与 XDR Contrast 提升,以及 macOS Sonoma 下显示连接与 OSD 绘制问题的修复。读完本文,你将理解 Lunar 传感器模式的传感器优先级模型、lux 窗口平均值在自适应亮度中的应用方式,以及 Sonoma 兼容性修复背后的实现细节,并掌握用 lunar lux 命令实测验证这些改动的操作方法。
版本总览:6.3.0 beta 1 的改动结构
| 类别 | 改动内容 | 对应源码区域 |
|---|---|---|
| Features | 尽可能使用 Apple 自家外部显示器内置的 lux 传感器 | Lunar/Data/CLI.swift、SensorMode 外部/内部传感器状态 |
| Improvements | Auto XDR 适配时改用传感器 lux 的窗口平均值(15 次读数) | Lunar/Modes/AdaptiveMode.swift 的 luxWindowAverage |
| Improvements | 提高 XDR Contrast,为 Auto XDR 即将默认启用做准备 | Lunar/Control/AppleNativeControl.swift |
| Fixes | 规避 Sonoma 在显示器处于断开状态时获取显示信息所触发的连接 bug | Lunar/Utils/DisplayController.swift 的 possiblyDisconnectedDisplays |
| Fixes | 修复 Sonoma 上 OSD 刻度块(chiclets)偶尔消失的问题 | Lunar/Views/OSDWindow.swift 的 chiclets 视图 |
新特性:读取 Apple 外部显示器的 lux 传感器
传感器模式的传感器优先级模型
Lunar 的 Sensor 模式依赖 SensorMode 抽象出"外部传感器"与"内部传感器"两类环境光来源。从 Lunar/Data/CLI.swift 中 Lux 命令的读取逻辑可以看到清晰的优先级模型:
if SensorMode.specific.externalSensorAvailable, let lux = SensorMode.specific.lastAmbientLight {
cliPrint(lux)
} else {
cliPrint(average ? (SensorMode.computeLuxWindowAverage() ?? -1.0) : (SensorMode.getInternalSensorLux() ?? -1.0))
}
- 外部传感器优先:只要
externalSensorAvailable为真且能读到lastAmbientLight,就直接输出外部 lux; - 内部传感器兜底:外部传感器不可用时,回退到
getInternalSensorLux()(例如 MacBook 内置屏的环境光传感器); - 同样的优先级判断也出现在 Lunar/Utils/DisplayController.swift 中(
ifExternalSensorAvailable()与ifInternalSensorAvailable()),说明这是贯穿整个 Sensor 模式的选择逻辑。
6.3.0 beta 1 的增量:Apple 外部显示器的 lux 传感器
本版本 Features 项的实质内容是:当外部显示器由 Apple 自家制造(如 Apple Studio Display、Pro Display XDR 等)且其固件提供了可读取的环境光传感器数据时,Lunar 会尝试直接读取该显示器的 lux 传感器,作为 Sensor 模式或 Auto XDR 判断环境亮度的输入来源。这一改动扩大了"外部传感器"的可用范围——此前外部传感器主要依赖用户自购的 USB 环境光传感器硬件(如 TSL2591、TSL2561、BH1750 等,配置见 Lunar/ALS/tsl2591.yaml),而 Apple 显示器本身自带传感器,可免去额外硬件。
从代码结构看,SensorMode 维护了 externalSensorAvailable、lastAmbientLight、lastExternalAmbientLight 等状态(Lunar/AppDelegate.swift 中也存在 SensorMode.specific.lastExternalAmbientLight 的引用),可以推断本版本让"Apple 外部显示器 lux 传感器"被纳入到这套外部传感器通道中,与 USB 传感器统一走 externalSensorAvailable 的优先级分支。具体读取实现位于受保护的源码中,但外部优先、内部兜底的模型与 CLI 行为是明确可验证的。
用 CLI 实测:lunar lux
对于任何环境光来源,都可以通过 Lunar 内置 CLI 直接读取当前 lux 值(Lunar/Data/CLI.swift):
# 读取当前环境光(外部传感器可用时优先;否则读取内部传感器)
lunar lux
# 持续监听 lux 变化
lunar lux --listen
# 读取最近 15 次读数的窗口平均值
lunar lux --average
- 传感器不可读时返回
-1(命令 help 文本原话:"or -1 if the sensor can't be read"); --listen模式下数据通过 Lunar 内置 socket 服务持续推送,可配合脚本观察环境光变化曲线;--average可以同时与--listen组合使用(CLI help 明确说明 "can be used with--listenas well"),此时推送的将是窗口平均值而非瞬时值。
改进:Auto XDR 传感器响应改用窗口平均值
为什么需要窗口平均值
Auto XDR 是 Lunar 依据环境光自动切换 MacBook 内置屏 XDR 亮度档位的功能(设置项 autoXdrSensor、autoXdrSensorLuxThreshold 等定义于 Lunar/Data/DataStore.swift)。如果直接用瞬时 lux 判断,传感器噪声、灯光闪烁、短暂遮挡都可能导致 XDR 反复开关(抖动)。6.3.0 beta 1 的改进项正是针对这一点:适配 Auto XDR 时使用传感器 lux 的窗口平均值而非最后一次瞬时读数。
15 次读数窗口的实现证据
窗口平均值的两个直接证据:
- Lunar/Data/CLI.swift 中
--average标志的 help 文本明确写着:"Get the window average of the last 15 lux readings"(最近 15 次 lux 读数的窗口平均值); - Lunar/ALS/tsl2591.yaml 中官方 TSL2591 传感器配置示例同样采用
sliding_window_moving_average且window_size: 15,与 CLI 的 15 次窗口一致。
在 SwiftUI 侧,Lunar/Modes/AdaptiveMode.swift 的 AdaptiveModeInfo 同时暴露两个发布属性:
@Published var lux: Double? // 瞬时 lux
@Published var luxWindowAverage: Double? // 窗口平均 lux
CLI 的 --listen 模式正是分别订阅 AMI.$lux 与 AMI.$luxWindowAverage(Lunar/Data/CLI.swift),说明窗口平均值已作为一级数据源接入实时推送链路。因此 Auto XDR 基于窗口平均值做判断后,瞬时噪声会被平滑掉,切换行为更稳定。
Auto XDR 的传感器调度链路
Auto XDR 的传感器读取与任务调度集中在 Lunar/Utils/DisplayController.swift 的 setupXdrTask():
autoXdrSensorPublisher.debounce(for: .seconds(1), scheduler: RunLoop.main).sink { change in
autoXdrSensor = change.newValue
guard let display = builtinDisplay, display.isMacBookXDR, let lux = SensorMode.getInternalSensorLux() else {
xdrSensorTask = nil
return
}
internalSensorLux = lux.f
xdrSensorTask = change.newValue ? getSensorTask() : nil
}
- 触发条件:存在内置屏、
isMacBookXDR(即 MacBook 且支持 Enhance/XDR),且能读到内部传感器 lux; - 读取结果写入
internalSensorLux供 UI 显示(Lunar/SwiftUIViews/HDRSettingsView.swift 中"Current ambient light: XX lux"即来源于此); - 开启开关后启动
xdrSensorTask周期任务(Repeater),并在显示列表变化时重建(Lunar/Utils/DisplayController.swift)。
用户可调阈值 autoXdrSensorLuxThreshold 表示"环境光高于多少 lux 时自动开启 XDR",Lunar/SwiftUIViews/HDRSettingsView.swift 中提供了带幂次映射的滑块(powf($0, 4) * XDR_MAX_LUX + XDR_LUX_LEAST_NONZERO)与一键恢复默认值(Reset 按钮恢复为 XDR_DEFAULT_LUX)的入口。
改进:提高 XDR Contrast,为 Auto XDR 就绪做准备
6.3.0 beta 1 的第二个改进项是在对比度层面做铺垫:调高 XDR 下的对比度增强幅度,为 Auto XDR 即将正式默认启用做准备。
XDR 对比度由 xdrContrastFactor(设置键注册于 Lunar/Data/DataStore.swift)控制,默认重置值为 0.3(Lunar/SwiftUIViews/HDRSettingsView.swift 中 Reset 按钮写入 xdrContrastFactor = 0.3)。对比度提升的实际作用区间位于 600–1600 nits 的 XDR 亮度段,见 Lunar/Control/AppleNativeControl.swift:
let xdrContrast = display.computeXDRContrast(
xdrBrightness: nits.f.capped(between: 600, and: 1600),
xdrContrastFactor: CachedDefaults[.xdrContrastFactor] + 0.3,
minBrightness: 600, maxBrightness: 1600, gamma: 2.2
)
if xdrContrast != DC.xdrContrast {
DC.setXDRContrast(xdrContrast)
}
- 亮度被限制在 600–1600 nits 区间内计算;
- 实际生效的对比度因子为
xdrContrastFactor + 0.3,即 beta 1 在用户配置值之上额外叠加 0.3 的提升量,这正是"increase XDR Contrast"的具体实现; - 计算得到的
xdrContrast与当前值不一致时通过DC.setXDRContrast平滑写入,避免对比度跳变。
从代码路径看,这一改动与 xdrContrast 开关、xdrContrastFactor 参数共同构成 XDR 对比度控制链路;配合 Auto XDR 的传感器判定(Lunar/Utils/DisplayController.swift 的 autoXdrAmbientLightPublisher 最终调用 d.enhanced = xdrEnabled 并更新 xdrState),对比度提升为 XDR 切换后的画面表现提前做了校准。
修复一:规避 Sonoma 显示连接 bug
macOS 14 Sonoma 引入了与显示连接状态相关的系统级问题:当显示器处于断开状态(disconnected)时,如果应用尝试获取其显示信息,可能触发显示连接 bug。Lunar 6.3.0 beta 1 的修复思路是:在获取显示器信息前先识别"可能断开但仍保留配置"的显示器,避免在错误时机访问系统显示服务。
对应的实现载体是 Lunar/Utils/DisplayController.swift 中的 possiblyDisconnectedDisplays 字典:
static var possiblyDisconnectedDisplays: [CGDirectDisplayID: Display] = [:] {
didSet {
debug("possiblyDisconnectedDisplays: \(DisplayController.possiblyDisconnectedDisplays.keys.sorted())")
DC.possiblyDisconnectedDisplayList = DisplayController.possiblyDisconnectedDisplays.values.sorted(by: \.id)
}
}
- 该集合维护"可能已断开连接"的显示器及其保留的 Display 对象,序列化到用户默认设置(
Defaults<a href="https://link.gitcode.com/i/7253fa43995b3125c32d482ff2662cc8" target="_blank">.possiblyDisconnectedDisplays],见 [Lunar/Utils/DisplayController.swift),重启后仍能恢复识别; - 当显示器重新出现在活动列表(
activeDisplays)时,按其 serial 从"可能断开"集合中移除(Lunar/Utils/DisplayController.swift),完成状态回收; - Auto Blackout 等流程也会检查该集合,避免对可能断开的显示器执行误操作(Lunar/Utils/DisplayController.swift)。
从源码结构看,该集合配合"断开时跳过实时信息抓取、仅在安全时机刷新"的策略,构成了对 Sonoma 连接 bug 的规避机制。possiblyDisconnectedDisplays 仅作用于 ARM 架构分支(#if arch(arm64) 包裹),符合 Apple Silicon 上通过 DCP/AVService 管理显示器的事实。
修复二:Sonoma 上 OSD 刻度块偶尔消失
OSD(On-Screen Display)是调节亮度/对比度时屏幕中央出现的悬浮指示条。6.3.0 beta 1 修复了 Sonoma 上 OSD 的刻度块(chiclets)偶尔完全消失的问题。
chiclets 视图实现在 Lunar/Views/OSDWindow.swift:OSD 底部以一条虚线"刻度尺"表示当前值位置,由底层实线轨道 + 上层虚线覆盖层合成:
var chiclets: some View {
ZStack {
Path { path in
path.move(to: CGPoint(x: 0.0, y: 0))
path.addLine(to: CGPoint(x: 161, y: 0))
}
.stroke(style: StrokeStyle(lineWidth: 8))
.foregroundColor(Color.black)
Path { path in
path.move(to: CGPoint(x: 1, y: 0))
path.addLine(to: CGPoint(x: value, y: 0))
}
.stroke(.black, style: StrokeStyle(lineWidth: 6, dash: [9, 1]))
.blendMode(.destinationOut)
...
}
.compositingGroup()
}
绘制要点:
- 轨道长度固定为 161pt、线宽 8pt 的黑色底轨;
- 上层用
dash: [9, 1]的虚线 stroke 绘制"刻度块"效果,并通过.destinationOut混合模式在底轨上"挖出"刻度槽位; - 整个 ZStack 应用
.compositingGroup()将内容视为单一合成层,再配合.animation(.easeOut(duration: 0.15))实现平滑推进。
Sonoma 上该图层偶发消失,与 AppKit/SwiftUI 在 macOS 14 的离屏渲染合成行为变化相关;beta 1 的修复即针对这一绘制链路做了规避(将渲染稳定在 compositingGroup 合成组语义上),确保 OSD 调节时刻度块稳定显示。OSD 显示入口(square 视图,Lunar/Views/OSDWindow.swift)在 !osd.locked 时才渲染 chiclets,锁定状态下隐藏刻度块属于既有设计。
如何体验这些改动
- 获取 beta 版本:从 Lunar 官方发布渠道下载 6.3.0 beta 1 或更高版本,替换当前稳定版(安装前建议备份当前设置,设置由
Defaults持久化,见 Lunar/Data/DataStore.swift 的APP_SETTINGS列表)。 - 验证外部传感器优先:连接 Apple 自家外部显示器(或 TSL2591 等 USB 传感器)后运行
lunar lux,确认输出为外部 lux 而非 -1。 - 验证窗口平均值:运行
lunar lux --listen --average,观察输出波动是否比lunar lux --listen更平滑,验证 15 次滑动窗口的平滑效果。 - 体验 Auto XDR:在设置界面(Lunar/SwiftUIViews/HDRSettingsView.swift)开启 "Toggle XDR Brightness based on ambient light",调节 lux 阈值滑块(默认
XDR_DEFAULT_LUX,上限XDR_MAX_LUX),并观察 MacBook XDR 内置屏是否按环境光平滑切换。 - 验证 Sonoma 修复:在 Sonoma 上热插拔外部显示器后调节亮度,观察 OSD 刻度块是否稳定出现、不再偶发消失;拔线后重新连接的显示器是否在"可能断开"列表中被正确回收。
说明:本文涉及的具体阈值常量(
XDR_DEFAULT_LUX、XDR_MAX_LUX、XDR_LUX_LEAST_NONZERO)及 Auto XDR 调度细节,部分位于受保护源码(.secret文件)中,本文仅依据可见源码与文档给出可验证的行为描述;文中标注的实现路径与行号均来自本仓库当前内容,可直接跳转核对。