From 8658ae1fb843c28e8ca421e79900aea377245b42 Mon Sep 17 00:00:00 2001 From: kaitao-ms Date: Fri, 28 Nov 2025 23:55:08 +0800 Subject: [PATCH] awake --- PowerToys.sln | 19 +++++ .../Awake.ModuleServices.csproj | 19 +++++ .../Awake.ModuleServices/AwakeService.cs | 79 +++++++++++++++++++ .../Awake.ModuleServices/IAwakeService.cs | 16 ++++ .../Commands/Awake/StartAwakeCommand.cs | 42 ++++++++++ .../Commands/Awake/StopAwakeCommand.cs | 34 ++++++++ .../{ => ColorPicker}/CopyColorCommand.cs | 0 .../Commands/StartAwakeCommand.cs | 59 -------------- .../Commands/StopAwakeCommand.cs | 66 ---------------- .../LaunchWorkspaceCommand.cs | 0 .../OpenWorkspaceEditorCommand.cs | 0 .../Modules/AwakeModuleCommandProvider.cs | 7 +- 12 files changed, 213 insertions(+), 128 deletions(-) create mode 100644 src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj create mode 100644 src/modules/awake/Awake.ModuleServices/AwakeService.cs create mode 100644 src/modules/awake/Awake.ModuleServices/IAwakeService.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StopAwakeCommand.cs rename src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/{ => ColorPicker}/CopyColorCommand.cs (100%) delete mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StartAwakeCommand.cs delete mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StopAwakeCommand.cs rename src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/{ => Workspaces}/LaunchWorkspaceCommand.cs (100%) rename src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/{ => Workspaces}/OpenWorkspaceEditorCommand.cs (100%) diff --git a/PowerToys.sln b/PowerToys.sln index 8ca4ccb3bf..ca8964e86f 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -829,6 +829,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workspaces.ModuleServices", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.ModuleContracts", "src\common\PowerToys.ModuleContracts\PowerToys.ModuleContracts.csproj", "{094E65D5-585E-4898-B465-97A47CD44380}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Awake.ModuleServices", "src\modules\awake\Awake.ModuleServices\Awake.ModuleServices.csproj", "{2141FF78-5F51-ED6B-E11B-C7079CCA1456}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -5255,6 +5257,22 @@ Global {094E65D5-585E-4898-B465-97A47CD44380}.Release|x64.Build.0 = Release|x64 {094E65D5-585E-4898-B465-97A47CD44380}.Release|x86.ActiveCfg = Release|x64 {094E65D5-585E-4898-B465-97A47CD44380}.Release|x86.Build.0 = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|Any CPU.Build.0 = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|ARM64.Build.0 = Debug|ARM64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|x64.ActiveCfg = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|x64.Build.0 = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|x86.ActiveCfg = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Debug|x86.Build.0 = Debug|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|Any CPU.ActiveCfg = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|Any CPU.Build.0 = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|ARM64.ActiveCfg = Release|ARM64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|ARM64.Build.0 = Release|ARM64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x64.ActiveCfg = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x64.Build.0 = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.ActiveCfg = Release|x64 + {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5588,6 +5606,7 @@ Global {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {D52AAF95-DE88-49EA-B28A-10E382BCD4AB} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {094E65D5-585E-4898-B465-97A47CD44380} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {2141FF78-5F51-ED6B-E11B-C7079CCA1456} = {127F38E0-40AA-4594-B955-5616BF206882} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj b/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj new file mode 100644 index 0000000000..a4c7c6c997 --- /dev/null +++ b/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj @@ -0,0 +1,19 @@ + + + + + + + enable + enable + false + false + + + + + + + + + diff --git a/src/modules/awake/Awake.ModuleServices/AwakeService.cs b/src/modules/awake/Awake.ModuleServices/AwakeService.cs new file mode 100644 index 0000000000..c71066f285 --- /dev/null +++ b/src/modules/awake/Awake.ModuleServices/AwakeService.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using Common.UI; +using ManagedCommon; +using PowerToys.ModuleContracts; + +namespace Awake.ModuleServices; + +/// +/// Provides CLI-based Awake control for reuse across hosts. +/// +public sealed class AwakeService : ModuleServiceBase, IAwakeService +{ + public static AwakeService Instance { get; } = new(); + + public override string Key => SettingsDeepLink.SettingsWindow.Awake.ToString(); + + protected override SettingsDeepLink.SettingsWindow SettingsWindow => SettingsDeepLink.SettingsWindow.Awake; + + public override Task LaunchAsync(CancellationToken cancellationToken = default) + { + // Default launch -> indefinite, honoring Awake's own settings for display behavior. + return SetIndefiniteAsync(cancellationToken); + } + + public Task SetIndefiniteAsync(CancellationToken cancellationToken = default) + { + return InvokeCliAsync("-m indefinite"); + } + + public Task SetTimedAsync(int minutes, CancellationToken cancellationToken = default) + { + if (minutes <= 0) + { + return Task.FromResult(OperationResult.Fail("Minutes must be greater than zero.")); + } + + return InvokeCliAsync($"-m timed -t {minutes}"); + } + + public Task SetOffAsync(CancellationToken cancellationToken = default) + { + return InvokeCliAsync("-m passive"); + } + + private static Task InvokeCliAsync(string arguments) + { + try + { + var basePath = PowerToysPathResolver.GetPowerToysInstallPath(); + if (string.IsNullOrWhiteSpace(basePath)) + { + return Task.FromResult(OperationResult.Fail("PowerToys install path not found.")); + } + + var exePath = Path.Combine(basePath, "PowerToys.Awake.exe"); + if (!File.Exists(exePath)) + { + return Task.FromResult(OperationResult.Fail("Unable to locate PowerToys.Awake.exe.")); + } + + var startInfo = new ProcessStartInfo(exePath, arguments) + { + UseShellExecute = false, + CreateNoWindow = true, + }; + + Process.Start(startInfo); + return Task.FromResult(OperationResult.Ok()); + } + catch (Exception ex) + { + return Task.FromResult(OperationResult.Fail($"Failed to invoke Awake: {ex.Message}")); + } + } +} diff --git a/src/modules/awake/Awake.ModuleServices/IAwakeService.cs b/src/modules/awake/Awake.ModuleServices/IAwakeService.cs new file mode 100644 index 0000000000..3ced4cd32b --- /dev/null +++ b/src/modules/awake/Awake.ModuleServices/IAwakeService.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using PowerToys.ModuleContracts; + +namespace Awake.ModuleServices; + +public interface IAwakeService : IModuleService +{ + Task SetIndefiniteAsync(CancellationToken cancellationToken = default); + + Task SetTimedAsync(int minutes, CancellationToken cancellationToken = default); + + Task SetOffAsync(CancellationToken cancellationToken = default); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs new file mode 100644 index 0000000000..ca8766d1a9 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CommandPalette.Extensions.Toolkit; +using Awake.ModuleServices; + +namespace PowerToysExtension.Commands; + +internal sealed partial class StartAwakeCommand : InvokableCommand +{ + private readonly Func> _action; + private readonly string? _successToast; + + internal StartAwakeCommand(string title, Func> action, string? successToast = null) + { + ArgumentNullException.ThrowIfNull(action); + ArgumentException.ThrowIfNullOrWhiteSpace(title); + + _action = action; + _successToast = successToast; + Name = title; + } + + public override CommandResult Invoke() + { + try + { + var result = _action().GetAwaiter().GetResult(); + if (!result.Success) + { + return CommandResult.ShowToast(result.Error ?? "Failed to start Awake."); + } + + return string.IsNullOrWhiteSpace(_successToast) ? CommandResult.Hide() : CommandResult.ShowToast(_successToast); + } + catch (Exception ex) + { + return CommandResult.ShowToast($"Launching Awake failed: {ex.Message}"); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StopAwakeCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StopAwakeCommand.cs new file mode 100644 index 0000000000..d5a3a08ea1 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StopAwakeCommand.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CommandPalette.Extensions.Toolkit; +using Awake.ModuleServices; + +namespace PowerToysExtension.Commands; + +internal sealed partial class StopAwakeCommand : InvokableCommand +{ + internal StopAwakeCommand() + { + Name = "Set Awake to Off"; + } + + public override CommandResult Invoke() + { + try + { + var result = AwakeService.Instance.SetOffAsync().GetAwaiter().GetResult(); + if (result.Success) + { + return CommandResult.ShowToast("Awake switched to Off."); + } + + return CommandResult.ShowToast(result.Error ?? "Awake does not appear to be running."); + } + catch (Exception ex) + { + return CommandResult.ShowToast($"Failed to switch Awake off: {ex.Message}"); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CopyColorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs similarity index 100% rename from src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CopyColorCommand.cs rename to src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StartAwakeCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StartAwakeCommand.cs deleted file mode 100644 index cd15f69d60..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StartAwakeCommand.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using Microsoft.CommandPalette.Extensions.Toolkit; -using PowerToysExtension.Helpers; - -namespace PowerToysExtension.Commands; - -internal sealed partial class StartAwakeCommand : InvokableCommand -{ - private readonly Func _argumentsProvider; - private readonly string? _successToast; - - internal StartAwakeCommand(string title, Func argumentsProvider, string? successToast = null) - { - ArgumentNullException.ThrowIfNull(argumentsProvider); - ArgumentException.ThrowIfNullOrWhiteSpace(title); - - _argumentsProvider = argumentsProvider; - _successToast = successToast; - Name = title; - } - - public override CommandResult Invoke() - { - try - { - var executablePath = PowerToysPathResolver.TryResolveExecutable("PowerToys.Awake.exe"); - if (string.IsNullOrEmpty(executablePath)) - { - return CommandResult.ShowToast("Unable to locate PowerToys.Awake.exe."); - } - - var arguments = _argumentsProvider()?.Trim() ?? string.Empty; - - var startInfo = new ProcessStartInfo(executablePath) - { - UseShellExecute = string.IsNullOrWhiteSpace(arguments), - CreateNoWindow = true, - }; - - if (!string.IsNullOrWhiteSpace(arguments)) - { - startInfo.Arguments = arguments; - startInfo.UseShellExecute = false; - } - - Process.Start(startInfo); - return string.IsNullOrWhiteSpace(_successToast) ? CommandResult.Hide() : CommandResult.ShowToast(_successToast); - } - catch (Exception ex) - { - return CommandResult.ShowToast($"Launching Awake failed: {ex.Message}"); - } - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StopAwakeCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StopAwakeCommand.cs deleted file mode 100644 index 3726523d5a..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/StopAwakeCommand.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using Microsoft.CommandPalette.Extensions.Toolkit; - -namespace PowerToysExtension.Commands; - -internal sealed partial class StopAwakeCommand : InvokableCommand -{ - private const string AwakeWindowClass = "Awake.MessageWindow"; - private const uint WmCommand = 0x0111; - private const int PassiveCommand = 0x0400 + 0x3; - - internal StopAwakeCommand() - { - Name = "Set Awake to Off"; - } - - public override CommandResult Invoke() - { - try - { - if (TrySendPassiveCommand()) - { - return CommandResult.ShowToast("Awake switched to Off."); - } - - return CommandResult.ShowToast("Awake does not appear to be running."); - } - catch (Exception ex) - { - return CommandResult.ShowToast($"Failed to switch Awake off: {ex.Message}"); - } - } - - private static bool TrySendPassiveCommand() - { - var handle = IntPtr.Zero; - var sent = false; - - while (true) - { - handle = FindWindowEx(IntPtr.Zero, handle, AwakeWindowClass, null); - if (handle == IntPtr.Zero) - { - break; - } - - if (PostMessage(handle, WmCommand, new IntPtr(PassiveCommand), IntPtr.Zero)) - { - sent = true; - } - } - - return sent; - } - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string? className, string? windowTitle); - - [DllImport("user32.dll", SetLastError = true)] - private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/LaunchWorkspaceCommand.cs similarity index 100% rename from src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs rename to src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/LaunchWorkspaceCommand.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/OpenWorkspaceEditorCommand.cs similarity index 100% rename from src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs rename to src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/OpenWorkspaceEditorCommand.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs index 65ea125b8e..93901517f1 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs @@ -7,6 +7,7 @@ using Common.UI; using Microsoft.CommandPalette.Extensions.Toolkit; using PowerToysExtension.Commands; using PowerToysExtension.Helpers; +using Awake.ModuleServices; namespace PowerToysExtension.Modules; @@ -19,9 +20,9 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider var more = new List { - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => "-m indefinite", "Awake set to indefinite")), - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => "-m timed -t 30", "Awake set for 30 minutes")), - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => "-m timed -t 120", "Awake set for 2 hours")), + new CommandContextItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite")), + new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => AwakeService.Instance.SetTimedAsync(30), "Awake set for 30 minutes")), + new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => AwakeService.Instance.SetTimedAsync(120), "Awake set for 2 hours")), new CommandContextItem(new StopAwakeCommand()), };