Skip to content

Fix: 修复 macOS 下主题模式「跟随系统设置」失效 - #6596

Closed
dwgx wants to merge 1 commit into
HMCL-dev:mainfrom
dwgx:fix/macos-follow-system-appearance
Closed

Fix: 修复 macOS 下主题模式「跟随系统设置」失效#6596
dwgx wants to merge 1 commit into
HMCL-dev:mainfrom
dwgx:fix/macos-follow-system-appearance

Conversation

@dwgx

@dwgx dwgx commented Aug 3, 2026

Copy link
Copy Markdown

Closes #6526

问题是什么

在 macOS 上,主题的「跟随系统设置」是坏的:

  • 从「浅色」或「深色」切换到「跟随系统设置」,界面没有任何变化
  • 就算一开始就设成「跟随系统设置」,之后去系统设置里切换明暗模式,HMCL 也不会跟着变

为什么会坏

说起来有点绕,但根子上是一个「自己读自己」的循环。

HMCL 想知道「系统现在是浅色还是深色」,读的是 JavaFX 提供的 Platform.Preferences.colorScheme

而 JavaFX 这个值不是凭空来的,它是这么算出来的(PlatformSupport.m / PreferenceProperties.java):

  1. NSApp.effectiveAppearance 注册 KVO 监听
  2. 外观变化时,用当前外观去解析系统颜色(窗口背景色、文字色等)
  3. 比较背景色和前景色的亮度,得出是深色还是浅色

问题就在这里:HMCL 自己会去改 NSApp.effectiveAppearance

#5778 为了让文件选择器这类原生窗口的配色跟着启动器走,加了 NSApplication setAppearance: 调用。而 setAppearance: 改的正是 effectiveAppearance —— JavaFX 拿来算明暗的那个属性。

于是就成了这样:

HMCL 想知道系统是深还是浅
  └─ 读 JavaFX colorScheme
       └─ 它由 NSApp.effectiveAppearance 推导
            └─ 而这个值是 HMCL 自己刚写进去的

HMCL 读到的「系统外观」,其实是它自己设定的外观。系统真正是深还是浅,它再也看不见了。

FXUtils.DARK_MODE 同时承担了两个角色 —— 既是「值的来源」,又是「变化的通知源」。被污染的是前者,后者仍然正常。

怎么改的

第一处:换一个不会被污染的信息源

改成读 AppleInterfaceStyle 用户默认项。这个值只反映系统设置,setAppearance: 影响不到它。

FXUtils.DARK_MODE 作为「变化通知源」保留不动 —— 没有它就收不到变化通知。只是不再从它取值。这样重算时得到的是同一个值,Objects.equals 会短路掉,循环收敛。

第二处:只在需要的时候才固定外观

原来是无条件调用 setAppearance:。改成:

启动器明暗模式 处理 原因
与系统一致 清除外观(传 nil) 让应用重新继承系统外观,JavaFX 得以继续观察系统变化
与系统不一致 固定外观 用户显式选了浅色/深色,本来就不该跟随系统

这一步是打破循环的关键。

有一个容易踩的坑:主题包可以通过 resolveCurrentThemeBrightness 覆盖明暗模式,也就是说即使用户没有显式选择浅色/深色,启动器的实际明暗也可能与系统不同。所以这里比较的是已经解析出来的 darkModeProperty(),而不是「明暗模式设置项是否等于 auto」。如果按后者判断,主题包指定的外观会被错误地清除,文件选择器就会跟着系统而不是启动器走。

#5778 的行为有没有丢

没有。用户显式选择浅色或深色时,外观照旧会被固定,文件选择器仍然跟启动器配色一致。

只有在「启动器明暗与系统本来就相同」时才清除 —— 那种情况下继承系统外观和固定外观的视觉结果是一样的,但前者保住了系统变化的通知。

影响范围

两个文件,只在 macOS 分支内:

  • MacOSNativeUtils.java — 新增 isSystemInDarkMode() 读系统真实明暗、clearAppearance() 清除外观
  • Themes.javagetAutomaticBrightness() 在 macOS 上改用系统真实值;新增 applyMacOSAppearance() 做上面那个判断

Windows 的检测路径(注册表 AppsUseLightTheme + DWMWA_USE_IMMERSIVE_DARK_MODE)和 Linux 的检测路径(dbus-send 查 xdg portal)都没有改动。

-Dhmcl.native.backend=noneMacOSNativeUtils.isSupported() 返回 false,会退回原有逻辑,不影响禁用 JNA 的场景。

测试

环境与 #6526 报告者一致:macOS 26.5.2 / Apple Silicon (arm64) / JDK 21.0.11。

用 JNA 直连 Objective-C 运行时,验证了四项行为:

system dark mode = true

