diff --git a/.idea/.idea.SystemTools/.idea/indexLayout.xml b/.idea/.idea.SystemTools/.idea/indexLayout.xml
new file mode 100644
index 00000000..7b08163c
--- /dev/null
+++ b/.idea/.idea.SystemTools/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.SystemTools/.idea/vcs.xml b/.idea/.idea.SystemTools/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/.idea.SystemTools/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.SystemTools/.idea/workspace.xml b/.idea/.idea.SystemTools/.idea/workspace.xml
index 40f53431..efe56f5e 100644
--- a/.idea/.idea.SystemTools/.idea/workspace.xml
+++ b/.idea/.idea.SystemTools/.idea/workspace.xml
@@ -35,6 +35,9 @@
}
}]]>
+
+
+
{
"associatedIndex": 4
}
@@ -62,7 +65,7 @@
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "preferences.pluginManager",
+ "settings.editor.selected.configurable": "preferences.lookFeel",
"vue.rearranger.settings.migration": "true"
}
}
diff --git a/Actions/ActionFlowExecutionConfirmationAction.cs b/Actions/ActionFlowExecutionConfirmationAction.cs
new file mode 100644
index 00000000..d90fdc7f
--- /dev/null
+++ b/Actions/ActionFlowExecutionConfirmationAction.cs
@@ -0,0 +1,388 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Threading;
+using ClassIsland.Core;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Services;
+using ClassIsland.Core.Attributes;
+using FluentAvalonia.UI.Controls;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using SystemTools.Settings;
+using SystemTools.Shared;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.ActionFlowExecutionConfirmation", "行动流执行确认", "\uE01D", false)]
+public class ActionFlowExecutionConfirmationAction(
+ IActionService actionService,
+ ILogger logger)
+ : ActionBase
+{
+ private const string ContinueResult = "continue";
+ private const string DelayResult = "delay";
+ private const string DelayConfirmedResult = "delayConfirmed";
+ private const string ConfirmDelayResult = "confirmDelay";
+ private const string CancelDelayResult = "cancelDelay";
+ private const string StopActionFlowResult = "stopActionFlow";
+ private const string InterruptedResult = "interrupted";
+
+ private FATaskDialog? _activeDialog;
+ private FATaskDialog? _activeDelayDialog;
+ private bool _isDelayDialogOpen;
+
+ protected override async Task OnInvoke()
+ {
+ await base.OnInvoke();
+
+ var outcome = await ShowConfirmationAsync();
+ if (outcome.DelaySeconds is int delaySeconds)
+ {
+ logger.LogInformation("行动流“{ActionSetName}”将在 {DelaySeconds} 秒后继续执行。",
+ ActionSet.Name, delaySeconds);
+
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(delaySeconds), InterruptCancellationToken);
+ }
+ catch (OperationCanceledException) when (InterruptCancellationToken.IsCancellationRequested)
+ {
+ // 行动流已被外部中断,无需继续等待。
+ }
+
+ return;
+ }
+
+ if (!Equals(outcome.Result, StopActionFlowResult))
+ {
+ return;
+ }
+
+ logger.LogInformation("用户停止了行动流“{ActionSetName}”。", ActionSet.Name);
+
+ // InterruptActionSetAsync 会等待当前行动结束,因此这里只发起中断而不等待。
+ _ = actionService.InterruptActionSetAsync(ActionSet);
+ }
+
+ protected override async Task OnInterrupted()
+ {
+ await Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ _activeDelayDialog?.Hide(InterruptedResult);
+ _activeDialog?.Hide(InterruptedResult);
+ });
+ await base.OnInterrupted();
+ }
+
+ private Task ShowConfirmationAsync()
+ {
+ var resultSource = new TaskCompletionSource(
+ TaskCreationOptions.RunContinuationsAsynchronously);
+
+ Dispatcher.UIThread.Post(async () =>
+ {
+ try
+ {
+ if (InterruptCancellationToken.IsCancellationRequested)
+ {
+ resultSource.TrySetResult(new ConfirmationOutcome(InterruptedResult));
+ return;
+ }
+
+ var mainWindow = AppBase.Current.MainWindow
+ ?? throw new InvalidOperationException("ClassIsland 主窗口尚未初始化");
+ var promptName = string.IsNullOrWhiteSpace(Settings.PromptName)
+ ? "未命名自动化"
+ : Settings.PromptName.Trim();
+
+ var dialog = new FATaskDialog
+ {
+ XamlRoot = mainWindow,
+ Title = "SystemTools - 行动流执行确认",
+ Header = "行动流执行确认",
+ SubHeader = $"即将执行自动化“{promptName}”,是否执行?",
+ IconSource = new FAFontIconSource { Glyph = "\uE01D" }
+ };
+ dialog.Buttons.Add(new FATaskDialogButton("停止行动流", StopActionFlowResult));
+ dialog.Buttons.Add(new FATaskDialogButton("延迟执行", DelayResult));
+ dialog.Buttons.Add(new FATaskDialogButton("立即执行", ContinueResult)
+ {
+ IsDefault = true
+ });
+ int? delaySeconds = null;
+ dialog.Opened += (_, _) => RestoreDialogPosition(dialog, mainWindow);
+ dialog.Closing += (sender, e) =>
+ {
+ if (Equals(e.Result, DelayResult))
+ {
+ e.Cancel = true;
+ if (!_isDelayDialogOpen)
+ {
+ _ = HandleDelayRequestAsync(dialog, seconds => delaySeconds = seconds);
+ }
+
+ return;
+ }
+
+ var isAllowedToClose = Equals(e.Result, ContinueResult) ||
+ Equals(e.Result, DelayConfirmedResult) ||
+ Equals(e.Result, StopActionFlowResult) ||
+ Equals(e.Result, InterruptedResult);
+ if (!isAllowedToClose)
+ {
+ e.Cancel = true;
+ return;
+ }
+
+ RememberDialogPosition(dialog);
+ };
+
+ _activeDialog = dialog;
+ var result = await dialog.ShowAsync(showHosted: false);
+ resultSource.TrySetResult(new ConfirmationOutcome(result, delaySeconds));
+ }
+ catch (Exception ex)
+ {
+ resultSource.TrySetException(ex);
+ }
+ finally
+ {
+ _activeDialog = null;
+ }
+ });
+
+ return resultSource.Task;
+ }
+
+ private async Task HandleDelayRequestAsync(FATaskDialog parentDialog, Action setDelaySeconds)
+ {
+ _isDelayDialogOpen = true;
+ try
+ {
+ var delaySeconds = await ShowDelayDialogAsync(parentDialog);
+ if (delaySeconds is not int seconds || InterruptCancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ setDelaySeconds(seconds);
+ parentDialog.Hide(DelayConfirmedResult);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "显示延迟执行设置窗口时发生错误。");
+ }
+ finally
+ {
+ _isDelayDialogOpen = false;
+ }
+ }
+
+ private async Task ShowDelayDialogAsync(FATaskDialog parentDialog)
+ {
+ if (TopLevel.GetTopLevel(parentDialog) is not Window parentWindow)
+ {
+ throw new InvalidOperationException("无法获取行动流执行确认窗口");
+ }
+
+ var secondsInput = new NumericUpDown
+ {
+ Minimum = 1,
+ Maximum = 86400,
+ Increment = 1,
+ Value = 10,
+ FormatString = "F0",
+ Width = 160,
+ HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
+ };
+ var content = new StackPanel { Spacing = 8 };
+ content.Children.Add(new TextBlock { Text = "延迟秒数" });
+ content.Children.Add(secondsInput);
+
+ var dialog = new FATaskDialog
+ {
+ XamlRoot = parentWindow,
+ Title = "SystemTools - 延迟执行",
+ Header = "延迟执行",
+ SubHeader = "设置多少秒后继续执行该行动流。",
+ Content = content,
+ IconSource = new FAFontIconSource { Glyph = "\uE01D" }
+ };
+ dialog.Buttons.Add(new FATaskDialogButton("取消", CancelDelayResult));
+ dialog.Buttons.Add(new FATaskDialogButton("确认", ConfirmDelayResult)
+ {
+ IsDefault = true
+ });
+ dialog.Opened += (_, _) =>
+ {
+ RestoreDelayDialogPosition(dialog, parentWindow);
+ if (TopLevel.GetTopLevel(dialog) is Window window)
+ {
+ window.Topmost = true;
+ window.Activate();
+ }
+ };
+ dialog.Closing += (_, _) => RememberDelayDialogPosition(dialog);
+
+ _activeDelayDialog = dialog;
+ var previousParentTopmost = parentWindow.Topmost;
+ parentWindow.Topmost = false;
+ try
+ {
+ var result = await dialog.ShowAsync(showHosted: false);
+ if (!Equals(result, ConfirmDelayResult))
+ {
+ return null;
+ }
+
+ return Math.Max(1, (int)(secondsInput.Value ?? 10));
+ }
+ finally
+ {
+ _activeDelayDialog = null;
+ parentWindow.Topmost = previousParentTopmost;
+ if (parentWindow.IsVisible)
+ {
+ parentWindow.Activate();
+ }
+ }
+ }
+
+ private static void RestoreDialogPosition(FATaskDialog dialog, Window owner)
+ {
+ if (TopLevel.GetTopLevel(dialog) is not Window window)
+ {
+ return;
+ }
+
+ var scaling = Math.Max(0.5, window.RenderScaling);
+ var widthPx = (int)Math.Round(window.Bounds.Width * scaling);
+ var heightPx = (int)Math.Round(window.Bounds.Height * scaling);
+ if (widthPx <= 0 || heightPx <= 0)
+ {
+ return;
+ }
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config?.ActionFlowExecutionConfirmationPositionX is int savedX &&
+ config.ActionFlowExecutionConfirmationPositionY is int savedY)
+ {
+ var savedPosition = new PixelPoint(savedX, savedY);
+ var savedRect = new PixelRect(savedPosition, new PixelSize(widthPx, heightPx));
+ if (owner.Screens.All.Any(screen => screen.WorkingArea.Intersects(savedRect)))
+ {
+ window.Position = savedPosition;
+ return;
+ }
+ }
+
+ CenterDialogWindow(window, owner, widthPx, heightPx);
+ }
+
+ private static void CenterDialogWindow(Window window, Window owner, int widthPx, int heightPx)
+ {
+ var screen = owner.Screens.ScreenFromWindow(owner) ?? owner.Screens.Primary;
+ if (screen is null)
+ {
+ return;
+ }
+
+ var area = screen.WorkingArea;
+ window.Position = new PixelPoint(
+ area.X + (area.Width - widthPx) / 2,
+ area.Y + (area.Height - heightPx) / 2);
+ }
+
+ private static void CenterDialogOverOwner(FATaskDialog dialog, Window owner)
+ {
+ if (TopLevel.GetTopLevel(dialog) is not Window window)
+ {
+ return;
+ }
+
+ var scaling = Math.Max(0.5, window.RenderScaling);
+ var ownerScaling = Math.Max(0.5, owner.RenderScaling);
+ var widthPx = (int)Math.Round(window.Bounds.Width * scaling);
+ var heightPx = (int)Math.Round(window.Bounds.Height * scaling);
+ var ownerWidthPx = (int)Math.Round(owner.Bounds.Width * ownerScaling);
+ var ownerHeightPx = (int)Math.Round(owner.Bounds.Height * ownerScaling);
+ if (widthPx <= 0 || heightPx <= 0 || ownerWidthPx <= 0 || ownerHeightPx <= 0)
+ {
+ return;
+ }
+
+ var screen = owner.Screens.ScreenFromWindow(owner) ?? owner.Screens.Primary;
+ if (screen is null)
+ {
+ return;
+ }
+
+ var area = screen.WorkingArea;
+ var desiredX = owner.Position.X + (ownerWidthPx - widthPx) / 2;
+ var desiredY = owner.Position.Y + (ownerHeightPx - heightPx) / 2;
+ window.Position = new PixelPoint(
+ Math.Clamp(desiredX, area.X, Math.Max(area.X, area.Right - widthPx)),
+ Math.Clamp(desiredY, area.Y, Math.Max(area.Y, area.Bottom - heightPx)));
+ }
+
+ private static void RestoreDelayDialogPosition(FATaskDialog dialog, Window owner)
+ {
+ if (TopLevel.GetTopLevel(dialog) is not Window window)
+ {
+ return;
+ }
+
+ var scaling = Math.Max(0.5, window.RenderScaling);
+ var widthPx = (int)Math.Round(window.Bounds.Width * scaling);
+ var heightPx = (int)Math.Round(window.Bounds.Height * scaling);
+ if (widthPx <= 0 || heightPx <= 0)
+ {
+ return;
+ }
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config?.ActionFlowExecutionDelayPositionX is int savedX &&
+ config.ActionFlowExecutionDelayPositionY is int savedY)
+ {
+ var savedPosition = new PixelPoint(savedX, savedY);
+ var savedRect = new PixelRect(savedPosition, new PixelSize(widthPx, heightPx));
+ if (owner.Screens.All.Any(screen => screen.WorkingArea.Intersects(savedRect)))
+ {
+ window.Position = savedPosition;
+ return;
+ }
+ }
+
+ CenterDialogOverOwner(dialog, owner);
+ }
+
+ private static void RememberDialogPosition(FATaskDialog dialog)
+ {
+ if (TopLevel.GetTopLevel(dialog) is not Window window ||
+ GlobalConstants.MainConfig is not { } config)
+ {
+ return;
+ }
+
+ config.Data.ActionFlowExecutionConfirmationPositionX = window.Position.X;
+ config.Data.ActionFlowExecutionConfirmationPositionY = window.Position.Y;
+ config.Save();
+ }
+
+ private static void RememberDelayDialogPosition(FATaskDialog dialog)
+ {
+ if (TopLevel.GetTopLevel(dialog) is not Window window ||
+ GlobalConstants.MainConfig is not { } config)
+ {
+ return;
+ }
+
+ config.Data.ActionFlowExecutionDelayPositionX = window.Position.X;
+ config.Data.ActionFlowExecutionDelayPositionY = window.Position.Y;
+ config.Save();
+ }
+
+ private sealed record ConfirmationOutcome(object Result, int? DelaySeconds = null);
+}
diff --git a/Actions/AdjustScreenBrightnessAction.cs b/Actions/AdjustScreenBrightnessAction.cs
index bbe5fe5c..2d5d5aa0 100644
--- a/Actions/AdjustScreenBrightnessAction.cs
+++ b/Actions/AdjustScreenBrightnessAction.cs
@@ -5,6 +5,9 @@
using System.Management;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using SystemTools.Shared;
namespace SystemTools.Actions;
@@ -51,6 +54,12 @@ await Task.Run(() =>
throw;
}
});
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行调整屏幕亮度操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/AdvancedShutdownAction.cs b/Actions/AdvancedShutdownAction.cs
index bf7699fc..1d6c3a6f 100644
--- a/Actions/AdvancedShutdownAction.cs
+++ b/Actions/AdvancedShutdownAction.cs
@@ -454,7 +454,7 @@ private void ShowOrUpdateFloatingWindow()
CanResize = false,
Topmost = true,
ShowInTaskbar = false,
- SystemDecorations = SystemDecorations.None,
+ WindowDecorations = WindowDecorations.None,
Background = Brushes.Transparent,
TransparencyLevelHint = [WindowTransparencyLevel.Transparent],
Content = new Border
diff --git a/Actions/AutoHideMainWindowWhenOccludedAction.cs b/Actions/AutoHideMainWindowWhenOccludedAction.cs
new file mode 100644
index 00000000..96e46276
--- /dev/null
+++ b/Actions/AutoHideMainWindowWhenOccludedAction.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+using SystemTools.Settings;
+using SystemTools.Shared;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.AutoHideMainWindowWhenOccluded", "遮挡文字时隐藏主界面", "\uEEE3", false)]
+public class AutoHideMainWindowWhenOccludedAction(ILogger logger) : ActionBase
+{
+ private readonly ILogger _logger = logger;
+
+ protected override async Task OnInvoke()
+ {
+ _logger.LogDebug("AutoHideMainWindowWhenOccludedAction OnInvoke 开始");
+
+ if (Settings == null) return;
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config == null) return;
+
+ try
+ {
+ config.AutoHideMainWindowWhenOccluded = Settings.Enable;
+ IAppHost.GetService().ApplyConfig();
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogInformation("已{State}遮挡文字时隐藏主界面功能", Settings.Enable ? "开启" : "关闭");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "设置遮挡文字时隐藏主界面功能失败");
+ throw;
+ }
+
+ if (Settings.NotifyOnExecute)
+ {
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask(
+ (Settings.Enable ? "已开启功能 " : "已关闭功能 ") + "遮挡文字时隐藏主界面", "\uEEE3", "")
+ });
+ }
+
+ await base.OnInvoke();
+ _logger.LogDebug("AutoHideMainWindowWhenOccludedAction OnInvoke 完成");
+ }
+}
\ No newline at end of file
diff --git a/Actions/AutoOpenUsbDriveOnInsertAction.cs b/Actions/AutoOpenUsbDriveOnInsertAction.cs
new file mode 100644
index 00000000..e0d276be
--- /dev/null
+++ b/Actions/AutoOpenUsbDriveOnInsertAction.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+using SystemTools.Settings;
+using SystemTools.Shared;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.AutoOpenUsbDriveOnInsert", "自动播放", "\uEE81", false)]
+public class AutoOpenUsbDriveOnInsertAction(ILogger logger) : ActionBase
+{
+ private readonly ILogger _logger = logger;
+
+ protected override async Task OnInvoke()
+ {
+ _logger.LogDebug("AutoOpenUsbDriveOnInsertAction OnInvoke 开始");
+
+ if (Settings == null) return;
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config == null) return;
+
+ try
+ {
+ config.AutoOpenUsbDriveOnInsert = Settings.Enable;
+ IAppHost.GetService().ApplyConfig();
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogInformation("已{State}自动播放功能", Settings.Enable ? "开启" : "关闭");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "设置自动播放功能失败");
+ throw;
+ }
+
+ if (Settings.NotifyOnExecute)
+ {
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask(
+ (Settings.Enable ? "已开启功能 " : "已关闭功能 ") + "自动播放", "\uEE81", "")
+ });
+ }
+
+ await base.OnInvoke();
+ _logger.LogDebug("AutoOpenUsbDriveOnInsertAction OnInvoke 完成");
+ }
+}
\ No newline at end of file
diff --git a/Actions/AutoSwitchClassIslandThemeAction.cs b/Actions/AutoSwitchClassIslandThemeAction.cs
new file mode 100644
index 00000000..058e3942
--- /dev/null
+++ b/Actions/AutoSwitchClassIslandThemeAction.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+using SystemTools.Settings;
+using SystemTools.Shared;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.AutoSwitchClassIslandTheme", "自动切换 ClassIsland 主题", "\uE5CB", false)]
+public class AutoSwitchClassIslandThemeAction(ILogger logger) : ActionBase
+{
+ private readonly ILogger _logger = logger;
+
+ protected override async Task OnInvoke()
+ {
+ _logger.LogDebug("AutoSwitchClassIslandThemeAction OnInvoke 开始");
+
+ if (Settings == null) return;
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config == null) return;
+
+ try
+ {
+ config.AutoSwitchClassIslandTheme = Settings.Enable;
+ IAppHost.GetService().ApplyConfig();
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogInformation("已{State}自动切换 ClassIsland 主题功能", Settings.Enable ? "开启" : "关闭");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "设置自动切换 ClassIsland 主题功能失败");
+ throw;
+ }
+
+ if (Settings.NotifyOnExecute)
+ {
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask(
+ (Settings.Enable ? "已开启功能 " : "已关闭功能 ") + "自动切换 ClassIsland 主题", "\uE5CB", "")
+ });
+ }
+
+ await base.OnInvoke();
+ _logger.LogDebug("AutoSwitchClassIslandThemeAction OnInvoke 完成");
+ }
+}
\ No newline at end of file
diff --git a/Actions/BackgroundPlayAudioAction.cs b/Actions/BackgroundPlayAudioAction.cs
index af538829..859725e4 100644
--- a/Actions/BackgroundPlayAudioAction.cs
+++ b/Actions/BackgroundPlayAudioAction.cs
@@ -7,6 +7,8 @@
using System.IO;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
namespace SystemTools.Actions;
diff --git a/Actions/BlackScreenHtmlAction.cs b/Actions/BlackScreenHtmlAction.cs
index 965c326a..4ace34f5 100644
--- a/Actions/BlackScreenHtmlAction.cs
+++ b/Actions/BlackScreenHtmlAction.cs
@@ -1,6 +1,10 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Diagnostics;
using System.IO;
@@ -11,7 +15,7 @@
namespace SystemTools.Actions;
[ActionInfo("SystemTools.BlackScreenHtml", "黑屏html", "\uE643", false)]
-public class BlackScreenHtmlAction(ILogger logger) : ActionBase
+public class BlackScreenHtmlAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -69,6 +73,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "执行黑屏html失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行黑屏操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("BlackScreenHtmlAction OnInvoke 完成");
diff --git a/Actions/CancelShutdownAction.cs b/Actions/CancelShutdownAction.cs
index f325fe38..180126ec 100644
--- a/Actions/CancelShutdownAction.cs
+++ b/Actions/CancelShutdownAction.cs
@@ -1,6 +1,10 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
@@ -8,7 +12,7 @@
namespace SystemTools.Actions;
[ActionInfo("SystemTools.CancelShutdown", "取消关机计划", "\uE4CC", false)]
-public class CancelShutdownAction(ILogger logger) : ActionBase
+public class CancelShutdownAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -38,6 +42,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "取消关机失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已取消关机计划", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("CancelShutdownAction OnInvoke 完成");
diff --git a/Actions/ChangeWallpaperAction.cs b/Actions/ChangeWallpaperAction.cs
index 3509bdf0..f2994684 100644
--- a/Actions/ChangeWallpaperAction.cs
+++ b/Actions/ChangeWallpaperAction.cs
@@ -1,4 +1,4 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
@@ -8,6 +8,9 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using Windows.Win32;
namespace SystemTools.Actions;
@@ -75,6 +78,12 @@ protected override async Task OnInvoke()
// 保持向上抛出异常,让上层 UI/宿主决定如何反馈给用户
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换桌面壁纸", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ChangeWallpaperAction OnInvoke 完成");
diff --git a/Actions/ClearAllNotificationsAction.cs b/Actions/ClearAllNotificationsAction.cs
index 742f18b3..677bfe40 100644
--- a/Actions/ClearAllNotificationsAction.cs
+++ b/Actions/ClearAllNotificationsAction.cs
@@ -2,14 +2,17 @@
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System.Reflection;
using System.Threading.Tasks;
-using ClassIsland.Shared;
namespace SystemTools.Actions;
[ActionInfo("SystemTools.ClearAllNotifications", "清除全部提醒", "\uE029", false)]
-public class ClearAllNotificationsAction(ILogger logger) : ActionBase
+public class ClearAllNotificationsAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -29,6 +32,12 @@ protected override async Task OnInvoke()
method.Invoke(notificationHostService, null);
_logger.LogInformation("已调用 ClassIsland 提醒系统的清除全部提醒");
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动清除全部提醒", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ClearAllNotificationsAction OnInvoke 完成");
diff --git a/Actions/CloneDisplayAction.cs b/Actions/CloneDisplayAction.cs
index bd95df79..30e5ce84 100644
--- a/Actions/CloneDisplayAction.cs
+++ b/Actions/CloneDisplayAction.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -11,7 +15,7 @@ namespace SystemTools.Actions;
/// 复制屏幕
///
[ActionInfo("SystemTools.CloneDisplay", "复制屏幕", "\uE635", false)]
-public class CloneDisplayAction(ILogger logger) : ActionBase
+public class CloneDisplayAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -54,6 +58,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "复制屏幕失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行复制屏幕操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/DisableDeviceAction.cs b/Actions/DisableDeviceAction.cs
index 07c1d1b7..116ba962 100644
--- a/Actions/DisableDeviceAction.cs
+++ b/Actions/DisableDeviceAction.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
@@ -6,6 +6,9 @@
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -73,6 +76,12 @@ net session >nul 2>&1
_logger.LogError(ex, "禁用设备失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动禁用硬件设备", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("DisableDeviceAction OnInvoke 完成");
diff --git a/Actions/EnableDeviceAction.cs b/Actions/EnableDeviceAction.cs
index 68ea078b..04092b59 100644
--- a/Actions/EnableDeviceAction.cs
+++ b/Actions/EnableDeviceAction.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
@@ -6,6 +6,9 @@
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -73,6 +76,12 @@ net session >nul 2>&1
_logger.LogError(ex, "启用设备失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动启用硬件设备", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("EnableDeviceAction OnInvoke 完成");
diff --git a/Actions/EnableVoiceWakeAiAction.cs b/Actions/EnableVoiceWakeAiAction.cs
new file mode 100644
index 00000000..bfc23fac
--- /dev/null
+++ b/Actions/EnableVoiceWakeAiAction.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+using SystemTools.Settings;
+using SystemTools.Shared;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.EnableVoiceWakeAi", "启用语音唤醒 AI", "\uED53", false)]
+public class EnableVoiceWakeAiAction(ILogger logger) : ActionBase
+{
+ private readonly ILogger _logger = logger;
+
+ protected override async Task OnInvoke()
+ {
+ _logger.LogDebug("EnableVoiceWakeAiAction OnInvoke 开始");
+
+ if (Settings == null) return;
+
+ var config = GlobalConstants.MainConfig?.Data;
+ if (config == null) return;
+
+ try
+ {
+ config.EnableVoiceWakeAi = Settings.Enable;
+ var service = IAppHost.TryGetService();
+ if (service == null)
+ {
+ config.EnableVoiceWakeAi = false;
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogWarning("AI 服务尚未加载,无法{State}语音唤醒 AI", Settings.Enable ? "开启" : "关闭");
+ return;
+ }
+
+ service.ApplyConfig();
+ if (Settings.Enable && !service.IsWakeWordEnabled)
+ {
+ config.EnableVoiceWakeAi = false;
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogWarning("语音唤醒 AI 未能启动:{Error}", service.LastError ?? "未知错误");
+ return;
+ }
+
+ GlobalConstants.MainConfig?.Save();
+ _logger.LogInformation("已{State}语音唤醒 AI 功能", Settings.Enable ? "开启" : "关闭");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "设置语音唤醒 AI 功能失败");
+ throw;
+ }
+
+ if (Settings.NotifyOnExecute)
+ {
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask(
+ (Settings.Enable ? "已开启功能 " : "已关闭功能 ") + "启用语音唤醒 AI", "\uED53", "")
+ });
+ }
+
+ await base.OnInvoke();
+ _logger.LogDebug("EnableVoiceWakeAiAction OnInvoke 完成");
+ }
+}
\ No newline at end of file
diff --git a/Actions/ExtendDisplayAction.cs b/Actions/ExtendDisplayAction.cs
index a181836e..880e1687 100644
--- a/Actions/ExtendDisplayAction.cs
+++ b/Actions/ExtendDisplayAction.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -11,7 +15,7 @@ namespace SystemTools.Actions;
/// 扩展屏幕
///
[ActionInfo("SystemTools.ExtendDisplay", "扩展屏幕", "\uE647", false)]
-public class ExtendDisplayAction(ILogger logger) : ActionBase
+public class ExtendDisplayAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -54,6 +58,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "扩展屏幕失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行扩展屏幕操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/ExternalDisplayAction.cs b/Actions/ExternalDisplayAction.cs
index ed6b9864..105d2e54 100644
--- a/Actions/ExternalDisplayAction.cs
+++ b/Actions/ExternalDisplayAction.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -11,7 +15,7 @@ namespace SystemTools.Actions;
/// 仅第二屏幕
///
[ActionInfo("SystemTools.ExternalDisplay", "仅第二屏幕", "\uE641", false)]
-public class ExternalDisplayAction(ILogger logger) : ActionBase
+public class ExternalDisplayAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -54,6 +58,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "仅第二屏幕失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行仅第二屏幕操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/FullscreenClockAction.cs b/Actions/FullscreenClockAction.cs
index 8fd9fbb7..52178814 100644
--- a/Actions/FullscreenClockAction.cs
+++ b/Actions/FullscreenClockAction.cs
@@ -1,11 +1,9 @@
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
-using Windows.Win32;
namespace SystemTools.Actions;
@@ -25,26 +23,11 @@ protected override async Task OnInvoke()
var psi = new ProcessStartInfo
{
- FileName = "cmd",
- Arguments = $"/c start \"\" \"{ClockUrl}\"",
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden
+ FileName = ClockUrl,
+ UseShellExecute = true
};
Process.Start(psi);
-
- _logger.LogInformation("等待3秒后发送F11全屏键");
-
- await Task.Delay(3000);
-
- _logger.LogDebug("发送F11键");
- PInvoke.keybd_event(VK_F11, 0, 0, UIntPtr.Zero);
- await Task.Delay(20);
- PInvoke.keybd_event(VK_F11, 0, Windows.Win32.UI.Input.KeyboardAndMouse.KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP,
- UIntPtr.Zero);
-
- _logger.LogInformation("F11全屏键已发送");
}
catch (Exception ex)
{
@@ -55,10 +38,4 @@ protected override async Task OnInvoke()
await base.OnInvoke();
_logger.LogDebug("FullscreenClockAction OnInvoke 完成");
}
-
- //[DllImport("user32.dll", SetLastError = true)]
- //private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
-
- private const byte VK_F11 = 0x7A;
- //private const uint KEYEVENTF_KEYUP = 0x0002;
-}
\ No newline at end of file
+}
diff --git a/Actions/InternalDisplayAction.cs b/Actions/InternalDisplayAction.cs
index 2d8440f3..c91e345f 100644
--- a/Actions/InternalDisplayAction.cs
+++ b/Actions/InternalDisplayAction.cs
@@ -1,9 +1,13 @@
-using System;
+using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -11,7 +15,7 @@ namespace SystemTools.Actions;
/// 仅电脑屏幕
///
[ActionInfo("SystemTools.InternalDisplay", "仅电脑屏幕", "\uE62F", false)]
-public class InternalDisplayAction(ILogger logger) : ActionBase
+public class InternalDisplayAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -54,6 +58,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "仅电脑屏幕失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行仅电脑屏幕操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/KillProcessAction.cs b/Actions/KillProcessAction.cs
index cb9308f1..13d85c7c 100644
--- a/Actions/KillProcessAction.cs
+++ b/Actions/KillProcessAction.cs
@@ -1,4 +1,4 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
@@ -6,6 +6,9 @@
using System.Net.Http;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -80,6 +83,12 @@ protected override async Task OnInvoke()
// _logger.LogWarning("终止进程 {ProcessName} 可能失败,退出码: {ExitCode}, 错误: {Error}",
// processName, process.ExitCode, error);
//}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行退出进程操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("KillProcessAction OnInvoke 完成");
diff --git a/Actions/LoadTemporaryClassPlanAction.cs b/Actions/LoadTemporaryClassPlanAction.cs
index d5fc783e..a8386477 100644
--- a/Actions/LoadTemporaryClassPlanAction.cs
+++ b/Actions/LoadTemporaryClassPlanAction.cs
@@ -6,6 +6,9 @@
using System.Collections.Concurrent;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -46,6 +49,12 @@ protected override async Task OnInvoke()
_profileService.Profile.TempClassPlanSetupTime = _exactTimeService.GetCurrentLocalDateTime();
_profileService.SaveProfile();
_logger.LogInformation("已加载临时课表:{ClassPlanName} ({ClassPlanId})", classPlan.Name, classPlanId);
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动加载临时课表", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/LockScreenAction.cs b/Actions/LockScreenAction.cs
index 0a89b8a1..6f488c37 100644
--- a/Actions/LockScreenAction.cs
+++ b/Actions/LockScreenAction.cs
@@ -1,6 +1,10 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
@@ -8,7 +12,7 @@
namespace SystemTools.Actions;
[ActionInfo("SystemTools.LockScreen", "锁定屏幕", "\uEAF0", false)]
-public class LockScreenAction(ILogger logger) : ActionBase
+public class LockScreenAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -38,6 +42,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "锁定屏幕失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已锁定屏幕", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("LockScreenAction OnInvoke 完成");
diff --git a/Actions/OpenAppSettingsAction.cs b/Actions/OpenAppSettingsAction.cs
index 845f1e20..18c5a50e 100644
--- a/Actions/OpenAppSettingsAction.cs
+++ b/Actions/OpenAppSettingsAction.cs
@@ -2,6 +2,10 @@
using ClassIsland.Core.Abstractions.Services;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Threading.Tasks;
@@ -10,7 +14,7 @@ namespace SystemTools.Actions;
[ActionInfo("SystemTools.OpenAppSettings", "打开应用设置", "\uEF27", false)]
public class OpenAppSettingsAction(
ILogger logger,
- IUriNavigationService uriNavigationService) : ActionBase
+ IUriNavigationService uriNavigationService) : ActionBase
{
private readonly ILogger _logger = logger;
private readonly IUriNavigationService _uriNavigationService = uriNavigationService;
@@ -19,6 +23,12 @@ protected override Task OnInvoke()
{
_logger.LogInformation("正在打开 ClassIsland 应用设置窗口");
_uriNavigationService.NavigateWrapped(new Uri("classisland://app/settings"));
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动打开应用设置", "\uE9FB", "")
+ });
+
return base.OnInvoke();
}
}
diff --git a/Actions/OpenClassSwapWindowAction.cs b/Actions/OpenClassSwapWindowAction.cs
index c26535af..aa880b37 100644
--- a/Actions/OpenClassSwapWindowAction.cs
+++ b/Actions/OpenClassSwapWindowAction.cs
@@ -2,6 +2,10 @@
using ClassIsland.Core.Abstractions.Services;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Threading.Tasks;
@@ -10,7 +14,7 @@ namespace SystemTools.Actions;
[ActionInfo("SystemTools.OpenClassSwapWindow", "打开换课窗口", "\uE13B", false)]
public class OpenClassSwapWindowAction(
ILogger logger,
- IUriNavigationService uriNavigationService) : ActionBase
+ IUriNavigationService uriNavigationService) : ActionBase
{
private readonly ILogger _logger = logger;
private readonly IUriNavigationService _uriNavigationService = uriNavigationService;
@@ -19,6 +23,12 @@ protected override Task OnInvoke()
{
_logger.LogInformation("正在打开 ClassIsland 换课窗口");
_uriNavigationService.NavigateWrapped(new Uri("classisland://app/class-swap"));
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动打开换课窗口", "\uE9FB", "")
+ });
+
return base.OnInvoke();
}
}
diff --git a/Actions/OpenProfileEditorAction.cs b/Actions/OpenProfileEditorAction.cs
index 9a444405..fed3997f 100644
--- a/Actions/OpenProfileEditorAction.cs
+++ b/Actions/OpenProfileEditorAction.cs
@@ -2,6 +2,10 @@
using ClassIsland.Core.Abstractions.Services;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Threading.Tasks;
@@ -10,7 +14,7 @@ namespace SystemTools.Actions;
[ActionInfo("SystemTools.OpenProfileEditor", "打开档案编辑", "\uE699", false)]
public class OpenProfileEditorAction(
ILogger logger,
- IUriNavigationService uriNavigationService) : ActionBase
+ IUriNavigationService uriNavigationService) : ActionBase
{
private readonly ILogger _logger = logger;
private readonly IUriNavigationService _uriNavigationService = uriNavigationService;
@@ -19,6 +23,12 @@ protected override Task OnInvoke()
{
_logger.LogInformation("正在打开 ClassIsland 档案编辑窗口");
_uriNavigationService.NavigateWrapped(new Uri("classisland://app/profile"));
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动打开档案编辑", "\uE9FB", "")
+ });
+
return base.OnInvoke();
}
}
diff --git a/Actions/ScreenShotAction.cs b/Actions/ScreenShotAction.cs
index 032d0b2e..35f2b09a 100644
--- a/Actions/ScreenShotAction.cs
+++ b/Actions/ScreenShotAction.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
@@ -8,6 +8,9 @@
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -58,6 +61,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "屏幕截图失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动执行屏幕截图", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ScreenShotAction OnInvoke 完成");
diff --git a/Actions/SetVolume.cs b/Actions/SetVolume.cs
index f783166e..bddc4e8c 100644
--- a/Actions/SetVolume.cs
+++ b/Actions/SetVolume.cs
@@ -1,6 +1,10 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@@ -31,6 +35,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "设置音量失败");
throw;
}
+
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动调整系统音量", "\uE9FB", "")
+ });
}
}
diff --git a/Actions/ShowAiChatDialogAction.cs b/Actions/ShowAiChatDialogAction.cs
new file mode 100644
index 00000000..3530758e
--- /dev/null
+++ b/Actions/ShowAiChatDialogAction.cs
@@ -0,0 +1,20 @@
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.ShowAiChatDialog", "显示AI对话框", "\uE8C3", false)]
+public class ShowAiChatDialogAction(
+ ILogger logger,
+ AiChatWindowService aiChatWindowService) : ActionBase
+{
+ protected override async Task OnInvoke()
+ {
+ logger.LogInformation("正在显示 AI 对话框");
+ await aiChatWindowService.ShowAsync();
+ await base.OnInvoke();
+ }
+}
diff --git a/Actions/ShowDesktopAction.cs b/Actions/ShowDesktopAction.cs
index 0cec00df..aa79f704 100644
--- a/Actions/ShowDesktopAction.cs
+++ b/Actions/ShowDesktopAction.cs
@@ -1,6 +1,10 @@
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using SystemTools.Settings;
+using ClassIsland.Shared;
using System;
using System.Threading.Tasks;
using Windows.Win32;
@@ -9,7 +13,7 @@
namespace SystemTools.Actions;
[ActionInfo("SystemTools.ShowDesktop", "显示桌面", "\uE62F", false)]
-public class ShowDesktopAction(ILogger logger) : ActionBase
+public class ShowDesktopAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
@@ -39,6 +43,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "发送 Win+D 失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行显示桌面操作", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/ShowFloatingWindowAction.cs b/Actions/ShowFloatingWindowAction.cs
index 94aceae3..0006c51a 100644
--- a/Actions/ShowFloatingWindowAction.cs
+++ b/Actions/ShowFloatingWindowAction.cs
@@ -6,6 +6,8 @@
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
using SystemTools.Shared;
namespace SystemTools.Actions;
@@ -55,6 +57,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "更新悬浮窗状态失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动显示悬浮窗", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ShowFloatingWindowAction OnInvoke 完成");
diff --git a/Actions/ShutdownAction.cs b/Actions/ShutdownAction.cs
index 4f921a92..fca7244e 100644
--- a/Actions/ShutdownAction.cs
+++ b/Actions/ShutdownAction.cs
@@ -1,4 +1,4 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
@@ -6,6 +6,9 @@
using System.Threading.Tasks;
using System.Windows.Forms;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -45,6 +48,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "执行关机失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已执行计时关机", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/SimulateKeyCombinationAction.cs b/Actions/SimulateKeyCombinationAction.cs
new file mode 100644
index 00000000..bbede1c5
--- /dev/null
+++ b/Actions/SimulateKeyCombinationAction.cs
@@ -0,0 +1,80 @@
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using ClassIsland.Core.Models.Notification;
+using ClassIsland.Shared;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using SystemTools.Services;
+using SystemTools.Settings;
+using Windows.Win32;
+using Windows.Win32.UI.Input.KeyboardAndMouse;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.SimulateKeyCombination", "模拟组合键", "\uEA15", false)]
+public class SimulateKeyCombinationAction(ILogger logger) : ActionBase
+{
+ private readonly ILogger _logger = logger;
+ private const int KeyEventDelay = 20;
+ private const int KeyHoldDelay = 40;
+ private const int MaxKeyCount = 5;
+
+ protected override async Task OnInvoke()
+ {
+ _logger.LogDebug("SimulateKeyCombinationAction OnInvoke 开始");
+
+ var keys = Settings?.Keys
+ .Where(x => x.KeyCode is > 0)
+ .Take(MaxKeyCount)
+ .Select(x => x.KeyCode!.Value)
+ .ToList() ?? [];
+
+ if (keys.Count == 0)
+ {
+ _logger.LogWarning("没有录入的组合键按键");
+ return;
+ }
+
+ var pressedKeys = new List();
+ try
+ {
+ _logger.LogInformation("正在模拟同时按下 {Count} 个按键", keys.Count);
+
+ foreach (var keyCode in keys)
+ {
+ PInvoke.keybd_event(keyCode, 0, 0, UIntPtr.Zero);
+ pressedKeys.Add(keyCode);
+ await Task.Delay(KeyEventDelay);
+ }
+
+ await Task.Delay(KeyHoldDelay);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "模拟组合键失败");
+ throw;
+ }
+ finally
+ {
+ for (var i = pressedKeys.Count - 1; i >= 0; i--)
+ {
+ PInvoke.keybd_event(pressedKeys[i], 0, KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP, UIntPtr.Zero);
+ await Task.Delay(KeyEventDelay);
+ }
+ }
+
+ if (Settings?.NotifyOnExecute == true)
+ {
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已完成模拟组合键", "\uE9FB", "")
+ });
+ }
+
+ await base.OnInvoke();
+ _logger.LogDebug("SimulateKeyCombinationAction OnInvoke 完成");
+ }
+}
\ No newline at end of file
diff --git a/Actions/SimulateKeyboardAction.cs b/Actions/SimulateKeyboardAction.cs
index a38e6f9a..5be86e1c 100644
--- a/Actions/SimulateKeyboardAction.cs
+++ b/Actions/SimulateKeyboardAction.cs
@@ -1,10 +1,13 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using Windows.Win32;
namespace SystemTools.Actions;
@@ -67,6 +70,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "模拟键盘失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已完成模拟键盘输入", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("SimulateKeyboardAction OnInvoke 完成");
diff --git a/Actions/SimulateMouseAction.cs b/Actions/SimulateMouseAction.cs
index 70bbeffa..a0a7e26c 100644
--- a/Actions/SimulateMouseAction.cs
+++ b/Actions/SimulateMouseAction.cs
@@ -1,4 +1,4 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
@@ -7,6 +7,9 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using Windows.Win32;
namespace SystemTools.Actions;
@@ -176,6 +179,12 @@ protected override async Task OnInvoke()
await ExecuteBatchFile("huifu.bat", "启用鼠标");
}
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已结束自动操作自动化", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("SimulateMouseAction OnInvoke 完成");
diff --git a/Actions/SwitchFloatingWindowThemeAction.cs b/Actions/SwitchFloatingWindowThemeAction.cs
index f33b6aed..502d01c1 100644
--- a/Actions/SwitchFloatingWindowThemeAction.cs
+++ b/Actions/SwitchFloatingWindowThemeAction.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
using SystemTools.Shared;
namespace SystemTools.Actions;
@@ -55,6 +56,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "切换悬浮窗主题失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换悬浮窗主题", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("SwitchFloatingWindowThemeAction OnInvoke 完成");
@@ -90,6 +97,7 @@ private static string GetThemeName(int theme)
0 => "跟随系统",
1 => "浅色",
2 => "深色",
+ 3 => "自适应背景",
_ => "未知"
};
}
diff --git a/Actions/SwitchThemeAction.cs b/Actions/SwitchThemeAction.cs
index 119f94be..c63aa9ee 100644
--- a/Actions/SwitchThemeAction.cs
+++ b/Actions/SwitchThemeAction.cs
@@ -1,4 +1,4 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
@@ -6,6 +6,9 @@
using System.Diagnostics;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
namespace SystemTools.Actions;
@@ -34,6 +37,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "切换主题失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换系统主题色", "\uE9FB", "")
+ });
+
await base.OnInvoke();
}
diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs
index d4dad2ca..56f5535a 100644
--- a/Actions/ToggleFloatingWindowLayerAction.cs
+++ b/Actions/ToggleFloatingWindowLayerAction.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
using SystemTools.Shared;
namespace SystemTools.Actions;
@@ -61,6 +62,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "切换悬浮窗层级失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换悬浮窗层级", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 完成");
diff --git a/Actions/ToggleFloatingWindowProfileAction.cs b/Actions/ToggleFloatingWindowProfileAction.cs
index fae43bba..82eb502f 100644
--- a/Actions/ToggleFloatingWindowProfileAction.cs
+++ b/Actions/ToggleFloatingWindowProfileAction.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using SystemTools.Services;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
namespace SystemTools.Actions;
@@ -56,6 +57,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "切换悬浮窗配置方案失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换悬浮窗配置方案", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 完成");
diff --git a/Actions/ToggleWorkflowAction.cs b/Actions/ToggleWorkflowAction.cs
index 86fef99d..a7515235 100644
--- a/Actions/ToggleWorkflowAction.cs
+++ b/Actions/ToggleWorkflowAction.cs
@@ -12,7 +12,7 @@
namespace SystemTools.Actions;
-[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE8B8", false)]
+[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE051", false)]
public class ToggleWorkflowAction(ILogger logger) : ActionBase
{
private readonly ILogger _logger = logger;
diff --git a/Actions/TypeContentAction.cs b/Actions/TypeContentAction.cs
index 3f059ac5..905b34ee 100644
--- a/Actions/TypeContentAction.cs
+++ b/Actions/TypeContentAction.cs
@@ -1,10 +1,13 @@
-using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -49,6 +52,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "键入内容失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动键入内容", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("OnInvoke 完成");
diff --git a/Actions/WakeUpVoiceConversationAiAction.cs b/Actions/WakeUpVoiceConversationAiAction.cs
new file mode 100644
index 00000000..42e336f3
--- /dev/null
+++ b/Actions/WakeUpVoiceConversationAiAction.cs
@@ -0,0 +1,25 @@
+using System.Threading.Tasks;
+using ClassIsland.Core.Abstractions.Automation;
+using ClassIsland.Core.Attributes;
+using Microsoft.Extensions.Logging;
+using SystemTools.Services;
+
+namespace SystemTools.Actions;
+
+[ActionInfo("SystemTools.WakeUpVoiceConversationAi", "唤醒语音对话 AI", "\uEFF9", false)]
+public class WakeUpVoiceConversationAiAction(
+ ILogger logger,
+ AiVoiceConversationService voiceConversationService) : ActionBase
+{
+ protected override async Task OnInvoke()
+ {
+ logger.LogInformation("正在通过行动唤醒语音对话 AI");
+
+ if (!voiceConversationService.TryStartVoiceConversation())
+ {
+ logger.LogWarning("唤醒语音对话 AI 失败:{Error}", voiceConversationService.LastError ?? "未知错误");
+ }
+
+ await base.OnInvoke();
+ }
+}
\ No newline at end of file
diff --git a/Actions/WindowOperationAction.cs b/Actions/WindowOperationAction.cs
index 2baa059d..66ccd024 100644
--- a/Actions/WindowOperationAction.cs
+++ b/Actions/WindowOperationAction.cs
@@ -1,10 +1,13 @@
-using System;
+using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Attributes;
using Microsoft.Extensions.Logging;
using SystemTools.Settings;
+using ClassIsland.Core.Models.Notification;
+using SystemTools.Services;
+using ClassIsland.Shared;
using Windows.Win32;
namespace SystemTools.Actions;
@@ -52,6 +55,12 @@ protected override async Task OnInvoke()
_logger.LogError(ex, "窗口操作失败");
throw;
}
+ if (Settings.NotifyOnExecute)
+ IAppHost.GetService()?.ShowNotification(new NotificationRequest
+ {
+ MaskContent = NotificationContent.CreateTwoIconsMask("已自动切换窗口样式", "\uE9FB", "")
+ });
+
await base.OnInvoke();
_logger.LogDebug("WindowOperationAction OnInvoke 完成");
diff --git a/Config/MainWindowClickTriggerConfig.cs b/Config/MainWindowClickTriggerConfig.cs
new file mode 100644
index 00000000..dc68a73e
--- /dev/null
+++ b/Config/MainWindowClickTriggerConfig.cs
@@ -0,0 +1,5 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace SystemTools.Triggers;
+
+public sealed class MainWindowClickTriggerConfig : ObservableRecipient;
diff --git a/ConfigHandlers/LiquidGlassButtonSettings.cs b/ConfigHandlers/LiquidGlassButtonSettings.cs
new file mode 100644
index 00000000..537dbdad
--- /dev/null
+++ b/ConfigHandlers/LiquidGlassButtonSettings.cs
@@ -0,0 +1,85 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace SystemTools.ConfigHandlers;
+
+///
+/// Persisted interaction and shadow settings shared by the approval buttons.
+///
+public sealed class LiquidGlassButtonSettings : INotifyPropertyChanged
+{
+ private double _scaleDip = 3.5;
+ private bool _interactiveHighlightEnabled = true;
+ private bool _shadowEnabled = true;
+ private double _shadowRadius = 14;
+ private double _shadowOffsetX;
+ private double _shadowOffsetY = 2;
+ private double _shadowOpacity = 0.55;
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ [JsonPropertyName("scaleDip")]
+ public double ScaleDip
+ {
+ get => _scaleDip;
+ set => Set(ref _scaleDip, Clamp(value, 0, 12));
+ }
+
+ [JsonPropertyName("interactiveHighlightEnabled")]
+ public bool InteractiveHighlightEnabled
+ {
+ get => _interactiveHighlightEnabled;
+ set => Set(ref _interactiveHighlightEnabled, value);
+ }
+
+ [JsonPropertyName("shadowEnabled")]
+ public bool ShadowEnabled
+ {
+ get => _shadowEnabled;
+ set => Set(ref _shadowEnabled, value);
+ }
+
+ [JsonPropertyName("shadowRadius")]
+ public double ShadowRadius
+ {
+ get => _shadowRadius;
+ set => Set(ref _shadowRadius, Clamp(value, 0, 64));
+ }
+
+ [JsonPropertyName("shadowOffsetX")]
+ public double ShadowOffsetX
+ {
+ get => _shadowOffsetX;
+ set => Set(ref _shadowOffsetX, Clamp(value, -32, 32));
+ }
+
+ [JsonPropertyName("shadowOffsetY")]
+ public double ShadowOffsetY
+ {
+ get => _shadowOffsetY;
+ set => Set(ref _shadowOffsetY, Clamp(value, -32, 32));
+ }
+
+ [JsonPropertyName("shadowOpacity")]
+ public double ShadowOpacity
+ {
+ get => _shadowOpacity;
+ set => Set(ref _shadowOpacity, Clamp(value, 0, 1));
+ }
+
+ private void Set(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (Equals(field, value))
+ {
+ return;
+ }
+
+ field = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ private static double Clamp(double value, double minimum, double maximum) =>
+ double.IsFinite(value) ? Math.Clamp(value, minimum, maximum) : minimum;
+}
diff --git a/ConfigHandlers/LiquidGlassSettings.cs b/ConfigHandlers/LiquidGlassSettings.cs
new file mode 100644
index 00000000..b3d2d339
--- /dev/null
+++ b/ConfigHandlers/LiquidGlassSettings.cs
@@ -0,0 +1,331 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace SystemTools.ConfigHandlers;
+
+public sealed class LiquidGlassSettings : INotifyPropertyChanged
+{
+ private bool _suppressNotifications;
+ private double _cornerRadius = 18;
+ private double _backdropRefreshIntervalMs = 50;
+ private double _backdropZoom = 1;
+ private double _backdropOffsetX;
+ private double _backdropOffsetY;
+ private double _refractionHeight = 12;
+ private double _refractionAmount = 24;
+ private bool _depthEffect;
+ private bool _chromaticAberration;
+ private double _blurRadius = 2;
+ private double _vibrancy = 1.5;
+ private double _brightness;
+ private double _contrast = 1;
+ private double _exposureEv;
+ private double _gammaPower = 1;
+ private double _backdropOpacity = 1;
+ private string _tintColor = "#00000000";
+ private string _surfaceColor = "#00000000";
+ private bool _progressiveBlurEnabled;
+ private double _progressiveBlurStart = 0.5;
+ private double _progressiveBlurEnd = 1;
+ private string _progressiveTintColor = "#00000000";
+ private double _progressiveTintIntensity = 0.8;
+ private bool _adaptiveLuminanceEnabled;
+ private double _adaptiveLuminanceUpdateIntervalMs = 250;
+ private double _adaptiveLuminanceSmoothing = 0.2;
+ private bool _highlightEnabled = true;
+ private double _highlightWidth = 0.5;
+ private double _highlightBlurRadius = 0.25;
+ private double _highlightOpacity = 0.5;
+ private double _highlightAngle = 45;
+ private double _highlightFalloff = 1;
+ private bool _shadowEnabled = true;
+ private double _shadowRadius = 24;
+ private double _shadowOffsetX;
+ private double _shadowOffsetY = 4;
+ private string _shadowColor = "#1A000000";
+ private double _shadowOpacity = 1;
+ private bool _innerShadowEnabled;
+ private double _innerShadowRadius = 24;
+ private double _innerShadowOffsetX;
+ private double _innerShadowOffsetY = 24;
+ private string _innerShadowColor = "#26000000";
+ private double _innerShadowOpacity = 1;
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ [JsonPropertyName("cornerRadius")]
+ public double CornerRadius { get => _cornerRadius; set => Set(ref _cornerRadius, Clamp(value, 0, 96)); }
+
+ [JsonPropertyName("backdropRefreshIntervalMs")]
+ public double BackdropRefreshIntervalMs { get => _backdropRefreshIntervalMs; set => Set(ref _backdropRefreshIntervalMs, Clamp(value, 5, 200)); }
+
+ [JsonPropertyName("backdropZoom")]
+ public double BackdropZoom { get => _backdropZoom; set => Set(ref _backdropZoom, Clamp(value, 0.1, 10)); }
+
+ [JsonPropertyName("backdropOffsetX")]
+ public double BackdropOffsetX { get => _backdropOffsetX; set => Set(ref _backdropOffsetX, Clamp(value, -500, 500)); }
+
+ [JsonPropertyName("backdropOffsetY")]
+ public double BackdropOffsetY { get => _backdropOffsetY; set => Set(ref _backdropOffsetY, Clamp(value, -500, 500)); }
+
+ [JsonPropertyName("refractionHeight")]
+ public double RefractionHeight { get => _refractionHeight; set => Set(ref _refractionHeight, Clamp(value, 0, 100)); }
+
+ [JsonPropertyName("refractionAmount")]
+ public double RefractionAmount { get => _refractionAmount; set => Set(ref _refractionAmount, Clamp(value, 0, 200)); }
+
+ [JsonPropertyName("depthEffect")]
+ public bool DepthEffect { get => _depthEffect; set => Set(ref _depthEffect, value); }
+
+ [JsonPropertyName("chromaticAberration")]
+ public bool ChromaticAberration { get => _chromaticAberration; set => Set(ref _chromaticAberration, value); }
+
+ [JsonPropertyName("blurRadius")]
+ public double BlurRadius { get => _blurRadius; set => Set(ref _blurRadius, Clamp(value, 0, 64)); }
+
+ [JsonPropertyName("vibrancy")]
+ public double Vibrancy { get => _vibrancy; set => Set(ref _vibrancy, Clamp(value, 0, 4)); }
+
+ [JsonPropertyName("brightness")]
+ public double Brightness { get => _brightness; set => Set(ref _brightness, Clamp(value, -1, 1)); }
+
+ [JsonPropertyName("contrast")]
+ public double Contrast { get => _contrast; set => Set(ref _contrast, Clamp(value, 0, 4)); }
+
+ [JsonPropertyName("exposureEv")]
+ public double ExposureEv { get => _exposureEv; set => Set(ref _exposureEv, Clamp(value, -4, 4)); }
+
+ [JsonPropertyName("gammaPower")]
+ public double GammaPower { get => _gammaPower; set => Set(ref _gammaPower, Clamp(value, 0.1, 4)); }
+
+ [JsonPropertyName("backdropOpacity")]
+ public double BackdropOpacity { get => _backdropOpacity; set => Set(ref _backdropOpacity, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("tintColor")]
+ public string TintColor { get => _tintColor; set => Set(ref _tintColor, NormalizeColor(value)); }
+
+ [JsonPropertyName("surfaceColor")]
+ public string SurfaceColor { get => _surfaceColor; set => Set(ref _surfaceColor, NormalizeColor(value)); }
+
+ [JsonPropertyName("progressiveBlurEnabled")]
+ public bool ProgressiveBlurEnabled { get => _progressiveBlurEnabled; set => Set(ref _progressiveBlurEnabled, value); }
+
+ [JsonPropertyName("progressiveBlurStart")]
+ public double ProgressiveBlurStart { get => _progressiveBlurStart; set => Set(ref _progressiveBlurStart, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("progressiveBlurEnd")]
+ public double ProgressiveBlurEnd { get => _progressiveBlurEnd; set => Set(ref _progressiveBlurEnd, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("progressiveTintColor")]
+ public string ProgressiveTintColor { get => _progressiveTintColor; set => Set(ref _progressiveTintColor, NormalizeColor(value)); }
+
+ [JsonPropertyName("progressiveTintIntensity")]
+ public double ProgressiveTintIntensity { get => _progressiveTintIntensity; set => Set(ref _progressiveTintIntensity, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("adaptiveLuminanceEnabled")]
+ public bool AdaptiveLuminanceEnabled { get => _adaptiveLuminanceEnabled; set => Set(ref _adaptiveLuminanceEnabled, value); }
+
+ [JsonPropertyName("adaptiveLuminanceUpdateIntervalMs")]
+ public double AdaptiveLuminanceUpdateIntervalMs { get => _adaptiveLuminanceUpdateIntervalMs; set => Set(ref _adaptiveLuminanceUpdateIntervalMs, Clamp(value, 16, 5000)); }
+
+ [JsonPropertyName("adaptiveLuminanceSmoothing")]
+ public double AdaptiveLuminanceSmoothing { get => _adaptiveLuminanceSmoothing; set => Set(ref _adaptiveLuminanceSmoothing, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("highlightEnabled")]
+ public bool HighlightEnabled { get => _highlightEnabled; set => Set(ref _highlightEnabled, value); }
+
+ [JsonPropertyName("highlightWidth")]
+ public double HighlightWidth { get => _highlightWidth; set => Set(ref _highlightWidth, Clamp(value, 0, 12)); }
+
+ [JsonPropertyName("highlightBlurRadius")]
+ public double HighlightBlurRadius { get => _highlightBlurRadius; set => Set(ref _highlightBlurRadius, Clamp(value, 0, 12)); }
+
+ [JsonPropertyName("highlightOpacity")]
+ public double HighlightOpacity { get => _highlightOpacity; set => Set(ref _highlightOpacity, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("highlightAngle")]
+ public double HighlightAngle { get => _highlightAngle; set => Set(ref _highlightAngle, Clamp(value, 0, 360)); }
+
+ [JsonPropertyName("highlightFalloff")]
+ public double HighlightFalloff { get => _highlightFalloff; set => Set(ref _highlightFalloff, Clamp(value, 0, 8)); }
+
+ [JsonPropertyName("shadowEnabled")]
+ public bool ShadowEnabled { get => _shadowEnabled; set => Set(ref _shadowEnabled, value); }
+
+ [JsonPropertyName("shadowRadius")]
+ public double ShadowRadius { get => _shadowRadius; set => Set(ref _shadowRadius, Clamp(value, 0, 128)); }
+
+ [JsonPropertyName("shadowOffsetX")]
+ public double ShadowOffsetX { get => _shadowOffsetX; set => Set(ref _shadowOffsetX, Clamp(value, -200, 200)); }
+
+ [JsonPropertyName("shadowOffsetY")]
+ public double ShadowOffsetY { get => _shadowOffsetY; set => Set(ref _shadowOffsetY, Clamp(value, -200, 200)); }
+
+ [JsonPropertyName("shadowColor")]
+ public string ShadowColor { get => _shadowColor; set => Set(ref _shadowColor, NormalizeColor(value)); }
+
+ [JsonPropertyName("shadowOpacity")]
+ public double ShadowOpacity { get => _shadowOpacity; set => Set(ref _shadowOpacity, Clamp(value, 0, 1)); }
+
+ [JsonPropertyName("innerShadowEnabled")]
+ public bool InnerShadowEnabled { get => _innerShadowEnabled; set => Set(ref _innerShadowEnabled, value); }
+
+ [JsonPropertyName("innerShadowRadius")]
+ public double InnerShadowRadius { get => _innerShadowRadius; set => Set(ref _innerShadowRadius, Clamp(value, 0, 128)); }
+
+ [JsonPropertyName("innerShadowOffsetX")]
+ public double InnerShadowOffsetX { get => _innerShadowOffsetX; set => Set(ref _innerShadowOffsetX, Clamp(value, -200, 200)); }
+
+ [JsonPropertyName("innerShadowOffsetY")]
+ public double InnerShadowOffsetY { get => _innerShadowOffsetY; set => Set(ref _innerShadowOffsetY, Clamp(value, -200, 200)); }
+
+ [JsonPropertyName("innerShadowColor")]
+ public string InnerShadowColor { get => _innerShadowColor; set => Set(ref _innerShadowColor, NormalizeColor(value)); }
+
+ [JsonPropertyName("innerShadowOpacity")]
+ public double InnerShadowOpacity { get => _innerShadowOpacity; set => Set(ref _innerShadowOpacity, Clamp(value, 0, 1)); }
+
+ public void Reset()
+ {
+ _suppressNotifications = true;
+ try
+ {
+ CornerRadius = 18;
+ BackdropRefreshIntervalMs = 50;
+ BackdropZoom = 1;
+ BackdropOffsetX = 0;
+ BackdropOffsetY = 0;
+ RefractionHeight = 12;
+ RefractionAmount = 24;
+ DepthEffect = false;
+ ChromaticAberration = false;
+ BlurRadius = 2;
+ Vibrancy = 1.5;
+ Brightness = 0;
+ Contrast = 1;
+ ExposureEv = 0;
+ GammaPower = 1;
+ BackdropOpacity = 1;
+ TintColor = "#00000000";
+ SurfaceColor = "#00000000";
+ ProgressiveBlurEnabled = false;
+ ProgressiveBlurStart = 0.5;
+ ProgressiveBlurEnd = 1;
+ ProgressiveTintColor = "#00000000";
+ ProgressiveTintIntensity = 0.8;
+ AdaptiveLuminanceEnabled = false;
+ AdaptiveLuminanceUpdateIntervalMs = 250;
+ AdaptiveLuminanceSmoothing = 0.2;
+ HighlightEnabled = true;
+ HighlightWidth = 0.5;
+ HighlightBlurRadius = 0.25;
+ HighlightOpacity = 0.5;
+ HighlightAngle = 45;
+ HighlightFalloff = 1;
+ ShadowEnabled = true;
+ ShadowRadius = 24;
+ ShadowOffsetX = 0;
+ ShadowOffsetY = 4;
+ ShadowColor = "#1A000000";
+ ShadowOpacity = 1;
+ InnerShadowEnabled = false;
+ InnerShadowRadius = 24;
+ InnerShadowOffsetX = 0;
+ InnerShadowOffsetY = 24;
+ InnerShadowColor = "#26000000";
+ InnerShadowOpacity = 1;
+ }
+ finally
+ {
+ _suppressNotifications = false;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
+ }
+
+ ///
+ /// Replaces all persisted liquid-glass values in one notification batch.
+ ///
+ public void CopyFrom(LiquidGlassSettings source)
+ {
+ ArgumentNullException.ThrowIfNull(source);
+
+ _suppressNotifications = true;
+ try
+ {
+ CornerRadius = source.CornerRadius;
+ BackdropRefreshIntervalMs = source.BackdropRefreshIntervalMs;
+ BackdropZoom = source.BackdropZoom;
+ BackdropOffsetX = source.BackdropOffsetX;
+ BackdropOffsetY = source.BackdropOffsetY;
+ RefractionHeight = source.RefractionHeight;
+ RefractionAmount = source.RefractionAmount;
+ DepthEffect = source.DepthEffect;
+ ChromaticAberration = source.ChromaticAberration;
+ BlurRadius = source.BlurRadius;
+ Vibrancy = source.Vibrancy;
+ Brightness = source.Brightness;
+ Contrast = source.Contrast;
+ ExposureEv = source.ExposureEv;
+ GammaPower = source.GammaPower;
+ BackdropOpacity = source.BackdropOpacity;
+ TintColor = source.TintColor;
+ SurfaceColor = source.SurfaceColor;
+ ProgressiveBlurEnabled = source.ProgressiveBlurEnabled;
+ ProgressiveBlurStart = source.ProgressiveBlurStart;
+ ProgressiveBlurEnd = source.ProgressiveBlurEnd;
+ ProgressiveTintColor = source.ProgressiveTintColor;
+ ProgressiveTintIntensity = source.ProgressiveTintIntensity;
+ AdaptiveLuminanceEnabled = source.AdaptiveLuminanceEnabled;
+ AdaptiveLuminanceUpdateIntervalMs = source.AdaptiveLuminanceUpdateIntervalMs;
+ AdaptiveLuminanceSmoothing = source.AdaptiveLuminanceSmoothing;
+ HighlightEnabled = source.HighlightEnabled;
+ HighlightWidth = source.HighlightWidth;
+ HighlightBlurRadius = source.HighlightBlurRadius;
+ HighlightOpacity = source.HighlightOpacity;
+ HighlightAngle = source.HighlightAngle;
+ HighlightFalloff = source.HighlightFalloff;
+ ShadowEnabled = source.ShadowEnabled;
+ ShadowRadius = source.ShadowRadius;
+ ShadowOffsetX = source.ShadowOffsetX;
+ ShadowOffsetY = source.ShadowOffsetY;
+ ShadowColor = source.ShadowColor;
+ ShadowOpacity = source.ShadowOpacity;
+ InnerShadowEnabled = source.InnerShadowEnabled;
+ InnerShadowRadius = source.InnerShadowRadius;
+ InnerShadowOffsetX = source.InnerShadowOffsetX;
+ InnerShadowOffsetY = source.InnerShadowOffsetY;
+ InnerShadowColor = source.InnerShadowColor;
+ InnerShadowOpacity = source.InnerShadowOpacity;
+ }
+ finally
+ {
+ _suppressNotifications = false;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
+ }
+
+ private bool Set(ref T field, T value, [CallerMemberName] string? propertyName = null)
+ {
+ if (Equals(field, value))
+ {
+ return false;
+ }
+
+ field = value;
+ if (!_suppressNotifications)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ return true;
+ }
+
+ private static double Clamp(double value, double minimum, double maximum) =>
+ double.IsFinite(value) ? Math.Clamp(value, minimum, maximum) : minimum;
+
+ private static string NormalizeColor(string? value) => value?.Trim() ?? string.Empty;
+}
diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs
index d764aae5..516b2e94 100644
--- a/ConfigHandlers/MainConfigData.cs
+++ b/ConfigHandlers/MainConfigData.cs
@@ -10,6 +10,13 @@ namespace SystemTools.ConfigHandlers;
public class MainConfigData : INotifyPropertyChanged
{
+ public MainConfigData()
+ {
+ _aiConversationLiquidGlass.PropertyChanged += OnLiquidGlassSettingsPropertyChanged;
+ _aiConversationApprovalButtonGlass.PropertyChanged += OnApprovalButtonGlassSettingsPropertyChanged;
+ _floatingWindowLiquidGlass.PropertyChanged += OnFloatingWindowLiquidGlassSettingsPropertyChanged;
+ }
+
public event EventHandler? RestartPropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
@@ -88,16 +95,31 @@ public bool EnableFaceRecognition
}
}
- bool _autoMatchMainBackgroundTheme;
+ bool _enableWindowsHello;
- [JsonPropertyName("autoMatchMainBackgroundTheme")]
- public bool AutoMatchMainBackgroundTheme
+ [JsonPropertyName("enableWindowsHello")]
+ public bool EnableWindowsHello
{
- get => _autoMatchMainBackgroundTheme;
+ get => _enableWindowsHello;
set
{
- if (value == _autoMatchMainBackgroundTheme) return;
- _autoMatchMainBackgroundTheme = value;
+ if (value == _enableWindowsHello) return;
+ _enableWindowsHello = value;
+ OnPropertyChanged();
+ RestartPropertyChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ bool _autoSwitchClassIslandTheme;
+
+ [JsonPropertyName("autoSwitchClassIslandTheme")]
+ public bool AutoSwitchClassIslandTheme
+ {
+ get => _autoSwitchClassIslandTheme;
+ set
+ {
+ if (value == _autoSwitchClassIslandTheme) return;
+ _autoSwitchClassIslandTheme = value;
OnPropertyChanged();
}
}
@@ -132,6 +154,35 @@ public bool AutoCleanupClassIslandMemory
}
}
+ bool _autoCleanupSystemMemory;
+
+ [JsonPropertyName("autoCleanupSystemMemory")]
+ public bool AutoCleanupSystemMemory
+ {
+ get => _autoCleanupSystemMemory;
+ set
+ {
+ if (value == _autoCleanupSystemMemory) return;
+ _autoCleanupSystemMemory = value;
+ OnPropertyChanged();
+ }
+ }
+
+ int _systemMemoryCleanupThresholdPercent = 90;
+
+ [JsonPropertyName("systemMemoryCleanupThresholdPercent")]
+ public int SystemMemoryCleanupThresholdPercent
+ {
+ get => _systemMemoryCleanupThresholdPercent;
+ set
+ {
+ var clamped = Math.Clamp(value, 50, 99);
+ if (clamped == _systemMemoryCleanupThresholdPercent) return;
+ _systemMemoryCleanupThresholdPercent = clamped;
+ OnPropertyChanged();
+ }
+ }
+
bool _autoHideMainWindowWhenOccluded;
[JsonPropertyName("autoHideMainWindowWhenOccluded")]
@@ -146,6 +197,173 @@ public bool AutoHideMainWindowWhenOccluded
}
}
+ bool _enableAiService;
+
+ [JsonPropertyName("enableAiService")]
+ public bool EnableAiService
+ {
+ get => _enableAiService;
+ set
+ {
+ if (value == _enableAiService) return;
+ _enableAiService = value;
+ OnPropertyChanged();
+ RestartPropertyChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ int _aiConversationFloatingWindowStyle;
+
+ [JsonPropertyName("aiConversationFloatingWindowStyle")]
+ public int AiConversationFloatingWindowStyle
+ {
+ get => _aiConversationFloatingWindowStyle;
+ set
+ {
+ var normalized = value == 1 ? 1 : 0;
+ if (normalized == _aiConversationFloatingWindowStyle) return;
+ _aiConversationFloatingWindowStyle = normalized;
+ OnPropertyChanged();
+ }
+ }
+
+ LiquidGlassSettings _aiConversationLiquidGlass = new();
+
+ [JsonPropertyName("aiConversationLiquidGlass")]
+ public LiquidGlassSettings AiConversationLiquidGlass
+ {
+ get => _aiConversationLiquidGlass;
+ set
+ {
+ value ??= new LiquidGlassSettings();
+ if (ReferenceEquals(value, _aiConversationLiquidGlass)) return;
+ _aiConversationLiquidGlass.PropertyChanged -= OnLiquidGlassSettingsPropertyChanged;
+ _aiConversationLiquidGlass = value;
+ _aiConversationLiquidGlass.PropertyChanged += OnLiquidGlassSettingsPropertyChanged;
+ OnPropertyChanged();
+ }
+ }
+
+ private LiquidGlassButtonSettings _aiConversationApprovalButtonGlass = new();
+
+ [JsonPropertyName("aiConversationApprovalButtonGlass")]
+ public LiquidGlassButtonSettings AiConversationApprovalButtonGlass
+ {
+ get => _aiConversationApprovalButtonGlass;
+ set
+ {
+ value ??= new LiquidGlassButtonSettings();
+ if (ReferenceEquals(value, _aiConversationApprovalButtonGlass)) return;
+ _aiConversationApprovalButtonGlass.PropertyChanged -= OnApprovalButtonGlassSettingsPropertyChanged;
+ _aiConversationApprovalButtonGlass = value;
+ _aiConversationApprovalButtonGlass.PropertyChanged += OnApprovalButtonGlassSettingsPropertyChanged;
+ OnPropertyChanged();
+ }
+ }
+
+ string _aiProviderName = "OpenAI";
+
+ [JsonPropertyName("aiProviderName")]
+ public string AiProviderName
+ {
+ get => _aiProviderName;
+ set
+ {
+ value ??= string.Empty;
+ if (string.Equals(value, _aiProviderName, StringComparison.Ordinal)) return;
+ _aiProviderName = value;
+ OnPropertyChanged();
+ }
+ }
+
+ string _aiApiKey = string.Empty;
+
+ [JsonPropertyName("aiApiKey")]
+ public string AiApiKey
+ {
+ get => _aiApiKey;
+ set
+ {
+ value ??= string.Empty;
+ if (string.Equals(value, _aiApiKey, StringComparison.Ordinal)) return;
+ _aiApiKey = value;
+ OnPropertyChanged();
+ }
+ }
+
+ string _aiApiUrl = "https://api.openai.com/v1";
+
+ [JsonPropertyName("aiApiUrl")]
+ public string AiApiUrl
+ {
+ get => _aiApiUrl;
+ set
+ {
+ value ??= string.Empty;
+ if (string.Equals(value, _aiApiUrl, StringComparison.Ordinal)) return;
+ _aiApiUrl = value;
+ OnPropertyChanged();
+ }
+ }
+
+ string _aiModel = string.Empty;
+
+ [JsonPropertyName("aiModel")]
+ public string AiModel
+ {
+ get => _aiModel;
+ set
+ {
+ value ??= string.Empty;
+ if (string.Equals(value, _aiModel, StringComparison.Ordinal)) return;
+ _aiModel = value;
+ OnPropertyChanged();
+ }
+ }
+
+ bool _shareAiRepliesWithClassIslandNotifications;
+
+ [JsonPropertyName("shareAiRepliesWithClassIslandNotifications")]
+ public bool ShareAiRepliesWithClassIslandNotifications
+ {
+ get => _shareAiRepliesWithClassIslandNotifications;
+ set
+ {
+ if (value == _shareAiRepliesWithClassIslandNotifications) return;
+ _shareAiRepliesWithClassIslandNotifications = value;
+ OnPropertyChanged();
+ }
+ }
+
+ bool _enableVoiceWakeAi;
+
+ [JsonPropertyName("enableVoiceWakeAi")]
+ public bool EnableVoiceWakeAi
+ {
+ get => _enableVoiceWakeAi;
+ set
+ {
+ if (value == _enableVoiceWakeAi) return;
+ _enableVoiceWakeAi = value;
+ OnPropertyChanged();
+ }
+ }
+
+ string _aiWakeWord = "你好ci";
+
+ [JsonPropertyName("aiWakeWord")]
+ public string AiWakeWord
+ {
+ get => _aiWakeWord;
+ set
+ {
+ value = string.IsNullOrWhiteSpace(value) ? "你好ci" : value.Trim();
+ if (string.Equals(value, _aiWakeWord, StringComparison.Ordinal)) return;
+ _aiWakeWord = value;
+ OnPropertyChanged();
+ }
+ }
+
// ========== 公告相关 ==========
/*string _lastAcceptedAnnouncement = string.Empty;
@@ -281,7 +499,7 @@ public int FloatingWindowTheme
get => _floatingWindowTheme;
set
{
- var normalized = value is 1 or 2 ? value : 0;
+ var normalized = value is 1 or 2 or 3 ? value : 0;
if (normalized == _floatingWindowTheme) return;
_floatingWindowTheme = normalized;
OnPropertyChanged();
@@ -316,6 +534,18 @@ public int FloatingWindowPositionY
}
}
+ [JsonPropertyName("actionFlowExecutionConfirmationPositionX")]
+ public int? ActionFlowExecutionConfirmationPositionX { get; set; }
+
+ [JsonPropertyName("actionFlowExecutionConfirmationPositionY")]
+ public int? ActionFlowExecutionConfirmationPositionY { get; set; }
+
+ [JsonPropertyName("actionFlowExecutionDelayPositionX")]
+ public int? ActionFlowExecutionDelayPositionX { get; set; }
+
+ [JsonPropertyName("actionFlowExecutionDelayPositionY")]
+ public int? ActionFlowExecutionDelayPositionY { get; set; }
+
int _floatingWindowLayer = 1;
[JsonPropertyName("floatingWindowLayer")]
@@ -428,4 +658,83 @@ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+ private int _floatingWindowAppearanceStyle = 1;
+
+ [JsonPropertyName("floatingWindowAppearanceStyle")]
+ public int FloatingWindowAppearanceStyle
+ {
+ get => _floatingWindowAppearanceStyle;
+ set
+ {
+ var normalized = value == 1 ? 1 : 0;
+ if (normalized == _floatingWindowAppearanceStyle) return;
+ _floatingWindowAppearanceStyle = normalized;
+ OnPropertyChanged();
+ }
+ }
+
+ private LiquidGlassSettings _floatingWindowLiquidGlass = CreateFloatingWindowLiquidGlassDefaults();
+
+ [JsonPropertyName("floatingWindowLiquidGlass")]
+ public LiquidGlassSettings FloatingWindowLiquidGlass
+ {
+ get => _floatingWindowLiquidGlass;
+ set
+ {
+ value ??= CreateFloatingWindowLiquidGlassDefaults();
+ if (ReferenceEquals(value, _floatingWindowLiquidGlass)) return;
+ _floatingWindowLiquidGlass.PropertyChanged -= OnFloatingWindowLiquidGlassSettingsPropertyChanged;
+ _floatingWindowLiquidGlass = value;
+ _floatingWindowLiquidGlass.PropertyChanged += OnFloatingWindowLiquidGlassSettingsPropertyChanged;
+ OnPropertyChanged();
+ }
+ }
+
+ private double _floatingWindowGlassButtonScaleDip = 3.5;
+
+ [JsonPropertyName("floatingWindowGlassButtonScaleDip")]
+ public double FloatingWindowGlassButtonScaleDip
+ {
+ get => _floatingWindowGlassButtonScaleDip;
+ set
+ {
+ var clamped = Math.Clamp(value, 0, 12);
+ if (Math.Abs(clamped - _floatingWindowGlassButtonScaleDip) < 0.0001) return;
+ _floatingWindowGlassButtonScaleDip = clamped;
+ OnPropertyChanged();
+ }
+ }
+
+ private void OnLiquidGlassSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) =>
+ OnPropertyChanged(nameof(AiConversationLiquidGlass));
+
+ private void OnApprovalButtonGlassSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) =>
+ OnPropertyChanged(nameof(AiConversationApprovalButtonGlass));
+
+ private void OnFloatingWindowLiquidGlassSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) =>
+ OnPropertyChanged(nameof(FloatingWindowLiquidGlass));
+
+ private static LiquidGlassSettings CreateFloatingWindowLiquidGlassDefaults()
+ {
+ return new LiquidGlassSettings
+ {
+ CornerRadius = 20,
+ BackdropRefreshIntervalMs = 50,
+ RefractionHeight = 10,
+ RefractionAmount = 20,
+ BlurRadius = 4,
+ Vibrancy = 1.25,
+ BackdropOpacity = 0.96,
+ HighlightEnabled = true,
+ HighlightWidth = 0.5,
+ HighlightBlurRadius = 0.3,
+ HighlightOpacity = 0.65,
+ ShadowEnabled = true,
+ ShadowRadius = 20,
+ ShadowOffsetY = 4,
+ ShadowColor = "#40000000",
+ ShadowOpacity = 0.85
+ };
+ }
}
diff --git a/Controls/ActionFlowExecutionConfirmationSettingsControl.cs b/Controls/ActionFlowExecutionConfirmationSettingsControl.cs
new file mode 100644
index 00000000..691767a1
--- /dev/null
+++ b/Controls/ActionFlowExecutionConfirmationSettingsControl.cs
@@ -0,0 +1,45 @@
+using Avalonia.Controls;
+using Avalonia.Data;
+using ClassIsland.Core.Abstractions.Controls;
+using SystemTools.Settings;
+
+namespace SystemTools.Controls;
+
+public class ActionFlowExecutionConfirmationSettingsControl
+ : ActionSettingsControlBase
+{
+ private readonly TextBox _promptNameTextBox;
+
+ public ActionFlowExecutionConfirmationSettingsControl()
+ {
+ var panel = new StackPanel
+ {
+ Spacing = 10,
+ Margin = new(10)
+ };
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "提示名称:"
+ });
+
+ _promptNameTextBox = new TextBox
+ {
+ PlaceholderText = "请输入将在确认窗口中显示的自动化名称",
+ AcceptsReturn = false
+ };
+ panel.Children.Add(_promptNameTextBox);
+
+ Content = panel;
+ }
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ _promptNameTextBox.Bind(TextBox.TextProperty, new Binding(nameof(Settings.PromptName))
+ {
+ Source = Settings,
+ Mode = BindingMode.TwoWay
+ });
+ }
+}
diff --git a/Controls/AdjustScreenBrightnessSettingsControl.cs b/Controls/AdjustScreenBrightnessSettingsControl.cs
index 84d99376..539ececf 100644
--- a/Controls/AdjustScreenBrightnessSettingsControl.cs
+++ b/Controls/AdjustScreenBrightnessSettingsControl.cs
@@ -8,6 +8,7 @@ namespace SystemTools.Controls;
public class AdjustScreenBrightnessSettingsControl : ActionSettingsControlBase
{
private NumericUpDown _brightnessInput;
+ private CheckBox _notifyCheckBox;
public AdjustScreenBrightnessSettingsControl()
{
@@ -25,7 +26,7 @@ public AdjustScreenBrightnessSettingsControl()
Maximum = 100,
Increment = 1,
FormatString = "0",
- Watermark = "输入 0-100 的整数"
+ PlaceholderText = "输入 0-100 的整数"
};
panel.Children.Add(_brightnessInput);
@@ -44,7 +45,11 @@ public AdjustScreenBrightnessSettingsControl()
Foreground = Avalonia.Media.Brushes.Gray,
FontSize = 11,
Margin = new(0, 10, 0, 0)
- });
+ }); _notifyCheckBox = new CheckBox { Content = "当执行时发出提醒" };
+ _notifyCheckBox.IsCheckedChanged += (s, e) => { Settings.NotifyOnExecute = _notifyCheckBox.IsChecked ?? false; };
+ panel.Children.Add(_notifyCheckBox);
+
+
Content = panel;
}
@@ -52,11 +57,12 @@ public AdjustScreenBrightnessSettingsControl()
protected override void OnInitialized()
{
base.OnInitialized();
+ _notifyCheckBox.IsChecked = Settings.NotifyOnExecute;
- _brightnessInput[!NumericUpDown.ValueProperty] = new Binding(nameof(Settings.BrightnessPercent))
+ _brightnessInput.Bind(NumericUpDown.ValueProperty, new Binding(nameof(Settings.BrightnessPercent))
{
Source = Settings,
Mode = BindingMode.TwoWay
- };
+ });
}
-}
\ No newline at end of file
+}
diff --git a/Controls/AiAttachmentDropConfirmation.axaml b/Controls/AiAttachmentDropConfirmation.axaml
new file mode 100644
index 00000000..66f5f5c8
--- /dev/null
+++ b/Controls/AiAttachmentDropConfirmation.axaml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Controls/AiAttachmentDropConfirmation.axaml.cs b/Controls/AiAttachmentDropConfirmation.axaml.cs
new file mode 100644
index 00000000..83ddb68f
--- /dev/null
+++ b/Controls/AiAttachmentDropConfirmation.axaml.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using Avalonia.Controls;
+using SystemTools.Models;
+using SystemTools.Services;
+
+namespace SystemTools.Controls;
+
+public partial class AiAttachmentDropConfirmation : UserControl
+{
+ public AiAttachmentDropConfirmation()
+ : this(new AiAttachmentLoadResult([], []))
+ {
+ }
+
+ public AiAttachmentDropConfirmation(AiAttachmentLoadResult result)
+ {
+ Accepted = result.Accepted;
+ Rejected = result.Rejected;
+ Summary = Accepted.Count == 1
+ ? "将把以下附件添加到当前消息。"
+ : $"将把以下 {Accepted.Count} 个附件添加到当前消息。";
+ InitializeComponent();
+ DataContext = this;
+ RejectedItemsWarning.IsVisible = Rejected.Count > 0;
+ }
+
+ public IReadOnlyList Accepted { get; }
+
+ public IReadOnlyList Rejected { get; }
+
+ public string Summary { get; }
+}
diff --git a/Controls/AiAttachmentDropOverlay.axaml b/Controls/AiAttachmentDropOverlay.axaml
new file mode 100644
index 00000000..571fb265
--- /dev/null
+++ b/Controls/AiAttachmentDropOverlay.axaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Controls/AiAttachmentDropOverlay.axaml.cs b/Controls/AiAttachmentDropOverlay.axaml.cs
new file mode 100644
index 00000000..5f8e7c46
--- /dev/null
+++ b/Controls/AiAttachmentDropOverlay.axaml.cs
@@ -0,0 +1,54 @@
+using Avalonia.Controls;
+
+namespace SystemTools.Controls;
+
+public partial class AiAttachmentDropOverlay : UserControl
+{
+ public AiAttachmentDropOverlay()
+ {
+ InitializeComponent();
+ }
+
+ public bool ShowForFiles(int fileCount, int availableSlots, bool canModifyAttachments)
+ {
+ if (fileCount <= 0)
+ {
+ Hide();
+ return false;
+ }
+
+ IsVisible = true;
+ var isValid = canModifyAttachments && availableSlots > 0;
+ ValidIcon.IsVisible = isValid;
+ InvalidIcon.IsVisible = !isValid;
+
+ if (!canModifyAttachments)
+ {
+ HintText.Text = "当前无法添加附件";
+ SubHintText.Text = "请等待当前附件处理或 AI 回复完成";
+ return false;
+ }
+
+ if (availableSlots <= 0)
+ {
+ HintText.Text = "附件数量已达上限";
+ SubHintText.Text = $"每条消息最多添加 {Services.AiAttachmentService.MaximumAttachmentCount} 个附件";
+ return false;
+ }
+
+ HintText.Text = fileCount == 1
+ ? "松开以检查 1 个附件"
+ : $"松开以检查 {fileCount} 个附件";
+ SubHintText.Text = fileCount > availableSlots
+ ? $"最多还可添加 {availableSlots} 个,其余文件会在确认时列出"
+ : "松开后可预览并确认上传";
+ return true;
+ }
+
+ public void Hide()
+ {
+ IsVisible = false;
+ ValidIcon.IsVisible = false;
+ InvalidIcon.IsVisible = false;
+ }
+}
diff --git a/Controls/AiChatGlassSurface.cs b/Controls/AiChatGlassSurface.cs
new file mode 100644
index 00000000..42b75ad9
--- /dev/null
+++ b/Controls/AiChatGlassSurface.cs
@@ -0,0 +1,137 @@
+using System;
+using Avalonia;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using SystemTools.ConfigHandlers;
+
+namespace SystemTools.Controls;
+
+///
+/// A non-interactive liquid-glass surface that follows the AI conversation's
+/// shared material settings. Keeping the copy in one control also makes the
+/// repeated message templates cheap to configure.
+///
+public sealed class AiChatGlassSurface : LiquidGlassAvaloniaUI.LiquidGlassSurface
+{
+ public static readonly StyledProperty SettingsProperty =
+ AvaloniaProperty.Register(nameof(Settings));
+
+ private LiquidGlassSettings? _observedSettings;
+
+ public LiquidGlassSettings? Settings
+ {
+ get => GetValue(SettingsProperty);
+ set => SetValue(SettingsProperty, value);
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ SubscribeToSettings();
+ ApplySettings();
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ UnsubscribeFromSettings();
+ base.OnDetachedFromVisualTree(e);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == SettingsProperty)
+ {
+ SubscribeToSettings();
+ ApplySettings();
+ }
+ }
+
+ private void SubscribeToSettings()
+ {
+ if (ReferenceEquals(_observedSettings, Settings))
+ {
+ return;
+ }
+
+ UnsubscribeFromSettings();
+ _observedSettings = Settings;
+ if (_observedSettings is not null)
+ {
+ _observedSettings.PropertyChanged += OnSettingsPropertyChanged;
+ }
+ }
+
+ private void UnsubscribeFromSettings()
+ {
+ if (_observedSettings is not null)
+ {
+ _observedSettings.PropertyChanged -= OnSettingsPropertyChanged;
+ _observedSettings = null;
+ }
+ }
+
+ private void OnSettingsPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (Dispatcher.UIThread.CheckAccess())
+ {
+ ApplySettings();
+ return;
+ }
+
+ Dispatcher.UIThread.Post(ApplySettings);
+ }
+
+ private void ApplySettings()
+ {
+ if (Settings is not { } settings)
+ {
+ return;
+ }
+
+ BackdropZoom = settings.BackdropZoom;
+ BackdropOffset = new Vector(settings.BackdropOffsetX, settings.BackdropOffsetY);
+ RefractionHeight = settings.RefractionHeight;
+ RefractionAmount = settings.RefractionAmount;
+ DepthEffect = settings.DepthEffect;
+ ChromaticAberration = settings.ChromaticAberration;
+ BlurRadius = settings.BlurRadius;
+ Vibrancy = settings.Vibrancy;
+ Brightness = settings.Brightness;
+ Contrast = settings.Contrast;
+ ExposureEv = settings.ExposureEv;
+ GammaPower = settings.GammaPower;
+ BackdropOpacity = settings.BackdropOpacity;
+ TintColor = ParseColor(settings.TintColor, Colors.Transparent);
+ // SurfaceColor is intentionally owned by the theme resource in XAML.
+ // It stays faintly tinted in both modes while the backdrop remains visible.
+ ProgressiveBlurEnabled = settings.ProgressiveBlurEnabled;
+ ProgressiveBlurStart = settings.ProgressiveBlurStart;
+ ProgressiveBlurEnd = settings.ProgressiveBlurEnd;
+ ProgressiveTintColor = ParseColor(settings.ProgressiveTintColor, Colors.Transparent);
+ ProgressiveTintIntensity = settings.ProgressiveTintIntensity;
+ AdaptiveLuminanceEnabled = settings.AdaptiveLuminanceEnabled;
+ AdaptiveLuminanceUpdateIntervalMs = settings.AdaptiveLuminanceUpdateIntervalMs;
+ AdaptiveLuminanceSmoothing = settings.AdaptiveLuminanceSmoothing;
+ HighlightEnabled = settings.HighlightEnabled;
+ HighlightWidth = settings.HighlightWidth;
+ HighlightBlurRadius = settings.HighlightBlurRadius;
+ HighlightOpacity = settings.HighlightOpacity;
+ HighlightAngle = settings.HighlightAngle;
+ HighlightFalloff = settings.HighlightFalloff;
+ ShadowEnabled = settings.ShadowEnabled;
+ ShadowRadius = Math.Min(settings.ShadowRadius, 14);
+ ShadowOffset = new Vector(0, 2);
+ ShadowColor = ParseColor(settings.ShadowColor, Color.FromArgb(26, 0, 0, 0));
+ ShadowOpacity = Math.Min(settings.ShadowOpacity, 0.55);
+ InnerShadowEnabled = settings.InnerShadowEnabled;
+ InnerShadowRadius = Math.Min(settings.InnerShadowRadius, 24);
+ InnerShadowOffset = new Vector(settings.InnerShadowOffsetX, settings.InnerShadowOffsetY);
+ InnerShadowColor = ParseColor(settings.InnerShadowColor, Color.FromArgb(38, 0, 0, 0));
+ InnerShadowOpacity = settings.InnerShadowOpacity;
+ }
+
+ private static Color ParseColor(string? value, Color fallback) =>
+ Color.TryParse(value, out var color) ? color : fallback;
+}
diff --git a/Controls/AutoHideMainWindowWhenOccludedActionSettingsControl.cs b/Controls/AutoHideMainWindowWhenOccludedActionSettingsControl.cs
new file mode 100644
index 00000000..eb1d1b3f
--- /dev/null
+++ b/Controls/AutoHideMainWindowWhenOccludedActionSettingsControl.cs
@@ -0,0 +1,52 @@
+using Avalonia.Controls;
+using ClassIsland.Core.Abstractions.Controls;
+using SystemTools.Settings;
+
+namespace SystemTools.Controls;
+
+public class AutoHideMainWindowWhenOccludedActionSettingsControl : ActionSettingsControlBase
+{
+ private ToggleSwitch _enableToggle;
+ private CheckBox _notifyCheckBox;
+
+ public AutoHideMainWindowWhenOccludedActionSettingsControl()
+ {
+ var panel = new StackPanel { Spacing = 10, Margin = new(10) };
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "遮挡文字时隐藏主界面",
+ FontWeight = Avalonia.Media.FontWeight.Bold,
+ FontSize = 14
+ });
+
+ _enableToggle = new ToggleSwitch
+ {
+ OnContent = "开启",
+ OffContent = "关闭"
+ };
+ _enableToggle.IsCheckedChanged += (s, e) => { Settings.Enable = _enableToggle.IsChecked ?? false; };
+ panel.Children.Add(_enableToggle);
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "设为“开启”时,触发行动将开启该功能;设为“关闭”时,触发行动将关闭该功能。",
+ TextWrapping = Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.7,
+ FontSize = 12
+ });
+
+ _notifyCheckBox = new CheckBox { Content = "当执行时发出提醒" };
+ _notifyCheckBox.IsCheckedChanged += (s, e) => { Settings.NotifyOnExecute = _notifyCheckBox.IsChecked ?? false; };
+ panel.Children.Add(_notifyCheckBox);
+
+ Content = panel;
+ }
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ _enableToggle.IsChecked = Settings.Enable;
+ _notifyCheckBox.IsChecked = Settings.NotifyOnExecute;
+ }
+}
\ No newline at end of file
diff --git a/Controls/AutoOpenUsbDriveOnInsertActionSettingsControl.cs b/Controls/AutoOpenUsbDriveOnInsertActionSettingsControl.cs
new file mode 100644
index 00000000..c7e2b3bb
--- /dev/null
+++ b/Controls/AutoOpenUsbDriveOnInsertActionSettingsControl.cs
@@ -0,0 +1,52 @@
+using Avalonia.Controls;
+using ClassIsland.Core.Abstractions.Controls;
+using SystemTools.Settings;
+
+namespace SystemTools.Controls;
+
+public class AutoOpenUsbDriveOnInsertActionSettingsControl : ActionSettingsControlBase
+{
+ private ToggleSwitch _enableToggle;
+ private CheckBox _notifyCheckBox;
+
+ public AutoOpenUsbDriveOnInsertActionSettingsControl()
+ {
+ var panel = new StackPanel { Spacing = 10, Margin = new(10) };
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "自动播放",
+ FontWeight = Avalonia.Media.FontWeight.Bold,
+ FontSize = 14
+ });
+
+ _enableToggle = new ToggleSwitch
+ {
+ OnContent = "开启",
+ OffContent = "关闭"
+ };
+ _enableToggle.IsCheckedChanged += (s, e) => { Settings.Enable = _enableToggle.IsChecked ?? false; };
+ panel.Children.Add(_enableToggle);
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "设为“开启”时,触发行动将开启该功能;设为“关闭”时,触发行动将关闭该功能。",
+ TextWrapping = Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.7,
+ FontSize = 12
+ });
+
+ _notifyCheckBox = new CheckBox { Content = "当执行时发出提醒" };
+ _notifyCheckBox.IsCheckedChanged += (s, e) => { Settings.NotifyOnExecute = _notifyCheckBox.IsChecked ?? false; };
+ panel.Children.Add(_notifyCheckBox);
+
+ Content = panel;
+ }
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ _enableToggle.IsChecked = Settings.Enable;
+ _notifyCheckBox.IsChecked = Settings.NotifyOnExecute;
+ }
+}
\ No newline at end of file
diff --git a/Controls/AutoSwitchClassIslandThemeActionSettingsControl.cs b/Controls/AutoSwitchClassIslandThemeActionSettingsControl.cs
new file mode 100644
index 00000000..fb6a0601
--- /dev/null
+++ b/Controls/AutoSwitchClassIslandThemeActionSettingsControl.cs
@@ -0,0 +1,52 @@
+using Avalonia.Controls;
+using ClassIsland.Core.Abstractions.Controls;
+using SystemTools.Settings;
+
+namespace SystemTools.Controls;
+
+public class AutoSwitchClassIslandThemeActionSettingsControl : ActionSettingsControlBase
+{
+ private ToggleSwitch _enableToggle;
+ private CheckBox _notifyCheckBox;
+
+ public AutoSwitchClassIslandThemeActionSettingsControl()
+ {
+ var panel = new StackPanel { Spacing = 10, Margin = new(10) };
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "自动切换 ClassIsland 主题",
+ FontWeight = Avalonia.Media.FontWeight.Bold,
+ FontSize = 14
+ });
+
+ _enableToggle = new ToggleSwitch
+ {
+ OnContent = "开启",
+ OffContent = "关闭"
+ };
+ _enableToggle.IsCheckedChanged += (s, e) => { Settings.Enable = _enableToggle.IsChecked ?? false; };
+ panel.Children.Add(_enableToggle);
+
+ panel.Children.Add(new TextBlock
+ {
+ Text = "设为“开启”时,触发行动将开启该功能;设为“关闭”时,触发行动将关闭该功能。",
+ TextWrapping = Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.7,
+ FontSize = 12
+ });
+
+ _notifyCheckBox = new CheckBox { Content = "当执行时发出提醒" };
+ _notifyCheckBox.IsCheckedChanged += (s, e) => { Settings.NotifyOnExecute = _notifyCheckBox.IsChecked ?? false; };
+ panel.Children.Add(_notifyCheckBox);
+
+ Content = panel;
+ }
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ _enableToggle.IsChecked = Settings.Enable;
+ _notifyCheckBox.IsChecked = Settings.NotifyOnExecute;
+ }
+}
\ No newline at end of file
diff --git a/Controls/BackgroundPlayAudioSettingsControl.cs b/Controls/BackgroundPlayAudioSettingsControl.cs
index 3d16cba4..256878db 100644
--- a/Controls/BackgroundPlayAudioSettingsControl.cs
+++ b/Controls/BackgroundPlayAudioSettingsControl.cs
@@ -11,6 +11,7 @@ namespace SystemTools.Controls;
public class BackgroundPlayAudioSettingsControl : ActionSettingsControlBase
{
+ private readonly CheckBox _notifyCheckBox;
private readonly TextBox _audioPathBox;
private readonly CheckBox _waitForCompletedCheckBox;
private readonly TextBlock _validationHintTextBlock;
@@ -34,7 +35,7 @@ public BackgroundPlayAudioSettingsControl()
_audioPathBox = new TextBox
{
- Watermark = "点击“浏览...”选择音频文件",
+ PlaceholderText = "点击“浏览...”选择音频文件",
Width = 320,
IsReadOnly = true
};
@@ -58,6 +59,8 @@ public BackgroundPlayAudioSettingsControl()
};
panel.Children.Add(_validationHintTextBlock);
+ _notifyCheckBox = new CheckBox { Content = "当执行时发出提醒" };
+
_waitForCompletedCheckBox = new CheckBox
{
Content = "播放后等待播放完成",
@@ -66,15 +69,25 @@ public BackgroundPlayAudioSettingsControl()
_waitForCompletedCheckBox.IsCheckedChanged += (_, _) =>
{
Settings.WaitForPlaybackCompleted = _waitForCompletedCheckBox.IsChecked == true;
+ _notifyCheckBox.IsEnabled = _waitForCompletedCheckBox.IsChecked != true;
+ if (!_notifyCheckBox.IsEnabled)
+ _notifyCheckBox.IsChecked = false;
};
panel.Children.Add(_waitForCompletedCheckBox);
+ _notifyCheckBox.IsCheckedChanged += (s, e) => { Settings.NotifyOnExecute = _notifyCheckBox.IsChecked ?? false; };
+ panel.Children.Add(_notifyCheckBox);
+
Content = panel;
}
protected override void OnInitialized()
{
base.OnInitialized();
+ _notifyCheckBox.IsChecked = Settings.NotifyOnExecute;
+ _notifyCheckBox.IsEnabled = !Settings.WaitForPlaybackCompleted;
+ if (Settings.WaitForPlaybackCompleted)
+ _notifyCheckBox.IsChecked = false;
_audioPathBox.Text = Settings.AudioFilePath;
_waitForCompletedCheckBox.IsChecked = Settings.WaitForPlaybackCompleted;
}
diff --git a/Controls/CameraCaptureSettingsControl.cs b/Controls/CameraCaptureSettingsControl.cs
index acf5145b..b9b205bb 100644
--- a/Controls/CameraCaptureSettingsControl.cs
+++ b/Controls/CameraCaptureSettingsControl.cs
@@ -26,7 +26,7 @@ public CameraCaptureSettingsControl()
_deviceNameBox = new TextBox
{
- Watermark = "输入摄像头名(在系统 设备管理器 中查询)"
+ PlaceholderText = "输入摄像头名(在系统 设备管理器 中查询)"
};
panel.Children.Add(_deviceNameBox);
@@ -38,7 +38,7 @@ public CameraCaptureSettingsControl()
_folderPathBox = new TextBox
{
- Watermark = "点击\"浏览...\"以选择保存文件夹",
+ PlaceholderText = "点击\"浏览...\"以选择保存文件夹",
IsReadOnly = true
};
panel.Children.Add(_folderPathBox);
@@ -109,4 +109,4 @@ private async Task BrowseFolder_Click()
logger?.LogError(ex, "选择保存文件夹失败");
}
}
-}
\ No newline at end of file
+}
diff --git a/Controls/Components/BetterCarouselContainerSettingsControl.axaml b/Controls/Components/BetterCarouselContainerSettingsControl.axaml
index d709b3c5..31e688eb 100644
--- a/Controls/Components/BetterCarouselContainerSettingsControl.axaml
+++ b/Controls/Components/BetterCarouselContainerSettingsControl.axaml
@@ -1,4 +1,4 @@
-
-
-
+
@@ -34,21 +34,21 @@
-
-
+
+
-
-
+
-
-
+
-
+
@@ -58,14 +58,14 @@
-
-
-
+
+
+
-
-
+
-
-
+
+
-
-
+
-
-
+
+
-
-
+
-
-
+
+
-
@@ -134,7 +134,7 @@
-
+