diff --git a/src/OpenClaw.Shared/SettingsData.cs b/src/OpenClaw.Shared/SettingsData.cs index e0314dae7..97af4dea3 100644 --- a/src/OpenClaw.Shared/SettingsData.cs +++ b/src/OpenClaw.Shared/SettingsData.cs @@ -127,6 +127,8 @@ public record class SettingsData /// "User interface" section. /// public bool UseLegacyWebChat { get; set; } = false; + public string AppTheme { get; set; } = "System"; + public bool? ShowDiagnostics { get; set; } public List? UserRules { get; set; } // ── MXC sandbox ───────────────────────────────────────────────────── diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index 7976b6c67..d623c5dda 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -60,6 +60,7 @@ public partial class App : Application, OpenClawTray.Services.IAppCommands public GatewayRegistry? Registry => _gatewayRegistry; public GatewayConnectionManager? ConnectionManager => _connectionManager; internal SettingsManager Settings => _settings ?? throw new InvalidOperationException("Settings are not initialized."); + internal SettingsManager? SettingsOrNull => _settings; internal string DataDirectoryPath => DataPath; /// The active hub window, exposed so pages can obtain an HWND for file pickers. @@ -699,6 +700,7 @@ _dispatcherQueue is null !prewarmIsBootstrapToken) { _chatWindow = new ChatWindow(prewarmUrl, prewarmToken); + ApplyThemePreference(_chatWindow); // Window is created but hidden — WebView2 initializes in the background } @@ -736,6 +738,7 @@ private void InitializeKeepAliveWindow() // This prevents GC/threading issues when creating windows after idle _keepAliveWindow = new Window(); _keepAliveWindow.Content = new Microsoft.UI.Xaml.Controls.Grid(); + ApplyThemePreference(_keepAliveWindow); _keepAliveWindow.AppWindow.IsShownInSwitchers = false; // Move off-screen and set minimal size @@ -768,10 +771,28 @@ private void InitializeTrayMenuWindow() { // Pre-create menu window once - reuse to avoid crash on window creation after idle _trayMenuWindow = new TrayMenuWindow(); + ApplyThemePreference(_trayMenuWindow); _trayMenuWindow.MenuItemClicked += OnTrayMenuItemClicked; // Don't close - just hide } + internal void ApplyThemePreferenceToOpenWindows() + { + ApplyThemePreference(_keepAliveWindow); + ApplyThemePreference(_hubWindow); + ApplyThemePreference(_trayMenuWindow); + ApplyThemePreference(_chatWindow); + ApplyThemePreference(_connectionStatusWindow); + } + + private void ApplyThemePreference(Window? window) + { + if (_settings is null) + return; + + ThemeHelper.ApplyTheme(window, _settings.AppTheme); + } + private void OnTrayIconSelected(TrayIcon sender, TrayIconEventArgs e) { if (_connectionManager?.CurrentSnapshot.OperatorState == RoleConnectionState.Connected) @@ -806,6 +827,7 @@ internal void ShowChatWindow() if (_chatWindow == null) { _chatWindow = new ChatWindow(url, token); + ApplyThemePreference(_chatWindow); } // Bug 2: cached ChatWindow may have been pre-warmed with empty/stale credentials @@ -3334,6 +3356,7 @@ internal void ShowHub(string? navigateTo = null, bool activate = true) if (_hubWindow == null || _hubWindow.IsClosed) { _hubWindow = new HubWindow(); + ApplyThemePreference(_hubWindow); _hubWindow.AppModel = _appState; _hubWindow.BindAppNotifications(_appNotificationService!); _hubWindow.ApplyNavPaneState(_settings!); @@ -3489,7 +3512,6 @@ private void OnSettingsSaved(object? sender, EventArgs e) WireAppCapabilityHandlers(); } - // Non-connection settings always applied regardless of impact if (_settings!.GlobalHotkeyEnabled) { _globalHotkey ??= new GlobalHotkeyService(); @@ -3508,19 +3530,24 @@ private void OnSettingsSaved(object? sender, EventArgs e) AutoStartManager.SetAutoStartAsync(_settings.AutoStart), "[App] Failed to apply auto-start setting"); - // Notify ad-hoc listeners (e.g. ChatWindow may be alive but not - // owned by the hub) that settings have changed. Marshal onto the - // UI thread because IAppCommands.NotifySettingsSaved is a public - // entry point that may be invoked from background work; existing - // handlers (DebugPage, ChatWindow) update UI directly and would - // crash if dispatched from a non-UI thread (Hanselman v2 #7). + // Apply UI-only settings and notify ad-hoc listeners. This public + // entry point can be invoked from background work, while existing + // listeners update UI directly. + void ApplyUiSettingsAndNotify() + { + ApplyThemePreferenceToOpenWindows(); + if (_hubWindow is { IsClosed: false }) + _hubWindow.RefreshDiagnosticsNavVisibility(); + SettingsChanged?.Invoke(this, EventArgs.Empty); + } + if (_dispatcherQueue != null && !_dispatcherQueue.HasThreadAccess) { - _dispatcherQueue.TryEnqueue(() => SettingsChanged?.Invoke(this, EventArgs.Empty)); + _dispatcherQueue.TryEnqueue(ApplyUiSettingsAndNotify); } else { - SettingsChanged?.Invoke(this, EventArgs.Empty); + ApplyUiSettingsAndNotify(); } } @@ -3581,6 +3608,7 @@ private void ShowConnectionStatusWindow() _connectionManager!.Diagnostics, _gatewayRegistry, _connectionManager); + ApplyThemePreference(_connectionStatusWindow); _connectionStatusWindow.Activate(); } diff --git a/src/OpenClaw.Tray.WinUI/Helpers/DiagnosticsGate.cs b/src/OpenClaw.Tray.WinUI/Helpers/DiagnosticsGate.cs new file mode 100644 index 000000000..5d50696a7 --- /dev/null +++ b/src/OpenClaw.Tray.WinUI/Helpers/DiagnosticsGate.cs @@ -0,0 +1,28 @@ +#if OPENCLAW_TRAY_TESTS +namespace OpenClawTray.Helpers; + +internal static class DiagnosticsGate +{ + public static bool BuildDefault => true; + public static bool IsVisible => BuildDefault; +} +#else +using Microsoft.UI.Xaml; + +namespace OpenClawTray.Helpers; + +/// +/// Gates the Diagnostics page. Diagnostics remain visible by default for +/// compatibility with existing installs; users can explicitly hide or show them +/// via Settings (SettingsManager.ShowDiagnosticsOverride). +/// +internal static class DiagnosticsGate +{ + public static bool BuildDefault => + true; + + /// Effective visibility: user override if set, else the build default. + public static bool IsVisible => + (Application.Current as OpenClawTray.App)?.SettingsOrNull?.ShowDiagnosticsEffective ?? BuildDefault; +} +#endif diff --git a/src/OpenClaw.Tray.WinUI/Helpers/ThemeHelper.cs b/src/OpenClaw.Tray.WinUI/Helpers/ThemeHelper.cs index c773de391..63448a333 100644 --- a/src/OpenClaw.Tray.WinUI/Helpers/ThemeHelper.cs +++ b/src/OpenClaw.Tray.WinUI/Helpers/ThemeHelper.cs @@ -30,6 +30,20 @@ public static ElementTheme GetCurrentTheme() return IsDarkMode() ? ElementTheme.Dark : ElementTheme.Light; } + public static ElementTheme GetRequestedTheme(string? preference) => + SettingsManager.NormalizeAppTheme(preference) switch + { + SettingsManager.AppThemeLight => ElementTheme.Light, + SettingsManager.AppThemeDark => ElementTheme.Dark, + _ => ElementTheme.Default + }; + + public static void ApplyTheme(Window? window, string? preference) + { + if (window?.Content is FrameworkElement rootElement) + rootElement.RequestedTheme = GetRequestedTheme(preference); + } + public static Color GetAccentColor() { // Returns the user's Windows accent color (previously hard-coded to diff --git a/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml b/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml deleted file mode 100644 index 58ac065d6..000000000 --- a/src/OpenClaw.Tray.WinUI/Pages/AboutPage.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -