dev
@@ -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" />
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 968 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 921 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||