diff --git a/Actions/DoSpeechAction.cs b/Actions/DoSpeechAction.cs new file mode 100644 index 0000000..0150a57 --- /dev/null +++ b/Actions/DoSpeechAction.cs @@ -0,0 +1,34 @@ +using ClassIsland.Core.Abstractions.Automation; +using ClassIsland.Core.Abstractions.Services.SpeechService; +using ClassIsland.Core.Attributes; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using SystemTools.Settings; + +namespace SystemTools.Actions +{ + [ActionInfo("SystemTools.DoSpeech", "语音播报", "\uE5C7", false)] + public class DoSpeechAction(ILogger logger, ISpeechService speechService) : ActionBase + { + private readonly ILogger _logger = logger; + private readonly ISpeechService _speechService = speechService; + + protected override async Task OnInvoke() + { + _logger.LogDebug("DoSpeechAction OnInvoke 开始"); + var text = Settings?.Text; + if (string.IsNullOrWhiteSpace(text)) + { + _logger.LogError("语音播报内容不能为空"); + throw new InvalidOperationException("语音播报内容不能为空"); + } + else + { + _speechService.EnqueueSpeechQueue(text); + } + await base.OnInvoke(); + _logger.LogDebug("DoSpeechAction OnInvoke 完成"); + } + } +} diff --git a/Actions/ImmediateRestartAction.cs b/Actions/ImmediateRestartAction.cs index bfa3183..a18a207 100644 --- a/Actions/ImmediateRestartAction.cs +++ b/Actions/ImmediateRestartAction.cs @@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging; using System; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using Windows.Win32; namespace SystemTools.Actions; @@ -11,30 +13,43 @@ namespace SystemTools.Actions; public class ImmediateRestartAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; - + [DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")] // 此API无法使用CsWin32包生成,因为这是个不公开的函数 + internal static extern uint W32_RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue); + internal const int SE_SHUTDOWN_PRIVILEGE = 19; protected override async Task OnInvoke() { _logger.LogDebug("ImmediateRestartAction OnInvoke 开始"); - try + if(W32_RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, true, false, out _) != 0x00000000) // STATUS_SUCCESS { - var psi = new ProcessStartInfo - { - FileName = "shutdown", - Arguments = "-r -t 0", - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; - - Process.Start(psi); - _logger.LogInformation("已执行立即重启命令"); + _logger.LogError("执行立即重启失败:获取关机权限失败"); + throw new InvalidOperationException("执行立即重启失败"); } - catch (Exception ex) + if(!PInvoke.ExitWindowsEx(Windows.Win32.System.Shutdown.EXIT_WINDOWS_FLAGS.EWX_REBOOT,Windows.Win32.System.Shutdown.SHUTDOWN_REASON.SHTDN_REASON_FLAG_PLANNED)) { - _logger.LogError(ex, "执行立即重启失败"); - throw; + _logger.LogError("执行立即重启失败"); + throw new InvalidOperationException("执行立即重启失败"); } + else { _logger.LogInformation("已执行立即重启命令"); } + //try + //{ + // var psi = new ProcessStartInfo + // { + // FileName = "shutdown", + // Arguments = "-r -t 0", + // UseShellExecute = false, + // CreateNoWindow = true, + // WindowStyle = ProcessWindowStyle.Hidden + // }; + + // Process.Start(psi); + // _logger.LogInformation("已执行立即重启命令"); + //} + //catch (Exception ex) + //{ + // _logger.LogError(ex, "执行立即重启失败"); + // throw; + //} await base.OnInvoke(); _logger.LogDebug("ImmediateRestartAction OnInvoke 完成"); diff --git a/Actions/ImmediateShutdownAction.cs b/Actions/ImmediateShutdownAction.cs index d22ebef..0f6a0cf 100644 --- a/Actions/ImmediateShutdownAction.cs +++ b/Actions/ImmediateShutdownAction.cs @@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging; using System; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using Windows.Win32; namespace SystemTools.Actions; @@ -11,30 +13,43 @@ namespace SystemTools.Actions; public class ImmediateShutdownAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; - + [DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")] // 此API无法使用CsWin32包生成,因为这是个不公开的函数 + internal static extern uint W32_RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue); + internal const int SE_SHUTDOWN_PRIVILEGE = 19; protected override async Task OnInvoke() { _logger.LogDebug("ImmediateShutdownAction OnInvoke 开始"); - try + if (W32_RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, true, false, out _) != 0x00000000) // STATUS_SUCCESS { - var psi = new ProcessStartInfo - { - FileName = "shutdown", - Arguments = "-s -t 0", - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; - - Process.Start(psi); - _logger.LogInformation("已执行立即关机命令"); + _logger.LogError("执行立即关机失败:获取关机权限失败"); + throw new InvalidOperationException("执行立即关机失败"); } - catch (Exception ex) + if (!PInvoke.ExitWindowsEx(Windows.Win32.System.Shutdown.EXIT_WINDOWS_FLAGS.EWX_SHUTDOWN | Windows.Win32.System.Shutdown.EXIT_WINDOWS_FLAGS.EWX_POWEROFF, Windows.Win32.System.Shutdown.SHUTDOWN_REASON.SHTDN_REASON_FLAG_PLANNED)) { - _logger.LogError(ex, "执行立即关机失败"); - throw; + _logger.LogError("执行立即关机失败"); + throw new InvalidOperationException("执行立即关机失败"); } + else { _logger.LogInformation("已执行立即关机命令"); } + //try + //{ + // var psi = new ProcessStartInfo + // { + // FileName = "shutdown", + // Arguments = "-s -t 0", + // UseShellExecute = false, + // CreateNoWindow = true, + // WindowStyle = ProcessWindowStyle.Hidden + // }; + + // Process.Start(psi); + // _logger.LogInformation("已执行立即关机命令"); + //} + //catch (Exception ex) + //{ + // _logger.LogError(ex, "执行立即关机失败"); + // throw; + //} await base.OnInvoke(); _logger.LogDebug("ImmediateShutdownAction OnInvoke 完成"); diff --git a/Controls/DoSpeechSettingsControl.cs b/Controls/DoSpeechSettingsControl.cs new file mode 100644 index 0000000..ac838b5 --- /dev/null +++ b/Controls/DoSpeechSettingsControl.cs @@ -0,0 +1,40 @@ +using Avalonia.Controls; +using ClassIsland.Core.Abstractions.Controls; +using SystemTools.Settings; + +namespace SystemTools.Controls; + +public class DoSpeechSettingsControl : ActionSettingsControlBase +{ + private readonly TextBox _textBox; + + public DoSpeechSettingsControl() + { + var panel = new StackPanel { Spacing = 8, Margin = new(10) }; + + panel.Children.Add(new TextBlock + { + Text = "语音播报", + FontWeight = Avalonia.Media.FontWeight.Bold, + FontSize = 14 + }); + + _textBox = new TextBox + { + Watermark = "请输入要播报的文字", + AcceptsReturn = true, + Height = 120, + Width = 420 + }; + _textBox.TextChanged += (_, _) => { Settings.Text = _textBox.Text ?? string.Empty; }; + panel.Children.Add(_textBox); + + Content = panel; + } + + protected override void OnInitialized() + { + base.OnInitialized(); + _textBox.Text = Settings.Text; + } +} diff --git a/NativeMethods.txt b/NativeMethods.txt index 985f2ae..559355d 100644 --- a/NativeMethods.txt +++ b/NativeMethods.txt @@ -35,4 +35,5 @@ EnumWindows GetClassName GetWindowText DEV_BROADCAST_DEVICEINTERFACE_W -DEV_BROADCAST_HDR \ No newline at end of file +DEV_BROADCAST_HDR +ExitWindowsEx \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index a7e177f..a965e13 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -278,6 +278,8 @@ private void RegisterBaseActions(IServiceCollection services) RegisterActionIfEnabled(services, config, "SystemTools.BackgroundPlayAudio"); RegisterActionIfEnabled(services, config, "SystemTools.ShowDesktop"); + // 语音播报 + RegisterActionIfEnabled(services, config, "SystemTools.DoSpeech"); // 悬浮窗设置 if (config.EnableFloatingWindowFeature) @@ -514,7 +516,7 @@ private void BuildBaseActionTree() if (HasAnyActionEnabled(config, "SystemTools.ClearAllNotifications", "SystemTools.RestartAsAdmin", "SystemTools.LoadTemporaryClassPlan", "SystemTools.OpenAppSettings", - "SystemTools.OpenProfileEditor", "SystemTools.OpenClassSwapWindow")) + "SystemTools.OpenProfileEditor", "SystemTools.OpenClassSwapWindow","SystemTools.DoSpeech")) { IActionService.ActionMenuTree["SystemTools 行动"].Add(new ActionMenuTreeGroup("ClassIsland…", "\uE5CB")); BuildClassIslandMenu(config); @@ -861,6 +863,8 @@ private void BuildClassIslandMenu(MainConfigData config) items.Add(new ActionMenuTreeItem("SystemTools.OpenProfileEditor", "打开档案编辑", "\uE699")); if (config.IsActionEnabled("SystemTools.OpenClassSwapWindow")) items.Add(new ActionMenuTreeItem("SystemTools.OpenClassSwapWindow", "打开换课窗口", "\uE13B")); + if (config.IsActionEnabled("SystemTools.DoSpeech")) + items.Add(new ActionMenuTreeItem("SystemTools.DoSpeech", "语音播报", "\uE5C7")); if (items.Count > 0) { diff --git a/Settings/DoSpeechSettings.cs b/Settings/DoSpeechSettings.cs new file mode 100644 index 0000000..d01e980 --- /dev/null +++ b/Settings/DoSpeechSettings.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace SystemTools.Settings; + +public class DoSpeechSettings +{ + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; +} diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 511a9d7..08a1611 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -213,6 +213,7 @@ public void InitializeFeatureItems() ("SystemTools.OpenAppSettings", "打开应用设置", "ClassIsland"), ("SystemTools.OpenProfileEditor", "打开档案编辑", "ClassIsland"), ("SystemTools.OpenClassSwapWindow", "打开换课窗口", "ClassIsland"), + ("SystemTools.DoSpeech", "语音播报", "ClassIsland"), }; if (Settings.EnableFloatingWindowFeature) diff --git a/manifest.yml b/manifest.yml index 7e001d8..7b3d777 100644 --- a/manifest.yml +++ b/manifest.yml @@ -16,3 +16,5 @@ icon: icon.png repoOwner: Programmer-MrWang repoName: SystemTools assetsRoot: main +supportedOSPlatforms: +- Windows \ No newline at end of file