From 79880392d8ba8b497cfb32072cbada46cfb027a0 Mon Sep 17 00:00:00 2001 From: Wang Haoyu Date: Sun, 5 Apr 2026 11:42:59 +0800 Subject: [PATCH 1/7] feat: improve adaptive theme sync and add occlusion auto-hide --- ConfigHandlers/MainConfigData.cs | 15 ++ .../InTimePeriodRuleSettingsControl.axaml | 32 ++++ Controls/InTimePeriodRuleSettingsControl.cs | 76 ++------ Plugin.cs | 3 + Services/AdaptiveThemeSyncService.cs | 69 ++++--- .../MainWindowOcclusionAutoHideService.cs | 172 ++++++++++++++++++ .../MoreFeaturesOptionsSettingsPage.axaml | 12 +- .../MoreFeaturesOptionsSettingsPage.axaml.cs | 11 ++ SystemTools.csproj | 1 + 9 files changed, 305 insertions(+), 86 deletions(-) create mode 100644 Controls/InTimePeriodRuleSettingsControl.axaml create mode 100644 Services/MainWindowOcclusionAutoHideService.cs diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index 8981084..c7f861c 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -100,6 +100,21 @@ public bool AutoMatchMainBackgroundTheme OnPropertyChanged(); } } + + + bool _autoHideMainWindowWhenOccluded; + + [JsonPropertyName("autoHideMainWindowWhenOccluded")] + public bool AutoHideMainWindowWhenOccluded + { + get => _autoHideMainWindowWhenOccluded; + set + { + if (value == _autoHideMainWindowWhenOccluded) return; + _autoHideMainWindowWhenOccluded = value; + OnPropertyChanged(); + } + } // ========== 公告相关 ========== /*string _lastAcceptedAnnouncement = string.Empty; diff --git a/Controls/InTimePeriodRuleSettingsControl.axaml b/Controls/InTimePeriodRuleSettingsControl.axaml new file mode 100644 index 0000000..dfe0e05 --- /dev/null +++ b/Controls/InTimePeriodRuleSettingsControl.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/Controls/InTimePeriodRuleSettingsControl.cs b/Controls/InTimePeriodRuleSettingsControl.cs index 99f563f..8f5723f 100644 --- a/Controls/InTimePeriodRuleSettingsControl.cs +++ b/Controls/InTimePeriodRuleSettingsControl.cs @@ -1,91 +1,51 @@ +using System; using Avalonia.Controls; -using Avalonia.Layout; +using Avalonia.Markup.Xaml; using ClassIsland.Core.Abstractions.Controls; -using System; using SystemTools.Rules; namespace SystemTools.Controls; - -public class InTimePeriodRuleSettingsControl : RuleSettingsControlBase +public partial class InTimePeriodRuleSettingsControl : RuleSettingsControlBase { - private readonly TimePicker _startTimePicker; - private readonly TimePicker _endTimePicker; - public InTimePeriodRuleSettingsControl() { - var panel = new StackPanel { Spacing = 10 }; - var row = new Grid - { - ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto,*,Auto"), - ColumnSpacing = 8, - VerticalAlignment = VerticalAlignment.Center - }; - - row.Children.Add(new TextBlock { Text = "从", VerticalAlignment = VerticalAlignment.Center }); - - _startTimePicker = new TimePicker - { - ClockIdentifier = "24HourClock", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - Grid.SetColumn(_startTimePicker, 1); - row.Children.Add(_startTimePicker); - - var sep = new TextBlock { Text = "至", VerticalAlignment = VerticalAlignment.Center }; - Grid.SetColumn(sep, 2); - row.Children.Add(sep); + InitializeComponent(); - _endTimePicker = new TimePicker - { - ClockIdentifier = "24HourClock", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - Grid.SetColumn(_endTimePicker, 3); - row.Children.Add(_endTimePicker); - - var hint = new TextBlock - { - Text = "提示:若起始晚于结束 将按跨天处理", - TextWrapping = Avalonia.Media.TextWrapping.Wrap, - Foreground = Avalonia.Media.Brushes.Gray, - FontSize = 12 - }; - panel.Children.Add(row); - panel.Children.Add(hint); - Content = panel; + StartTimePicker.SelectedTimeChanged += (_, _) => SyncSettings(); + EndTimePicker.SelectedTimeChanged += (_, _) => SyncSettings(); + } - _startTimePicker.SelectedTimeChanged += (s, e) => SyncSettings(); - _endTimePicker.SelectedTimeChanged += (s, e) => SyncSettings(); + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); } protected override void OnInitialized() { base.OnInitialized(); - + if (TimeSpan.TryParse(Settings.StartTime, out var start)) { - _startTimePicker.SelectedTime = start; + StartTimePicker.SelectedTime = start; } - + if (TimeSpan.TryParse(Settings.EndTime, out var end)) { - _endTimePicker.SelectedTime = end; + EndTimePicker.SelectedTime = end; } } private void SyncSettings() { - if (_startTimePicker.SelectedTime.HasValue) + if (StartTimePicker.SelectedTime.HasValue) { - Settings.StartTime = _startTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); + Settings.StartTime = StartTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); } - if (_endTimePicker.SelectedTime.HasValue) + if (EndTimePicker.SelectedTime.HasValue) { - Settings.EndTime = _endTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); + Settings.EndTime = EndTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); } } } diff --git a/Plugin.cs b/Plugin.cs index 8f88328..4fbfd97 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -63,6 +63,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s services.AddSingleton(GlobalConstants.MainConfig); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // ========== 注册可选人脸识别 ========== if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -109,6 +110,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s IAppHost.GetService().Start(); } IAppHost.GetService().Start(); + IAppHost.GetService().Start(); _logger = IAppHost.GetService>(); _logger?.LogInformation("[SystemTools]实验性功能状态: {Status}", experimentalEnabled); @@ -801,6 +803,7 @@ private void RegisterSettingsPageGroup(IServiceCollection services) private void OnAppStopping(object? sender, EventArgs e) { IAppHost.GetService().Stop(); + IAppHost.GetService().Stop(); AdvancedShutdownAction.CancelPlanOnAppStopping(); if (GlobalConstants.MainConfig?.Data.EnableFloatingWindowFeature == true) { diff --git a/Services/AdaptiveThemeSyncService.cs b/Services/AdaptiveThemeSyncService.cs index b7baa6b..939f9c4 100644 --- a/Services/AdaptiveThemeSyncService.cs +++ b/Services/AdaptiveThemeSyncService.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; -using ClassIsland.Shared; +using System.Windows.Forms; using SystemTools.Shared; namespace SystemTools.Services; @@ -48,7 +48,7 @@ private void OnTick(object? sender, EventArgs e) try { - var targetTheme = DetectThemeByMainWindowBackground(); + var targetTheme = DetectThemeByScreenBackground(); if (targetTheme == null || targetTheme == _lastAppliedTheme) { return; @@ -60,9 +60,10 @@ private void OnTick(object? sender, EventArgs e) return; } + // 仅切换明暗主题,保持主题色来源与强调色不变。 themeService.SetTheme(targetTheme.Value, null); _lastAppliedTheme = targetTheme; - _logger.LogDebug("已自动匹配主题为:{Theme}", targetTheme == 2 ? "黑暗" : "明亮"); + _logger.LogDebug("已自动匹配主题为:{Theme}", targetTheme == 1 ? "黑暗" : "明亮"); } catch (Exception ex) { @@ -70,44 +71,58 @@ private void OnTick(object? sender, EventArgs e) } } - private static int? DetectThemeByMainWindowBackground() + private static int? DetectThemeByScreenBackground() { var handle = Process.GetCurrentProcess().MainWindowHandle; - if (handle == IntPtr.Zero) + if (handle == IntPtr.Zero || !GetWindowRect(handle, out var rect)) { return null; } - if (!GetWindowRect(handle, out var rect)) - { - return null; - } - - var width = Math.Max(1, rect.Right - rect.Left); - var height = Math.Max(1, rect.Bottom - rect.Top); + var mainWindow = Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom); + var screen = Screen.FromRectangle(mainWindow); + var captureRect = BuildTargetArea(screen.Bounds, mainWindow); - using var bitmap = new Bitmap(width, height); + using var bitmap = new Bitmap(captureRect.Width, captureRect.Height); using var graphics = Graphics.FromImage(bitmap); - graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(width, height)); + graphics.CopyFromScreen(captureRect.Left, captureRect.Top, 0, 0, captureRect.Size); - var samples = new (int X, int Y)[] + double luminance = 0; + long samples = 0; + const int grid = 8; + var stepX = Math.Max(1, captureRect.Width / grid); + var stepY = Math.Max(1, captureRect.Height / grid); + + for (var y = 0; y < captureRect.Height; y += stepY) { - (width / 2, height / 2), - (Math.Max(0, width / 4), Math.Max(0, height / 4)), - (Math.Max(0, width * 3 / 4), Math.Max(0, height / 4)), - (Math.Max(0, width / 4), Math.Max(0, height * 3 / 4)), - (Math.Max(0, width * 3 / 4), Math.Max(0, height * 3 / 4)), - }; + for (var x = 0; x < captureRect.Width; x += stepX) + { + var color = bitmap.GetPixel(Math.Clamp(x, 0, captureRect.Width - 1), Math.Clamp(y, 0, captureRect.Height - 1)); + luminance += 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + samples++; + } + } - double luminance = 0; - foreach (var sample in samples) + if (samples == 0) { - var color = bitmap.GetPixel(Math.Clamp(sample.X, 0, width - 1), Math.Clamp(sample.Y, 0, height - 1)); - luminance += 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + return null; } - luminance /= samples.Length; - return luminance < 128 ? 2 : 1; // 2=黑暗,1=明亮 + luminance /= samples; + + // 与 ClassIsland 的主题模式保持一致:0=明亮,1=黑暗。 + return luminance < 128 ? 1 : 0; + } + + private static Rectangle BuildTargetArea(Rectangle screenBounds, Rectangle mainWindow) + { + var topHeight = Math.Max(1, screenBounds.Height / 5); + var bottomY = screenBounds.Bottom - topHeight; + + var isTop = mainWindow.Top + mainWindow.Height / 2 <= screenBounds.Top + screenBounds.Height / 2; + return isTop + ? new Rectangle(screenBounds.Left, screenBounds.Top, screenBounds.Width, topHeight) + : new Rectangle(screenBounds.Left, bottomY, screenBounds.Width, topHeight); } [DllImport("user32.dll")] diff --git a/Services/MainWindowOcclusionAutoHideService.cs b/Services/MainWindowOcclusionAutoHideService.cs new file mode 100644 index 0000000..c93a3f4 --- /dev/null +++ b/Services/MainWindowOcclusionAutoHideService.cs @@ -0,0 +1,172 @@ +using Avalonia.Threading; +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Runtime.InteropServices.WindowsRuntime; +using SystemTools.Shared; +using Windows.Graphics.Imaging; +using Windows.Media.Ocr; +using Windows.Storage.Streams; + +namespace SystemTools.Services; + +public class MainWindowOcclusionAutoHideService(ILogger logger) +{ + private readonly ILogger _logger = logger; + private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromSeconds(2) }; + private readonly SemaphoreSlim _ocrLock = new(1, 1); + private bool? _isHidden; + + public void Start() + { + _timer.Tick -= OnTick; + _timer.Tick += OnTick; + _timer.Start(); + } + + public void Stop() + { + _timer.Stop(); + } + + public void RefreshNow() + { + _ = CheckAndToggleAsync(); + } + + private void OnTick(object? sender, EventArgs e) + { + _ = CheckAndToggleAsync(); + } + + private async Task CheckAndToggleAsync() + { + if (GlobalConstants.MainConfig?.Data.AutoHideMainWindowWhenOccluded != true) + { + return; + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + if (!await _ocrLock.WaitAsync(0)) + { + return; + } + + try + { + var handle = Process.GetCurrentProcess().MainWindowHandle; + if (handle == IntPtr.Zero || !GetWindowRect(handle, out var rect)) + { + return; + } + + var captureRect = BuildCaptureRect(rect); + if (captureRect.Width <= 1 || captureRect.Height <= 1) + { + return; + } + + var textLength = await DetectTextLengthAsync(captureRect); + var shouldHide = textLength > 4; + + if (_isHidden == shouldHide) + { + return; + } + + if (shouldHide) + { + ShowWindow(handle, SW_HIDE); + } + else + { + ShowWindow(handle, SW_SHOWNA); + } + + _isHidden = shouldHide; + _logger.LogDebug("主界面遮挡检测: 文本长度={TextLength}, 动作={Action}", textLength, shouldHide ? "隐藏" : "显示"); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "主界面遮挡检测失败,将在下次重试。"); + } + finally + { + _ocrLock.Release(); + } + } + + private static Rectangle BuildCaptureRect(RECT rect) + { + var screen = Screen.FromRectangle(Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom)); + var bounds = screen.Bounds; + + var windowWidth = Math.Max(1, rect.Right - rect.Left); + var windowHeight = Math.Max(1, rect.Bottom - rect.Top); + + var width = Math.Clamp(windowWidth, 50, bounds.Width); + var height = Math.Clamp(windowHeight / 2, 24, Math.Max(24, bounds.Height / 4)); + + var x = Math.Clamp(rect.Left, bounds.Left, bounds.Right - width); + var y = Math.Clamp(rect.Bottom + 4, bounds.Top, bounds.Bottom - height); + + return new Rectangle(x, y, width, height); + } + + private static async Task DetectTextLengthAsync(Rectangle captureRect) + { + using var bitmap = new Bitmap(captureRect.Width, captureRect.Height); + using (var graphics = Graphics.FromImage(bitmap)) + { + graphics.CopyFromScreen(captureRect.Left, captureRect.Top, 0, 0, captureRect.Size); + } + + using var ms = new MemoryStream(); + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Position = 0; + + using var randomAccessStream = new InMemoryRandomAccessStream(); + await randomAccessStream.WriteAsync(ms.ToArray().AsBuffer()); + randomAccessStream.Seek(0); + + var decoder = await BitmapDecoder.CreateAsync(randomAccessStream); + var softwareBitmap = await decoder.GetSoftwareBitmapAsync(); + + var engine = OcrEngine.TryCreateFromUserProfileLanguages(); + if (engine == null) + { + return 0; + } + + var ocrResult = await engine.RecognizeAsync(softwareBitmap); + var text = (ocrResult.Text ?? string.Empty).Replace("\r", "").Replace("\n", "").Trim(); + return text.Length; + } + + private const int SW_HIDE = 0; + private const int SW_SHOWNA = 8; + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + private struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } +} diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml index 8dc1b25..b8c803b 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml @@ -8,13 +8,23 @@ + Description="每 2 秒检测屏幕顶部/底部五分之一亮度:偏黑切换黑暗,偏白切换明亮。"> + + + + + + diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs index a15fc34..488d1da 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs @@ -28,4 +28,15 @@ private void AutoMatchThemeToggle_OnChanged(object? sender, RoutedEventArgs e) GlobalConstants.MainConfig?.Save(); } + + private void AutoHideMainWindowToggle_OnChanged(object? sender, RoutedEventArgs e) + { + var service = ClassIsland.Shared.IAppHost.GetService(); + if (Config.AutoHideMainWindowWhenOccluded) + { + service.RefreshNow(); + } + + GlobalConstants.MainConfig?.Save(); + } } diff --git a/SystemTools.csproj b/SystemTools.csproj index a93655c..bbbae70 100644 --- a/SystemTools.csproj +++ b/SystemTools.csproj @@ -15,6 +15,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive From d8764d9aafd96499bff28be3f8be20576fe9733c Mon Sep 17 00:00:00 2001 From: Wang Haoyu Date: Sun, 5 Apr 2026 11:57:12 +0800 Subject: [PATCH 2/7] fix: preserve main window handle for occlusion auto-hide --- .../MainWindowOcclusionAutoHideService.cs | 75 ++++++++++++++++--- SystemTools.csproj | 1 - 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/Services/MainWindowOcclusionAutoHideService.cs b/Services/MainWindowOcclusionAutoHideService.cs index c93a3f4..e43d0b8 100644 --- a/Services/MainWindowOcclusionAutoHideService.cs +++ b/Services/MainWindowOcclusionAutoHideService.cs @@ -5,10 +5,10 @@ using System.Drawing; using System.IO; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.WindowsRuntime; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; -using System.Runtime.InteropServices.WindowsRuntime; using SystemTools.Shared; using Windows.Graphics.Imaging; using Windows.Media.Ocr; @@ -21,7 +21,9 @@ public class MainWindowOcclusionAutoHideService(ILogger _logger = logger; private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromSeconds(2) }; private readonly SemaphoreSlim _ocrLock = new(1, 1); + private readonly int _currentProcessId = Process.GetCurrentProcess().Id; private bool? _isHidden; + private IntPtr _cachedMainWindowHandle = IntPtr.Zero; public void Start() { @@ -64,7 +66,7 @@ private async Task CheckAndToggleAsync() try { - var handle = Process.GetCurrentProcess().MainWindowHandle; + var handle = ResolveMainWindowHandle(); if (handle == IntPtr.Zero || !GetWindowRect(handle, out var rect)) { return; @@ -84,15 +86,7 @@ private async Task CheckAndToggleAsync() return; } - if (shouldHide) - { - ShowWindow(handle, SW_HIDE); - } - else - { - ShowWindow(handle, SW_SHOWNA); - } - + ShowWindow(handle, shouldHide ? SW_HIDE : SW_SHOWNA); _isHidden = shouldHide; _logger.LogDebug("主界面遮挡检测: 文本长度={TextLength}, 动作={Action}", textLength, shouldHide ? "隐藏" : "显示"); } @@ -106,6 +100,52 @@ private async Task CheckAndToggleAsync() } } + private IntPtr ResolveMainWindowHandle() + { + if (_cachedMainWindowHandle != IntPtr.Zero && IsWindow(_cachedMainWindowHandle)) + { + return _cachedMainWindowHandle; + } + + var processHandle = Process.GetCurrentProcess().MainWindowHandle; + if (processHandle != IntPtr.Zero && IsWindow(processHandle)) + { + _cachedMainWindowHandle = processHandle; + return processHandle; + } + + var discovered = FindWindowByProcessId(_currentProcessId); + if (discovered != IntPtr.Zero) + { + _cachedMainWindowHandle = discovered; + } + + return discovered; + } + + private static IntPtr FindWindowByProcessId(int processId) + { + IntPtr found = IntPtr.Zero; + EnumWindows((hWnd, _) => + { + if (!IsWindow(hWnd)) + { + return true; + } + + _ = GetWindowThreadProcessId(hWnd, out var windowProcessId); + if (windowProcessId == processId) + { + found = hWnd; + return false; + } + + return true; + }, IntPtr.Zero); + + return found; + } + private static Rectangle BuildCaptureRect(RECT rect) { var screen = Screen.FromRectangle(Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom)); @@ -162,6 +202,19 @@ private static async Task DetectTextLengthAsync(Rectangle captureRect) [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindow(IntPtr hWnd); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + private struct RECT { public int Left; diff --git a/SystemTools.csproj b/SystemTools.csproj index bbbae70..a93655c 100644 --- a/SystemTools.csproj +++ b/SystemTools.csproj @@ -15,7 +15,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive From 9535a51dbc70ed56c6ef34d6d63bb5d235073d20 Mon Sep 17 00:00:00 2001 From: Programmer_MrWang Date: Thu, 9 Apr 2026 00:07:33 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=E5=8D=8A=E6=88=90=E5=93=81?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/AdaptiveThemeSyncService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/AdaptiveThemeSyncService.cs b/Services/AdaptiveThemeSyncService.cs index 939f9c4..97378da 100644 --- a/Services/AdaptiveThemeSyncService.cs +++ b/Services/AdaptiveThemeSyncService.cs @@ -7,6 +7,7 @@ using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; +using ClassIsland.Shared; using SystemTools.Shared; namespace SystemTools.Services; @@ -60,7 +61,6 @@ private void OnTick(object? sender, EventArgs e) return; } - // 仅切换明暗主题,保持主题色来源与强调色不变。 themeService.SetTheme(targetTheme.Value, null); _lastAppliedTheme = targetTheme; _logger.LogDebug("已自动匹配主题为:{Theme}", targetTheme == 1 ? "黑暗" : "明亮"); From e4a207f035156f1caec1f38fe8690f14b8c353dd Mon Sep 17 00:00:00 2001 From: Programmer_MrWang Date: Thu, 9 Apr 2026 00:08:08 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=AB=98?= =?UTF-8?q?=E7=BA=A7=E8=AE=A1=E6=97=B6=E5=85=B3=E6=9C=BAUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Actions/AdvancedShutdownAction.cs | 2 ++ Views/AdvancedShutdownDialog.axaml | 5 ++--- Views/ExtendShutdownDialog.axaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Actions/AdvancedShutdownAction.cs b/Actions/AdvancedShutdownAction.cs index ef5a366..bf7699f 100644 --- a/Actions/AdvancedShutdownAction.cs +++ b/Actions/AdvancedShutdownAction.cs @@ -455,6 +455,8 @@ private void ShowOrUpdateFloatingWindow() Topmost = true, ShowInTaskbar = false, SystemDecorations = SystemDecorations.None, + Background = Brushes.Transparent, + TransparencyLevelHint = [WindowTransparencyLevel.Transparent], Content = new Border { CornerRadius = new CornerRadius(10), diff --git a/Views/AdvancedShutdownDialog.axaml b/Views/AdvancedShutdownDialog.axaml index 705aac2..1b02a52 100644 --- a/Views/AdvancedShutdownDialog.axaml +++ b/Views/AdvancedShutdownDialog.axaml @@ -2,7 +2,7 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ci="http://classisland.tech/schemas/xaml/core" - Width="590" + Width="588" Height="200" WindowStartupLocation="CenterScreen" Topmost="True" @@ -24,8 +24,7 @@ diff --git a/Views/ExtendShutdownDialog.axaml b/Views/ExtendShutdownDialog.axaml index fbb5cfd..d89c808 100644 --- a/Views/ExtendShutdownDialog.axaml +++ b/Views/ExtendShutdownDialog.axaml @@ -2,7 +2,7 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ci="http://classisland.tech/schemas/xaml/core" - Width="270" + Width="268" Height="148" CanResize="False" WindowStartupLocation="CenterOwner" From 34f5f5cdc6dbb262a254c26784ff1833cafa51d7 Mon Sep 17 00:00:00 2001 From: Programmer_MrWang Date: Thu, 9 Apr 2026 00:08:30 +0800 Subject: [PATCH 5/7] =?UTF-8?q?re:=20=E5=9B=9E=E9=80=80OCR=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MainWindowOcclusionAutoHideService.cs | 225 ------------------ .../MoreFeaturesOptionsSettingsPage.axaml | 9 - .../MoreFeaturesOptionsSettingsPage.axaml.cs | 10 - 3 files changed, 244 deletions(-) delete mode 100644 Services/MainWindowOcclusionAutoHideService.cs diff --git a/Services/MainWindowOcclusionAutoHideService.cs b/Services/MainWindowOcclusionAutoHideService.cs deleted file mode 100644 index e43d0b8..0000000 --- a/Services/MainWindowOcclusionAutoHideService.cs +++ /dev/null @@ -1,225 +0,0 @@ -using Avalonia.Threading; -using Microsoft.Extensions.Logging; -using System; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using SystemTools.Shared; -using Windows.Graphics.Imaging; -using Windows.Media.Ocr; -using Windows.Storage.Streams; - -namespace SystemTools.Services; - -public class MainWindowOcclusionAutoHideService(ILogger logger) -{ - private readonly ILogger _logger = logger; - private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromSeconds(2) }; - private readonly SemaphoreSlim _ocrLock = new(1, 1); - private readonly int _currentProcessId = Process.GetCurrentProcess().Id; - private bool? _isHidden; - private IntPtr _cachedMainWindowHandle = IntPtr.Zero; - - public void Start() - { - _timer.Tick -= OnTick; - _timer.Tick += OnTick; - _timer.Start(); - } - - public void Stop() - { - _timer.Stop(); - } - - public void RefreshNow() - { - _ = CheckAndToggleAsync(); - } - - private void OnTick(object? sender, EventArgs e) - { - _ = CheckAndToggleAsync(); - } - - private async Task CheckAndToggleAsync() - { - if (GlobalConstants.MainConfig?.Data.AutoHideMainWindowWhenOccluded != true) - { - return; - } - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return; - } - - if (!await _ocrLock.WaitAsync(0)) - { - return; - } - - try - { - var handle = ResolveMainWindowHandle(); - if (handle == IntPtr.Zero || !GetWindowRect(handle, out var rect)) - { - return; - } - - var captureRect = BuildCaptureRect(rect); - if (captureRect.Width <= 1 || captureRect.Height <= 1) - { - return; - } - - var textLength = await DetectTextLengthAsync(captureRect); - var shouldHide = textLength > 4; - - if (_isHidden == shouldHide) - { - return; - } - - ShowWindow(handle, shouldHide ? SW_HIDE : SW_SHOWNA); - _isHidden = shouldHide; - _logger.LogDebug("主界面遮挡检测: 文本长度={TextLength}, 动作={Action}", textLength, shouldHide ? "隐藏" : "显示"); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "主界面遮挡检测失败,将在下次重试。"); - } - finally - { - _ocrLock.Release(); - } - } - - private IntPtr ResolveMainWindowHandle() - { - if (_cachedMainWindowHandle != IntPtr.Zero && IsWindow(_cachedMainWindowHandle)) - { - return _cachedMainWindowHandle; - } - - var processHandle = Process.GetCurrentProcess().MainWindowHandle; - if (processHandle != IntPtr.Zero && IsWindow(processHandle)) - { - _cachedMainWindowHandle = processHandle; - return processHandle; - } - - var discovered = FindWindowByProcessId(_currentProcessId); - if (discovered != IntPtr.Zero) - { - _cachedMainWindowHandle = discovered; - } - - return discovered; - } - - private static IntPtr FindWindowByProcessId(int processId) - { - IntPtr found = IntPtr.Zero; - EnumWindows((hWnd, _) => - { - if (!IsWindow(hWnd)) - { - return true; - } - - _ = GetWindowThreadProcessId(hWnd, out var windowProcessId); - if (windowProcessId == processId) - { - found = hWnd; - return false; - } - - return true; - }, IntPtr.Zero); - - return found; - } - - private static Rectangle BuildCaptureRect(RECT rect) - { - var screen = Screen.FromRectangle(Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom)); - var bounds = screen.Bounds; - - var windowWidth = Math.Max(1, rect.Right - rect.Left); - var windowHeight = Math.Max(1, rect.Bottom - rect.Top); - - var width = Math.Clamp(windowWidth, 50, bounds.Width); - var height = Math.Clamp(windowHeight / 2, 24, Math.Max(24, bounds.Height / 4)); - - var x = Math.Clamp(rect.Left, bounds.Left, bounds.Right - width); - var y = Math.Clamp(rect.Bottom + 4, bounds.Top, bounds.Bottom - height); - - return new Rectangle(x, y, width, height); - } - - private static async Task DetectTextLengthAsync(Rectangle captureRect) - { - using var bitmap = new Bitmap(captureRect.Width, captureRect.Height); - using (var graphics = Graphics.FromImage(bitmap)) - { - graphics.CopyFromScreen(captureRect.Left, captureRect.Top, 0, 0, captureRect.Size); - } - - using var ms = new MemoryStream(); - bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - ms.Position = 0; - - using var randomAccessStream = new InMemoryRandomAccessStream(); - await randomAccessStream.WriteAsync(ms.ToArray().AsBuffer()); - randomAccessStream.Seek(0); - - var decoder = await BitmapDecoder.CreateAsync(randomAccessStream); - var softwareBitmap = await decoder.GetSoftwareBitmapAsync(); - - var engine = OcrEngine.TryCreateFromUserProfileLanguages(); - if (engine == null) - { - return 0; - } - - var ocrResult = await engine.RecognizeAsync(softwareBitmap); - var text = (ocrResult.Text ?? string.Empty).Replace("\r", "").Replace("\n", "").Trim(); - return text.Length; - } - - private const int SW_HIDE = 0; - private const int SW_SHOWNA = 8; - - [DllImport("user32.dll")] - private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); - - [DllImport("user32.dll")] - private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); - - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); - - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsWindow(IntPtr hWnd); - - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); - - private struct RECT - { - public int Left; - public int Top; - public int Right; - public int Bottom; - } -} diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml index b8c803b..ae3c504 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml @@ -16,15 +16,6 @@ - - - - - diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs index 488d1da..17e53f6 100644 --- a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs @@ -29,14 +29,4 @@ private void AutoMatchThemeToggle_OnChanged(object? sender, RoutedEventArgs e) GlobalConstants.MainConfig?.Save(); } - private void AutoHideMainWindowToggle_OnChanged(object? sender, RoutedEventArgs e) - { - var service = ClassIsland.Shared.IAppHost.GetService(); - if (Config.AutoHideMainWindowWhenOccluded) - { - service.RefreshNow(); - } - - GlobalConstants.MainConfig?.Save(); - } } From 5e60b302f2bddca07f7a455aee5b79a82aa7617b Mon Sep 17 00:00:00 2001 From: Programmer_MrWang Date: Thu, 9 Apr 2026 00:08:46 +0800 Subject: [PATCH 6/7] =?UTF-8?q?re:=20=E5=9B=9E=E9=80=80OCR=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugin.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Plugin.cs b/Plugin.cs index 4fbfd97..8f88328 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -63,7 +63,6 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s services.AddSingleton(GlobalConstants.MainConfig); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); // ========== 注册可选人脸识别 ========== if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -110,7 +109,6 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s IAppHost.GetService().Start(); } IAppHost.GetService().Start(); - IAppHost.GetService().Start(); _logger = IAppHost.GetService>(); _logger?.LogInformation("[SystemTools]实验性功能状态: {Status}", experimentalEnabled); @@ -803,7 +801,6 @@ private void RegisterSettingsPageGroup(IServiceCollection services) private void OnAppStopping(object? sender, EventArgs e) { IAppHost.GetService().Stop(); - IAppHost.GetService().Stop(); AdvancedShutdownAction.CancelPlanOnAppStopping(); if (GlobalConstants.MainConfig?.Data.EnableFloatingWindowFeature == true) { From 3f1b3d76bcc9396c02d3966ba220a024ad09ccdc Mon Sep 17 00:00:00 2001 From: Programmer_MrWang Date: Thu, 9 Apr 2026 00:09:10 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E5=8A=9F=E8=83=BDSettingsExpander?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SettingsPage/SystemToolsSettingsPage.axaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/SettingsPage/SystemToolsSettingsPage.axaml b/SettingsPage/SystemToolsSettingsPage.axaml index 1d6bb2c..ab896fd 100644 --- a/SettingsPage/SystemToolsSettingsPage.axaml +++ b/SettingsPage/SystemToolsSettingsPage.axaml @@ -167,17 +167,8 @@ - - - - - - - - + +