This commit is contained in:
vanzue
2025-11-26 16:02:41 +08:00
parent 5e1fd47d8e
commit a32b7c634c
73 changed files with 2055 additions and 271 deletions

View File

@@ -58,6 +58,9 @@
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.5.250829002" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools.MSIX" Version="1.7.20250829.1" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.10" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,185 +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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using WinRT;
namespace Microsoft.CmdPal.Ext.PowerToys.ComServer;
/// <summary>
/// Local copy of ExtensionServer that accepts IID_IExtension and related IIDs when CmdPal calls CoCreateInstance.
/// </summary>
internal sealed partial class PowerToysExtensionServer : IDisposable
{
private readonly HashSet<int> _registrationCookies = [];
private PowerToysExtensionInstanceManager? _instanceManager;
private ComWrappers? _comWrappers;
public void RegisterExtension<T>(Func<T> createExtension)
where T : IExtension
{
var exePath = Process.GetCurrentProcess().MainModule?.FileName ?? "unknown";
Logger.LogInfo($"PowerToys COM server registering CLSID {typeof(T).GUID:B} from {exePath}");
Trace.WriteLine("Registering PowerToys extension class object:");
Trace.Indent();
Trace.WriteLine($"CLSID: {typeof(T).GUID:B}");
Trace.WriteLine($"Type: {typeof(T)}");
var clsid = typeof(T).GUID;
var wrappedCallback = () => (IExtension)createExtension();
_instanceManager ??= new PowerToysExtensionInstanceManager(wrappedCallback, clsid);
_comWrappers ??= new StrategyBasedComWrappers();
var classObjectPtr = _comWrappers.GetOrCreateComInterfaceForObject(_instanceManager, CreateComInterfaceFlags.None);
var hr = Ole32.CoRegisterClassObject(
ref clsid,
classObjectPtr,
Ole32.CLSCTX_LOCAL_SERVER,
Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED,
out var cookie);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
_registrationCookies.Add(cookie);
Logger.LogInfo($"Registered CLSID {clsid:B} with cookie {cookie}");
Trace.WriteLine($"Cookie: {cookie}");
Trace.Unindent();
hr = Ole32.CoResumeClassObjects();
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
Logger.LogInfo("CoResumeClassObjects succeeded for PowerToys extension server.");
}
public void Dispose()
{
Trace.WriteLine("Revoking PowerToys extension class object registrations:");
Trace.Indent();
foreach (var cookie in _registrationCookies)
{
Trace.WriteLine($"Cookie: {cookie}");
var hr = Ole32.CoRevokeClassObject(cookie);
Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}");
Logger.LogInfo($"Revoked PowerToys extension class object cookie {cookie} (hr={hr}).");
}
Trace.Unindent();
Logger.LogInfo("PowerToys extension server dispose completed.");
}
private sealed class Ole32
{
#pragma warning disable SA1310 // Field names should not contain underscore
public const int CLSCTX_LOCAL_SERVER = 0x4;
public const int REGCLS_MULTIPLEUSE = 1;
public const int REGCLS_SUSPENDED = 4;
#pragma warning restore SA1310 // Field names should not contain underscore
[DllImport(nameof(Ole32))]
public static extern int CoRegisterClassObject(ref Guid guid, IntPtr obj, int context, int flags, out int register);
[DllImport(nameof(Ole32))]
public static extern int CoResumeClassObjects();
[DllImport(nameof(Ole32))]
public static extern int CoRevokeClassObject(int register);
}
}
[ComVisible(true)]
[GeneratedComClass]
#pragma warning disable SA1402 // File may only contain a single type
internal sealed partial class PowerToysExtensionInstanceManager : IClassFactory
#pragma warning restore SA1402 // File may only contain a single type
{
#pragma warning disable SA1310 // Field names should not contain underscore
private const int E_NOINTERFACE = unchecked((int)0x80004002);
private const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
private static readonly Guid IID_IUnknown = Guid.Parse("00000000-0000-0000-C000-000000000046");
private static readonly Guid IID_IExtension = typeof(IExtension).GUID;
private static readonly Guid IID_IInspectable = Guid.Parse("AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90");
private static readonly Guid IID_IAgileObject = Guid.Parse("94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90");
#pragma warning restore SA1310 // Field names should not contain underscore
private readonly Func<IExtension> _createExtension;
private readonly Guid _clsid;
public PowerToysExtensionInstanceManager(Func<IExtension> createExtension, Guid clsid)
{
_createExtension = createExtension;
_clsid = clsid;
Logger.LogInfo($"PowerToysExtensionInstanceManager created. IID_IExtension={IID_IExtension:B}, CLSID={_clsid:B}");
}
public void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
ref Guid riid,
out IntPtr ppvObject)
{
var requestedIid = riid;
Logger.LogInfo($"PowerToysExtensionInstanceManager.CreateInstance requested. riid={requestedIid:B}, clsid={_clsid:B}, process={Process.GetCurrentProcess().MainModule?.FileName ?? "unknown"}");
ppvObject = IntPtr.Zero;
try
{
if (pUnkOuter is not null)
{
Logger.LogWarning("Aggregation requested but not supported; returning CLASS_E_NOAGGREGATION.");
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (requestedIid == _clsid || requestedIid == IID_IUnknown || requestedIid == IID_IExtension || requestedIid == IID_IInspectable || requestedIid == IID_IAgileObject)
{
var managed = _createExtension();
Logger.LogInfo("Managed PowerToysExtension instance created; marshalling to inspectable.");
var inspectable = MarshalInspectable<IExtension>.FromManaged(managed);
Logger.LogInfo($"MarshalInspectable returned {(inspectable == IntPtr.Zero ? "null" : inspectable.ToString(CultureInfo.InvariantCulture))}.");
ppvObject = inspectable;
Logger.LogInfo("PowerToys extension COM instance created successfully.");
}
else
{
Logger.LogWarning($"PowerToys extension requested unsupported IID {requestedIid:B}. Throwing E_NOINTERFACE.");
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
catch (Exception ex)
{
Logger.LogError($"CreateInstance failed for IID {requestedIid:B}, CLSID {_clsid:B}", ex);
ppvObject = IntPtr.Zero;
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock)
{
}
}
[GeneratedComInterface]
[Guid("00000001-0000-0000-C000-000000000046")]
internal partial interface IClassFactory
{
void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
ref Guid riid,
out IntPtr ppvObject);
void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}

View File

@@ -0,0 +1,121 @@
// 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 System.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches a PowerToys module either by raising its shared event or starting the backing executable.
/// </summary>
internal sealed partial class LaunchModuleCommand : InvokableCommand
{
private readonly string _moduleName;
private readonly string? _eventName;
private readonly string? _executableName;
private readonly string? _arguments;
internal LaunchModuleCommand(
string moduleName,
string? eventName = null,
string? executableName = null,
string? arguments = null,
string? displayName = null)
{
if (string.IsNullOrWhiteSpace(moduleName))
{
throw new ArgumentException("Module name is required", nameof(moduleName));
}
_moduleName = moduleName;
_eventName = eventName;
_executableName = executableName;
_arguments = arguments;
Name = string.IsNullOrWhiteSpace(displayName) ? $"Launch {moduleName}" : displayName;
}
public override CommandResult Invoke()
{
try
{
if (TrySignalEvent())
{
return CommandResult.Hide();
}
if (TryLaunchExecutable())
{
return CommandResult.Hide();
}
return CommandResult.ShowToast($"Unable to launch {_moduleName}.");
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Launching {_moduleName} failed: {ex.Message}");
}
}
private bool TrySignalEvent()
{
if (string.IsNullOrEmpty(_eventName))
{
return false;
}
try
{
using var existingHandle = EventWaitHandle.OpenExisting(_eventName);
return existingHandle.Set();
}
catch (WaitHandleCannotBeOpenedException)
{
try
{
using var newHandle = new EventWaitHandle(false, EventResetMode.AutoReset, _eventName, out _);
return newHandle.Set();
}
catch
{
return false;
}
}
catch
{
return false;
}
}
private bool TryLaunchExecutable()
{
if (string.IsNullOrEmpty(_executableName))
{
return false;
}
var executablePath = PowerToysPathResolver.TryResolveExecutable(_executableName);
if (string.IsNullOrEmpty(executablePath))
{
return false;
}
var startInfo = new ProcessStartInfo(executablePath)
{
UseShellExecute = true,
};
if (!string.IsNullOrWhiteSpace(_arguments))
{
startInfo.Arguments = _arguments;
startInfo.UseShellExecute = false;
}
Process.Start(startInfo);
return true;
}
}

View File

@@ -0,0 +1,49 @@
// 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 LaunchWorkspaceCommand : InvokableCommand
{
private readonly string _workspaceId;
internal LaunchWorkspaceCommand(string workspaceId)
{
_workspaceId = workspaceId;
}
public override CommandResult Invoke()
{
if (string.IsNullOrEmpty(_workspaceId))
{
return CommandResult.KeepOpen();
}
try
{
var launcherPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.WorkspacesLauncher.exe");
if (string.IsNullOrEmpty(launcherPath))
{
return CommandResult.ShowToast("Unable to locate PowerToys Workspaces launcher.");
}
var startInfo = new ProcessStartInfo(launcherPath, _workspaceId)
{
UseShellExecute = true,
};
Process.Start(startInfo);
return CommandResult.Hide();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Launching workspace failed: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,20 @@
// 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;
namespace PowerToysExtension.Commands;
internal sealed partial class NoOpCommand : InvokableCommand
{
internal NoOpCommand(string title = "No operation")
{
Name = title;
}
public override CommandResult Invoke()
{
return CommandResult.Hide();
}
}

View File

@@ -0,0 +1,61 @@
// 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;
/// <summary>
/// Opens the PowerToys settings application deep linked to a specific module.
/// </summary>
internal sealed partial class OpenPowerToysSettingsCommand : InvokableCommand
{
private readonly string _moduleName;
private readonly string _settingsKey;
internal OpenPowerToysSettingsCommand(string moduleName, string settingsKey)
{
if (string.IsNullOrWhiteSpace(moduleName))
{
throw new ArgumentException("Module name is required", nameof(moduleName));
}
if (string.IsNullOrWhiteSpace(settingsKey))
{
throw new ArgumentException("Settings key is required", nameof(settingsKey));
}
_moduleName = moduleName;
_settingsKey = settingsKey;
Name = $"Open {_moduleName} settings";
}
public override CommandResult Invoke()
{
try
{
var powerToysPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.exe");
if (string.IsNullOrEmpty(powerToysPath))
{
return CommandResult.ShowToast("Unable to locate PowerToys.");
}
var startInfo = new ProcessStartInfo(powerToysPath)
{
Arguments = $"--open-settings={_settingsKey}",
UseShellExecute = false,
};
Process.Start(startInfo);
return CommandResult.Hide();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Opening {_moduleName} settings failed: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,103 @@
// 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 System.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Commands;
internal sealed partial class OpenWorkspaceEditorCommand : InvokableCommand
{
private const string LaunchEditorEventName = "Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
public override CommandResult Invoke()
{
try
{
if (TrySignalLaunchEvent())
{
return CommandResult.Hide();
}
if (TryLaunchEditorExecutable())
{
return CommandResult.Hide();
}
if (TryLaunchThroughSettings())
{
return CommandResult.Hide();
}
return CommandResult.ShowToast("Unable to launch the Workspaces editor.");
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Launching editor failed: {ex.Message}");
}
}
private static bool TrySignalLaunchEvent()
{
try
{
using var existing = EventWaitHandle.OpenExisting(LaunchEditorEventName);
return existing.Set();
}
catch (WaitHandleCannotBeOpenedException)
{
try
{
using var created = new EventWaitHandle(false, EventResetMode.AutoReset, LaunchEditorEventName, out _);
return created.Set();
}
catch
{
return false;
}
}
catch
{
return false;
}
}
private static bool TryLaunchEditorExecutable()
{
var editorPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.WorkspacesEditor.exe");
if (string.IsNullOrEmpty(editorPath))
{
return false;
}
var startInfo = new ProcessStartInfo(editorPath)
{
UseShellExecute = true,
};
Process.Start(startInfo);
return true;
}
private static bool TryLaunchThroughSettings()
{
var powerToysExe = PowerToysPathResolver.TryResolveExecutable("PowerToys.exe");
if (string.IsNullOrEmpty(powerToysExe))
{
return false;
}
var startInfo = new ProcessStartInfo(powerToysExe)
{
Arguments = "--open-settings=Workspaces",
UseShellExecute = false,
};
Process.Start(startInfo);
return true;
}
}

View File

@@ -0,0 +1,59 @@
// 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<string> _argumentsProvider;
private readonly string? _successToast;
internal StartAwakeCommand(string title, Func<string> 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}");
}
}
}

View File

@@ -0,0 +1,66 @@
// 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);
}

View File

@@ -0,0 +1,256 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
namespace PowerToysExtension.Helpers;
internal static class AwakeCommandsFactory
{
private static readonly IReadOnlyList<AwakeCommandDefinition> PresetCommands =
[
new(
Title: "Start Awake (display on)",
Subtitle: "Keep the PC and display awake until Command Palette closes",
ArgumentsFactory: () => "--use-parent-pid --display-on true",
Toast: "Awake running with display on"),
new(
Title: "Start Awake (allow display sleep)",
Subtitle: "Keep the PC awake but let the display sleep",
ArgumentsFactory: () => "--use-parent-pid --display-on false",
Toast: "Awake running with display allowed to sleep"),
new(
Title: "Start Awake indefinitely (display on)",
Subtitle: "Keep the PC and display awake until you stop Awake",
ArgumentsFactory: () => "--display-on true",
Toast: "Awake running indefinitely with display on"),
new(
Title: "Start Awake indefinitely (allow display sleep)",
Subtitle: "Keep the PC awake indefinitely but let the display sleep",
ArgumentsFactory: () => "--display-on false",
Toast: "Awake running indefinitely with display allowed to sleep"),
new(
Title: "Start Awake for 30 minutes",
Subtitle: "Keeps the PC awake for 30 minutes",
ArgumentsFactory: () => "--use-parent-pid --display-on true --time-limit 1800",
Toast: "Awake timer set for 30 minutes"),
new(
Title: "Start Awake for 2 hours",
Subtitle: "Keeps the PC awake for two hours",
ArgumentsFactory: () => "--use-parent-pid --display-on true --time-limit 7200",
Toast: "Awake timer set for 2 hours"),
];
internal static void PopulateModuleCommands(List<ICommandContextItem> moreCommands)
{
ArgumentNullException.ThrowIfNull(moreCommands);
var stopCommand = new StopAwakeCommand();
var stopContext = new CommandContextItem(stopCommand)
{
Title = "Set Awake to Off",
Subtitle = "Switch Awake to passive mode",
};
moreCommands.Add(stopContext);
}
internal static IListItem[] GetSessionItems(string? searchText)
{
var results = new List<IListItem>();
var statusSubtitle = AwakeStatusService.BuildSubtitle();
if (Matches("Current status", statusSubtitle, searchText))
{
var statusItem = new ListItem(new CommandItem(new Commands.NoOpCommand("Awake status")))
{
Title = "Current status",
Subtitle = statusSubtitle,
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(statusItem);
}
foreach (var preset in PresetCommands)
{
if (!Matches(preset.Title, preset.Subtitle, searchText))
{
continue;
}
var command = new StartAwakeCommand(preset.Title, preset.ArgumentsFactory, preset.Toast);
var item = new ListItem(new CommandItem(command))
{
Title = preset.Title,
Subtitle = preset.Subtitle,
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(item);
}
foreach (var preset in AwakeStatusService.ReadCustomPresets())
{
var title = $"Start Awake for {FormatDuration(preset.Duration)}";
var subtitle = $"Custom preset '{preset.Name}'";
if (!Matches(title, subtitle, searchText))
{
continue;
}
var seconds = (int)Math.Round(preset.Duration.TotalSeconds);
var command = new StartAwakeCommand(
title,
() => $"--use-parent-pid --display-on true --time-limit {seconds}",
$"Awake timer set for {FormatDuration(preset.Duration)}");
var item = new ListItem(new CommandItem(command))
{
Title = title,
Subtitle = subtitle,
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(item);
}
if (Matches("Bind Awake to another process", "Keep awake while a process is running", searchText))
{
var processPageItem = new CommandItem(new Pages.AwakeProcessListPage())
{
Title = "Bind Awake to another process",
Subtitle = "Stop automatically when the target process exits",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(new ListItem(processPageItem)
{
Title = processPageItem.Title,
Subtitle = processPageItem.Subtitle,
Icon = processPageItem.Icon,
});
}
if (Matches("Set Awake to Off", "Switch Awake to passive mode", searchText))
{
var stopCommand = new StopAwakeCommand();
var stopItem = new ListItem(new CommandItem(stopCommand))
{
Title = "Set Awake to Off",
Subtitle = "Switch Awake to passive mode",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(stopItem);
}
if (Matches("Open Awake settings", "Configure Awake in PowerToys", searchText))
{
var settingsCommand = new OpenPowerToysSettingsCommand("Awake", "Awake");
var settingsItem = new ListItem(new CommandItem(settingsCommand))
{
Title = "Open Awake settings",
Subtitle = "Configure Awake inside PowerToys",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(settingsItem);
}
return results.ToArray();
}
internal static IListItem[] GetProcessItems(string? searchText)
{
var results = new List<IListItem>();
Process[] processes;
try
{
processes = Process.GetProcesses();
}
catch
{
return Array.Empty<IListItem>();
}
foreach (var process in processes.OrderBy(p => p.ProcessName, StringComparer.CurrentCultureIgnoreCase).Take(200))
{
try
{
var name = process.ProcessName;
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
var title = $"{name} ({process.Id})";
if (!Matches(title, null, searchText))
{
continue;
}
var command = new StartAwakeCommand(
$"Bind Awake to {title}",
() => $"--pid {process.Id} --display-on true",
$"Awake bound to PID {process.Id}");
var item = new ListItem(new CommandItem(command))
{
Title = title,
Subtitle = "Keep the PC awake while this process is running",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
results.Add(item);
}
catch
{
// Ignore processes that exit or cannot be inspected
}
finally
{
process.Dispose();
}
}
return results.ToArray();
}
internal static bool Matches(string? source, string? subtitle, string? searchText)
{
if (string.IsNullOrWhiteSpace(searchText))
{
return true;
}
return Contains(source, searchText) || Contains(subtitle, searchText);
}
private static bool Contains(string? source, string? searchText)
{
return !string.IsNullOrEmpty(source) && source.Contains(searchText!, StringComparison.CurrentCultureIgnoreCase);
}
private static string FormatDuration(TimeSpan span)
{
if (span.TotalHours >= 1)
{
var hours = (int)Math.Floor(span.TotalHours);
var minutes = span.Minutes;
return minutes > 0 ? $"{hours}h {minutes}m" : $"{hours}h";
}
if (span.TotalMinutes >= 1)
{
return $"{(int)Math.Round(span.TotalMinutes)}m";
}
return span.TotalSeconds >= 1 ? $"{(int)Math.Round(span.TotalSeconds)}s" : "\u2014";
}
private sealed record AwakeCommandDefinition(string Title, string Subtitle, Func<string> ArgumentsFactory, string? Toast);
}

View File

@@ -0,0 +1,15 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace PowerToysExtension.Helpers;
internal sealed record AwakeSettingsDocument(AwakeSettingsProperties Properties);

View File

@@ -0,0 +1,15 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace PowerToysExtension.Helpers;
internal sealed record AwakeSettingsProperties(bool KeepDisplayOn, int Mode, int IntervalHours, int IntervalMinutes, DateTimeOffset? ExpirationDateTime, Dictionary<string, int> CustomTrayTimes);

View File

@@ -0,0 +1,168 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace PowerToysExtension.Helpers;
internal static class AwakeStatusService
{
private const string SettingsFileName = "settings.json";
private static readonly JsonSerializerOptions SerializerOptions = new() { PropertyNameCaseInsensitive = true };
internal static AwakeStatusSnapshot GetSnapshot()
{
var isRunning = IsAwakeProcessRunning();
var settings = ReadSettings();
if (settings is null)
{
return new AwakeStatusSnapshot(isRunning, AwakeMode.Passive, false, null, null);
}
var mode = Enum.IsDefined(typeof(AwakeMode), settings.Properties.Mode)
? (AwakeMode)settings.Properties.Mode
: AwakeMode.Passive;
TimeSpan? duration = null;
DateTimeOffset? expiration = null;
switch (mode)
{
case AwakeMode.Timed:
duration = TimeSpan.FromHours(settings.Properties.IntervalHours) + TimeSpan.FromMinutes(settings.Properties.IntervalMinutes);
break;
case AwakeMode.Expirable:
expiration = settings.Properties.ExpirationDateTime;
break;
}
return new AwakeStatusSnapshot(isRunning, mode, settings.Properties.KeepDisplayOn, duration, expiration);
}
internal static string BuildSubtitle()
{
var snapshot = GetSnapshot();
if (!snapshot.IsRunning)
{
return "Awake is idle";
}
return snapshot.Mode switch
{
AwakeMode.Passive => "Awake is idle",
AwakeMode.Indefinite => snapshot.KeepDisplayOn ? "Active <20> indefinite (display on)" : "Active <20> indefinite",
AwakeMode.Timed => snapshot.Duration is { } span
? $"Active <20> timer {FormatDuration(span)}"
: "Active <20> timer",
AwakeMode.Expirable => snapshot.Expiration is { } expiry
? $"Active <20> until {expiry.ToLocalTime():t}"
: "Active <20> scheduled",
_ => "Awake is running",
};
}
internal static IReadOnlyList<AwakeCustomPreset> ReadCustomPresets()
{
var settings = ReadSettings();
if (settings?.Properties?.CustomTrayTimes is null || settings.Properties.CustomTrayTimes.Count == 0)
{
return Array.Empty<AwakeCustomPreset>();
}
var presets = new List<AwakeCustomPreset>();
foreach (var kvp in settings.Properties.CustomTrayTimes)
{
try
{
var minutes = kvp.Value;
if (minutes <= 0)
{
continue;
}
var span = TimeSpan.FromMinutes(minutes);
presets.Add(new AwakeCustomPreset(kvp.Key, span));
}
catch
{
// Ignore malformed entries
}
}
return presets;
}
private static bool IsAwakeProcessRunning()
{
try
{
return Process.GetProcessesByName("PowerToys.Awake").Length > 0;
}
catch
{
return false;
}
}
private static AwakeSettingsDocument? ReadSettings()
{
try
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (string.IsNullOrEmpty(appData))
{
return null;
}
var path = Path.Combine(appData, "Microsoft", "PowerToys", "Awake", SettingsFileName);
if (!File.Exists(path))
{
return null;
}
var json = File.ReadAllText(path);
return JsonSerializer.Deserialize(json, PowerToysJsonContext.Default.AwakeSettingsDocument);
}
catch
{
return null;
}
}
private static string FormatDuration(TimeSpan span)
{
if (span.TotalHours >= 1)
{
var hours = (int)Math.Floor(span.TotalHours);
var minutes = span.Minutes;
return minutes > 0 ? $"{hours}h {minutes}m" : $"{hours}h";
}
if (span.TotalMinutes >= 1)
{
return $"{(int)Math.Round(span.TotalMinutes)}m";
}
return span.TotalSeconds >= 1 ? $"{(int)Math.Round(span.TotalSeconds)}s" : "\u2014";
}
}
internal enum AwakeMode
{
Passive = 0,
Indefinite = 1,
Timed = 2,
Expirable = 3,
}
internal readonly record struct AwakeStatusSnapshot(bool IsRunning, AwakeMode Mode, bool KeepDisplayOn, TimeSpan? Duration, DateTimeOffset? Expiration);
internal readonly record struct AwakeCustomPreset(string Name, TimeSpan Duration);

View File

@@ -0,0 +1,32 @@
// 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.
namespace PowerToysExtension.Helpers;
/// <summary>
/// Mirrors the event names exposed by PowerToys' shared constants so the extension can raise them.
/// Keep these values in sync with src/common/interop/shared_constants.h in the PowerToys repo.
/// </summary>
internal static class PowerToysEventNames
{
internal const string AlwaysOnTopPin = "Local\\AlwaysOnTopPinEvent-892e0aa2-cfa8-4cc4-b196-ddeb32314ce8";
internal const string ColorPickerShow = "Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525";
internal const string CommandPaletteShow = "Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
internal const string AwakeExitEvent = "Local\\PowerToysAwakeExitEvent-c0d5e305-35fc-4fb5-83ec-f6070cfaf7fe";
internal const string EnvironmentVariablesShow = "Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
internal const string EnvironmentVariablesShowAdmin = "Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
internal const string FancyZonesToggleEditor = "Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14";
internal const string HostsShow = "Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0";
internal const string HostsShowAdmin = "Local\\Hosts-ShowHostsAdminEvent-60ff44e2-efd3-43bf-928a-f4d269f98bec";
internal const string MeasureToolTrigger = "Local\\MeasureToolEvent-3d46745f-09b3-4671-a577-236be7abd199";
internal const string PeekShow = "Local\\ShowPeekEvent";
internal const string PowerOcrShow = "Local\\PowerOCREvent-dc864e06-e1af-4ecc-9078-f98bee745e3a";
internal const string PowerToysRunInvoke = "Local\\PowerToysRunInvokeEvent-30f26ad7-d36d-4c0e-ab02-68bb5ff3c4ab";
internal const string RegistryPreviewTrigger = "Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687";
internal const string ShortcutGuideTrigger = "Local\\ShortcutGuide-TriggerEvent-d4275ad3-2531-4d19-9252-c0becbd9b496";
internal const string WorkspacesLaunchEditor = "Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
internal const string WorkspacesHotkey = "Local\\PowerToys-Workspaces-HotkeyEvent-2625C3C8-BAC9-4DB3-BCD6-3B4391A26FD0";
internal const string CropAndLockThumbnail = "Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
internal const string CropAndLockReparent = "Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36";
}

View File

@@ -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 System.Text.Json.Serialization;
namespace PowerToysExtension.Helpers;
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspacesData))]
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspaceProject))]
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspaceApplication))]
[JsonSerializable(typeof(AwakeSettingsDocument))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true)]
internal sealed partial class PowerToysJsonContext : JsonSerializerContext
{
}

