-
Notifications
You must be signed in to change notification settings - Fork 7
新增“切换系统强调色”行动,修复媒体规则抖动并统一主题切换逻辑 #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| using ClassIsland.Core.Abstractions.Automation; | ||
| using ClassIsland.Core.Attributes; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Win32; | ||
| using System; | ||
| using System.Globalization; | ||
| using System.Runtime.InteropServices; | ||
| using System.Threading.Tasks; | ||
| using SystemTools.Settings; | ||
|
|
||
| namespace SystemTools.Actions; | ||
|
|
||
| [ActionInfo("SystemTools.SwitchSystemAccentColor", "切换系统强调色", "\uE790", false)] | ||
| public class SwitchSystemAccentColorAction(ILogger<SwitchSystemAccentColorAction> logger) : ActionBase<AccentColorSettings> | ||
| { | ||
| private readonly ILogger<SwitchSystemAccentColorAction> _logger = logger; | ||
|
|
||
| [DllImport("user32.dll", SetLastError = true)] | ||
| static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); | ||
|
|
||
| const uint HWND_BROADCAST = 0xFFFF; | ||
| const uint WM_SETTINGCHANGE = 0x001A; | ||
| const uint SMTO_ABORTIFHUNG = 0x0002; | ||
|
|
||
| protected override async Task OnInvoke() | ||
| { | ||
| if (Settings == null || string.IsNullOrWhiteSpace(Settings.ColorHex)) return; | ||
|
|
||
| try | ||
| { | ||
| var color = ParseColor(Settings.ColorHex); | ||
| // Windows 使用 ABGR 格式(低位字节是 R) | ||
| var dword = ((uint)color.A << 24) | ((uint)color.B << 16) | ((uint)color.G << 8) | color.R; | ||
| // ColorizationColor 通常使用 C4 (196) 作为 Alpha | ||
| 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("ColorPrevalence", 1, RegistryValueKind.DWord); | ||
|
|
||
| using var explorerKey = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent"); | ||
| explorerKey?.SetValue("AccentColorMenu", dword, RegistryValueKind.DWord); | ||
|
|
||
| // 通知 Windows 刷新主题色 | ||
| SendMessageTimeout((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE, (UIntPtr)0, "ImmersiveColorSet", SMTO_ABORTIFHUNG, 5000, out _); | ||
|
|
||
| _logger.LogInformation("系统强调色已切换为 {Color}", Settings.ColorHex); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "切换系统强调色失败"); | ||
| throw; | ||
| } | ||
|
|
||
| await base.OnInvoke(); | ||
| } | ||
|
|
||
| private static (byte A, byte R, byte G, byte B) ParseColor(string colorHex) | ||
| { | ||
| var hex = colorHex.Trim().TrimStart('#'); | ||
| if (hex.Length == 6) hex = "FF" + hex; | ||
| if (hex.Length != 8) throw new FormatException("颜色格式无效"); | ||
|
|
||
| var value = uint.Parse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture); | ||
| return ((byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| using Avalonia.Controls; | ||
| using Avalonia.Layout; | ||
| using ClassIsland.Core.Abstractions.Controls; | ||
| using SystemTools.Settings; | ||
|
|
||
| namespace SystemTools.Controls; | ||
|
|
||
| public class AccentColorSettingsControl : ActionSettingsControlBase<AccentColorSettings> | ||
| { | ||
| private readonly ColorPicker _colorPicker; | ||
|
|
||
| public AccentColorSettingsControl() | ||
| { | ||
| var panel = new StackPanel { Spacing = 10, Margin = new(10) }; | ||
| panel.Children.Add(new TextBlock { Text = "切换系统强调色", FontSize = 14, FontWeight = Avalonia.Media.FontWeight.Bold }); | ||
|
|
||
| _colorPicker = new ColorPicker | ||
| { | ||
| IsAlphaEnabled = false, | ||
| HorizontalAlignment = HorizontalAlignment.Left | ||
| }; | ||
| _colorPicker.ColorChanged += (_, _) => Settings.ColorHex = _colorPicker.Color.ToString(); | ||
|
|
||
| panel.Children.Add(_colorPicker); | ||
| Content = panel; | ||
| } | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| base.OnInitialized(); | ||
| if (Avalonia.Media.Color.TryParse(Settings.ColorHex, out var color)) | ||
| _colorPicker.Color = color; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| using Avalonia.Threading; | ||
| using ClassIsland.Core; | ||
| using ClassIsland.Core.Abstractions.Services; | ||
| using System.Reflection; | ||
| using Microsoft.Extensions.Logging; | ||
| using System; | ||
| using System.Drawing; | ||
|
|
@@ -56,13 +57,7 @@ private void OnTick(object? sender, EventArgs e) | |
| return; | ||
| } | ||
|
|
||
| var themeService = IAppHost.TryGetService<IThemeService>(); | ||
| if (themeService == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| themeService.SetTheme(targetTheme.Value, null); | ||
| ApplyThemeLikeAppSettings(targetTheme.Value); | ||
| _lastAppliedTheme = targetTheme; | ||
| _logger.LogDebug("已自动匹配主题为:{Theme}", targetTheme == 1 ? "黑暗" : "明亮"); | ||
| } | ||
|
|
@@ -178,6 +173,28 @@ private static bool ResolveUseTopAreaFromClassIslandSettings() | |
| return null; | ||
| } | ||
|
|
||
| private static void ApplyThemeLikeAppSettings(int targetTheme) | ||
| { | ||
| var settingsServiceObj = IAppHost.TryGetService<object>(); | ||
| if (settingsServiceObj != null) | ||
| { | ||
| var type = settingsServiceObj.GetType(); | ||
| if (type.Name == "SettingsService") | ||
|
Comment on lines
+178
to
+182
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This lookup asks DI for Useful? React with 👍 / 👎. |
||
| { | ||
| var settingsProp = type.GetProperty("Settings", BindingFlags.Instance | BindingFlags.Public); | ||
| var settings = settingsProp?.GetValue(settingsServiceObj); | ||
| var themeProp = settings?.GetType().GetProperty("Theme", BindingFlags.Instance | BindingFlags.Public); | ||
| if (themeProp?.CanWrite == true) | ||
| { | ||
| themeProp.SetValue(settings, targetTheme); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| IAppHost.TryGetService<IThemeService>()?.SetTheme(targetTheme, null); | ||
| } | ||
|
|
||
| private static string[] GetPossibleClassIslandSettingsPaths() | ||
| { | ||
| var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace SystemTools.Settings; | ||
|
|
||
| public class AccentColorSettings | ||
| { | ||
| [JsonPropertyName("colorHex")] public string ColorHex { get; set; } = "#FF0078D4"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If media was previously detected as playing, this catch path returns that stale
truevalue on every SMTC failure, so the rule can remain permanently true after playback has already stopped wheneverRequestAsync()/GetSessions()starts throwing (for example, intermittent WinRT/permission failures). Because this rule drives automations, returning the last positive state on exceptions can cause repeated false triggers; the failure path should invalidate or age out the cached result instead of reusing it indefinitely.Useful? React with 👍 / 👎.