Merge pull request #2572 from Loskh/dark_mode

fix: respect system dark mode setting
This commit is contained in:
goat 2026-01-10 12:50:42 +01:00 committed by GitHub
commit 5ee339b5a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 75 additions and 2 deletions

View file

@ -32,6 +32,7 @@ using Dalamud.Interface.Windowing;
using Dalamud.Interface.Windowing.Persistence;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
@ -502,6 +503,34 @@ internal partial class InterfaceManager : IInternalDisposableService
ImGuiHelpers.ClearStacksOnContext();
}
/// <summary>
/// Applies immersive dark mode to the game window based on the current system theme setting.
/// </summary>
internal void SetImmersiveModeFromSystemTheme()
{
bool useDark = this.IsSystemInDarkMode();
this.SetImmersiveMode(useDark);
}
/// <summary>
/// Checks whether the system use dark mode.
/// </summary>
/// <returns>Returns true if dark mode is preferred.</returns>
internal bool IsSystemInDarkMode()
{
try
{
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
var value = key?.GetValue("AppsUseLightTheme") as int?;
return value != 1;
}
catch
{
return false;
}
}
/// <summary>
/// Toggle Windows 11 immersive mode on the game window.
/// </summary>
@ -745,6 +774,18 @@ internal partial class InterfaceManager : IInternalDisposableService
private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
{
if (args.Message == WM.WM_SETTINGCHANGE)
{
if (this.dalamudConfiguration.WindowIsImmersive)
{
if (MemoryHelper.EqualsZeroTerminatedWideString("ImmersiveColorSet", args.LParam) ||
MemoryHelper.EqualsZeroTerminatedWideString("VisualStyleChanged", args.LParam))
{
this.SetImmersiveModeFromSystemTheme();
}
}
}
var r = this.backend?.ProcessWndProcW(args.Hwnd, args.Message, args.WParam, args.LParam);
if (r is not null)
args.SuppressWithValue(r.Value);
@ -859,7 +900,7 @@ internal partial class InterfaceManager : IInternalDisposableService
{
// Requires that game window to be there, which will be the case once game swap chain is initialized.
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
this.SetImmersiveMode(true);
this.SetImmersiveModeFromSystemTheme();
}
catch (Exception ex)
{

View file

@ -66,7 +66,14 @@ internal sealed class SettingsTabLook : SettingsTab
{
try
{
Service<InterfaceManager>.GetNullable()?.SetImmersiveMode(b);
if (b)
{
Service<InterfaceManager>.GetNullable()?.SetImmersiveModeFromSystemTheme();
}
else
{
Service<InterfaceManager>.GetNullable()?.SetImmersiveMode(false);
}
}
catch (Exception ex)
{

View file

@ -268,6 +268,31 @@ public static unsafe class MemoryHelper
}
}
/// <summary>
/// Compares a UTF-16 character span with a null-terminated UTF-16 string at <paramref name="memoryAddress"/>.
/// </summary>
/// <param name="charSpan">UTF-16 character span (e.g., from a string literal).</param>
/// <param name="memoryAddress">Address of null-terminated UTF-16 (wide) string, as used by Windows APIs.</param>
/// <returns><see langword="true"/> if equal; otherwise, <see langword="false"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool EqualsZeroTerminatedWideString(
scoped ReadOnlySpan<char> charSpan,
nint memoryAddress)
{
if (memoryAddress == 0)
return charSpan.Length == 0;
char* p = (char*)memoryAddress;
foreach (char c in charSpan)
{
if (*p++ != c)
return false;
}
return *p == '\0';
}
/// <summary>
/// Read a UTF-8 encoded string from a specified memory address.
/// </summary>