View File

@@ -0,0 +1,452 @@
// 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.Collections.Generic;
using System.Linq;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using NoOpCommand = PowerToysExtension.Commands.NoOpCommand;
namespace Microsoft.CmdPal.Ext.PowerToys.Helpers;
internal static class PowerToysModuleItemsHelper
{
private sealed record ModuleMetadata(
string SettingsKey,
string Title,
string IconPath,
string[] SearchTerms,
string? Subtitle = null,
string? LaunchEvent = null,
string? LaunchExecutable = null,
string? LaunchArguments = null,
ModuleCommandMetadata[]? AdditionalLaunchCommands = null);
private sealed record ModuleCommandMetadata(
string Title,
string? Subtitle = null,
string? LaunchEvent = null,
string? LaunchExecutable = null,
string? LaunchArguments = null,
string[]? SearchTerms = null);
private sealed record CustomEntry(Func<ListItem> Factory, string[] SearchTerms)
{
public ListItem CreateItem() => Factory();
}
private static readonly ModuleMetadata[] Modules =
[
new(
SettingsKey: "AdvancedPaste",
Title: "Advanced Paste",
IconPath: "Assets\\AdvancedPaste.png",
SearchTerms: new[] { "advanced", "paste", "clipboard", "text" },
Subtitle: "Open Advanced Paste settings"),
new(
SettingsKey: "AlwaysOnTop",
Title: "Always On Top",
IconPath: "Assets\\AlwaysOnTop.png",
SearchTerms: new[] { "always", "top", "pin", "window" },
Subtitle: "Toggle Always On Top for the active window"),
new(
SettingsKey: "Awake",
Title: "Awake",
IconPath: "Assets\\Awake.png",
SearchTerms: new[] { "awake", "sleep", "keep", "monitor" },
Subtitle: "Keep your PC awake",
LaunchExecutable: "PowerToys.Awake.exe",
LaunchArguments: "--use-parent-pid --display-on true"),
new(
SettingsKey: "ColorPicker",
Title: "Color Picker",
IconPath: "Assets\\ColorPicker.png",
SearchTerms: new[] { "color", "picker", "eyedropper", "pick" },
Subtitle: "Copy colors from anywhere",
LaunchEvent: PowerToysEventNames.ColorPickerShow,
LaunchExecutable: "PowerToys.ColorPickerUI.exe"),
new(
SettingsKey: "CropAndLock",
Title: "Crop And Lock",
IconPath: "Assets\\CropAndLock.png",
SearchTerms: new[] { "crop", "lock", "thumbnail", "reparent" },
Subtitle: "Configure Crop And Lock",
AdditionalLaunchCommands: new[]
{
new ModuleCommandMetadata(
Title: "Crop active window (thumbnail)",
Subtitle: "Creates a static snapshot window",
LaunchEvent: PowerToysEventNames.CropAndLockThumbnail,
SearchTerms: new[] { "thumbnail", "snapshot", "crop" }),
new ModuleCommandMetadata(
Title: "Crop active window (reparent)",
Subtitle: "Embeds the active window inside a new host",
LaunchEvent: PowerToysEventNames.CropAndLockReparent,
SearchTerms: new[] { "reparent", "embed", "live window" }),
}),
new(
SettingsKey: "EnvironmentVariables",
Title: "Environment Variables",
IconPath: "Assets\\EnvironmentVariables.png",
SearchTerms: new[] { "environment", "variables", "env", "path" },
Subtitle: "Manage environment variables",
LaunchEvent: PowerToysEventNames.EnvironmentVariablesShow,
AdditionalLaunchCommands: new[]
{
new ModuleCommandMetadata(
Title: "Open as administrator",
Subtitle: "Launches the elevated editor",
LaunchEvent: PowerToysEventNames.EnvironmentVariablesShowAdmin,
SearchTerms: new[] { "admin", "administrator" }),
}),
new(
SettingsKey: "FancyZones",
Title: "FancyZones",
IconPath: "Assets\\FancyZones.png",
SearchTerms: new[] { "fancyzones", "zones", "layout", "window" },
Subtitle: "Adjust FancyZones layouts",
LaunchEvent: PowerToysEventNames.FancyZonesToggleEditor,
LaunchExecutable: "PowerToys.FancyZonesEditor.exe"),
new(
SettingsKey: "FileExplorer",
Title: "File Explorer Add-ons",
IconPath: "Assets\\FileExplorerPreview.png",
SearchTerms: new[] { "file explorer", "preview", "addons", "powerpreview" },
Subtitle: "Configure File Explorer add-ons"),
new(
SettingsKey: "FileLocksmith",
Title: "File Locksmith",
IconPath: "Assets\\FileLocksmith.png",
SearchTerms: new[] { "file", "locksmith", "lock", "unlock" },
Subtitle: "Find which process locks a file"),
new(
SettingsKey: "Hosts",
Title: "Hosts File Editor",
IconPath: "Assets\\Hosts.png",
SearchTerms: new[] { "hosts", "file", "editor" },
Subtitle: "Edit the hosts file",
LaunchEvent: PowerToysEventNames.HostsShow,
AdditionalLaunchCommands: new[]
{
new ModuleCommandMetadata(
Title: "Open as administrator",
Subtitle: "Launches the elevated editor",
LaunchEvent: PowerToysEventNames.HostsShowAdmin,
SearchTerms: new[] { "admin", "administrator" }),
}),
new(
SettingsKey: "ImageResizer",
Title: "Image Resizer",
IconPath: "Assets\\ImageResizer.png",
SearchTerms: new[] { "image", "resize", "resizer", "photo" },
Subtitle: "Resize images in bulk"),
new(
SettingsKey: "KBM",
Title: "Keyboard Manager",
IconPath: "Assets\\KeyboardManager.png",
SearchTerms: new[] { "keyboard", "manager", "remap", "kbm", "shortcut" },
Subtitle: "Remap keys and shortcuts"),
new(
SettingsKey: "MeasureTool",
Title: "Screen Ruler",
IconPath: "Assets\\ScreenRuler.png",
SearchTerms: new[] { "screen", "ruler", "measure", "measuretool" },
Subtitle: "Measure on-screen elements",
LaunchEvent: PowerToysEventNames.MeasureToolTrigger),
new(
SettingsKey: "MouseUtils",
Title: "Mouse Utilities",
IconPath: "Assets\\MouseUtils.png",
SearchTerms: new[] { "mouse", "utilities", "finder", "highlighter", "crosshairs" },
Subtitle: "Configure mouse utilities"),
new(
SettingsKey: "MouseWithoutBorders",
Title: "Mouse Without Borders",
IconPath: "Assets\\MouseWithoutBorders.png",
SearchTerms: new[] { "mouse", "borders", "multi pc", "mwob" },
Subtitle: "Share mouse and keyboard across PCs"),
new(
SettingsKey: "NewPlus",
Title: "New+",
IconPath: "Assets\\NewPlus.png",
SearchTerms: new[] { "new", "template", "file" },
Subtitle: "Create templates quickly"),
new(
SettingsKey: "Peek",
Title: "Peek",
IconPath: "Assets\\Peek.png",
SearchTerms: new[] { "peek", "preview", "quick look" },
Subtitle: "Preview files instantly",
LaunchEvent: PowerToysEventNames.PeekShow),
new(
SettingsKey: "PowerAccent",
Title: "Quick Accent",
IconPath: "Assets\\QuickAccent.png",
SearchTerms: new[] { "accent", "quick", "characters", "diacritics", "poweraccent" },
Subtitle: "Insert accented characters"),
new(
SettingsKey: "PowerOCR",
Title: "Text Extractor",
IconPath: "Assets\\TextExtractor.png",
SearchTerms: new[] { "text", "extractor", "ocr", "copy" },
Subtitle: "Extract text from the screen",
LaunchEvent: PowerToysEventNames.PowerOcrShow,
LaunchExecutable: "PowerToys.PowerOCR.exe"),
new(
SettingsKey: "PowerRename",
Title: "PowerRename",
IconPath: "Assets\\PowerRename.png",
SearchTerms: new[] { "rename", "files", "powerrename" },
Subtitle: "Batch rename files",
LaunchExecutable: "PowerRename.exe"),
new(
SettingsKey: "RegistryPreview",
Title: "Registry Preview",
IconPath: "Assets\\RegistryPreview.png",
SearchTerms: new[] { "registry", "preview", "reg" },
Subtitle: "Inspect and edit registry files",
LaunchEvent: PowerToysEventNames.RegistryPreviewTrigger),
new(
SettingsKey: "Run",
Title: "PowerToys Run",
IconPath: "Assets\\PowerToysRun.png",
SearchTerms: new[] { "run", "launcher", "search", "powertoys run", "powerlauncher" },
Subtitle: "Quickly search and launch",
LaunchEvent: PowerToysEventNames.PowerToysRunInvoke),
new(
SettingsKey: "ShortcutGuide",
Title: "Shortcut Guide",
IconPath: "Assets\\ShortcutGuide.png",
SearchTerms: new[] { "shortcut", "guide", "keys", "help" },
Subtitle: "View available shortcuts",
LaunchEvent: PowerToysEventNames.ShortcutGuideTrigger),
new(
SettingsKey: "CmdPal",
Title: "Command Palette",
IconPath: "Assets\\CmdPal.png",
SearchTerms: new[] { "command", "palette", "cmdpal", "prompt" },
Subtitle: "Open the Command Palette",
LaunchEvent: PowerToysEventNames.CommandPaletteShow),
new(
SettingsKey: "CmdNotFound",
Title: "Command Not Found",
IconPath: "Assets\\CommandNotFound.png",
SearchTerms: new[] { "command", "not", "found", "terminal" },
Subtitle: "Suggest commands when mistyped"),
new(
SettingsKey: "Workspaces",
Title: "Workspaces",
IconPath: "Assets\\Workspaces.png",
SearchTerms: new[] { "workspace", "layouts", "projects" },
Subtitle: "Manage PowerToys workspaces",
LaunchEvent: PowerToysEventNames.WorkspacesLaunchEditor,
AdditionalLaunchCommands: new[]
{
new ModuleCommandMetadata(
Title: "Trigger Workspaces hotkey",
Subtitle: "Invokes the configured Workspaces action",
LaunchEvent: PowerToysEventNames.WorkspacesHotkey,
SearchTerms: new[] { "hotkey", "shortcut" }),
}),
new(
SettingsKey: "ZoomIt",
Title: "ZoomIt",
IconPath: "Assets\\ZoomIt.png",
SearchTerms: new[] { "zoom", "zoomit", "presentation" },
Subtitle: "Configure ZoomIt"),
new(
SettingsKey: "Overview",
Title: "General",
IconPath: "Assets\\PowerToys.png",
SearchTerms: new[] { "general", "overview", "about" },
Subtitle: "General PowerToys settings"),
new(
SettingsKey: "Dashboard",
Title: "Dashboard",
IconPath: "Assets\\PowerToys.png",
SearchTerms: new[] { "dashboard", "status", "summary" },
Subtitle: "View overall status"),
];
private static readonly CustomEntry[] CustomEntries =
[
new(
() => new ListItem(new CommandItem(new WorkspacesListPage()))
{
Title = "Workspaces list",
Subtitle = "Browse individual workspaces",
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
},
new[] { "workspace", "workspaces", "layout" }),
new(
() => new ListItem(new CommandItem(new AwakeSessionsPage()))
{
Title = "Awake actions",
Subtitle = "Start, stop, or schedule Awake",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
},
new[] { "awake", "keep awake", "sleep", "prevent", "timer" }),
];
internal static IListItem[] GetModuleItems(string? searchText)
{
var results = new List<IListItem>();
foreach (var module in Modules)
{
if (Matches(module, searchText))
{
results.Add(CreateModuleItem(module));
}
}
foreach (var entry in CustomEntries)
{
var item = entry.CreateItem();
if (Matches(item, entry.SearchTerms, searchText))
{
results.Add(item);
}
}
var workspaceItems = WorkspaceItemsHelper.GetWorkspaceItems(searchText);
if (workspaceItems.Length > 0)
{
results.AddRange(workspaceItems);
}
return results.ToArray();
}
private static ListItem CreateModuleItem(ModuleMetadata metadata)
{
var mainCommand = CreatePrimaryCommand(metadata, out var usesSettingsForPrimary);
var listItem = new ListItem(mainCommand)
{
Title = metadata.Title,
Subtitle = metadata.Subtitle ?? (usesSettingsForPrimary ? "Open module settings" : $"Launch {metadata.Title}"),
Icon = IconHelpers.FromRelativePath(metadata.IconPath),
};
var moreCommands = new List<ICommandContextItem>();
if (string.Equals(metadata.SettingsKey, "Awake", StringComparison.OrdinalIgnoreCase))
{
listItem.Subtitle = AwakeStatusService.BuildSubtitle();
AwakeCommandsFactory.PopulateModuleCommands(moreCommands);
}
if (metadata.AdditionalLaunchCommands is { Length: > 0 })
{
foreach (var additional in metadata.AdditionalLaunchCommands)
{
var additionalCommand = new LaunchModuleCommand(
metadata.Title,
additional.LaunchEvent,
additional.LaunchExecutable,
additional.LaunchArguments,
additional.Title);
var contextItem = new CommandContextItem(additionalCommand);
if (!string.IsNullOrWhiteSpace(additional.Title))
{
contextItem.Title = additional.Title;
}
if (!string.IsNullOrWhiteSpace(additional.Subtitle))
{
contextItem.Subtitle = additional.Subtitle;
}
moreCommands.Add(contextItem);
}
}
if (!usesSettingsForPrimary && !string.IsNullOrEmpty(metadata.SettingsKey))
{
moreCommands.Add(new CommandContextItem(new OpenPowerToysSettingsCommand(metadata.Title, metadata.SettingsKey)));
}
if (moreCommands.Count > 0)
{
listItem.MoreCommands = moreCommands.ToArray();
}
return listItem;
}
private static InvokableCommand CreatePrimaryCommand(ModuleMetadata metadata, out bool usesSettings)
{
usesSettings = false;
if (!string.IsNullOrEmpty(metadata.LaunchEvent) || !string.IsNullOrEmpty(metadata.LaunchExecutable))
{
return new LaunchModuleCommand(metadata.Title, metadata.LaunchEvent, metadata.LaunchExecutable, metadata.LaunchArguments);
}
if (!string.IsNullOrEmpty(metadata.SettingsKey))
{
usesSettings = true;
return new OpenPowerToysSettingsCommand(metadata.Title, metadata.SettingsKey);
}
return new NoOpCommand();
}
private static bool Matches(ModuleMetadata metadata, string? searchText)
{
if (string.IsNullOrWhiteSpace(searchText))
{
return true;
}
if (Contains(metadata.Title, searchText) || Contains(metadata.Subtitle, searchText))
{
return true;
}
if (metadata.AdditionalLaunchCommands is { Length: > 0 })
{
foreach (var additional in metadata.AdditionalLaunchCommands)
{
if (Contains(additional.Title, searchText) || Contains(additional.Subtitle, searchText))
{
return true;
}
if (additional.SearchTerms is { Length: > 0 } && additional.SearchTerms.Any(term => Contains(term, searchText)))
{
return true;
}
}
}
return metadata.SearchTerms.Any(term => Contains(term, searchText));
}
private static bool Matches(ListItem item, IReadOnlyCollection<string> searchTerms, string? searchText)
{
if (string.IsNullOrWhiteSpace(searchText))
{
return true;
}
if (Contains(item.Title, searchText) || Contains(item.Subtitle, searchText))
{
return true;
}
return searchTerms.Any(term => Contains(term, searchText));
}
private static bool Contains(string? source, string query)
{
return !string.IsNullOrEmpty(source) && source.Contains(query, StringComparison.CurrentCultureIgnoreCase);
}
}

