Lunar 6.3.0 beta 1 技术解析:外部显示器环境光传感器接入与 Auto XDR 稳定性优化

原创2026-10-15 19:39:131,610 阅读
文章标签:桌面应用硬件开发

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 --listen as 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 次读数窗口的实现证据

窗口平均值的两个直接证据:

  1. Lunar/Data/CLI.swift 中 --average 标志的 help 文本明确写着:"Get the window average of the last 15 lux readings"(最近 15 次 lux 读数的窗口平均值);
  2. 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,锁定状态下隐藏刻度块属于既有设计。

如何体验这些改动

  1. 获取 beta 版本:从 Lunar 官方发布渠道下载 6.3.0 beta 1 或更高版本,替换当前稳定版(安装前建议备份当前设置,设置由 Defaults 持久化,见 Lunar/Data/DataStore.swift 的 APP_SETTINGS 列表)。
  2. 验证外部传感器优先:连接 Apple 自家外部显示器(或 TSL2591 等 USB 传感器)后运行 lunar lux,确认输出为外部 lux 而非 -1。
  3. 验证窗口平均值:运行 lunar lux --listen --average,观察输出波动是否比 lunar lux --listen 更平滑,验证 15 次滑动窗口的平滑效果。
  4. 体验 Auto XDR:在设置界面(Lunar/SwiftUIViews/HDRSettingsView.swift)开启 "Toggle XDR Brightness based on ambient light",调节 lux 阈值滑块(默认 XDR_DEFAULT_LUX,上限 XDR_MAX_LUX),并观察 MacBook XDR 内置屏是否按环境光平滑切换。
  5. 验证 Sonoma 修复:在 Sonoma 上热插拔外部显示器后调节亮度,观察 OSD 刻度块是否稳定出现、不再偶发消失;拔线后重新连接的显示器是否在"可能断开"列表中被正确回收。

说明:本文涉及的具体阈值常量(XDR_DEFAULT_LUX、XDR_MAX_LUX、XDR_LUX_LEAST_NONZERO)及 Auto XDR 调度细节,部分位于受保护源码(.secret 文件)中,本文仅依据可见源码与文档给出可验证的行为描述;文中标注的实现路径与行号均来自本仓库当前内容,可直接跳转核对。

登录后查看全文
Lunar