[PASS] 启动器明暗与系统一致      appearance = nil(继承系统)
[PASS] 启动器明暗与系统不一致    appearance = NSAppearanceNameAqua
[PASS] 固定后切回一致            appearance = nil(继承系统)    ← #6526 的路径
[PASS] 固定期间读取系统明暗      isSystemInDarkMode() = true     ← 信息源独立性

最后一项是整个修复的前提 —— 确认外观被固定时,AppleInterfaceStyle 仍然返回系统真实值。

另外验证了 setAppearance: 传 nil 确实能把 NSApp.appearance 恢复成继承状态:

                     appearance
初始                 nil
固定为 Aqua          NSAppearanceNameAqua
固定为 DarkAqua      NSAppearanceNameDarkAqua
传 nil               nil          ← 恢复继承

构建与检查:

  • ./gradlew :HMCL:compileJava 通过
  • ./gradlew checkstyle checkTranslations 通过(与 Check Codes 工作流一致)

另外说明一下,./gradlew test 在我的 macOS 机器上有一个失败项 GameDirectoriesTest.newIsolatedInstallingInstanceUsesVersionRootBeforeVersionExists,与本 PR 无关 —— 我在未修改的 c8929c8 上复跑过,同样失败。那是一个独立的问题,我会另开 issue 说明。

尚未验证的部分

上面的验证覆盖了 Objective-C 层的行为和构建检查,但我没有在完整 GUI 中做端到端点击验证(在启动器里切换「跟随系统设置」、再去系统设置里切换明暗模式,观察界面实际跟随)。JavaFX 绑定链在真实事件循环下的收敛我只做了代码层面的推导。如果需要,我可以补上这部分验证结果。

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
@github-actions github-actions Bot added the 40+ label Aug 3, 2026
@dwgx

dwgx commented Aug 3, 2026

Copy link
Copy Markdown
Author

补充端到端验证结果。我在 PR 描述末尾提到未在完整运行环境中验证绑定链的收敛,现已补上,并附未打补丁版本作为对照。

方法

applyMacOSAppearance 中临时加入一行日志,输出启动器明暗、系统明暗、采取的动作(固定或清除)、getAutomaticBrightness() 的结果,以及 FXUtils.DARK_MODE 的当前值。对照组在未打补丁的 main 上于等效位置(setAppearance 调用点)加入同样的日志。

两者均以 JDK 25 启动,运行时下载 JavaFX 25(JavaFX Version: 25+29),因此 Platform.Preferences 可用、FXUtils.DARK_MODE 非 null —— 这是该问题成立的前提。随后用 osascript 程序化切换系统外观。

日志代码仅用于本次验证,未包含在本 PR 中。

打补丁后

系统外观切换 Dark → Light → Dark → Light → Dark,每次都触发一次,启动器明暗始终跟随系统:

launcherDark=true  systemDark=true  action=CLEAR automaticBrightness=DARK  fxDarkMode=true
launcherDark=false systemDark=false action=CLEAR automaticBrightness=LIGHT fxDarkMode=false
launcherDark=true  systemDark=true  action=CLEAR automaticBrightness=DARK  fxDarkMode=true
launcherDark=false systemDark=false action=CLEAR automaticBrightness=LIGHT fxDarkMode=false
launcherDark=true  systemDark=true  action=CLEAR automaticBrightness=DARK  fxDarkMode=true

明暗模式为默认的「跟随系统设置」,启动器明暗与系统一致,因此每次都是 CLEAR(清除外观)。由于外观未被固定,fxDarkMode 始终与系统一致,未被污染。

未打补丁(对照)

同样的操作,系统外观切换了三次,但监听器只在启动时触发过一次,之后再无任何记录

launcherDark=true  automaticBrightness=DARK  fxDarkMode=true
(系统切到 Light —— 无记录)
(系统切回 Dark  —— 无记录)
(系统切到 Light —— 无记录)

这与问题描述一致,也说明了失效的具体环节:启动时 setAppearance 固定了应用外观后,NSApp.effectiveAppearance 不再随系统变化,JavaFX 观察不到变化,darkModeProperty() 不再更新,监听器因此不再触发,启动器也就永远停在启动时的明暗上。

对照鲜明:同样三次系统切换,打补丁后触发 5 次并全部跟随,未打补丁触发 0 次。

环境

macOS 26.5.2 / Apple Silicon (arm64) / JDK 25.0.3 (Temurin) / JavaFX 25+29

两个版本均从本 PR 的分支和未修改的 c8929c8 分别构建,除上述日志代码外无其他差异。

@burningtnt

Copy link
Copy Markdown
Member

我怀疑本 PR 是由 AI 完全独立完成的,并没有经过你的审查。介于 HMCL 近期被 AI slop 骚扰的情况,我要求您提供相关证据证明本 PR 确实正常工作,或证明你已经充分审查了该 PR 的代码。

@ToobLac

ToobLac commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Duplicate to #6555

@Glavo Glavo closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] macOS 26 主题模式-跟随系统设置 失效

4 participants