View File

@@ -0,0 +1,161 @@
// 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.IO;
using Microsoft.Win32;
namespace PowerToysExtension.Helpers;
/// <summary>
/// Helper methods for locating the installed PowerToys binaries.
/// </summary>
internal static class PowerToysPathResolver
{
private const string PowerToysProtocolKey = @"Software\Classes\powertoys";
private const string PowerToysUserKey = @"Software\Microsoft\PowerToys";
internal static string? GetPowerToysInstallPath()
{
var perUser = GetInstallPathFromRegistry(RegistryHive.CurrentUser);
if (!string.IsNullOrEmpty(perUser))
{
return perUser;
}
return GetInstallPathFromRegistry(RegistryHive.LocalMachine);
}
internal static string? TryResolveExecutable(string executableName)
{
if (string.IsNullOrEmpty(executableName))
{
return null;
}
var baseDirectory = GetPowerToysInstallPath();
if (string.IsNullOrEmpty(baseDirectory))
{
return null;
}
var candidate = Path.Combine(baseDirectory, executableName);
return File.Exists(candidate) ? candidate : null;
}
private static string? GetInstallPathFromRegistry(RegistryHive hive)
{
try
{
using var baseKey = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64);
var protocolPath = GetPathFromProtocolRegistration(baseKey);
if (!string.IsNullOrEmpty(protocolPath))
{
return protocolPath;
}
if (hive == RegistryHive.CurrentUser)
{
var userPath = GetPathFromUserRegistration(baseKey);
if (!string.IsNullOrEmpty(userPath))
{
return userPath;
}
}
}
catch
{
// Ignore registry access failures and fall back to other checks.
}
return null;
}
private static string? GetPathFromProtocolRegistration(RegistryKey baseKey)
{
try
{
using var commandKey = baseKey.OpenSubKey($@"{PowerToysProtocolKey}\shell\open\command");
if (commandKey == null)
{
return null;
}
var command = commandKey.GetValue(string.Empty)?.ToString();
if (string.IsNullOrEmpty(command))
{
return null;
}
return ExtractInstallDirectory(command);
}
catch
{
return null;
}
}
private static string? GetPathFromUserRegistration(RegistryKey baseKey)
{
try
{
using var userKey = baseKey.OpenSubKey(PowerToysUserKey);
if (userKey == null)
{
return null;
}
var installedValue = userKey.GetValue("installed");
if (installedValue != null && installedValue.ToString() == "1")
{
return GetPathFromProtocolRegistration(baseKey);
}
}
catch
{
// Ignore registry access failures.
}
return null;
}
private static string? ExtractInstallDirectory(string command)
{
if (string.IsNullOrEmpty(command))
{
return null;
}
try
{
if (command.StartsWith('"'))
{
var closingQuote = command.IndexOf('"', 1);
if (closingQuote > 1)
{
var quotedPath = command.Substring(1, closingQuote - 1);
if (File.Exists(quotedPath))
{
return Path.GetDirectoryName(quotedPath);
}
}
}
else
{
var parts = command.Split(' ');
if (parts.Length > 0 && File.Exists(parts[0]))
{
return Path.GetDirectoryName(parts[0]);
}
}
}
catch
{
// Fall through and report no path.
}
return null;
}
}

