
桌面应用【免费下载链接】LunarIntelligent adaptive brightness for your external monitors项目地址https://gitcode.com/gh_mirrors/lu/Lunar点击查看免费下载Lunar 6.9.7 是一次面向 macOS 26.3 兼容性与稳定性的修复版本核心包含两项变更移除在 macOS 26.3 及以后系统上已失效的Unlock preset预设解锁功能以及修复应用挂起hang检测时无法获取正确调用栈的问题。本文以 ReleaseNotes/6.9.7.md 为主干结合仓库中 LunarShortcuts/LunarShortcuts.swift、Lunar/ErrorReports.swift、Lunar/AppDelegate.swift 等源码讲解这两项变更的前因后果与实现原理。读完本文你将理解参考预设为何会被 macOS 锁定、Lunar 的预设解锁能力如何实现、为何 26.3 后必须移除以及挂起检测的采样、归因与自动重启机制如何工作。背景macOS 26.3 系统限制与 Lunar 的兼容性策略Lunar 是面向外接显示器的智能亮度调节工具参考预设reference presets如Design Print、Photography等由 macOS 提供用于为专业显示场景锁定特定的色彩与亮度行为。在使用参考预设时macOS 会一并锁定亮度控制、自适应亮度、Night Shift、True Tone 等功能避免用户在专业预设下误改显示参数。为了打破这种限制Lunar 此前提供了一套预设解锁preset unlocking能力在应用参考预设的同时解锁被 macOS 锁定的亮度控制、自适应亮度、Night Shift 与 True Tone。这一功能从 6.5.0 版本引入见 CHANGELOG.md 中的 “Preset unlocking” 一节。然而到了 macOS 26.3系统在底层封堵了这套解锁机制相关调用不再生效。Lunar 的开发策略是既然该功能在 26.3 及以后已无法工作就将其从功能入口移除而不是保留一个“看似可用实则失效”的开关避免用户产生困惑。仓库中的版本判断常量印证了这一点// Lunar/AppDelegate.swift#L3069-L3070 let MAC26 if #available(macOS 26, *) { true } else { false } let MAC26POINT3 if #available(macOS 26.3, *) { true } else { false }该功能移除涉及的三层入口在后续小节逐一展开。变更一移除 macOS 26.3 上的 Unlock preset 功能从 UI 侧隐藏解锁入口在显示列表行视图中当显示器处于参考预设锁定状态时会渲染lockedPresetView其中有一个用于解锁当前预设的菜单项。6.9.7 用MAC26POINT3条件将其隐藏// Lunar/SwiftUIViews/DisplayRowView.swift#L670-L679 if let name display.referencePreset?.presetName { Menu(name) { if !MAC26POINT3 { SwiftUI.Button(Unlock \\(name)\) { display.panel?.unlockActivePreset() } Divider() } // ... 其余参考预设列表仍可正常切换 } }即在 macOS 26.3 之前用户可以点击Unlock xxx调用unlockActivePreset()解锁当前激活的参考预设在 26.3 及以后该入口被整体隐藏仅保留参考预设的浏览与切换能力activatePreset确保用户不会误以为解锁仍然可用。从 Shortcuts 侧标注参数不可用Lunar 的 “Change Screen Preset” 快捷指令SetPanelPresetIntent此前支持通过 4 个开关参数解锁对应功能并附带亮度上下限// LunarShortcuts/LunarShortcuts.swift#L2708-L2724 Parameter(title: Unlock brightness control (unavailable on macOS 26.3 and later)) var unlockBrightnessControl: Bool Parameter(title: Unlock Adaptive Brightness (unavailable on macOS 26.3 and later)) var unlockAdaptiveBrightness: Bool Parameter(title: Unlock Night Shift (unavailable on macOS 26.3 and later)) var unlockNightShift: Bool Parameter(title: Unlock True Tone (unavailable on macOS 26.3 and later)) var unlockTrueTone: Bool Parameter(title: Min brightness (in nits) (unavailable on macOS 26.3 and later), default: 4, inclusiveRange: (1, 500)) var minBrightness: Int Parameter(title: Max brightness (in nits) (unavailable on macOS 26.3 and later), default: 500, inclusiveRange: (50, 500)) var maxBrightness: Int各参数含义与默认取值整理如下参数说明默认值取值范围unlockBrightnessControl解锁亮度控制false布尔unlockAdaptiveBrightness解锁自适应亮度false布尔unlockNightShift解锁 Night Shiftfalse布尔unlockTrueTone解锁 True Tonefalse布尔minBrightness解锁后的最小亮度nits41–500maxBrightness解锁后的最大亮度nits50050–500所有解锁相关参数的标题中都明确标注了 “unavailable on macOS 26.3 and later”提醒快捷指令的创建者在 26.3 上这些开关不再生效。执行逻辑解锁失败时安全回退到普通预设perform()中只有当至少勾选了unlockBrightnessControl、unlockNightShift、unlockTrueTone之一时才尝试解锁解锁成功后直接激活预设失败则回退为普通activatePreset(preset)并顺手关闭 Full Range XDR 状态// LunarShortcuts/LunarShortcuts.swift#L2738-L2754 let unlock unlockBrightnessControl || unlockNightShift || unlockTrueTone display.reconfigure { panel in guard unlock, !preset.isUnlocked, preset.unlock( activate: true, brightnessControl: unlockBrightnessControl, adaptiveBrightness: unlockAdaptiveBrightness, nightShift: unlockNightShift, trueTone: unlockTrueTone, minBrightness: minBrightness.d.capped(between: 1, and: maxBrightness.d).i, maxBrightness: maxBrightness.d.capped(between: minBrightness.d, and: 500).i ) else { if display.fullRange { display.fullRange false } panel.activatePreset(preset) return } }其中minBrightness被约束在1与maxBrightness之间、maxBrightness被约束在minBrightness与500之间capped(between:and:)保证传入底层时的参数恒有效。unlock(activate:...)是 macOS 私有框架PanelKit/DisplayServices提供的 API当系统版本升至 26.3、底层行为被封堵后该调用不再生效这正是 6.9.7 移除该功能的技术根因。预设数据来源与解锁能力查询快捷指令的预设列表来自PanelPresetQuery它通过DC.activeDisplayList.filter { $0.panel?.hasPresets ?? false }筛选出带预设的显示器用buildPresetsList()刷新预设列表再按presetGroup分组返回见 LunarShortcuts/LunarShortcuts.swift。显示侧的数据同步逻辑位于 Lunar/Data/Display.swift 的refetchPanelPresetProps()调用panel.buildPresetsList()后写入panelPresets与referencePreset并读取panel.supportsBrightnessControl判断当前预设是否锁定了亮度控制。变更二修复挂起检测取不到正确调用栈的问题挂起检测的整体设计Lunar 从 6.9.6 引入了全新的应用挂起检测与缓解逻辑CHANGELOG.md并在 6.8.x 时将检测窗口从 60 秒调整为 40 秒以避免频繁误重启CHANGELOG.md。整个机制集中在 Lunar/ErrorReports.swift核心思路是主线程心跳 定时巡检 采样归因 自动重启。// Lunar/ErrorReports.swift#L7-L8 private let APP_HANG_DETECTION_INTERVAL: TimeInterval 40.0 private let APP_HANG_CHECK_INTERVAL: TimeInterval 1.0主线程心跳与“不再响应”判定configureAppHangDetection()创建了一个每秒触发一次的DispatchSourceTimer每次触发时更新lastMainThreadCheckin通过主队列async与RunLoop.main.perform两种途径打入覆盖.modalPanel、.eventTracking、.default、.common等运行模式随后判断// Lunar/ErrorReports.swift#L171-L196 if !appHangTriggered, !sleeping, now - lastMainThreadCheckin APP_HANG_DETECTION_INTERVAL { appHangTriggered true shouldTrigger true }只要主线程在 40 秒内无法完成一次心跳说明主线程被长时间阻塞、事件循环不再运转就判定为挂起并触发onAppHangDetected()。同时监听系统睡眠/唤醒通知NSWorkspace.willSleepNotification/didWakeNotification睡眠期间暂停判定唤醒后重置状态避免把系统睡眠误判为挂起。6.9.7 的核心修复用 sample 提取主线程调用栈onAppHangDetected()的调用栈采样实现正是本次修复点。旧实现从 6.9.6 起因调用方式不正确拿不到主线程的真实调用栈6.9.7 改为直接调用系统自带的进程采样工具/usr/bin/sample对当前进程采样极短时间0.001秒再从输出中精确截取主线程段// Lunar/ErrorReports.swift#L218-L242 private func sampleMainThread() - String? { let result shell(/usr/bin/sample, args: [\(getpid()), 0.001], timeout: 5.seconds) guard let output result.o, result.success else { return nil } var mainThreadLines: [String] [] var inMainThread false for line in output.components(separatedBy: \n) { if line.contains(com.apple.main-thread) { inMainThread true mainThreadLines.append(line) continue } guard inMainThread else { continue } let trimmed line.trimmingCharacters(in: .whitespaces) if trimmed.isEmpty || line.contains(Thread_) || trimmed.hasPrefix(Total number) || trimmed.hasPrefix(Sort by) { break } mainThreadLines.append(line) } return mainThreadLines.isEmpty ? nil : mainThreadLines.joined(separator: \n) }解析逻辑以com.apple.main-thread标记为起点遇到下一个线程段Thread_或 sample 输出的统计区Total number、Sort by即停止从而提取出准确的当前主线程调用栈。这个调用栈随后会写入日志供排查挂起根因使用。挂起归因识别已知肇事框架拿到正确的主线程调用栈后Lunar 会将其与已知的“肇事框架”比对归因并记录。RepeatingHangState维护了两类已知挂起原因见 Lunar/ErrorReports.swift归因标识cause期望栈帧expectedStackFrame触发场景audio-deviceAudio音频设备相关CoreAudio挂起core-locationCLConnectionServer定位服务Location Services初始化挂起归因判定逻辑在 Lunar/ErrorReports.swift 的isCulprit(mainThreadStack:)只要采样得到的调用栈包含期望栈帧字符串就认定该次挂起由对应模块引起。这正是 6.9.7 修复的价值所在——没有正确的主线程调用栈归因永远无法命中也就无法触发后续的针对性缓解。缓解措施自动重启与重复挂起降级触发挂起后若用户开启了autoRestartOnHang开关Lunar 会记录归因时间戳并在 5 秒后自动重启// Lunar/ErrorReports.swift#L254-L265 if Defaults[.autoRestartOnHang] { // 记录本次挂起的归因时间戳 if let hang RepeatingHangState.hangs.values.first(where: { $0.isCulprit(mainThreadStack: mainThreadStack) }) { RepeatingHangState.record(cause: hang.cause, at: now) } log.warning(Auto-restarting app due to hang detection.) concurrentQueue.asyncAfter(ms: 5000) { restart(hang: true) } }RepeatingHangState将归因时间戳以 JSON 形式持久化在临时目录lunar_hang_causes.json仅在5 分钟窗口内保留窗口内同一原因挂起3 次即视为重复挂起// Lunar/ErrorReports.swift#L55-L58 static let window: TimeInterval 5 * 60 static let threshold 3 static let fileName lunar_hang_causes.json重复挂起会触发降级缓解定位服务coreLocation.exceedsThreshold为真时在configureAppHangDetection()中强制Defaults[.manualLocation] trueLunar/ErrorReports.swift即关闭自动定位、改用手动定位模式从根源上绕开挂起模块。这也与 6.5.0 修复的 “app hanging when initializing Location Services” 问题一脉相承CHANGELOG.md音频设备defaultAudioOutputDevice在defaultAudioOutput.exceedsThreshold时返回nilLunar/ErrorReports.swift即停止查询默认输出设备绕过 CoreAudio 挂起点。重启保护防止重启风暴restart(hang:)通过命令行参数restartstimestamp:timestamp:timestamp传递历史重启时间戳若1 分钟内重启超过 3 次则直接退出防止系统陷入无限重启循环Lunar/AppDelegate.swift。这一防护同时服务于挂起重启与崩溃重启restartOnCrash()中注册的SIGABRT、SIGSEGV等信号处理均复用该逻辑见 Lunar/AppDelegate.swift。用户视角的影响与应对建议macOS 26.3 及以后参考预设解锁入口UI 菜单、快捷指令解锁参数均已被移除。若你的显示器处于参考预设锁定状态只能通过切换其他预设来改变显示行为建议继续使用 Lunar 的常规亮度控制DDC/Apple Native/Gamma来调节已解锁的显示器。挂起检测默认情况下挂起检测每 40 秒判定一次检测到挂起后日志中会包含通过/usr/bin/sample采样得到的主线程调用栈autoRestartOnHang开启时 5 秒后自动重启同一原因在 5 分钟内触发 3 次会触发针对定位或音频模块的自动降级。小结6.9.7 的两个修复分别对应“跟随系统收敛能力”与“增强自身可观测性”两条主线Unlock preset 因 macOS 26.3 封堵底层 API 而被干净移除体现了对系统版本变更的及时适配挂起检测的调用栈修复则让“检测 → 归因 → 降级/重启”这条缓解链路真正闭环——只有拿到正确的主线程调用栈Lunar 才能准确识别挂起来自音频设备还是定位服务并作出对应的自愈动作。对于需要在 macOS 26.3 上长期运行 Lunar 的用户理解这两处变更有助于正确预期功能边界也能在排查挂起问题时读懂日志中的调用栈信息。赞分享桌面应用【免费下载链接】LunarIntelligent adaptive brightness for your external monitors项目地址https://gitcode.com/gh_mirrors/lu/Lunar点击查看免费下载相关推荐Lunar 6.7.2b3 解析应用预设与参考预设两大修复背后的实现机制Lunar 6.7.2b3 解析应用预设与参考预设两大修复背后的实现机制 本篇技术指南聚焦 LunarIntelligent adaptive bright桌面应用Lunar 6.8.6 修复解析HDR 亮度指令挂起、macOS Sequoia 崩溃与 lunar listen 的 nits 事件Lunar 6.8.6 修复解析HDR 亮度指令挂起、macOS Sequoia 崩溃与 lunar listen 的 nits 事件 导读 Lunar 6.桌面应用remark42 测试死锁实录TestRest_securityHeaders 在 macOS 上挂起整个 API 套件的根因与修复remark42 测试死锁实录TestRest_securityHeaders 在 macOS 上挂起整个 API 套件的根因与修复 本篇技术指南围绕 rem后端前端上一篇GBFR-Logs DPS统计碧蓝幻想Relink伤害分析完整指南下一篇显卡驱动彻底卸载指南DDU 四步清理残留创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考