From 65ece2f6e05add80bd92c3cfeadccdc8c21421a7 Mon Sep 17 00:00:00 2001 From: Wang Haoyu Date: Sat, 16 May 2026 22:06:15 +0800 Subject: [PATCH 1/3] Fix accent color registry writes and add floating-window revert --- Actions/ShowFloatingWindowAction.cs | 25 +++++++++++++++++++ Actions/SwitchSystemAccentColorAction.cs | 8 +++--- Controls/ShowFloatingWindowSettingsControl.cs | 11 ++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Actions/ShowFloatingWindowAction.cs b/Actions/ShowFloatingWindowAction.cs index ca5fab4..d304d39 100644 --- a/Actions/ShowFloatingWindowAction.cs +++ b/Actions/ShowFloatingWindowAction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; @@ -16,6 +17,7 @@ public class ShowFloatingWindowAction( { private readonly ILogger _logger = logger; private readonly FloatingWindowService _floatingWindowService = floatingWindowService; + private static readonly ConcurrentDictionary PreviousStates = new(); protected override async Task OnInvoke() { @@ -24,6 +26,12 @@ protected override async Task OnInvoke() try { var shouldShow = Settings.ShowFloatingWindow; + + if (IsRevertable) + { + PreviousStates[ActionSet.Guid] = GlobalConstants.MainConfig!.Data.ShowFloatingWindow; + } + GlobalConstants.MainConfig!.Data.ShowFloatingWindow = shouldShow; GlobalConstants.MainConfig.Save(); _floatingWindowService.UpdateWindowState(); @@ -39,4 +47,21 @@ protected override async Task OnInvoke() await base.OnInvoke(); _logger.LogDebug("ShowFloatingWindowAction OnInvoke 完成"); } + + protected override async Task OnRevert() + { + await base.OnRevert(); + + if (!PreviousStates.TryRemove(ActionSet.Guid, out var previousState)) + { + _logger.LogInformation("未找到恢复快照,跳过悬浮窗恢复。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + GlobalConstants.MainConfig!.Data.ShowFloatingWindow = previousState; + GlobalConstants.MainConfig.Save(); + _floatingWindowService.UpdateWindowState(); + + _logger.LogInformation("已恢复悬浮窗状态为: {State}", previousState ? "开启" : "关闭"); + } } diff --git a/Actions/SwitchSystemAccentColorAction.cs b/Actions/SwitchSystemAccentColorAction.cs index 9b7f327..afbe7a9 100644 --- a/Actions/SwitchSystemAccentColorAction.cs +++ b/Actions/SwitchSystemAccentColorAction.cs @@ -35,13 +35,13 @@ protected override async Task OnInvoke() var colorizationDword = (0xC4u << 24) | ((uint)color.B << 16) | ((uint)color.G << 8) | color.R; using var dwmKey = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\DWM"); - dwmKey?.SetValue("AccentColor", dword, RegistryValueKind.DWord); - dwmKey?.SetValue("ColorizationColor", colorizationDword, RegistryValueKind.DWord); - dwmKey?.SetValue("ColorizationAfterglow", colorizationDword, RegistryValueKind.DWord); + dwmKey?.SetValue("AccentColor", unchecked((int)dword), RegistryValueKind.DWord); + dwmKey?.SetValue("ColorizationColor", unchecked((int)colorizationDword), RegistryValueKind.DWord); + dwmKey?.SetValue("ColorizationAfterglow", unchecked((int)colorizationDword), RegistryValueKind.DWord); dwmKey?.SetValue("ColorPrevalence", 1, RegistryValueKind.DWord); using var explorerKey = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent"); - explorerKey?.SetValue("AccentColorMenu", dword, RegistryValueKind.DWord); + explorerKey?.SetValue("AccentColorMenu", unchecked((int)dword), RegistryValueKind.DWord); // 通知 Windows 刷新主题色 SendMessageTimeout((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE, (UIntPtr)0, "ImmersiveColorSet", SMTO_ABORTIFHUNG, 5000, out _); diff --git a/Controls/ShowFloatingWindowSettingsControl.cs b/Controls/ShowFloatingWindowSettingsControl.cs index b554ee7..4c0bed9 100644 --- a/Controls/ShowFloatingWindowSettingsControl.cs +++ b/Controls/ShowFloatingWindowSettingsControl.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Data; -using Avalonia.Controls.Primitives; using ClassIsland.Core.Abstractions.Controls; using SystemTools.Settings; @@ -8,19 +7,19 @@ namespace SystemTools.Controls; public class ShowFloatingWindowSettingsControl : ActionSettingsControlBase { - private CheckBox _toggleCheckBox; + private readonly ToggleSwitch _toggleSwitch; public ShowFloatingWindowSettingsControl() { var panel = new StackPanel { Spacing = 10, Margin = new(10) }; - _toggleCheckBox = new CheckBox + _toggleSwitch = new ToggleSwitch { - Content = "显示悬浮窗", + Header = "显示悬浮窗", IsChecked = true }; - panel.Children.Add(_toggleCheckBox); + panel.Children.Add(_toggleSwitch); Content = panel; } @@ -29,7 +28,7 @@ protected override void OnInitialized() { base.OnInitialized(); - _toggleCheckBox[!ToggleButton.IsCheckedProperty] = new Binding(nameof(Settings.ShowFloatingWindow)) + _toggleSwitch[!ToggleSwitch.IsCheckedProperty] = new Binding(nameof(Settings.ShowFloatingWindow)) { Source = Settings, Mode = BindingMode.TwoWay From 7c25a3ed809fbf5252cabf757782208b074d5272 Mon Sep 17 00:00:00 2001 From: Wang Haoyu Date: Sat, 16 May 2026 22:18:56 +0800 Subject: [PATCH 2/3] Fix toggle switch API and add auto ClassIsland memory cleanup option --- ConfigHandlers/MainConfigData.cs | 15 +++ Controls/ShowFloatingWindowSettingsControl.cs | 2 +- Plugin.cs | 3 + .../ClassIslandMemoryAutoCleanupService.cs | 127 ++++++++++++++++++ .../MoreFeaturesOptionsSettingsPage.axaml | 10 ++ .../MoreFeaturesOptionsSettingsPage.axaml.cs | 12 ++ 6 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 Services/ClassIslandMemoryAutoCleanupService.cs diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index c0e0eb8..52b04d8 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -116,6 +116,21 @@ public bool AutoOpenUsbDriveOnInsert } + + bool _autoCleanupClassIslandMemory; + + [JsonPropertyName("autoCleanupClassIslandMemory")] + public bool AutoCleanupClassIslandMemory + { + get => _autoCleanupClassIslandMemory; + set + { + if (value == _autoCleanupClassIslandMemory) return; + _autoCleanupClassIslandMemory = value; + OnPropertyChanged(); + } + } + bool _autoHideMainWindowWhenOccluded; [JsonPropertyName("autoHideMainWindowWhenOccluded")] diff --git a/Controls/ShowFloatingWindowSettingsControl.cs b/Controls/ShowFloatingWindowSettingsControl.cs index 4c0bed9..d0ccd47 100644 --- a/Controls/ShowFloatingWindowSettingsControl.cs +++ b/Controls/ShowFloatingWindowSettingsControl.cs @@ -15,7 +15,7 @@ public ShowFloatingWindowSettingsControl() _toggleSwitch = new ToggleSwitch { - Header = "显示悬浮窗", + Content = "显示悬浮窗", IsChecked = true }; diff --git a/Plugin.cs b/Plugin.cs index a7e177f..971af7b 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -73,6 +73,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // ========== 注册可选人脸识别 ========== if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -120,6 +121,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s } IAppHost.GetService().Start(); IAppHost.GetService().Start(); + IAppHost.GetService().ApplyConfig(); _logger = IAppHost.GetService>(); _logger?.LogInformation("[SystemTools]实验性功能状态: {Status}", experimentalEnabled); @@ -906,6 +908,7 @@ private void OnAppStopping(object? sender, EventArgs e) { IAppHost.GetService().Stop(); IAppHost.GetService().Stop(); + IAppHost.GetService().Stop(); AdvancedShutdownAction.CancelPlanOnAppStopping(); if (GlobalConstants.MainConfig?.Data.EnableFloatingWindowFeature == true) { diff --git a/Services/ClassIslandMemoryAutoCleanupService.cs b/Services/ClassIslandMemoryAutoCleanupService.cs new file mode 100644 index 0000000..95e8e10 --- /dev/null +++ b/Services/ClassIslandMemoryAutoCleanupService.cs @@ -0,0 +1,127 @@ +using ClassIsland.Core; +using ClassIsland.Shared; +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using SystemTools.Shared; + +namespace SystemTools.Services; + +public class ClassIslandMemoryAutoCleanupService(ILogger logger) +{ + private readonly ILogger _logger = logger; + private readonly object _sync = new(); + private CancellationTokenSource? _cts; + private Task? _workerTask; + + private const long ThresholdBytes = 500L * 1024 * 1024; + + [DllImport("psapi.dll", SetLastError = true)] + private static extern bool EmptyWorkingSet(IntPtr hProcess); + + public void ApplyConfig() + { + var enabled = GlobalConstants.MainConfig?.Data.AutoCleanupClassIslandMemory == true; + if (enabled) + { + Start(); + return; + } + + Stop(); + } + + public void Start() + { + lock (_sync) + { + if (_workerTask is { IsCompleted: false }) + { + return; + } + + _cts = new CancellationTokenSource(); + _workerTask = Task.Run(() => RunAsync(_cts.Token)); + } + } + + public void Stop() + { + CancellationTokenSource? cts; + Task? worker; + lock (_sync) + { + cts = _cts; + worker = _workerTask; + _cts = null; + _workerTask = null; + } + + if (cts == null) + { + return; + } + + try { cts.Cancel(); } catch { } + cts.Dispose(); + + if (worker != null) + { + try { worker.Wait(1000); } catch { } + } + } + + private async Task RunAsync(CancellationToken cancellationToken) + { + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); + + try + { + while (await timer.WaitForNextTickAsync(cancellationToken)) + { + TryCleanupOnce(); + } + } + catch (OperationCanceledException) + { + // Ignore cancellation. + } + } + + private void TryCleanupOnce() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + try + { + var process = Process.GetCurrentProcess(); + process.Refresh(); + var privateMemory = process.PrivateMemorySize64; + + if (privateMemory <= ThresholdBytes) + { + return; + } + + var before = GC.GetTotalMemory(true); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); + GC.WaitForPendingFinalizers(); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); + var after = GC.GetTotalMemory(true); + + _ = EmptyWorkingSet(process.Handle); + + _logger.LogInformation("ClassIsland 内存自动清理已执行。PrivateMemory={PrivateMemoryBytes}B ManagedBefore={ManagedBefore}B ManagedAfter={ManagedAfter}B", privateMemory, before, after); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "ClassIsland 内存自动清理执行失败,将在下次周期继续。"); + } + } +} diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml index cb790ae..b5f3df7 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml @@ -26,6 +26,16 @@ + + + + + + diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs index 960c4d5..168fb65 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs @@ -40,4 +40,16 @@ private void AutoOpenUsbToggle_OnChanged(object? sender, RoutedEventArgs e) service.ApplyConfig(); GlobalConstants.MainConfig?.Save(); } + + private void AutoCleanupMemoryToggle_OnChanged(object? sender, RoutedEventArgs e) + { + if (sender is Avalonia.Controls.ToggleSwitch toggleSwitch) + { + Config.AutoCleanupClassIslandMemory = toggleSwitch.IsChecked == true; + } + + var service = ClassIsland.Shared.IAppHost.GetService(); + service.ApplyConfig(); + GlobalConstants.MainConfig?.Save(); + } } From d13db290aa52eb54ccdc8a528d9dadefdf2a45e7 Mon Sep 17 00:00:00 2001 From: Wang Haoyu Date: Sat, 16 May 2026 22:25:29 +0800 Subject: [PATCH 3/3] Apply true system accent palette instead of titlebar-only colorization --- Actions/SwitchSystemAccentColorAction.cs | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Actions/SwitchSystemAccentColorAction.cs b/Actions/SwitchSystemAccentColorAction.cs index afbe7a9..5c5c578 100644 --- a/Actions/SwitchSystemAccentColorAction.cs +++ b/Actions/SwitchSystemAccentColorAction.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Win32; using System; +using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -42,6 +43,8 @@ protected override async Task OnInvoke() using var explorerKey = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent"); explorerKey?.SetValue("AccentColorMenu", unchecked((int)dword), RegistryValueKind.DWord); + explorerKey?.SetValue("StartColorMenu", unchecked((int)dword), RegistryValueKind.DWord); + explorerKey?.SetValue("AccentPalette", BuildAccentPalette(color.R, color.G, color.B), RegistryValueKind.Binary); // 通知 Windows 刷新主题色 SendMessageTimeout((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE, (UIntPtr)0, "ImmersiveColorSet", SMTO_ABORTIFHUNG, 5000, out _); @@ -66,4 +69,38 @@ private static (byte A, byte R, byte G, byte B) ParseColor(string colorHex) var value = uint.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture); return ((byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value); } + + private static byte[] BuildAccentPalette(byte r, byte g, byte b) + { + var colors = new List<(byte R, byte G, byte B)> + { + Scale(r, g, b, 0.60), + Scale(r, g, b, 0.75), + Scale(r, g, b, 0.90), + (r, g, b), + Scale(r, g, b, 1.10), + Scale(r, g, b, 1.25), + Scale(r, g, b, 1.40), + Scale(r, g, b, 1.55) + }; + + var palette = new byte[32]; + for (var i = 0; i < colors.Count; i++) + { + var c = colors[i]; + var p = i * 4; + palette[p] = c.R; + palette[p + 1] = c.G; + palette[p + 2] = c.B; + palette[p + 3] = 0xFF; + } + + return palette; + } + + private static (byte R, byte G, byte B) Scale(byte r, byte g, byte b, double factor) + { + static byte ClampToByte(double v) => (byte)Math.Clamp((int)Math.Round(v), 0, 255); + return (ClampToByte(r * factor), ClampToByte(g * factor), ClampToByte(b * factor)); + } }