View File

@@ -0,0 +1,142 @@
// 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.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
namespace PowerToysExtension.Helpers;
internal static class WorkspaceItemsHelper
{
private const string WorkspacesDirectory = "Microsoft\\PowerToys\\Workspaces";
private const string WorkspacesFileName = "workspaces.json";
internal sealed class WorkspaceApplication
{
public string? Application { get; set; }
}
internal sealed class WorkspaceProject
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public List<WorkspaceApplication>? Applications { get; set; }
}
internal sealed class WorkspacesData
{
public List<WorkspaceProject>? Workspaces { get; set; }
}
internal static ListItem CreateOpenEditorItem()
{
return new ListItem(new OpenWorkspaceEditorCommand())
{
Title = "Open Workspaces editor",
Subtitle = "Launch the PowerToys Workspaces editor",
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
};
}
internal static IListItem[] GetWorkspaceItems(string? searchText)
{
var workspaces = LoadWorkspaces();
var filtered = string.IsNullOrWhiteSpace(searchText)
? workspaces
: workspaces.Where(ws => Contains(ws.Name, searchText)).ToList();
var items = new List<IListItem>(filtered.Count + 1)
{
CreateOpenEditorItem(),
};
foreach (var workspace in filtered)
{
if (string.IsNullOrEmpty(workspace.Id) || string.IsNullOrEmpty(workspace.Name))
{
continue;
}
items.Add(new ListItem(new LaunchWorkspaceCommand(workspace.Id))
{
Title = workspace.Name,
Subtitle = BuildSubtitle(workspace),
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
});
}
return items.ToArray();
}
private static List<WorkspaceProject> LoadWorkspaces()
{
try
{
var path = GetWorkspacesFilePath();
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
return [];
}
var json = File.ReadAllText(path);
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
var data = JsonSerializer.Deserialize(json, PowerToysJsonContext.Default.WorkspacesData);
return data?.Workspaces ?? [];
}
catch
{
return [];
}
}
private static string? GetWorkspacesFilePath()
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (string.IsNullOrEmpty(localAppData))
{
return null;
}
return Path.Combine(localAppData, WorkspacesDirectory, WorkspacesFileName);
}
private static string BuildSubtitle(WorkspaceProject workspace)
{
var count = workspace.Applications?.Count ?? 0;
if (count == 0)
{
return "No applications";
}
if (count == 1)
{
return "1 application";
}
return string.Format(CultureInfo.CurrentCulture, "{0} applications", count);
}
private static bool Contains(string? source, string needle)
{
if (string.IsNullOrEmpty(source))
{
return false;
}
return source.Contains(needle, StringComparison.CurrentCultureIgnoreCase);
}
}

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>Microsoft.CmdPal.Ext.PowerToys</RootNamespace>
<RootNamespace>PowerToysExtension</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>false</UseWinUI>
<WindowsPackageType>None</WindowsPackageType>
<EnableMsixTooling>false</EnableMsixTooling>
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPalExtensions\$(MSBuildProjectName)\</OutputPath>
@@ -16,64 +17,34 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\SplashScreen.scale-200.png">
<Link>Assets\SplashScreen.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\LockScreenLogo.scale-200.png">
<Link>Assets\LockScreenLogo.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\Square150x150Logo.scale-200.png">
<Link>Assets\Square150x150Logo.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\Square44x44Logo.scale-200.png">
<Link>Assets\Square44x44Logo.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\Square44x44Logo.targetsize-24_altform-unplated.png">
<Link>Assets\Square44x44Logo.targetsize-24_altform-unplated.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\StoreLogo.scale-200.png">
<Link>Assets\StoreLogo.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\Microsoft.CmdPal.UI\Assets\Stable\Wide310x150Logo.scale-200.png">
<Link>Assets\Wide310x150Logo.scale-200.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="Public\README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CommandPalette.Extensions" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Shmuelie.WinRTServer" />
</ItemGroup>
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap uap3 rescap">
<Identity
Name="PowerToysExtension"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="0.0.1.0" />
<!-- When you're ready to publish your extension, you'll need to change the
Publisher= to match your own identity -->
<Properties>
<DisplayName>PowerToysExtension</DisplayName>
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="PowerToysExtension"
Description="PowerToysExtension"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="PowerToysExtension.exe" Arguments="-RegisterProcessAsComServer" DisplayName="PowerToysExtension">
<com:Class Id="257c4fae-e2d3-432c-9e86-df45c3460072" DisplayName="PowerToysExtension" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.commandpalette"
Id="ID"
PublicFolder="Public"
DisplayName="PowerToysExtension"
Description="PowerToysExtension">
<uap3:Properties>
<CmdPalProvider>
<Activation>
<CreateInstance ClassId="257c4fae-e2d3-432c-9e86-df45c3460072" />
</Activation>
<SupportedInterfaces>
<Commands/>
</SupportedInterfaces>
</CmdPalProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,45 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class AwakeProcessListPage : DynamicListPage
{
private readonly CommandItem _emptyContent;
public AwakeProcessListPage()
{
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png");
Title = "Bind Awake to process";
Name = "AwakeProcessBinding";
Id = "com.microsoft.powertoys.awake.processbinding";
_emptyContent = new CommandItem()
{
Title = "No matching processes",
Subtitle = "Try another search.",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
EmptyContent = _emptyContent;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
? "Try another search."
: $"No processes matching '{newSearch}'";
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
return AwakeCommandsFactory.GetProcessItems(SearchText);
}
}

View File

@@ -0,0 +1,45 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class AwakeSessionsPage : DynamicListPage
{
private readonly CommandItem _emptyContent;
public AwakeSessionsPage()
{
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png");
Title = "Awake actions";
Name = "AwakeActions";
Id = "com.microsoft.powertoys.awake.actions";
_emptyContent = new CommandItem()
{
Title = "No Awake actions",
Subtitle = "Try a different search phrase.",
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
};
EmptyContent = _emptyContent;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
? "Try a different search phrase."
: $"No Awake actions matching '{newSearch}'";
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
return AwakeCommandsFactory.GetSessionItems(SearchText);
}
}

View File

@@ -0,0 +1,25 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension;
internal sealed partial class PowerToysExtensionPage : ListPage
{
public PowerToysExtensionPage()
{
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
Title = "PowerToysExtension";
Name = "Open";
}
public override IListItem[] GetItems()
{
return [
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
];
}
}

View File

@@ -0,0 +1,45 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class WorkspacesListPage : DynamicListPage
{
private readonly CommandItem _emptyContent;
public WorkspacesListPage()
{
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png");
Title = "Workspaces";
Name = "Workspaces";
Id = "com.microsoft.powertoys.workspaces";
_emptyContent = new CommandItem()
{
Title = "No workspaces found",
Subtitle = "Create a workspace in PowerToys to get started.",
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
};
EmptyContent = _emptyContent;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
? "Create a workspace in PowerToys to get started."
: $"No workspaces matching '{newSearch}'";
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
return WorkspaceItemsHelper.GetWorkspaceItems(SearchText);
}
}

View File

@@ -6,9 +6,9 @@ using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.PowerToys;
namespace PowerToysExtension;
public partial class PowerToysCommandsProvider : CommandProvider
public sealed partial class PowerToysCommandsProvider : CommandProvider
{
public PowerToysCommandsProvider()
{

View File

@@ -5,28 +5,24 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Ext.PowerToys;
namespace PowerToysExtension;
[ComVisible(true)]
[Guid("F0A8B809-CE2C-475A-935F-64A0348B1D29")]
[ComDefaultInterface(typeof(IExtension))]
public sealed partial class PowerToysExtension : IExtension, IDisposable
{
private readonly ManualResetEvent _lifetime;
private readonly PowerToysCommandsProvider _provider = new();
private readonly ManualResetEvent _extensionDisposedEvent;
private readonly PowerToysExtensionCommandsProvider _provider = new();
public PowerToysExtension(ManualResetEvent extensionDisposedEvent)
{
_lifetime = extensionDisposedEvent;
Logger.LogInfo("PowerToysExtension constructed.");
this._extensionDisposedEvent = extensionDisposedEvent;
}
public object? GetProvider(ProviderType providerType)
{
Logger.LogInfo($"PowerToysExtension.GetProvider called with {providerType}.");
return providerType switch
{
ProviderType.Commands => _provider,
@@ -34,9 +30,5 @@ public sealed partial class PowerToysExtension : IExtension, IDisposable
};
}
public void Dispose()
{
Logger.LogInfo("PowerToysExtension.Dispose invoked; signaling lifetime event.");
_lifetime.Set();
}
public void Dispose() => this._extensionDisposedEvent.Set();
}

View File

@@ -0,0 +1,27 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension;
public partial class PowerToysExtensionCommandsProvider : CommandProvider
{
private readonly ICommandItem[] _commands;
public PowerToysExtensionCommandsProvider()
{
DisplayName = "PowerToysExtension";
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
_commands = [
new CommandItem(new PowerToysExtensionPage()) { Title = DisplayName },
];
}
public override ICommandItem[] TopLevelCommands()
{
return _commands;
}
}

View File

@@ -3,49 +3,49 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.PowerToys.ComServer;
using Microsoft.CommandPalette.Extensions;
using WinRT;
using Shmuelie.WinRTServer;
using Shmuelie.WinRTServer.CsWinRT;
namespace Microsoft.CmdPal.Ext.PowerToys;
namespace PowerToysExtension;
public static class Program
public class Program
{
[MTAThread]
public static int Main(string[] args)
public static void Main(string[] args)
{
try
{
// Initialize per-extension log under CmdPal/PowerToysExtension.
Logger.InitializeLogger("\\CmdPal\\PowerToysExtension\\Logs");
}
catch
{
// If logging fails we still continue; CmdPal host will surface failures.
// Continue even if logging fails.
}
var exePath = Process.GetCurrentProcess().MainModule?.FileName ?? "unknown";
Logger.LogInfo($"PowerToys CmdPal extension entry point. exe={exePath}, args=\"{string.Join(' ', args)}\"");
if (args.Length > 0 && args[0].Equals("-RegisterProcessAsComServer", StringComparison.OrdinalIgnoreCase))
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
{
// Ensure cswinrt uses our StrategyBasedComWrappers so the IExtension WinRT interface marshals correctly cross-process.
ComWrappersSupport.InitializeComWrappers(new StrategyBasedComWrappers());
using ExtensionServer server = new();
using PowerToysExtensionServer server = new();
using ManualResetEvent extensionDisposed = new(false);
var extensionInstance = new PowerToysExtension(extensionDisposed);
ManualResetEvent extensionDisposedEvent = new(false);
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
PowerToysExtension extensionInstance = new(extensionDisposedEvent);
server.RegisterExtension(() => extensionInstance);
Logger.LogInfo("Registered PowerToys CmdPal extension COM server. Waiting for dispose signal.");
extensionDisposed.WaitOne();
return 0;
}
Logger.LogWarning("PowerToys CmdPal extension launched without COM registration arguments. Exiting.");
return 0;
// This will make the main thread wait until the event is signalled by the extension class.
// Since we have single instance of the extension object, we exit as soon as it is disposed.
extensionDisposedEvent.WaitOne();
}
else
{
Console.WriteLine("Not being launched as a Extension... exiting.");
}
}
}