Fix: 修复 macOS 下主题模式「跟随系统设置」失效 - #6596
Conversation
HMCL-dev#5778 之后,HMCL 在 macOS 上会调用 NSApplication setAppearance: 把应用外观 固定为启动器自己的明暗模式,用来让文件选择器等原生窗口跟随启动器配色。 但 JavaFX 的 Platform.Preferences.colorScheme 正是由 NSApp.effectiveAppearance 推导出来的(PlatformSupport.m 对 effectiveAppearance 注册 KVO,再用它解析 NSColor,PreferenceProperties 比较前景色与背景色亮度得出明暗)。setAppearance: 改的就是 effectiveAppearance,因此 colorScheme 会变成启动器自己写入的值, 不再反映系统设置,形成自我反馈: FXUtils.DARK_MODE ← colorScheme ← effectiveAppearance ← 启动器写入的值 于是「跟随系统设置」读到的是启动器自己的明暗模式,切换该选项没有效果, 系统明暗模式变化后启动器也不再跟随。 修复分两处: - 在 macOS 上改为直接读取 AppleInterfaceStyle 用户默认项获取系统明暗模式。 该值反映系统设置,不受 setAppearance: 影响。FXUtils.DARK_MODE 仍作为 失效触发器保留,只是不再作为取值来源,因此重算会收敛。 - 仅在启动器明暗模式与系统不一致时固定应用外观;一致时清除外观,让应用 重新继承系统外观,JavaFX 得以继续观察系统变化。由于主题包可以通过 resolveCurrentThemeBrightness 覆盖明暗模式,这里比较的是已解析的 darkModeProperty 而非明暗模式设置项,以免主题包指定的外观被错误清除。 用户显式选择 light/dark 时外观仍会被固定,文件选择器配色行为不变。 改动仅在 macOS 分支内,Windows 与 Linux 的明暗模式检测路径未受影响。 Closes HMCL-dev#6526
|
补充端到端验证结果。我在 PR 描述末尾提到未在完整运行环境中验证绑定链的收敛,现已补上,并附未打补丁版本作为对照。 方法在 两者均以 JDK 25 启动,运行时下载 JavaFX 25( 日志代码仅用于本次验证,未包含在本 PR 中。 打补丁后系统外观切换 Dark → Light → Dark → Light → Dark,每次都触发一次,启动器明暗始终跟随系统: 明暗模式为默认的「跟随系统设置」,启动器明暗与系统一致,因此每次都是 未打补丁(对照)同样的操作,系统外观切换了三次,但监听器只在启动时触发过一次,之后再无任何记录: 这与问题描述一致,也说明了失效的具体环节:启动时 对照鲜明:同样三次系统切换,打补丁后触发 5 次并全部跟随,未打补丁触发 0 次。 环境macOS 26.5.2 / Apple Silicon (arm64) / JDK 25.0.3 (Temurin) / JavaFX 25+29 两个版本均从本 PR 的分支和未修改的 |
|
我怀疑本 PR 是由 AI 完全独立完成的,并没有经过你的审查。介于 HMCL 近期被 AI slop 骚扰的情况,我要求您提供相关证据证明本 PR 确实正常工作,或证明你已经充分审查了该 PR 的代码。 |
|
Duplicate to #6555 |
Closes #6526
问题是什么
在 macOS 上,主题的「跟随系统设置」是坏的:
为什么会坏
说起来有点绕,但根子上是一个「自己读自己」的循环。
HMCL 想知道「系统现在是浅色还是深色」,读的是 JavaFX 提供的
Platform.Preferences.colorScheme。而 JavaFX 这个值不是凭空来的,它是这么算出来的(
PlatformSupport.m/PreferenceProperties.java):NSApp.effectiveAppearance注册 KVO 监听问题就在这里:HMCL 自己会去改
NSApp.effectiveAppearance。#5778 为了让文件选择器这类原生窗口的配色跟着启动器走,加了
NSApplication setAppearance:调用。而setAppearance:改的正是effectiveAppearance—— JavaFX 拿来算明暗的那个属性。于是就成了这样:
HMCL 读到的「系统外观」,其实是它自己设定的外观。系统真正是深还是浅,它再也看不见了。
FXUtils.DARK_MODE同时承担了两个角色 —— 既是「值的来源」,又是「变化的通知源」。被污染的是前者,后者仍然正常。怎么改的
第一处:换一个不会被污染的信息源
改成读
AppleInterfaceStyle用户默认项。这个值只反映系统设置,setAppearance:影响不到它。FXUtils.DARK_MODE作为「变化通知源」保留不动 —— 没有它就收不到变化通知。只是不再从它取值。这样重算时得到的是同一个值,Objects.equals会短路掉,循环收敛。第二处:只在需要的时候才固定外观
原来是无条件调用
setAppearance:。改成:这一步是打破循环的关键。
有一个容易踩的坑:主题包可以通过
resolveCurrentThemeBrightness覆盖明暗模式,也就是说即使用户没有显式选择浅色/深色,启动器的实际明暗也可能与系统不同。所以这里比较的是已经解析出来的darkModeProperty(),而不是「明暗模式设置项是否等于 auto」。如果按后者判断,主题包指定的外观会被错误地清除,文件选择器就会跟着系统而不是启动器走。#5778 的行为有没有丢
没有。用户显式选择浅色或深色时,外观照旧会被固定,文件选择器仍然跟启动器配色一致。
只有在「启动器明暗与系统本来就相同」时才清除 —— 那种情况下继承系统外观和固定外观的视觉结果是一样的,但前者保住了系统变化的通知。
影响范围
两个文件,只在 macOS 分支内:
MacOSNativeUtils.java— 新增isSystemInDarkMode()读系统真实明暗、clearAppearance()清除外观Themes.java—getAutomaticBrightness()在 macOS 上改用系统真实值;新增applyMacOSAppearance()做上面那个判断Windows 的检测路径(注册表
AppsUseLightTheme+DWMWA_USE_IMMERSIVE_DARK_MODE)和 Linux 的检测路径(dbus-send查 xdg portal)都没有改动。在
-Dhmcl.native.backend=none下MacOSNativeUtils.isSupported()返回 false,会退回原有逻辑,不影响禁用 JNA 的场景。测试
环境与 #6526 报告者一致:macOS 26.5.2 / Apple Silicon (arm64) / JDK 21.0.11。
用 JNA 直连 Objective-C 运行时,验证了四项行为:
最后一项是整个修复的前提 —— 确认外观被固定时,
AppleInterfaceStyle仍然返回系统真实值。另外验证了
setAppearance:传 nil 确实能把NSApp.appearance恢复成继承状态:构建与检查:
./gradlew :HMCL:compileJava通过./gradlew checkstyle checkTranslations通过(与 Check Codes 工作流一致)另外说明一下,
./gradlew test在我的 macOS 机器上有一个失败项GameDirectoriesTest.newIsolatedInstallingInstanceUsesVersionRootBeforeVersionExists,与本 PR 无关 —— 我在未修改的 c8929c8 上复跑过,同样失败。那是一个独立的问题,我会另开 issue 说明。尚未验证的部分
上面的验证覆盖了 Objective-C 层的行为和构建检查,但我没有在完整 GUI 中做端到端点击验证(在启动器里切换「跟随系统设置」、再去系统设置里切换明暗模式,观察界面实际跟随)。JavaFX 绑定链在真实事件循环下的收敛我只做了代码层面的推导。如果需要,我可以补上这部分验证结果。