diff --git a/DesktopClock/Properties/Settings.cs b/DesktopClock/Properties/Settings.cs
index 6c83d13..cf11630 100644
--- a/DesktopClock/Properties/Settings.cs
+++ b/DesktopClock/Properties/Settings.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
+using DesktopClock.Utilities;
using Newtonsoft.Json;
using WpfWindowPlacement;
@@ -25,7 +26,6 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
public static readonly double MaxSizeLog = 6.5;
public static readonly double MinSizeLog = 2.7;
-
static Settings()
{
// Settings file path from the same directory as the executable.
@@ -349,6 +349,11 @@ private static Settings LoadAndAttemptSave()
{
var settings = LoadFromFile();
+ if (!File.Exists(FilePath))
+ {
+ settings.ApplySystemThemeDefaultsIfAvailable();
+ }
+
CanBeSaved = settings.Save();
return settings;
@@ -382,6 +387,15 @@ public void ScaleHeight(double steps)
Height = (int)exp;
}
+ private void ApplySystemThemeDefaultsIfAvailable()
+ {
+ if (!SystemThemeService.TryGetThemeDefaults(out var textColor, out var outerColor))
+ return;
+
+ TextColor = textColor;
+ OuterColor = outerColor;
+ }
+
public void Dispose()
{
// We don't dispose of the watcher anymore because it would actually hang indefinitely if you had multiple instances of the same clock open.
diff --git a/DesktopClock/Utilities/SystemThemeService.cs b/DesktopClock/Utilities/SystemThemeService.cs
new file mode 100644
index 0000000..26203da
--- /dev/null
+++ b/DesktopClock/Utilities/SystemThemeService.cs
@@ -0,0 +1,172 @@
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Media;
+using Microsoft.Win32;
+
+namespace DesktopClock.Utilities;
+
+///
+/// Reads Windows theme and accent color values to seed default UI colors.
+///
+public static class SystemThemeService
+{
+ // Fallbacks match the app's existing default colors.
+ private static readonly Color DefaultAccentColor = Color.FromRgb(0, 120, 215);
+ private static readonly Color LightThemeOuterColor = Color.FromRgb(247, 247, 247);
+ private static readonly Color DarkThemeOuterColor = Color.FromRgb(32, 32, 32);
+
+ // Theme and colorization values stored under HKCU.
+ private const string PersonalizeKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
+ private const string DwmKeyPath = @"Software\Microsoft\Windows\DWM";
+ private const string AppsUseLightThemeValueName = "AppsUseLightTheme";
+ private const string SystemUsesLightThemeValueName = "SystemUsesLightTheme";
+ private const string ColorizationColorValueName = "ColorizationColor";
+
+ ///
+ /// Tries to build default text/outer colors from the system theme and accent color.
+ ///
+ ///
+ /// Returns false when the system theme cannot be read (older Windows or missing keys).
+ ///
+ public static bool TryGetThemeDefaults(out Color textColor, out Color outerColor)
+ {
+ textColor = default;
+ outerColor = default;
+
+ if (!TryGetSystemThemeIsLight(out var isLightTheme))
+ return false;
+
+ textColor = GetSystemAccentColor();
+ outerColor = isLightTheme ? LightThemeOuterColor : DarkThemeOuterColor;
+ return true;
+ }
+
+ ///
+ /// Reads the light/dark preference from the Windows personalize key.
+ ///
+ private static bool TryGetSystemThemeIsLight(out bool isLightTheme)
+ {
+ // AppsUseLightTheme is the primary indicator for Win10+ app theme.
+ if (TryGetRegistryDword(PersonalizeKeyPath, AppsUseLightThemeValueName, out var appsUseLightTheme))
+ {
+ isLightTheme = appsUseLightTheme > 0;
+ return true;
+ }
+
+ // SystemUsesLightTheme is a fallback when AppsUseLightTheme is absent.
+ if (TryGetRegistryDword(PersonalizeKeyPath, SystemUsesLightThemeValueName, out var systemUsesLightTheme))
+ {
+ isLightTheme = systemUsesLightTheme > 0;
+ return true;
+ }
+
+ // Unknown; caller treats this as "no system theme available".
+ isLightTheme = true;
+ return false;
+ }
+
+ ///
+ /// Resolves the current accent color using DWM, registry, then system parameters.
+ ///
+ private static Color GetSystemAccentColor()
+ {
+ // Prefer DWM because it tracks the active colorization value.
+ if (TryGetAccentColorFromDwm(out var accent) || TryGetAccentColorFromRegistry(out accent))
+ return accent;
+
+ // SystemParameters is a safe fallback when DWM/registry are unavailable.
+ accent = SystemParameters.WindowGlassColor;
+ if (accent.A != 0)
+ return Color.FromArgb(255, accent.R, accent.G, accent.B);
+
+ return DefaultAccentColor;
+ }
+
+ ///
+ /// Reads a DWORD value from HKCU, if present.
+ ///
+ private static bool TryGetRegistryDword(string keyPath, string valueName, out int value)
+ {
+ value = default;
+
+ try
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(keyPath);
+ if (key?.GetValue(valueName) is int intValue)
+ {
+ value = intValue;
+ return true;
+ }
+
+ if (key?.GetValue(valueName) is byte byteValue)
+ {
+ value = byteValue;
+ return true;
+ }
+
+ if (key?.GetValue(valueName) is uint uintValue)
+ {
+ value = unchecked((int)uintValue);
+ return true;
+ }
+ }
+ catch
+ {
+ }
+
+ return false;
+ }
+
+ ///
+ /// Uses DWM to fetch the current colorization color.
+ ///
+ private static bool TryGetAccentColorFromDwm(out Color color)
+ {
+ color = default;
+
+ try
+ {
+ var result = DwmGetColorizationColor(out var colorizationColor, out _);
+ if (result != 0)
+ return false;
+
+ color = ColorFromArgbUint(colorizationColor);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Reads the colorization color from the DWM registry key.
+ ///
+ private static bool TryGetAccentColorFromRegistry(out Color color)
+ {
+ color = default;
+
+ if (!TryGetRegistryDword(DwmKeyPath, ColorizationColorValueName, out var colorization))
+ return false;
+
+ color = ColorFromArgbUint(unchecked((uint)colorization));
+ return true;
+ }
+
+ ///
+ /// Converts a DWM colorization ARGB value into an opaque WPF color.
+ ///
+ private static Color ColorFromArgbUint(uint color)
+ {
+ var r = (byte)((color >> 16) & 0xFF);
+ var g = (byte)((color >> 8) & 0xFF);
+ var b = (byte)(color & 0xFF);
+ return Color.FromArgb(255, r, g, b);
+ }
+
+ ///
+ /// Win32 API for reading the current DWM colorization color.
+ ///
+ [DllImport("dwmapi.dll", PreserveSig = true)]
+ private static extern int DwmGetColorizationColor(out uint colorizationColor, out bool opaqueBlend);
+}