Cmdpal extension: Powertoys extension for cmdpal (#44006)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Installer built, and every command works as expected,
Now use sparse app deployment, so we don't need an extra msix

---------

Co-authored-by: kaitao-ms <kaitao1105@gmail.com>
This commit is contained in:
Kai Tao
2025-12-23 21:07:44 +08:00
committed by GitHub
parent 534c411fd8
commit d87dde132d
206 changed files with 8800 additions and 691 deletions

View File

@@ -0,0 +1,36 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Opens Advanced Paste UI by signaling the module's show event.
/// The DLL interface handles starting the process if it's not running.
/// </summary>
internal sealed partial class OpenAdvancedPasteCommand : InvokableCommand
{
public OpenAdvancedPasteCommand()
{
Name = "Open Advanced Paste";
}
public override CommandResult Invoke()
{
try
{
using var showEvent = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AdvancedPasteShowUIEvent());
showEvent.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Advanced Paste: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,26 @@
// 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 Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Commands;
internal sealed partial class RefreshAwakeStatusCommand : InvokableCommand
{
private readonly Action _refreshAction;
internal RefreshAwakeStatusCommand(Action refreshAction)
{
ArgumentNullException.ThrowIfNull(refreshAction);
_refreshAction = refreshAction;
Name = "Refresh Awake status";
}
public override CommandResult Invoke()
{
_refreshAction();
return CommandResult.KeepOpen();
}
}

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.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.ModuleContracts;
namespace PowerToysExtension.Commands;
internal sealed partial class StartAwakeCommand : InvokableCommand
{
private readonly Func<Task<OperationResult>> _action;
private readonly string _successToast;
private readonly Action? _onSuccess;
internal StartAwakeCommand(string title, Func<Task<OperationResult>> action, string successToast = "", Action? onSuccess = null)
{
ArgumentNullException.ThrowIfNull(action);
ArgumentException.ThrowIfNullOrWhiteSpace(title);
_action = action;
_successToast = successToast ?? string.Empty;
_onSuccess = onSuccess;
Name = title;
}
public override CommandResult Invoke()
{
try
{
var result = _action().GetAwaiter().GetResult();
if (!result.Success)
{
return ShowToastKeepOpen(result.Error ?? "Failed to start Awake.");
}
_onSuccess?.Invoke();
return string.IsNullOrWhiteSpace(_successToast)
? CommandResult.KeepOpen()
: ShowToastKeepOpen(_successToast);
}
catch (Exception ex)
{
return ShowToastKeepOpen($"Launching Awake failed: {ex.Message}");
}
}
private static CommandResult ShowToastKeepOpen(string message)
{
return CommandResult.ShowToast(new ToastArgs()
{
Message = message,
Result = CommandResult.KeepOpen(),
});
}
}

View File

@@ -0,0 +1,48 @@
// 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 Awake.ModuleServices;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Commands;
internal sealed partial class StopAwakeCommand : InvokableCommand
{
private readonly Action? _onSuccess;
internal StopAwakeCommand(Action? onSuccess = null)
{
_onSuccess = onSuccess;
Name = "Set Awake to Off";
}
public override CommandResult Invoke()
{
try
{
var result = AwakeService.Instance.SetOffAsync().GetAwaiter().GetResult();
if (result.Success)
{
_onSuccess?.Invoke();
return ShowToastKeepOpen("Awake switched to Off.");
}
return ShowToastKeepOpen(result.Error ?? "Awake does not appear to be running.");
}
catch (Exception ex)
{
return ShowToastKeepOpen($"Failed to switch Awake off: {ex.Message}");
}
}
private static CommandResult ShowToastKeepOpen(string message)
{
return CommandResult.ShowToast(new ToastArgs()
{
Message = message,
Result = CommandResult.KeepOpen(),
});
}
}

View File

@@ -0,0 +1,39 @@
// 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 ColorPicker.ModuleServices;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Commands;
/// <summary>
/// Copies a saved color in a chosen format.
/// </summary>
internal sealed partial class CopySavedColorCommand : InvokableCommand
{
private readonly SavedColor _color;
private readonly string _copyValue;
public CopySavedColorCommand(SavedColor color, string copyValue)
{
_color = color;
_copyValue = copyValue;
Name = $"Copy {_color.Hex}";
}
public override CommandResult Invoke()
{
try
{
ClipboardHelper.SetText(_copyValue);
return CommandResult.ShowToast($"Copied {_copyValue}");
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to copy color: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,38 @@
// 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 ColorPicker.ModuleServices;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Commands;
/// <summary>
/// Opens the Color Picker picker session via shared event.
/// </summary>
internal sealed partial class OpenColorPickerCommand : InvokableCommand
{
public OpenColorPickerCommand()
{
Name = "Open Color Picker";
}
public override CommandResult Invoke()
{
try
{
var result = ColorPickerService.Instance.OpenPickerAsync().GetAwaiter().GetResult();
if (!result.Success)
{
return CommandResult.ShowToast(result.Error ?? "Failed to open Color Picker.");
}
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Color Picker: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Triggers Crop and Lock reparent mode via the shared event.
/// </summary>
internal sealed partial class CropAndLockReparentCommand : InvokableCommand
{
public CropAndLockReparentCommand()
{
Name = "Crop and Lock (Reparent)";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to start Crop and Lock (Reparent): {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Triggers Crop and Lock thumbnail mode via the shared event.
/// </summary>
internal sealed partial class CropAndLockThumbnailCommand : InvokableCommand
{
public CropAndLockThumbnailCommand()
{
Name = "Crop and Lock (Thumbnail)";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to start Crop and Lock (Thumbnail): {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches Environment Variables (admin) via the shared event.
/// </summary>
internal sealed partial class OpenEnvironmentVariablesAdminCommand : InvokableCommand
{
public OpenEnvironmentVariablesAdminCommand()
{
Name = "Open Environment Variables (Admin)";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowEnvironmentVariablesAdminSharedEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Environment Variables (Admin): {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches Environment Variables (user) via the shared event.
/// </summary>
internal sealed partial class OpenEnvironmentVariablesCommand : InvokableCommand
{
public OpenEnvironmentVariablesCommand()
{
Name = "Open Environment Variables";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowEnvironmentVariablesSharedEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Environment Variables: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Commands;
internal sealed partial class ApplyFancyZonesLayoutCommand : InvokableCommand
{
private readonly FancyZonesLayoutDescriptor _layout;
private readonly FancyZonesMonitorDescriptor? _targetMonitor;
public ApplyFancyZonesLayoutCommand(FancyZonesLayoutDescriptor layout, FancyZonesMonitorDescriptor? monitor)
{
_layout = layout;
_targetMonitor = monitor;
Name = monitor is null ? "Apply to all monitors" : $"Apply to Monitor {monitor.Value.Title}";
Icon = new IconInfo("\uF78C");
}
public override CommandResult Invoke()
{
var monitor = _targetMonitor;
var (success, message) = monitor is null
? FancyZonesDataService.ApplyLayoutToAllMonitors(_layout)
: FancyZonesDataService.ApplyLayoutToMonitor(_layout, monitor.Value);
return success
? CommandResult.Dismiss()
: CommandResult.ShowToast(message);
}
}

View File

@@ -0,0 +1,70 @@
// 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.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesLayoutListItem : ListItem
{
private readonly Lazy<Task<IconInfo?>> _iconLoadTask;
private readonly string _layoutId;
private readonly string _layoutTitle;
private int _isLoadingIcon;
public override IIconInfo? Icon
{
get
{
if (Interlocked.Exchange(ref _isLoadingIcon, 1) == 0)
{
_ = LoadIconAsync();
}
return base.Icon;
}
set => base.Icon = value;
}
public FancyZonesLayoutListItem(ICommand command, FancyZonesLayoutDescriptor layout, IconInfo fallbackIcon)
: base(command)
{
Title = layout.Title;
Subtitle = layout.Subtitle;
Icon = fallbackIcon;
_layoutId = layout.Id;
_layoutTitle = layout.Title;
_iconLoadTask = new Lazy<Task<IconInfo?>>(async () => await FancyZonesThumbnailRenderer.RenderLayoutIconAsync(layout));
}
private async Task LoadIconAsync()
{
try
{
Logger.LogDebug($"FancyZones layout icon load starting. LayoutId={_layoutId} Title=\"{_layoutTitle}\"");
var icon = await _iconLoadTask.Value;
if (icon is not null)
{
Icon = icon;
Logger.LogDebug($"FancyZones layout icon load succeeded. LayoutId={_layoutId} Title=\"{_layoutTitle}\"");
}
else
{
Logger.LogDebug($"FancyZones layout icon load returned null. LayoutId={_layoutId} Title=\"{_layoutTitle}\"");
}
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones layout icon load failed. LayoutId={_layoutId} Title=\"{_layoutTitle}\" Exception={ex}");
}
}
}

View File

@@ -0,0 +1,75 @@
// 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.Collections.Generic;
using System.Globalization;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorListItem : ListItem
{
public FancyZonesMonitorListItem(FancyZonesMonitorDescriptor monitor, string subtitle, IconInfo icon)
: base(new IdentifyFancyZonesMonitorCommand(monitor))
{
Title = monitor.Title;
Subtitle = subtitle;
Icon = icon;
Details = BuildMonitorDetails(monitor);
var pickerPage = new FancyZonesMonitorLayoutPickerPage(monitor)
{
Name = "Set active layout",
};
MoreCommands =
[
new CommandContextItem(pickerPage)
{
Title = "Set active layout",
Subtitle = "Pick a layout for this monitor",
},
];
}
public static Details BuildMonitorDetails(FancyZonesMonitorDescriptor monitor)
{
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
var tags = new List<IDetailsElement>
{
DetailTag("Monitor", monitor.Data.Monitor),
DetailTag("Instance", monitor.Data.MonitorInstanceId),
DetailTag("Serial", monitor.Data.MonitorSerialNumber),
DetailTag("Number", monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
DetailTag("Virtual desktop", currentVirtualDesktop),
DetailTag("Work area", $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
DetailTag("Resolution", $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
DetailTag("DPI", monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
};
return new Details
{
Title = monitor.Title,
HeroImage = FancyZonesMonitorPreviewRenderer.TryRenderMonitorHeroImage(monitor) ??
PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
Metadata = tags.ToArray(),
};
}
private static DetailsElement DetailTag(string key, string? value)
{
return new DetailsElement
{
Key = key,
Data = new DetailsTags
{
Tags = [new Tag(string.IsNullOrWhiteSpace(value) ? "n/a" : value)],
},
};
}
}

View File

@@ -0,0 +1,46 @@
// 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.Linq;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Commands;
internal sealed partial class IdentifyFancyZonesMonitorCommand : InvokableCommand
{
private readonly FancyZonesMonitorDescriptor _monitor;
public IdentifyFancyZonesMonitorCommand(FancyZonesMonitorDescriptor monitor)
{
_monitor = monitor;
Name = $"Identify {_monitor.Title}";
Icon = new IconInfo("\uE773");
}
public override CommandResult Invoke()
{
if (!FancyZonesDataService.TryGetMonitors(out var monitors, out var error))
{
return CommandResult.ShowToast(error);
}
var monitor = monitors.FirstOrDefault(m => m.Data.MonitorInstanceId == _monitor.Data.MonitorInstanceId);
if (monitor == null)
{
return CommandResult.ShowToast("Monitor not found.");
}
FancyZonesMonitorIdentifier.Show(
monitor.Data.LeftCoordinate,
monitor.Data.TopCoordinate,
monitor.Data.WorkAreaWidth,
monitor.Data.WorkAreaHeight,
_monitor.Title,
durationMs: 1200);
return CommandResult.KeepOpen();
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches the FancyZones layout editor via the shared event.
/// </summary>
internal sealed partial class OpenFancyZonesEditorCommand : InvokableCommand
{
public OpenFancyZonesEditorCommand()
{
Name = "Open FancyZones Editor";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open FancyZones editor: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches Hosts File Editor (Admin) via the shared event.
/// </summary>
internal sealed partial class OpenHostsEditorAdminCommand : InvokableCommand
{
public OpenHostsEditorAdminCommand()
{
Name = "Open Hosts File Editor (Admin)";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowHostsAdminSharedEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Hosts File Editor (Admin): {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches Hosts File Editor via the shared event.
/// </summary>
internal sealed partial class OpenHostsEditorCommand : InvokableCommand
{
public OpenHostsEditorCommand()
{
Name = "Open Hosts File Editor";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowHostsSharedEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Hosts File Editor: {ex.Message}");
}
}
}

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 = "",
string executableName = "",
string arguments = "",
string displayName = "")
{
if (string.IsNullOrWhiteSpace(moduleName))
{
throw new ArgumentException("Module name is required", nameof(moduleName));
}
_moduleName = moduleName;
_eventName = eventName ?? string.Empty;
_executableName = executableName ?? string.Empty;
_arguments = arguments ?? string.Empty;
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,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles Light Switch via the shared event.
/// </summary>
internal sealed partial class ToggleLightSwitchCommand : InvokableCommand
{
public ToggleLightSwitchCommand()
{
Name = "Toggle Light Switch";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.LightSwitchToggleEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Light Switch: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Shows Mouse Jump preview via the shared event.
/// </summary>
internal sealed partial class ShowMouseJumpPreviewCommand : InvokableCommand
{
public ShowMouseJumpPreviewCommand()
{
Name = "Show Mouse Jump Preview";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MouseJumpShowPreviewEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to show Mouse Jump preview: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles Cursor Wrap via the shared trigger event.
/// </summary>
internal sealed partial class ToggleCursorWrapCommand : InvokableCommand
{
public ToggleCursorWrapCommand()
{
Name = "Toggle Cursor Wrap";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CursorWrapTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Cursor Wrap: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Triggers Find My Mouse via the shared event.
/// </summary>
internal sealed partial class ToggleFindMyMouseCommand : InvokableCommand
{
public ToggleFindMyMouseCommand()
{
Name = "Trigger Find My Mouse";
}
public override CommandResult Invoke()
{
// Delay the trigger so the Command Palette dismisses first
_ = Task.Run(async () =>
{
await Task.Delay(200).ConfigureAwait(false);
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FindMyMouseTriggerEvent());
evt.Set();
}
catch
{
// Ignore errors in background task
}
});
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles Mouse Pointer Crosshairs via the shared event.
/// </summary>
internal sealed partial class ToggleMouseCrosshairsCommand : InvokableCommand
{
public ToggleMouseCrosshairsCommand()
{
Name = "Toggle Mouse Crosshairs";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MouseCrosshairsTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Mouse Crosshairs: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles Mouse Highlighter via the shared event.
/// </summary>
internal sealed partial class ToggleMouseHighlighterCommand : InvokableCommand
{
public ToggleMouseHighlighterCommand()
{
Name = "Toggle Mouse Highlighter";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MouseHighlighterTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Mouse Highlighter: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,28 @@
// 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 Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Commands;
/// <summary>
/// Opens the PowerToys settings page for the given module via SettingsDeepLink.
/// </summary>
internal sealed partial class OpenInSettingsCommand : InvokableCommand
{
private readonly SettingsDeepLink.SettingsWindow _module;
internal OpenInSettingsCommand(SettingsDeepLink.SettingsWindow module, string title = "")
{
_module = module;
Name = string.IsNullOrWhiteSpace(title) ? $"Open {_module} settings" : title;
}
public override CommandResult Invoke()
{
SettingsDeepLink.OpenSettings(_module);
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,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Launches Registry Preview via the shared event.
/// </summary>
internal sealed partial class OpenRegistryPreviewCommand : InvokableCommand
{
public OpenRegistryPreviewCommand()
{
Name = "Open Registry Preview";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to open Registry Preview: {ex.Message}");
}
}
}

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.
using System;
using System.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
internal sealed partial class ToggleScreenRulerCommand : InvokableCommand
{
public ToggleScreenRulerCommand()
{
Name = "Toggle Screen Ruler";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Screen Ruler: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles the Shortcut Guide UI via the shared trigger event.
/// </summary>
internal sealed partial class ToggleShortcutGuideCommand : InvokableCommand
{
public ToggleShortcutGuideCommand()
{
Name = "Toggle Shortcut Guide";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Shortcut Guide: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Triggers the Text Extractor UI via the existing show event.
/// </summary>
internal sealed partial class ToggleTextExtractorCommand : InvokableCommand
{
public ToggleTextExtractorCommand()
{
Name = "Toggle Text Extractor";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Text Extractor: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
using Workspaces.ModuleServices;
namespace PowerToysExtension.Commands;
internal sealed partial class LaunchWorkspaceCommand : InvokableCommand
{
private readonly string _workspaceId;
internal LaunchWorkspaceCommand(string workspaceId)
{
_workspaceId = workspaceId;
Name = "Launch workspace";
}
public override CommandResult Invoke()
{
if (string.IsNullOrEmpty(_workspaceId))
{
return CommandResult.KeepOpen();
}
var result = WorkspaceService.Instance.LaunchWorkspaceAsync(_workspaceId).GetAwaiter().GetResult();
if (!result.Success)
{
return CommandResult.ShowToast(result.Error ?? "Launching workspace failed.");
}
return CommandResult.Hide();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
using Workspaces.ModuleServices;
namespace PowerToysExtension.Commands;
internal sealed partial class OpenWorkspaceEditorCommand : InvokableCommand
{
public override CommandResult Invoke()
{
var result = WorkspaceService.Instance.LaunchEditorAsync().GetAwaiter().GetResult();
if (!result.Success)
{
return CommandResult.ShowToast(result.Error ?? "Unable to launch the Workspaces editor.");
}
return CommandResult.Hide();
}
}

View File

@@ -0,0 +1,117 @@
// 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 Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
using WorkspacesCsharpLibrary.Data;
namespace PowerToysExtension.Commands;
internal sealed partial class WorkspaceListItem : ListItem
{
public WorkspaceListItem(ProjectWrapper workspace, IconInfo icon)
: base(new LaunchWorkspaceCommand(workspace.Id))
{
Title = workspace.Name;
Subtitle = BuildSubtitle(workspace);
Icon = icon;
Details = BuildDetails(workspace, icon);
}
private static string BuildSubtitle(ProjectWrapper workspace)
{
var appCount = workspace.Applications?.Count ?? 0;
var appsText = appCount switch
{
0 => "No applications",
_ => string.Format(CultureInfo.CurrentCulture, "{0} applications", appCount),
};
var lastLaunched = workspace.LastLaunchedTime > 0
? $"Last launched {FormatRelativeTime(workspace.LastLaunchedTime)}"
: "Never launched";
return $"{appsText} \u2022 {lastLaunched}";
}
private static Details BuildDetails(ProjectWrapper workspace, IconInfo icon)
{
var appCount = workspace.Applications?.Count ?? 0;
var body = appCount switch
{
0 => "No applications in this workspace",
1 => "1 application",
_ => $"{appCount} applications",
};
return new Details
{
HeroImage = icon,
Title = workspace.Name ?? "Workspace",
Body = body,
Metadata = BuildAppMetadata(workspace),
};
}
private static IDetailsElement[] BuildAppMetadata(ProjectWrapper workspace)
{
if (workspace.Applications is null || workspace.Applications.Count == 0)
{
return Array.Empty<IDetailsElement>();
}
var elements = new List<IDetailsElement>();
foreach (var app in workspace.Applications)
{
var appName = string.IsNullOrWhiteSpace(app.Application) ? "App" : app.Application;
var title = string.IsNullOrWhiteSpace(app.Title) ? appName : app.Title;
var tags = new List<ITag>();
if (!string.IsNullOrWhiteSpace(app.ApplicationPath))
{
tags.Add(new Tag(app.ApplicationPath));
}
else
{
tags.Add(new Tag(appName));
}
elements.Add(new DetailsElement
{
Key = title,
Data = new DetailsTags { Tags = tags.ToArray() },
});
}
return elements.ToArray();
}
private static string FormatRelativeTime(long unixSeconds)
{
var lastLaunch = DateTimeOffset.FromUnixTimeSeconds(unixSeconds).UtcDateTime;
var delta = DateTime.UtcNow - lastLaunch;
if (delta.TotalMinutes < 1)
{
return "just now";
}
if (delta.TotalMinutes < 60)
{
return string.Format(CultureInfo.CurrentCulture, "{0} min ago", (int)delta.TotalMinutes);
}
if (delta.TotalHours < 24)
{
return string.Format(CultureInfo.CurrentCulture, "{0} hr ago", (int)delta.TotalHours);
}
return string.Format(CultureInfo.CurrentCulture, "{0} days ago", (int)delta.TotalDays);
}
}

View File

@@ -0,0 +1,84 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
internal sealed partial class ZoomItActionCommand : InvokableCommand
{
private readonly string _action;
private readonly string _title;
public ZoomItActionCommand(string action, string title)
{
_action = action;
_title = title;
Name = title;
}
public override CommandResult Invoke()
{
try
{
if (!TryGetEventName(_action, out var eventName))
{
return CommandResult.ShowToast($"Unknown ZoomIt action: {_action}.");
}
var evt = EventWaitHandle.OpenExisting(eventName);
_ = Task.Run(async () =>
{
using (evt)
{
// Hide CmdPal first, then signal shortly after so UI like snip/zoom won't capture it.
await Task.Delay(50).ConfigureAwait(false);
evt.Set();
}
});
return CommandResult.Hide();
}
catch (WaitHandleCannotBeOpenedException)
{
return CommandResult.ShowToast("ZoomIt is not running. Please start it from PowerToys and try again.");
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to invoke ZoomIt ({_title}): {ex.Message}");
}
}
private static bool TryGetEventName(string action, out string eventName)
{
switch (action.ToLowerInvariant())
{
case "zoom":
eventName = Constants.ZoomItZoomEvent();
return true;
case "draw":
eventName = Constants.ZoomItDrawEvent();
return true;
case "break":
eventName = Constants.ZoomItBreakEvent();
return true;
case "livezoom":
eventName = Constants.ZoomItLiveZoomEvent();
return true;
case "snip":
eventName = Constants.ZoomItSnipEvent();
return true;
case "record":
eventName = Constants.ZoomItRecordEvent();
return true;
default:
eventName = string.Empty;
return false;
}
}
}

View File

@@ -0,0 +1,60 @@
// 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 Awake.ModuleServices;
using Common.UI;
namespace PowerToysExtension.Helpers;
internal static class AwakeStatusService
{
internal static string GetStatusSubtitle()
{
var state = AwakeService.Instance.GetCurrentState();
if (!state.IsRunning)
{
return "Awake is idle";
}
if (state.Mode == AwakeStateMode.Passive)
{
// When the PowerToys Awake module is enabled, the Awake process stays resident
// even in passive mode. In that case "idle" is correct. If the module is disabled,
// a running process implies a standalone/session keep-awake, so report as active.
return ModuleEnablementService.IsModuleEnabled(SettingsDeepLink.SettingsWindow.Awake)
? "Awake is idle"
: "Active - session running";
}
return state.Mode switch
{
AwakeStateMode.Indefinite => "Active - indefinite",
AwakeStateMode.Timed => state.Duration is { } span
? $"Active - timer {FormatDuration(span)}"
: "Active - timer",
AwakeStateMode.Expirable => state.Expiration is { } expiry
? $"Active - until {expiry.ToLocalTime():t}"
: "Active - scheduled",
_ => "Awake is running",
};
}
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";
}
}

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 System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
namespace PowerToysExtension.Helpers;
internal static class ColorSwatchIconFactory
{
public static IconInfo Create(byte r, byte g, byte b, byte a)
{
try
{
using var bmp = new Bitmap(32, 32, PixelFormat.Format32bppArgb);
using var gfx = Graphics.FromImage(bmp);
gfx.SmoothingMode = SmoothingMode.AntiAlias;
gfx.Clear(Color.Transparent);
using var brush = new SolidBrush(Color.FromArgb(a, r, g, b));
const int padding = 4;
gfx.FillEllipse(brush, padding, padding, bmp.Width - (padding * 2), bmp.Height - (padding * 2));
using var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Png);
var ras = new InMemoryRandomAccessStream();
var writer = new DataWriter(ras);
writer.WriteBytes(ms.ToArray());
writer.StoreAsync().GetResults();
ras.Seek(0);
return IconInfo.FromStream(ras);
}
catch
{
// Fallback to a simple colored glyph when drawing fails.
return new IconInfo("\u25CF"); // Black circle glyph
}
}
}

View File

@@ -0,0 +1,517 @@
// 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.IO;
using System.Linq;
using System.Text.Json;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
using ManagedCommon;
using FZPaths = FancyZonesEditorCommon.Data.FancyZonesPaths;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesDataService
{
private const string ZeroUuid = "{00000000-0000-0000-0000-000000000000}";
public static bool TryGetMonitors(out IReadOnlyList<FancyZonesMonitorDescriptor> monitors, out string error)
{
monitors = Array.Empty<FancyZonesMonitorDescriptor>();
error = string.Empty;
Logger.LogInfo($"TryGetMonitors: Starting. EditorParametersPath={FZPaths.EditorParameters}");
try
{
if (!File.Exists(FZPaths.EditorParameters))
{
error = "FancyZones monitor data not found. Open FancyZones Editor once to initialize.";
Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
return false;
}
Logger.LogInfo($"TryGetMonitors: File exists, reading...");
var editorParams = FancyZonesDataIO.ReadEditorParameters();
Logger.LogInfo($"TryGetMonitors: ReadEditorParameters returned. Monitors={editorParams.Monitors?.Count ?? -1}");
var editorMonitors = editorParams.Monitors;
if (editorMonitors is null || editorMonitors.Count == 0)
{
error = "No FancyZones monitors found.";
Logger.LogWarning($"TryGetMonitors: No monitors in file.");
return false;
}
monitors = editorMonitors
.Select((monitor, i) => new FancyZonesMonitorDescriptor(i + 1, monitor))
.ToArray();
Logger.LogInfo($"TryGetMonitors: Succeeded. MonitorCount={monitors.Count}");
return true;
}
catch (Exception ex)
{
error = $"Failed to read FancyZones monitor data: {ex.Message}";
Logger.LogError($"TryGetMonitors: Exception. Message={ex.Message} Stack={ex.StackTrace}");
return false;
}
}
public static IReadOnlyList<FancyZonesLayoutDescriptor> GetLayouts()
{
Logger.LogInfo($"GetLayouts: Starting. LayoutTemplatesPath={FZPaths.LayoutTemplates} CustomLayoutsPath={FZPaths.CustomLayouts}");
var layouts = new List<FancyZonesLayoutDescriptor>();
try
{
var templates = GetTemplateLayouts().ToArray();
Logger.LogInfo($"GetLayouts: GetTemplateLayouts returned {templates.Length} layouts");
layouts.AddRange(templates);
}
catch (Exception ex)
{
Logger.LogError($"GetLayouts: GetTemplateLayouts failed. Message={ex.Message} Stack={ex.StackTrace}");
}
try
{
var customLayouts = GetCustomLayouts().ToArray();
Logger.LogInfo($"GetLayouts: GetCustomLayouts returned {customLayouts.Length} layouts");
layouts.AddRange(customLayouts);
}
catch (Exception ex)
{
Logger.LogError($"GetLayouts: GetCustomLayouts failed. Message={ex.Message} Stack={ex.StackTrace}");
}
Logger.LogInfo($"GetLayouts: Total layouts={layouts.Count}");
return layouts;
}
public static bool TryGetAppliedLayoutForMonitor(EditorParameters.NativeMonitorDataWrapper monitor, out AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper? appliedLayout)
=> TryGetAppliedLayoutForMonitor(monitor, FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString(), out appliedLayout);
public static bool TryGetAppliedLayoutForMonitor(EditorParameters.NativeMonitorDataWrapper monitor, string virtualDesktopId, out AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper? appliedLayout)
{
appliedLayout = null;
if (!TryReadAppliedLayouts(out var file))
{
return false;
}
var match = FindAppliedLayoutEntry(file, monitor, virtualDesktopId);
if (match is not null)
{
appliedLayout = match.Value.AppliedLayout;
return true;
}
return false;
}
public static (bool Success, string Message) ApplyLayoutToAllMonitors(FancyZonesLayoutDescriptor layout)
{
if (!TryGetMonitors(out var monitors, out var error))
{
return (false, error);
}
return ApplyLayoutToMonitors(layout, monitors.Select(m => m.Data));
}
public static (bool Success, string Message) ApplyLayoutToMonitor(FancyZonesLayoutDescriptor layout, FancyZonesMonitorDescriptor monitor)
{
if (!TryGetMonitors(out var monitors, out var error))
{
return (false, error);
}
EditorParameters.NativeMonitorDataWrapper? monitorData = null;
foreach (var candidate in monitors)
{
if (candidate.Data.MonitorInstanceId == monitor.Data.MonitorInstanceId)
{
monitorData = candidate.Data;
break;
}
}
if (monitorData is null)
{
return (false, "Monitor not found.");
}
return ApplyLayoutToMonitors(layout, [monitorData.Value]);
}
private static (bool Success, string Message) ApplyLayoutToMonitors(FancyZonesLayoutDescriptor layout, IEnumerable<EditorParameters.NativeMonitorDataWrapper> monitors)
{
AppliedLayouts.AppliedLayoutsListWrapper appliedFile;
if (!TryReadAppliedLayouts(out var existingFile))
{
appliedFile = new AppliedLayouts.AppliedLayoutsListWrapper { AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper>() };
}
else
{
appliedFile = existingFile;
}
appliedFile.AppliedLayouts ??= new List<AppliedLayouts.AppliedLayoutWrapper>();
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
foreach (var monitor in monitors)
{
var existingEntry = FindAppliedLayoutEntry(appliedFile, monitor, currentVirtualDesktop);
if (existingEntry is not null)
{
// Remove the existing entry so we can add a new one
appliedFile.AppliedLayouts.Remove(existingEntry.Value);
}
var newEntry = new AppliedLayouts.AppliedLayoutWrapper
{
Device = new AppliedLayouts.AppliedLayoutWrapper.DeviceIdWrapper
{
Monitor = monitor.Monitor,
MonitorInstance = monitor.MonitorInstanceId ?? string.Empty,
SerialNumber = monitor.MonitorSerialNumber ?? string.Empty,
MonitorNumber = monitor.MonitorNumber,
VirtualDesktop = currentVirtualDesktop,
},
AppliedLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
{
Uuid = layout.ApplyLayout.Uuid,
Type = layout.ApplyLayout.Type,
ZoneCount = layout.ApplyLayout.ZoneCount,
ShowSpacing = layout.ApplyLayout.ShowSpacing,
Spacing = layout.ApplyLayout.Spacing,
SensitivityRadius = layout.ApplyLayout.SensitivityRadius,
},
};
appliedFile.AppliedLayouts.Add(newEntry);
}
try
{
FancyZonesDataIO.WriteAppliedLayouts(appliedFile);
}
catch (Exception ex)
{
return (false, $"Failed to write applied layouts: {ex.Message}");
}
try
{
FancyZonesNotifier.NotifyAppliedLayoutsChanged();
}
catch (Exception ex)
{
return (true, $"Layout applied, but FancyZones could not be notified: {ex.Message}");
}
return (true, "Layout applied.");
}
private static AppliedLayouts.AppliedLayoutWrapper? FindAppliedLayoutEntry(AppliedLayouts.AppliedLayoutsListWrapper file, EditorParameters.NativeMonitorDataWrapper monitor, string virtualDesktopId)
{
if (file.AppliedLayouts is null)
{
return null;
}
return file.AppliedLayouts.FirstOrDefault(e =>
string.Equals(e.Device.Monitor, monitor.Monitor, StringComparison.OrdinalIgnoreCase) &&
string.Equals(e.Device.MonitorInstance ?? string.Empty, monitor.MonitorInstanceId ?? string.Empty, StringComparison.OrdinalIgnoreCase) &&
string.Equals(e.Device.SerialNumber ?? string.Empty, monitor.MonitorSerialNumber ?? string.Empty, StringComparison.OrdinalIgnoreCase) &&
e.Device.MonitorNumber == monitor.MonitorNumber &&
string.Equals(e.Device.VirtualDesktop, virtualDesktopId, StringComparison.OrdinalIgnoreCase));
}
private static bool TryReadAppliedLayouts(out AppliedLayouts.AppliedLayoutsListWrapper file)
{
file = default;
try
{
if (!File.Exists(FZPaths.AppliedLayouts))
{
return false;
}
file = FancyZonesDataIO.ReadAppliedLayouts();
return true;
}
catch
{
return false;
}
}
private static IEnumerable<FancyZonesLayoutDescriptor> GetTemplateLayouts()
{
Logger.LogInfo($"GetTemplateLayouts: Starting. Path={FZPaths.LayoutTemplates} Exists={File.Exists(FZPaths.LayoutTemplates)}");
LayoutTemplates.TemplateLayoutsListWrapper templates;
try
{
if (!File.Exists(FZPaths.LayoutTemplates))
{
Logger.LogWarning($"GetTemplateLayouts: File not found.");
yield break;
}
templates = FancyZonesDataIO.ReadLayoutTemplates();
Logger.LogInfo($"GetTemplateLayouts: ReadLayoutTemplates succeeded. Count={templates.LayoutTemplates?.Count ?? -1}");
}
catch (Exception ex)
{
Logger.LogError($"GetTemplateLayouts: ReadLayoutTemplates failed. Message={ex.Message} Stack={ex.StackTrace}");
yield break;
}
var templateLayouts = templates.LayoutTemplates;
if (templateLayouts is null)
{
Logger.LogWarning($"GetTemplateLayouts: LayoutTemplates is null.");
yield break;
}
foreach (var template in templateLayouts)
{
if (string.IsNullOrWhiteSpace(template.Type))
{
continue;
}
var type = template.Type.Trim();
var zoneCount = type.Equals("blank", StringComparison.OrdinalIgnoreCase)
? 0
: template.ZoneCount > 0 ? template.ZoneCount : 3;
var title = $"Template: {type}";
var subtitle = $"{zoneCount} zones";
yield return new FancyZonesLayoutDescriptor
{
Id = $"template:{type.ToLowerInvariant()}",
Source = FancyZonesLayoutSource.Template,
Title = title,
Subtitle = subtitle,
Template = template,
ApplyLayout = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
{
Type = type.ToLowerInvariant(),
Uuid = ZeroUuid,
ZoneCount = zoneCount,
ShowSpacing = template.ShowSpacing,
Spacing = template.Spacing,
SensitivityRadius = template.SensitivityRadius,
},
};
}
}
private static IEnumerable<FancyZonesLayoutDescriptor> GetCustomLayouts()
{
CustomLayouts.CustomLayoutListWrapper customLayouts;
try
{
if (!File.Exists(FZPaths.CustomLayouts))
{
yield break;
}
customLayouts = FancyZonesDataIO.ReadCustomLayouts();
}
catch
{
yield break;
}
var layouts = customLayouts.CustomLayouts;
if (layouts is null)
{
yield break;
}
foreach (var custom in layouts)
{
if (string.IsNullOrWhiteSpace(custom.Uuid) || string.IsNullOrWhiteSpace(custom.Name))
{
continue;
}
var uuid = custom.Uuid.Trim();
var customType = custom.Type?.Trim().ToLowerInvariant() ?? string.Empty;
if (!TryBuildAppliedLayoutForCustom(custom, out var applied))
{
continue;
}
var title = custom.Name.Trim();
var subtitle = customType switch
{
"grid" => $"Custom grid \u2022 {applied.ZoneCount} zones",
"canvas" => $"Custom canvas \u2022 {applied.ZoneCount} zones",
_ => $"Custom \u2022 {applied.ZoneCount} zones",
};
yield return new FancyZonesLayoutDescriptor
{
Id = $"custom:{uuid}",
Source = FancyZonesLayoutSource.Custom,
Title = title,
Subtitle = subtitle,
Custom = custom,
ApplyLayout = applied,
};
}
}
private static bool TryBuildAppliedLayoutForCustom(CustomLayouts.CustomLayoutWrapper custom, out AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper applied)
{
applied = new AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper
{
Type = "custom",
Uuid = custom.Uuid.Trim(),
ShowSpacing = false,
Spacing = 0,
ZoneCount = 0,
SensitivityRadius = 20,
};
if (custom.Info.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
{
return false;
}
var customType = custom.Type?.Trim().ToLowerInvariant() ?? string.Empty;
if (customType == "grid")
{
if (!TryParseCustomGridInfo(custom.Info, out var zoneCount, out var showSpacing, out var spacing, out var sensitivity))
{
return false;
}
applied.ZoneCount = zoneCount;
applied.ShowSpacing = showSpacing;
applied.Spacing = spacing;
applied.SensitivityRadius = sensitivity;
return true;
}
if (customType == "canvas")
{
if (!TryParseCustomCanvasInfo(custom.Info, out var zoneCount, out var sensitivity))
{
return false;
}
applied.ZoneCount = zoneCount;
applied.SensitivityRadius = sensitivity;
applied.ShowSpacing = false;
applied.Spacing = 0;
return true;
}
return false;
}
internal static bool TryParseCustomGridInfo(JsonElement info, out int zoneCount, out bool showSpacing, out int spacing, out int sensitivityRadius)
{
zoneCount = 0;
showSpacing = false;
spacing = 0;
sensitivityRadius = 20;
if (!info.TryGetProperty("rows", out var rowsProp) ||
!info.TryGetProperty("columns", out var columnsProp) ||
rowsProp.ValueKind != JsonValueKind.Number ||
columnsProp.ValueKind != JsonValueKind.Number)
{
return false;
}
var rows = rowsProp.GetInt32();
var columns = columnsProp.GetInt32();
if (rows <= 0 || columns <= 0)
{
return false;
}
if (info.TryGetProperty("cell-child-map", out var cellMap) && cellMap.ValueKind == JsonValueKind.Array)
{
var max = -1;
foreach (var row in cellMap.EnumerateArray())
{
if (row.ValueKind != JsonValueKind.Array)
{
continue;
}
foreach (var cell in row.EnumerateArray())
{
if (cell.ValueKind == JsonValueKind.Number && cell.TryGetInt32(out var value))
{
max = Math.Max(max, value);
}
}
}
zoneCount = max + 1;
}
else
{
zoneCount = rows * columns;
}
if (zoneCount <= 0)
{
return false;
}
if (info.TryGetProperty("show-spacing", out var showSpacingProp) &&
(showSpacingProp.ValueKind == JsonValueKind.True || showSpacingProp.ValueKind == JsonValueKind.False))
{
showSpacing = showSpacingProp.GetBoolean();
}
if (info.TryGetProperty("spacing", out var spacingProp) && spacingProp.ValueKind == JsonValueKind.Number)
{
spacing = spacingProp.GetInt32();
}
if (info.TryGetProperty("sensitivity-radius", out var sensitivityProp) && sensitivityProp.ValueKind == JsonValueKind.Number)
{
sensitivityRadius = sensitivityProp.GetInt32();
}
return true;
}
internal static bool TryParseCustomCanvasInfo(JsonElement info, out int zoneCount, out int sensitivityRadius)
{
zoneCount = 0;
sensitivityRadius = 20;
if (!info.TryGetProperty("zones", out var zones) || zones.ValueKind != JsonValueKind.Array)
{
return false;
}
zoneCount = zones.GetArrayLength();
if (info.TryGetProperty("sensitivity-radius", out var sensitivityProp) && sensitivityProp.ValueKind == JsonValueKind.Number)
{
sensitivityRadius = sensitivityProp.GetInt32();
}
return zoneCount >= 0;
}
}

View File

@@ -0,0 +1,24 @@
// 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 FancyZonesEditorCommon.Data;
namespace PowerToysExtension.Helpers;
internal sealed class FancyZonesLayoutDescriptor
{
public required string Id { get; init; } // "template:<type>" or "custom:<uuid>"
public required FancyZonesLayoutSource Source { get; init; }
public required string Title { get; init; }
public required string Subtitle { get; init; }
public required AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper ApplyLayout { get; init; }
public LayoutTemplates.TemplateLayoutWrapper? Template { get; init; }
public CustomLayouts.CustomLayoutWrapper? Custom { get; init; }
}

View File

@@ -0,0 +1,5 @@
// 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.
// Intentionally empty: this file was an accidental duplicate of FancyZonesLayoutDescriptor.cs.

View File

@@ -0,0 +1,11 @@
// 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;
internal enum FancyZonesLayoutSource
{
Template,
Custom,
}

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 System;
using System.Globalization;
using FancyZonesEditorCommon.Data;
namespace PowerToysExtension.Helpers;
internal readonly record struct FancyZonesMonitorDescriptor(
int Index,
EditorParameters.NativeMonitorDataWrapper Data)
{
public string Title => Data.Monitor;
public string Subtitle
{
get
{
var size = $"{Data.MonitorWidth}×{Data.MonitorHeight}";
var scaling = Data.Dpi > 0 ? string.Format(CultureInfo.InvariantCulture, "{0}%", (int)Math.Round(Data.Dpi * 100 / 96.0)) : "n/a";
return $"{size} \u2022 {scaling}";
}
}
}

View File

@@ -0,0 +1,461 @@
// 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.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesMonitorIdentifier
{
private const string WindowClassName = "PowerToys_FancyZones_MonitorIdentify";
private const uint WsExToolWindow = 0x00000080;
private const uint WsExTopmost = 0x00000008;
private const uint WsExTransparent = 0x00000020;
private const uint WsPopup = 0x80000000;
private const uint WmDestroy = 0x0002;
private const uint WmPaint = 0x000F;
private const uint WmTimer = 0x0113;
private const uint CsVRedraw = 0x0001;
private const uint CsHRedraw = 0x0002;
private const int SwShowNoActivate = 4;
private const int Transparent = 1;
private const int BaseFontHeightPx = 52;
private const int BaseDpi = 96;
private const uint DtCenter = 0x00000001;
private const uint DtVCenter = 0x00000004;
private const uint DtSingleLine = 0x00000020;
private const uint MonitorDefaultToNearest = 2;
private static readonly nint DpiAwarenessContextUnaware = new(-1);
private static readonly object Sync = new();
private static bool classRegistered;
private static GCHandle? currentPinnedTextHandle;
public static void Show(int left, int top, int width, int height, string text, int durationMs = 1200)
{
if (string.IsNullOrWhiteSpace(text))
{
text = "Monitor";
}
_ = Task.Run(() => RunWindow(left, top, width, height, text, durationMs))
.ContinueWith(static t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted);
}
private static unsafe void RunWindow(int left, int top, int width, int height, string text, int durationMs)
{
EnsureClassRegistered();
var workArea = TryGetWorkAreaFromFancyZonesCoordinates(left, top, width, height, out var resolvedWorkArea)
? resolvedWorkArea
: new RECT
{
Left = left,
Top = top,
Right = left + width,
Bottom = top + height,
};
var workAreaWidth = Math.Max(0, workArea.Right - workArea.Left);
var workAreaHeight = Math.Max(0, workArea.Bottom - workArea.Top);
var overlayWidth = Math.Clamp(workAreaWidth / 4, 220, 420);
var overlayHeight = Math.Clamp(workAreaHeight / 6, 120, 240);
var x = workArea.Left + ((workAreaWidth - overlayWidth) / 2);
var y = workArea.Top + ((workAreaHeight - overlayHeight) / 2);
lock (Sync)
{
currentPinnedTextHandle?.Free();
currentPinnedTextHandle = GCHandle.Alloc(text, GCHandleType.Pinned);
}
var hwnd = CreateWindowExW(
WsExToolWindow | WsExTopmost | WsExTransparent,
WindowClassName,
"MonitorIdentify",
WsPopup,
x,
y,
overlayWidth,
overlayHeight,
nint.Zero,
nint.Zero,
GetModuleHandleW(null),
nint.Zero);
if (hwnd == nint.Zero)
{
return;
}
_ = ShowWindow(hwnd, SwShowNoActivate);
_ = UpdateWindow(hwnd);
_ = SetTimer(hwnd, 1, (uint)durationMs, nint.Zero);
MSG msg;
while (GetMessageW(out msg, nint.Zero, 0, 0) != 0)
{
_ = TranslateMessage(in msg);
_ = DispatchMessageW(in msg);
}
lock (Sync)
{
currentPinnedTextHandle?.Free();
currentPinnedTextHandle = null;
}
}
private static unsafe void EnsureClassRegistered()
{
lock (Sync)
{
if (classRegistered)
{
return;
}
fixed (char* className = WindowClassName ?? string.Empty)
{
var wc = new WNDCLASSEXW
{
CbSize = (uint)sizeof(WNDCLASSEXW),
Style = CsHRedraw | CsVRedraw,
LpfnWndProc = &WndProc,
HInstance = GetModuleHandleW(null),
HCursor = LoadCursorW(nint.Zero, new IntPtr(32512)), // IDC_ARROW
LpszClassName = className,
};
_ = RegisterClassExW(in wc);
classRegistered = true;
}
}
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static unsafe nint WndProc(nint hwnd, uint msg, nuint wParam, nint lParam)
{
switch (msg)
{
case WmTimer:
_ = KillTimer(hwnd, 1);
_ = DestroyWindow(hwnd);
return nint.Zero;
case WmDestroy:
PostQuitMessage(0);
return nint.Zero;
case WmPaint:
{
var hdc = BeginPaint(hwnd, out var ps);
_ = GetClientRect(hwnd, out var rect);
var bgBrush = CreateSolidBrush(0x202020);
_ = FillRect(hdc, in rect, bgBrush);
_ = SetBkMode(hdc, Transparent);
_ = SetTextColor(hdc, 0xFFFFFF);
var dpi = GetDpiForWindow(hwnd);
var fontHeight = -MulDiv(BaseFontHeightPx, (int)dpi, BaseDpi);
var font = CreateFontW(
fontHeight,
0,
0,
0,
700,
0,
0,
0,
1, // DEFAULT_CHARSET
0, // OUT_DEFAULT_PRECIS
0, // CLIP_DEFAULT_PRECIS
5, // CLEARTYPE_QUALITY
0x20, // FF_SWISS
"Segoe UI");
var oldFont = SelectObject(hdc, font);
var textPtr = GetPinnedTextPointer();
if (textPtr is not null)
{
var textNint = (nint)textPtr;
_ = DrawTextW(hdc, textNint, -1, ref rect, DtCenter | DtVCenter | DtSingleLine);
}
_ = SelectObject(hdc, oldFont);
_ = DeleteObject(font);
_ = DeleteObject(bgBrush);
_ = EndPaint(hwnd, ref ps);
return nint.Zero;
}
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
private static unsafe char* GetPinnedTextPointer()
{
lock (Sync)
{
if (!currentPinnedTextHandle.HasValue || !currentPinnedTextHandle.Value.IsAllocated)
{
return null;
}
return (char*)currentPinnedTextHandle.Value.AddrOfPinnedObject();
}
}
private static bool TryGetWorkAreaFromFancyZonesCoordinates(int left, int top, int width, int height, out RECT workArea)
{
workArea = default;
if (width <= 0 || height <= 0)
{
return false;
}
var logicalRect = new RECT
{
Left = left,
Top = top,
Right = left + width,
Bottom = top + height,
};
var previousContext = SetThreadDpiAwarenessContext(DpiAwarenessContextUnaware);
nint monitor;
try
{
monitor = MonitorFromRect(ref logicalRect, MonitorDefaultToNearest);
}
finally
{
_ = SetThreadDpiAwarenessContext(previousContext);
}
if (monitor == nint.Zero)
{
return false;
}
var mi = new MONITORINFOEXW
{
CbSize = (uint)Marshal.SizeOf<MONITORINFOEXW>(),
};
if (!GetMonitorInfoW(monitor, ref mi))
{
return false;
}
workArea = mi.RcWork;
return true;
}
[StructLayout(LayoutKind.Sequential)]
private unsafe struct WNDCLASSEXW
{
public uint CbSize;
public uint Style;
public delegate* unmanaged[Stdcall]<nint, uint, nuint, nint, nint> LpfnWndProc;
public int CbClsExtra;
public int CbWndExtra;
public nint HInstance;
public nint HIcon;
public nint HCursor;
public nint HbrBackground;
public char* LpszMenuName;
public char* LpszClassName;
public nint HIconSm;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSG
{
public nint Hwnd;
public uint Message;
public nuint WParam;
public nint LParam;
public uint Time;
public POINT Pt;
public uint LPrivate;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct MONITORINFOEXW
{
public uint CbSize;
public RECT RcMonitor;
public RECT RcWork;
public uint DwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string SzDevice;
}
[StructLayout(LayoutKind.Sequential)]
private unsafe struct PAINTSTRUCT
{
public nint Hdc;
public int FErase;
public RECT RcPaint;
public int FRestore;
public int FIncUpdate;
public fixed byte RgbReserved[32];
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern nint GetModuleHandleW(string? lpModuleName);
[DllImport("kernel32.dll")]
private static extern int MulDiv(int nNumber, int nNumerator, int nDenominator);
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(nint hwnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UpdateWindow(nint hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyWindow(nint hWnd);
[DllImport("user32.dll")]
private static extern void PostQuitMessage(int nExitCode);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool KillTimer(nint hWnd, nuint uIDEvent);
[DllImport("user32.dll", SetLastError = true)]
private static extern nuint SetTimer(nint hWnd, nuint nIDEvent, uint uElapse, nint lpTimerFunc);
[DllImport("user32.dll")]
private static extern int GetMessageW(out MSG lpMsg, nint hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
private static extern bool TranslateMessage(in MSG lpMsg);
[DllImport("user32.dll")]
private static extern nint DispatchMessageW(in MSG lpMsg);
[DllImport("user32.dll")]
private static extern nint SetThreadDpiAwarenessContext(nint dpiContext);
[DllImport("user32.dll")]
private static extern nint MonitorFromRect(ref RECT lprc, uint dwFlags);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern bool GetMonitorInfoW(nint hMonitor, ref MONITORINFOEXW lpmi);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern ushort RegisterClassExW(in WNDCLASSEXW lpwcx);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern nint CreateWindowExW(
uint dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
nint hWndParent,
nint hMenu,
nint hInstance,
nint lpParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern nint LoadCursorW(nint hInstance, nint lpCursorName);
[DllImport("user32.dll")]
private static extern nint DefWindowProcW(nint hWnd, uint msg, nuint wParam, nint lParam);
[DllImport("user32.dll")]
private static extern nint BeginPaint(nint hWnd, out PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
private static extern bool EndPaint(nint hWnd, ref PAINTSTRUCT lpPaint);
[DllImport("user32.dll")]
private static extern bool GetClientRect(nint hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern int FillRect(nint hDC, in RECT lprc, nint hbr);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int DrawTextW(nint hdc, nint lpchText, int cchText, ref RECT lprc, uint format);
[DllImport("gdi32.dll")]
private static extern nint CreateSolidBrush(uint colorRef);
[DllImport("gdi32.dll")]
private static extern int SetBkMode(nint hdc, int mode);
[DllImport("gdi32.dll")]
private static extern uint SetTextColor(nint hdc, uint colorRef);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
private static extern nint CreateFontW(
int nHeight,
int nWidth,
int nEscapement,
int nOrientation,
int fnWeight,
uint fdwItalic,
uint fdwUnderline,
uint fdwStrikeOut,
uint fdwCharSet,
uint fdwOutputPrecision,
uint fdwClipPrecision,
uint fdwQuality,
uint fdwPitchAndFamily,
string lpszFace);
[DllImport("gdi32.dll")]
private static extern nint SelectObject(nint hdc, nint hgdiobj);
[DllImport("gdi32.dll")]
private static extern bool DeleteObject(nint hObject);
}

View File

@@ -0,0 +1,399 @@
// 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.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using FancyZonesEditorCommon.Data;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using InteropConstants = PowerToys.Interop.Constants;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesMonitorPreviewRenderer
{
public static IconInfo? TryRenderMonitorHeroImage(FancyZonesMonitorDescriptor monitor)
{
try
{
var cached = TryGetCachedIcon(monitor);
if (cached is not null)
{
return cached;
}
var icon = RenderMonitorHeroImageAsync(monitor).GetAwaiter().GetResult();
return icon;
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones monitor hero render failed. Monitor={monitor.Data.Monitor} Index={monitor.Index} Exception={ex}");
return null;
}
}
private static IconInfo? TryGetCachedIcon(FancyZonesMonitorDescriptor monitor)
{
var cachePath = GetCachePath(monitor);
if (string.IsNullOrEmpty(cachePath))
{
return null;
}
try
{
if (File.Exists(cachePath))
{
return new IconInfo(cachePath);
}
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones monitor hero cache check failed. Path=\"{cachePath}\" Monitor={monitor.Data.Monitor} Index={monitor.Index} Exception={ex}");
}
return null;
}
private static async Task<IconInfo?> RenderMonitorHeroImageAsync(FancyZonesMonitorDescriptor monitor)
{
var cachePath = GetCachePath(monitor);
if (string.IsNullOrEmpty(cachePath))
{
return null;
}
var (widthPx, heightPx) = ComputeCanvasSize(monitor.Data);
Logger.LogDebug($"FancyZones monitor hero render starting. Monitor={monitor.Data.Monitor} Index={monitor.Index} Size={widthPx}x{heightPx}");
var (layoutRectangles, spacing) = GetLayoutRectangles(monitor.Data);
var pixelBytes = RenderMonitorPreviewBgra(widthPx, heightPx, layoutRectangles, spacing);
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)widthPx,
(uint)heightPx,
96,
96,
pixelBytes);
await encoder.FlushAsync();
stream.Seek(0);
try
{
var tempPath = FormattableString.Invariant($"{cachePath}.{Guid.NewGuid():N}.tmp");
Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!);
await WriteStreamToFileAsync(stream, tempPath);
File.Move(tempPath, cachePath, overwrite: true);
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones monitor hero cache write failed. Path=\"{cachePath}\" Monitor={monitor.Data.Monitor} Index={monitor.Index} Exception={ex}");
return null;
}
Logger.LogDebug($"FancyZones monitor hero render succeeded. Monitor={monitor.Data.Monitor} Index={monitor.Index} Path=\"{cachePath}\"");
return new IconInfo(cachePath);
}
private static (int WidthPx, int HeightPx) ComputeCanvasSize(EditorParameters.NativeMonitorDataWrapper monitor)
{
const int maxDim = 320;
var w = monitor.WorkAreaWidth > 0 ? monitor.WorkAreaWidth : monitor.MonitorWidth;
var h = monitor.WorkAreaHeight > 0 ? monitor.WorkAreaHeight : monitor.MonitorHeight;
if (w <= 0 || h <= 0)
{
return (maxDim, 180);
}
var aspect = (float)w / h;
if (aspect >= 1)
{
var height = (int)Math.Clamp(Math.Round(maxDim / aspect), 90, maxDim);
return (maxDim, height);
}
else
{
var width = (int)Math.Clamp(Math.Round(maxDim * aspect), 90, maxDim);
return (width, maxDim);
}
}
private static (List<FancyZonesThumbnailRenderer.NormalizedRect> Rects, int Spacing) GetLayoutRectangles(EditorParameters.NativeMonitorDataWrapper monitor)
{
if (!FancyZonesDataService.TryGetAppliedLayoutForMonitor(monitor, out var applied) || applied is null)
{
return ([], 0);
}
var layout = FindLayoutDescriptor(applied.Value);
if (layout is null)
{
return ([], 0);
}
var rects = FancyZonesThumbnailRenderer.GetNormalizedRectsForLayout(layout);
var spacing = layout.ApplyLayout.ShowSpacing && layout.ApplyLayout.Spacing > 0 ? layout.ApplyLayout.Spacing : 0;
return (rects, spacing);
}
private static FancyZonesLayoutDescriptor? FindLayoutDescriptor(AppliedLayouts.AppliedLayoutWrapper.LayoutWrapper applied)
{
try
{
var layouts = FancyZonesDataService.GetLayouts();
if (!string.IsNullOrWhiteSpace(applied.Uuid) &&
!applied.Uuid.Equals("{00000000-0000-0000-0000-000000000000}", StringComparison.OrdinalIgnoreCase))
{
return layouts.FirstOrDefault(l => l.Source == FancyZonesLayoutSource.Custom &&
l.Custom is not null &&
string.Equals(l.Custom.Value.Uuid?.Trim(), applied.Uuid.Trim(), StringComparison.OrdinalIgnoreCase));
}
var type = applied.Type?.Trim().ToLowerInvariant() ?? string.Empty;
var zoneCount = applied.ZoneCount;
return layouts.FirstOrDefault(l =>
l.Source == FancyZonesLayoutSource.Template &&
string.Equals(l.ApplyLayout.Type, type, StringComparison.OrdinalIgnoreCase) &&
l.ApplyLayout.ZoneCount == zoneCount &&
l.ApplyLayout.ShowSpacing == applied.ShowSpacing &&
l.ApplyLayout.Spacing == applied.Spacing);
}
catch
{
return null;
}
}
private static string? GetCachePath(FancyZonesMonitorDescriptor monitor)
{
try
{
var basePath = InteropConstants.AppDataPath();
if (string.IsNullOrWhiteSpace(basePath))
{
return null;
}
var cacheFolder = Path.Combine(basePath, "CmdPal", "PowerToysExtension", "Cache", "FancyZones", "MonitorPreviews");
var fileName = ComputeMonitorHash(monitor) + ".png";
return Path.Combine(cacheFolder, fileName);
}
catch
{
return null;
}
}
private static string ComputeMonitorHash(FancyZonesMonitorDescriptor monitor)
{
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
var appliedFingerprint = string.Empty;
if (FancyZonesDataService.TryGetAppliedLayoutForMonitor(monitor.Data, out var applied) && applied is not null)
{
appliedFingerprint = FormattableString.Invariant($"{applied.Value.Type}|{applied.Value.Uuid}|{applied.Value.ZoneCount}|{applied.Value.ShowSpacing}|{applied.Value.Spacing}");
}
var identity = FormattableString.Invariant(
$"{monitor.Data.Monitor}|{monitor.Data.MonitorInstanceId}|{monitor.Data.MonitorSerialNumber}|{monitor.Data.MonitorNumber}|{currentVirtualDesktop}|{monitor.Data.WorkAreaWidth}x{monitor.Data.WorkAreaHeight}|{monitor.Data.MonitorWidth}x{monitor.Data.MonitorHeight}|{appliedFingerprint}");
var bytes = Encoding.UTF8.GetBytes(identity);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static byte[] RenderMonitorPreviewBgra(
int widthPx,
int heightPx,
IReadOnlyList<FancyZonesThumbnailRenderer.NormalizedRect> rects,
int spacing)
{
var pixels = new byte[widthPx * heightPx * 4];
var frame = Premultiply(new BgraColor(0x80, 0x80, 0x80, 0xFF));
var bezelFill = Premultiply(new BgraColor(0x20, 0x20, 0x20, 0x18));
var screenFill = Premultiply(new BgraColor(0x00, 0x00, 0x00, 0x00));
var border = Premultiply(new BgraColor(0xFF, 0xD8, 0x8C, 0xFF));
var fill = Premultiply(new BgraColor(0xFF, 0xD8, 0x8C, 0xC0));
var background = Premultiply(new BgraColor(0x00, 0x00, 0x00, 0x00));
for (var i = 0; i < pixels.Length; i += 4)
{
pixels[i + 0] = background.B;
pixels[i + 1] = background.G;
pixels[i + 2] = background.R;
pixels[i + 3] = background.A;
}
DrawRectBorder(pixels, widthPx, heightPx, 0, 0, widthPx, heightPx, frame);
const int bezel = 3;
FillRect(pixels, widthPx, heightPx, 1, 1, widthPx - 1, heightPx - 1, bezelFill);
FillRect(pixels, widthPx, heightPx, 1 + bezel, 1 + bezel, widthPx - 1 - bezel, heightPx - 1 - bezel, screenFill);
var innerLeft = 1 + bezel;
var innerTop = 1 + bezel;
var innerRight = widthPx - 1 - bezel;
var innerBottom = heightPx - 1 - bezel;
var innerWidth = Math.Max(1, innerRight - innerLeft);
var innerHeight = Math.Max(1, innerBottom - innerTop);
var gapPx = spacing > 0 ? Math.Clamp(spacing / 8, 1, 3) : 0;
foreach (var rect in rects)
{
var (x1, y1, x2, y2) = ToPixelBounds(rect, innerLeft, innerTop, innerWidth, innerHeight, gapPx);
if (x2 <= x1 || y2 <= y1)
{
continue;
}
FillRect(pixels, widthPx, heightPx, x1, y1, x2, y2, fill);
DrawRectBorder(pixels, widthPx, heightPx, x1, y1, x2, y2, border);
}
return pixels;
}
private static (int X1, int Y1, int X2, int Y2) ToPixelBounds(
FancyZonesThumbnailRenderer.NormalizedRect rect,
int originX,
int originY,
int widthPx,
int heightPx,
int gapPx)
{
var x1 = originX + (int)MathF.Round(rect.X * widthPx);
var y1 = originY + (int)MathF.Round(rect.Y * heightPx);
var x2 = originX + (int)MathF.Round((rect.X + rect.Width) * widthPx);
var y2 = originY + (int)MathF.Round((rect.Y + rect.Height) * heightPx);
x1 = Math.Clamp(x1 + gapPx, originX, originX + widthPx - 1);
y1 = Math.Clamp(y1 + gapPx, originY, originY + heightPx - 1);
x2 = Math.Clamp(x2 - gapPx, originX + 1, originX + widthPx);
y2 = Math.Clamp(y2 - gapPx, originY + 1, originY + heightPx);
if (x2 <= x1 + 1)
{
x2 = Math.Min(originX + widthPx, x1 + 2);
}
if (y2 <= y1 + 1)
{
y2 = Math.Min(originY + heightPx, y1 + 2);
}
return (x1, y1, x2, y2);
}
private static void FillRect(byte[] pixels, int widthPx, int heightPx, int x1, int y1, int x2, int y2, BgraColor color)
{
for (var y = y1; y < y2; y++)
{
if ((uint)y >= (uint)heightPx)
{
continue;
}
var rowStart = y * widthPx * 4;
for (var x = x1; x < x2; x++)
{
if ((uint)x >= (uint)widthPx)
{
continue;
}
var i = rowStart + (x * 4);
pixels[i + 0] = color.B;
pixels[i + 1] = color.G;
pixels[i + 2] = color.R;
pixels[i + 3] = color.A;
}
}
}
private static void DrawRectBorder(byte[] pixels, int widthPx, int heightPx, int x1, int y1, int x2, int y2, BgraColor color)
{
var left = x1;
var right = x2 - 1;
var top = y1;
var bottom = y2 - 1;
for (var x = left; x <= right; x++)
{
SetPixel(pixels, widthPx, heightPx, x, top, color);
SetPixel(pixels, widthPx, heightPx, x, bottom, color);
}
for (var y = top; y <= bottom; y++)
{
SetPixel(pixels, widthPx, heightPx, left, y, color);
SetPixel(pixels, widthPx, heightPx, right, y, color);
}
}
private static void SetPixel(byte[] pixels, int widthPx, int heightPx, int x, int y, BgraColor color)
{
if ((uint)x >= (uint)widthPx || (uint)y >= (uint)heightPx)
{
return;
}
var i = ((y * widthPx) + x) * 4;
pixels[i + 0] = color.B;
pixels[i + 1] = color.G;
pixels[i + 2] = color.R;
pixels[i + 3] = color.A;
}
private static BgraColor Premultiply(BgraColor color)
{
if (color.A == 0 || color.A == 255)
{
return color;
}
byte Premul(byte c) => (byte)(((c * color.A) + 127) / 255);
return new BgraColor(Premul(color.B), Premul(color.G), Premul(color.R), color.A);
}
private readonly record struct BgraColor(byte B, byte G, byte R, byte A);
private static async Task WriteStreamToFileAsync(IRandomAccessStream stream, string filePath)
{
stream.Seek(0);
var size = stream.Size;
if (size == 0)
{
File.WriteAllBytes(filePath, Array.Empty<byte>());
return;
}
if (size > int.MaxValue)
{
throw new InvalidOperationException("Icon stream too large.");
}
using var input = stream.GetInputStreamAt(0);
using var reader = new DataReader(input);
await reader.LoadAsync((uint)size);
var bytes = new byte[(int)size];
reader.ReadBytes(bytes);
File.WriteAllBytes(filePath, bytes);
}
}

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 System;
using System.Runtime.InteropServices;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesNotifier
{
private const string AppliedLayoutsFileUpdateMessage = "{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}";
private static readonly uint WmPrivAppliedLayoutsFileUpdate = RegisterWindowMessageW(AppliedLayoutsFileUpdateMessage);
public static void NotifyAppliedLayoutsChanged()
{
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivAppliedLayoutsFileUpdate, UIntPtr.Zero, IntPtr.Zero);
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint RegisterWindowMessageW(string lpString);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool PostMessageW(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam);
}

View File

@@ -0,0 +1,716 @@
// 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.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using FancyZonesEditorCommon.Data;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using InteropConstants = PowerToys.Interop.Constants;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesThumbnailRenderer
{
internal readonly record struct NormalizedRect(float X, float Y, float Width, float Height);
private readonly record struct BgraColor(byte B, byte G, byte R, byte A);
public static async Task<IconInfo?> RenderLayoutIconAsync(FancyZonesLayoutDescriptor layout, int sizePx = 72)
{
try
{
Logger.LogDebug($"FancyZones thumbnail render starting. LayoutId={layout.Id} Type={layout.ApplyLayout.Type} ZoneCount={layout.ApplyLayout.ZoneCount} Source={layout.Source}");
if (sizePx < 16)
{
sizePx = 16;
}
var cachedIcon = TryGetCachedIcon(layout);
if (cachedIcon is not null)
{
Logger.LogDebug($"FancyZones thumbnail cache hit. LayoutId={layout.Id}");
return cachedIcon;
}
var rects = GetNormalizedRectsForLayout(layout);
Logger.LogDebug($"FancyZones thumbnail rects computed. LayoutId={layout.Id} RectCount={rects.Count}");
var pixelBytes = RenderBgra(rects, sizePx, layout.ApplyLayout.ShowSpacing && layout.ApplyLayout.Spacing > 0 ? layout.ApplyLayout.Spacing : 0);
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)sizePx,
(uint)sizePx,
96,
96,
pixelBytes);
await encoder.FlushAsync();
stream.Seek(0);
var cachePath = GetCachePath(layout);
if (!string.IsNullOrEmpty(cachePath))
{
try
{
var tempPath = FormattableString.Invariant($"{cachePath}.{Guid.NewGuid():N}.tmp");
Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!);
await WriteStreamToFileAsync(stream, tempPath);
File.Move(tempPath, cachePath, overwrite: true);
var fileIcon = new IconInfo(cachePath);
Logger.LogDebug($"FancyZones thumbnail render succeeded (file cache). LayoutId={layout.Id} Path=\"{cachePath}\"");
return fileIcon;
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail write cache failed. LayoutId={layout.Id} Path=\"{cachePath}\" Exception={ex}");
}
}
// Fallback: return an in-memory stream icon. This may not marshal reliably cross-proc,
// so prefer the file-cached path above.
stream.Seek(0);
var inMemoryIcon = IconInfo.FromStream(stream);
Logger.LogDebug($"FancyZones thumbnail render succeeded (in-memory). LayoutId={layout.Id}");
return inMemoryIcon;
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail render failed. LayoutId={layout.Id} Type={layout.ApplyLayout.Type} ZoneCount={layout.ApplyLayout.ZoneCount} Source={layout.Source} Exception={ex}");
return null;
}
}
private static IconInfo? TryGetCachedIcon(FancyZonesLayoutDescriptor layout)
{
var cachePath = GetCachePath(layout);
if (string.IsNullOrEmpty(cachePath))
{
return null;
}
try
{
if (File.Exists(cachePath))
{
return new IconInfo(cachePath);
}
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail cache check failed. LayoutId={layout.Id} Path=\"{cachePath}\" Exception={ex}");
}
return null;
}
/// <summary>
/// Removes cached thumbnail files that no longer correspond to any current layout.
/// Call this on startup or periodically to prevent unbounded cache growth.
/// </summary>
public static void PurgeOrphanedCache()
{
try
{
var cacheFolder = GetCacheFolder();
if (string.IsNullOrEmpty(cacheFolder) || !Directory.Exists(cacheFolder))
{
return;
}
// Get all current layouts and compute their expected cache file names
var layouts = FancyZonesDataService.GetLayouts();
var validHashes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var layout in layouts)
{
validHashes.Add(ComputeLayoutHash(layout) + ".png");
}
// Delete any .png files not in the valid set
var deletedCount = 0;
foreach (var filePath in Directory.EnumerateFiles(cacheFolder, "*.png"))
{
var fileName = Path.GetFileName(filePath);
if (!validHashes.Contains(fileName))
{
try
{
File.Delete(filePath);
deletedCount++;
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail cache purge: failed to delete \"{filePath}\". Exception={ex.Message}");
}
}
}
if (deletedCount > 0)
{
Logger.LogInfo($"FancyZones thumbnail cache purge: deleted {deletedCount} orphaned file(s).");
}
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail cache purge failed. Exception={ex}");
}
}
private static string? GetCacheFolder()
{
var basePath = InteropConstants.AppDataPath();
if (string.IsNullOrWhiteSpace(basePath))
{
return null;
}
return Path.Combine(basePath, "CmdPal", "PowerToysExtension", "Cache", "FancyZones", "LayoutThumbnails");
}
private static string? GetCachePath(FancyZonesLayoutDescriptor layout)
{
try
{
var cacheFolder = GetCacheFolder();
if (string.IsNullOrEmpty(cacheFolder))
{
return null;
}
var fileName = ComputeLayoutHash(layout) + ".png";
return Path.Combine(cacheFolder, fileName);
}
catch (Exception ex)
{
Logger.LogWarning($"FancyZones thumbnail cache path failed. LayoutId={layout.Id} Exception={ex}");
return null;
}
}
private static string ComputeLayoutHash(FancyZonesLayoutDescriptor layout)
{
var customType = layout.Custom?.Type?.Trim() ?? string.Empty;
var customInfo = layout.Custom is not null && layout.Custom.Value.Info.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null
? layout.Custom.Value.Info.GetRawText()
: string.Empty;
var fingerprint = FormattableString.Invariant(
$"{layout.Id}|{layout.Source}|{layout.ApplyLayout.Type}|{layout.ApplyLayout.ZoneCount}|{layout.ApplyLayout.ShowSpacing}|{layout.ApplyLayout.Spacing}|{customType}|{customInfo}");
var bytes = Encoding.UTF8.GetBytes(fingerprint);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static async Task WriteStreamToFileAsync(IRandomAccessStream stream, string filePath)
{
stream.Seek(0);
var size = stream.Size;
if (size == 0)
{
File.WriteAllBytes(filePath, Array.Empty<byte>());
return;
}
if (size > int.MaxValue)
{
throw new InvalidOperationException("Icon stream too large.");
}
using var input = stream.GetInputStreamAt(0);
using var reader = new DataReader(input);
await reader.LoadAsync((uint)size);
var bytes = new byte[(int)size];
reader.ReadBytes(bytes);
File.WriteAllBytes(filePath, bytes);
}
internal static List<NormalizedRect> GetNormalizedRectsForLayout(FancyZonesLayoutDescriptor layout)
{
var type = layout.ApplyLayout.Type.ToLowerInvariant();
if (layout.Source == FancyZonesLayoutSource.Custom && layout.Custom is not null)
{
return GetCustomRects(layout.Custom.Value);
}
return type switch
{
"columns" => GetColumnsRects(layout.ApplyLayout.ZoneCount),
"rows" => GetRowsRects(layout.ApplyLayout.ZoneCount),
"grid" => GetGridRects(layout.ApplyLayout.ZoneCount),
"priority-grid" => GetPriorityGridRects(layout.ApplyLayout.ZoneCount),
"focus" => GetFocusRects(layout.ApplyLayout.ZoneCount),
"blank" => new List<NormalizedRect>(),
_ => GetGridRects(layout.ApplyLayout.ZoneCount),
};
}
private static List<NormalizedRect> GetCustomRects(CustomLayouts.CustomLayoutWrapper custom)
{
var type = custom.Type?.Trim().ToLowerInvariant() ?? string.Empty;
if (custom.Info.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
{
return new List<NormalizedRect>();
}
return type switch
{
"grid" => GetCustomGridRects(custom.Info),
"canvas" => GetCustomCanvasRects(custom.Info),
_ => new List<NormalizedRect>(),
};
}
private static List<NormalizedRect> GetCustomCanvasRects(JsonElement info)
{
if (!info.TryGetProperty("ref-width", out var refWidthProp) ||
!info.TryGetProperty("ref-height", out var refHeightProp) ||
!info.TryGetProperty("zones", out var zonesProp))
{
return new List<NormalizedRect>();
}
if (refWidthProp.ValueKind != JsonValueKind.Number || refHeightProp.ValueKind != JsonValueKind.Number || zonesProp.ValueKind != JsonValueKind.Array)
{
return new List<NormalizedRect>();
}
var refWidth = Math.Max(1, refWidthProp.GetInt32());
var refHeight = Math.Max(1, refHeightProp.GetInt32());
var rects = new List<NormalizedRect>(zonesProp.GetArrayLength());
foreach (var zone in zonesProp.EnumerateArray())
{
if (zone.ValueKind != JsonValueKind.Object)
{
continue;
}
if (!zone.TryGetProperty("X", out var xProp) ||
!zone.TryGetProperty("Y", out var yProp) ||
!zone.TryGetProperty("width", out var wProp) ||
!zone.TryGetProperty("height", out var hProp))
{
continue;
}
if (xProp.ValueKind != JsonValueKind.Number ||
yProp.ValueKind != JsonValueKind.Number ||
wProp.ValueKind != JsonValueKind.Number ||
hProp.ValueKind != JsonValueKind.Number)
{
continue;
}
var x = xProp.GetSingle() / refWidth;
var y = yProp.GetSingle() / refHeight;
var w = wProp.GetSingle() / refWidth;
var h = hProp.GetSingle() / refHeight;
rects.Add(NormalizeRect(x, y, w, h));
}
return rects;
}
private static List<NormalizedRect> GetCustomGridRects(JsonElement info)
{
if (!TryGetGridDefinition(info, out var rows, out var cols, out var rowsPercents, out var colsPercents, out var cellMap))
{
return new List<NormalizedRect>();
}
return BuildRectsFromGridDefinition(rows, cols, rowsPercents, colsPercents, cellMap);
}
private static bool TryGetGridDefinition(
JsonElement info,
out int rows,
out int cols,
out int[] rowPercents,
out int[] colPercents,
out int[][] cellChildMap)
{
rows = 0;
cols = 0;
rowPercents = Array.Empty<int>();
colPercents = Array.Empty<int>();
cellChildMap = Array.Empty<int[]>();
if (!info.TryGetProperty("rows", out var rowsProp) ||
!info.TryGetProperty("columns", out var colsProp) ||
!info.TryGetProperty("rows-percentage", out var rowsPercentsProp) ||
!info.TryGetProperty("columns-percentage", out var colsPercentsProp) ||
!info.TryGetProperty("cell-child-map", out var cellMapProp))
{
return false;
}
if (rowsProp.ValueKind != JsonValueKind.Number ||
colsProp.ValueKind != JsonValueKind.Number ||
rowsPercentsProp.ValueKind != JsonValueKind.Array ||
colsPercentsProp.ValueKind != JsonValueKind.Array ||
cellMapProp.ValueKind != JsonValueKind.Array)
{
return false;
}
rows = rowsProp.GetInt32();
cols = colsProp.GetInt32();
if (rows <= 0 || cols <= 0)
{
return false;
}
rowPercents = rowsPercentsProp.EnumerateArray().Where(v => v.ValueKind == JsonValueKind.Number).Select(v => v.GetInt32()).ToArray();
colPercents = colsPercentsProp.EnumerateArray().Where(v => v.ValueKind == JsonValueKind.Number).Select(v => v.GetInt32()).ToArray();
if (rowPercents.Length != rows || colPercents.Length != cols)
{
return false;
}
var mapRows = new List<int[]>(rows);
foreach (var row in cellMapProp.EnumerateArray())
{
if (row.ValueKind != JsonValueKind.Array)
{
return false;
}
var cells = row.EnumerateArray().Where(v => v.ValueKind == JsonValueKind.Number).Select(v => v.GetInt32()).ToArray();
if (cells.Length != cols)
{
return false;
}
mapRows.Add(cells);
}
if (mapRows.Count != rows)
{
return false;
}
cellChildMap = mapRows.ToArray();
return true;
}
private static List<NormalizedRect> GetColumnsRects(int zoneCount)
{
zoneCount = Math.Clamp(zoneCount, 1, 16);
var rects = new List<NormalizedRect>(zoneCount);
for (var i = 0; i < zoneCount; i++)
{
rects.Add(new NormalizedRect(i / (float)zoneCount, 0, 1f / zoneCount, 1f));
}
return rects;
}
private static List<NormalizedRect> GetRowsRects(int zoneCount)
{
zoneCount = Math.Clamp(zoneCount, 1, 16);
var rects = new List<NormalizedRect>(zoneCount);
for (var i = 0; i < zoneCount; i++)
{
rects.Add(new NormalizedRect(0, i / (float)zoneCount, 1f, 1f / zoneCount));
}
return rects;
}
private static List<NormalizedRect> GetGridRects(int zoneCount)
{
zoneCount = Math.Clamp(zoneCount, 1, 25);
var rows = 1;
while (zoneCount / rows >= rows)
{
rows++;
}
rows--;
var cols = zoneCount / rows;
if (zoneCount % rows != 0)
{
cols++;
}
var rowPercents = Enumerable.Repeat(10000 / rows, rows).ToArray();
var colPercents = Enumerable.Repeat(10000 / cols, cols).ToArray();
var cellMap = new int[rows][];
var index = 0;
for (var r = 0; r < rows; r++)
{
cellMap[r] = new int[cols];
for (var c = 0; c < cols; c++)
{
cellMap[r][c] = index;
index++;
if (index == zoneCount)
{
index--;
}
}
}
return BuildRectsFromGridDefinition(rows, cols, rowPercents, colPercents, cellMap);
}
private static List<NormalizedRect> GetPriorityGridRects(int zoneCount)
{
zoneCount = Math.Clamp(zoneCount, 1, 25);
if (zoneCount is >= 1 and <= 11 && PriorityGrid.TryGetValue(zoneCount, out var def))
{
return BuildRectsFromGridDefinition(def.Rows, def.Cols, def.RowPercents, def.ColPercents, def.CellMap);
}
return GetGridRects(zoneCount);
}
private static List<NormalizedRect> GetFocusRects(int zoneCount)
{
zoneCount = Math.Clamp(zoneCount, 1, 8);
var rects = new List<NormalizedRect>(zoneCount);
for (var i = 0; i < zoneCount; i++)
{
var offset = i * 0.06f;
rects.Add(new NormalizedRect(0.1f + offset, 0.1f + offset, 0.8f, 0.8f));
}
return rects;
}
private static List<NormalizedRect> BuildRectsFromGridDefinition(int rows, int cols, int[] rowPercents, int[] colPercents, int[][] cellChildMap)
{
const float multiplier = 10000f;
var rowPrefix = new float[rows + 1];
var colPrefix = new float[cols + 1];
for (var r = 0; r < rows; r++)
{
rowPrefix[r + 1] = rowPrefix[r] + (rowPercents[r] / multiplier);
}
for (var c = 0; c < cols; c++)
{
colPrefix[c + 1] = colPrefix[c] + (colPercents[c] / multiplier);
}
var maxZone = -1;
for (var r = 0; r < rows; r++)
{
for (var c = 0; c < cols; c++)
{
maxZone = Math.Max(maxZone, cellChildMap[r][c]);
}
}
var rects = new List<NormalizedRect>(maxZone + 1);
for (var i = 0; i <= maxZone; i++)
{
rects.Add(new NormalizedRect(1, 1, 0, 0));
}
for (var r = 0; r < rows; r++)
{
for (var c = 0; c < cols; c++)
{
var zoneId = cellChildMap[r][c];
if (zoneId < 0 || zoneId >= rects.Count)
{
continue;
}
var x1 = colPrefix[c];
var y1 = rowPrefix[r];
var x2 = colPrefix[c + 1];
var y2 = rowPrefix[r + 1];
var existing = rects[zoneId];
if (existing.Width <= 0 || existing.Height <= 0)
{
rects[zoneId] = new NormalizedRect(x1, y1, x2 - x1, y2 - y1);
}
else
{
var ex2 = existing.X + existing.Width;
var ey2 = existing.Y + existing.Height;
var nx1 = Math.Min(existing.X, x1);
var ny1 = Math.Min(existing.Y, y1);
var nx2 = Math.Max(ex2, x2);
var ny2 = Math.Max(ey2, y2);
rects[zoneId] = new NormalizedRect(nx1, ny1, nx2 - nx1, ny2 - ny1);
}
}
}
return rects
.Where(r => r.Width > 0 && r.Height > 0)
.Select(r => NormalizeRect(r.X, r.Y, r.Width, r.Height))
.ToList();
}
private static NormalizedRect NormalizeRect(float x, float y, float w, float h)
{
x = Math.Clamp(x, 0, 1);
y = Math.Clamp(y, 0, 1);
w = Math.Clamp(w, 0, 1 - x);
h = Math.Clamp(h, 0, 1 - y);
return new NormalizedRect(x, y, w, h);
}
private static byte[] RenderBgra(IReadOnlyList<NormalizedRect> rects, int sizePx, int spacing)
{
var pixels = new byte[sizePx * sizePx * 4];
var border = Premultiply(new BgraColor(0x30, 0x30, 0x30, 0xFF));
var frame = Premultiply(new BgraColor(0x40, 0x40, 0x40, 0xA0));
var fill = Premultiply(new BgraColor(0xFF, 0xD8, 0x8C, 0xC0)); // light-ish blue with alpha
var background = Premultiply(new BgraColor(0x00, 0x00, 0x00, 0x00));
for (var i = 0; i < pixels.Length; i += 4)
{
pixels[i + 0] = background.B;
pixels[i + 1] = background.G;
pixels[i + 2] = background.R;
pixels[i + 3] = background.A;
}
DrawRectBorder(pixels, sizePx, 1, 1, sizePx - 1, sizePx - 1, frame);
var gapPx = spacing > 0 ? Math.Clamp(spacing / 8, 1, 3) : 0;
foreach (var rect in rects)
{
var (x1, y1, x2, y2) = ToPixelBounds(rect, sizePx, gapPx);
if (x2 <= x1 || y2 <= y1)
{
continue;
}
FillRect(pixels, sizePx, x1, y1, x2, y2, fill);
DrawRectBorder(pixels, sizePx, x1, y1, x2, y2, border);
}
return pixels;
}
private static (int X1, int Y1, int X2, int Y2) ToPixelBounds(NormalizedRect rect, int sizePx, int gapPx)
{
var x1 = (int)MathF.Round(rect.X * sizePx);
var y1 = (int)MathF.Round(rect.Y * sizePx);
var x2 = (int)MathF.Round((rect.X + rect.Width) * sizePx);
var y2 = (int)MathF.Round((rect.Y + rect.Height) * sizePx);
x1 = Math.Clamp(x1 + gapPx, 0, sizePx - 1);
y1 = Math.Clamp(y1 + gapPx, 0, sizePx - 1);
x2 = Math.Clamp(x2 - gapPx, 1, sizePx);
y2 = Math.Clamp(y2 - gapPx, 1, sizePx);
if (x2 <= x1 + 1)
{
x2 = Math.Min(sizePx, x1 + 2);
}
if (y2 <= y1 + 1)
{
y2 = Math.Min(sizePx, y1 + 2);
}
return (x1, y1, x2, y2);
}
private static void FillRect(byte[] pixels, int sizePx, int x1, int y1, int x2, int y2, BgraColor color)
{
for (var y = y1; y < y2; y++)
{
var rowStart = y * sizePx * 4;
for (var x = x1; x < x2; x++)
{
var i = rowStart + (x * 4);
pixels[i + 0] = color.B;
pixels[i + 1] = color.G;
pixels[i + 2] = color.R;
pixels[i + 3] = color.A;
}
}
}
private static void DrawRectBorder(byte[] pixels, int sizePx, int x1, int y1, int x2, int y2, BgraColor color)
{
var left = x1;
var right = x2 - 1;
var top = y1;
var bottom = y2 - 1;
for (var x = left; x <= right; x++)
{
SetPixel(pixels, sizePx, x, top, color);
SetPixel(pixels, sizePx, x, bottom, color);
}
for (var y = top; y <= bottom; y++)
{
SetPixel(pixels, sizePx, left, y, color);
SetPixel(pixels, sizePx, right, y, color);
}
}
private static void SetPixel(byte[] pixels, int sizePx, int x, int y, BgraColor color)
{
if ((uint)x >= (uint)sizePx || (uint)y >= (uint)sizePx)
{
return;
}
var i = ((y * sizePx) + x) * 4;
pixels[i + 0] = color.B;
pixels[i + 1] = color.G;
pixels[i + 2] = color.R;
pixels[i + 3] = color.A;
}
private static BgraColor Premultiply(BgraColor color)
{
if (color.A == 0 || color.A == 255)
{
return color;
}
byte Premul(byte c) => (byte)(((c * color.A) + 127) / 255);
return new BgraColor(Premul(color.B), Premul(color.G), Premul(color.R), color.A);
}
private sealed record PriorityGridDefinition(int Rows, int Cols, int[] RowPercents, int[] ColPercents, int[][] CellMap);
private static readonly IReadOnlyDictionary<int, PriorityGridDefinition> PriorityGrid = new Dictionary<int, PriorityGridDefinition>
{
[1] = new PriorityGridDefinition(1, 1, [10000], [10000], [[0]]),
[2] = new PriorityGridDefinition(1, 2, [10000], [6667, 3333], [[0, 1]]),
[3] = new PriorityGridDefinition(1, 3, [10000], [2500, 5000, 2500], [[0, 1, 2]]),
[4] = new PriorityGridDefinition(2, 3, [5000, 5000], [2500, 5000, 2500], [[0, 1, 2], [0, 1, 3]]),
[5] = new PriorityGridDefinition(2, 3, [5000, 5000], [2500, 5000, 2500], [[0, 1, 2], [3, 1, 4]]),
[6] = new PriorityGridDefinition(3, 3, [3333, 3334, 3333], [2500, 5000, 2500], [[0, 1, 2], [0, 1, 3], [4, 1, 5]]),
[7] = new PriorityGridDefinition(3, 3, [3333, 3334, 3333], [2500, 5000, 2500], [[0, 1, 2], [3, 1, 4], [5, 1, 6]]),
[8] = new PriorityGridDefinition(3, 4, [3333, 3334, 3333], [2500, 2500, 2500, 2500], [[0, 1, 2, 3], [4, 1, 2, 5], [6, 1, 2, 7]]),
[9] = new PriorityGridDefinition(3, 4, [3333, 3334, 3333], [2500, 2500, 2500, 2500], [[0, 1, 2, 3], [4, 1, 2, 5], [6, 1, 7, 8]]),
[10] = new PriorityGridDefinition(3, 4, [3333, 3334, 3333], [2500, 2500, 2500, 2500], [[0, 1, 2, 3], [4, 1, 5, 6], [7, 1, 8, 9]]),
[11] = new PriorityGridDefinition(3, 4, [3333, 3334, 3333], [2500, 2500, 2500, 2500], [[0, 1, 2, 3], [4, 1, 5, 6], [7, 8, 9, 10]]),
};
}

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.Runtime.InteropServices;
using Microsoft.Win32;
namespace PowerToysExtension.Helpers;
internal static class FancyZonesVirtualDesktop
{
private const string VirtualDesktopsKey = @"Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops";
private const string SessionVirtualDesktopsKeyPrefix = @"Software\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo\";
private const string SessionVirtualDesktopsKeySuffix = @"\VirtualDesktops";
private const string CurrentVirtualDesktopValue = "CurrentVirtualDesktop";
private const string VirtualDesktopIdsValue = "VirtualDesktopIDs";
public static string GetCurrentVirtualDesktopIdString()
{
var id = TryGetCurrentVirtualDesktopId()
?? TryGetCurrentVirtualDesktopIdFromSession()
?? TryGetFirstVirtualDesktopId()
?? Guid.Empty;
return "{" + id.ToString().ToUpperInvariant() + "}";
}
private static Guid? TryGetCurrentVirtualDesktopId()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(VirtualDesktopsKey, writable: false);
var bytes = key?.GetValue(CurrentVirtualDesktopValue) as byte[];
return TryGetGuid(bytes);
}
catch
{
return null;
}
}
private static Guid? TryGetCurrentVirtualDesktopIdFromSession()
{
try
{
if (!ProcessIdToSessionId((uint)Environment.ProcessId, out var sessionId))
{
return null;
}
var path = SessionVirtualDesktopsKeyPrefix + sessionId + SessionVirtualDesktopsKeySuffix;
using var key = Registry.CurrentUser.OpenSubKey(path, writable: false);
var bytes = key?.GetValue(CurrentVirtualDesktopValue) as byte[];
return TryGetGuid(bytes);
}
catch
{
return null;
}
}
private static Guid? TryGetFirstVirtualDesktopId()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(VirtualDesktopsKey, writable: false);
var bytes = key?.GetValue(VirtualDesktopIdsValue) as byte[];
if (bytes is null || bytes.Length < 16)
{
return null;
}
var first = new byte[16];
Array.Copy(bytes, 0, first, 0, 16);
return TryGetGuid(first);
}
catch
{
return null;
}
}
private static Guid? TryGetGuid(byte[]? bytes)
{
try
{
if (bytes is null || bytes.Length < 16)
{
return null;
}
return new Guid(bytes.AsSpan(0, 16));
}
catch
{
return null;
}
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId);
}

View File

@@ -0,0 +1,86 @@
// 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 Microsoft.Win32;
namespace PowerToysExtension.Helpers;
internal enum GpoRuleConfiguredValue
{
WrongValue = -3,
Unavailable = -2,
NotConfigured = -1,
Disabled = 0,
Enabled = 1,
}
/// <summary>
/// Lightweight GPO reader for module/feature enablement policies.
/// Mirrors the logic in src/common/utils/gpo.h but avoids taking a dependency on the full GPOWrapper.
/// </summary>
internal static class GpoEnablementService
{
private const string PoliciesPath = @"SOFTWARE\Policies\PowerToys";
private const string PolicyConfigureEnabledGlobalAllUtilities = "ConfigureGlobalUtilityEnabledState";
internal static GpoRuleConfiguredValue GetUtilityEnabledValue(string individualPolicyValueName)
{
if (!string.IsNullOrEmpty(individualPolicyValueName))
{
var individual = GetConfiguredValue(individualPolicyValueName);
if (individual is GpoRuleConfiguredValue.Disabled or GpoRuleConfiguredValue.Enabled)
{
return individual;
}
}
return GetConfiguredValue(PolicyConfigureEnabledGlobalAllUtilities);
}
private static GpoRuleConfiguredValue GetConfiguredValue(string registryValueName)
{
try
{
// Machine scope has priority over user scope.
var value = ReadRegistryValue(Registry.LocalMachine, registryValueName);
value ??= ReadRegistryValue(Registry.CurrentUser, registryValueName);
if (!value.HasValue)
{
return GpoRuleConfiguredValue.NotConfigured;
}
return value.Value switch
{
0 => GpoRuleConfiguredValue.Disabled,
1 => GpoRuleConfiguredValue.Enabled,
_ => GpoRuleConfiguredValue.WrongValue,
};
}
catch
{
return GpoRuleConfiguredValue.Unavailable;
}
}
private static int? ReadRegistryValue(RegistryKey rootKey, string valueName)
{
try
{
using var key = rootKey.OpenSubKey(PoliciesPath, writable: false);
if (key is null)
{
return null;
}
var value = key.GetValue(valueName);
return value as int?;
}
catch
{
return null;
}
}
}

View File

@@ -0,0 +1,54 @@
// 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.Modules;
namespace PowerToysExtension.Helpers;
/// <summary>
/// Aggregates commands exposed by individual module providers and applies fuzzy filtering.
/// </summary>
internal static class ModuleCommandCatalog
{
private static readonly ModuleCommandProvider[] Providers =
[
new AwakeModuleCommandProvider(),
new AdvancedPasteModuleCommandProvider(),
new WorkspacesModuleCommandProvider(),
new LightSwitchModuleCommandProvider(),
new PowerToysRunModuleCommandProvider(),
new ScreenRulerModuleCommandProvider(),
new ShortcutGuideModuleCommandProvider(),
new TextExtractorModuleCommandProvider(),
new ZoomItModuleCommandProvider(),
new ColorPickerModuleCommandProvider(),
new AlwaysOnTopModuleCommandProvider(),
new CropAndLockModuleCommandProvider(),
new FancyZonesModuleCommandProvider(),
new KeyboardManagerModuleCommandProvider(),
new MouseUtilsModuleCommandProvider(),
new MouseWithoutBordersModuleCommandProvider(),
new QuickAccentModuleCommandProvider(),
new FileExplorerAddonsModuleCommandProvider(),
new FileLocksmithModuleCommandProvider(),
new ImageResizerModuleCommandProvider(),
new NewPlusModuleCommandProvider(),
new PeekModuleCommandProvider(),
new PowerRenameModuleCommandProvider(),
new CommandNotFoundModuleCommandProvider(),
new EnvironmentVariablesModuleCommandProvider(),
new HostsModuleCommandProvider(),
new RegistryPreviewModuleCommandProvider(),
];
public static IListItem[] GetAllItems()
{
return [.. Providers.SelectMany(provider => provider.BuildCommands())];
}
}

View File

@@ -0,0 +1,162 @@
// 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.IO;
using System.Text.Json;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Helpers;
/// <summary>
/// Reads PowerToys module enablement flags from the global settings.json.
/// </summary>
internal static class ModuleEnablementService
{
internal static string SettingsFilePath { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
"settings.json");
internal static bool IsModuleEnabled(SettingsWindow module)
{
var key = GetEnabledKey(module);
if (string.IsNullOrEmpty(key))
{
var globalRule = GpoEnablementService.GetUtilityEnabledValue(string.Empty);
return globalRule != GpoRuleConfiguredValue.Disabled;
}
return IsKeyEnabled(key);
}
internal static bool IsKeyEnabled(string enabledKey)
{
if (string.IsNullOrWhiteSpace(enabledKey))
{
return true;
}
var gpoPolicy = GetGpoPolicyForEnabledKey(enabledKey);
var gpoRule = GpoEnablementService.GetUtilityEnabledValue(gpoPolicy);
if (gpoRule == GpoRuleConfiguredValue.Disabled)
{
return false;
}
if (gpoRule == GpoRuleConfiguredValue.Enabled)
{
return true;
}
try
{
var enabled = ReadEnabledFlags();
return enabled is null || !enabled.TryGetValue(enabledKey, out var value) || value;
}
catch
{
return true;
}
}
private static Dictionary<string, bool>? ReadEnabledFlags()
{
if (!File.Exists(SettingsFilePath))
{
return null;
}
var json = File.ReadAllText(SettingsFilePath).Trim('\0');
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("enabled", out var enabledRoot) ||
enabledRoot.ValueKind != JsonValueKind.Object)
{
return null;
}
var result = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var prop in enabledRoot.EnumerateObject())
{
if (prop.Value.ValueKind is JsonValueKind.True or JsonValueKind.False)
{
result[prop.Name] = prop.Value.GetBoolean();
}
}
return result;
}
private static string GetEnabledKey(SettingsWindow module) => module switch
{
SettingsWindow.Awake => "Awake",
SettingsWindow.AdvancedPaste => "AdvancedPaste",
SettingsWindow.AlwaysOnTop => "AlwaysOnTop",
SettingsWindow.ColorPicker => "ColorPicker",
SettingsWindow.CropAndLock => "CropAndLock",
SettingsWindow.EnvironmentVariables => "EnvironmentVariables",
SettingsWindow.FancyZones => "FancyZones",
SettingsWindow.FileExplorer => "File Explorer Preview",
SettingsWindow.FileLocksmith => "FileLocksmith",
SettingsWindow.Hosts => "Hosts",
SettingsWindow.ImageResizer => "Image Resizer",
SettingsWindow.KBM => "Keyboard Manager",
SettingsWindow.LightSwitch => "LightSwitch",
SettingsWindow.MeasureTool => "Measure Tool",
SettingsWindow.MouseWithoutBorders => "MouseWithoutBorders",
SettingsWindow.NewPlus => "NewPlus",
SettingsWindow.Peek => "Peek",
SettingsWindow.PowerAccent => "QuickAccent",
SettingsWindow.PowerLauncher => "PowerToys Run",
SettingsWindow.Run => "PowerToys Run",
SettingsWindow.PowerRename => "PowerRename",
SettingsWindow.PowerOCR => "TextExtractor",
SettingsWindow.RegistryPreview => "RegistryPreview",
SettingsWindow.ShortcutGuide => "Shortcut Guide",
SettingsWindow.Workspaces => "Workspaces",
SettingsWindow.ZoomIt => "ZoomIt",
SettingsWindow.CmdNotFound => "CmdNotFound",
SettingsWindow.CmdPal => "CmdPal",
_ => string.Empty,
};
private static string GetGpoPolicyForEnabledKey(string enabledKey) => enabledKey switch
{
"AdvancedPaste" => "ConfigureEnabledUtilityAdvancedPaste",
"AlwaysOnTop" => "ConfigureEnabledUtilityAlwaysOnTop",
"Awake" => "ConfigureEnabledUtilityAwake",
"CmdNotFound" => "ConfigureEnabledUtilityCmdNotFound",
"CmdPal" => "ConfigureEnabledUtilityCmdPal",
"ColorPicker" => "ConfigureEnabledUtilityColorPicker",
"CropAndLock" => "ConfigureEnabledUtilityCropAndLock",
"CursorWrap" => "ConfigureEnabledUtilityCursorWrap",
"EnvironmentVariables" => "ConfigureEnabledUtilityEnvironmentVariables",
"FancyZones" => "ConfigureEnabledUtilityFancyZones",
"FileLocksmith" => "ConfigureEnabledUtilityFileLocksmith",
"FindMyMouse" => "ConfigureEnabledUtilityFindMyMouse",
"Hosts" => "ConfigureEnabledUtilityHostsFileEditor",
"Image Resizer" => "ConfigureEnabledUtilityImageResizer",
"Keyboard Manager" => "ConfigureEnabledUtilityKeyboardManager",
"LightSwitch" => "ConfigureEnabledUtilityLightSwitch",
"Measure Tool" => "ConfigureEnabledUtilityScreenRuler",
"MouseHighlighter" => "ConfigureEnabledUtilityMouseHighlighter",
"MouseJump" => "ConfigureEnabledUtilityMouseJump",
"MousePointerCrosshairs" => "ConfigureEnabledUtilityMousePointerCrosshairs",
"MouseWithoutBorders" => "ConfigureEnabledUtilityMouseWithoutBorders",
"NewPlus" => "ConfigureEnabledUtilityNewPlus",
"Peek" => "ConfigureEnabledUtilityPeek",
"PowerRename" => "ConfigureEnabledUtilityPowerRename",
"PowerToys Run" => "ConfigureEnabledUtilityPowerLauncher",
"QuickAccent" => "ConfigureEnabledUtilityQuickAccent",
"RegistryPreview" => "ConfigureEnabledUtilityRegistryPreview",
"Shortcut Guide" => "ConfigureEnabledUtilityShortcutGuide",
"TextExtractor" => "ConfigureEnabledUtilityTextExtractor",
"Workspaces" => "ConfigureEnabledUtilityWorkspaces",
"ZoomIt" => "ConfigureEnabledUtilityZoomIt",
_ => string.Empty,
};
}

View File

@@ -0,0 +1,88 @@
// 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 Common.Search.FuzzSearch;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Helpers;
/// <summary>
/// A fallback item that filters itself based on the landing-page query.
/// It hides itself (empty Title) when the query doesn't fuzzy-match the title or subtitle.
/// </summary>
internal sealed partial class PowerToysFallbackCommandItem : FallbackCommandItem, IFallbackHandler
{
private readonly string _baseTitle;
private readonly string _baseSubtitle;
private readonly string _baseName;
private readonly Command? _mutableCommand;
public PowerToysFallbackCommandItem(ICommand command, string title, string subtitle, IIconInfo? icon, IContextItem[]? moreCommands)
: base(command, title)
{
_baseTitle = title ?? string.Empty;
_baseSubtitle = subtitle ?? string.Empty;
_baseName = command?.Name ?? string.Empty;
_mutableCommand = command as Command;
// Start hidden; we only surface when the query matches
Title = string.Empty;
Subtitle = string.Empty;
if (_mutableCommand is not null)
{
_mutableCommand.Name = string.Empty;
}
if (icon != null)
{
Icon = icon;
}
MoreCommands = moreCommands ?? Array.Empty<IContextItem>();
// Ensure fallback updates route to this instance
FallbackHandler = this;
}
public override void UpdateQuery(string query)
{
if (string.IsNullOrWhiteSpace(query))
{
Title = string.Empty;
Subtitle = string.Empty;
if (_mutableCommand is not null)
{
_mutableCommand.Name = string.Empty;
}
return;
}
// Simple fuzzy match against title/subtitle; hide if neither match
var titleMatch = Common.Search.FuzzSearch.StringMatcher.FuzzyMatch(query, _baseTitle);
var subtitleMatch = Common.Search.FuzzSearch.StringMatcher.FuzzyMatch(query, _baseSubtitle);
var matches = (titleMatch.Success && titleMatch.Score > 0) || (subtitleMatch.Success && subtitleMatch.Score > 0);
if (matches)
{
Title = _baseTitle;
Subtitle = _baseSubtitle;
if (_mutableCommand is not null)
{
_mutableCommand.Name = _baseName;
}
}
else
{
Title = string.Empty;
Subtitle = string.Empty;
if (_mutableCommand is not null)
{
_mutableCommand.Name = string.Empty;
}
}
}
}

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 string.Empty;
}
var baseDirectory = GetPowerToysInstallPath();
if (string.IsNullOrEmpty(baseDirectory))
{
return string.Empty;
}
var candidate = Path.Combine(baseDirectory, executableName);
return File.Exists(candidate) ? candidate : string.Empty;
}
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 string.Empty;
}
private static string GetPathFromProtocolRegistration(RegistryKey baseKey)
{
try
{
using var commandKey = baseKey.OpenSubKey($@"{PowerToysProtocolKey}\shell\open\command");
if (commandKey == null)
{
return string.Empty;
}
var command = commandKey.GetValue(string.Empty)?.ToString() ?? string.Empty;
if (string.IsNullOrEmpty(command))
{
return string.Empty;
}
return ExtractInstallDirectory(command);
}
catch
{
return string.Empty;
}
}
private static string GetPathFromUserRegistration(RegistryKey baseKey)
{
try
{
using var userKey = baseKey.OpenSubKey(PowerToysUserKey);
if (userKey == null)
{
return string.Empty;
}
var installedValue = userKey.GetValue("installed");
if (installedValue != null && installedValue.ToString() == "1")
{
return GetPathFromProtocolRegistration(baseKey);
}
}
catch
{
// Ignore registry access failures.
}
return string.Empty;
}
private static string ExtractInstallDirectory(string command)
{
if (string.IsNullOrEmpty(command))
{
return string.Empty;
}
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) ?? string.Empty;
}
}
}
else
{
var parts = command.Split(' ');
if (parts.Length > 0 && File.Exists(parts[0]))
{
return Path.GetDirectoryName(parts[0]) ?? string.Empty;
}
}
}
catch
{
// Fall through and report no path.
}
return string.Empty;
}
}

View File

@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Helpers;
internal static class PowerToysResourcesHelper
{
private const string SettingsIconRoot = "WinUI3Apps\\Assets\\Settings\\Icons\\";
internal static IconInfo IconFromSettingsIcon(string fileName) => IconHelpers.FromRelativePath($"{SettingsIconRoot}{fileName}");
public static IconInfo ProviderIcon() => IconFromSettingsIcon("PowerToys.png");
public static IconInfo ModuleIcon(this SettingsWindow module)
{
var iconFile = module switch
{
SettingsWindow.ColorPicker => "ColorPicker.png",
SettingsWindow.FancyZones => "FancyZones.png",
SettingsWindow.Hosts => "Hosts.png",
SettingsWindow.PowerOCR => "TextExtractor.png",
SettingsWindow.RegistryPreview => "RegistryPreview.png",
SettingsWindow.MeasureTool => "ScreenRuler.png",
SettingsWindow.ShortcutGuide => "ShortcutGuide.png",
SettingsWindow.CropAndLock => "CropAndLock.png",
SettingsWindow.EnvironmentVariables => "EnvironmentVariables.png",
SettingsWindow.Awake => "Awake.png",
SettingsWindow.PowerRename => "PowerRename.png",
SettingsWindow.Run => "PowerToysRun.png",
SettingsWindow.ImageResizer => "ImageResizer.png",
SettingsWindow.KBM => "KeyboardManager.png",
SettingsWindow.MouseUtils => "MouseUtils.png",
SettingsWindow.Workspaces => "Workspaces.png",
SettingsWindow.AdvancedPaste => "AdvancedPaste.png",
SettingsWindow.CmdPal => "CmdPal.png",
SettingsWindow.ZoomIt => "ZoomIt.png",
SettingsWindow.FileExplorer => "FileExplorerPreview.png",
SettingsWindow.FileLocksmith => "FileLocksmith.png",
SettingsWindow.NewPlus => "NewPlus.png",
SettingsWindow.Peek => "Peek.png",
SettingsWindow.LightSwitch => "LightSwitch.png",
SettingsWindow.AlwaysOnTop => "AlwaysOnTop.png",
SettingsWindow.CmdNotFound => "CommandNotFound.png",
SettingsWindow.MouseWithoutBorders => "MouseWithoutBorders.png",
SettingsWindow.PowerAccent => "QuickAccent.png",
SettingsWindow.PowerLauncher => "PowerToysRun.png",
SettingsWindow.PowerPreview => "FileExplorerPreview.png",
SettingsWindow.Overview => "PowerToys.png",
SettingsWindow.Dashboard => "PowerToys.png",
_ => "PowerToys.png",
};
return IconFromSettingsIcon(iconFile);
}
public static string ModuleDisplayName(this SettingsWindow module)
{
return module switch
{
SettingsWindow.ColorPicker => "Color Picker",
SettingsWindow.FancyZones => "FancyZones",
SettingsWindow.Hosts => "Hosts File Editor",
SettingsWindow.PowerOCR => "Text Extractor",
SettingsWindow.RegistryPreview => "Registry Preview",
SettingsWindow.MeasureTool => "Screen Ruler",
SettingsWindow.ShortcutGuide => "Shortcut Guide",
SettingsWindow.CropAndLock => "Crop And Lock",
SettingsWindow.EnvironmentVariables => "Environment Variables",
SettingsWindow.Awake => "Awake",
SettingsWindow.PowerRename => "PowerRename",
SettingsWindow.Run => "PowerToys Run",
SettingsWindow.ImageResizer => "Image Resizer",
SettingsWindow.KBM => "Keyboard Manager",
SettingsWindow.MouseUtils => "Mouse Utilities",
SettingsWindow.Workspaces => "Workspaces",
SettingsWindow.AdvancedPaste => "Advanced Paste",
SettingsWindow.CmdPal => "Command Palette",
SettingsWindow.ZoomIt => "ZoomIt",
SettingsWindow.FileExplorer => "File Explorer Add-ons",
SettingsWindow.FileLocksmith => "File Locksmith",
SettingsWindow.NewPlus => "New+",
SettingsWindow.Peek => "Peek",
SettingsWindow.LightSwitch => "Light Switch",
SettingsWindow.AlwaysOnTop => "Always On Top",
SettingsWindow.CmdNotFound => "Command Not Found",
SettingsWindow.MouseWithoutBorders => "Mouse Without Borders",
SettingsWindow.PowerAccent => "Quick Accent",
SettingsWindow.Overview => "General",
SettingsWindow.Dashboard => "Dashboard",
SettingsWindow.PowerLauncher => "PowerToys Run",
SettingsWindow.PowerPreview => "File Explorer Add-ons",
_ => module.ToString(),
};
}
}

View File

@@ -0,0 +1,72 @@
// 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 System.Threading;
using ManagedCommon;
namespace PowerToysExtension.Helpers;
/// <summary>
/// Watches the global PowerToys settings.json and notifies listeners when it changes.
/// </summary>
internal static class SettingsChangeNotifier
{
private static readonly object Sync = new();
private static FileSystemWatcher? _watcher;
private static Timer? _debounceTimer;
internal static event Action? SettingsChanged;
static SettingsChangeNotifier()
{
TryStartWatcher();
}
private static void TryStartWatcher()
{
try
{
var filePath = ModuleEnablementService.SettingsFilePath;
var directory = Path.GetDirectoryName(filePath);
var fileName = Path.GetFileName(filePath);
if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(fileName))
{
return;
}
_watcher = new FileSystemWatcher(directory)
{
Filter = fileName,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size,
IncludeSubdirectories = false,
EnableRaisingEvents = true,
};
_watcher.Changed += (_, _) => ScheduleRaise();
_watcher.Created += (_, _) => ScheduleRaise();
_watcher.Deleted += (_, _) => ScheduleRaise();
_watcher.Renamed += (_, _) => ScheduleRaise();
}
catch (Exception ex)
{
Logger.LogError($"SettingsChangeNotifier failed to start: {ex.Message}");
}
}
private static void ScheduleRaise()
{
lock (Sync)
{
_debounceTimer?.Dispose();
_debounceTimer = new Timer(
_ => SettingsChanged?.Invoke(),
null,
200,
Timeout.Infinite);
}
}
}

View File

@@ -0,0 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\..\..\..\CmdPalVersion.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>PowerToysExtension</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<EnableMsixTooling>false</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<GenerateAppxPackageOnBuild>false</GenerateAppxPackageOnBuild>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<Version>$(CmdPalVersion)</Version>
</PropertyGroup>
<PropertyGroup>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' and '$(Platform)' == 'ARM64'">win-arm64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' and '$(Platform)' == 'x64'">win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\Icons\*.png" Link="WinUI3Apps\Assets\Settings\Icons\%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.CommandPalette.Extensions" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Shmuelie.WinRTServer" />
</ItemGroup>
<!-- Enable Single-project MSIX packaging support -->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\Common.Search\Common.Search.csproj" />
<ProjectReference Include="..\..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\Awake\Awake.ModuleServices\Awake.ModuleServices.csproj" />
<ProjectReference Include="..\..\..\colorPicker\ColorPicker.ModuleServices\ColorPicker.ModuleServices.csproj" />
<ProjectReference Include="..\..\..\fancyzones\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj" />
<ProjectReference Include="..\..\..\Workspaces\Workspaces.ModuleServices\Workspaces.ModuleServices.csproj" />
</ItemGroup>
<PropertyGroup>
<!-- Always build/publish AOT so the extension ships as native code -->
<SelfContained>true</SelfContained>
<PublishAot>true</PublishAot>
<PublishSingleFile>false</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<DisableRuntimeMarshalling>false</DisableRuntimeMarshalling>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,38 @@
// 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.Collections.Generic;
using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Modules;
internal sealed class AdvancedPasteModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsDeepLink.SettingsWindow.AdvancedPaste;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new OpenAdvancedPasteCommand())
{
Title = "Open Advanced Paste",
Subtitle = "Launch the Advanced Paste UI",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Advanced Paste settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class AlwaysOnTopModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.AlwaysOnTop.ModuleDisplayName();
var icon = SettingsWindow.AlwaysOnTop.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.AlwaysOnTop, title))
{
Title = title,
Subtitle = "Open Always On Top settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,91 @@
// 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 Awake.ModuleServices;
using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
namespace PowerToysExtension.Modules;
internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var items = new List<ListItem>();
var module = SettingsDeepLink.SettingsWindow.Awake;
var title = module.ModuleDisplayName();
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Awake.png");
var moduleIcon = module.ModuleIcon();
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Awake settings",
Icon = moduleIcon,
});
if (!ModuleEnablementService.IsModuleEnabled(module))
{
return items;
}
// Direct commands surfaced in the PowerToys list page.
ListItem? statusItem = null;
Action refreshStatus = () =>
{
if (statusItem is not null)
{
statusItem.Subtitle = AwakeStatusService.GetStatusSubtitle();
}
};
var refreshCommand = new RefreshAwakeStatusCommand(refreshStatus);
statusItem = new ListItem(new CommandItem(refreshCommand))
{
Title = "Awake: Current status",
Subtitle = AwakeStatusService.GetStatusSubtitle(),
Icon = icon,
};
items.Add(statusItem);
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite", refreshStatus))
{
Title = "Awake: Keep awake indefinitely",
Subtitle = "Run Awake in indefinite mode",
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => AwakeService.Instance.SetTimedAsync(30), "Awake set for 30 minutes", refreshStatus))
{
Title = "Awake: Keep awake for 30 minutes",
Subtitle = "Run Awake timed for 30 minutes",
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 1 hour", () => AwakeService.Instance.SetTimedAsync(60), "Awake set for 1 hour", refreshStatus))
{
Title = "Awake: Keep awake for 1 hour",
Subtitle = "Run Awake timed for 1 hour",
Icon = icon,
});
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => AwakeService.Instance.SetTimedAsync(120), "Awake set for 2 hours", refreshStatus))
{
Title = "Awake: Keep awake for 2 hours",
Subtitle = "Run Awake timed for 2 hours",
Icon = icon,
});
items.Add(new ListItem(new StopAwakeCommand(refreshStatus))
{
Title = "Awake: Turn off",
Subtitle = "Switch Awake back to Off",
Icon = icon,
});
return items;
}
}

View File

@@ -0,0 +1,53 @@
// 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.Collections.Generic;
using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
namespace PowerToysExtension.Modules;
internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsDeepLink.SettingsWindow.ColorPicker;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
var commands = new List<ListItem>();
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Color Picker settings",
Icon = icon,
});
if (!ModuleEnablementService.IsModuleEnabled(module))
{
return commands;
}
// Direct entries in the module list.
commands.Add(new ListItem(new OpenColorPickerCommand())
{
Title = "Open Color Picker",
Subtitle = "Start a color pick session",
Icon = icon,
});
commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage()))
{
Title = "Saved colors",
Subtitle = "Browse and copy saved colors",
Icon = icon,
});
return commands;
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class CommandNotFoundModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.CmdNotFound.ModuleDisplayName();
var icon = SettingsWindow.CmdNotFound.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.CmdNotFound, title))
{
Title = title,
Subtitle = "Open Command Not Found settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.CropAndLock;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new CropAndLockReparentCommand())
{
Title = "Crop and Lock (Reparent)",
Subtitle = "Create a cropped reparented window",
Icon = icon,
};
yield return new ListItem(new CropAndLockThumbnailCommand())
{
Title = "Crop and Lock (Thumbnail)",
Subtitle = "Create a cropped thumbnail window",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Crop and Lock settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class EnvironmentVariablesModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.EnvironmentVariables;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new OpenEnvironmentVariablesCommand())
{
Title = "Open Environment Variables",
Subtitle = "Launch Environment Variables editor",
Icon = icon,
};
yield return new ListItem(new OpenEnvironmentVariablesAdminCommand())
{
Title = "Open Environment Variables (Admin)",
Subtitle = "Launch Environment Variables editor as admin",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Environment Variables settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,53 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class FancyZonesModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.FancyZones;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new CommandItem(new FancyZonesLayoutsPage()))
{
Title = "FancyZones: Layouts",
Subtitle = "Apply a layout to all monitors or a specific monitor",
Icon = icon,
};
yield return new ListItem(new CommandItem(new FancyZonesMonitorsPage()))
{
Title = "FancyZones: Monitors",
Subtitle = "Identify monitors and apply layouts",
Icon = icon,
};
yield return new ListItem(new OpenFancyZonesEditorCommand())
{
Title = "Open FancyZones Editor",
Subtitle = "Launch layout editor",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open FancyZones settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class FileExplorerAddonsModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.FileExplorer.ModuleDisplayName();
var icon = SettingsWindow.FileExplorer.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileExplorer, title))
{
Title = title,
Subtitle = "Open File Explorer add-ons settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class FileLocksmithModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.FileLocksmith.ModuleDisplayName();
var icon = SettingsWindow.FileLocksmith.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileLocksmith, title))
{
Title = title,
Subtitle = "Open File Locksmith settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class HostsModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.Hosts;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new OpenHostsEditorCommand())
{
Title = "Open Hosts File Editor",
Subtitle = "Launch Hosts File Editor",
Icon = icon,
};
yield return new ListItem(new OpenHostsEditorAdminCommand())
{
Title = "Open Hosts File Editor (Admin)",
Subtitle = "Launch Hosts File Editor as admin",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Hosts File Editor settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class ImageResizerModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.ImageResizer.ModuleDisplayName();
var icon = SettingsWindow.ImageResizer.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.ImageResizer, title))
{
Title = title,
Subtitle = "Open Image Resizer settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.KBM.ModuleDisplayName();
var icon = SettingsWindow.KBM.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.KBM, title))
{
Title = title,
Subtitle = "Open Keyboard Manager settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class LightSwitchModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.LightSwitch;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
var items = new List<ListItem>();
if (ModuleEnablementService.IsModuleEnabled(module))
{
items.Add(new ListItem(new ToggleLightSwitchCommand())
{
Title = "Toggle Light Switch",
Subtitle = "Toggle system/apps theme immediately",
Icon = icon,
});
}
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Light Switch settings",
Icon = icon,
});
return items;
}
}

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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace PowerToysExtension.Modules;
/// <summary>
/// Base contract for a PowerToys module to expose its command palette entries.
/// </summary>
internal abstract class ModuleCommandProvider
{
public abstract IEnumerable<ListItem> BuildCommands();
}

View File

@@ -0,0 +1,78 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.MouseUtils;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsKeyEnabled("FindMyMouse"))
{
yield return new ListItem(new ToggleFindMyMouseCommand())
{
Title = "Trigger Find My Mouse",
Subtitle = "Focus the mouse pointer",
Icon = icon,
};
}
if (ModuleEnablementService.IsKeyEnabled("MouseHighlighter"))
{
yield return new ListItem(new ToggleMouseHighlighterCommand())
{
Title = "Toggle Mouse Highlighter",
Subtitle = "Highlight mouse clicks",
Icon = icon,
};
}
if (ModuleEnablementService.IsKeyEnabled("MousePointerCrosshairs"))
{
yield return new ListItem(new ToggleMouseCrosshairsCommand())
{
Title = "Toggle Mouse Crosshairs",
Subtitle = "Enable or disable pointer crosshairs",
Icon = icon,
};
}
if (ModuleEnablementService.IsKeyEnabled("CursorWrap"))
{
yield return new ListItem(new ToggleCursorWrapCommand())
{
Title = "Toggle Cursor Wrap",
Subtitle = "Wrap the cursor across monitor edges",
Icon = icon,
};
}
if (ModuleEnablementService.IsKeyEnabled("MouseJump"))
{
yield return new ListItem(new ShowMouseJumpPreviewCommand())
{
Title = "Show Mouse Jump Preview",
Subtitle = "Jump the pointer to a target",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Mouse Utilities settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class MouseWithoutBordersModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.MouseWithoutBorders.ModuleDisplayName();
var icon = SettingsWindow.MouseWithoutBorders.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.MouseWithoutBorders, title))
{
Title = title,
Subtitle = "Open Mouse Without Borders settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class NewPlusModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.NewPlus.ModuleDisplayName();
var icon = SettingsWindow.NewPlus.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.NewPlus, title))
{
Title = title,
Subtitle = "Open New+ settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class PeekModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.Peek.ModuleDisplayName();
var icon = SettingsWindow.Peek.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.Peek, title))
{
Title = title,
Subtitle = "Open Peek settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class PowerRenameModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.PowerRename.ModuleDisplayName();
var icon = SettingsWindow.PowerRename.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerRename, title))
{
Title = title,
Subtitle = "Open PowerRename settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class PowerToysRunModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.PowerLauncher.ModuleDisplayName();
var icon = SettingsWindow.PowerLauncher.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerLauncher, title))
{
Title = title,
Subtitle = "Open PowerToys Run settings",
Icon = icon,
};
}
}

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 System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class QuickAccentModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var title = SettingsWindow.PowerAccent.ModuleDisplayName();
var icon = SettingsWindow.PowerAccent.ModuleIcon();
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerAccent, title))
{
Title = title,
Subtitle = "Open Quick Accent settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,38 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class RegistryPreviewModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.RegistryPreview;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new OpenRegistryPreviewCommand())
{
Title = "Open Registry Preview",
Subtitle = "Launch Registry Preview",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Registry Preview settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,38 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class ScreenRulerModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.MeasureTool;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new ToggleScreenRulerCommand())
{
Title = "Toggle Screen Ruler",
Subtitle = "Start or close Screen Ruler",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Screen Ruler settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,38 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class ShortcutGuideModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.ShortcutGuide;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new ToggleShortcutGuideCommand())
{
Title = "Toggle Shortcut Guide",
Subtitle = "Show or hide Shortcut Guide",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Shortcut Guide settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,38 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class TextExtractorModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.PowerOCR;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
yield return new ListItem(new ToggleTextExtractorCommand())
{
Title = "Toggle Text Extractor",
Subtitle = "Start or close Text Extractor",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Text Extractor settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,64 @@
// 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.Collections.Generic;
using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using Workspaces.ModuleServices;
using WorkspacesCsharpLibrary.Data;
namespace PowerToysExtension.Modules;
internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var items = new List<ListItem>();
var module = SettingsDeepLink.SettingsWindow.Workspaces;
var title = module.ModuleDisplayName();
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Workspaces.png");
var moduleIcon = module.ModuleIcon();
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open Workspaces settings",
Icon = moduleIcon,
});
if (!ModuleEnablementService.IsModuleEnabled(module))
{
return items;
}
// Settings entry plus common actions.
items.Add(new ListItem(new OpenWorkspaceEditorCommand())
{
Title = "Workspaces: Open editor",
Subtitle = "Create or edit workspaces",
Icon = icon,
});
// Per-workspace entries via the shared service.
foreach (var workspace in LoadWorkspaces())
{
if (string.IsNullOrWhiteSpace(workspace.Id) || string.IsNullOrWhiteSpace(workspace.Name))
{
continue;
}
items.Add(new WorkspaceListItem(workspace, icon));
}
return items;
}
private static IReadOnlyList<ProjectWrapper> LoadWorkspaces()
{
var result = WorkspaceService.Instance.GetWorkspacesAsync().GetAwaiter().GetResult();
return result.Success && result.Value is not null ? result.Value : System.Array.Empty<ProjectWrapper>();
}
}

View File

@@ -0,0 +1,69 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
internal sealed class ZoomItModuleCommandProvider : ModuleCommandProvider
{
public override IEnumerable<ListItem> BuildCommands()
{
var module = SettingsWindow.ZoomIt;
var title = module.ModuleDisplayName();
var icon = module.ModuleIcon();
if (ModuleEnablementService.IsModuleEnabled(module))
{
// Action commands via ZoomIt IPC
yield return new ListItem(new ZoomItActionCommand("zoom", "ZoomIt: Zoom"))
{
Title = "ZoomIt: Zoom",
Subtitle = "Enter zoom mode",
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("draw", "ZoomIt: Draw"))
{
Title = "ZoomIt: Draw",
Subtitle = "Enter drawing mode",
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("break", "ZoomIt: Break"))
{
Title = "ZoomIt: Break",
Subtitle = "Enter break timer",
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("liveZoom", "ZoomIt: Live Zoom"))
{
Title = "ZoomIt: Live Zoom",
Subtitle = "Toggle live zoom",
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("snip", "ZoomIt: Snip"))
{
Title = "ZoomIt: Snip",
Subtitle = "Enter snip mode",
Icon = icon,
};
yield return new ListItem(new ZoomItActionCommand("record", "ZoomIt: Record"))
{
Title = "ZoomIt: Record",
Subtitle = "Start recording",
Icon = icon,
};
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
Subtitle = "Open ZoomIt settings",
Icon = icon,
};
}
}

View File

@@ -0,0 +1,96 @@
// 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.Linq;
using System.Text;
using ColorPicker.ModuleServices;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage
{
private readonly CommandItem _emptyContent;
public ColorPickerSavedColorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png");
Title = "Saved colors";
Name = "ColorPickerSavedColors";
Id = "com.microsoft.powertoys.colorpicker.savedColors";
_emptyContent = new CommandItem()
{
Title = "No saved colors",
Subtitle = "Pick a color first, then try again.",
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png"),
};
EmptyContent = _emptyContent;
}
public override IListItem[] GetItems()
{
var result = ColorPickerService.Instance.GetSavedColorsAsync().GetAwaiter().GetResult();
if (!result.Success || result.Value is null || result.Value.Count == 0)
{
return Array.Empty<IListItem>();
}
var search = SearchText;
var filtered = string.IsNullOrWhiteSpace(search)
? result.Value
: result.Value.Where(saved =>
saved.Hex.Contains(search, StringComparison.OrdinalIgnoreCase) ||
saved.Formats.Any(f => f.Value.Contains(search, StringComparison.OrdinalIgnoreCase) ||
f.Format.Contains(search, StringComparison.OrdinalIgnoreCase)));
var items = filtered.Select(saved =>
{
var copyValue = SelectPreferredFormat(saved);
var subtitle = BuildSubtitle(saved);
var command = new CopySavedColorCommand(saved, copyValue);
return (IListItem)new ListItem(new CommandItem(command))
{
Title = saved.Hex,
Subtitle = subtitle,
Icon = ColorSwatchIconFactory.Create(saved.R, saved.G, saved.B, saved.A),
};
}).ToArray();
return items;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
? "Pick a color first, then try again."
: $"No saved colors matching '{newSearch}'";
RaiseItemsChanged(0);
}
private static string SelectPreferredFormat(SavedColor saved) => saved.Hex;
private static string BuildSubtitle(SavedColor saved)
{
var sb = new StringBuilder();
foreach (var format in saved.Formats.Take(3))
{
if (sb.Length > 0)
{
sb.Append(" · ");
}
sb.Append(format.Value);
}
return sb.Length > 0 ? sb.ToString() : saved.Hex;
}
}

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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
{
private readonly CommandItem _emptyMessage;
public FancyZonesLayoutsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = "FancyZones Layouts";
Id = "com.microsoft.cmdpal.powertoys.fancyzones.layouts";
_emptyMessage = new CommandItem()
{
Title = "No layouts found",
Subtitle = "Open FancyZones Editor once to initialize layouts.",
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
// Purge orphaned cache files in background (non-blocking)
Task.Run(FancyZonesThumbnailRenderer.PurgeOrphanedCache);
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
try
{
var layouts = FancyZonesDataService.GetLayouts();
if (!string.IsNullOrWhiteSpace(SearchText))
{
layouts = layouts
.Where(l => l.Title.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase) ||
l.Subtitle.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
.ToArray();
}
if (layouts.Count == 0)
{
return Array.Empty<IListItem>();
}
_ = FancyZonesDataService.TryGetMonitors(out var monitors, out _);
var fallbackIcon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
var items = new List<IListItem>(layouts.Count);
foreach (var layout in layouts)
{
var defaultCommand = new ApplyFancyZonesLayoutCommand(layout, monitor: null);
var item = new FancyZonesLayoutListItem(defaultCommand, layout, fallbackIcon)
{
MoreCommands = BuildLayoutContext(layout, monitors),
};
items.Add(item);
}
return items.ToArray();
}
catch (Exception ex)
{
_emptyMessage.Subtitle = ex.Message;
return Array.Empty<IListItem>();
}
}
private static IContextItem[] BuildLayoutContext(FancyZonesLayoutDescriptor layout, IReadOnlyList<FancyZonesMonitorDescriptor> monitors)
{
var commands = new List<IContextItem>(monitors.Count);
for (var i = 0; i < monitors.Count; i++)
{
var monitor = monitors[i];
commands.Add(new CommandContextItem(new ApplyFancyZonesLayoutCommand(layout, monitor))
{
Title = string.Format(CultureInfo.CurrentCulture, "Apply to {0}", monitor.Title),
Subtitle = monitor.Subtitle,
});
}
return commands.ToArray();
}
}

View File

@@ -0,0 +1,68 @@
// 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;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorLayoutPickerPage : DynamicListPage
{
private readonly FancyZonesMonitorDescriptor _monitor;
private readonly CommandItem _emptyMessage;
public FancyZonesMonitorLayoutPickerPage(FancyZonesMonitorDescriptor monitor)
{
_monitor = monitor;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = $"Set active layout for {_monitor.Title}";
Id = $"com.microsoft.cmdpal.powertoys.fancyzones.monitor.{_monitor.Index}.layouts";
_emptyMessage = new CommandItem()
{
Title = "No layouts found",
Subtitle = "Open FancyZones Editor once to initialize layouts.",
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
var layouts = FancyZonesDataService.GetLayouts();
if (!string.IsNullOrWhiteSpace(SearchText))
{
layouts = layouts
.Where(l => l.Title.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase) ||
l.Subtitle.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
.ToArray();
}
if (layouts.Count == 0)
{
return Array.Empty<IListItem>();
}
var fallbackIcon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
var items = new List<IListItem>(layouts.Count);
foreach (var layout in layouts)
{
var command = new ApplyFancyZonesLayoutCommand(layout, _monitor);
var item = new FancyZonesLayoutListItem(command, layout, fallbackIcon);
items.Add(item);
}
return [.. items];
}
}

View File

@@ -0,0 +1,67 @@
// 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 Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorsPage : DynamicListPage
{
private readonly CommandItem _emptyMessage;
public FancyZonesMonitorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
Name = Title = "FancyZones Monitors";
Id = "com.microsoft.cmdpal.powertoys.fancyzones.monitors";
_emptyMessage = new CommandItem()
{
Title = "No monitors found",
Subtitle = "Open FancyZones Editor once to initialize monitor data.",
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
if (!FancyZonesDataService.TryGetMonitors(out var monitors, out var error))
{
_emptyMessage.Subtitle = error;
return Array.Empty<IListItem>();
}
var monitorIcon = new IconInfo("\uE7F4");
var items = new List<IListItem>(monitors.Count);
foreach (var monitor in monitors)
{
if (!string.IsNullOrWhiteSpace(SearchText) &&
!monitor.Title.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase) &&
!monitor.Subtitle.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
var layoutDescription = FancyZonesDataService.TryGetAppliedLayoutForMonitor(monitor.Data, out var applied) && applied is not null
? $"Current layout: {applied.Value.Type}"
: "Current layout: unknown";
var item = new FancyZonesMonitorListItem(monitor, layoutDescription, monitorIcon);
items.Add(item);
}
return [.. items];
}
}

View File

@@ -0,0 +1,46 @@
// 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 Awake.ModuleServices;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
namespace PowerToysExtension;
internal sealed partial class PowerToysExtensionPage : ListPage
{
public PowerToysExtensionPage()
{
Icon = Helpers.PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
Title = "PowerToys";
Name = "PowerToys commands";
}
public override IListItem[] GetItems()
{
return [
new ListItem(new LaunchModuleCommand("PowerToys", executableName: "PowerToys.exe", displayName: "Open PowerToys"))
{
Title = "Open PowerToys",
Subtitle = "Launch the PowerToys shell",
},
new ListItem(new OpenPowerToysSettingsCommand("PowerToys", "General"))
{
Title = "Open PowerToys settings",
Subtitle = "Open the main PowerToys settings window",
},
new ListItem(new OpenPowerToysSettingsCommand("Workspaces", "Workspaces"))
{
Title = "Open Workspaces settings",
Subtitle = "Jump directly to Workspaces settings",
},
new ListItem(new OpenWorkspaceEditorCommand())
{
Title = "Open Workspaces editor",
Subtitle = "Launch the Workspaces editor",
},
];
}
}

View File

@@ -0,0 +1,36 @@
// 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 PowerToysListPage : ListPage
{
private readonly CommandItem _empty;
public PowerToysListPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
Name = Title = "PowerToys";
Id = "com.microsoft.cmdpal.powertoys";
SettingsChangeNotifier.SettingsChanged += OnSettingsChanged;
_empty = new CommandItem()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png"),
Title = "No matching module found",
Subtitle = SearchText,
};
EmptyContent = _empty;
}
private void OnSettingsChanged()
{
RaiseItemsChanged(0);
}
public override IListItem[] GetItems() => ModuleCommandCatalog.GetAllItems();
}

View File

@@ -0,0 +1,46 @@
// 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.Collections.Generic;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension;
public sealed partial class PowerToysCommandsProvider : CommandProvider
{
public PowerToysCommandsProvider()
{
DisplayName = "PowerToys";
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
}
public override ICommandItem[] TopLevelCommands() =>
[
new CommandItem(new Pages.PowerToysListPage())
{
Title = "PowerToys",
Subtitle = "PowerToys commands and settings",
}
];
public override IFallbackCommandItem[] FallbackCommands()
{
var items = ModuleCommandCatalog.GetAllItems();
var fallbacks = new List<IFallbackCommandItem>(items.Length);
foreach (var item in items)
{
if (item?.Command is not ICommand cmd)
{
continue;
}
fallbacks.Add(new PowerToysFallbackCommandItem(cmd, item.Title, item.Subtitle, item.Icon, item.MoreCommands));
}
return fallbacks.ToArray();
}
}

View File

@@ -0,0 +1,41 @@
// 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 System.Threading;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
namespace PowerToysExtension;
[Guid("7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17")]
public sealed partial class PowerToysExtension : IExtension, IDisposable
{
private readonly ManualResetEvent _extensionDisposedEvent;
private readonly PowerToysExtensionCommandsProvider _provider = new();
public PowerToysExtension(ManualResetEvent extensionDisposedEvent)
{
this._extensionDisposedEvent = extensionDisposedEvent;
Logger.LogInfo($"PowerToysExtension constructed. ProcArch={RuntimeInformation.ProcessArchitecture} OSArch={RuntimeInformation.OSArchitecture} BaseDir={AppContext.BaseDirectory}");
}
public object? GetProvider(ProviderType providerType)
{
Logger.LogInfo($"GetProvider requested: {providerType}");
return providerType switch
{
ProviderType.Commands => _provider,
_ => null,
};
}
public void Dispose()
{
Logger.LogInfo("PowerToysExtension disposing; signalling exit.");
this._extensionDisposedEvent.Set();
}
}

View File

@@ -0,0 +1,50 @@
// 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.Collections.Generic;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
namespace PowerToysExtension;
public partial class PowerToysExtensionCommandsProvider : CommandProvider
{
private readonly ICommandItem[] _commands;
public PowerToysExtensionCommandsProvider()
{
DisplayName = "PowerToys";
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
_commands = [
new CommandItem(new Pages.PowerToysListPage())
{
Title = "PowerToys",
Subtitle = "PowerToys commands and settings",
},
];
}
public override ICommandItem[] TopLevelCommands()
{
return _commands;
}
public override IFallbackCommandItem[] FallbackCommands()
{
var items = ModuleCommandCatalog.GetAllItems();
var fallbacks = new List<IFallbackCommandItem>(items.Length);
foreach (var item in items)
{
if (item?.Command is not ICommand cmd)
{
continue;
}
fallbacks.Add(new PowerToysFallbackCommandItem(cmd, item.Title, item.Subtitle, item.Icon, item.MoreCommands));
}
return fallbacks.ToArray();
}
}

View File

@@ -0,0 +1,70 @@
// 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 System.Threading;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Shmuelie.WinRTServer;
using Shmuelie.WinRTServer.CsWinRT;
namespace PowerToysExtension;
public class Program
{
[MTAThread]
public static void Main(string[] args)
{
try
{
// Initialize per-extension log under CmdPal/PowerToysExtension.
Logger.InitializeLogger("\\CmdPal\\PowerToysExtension\\Logs");
Logger.LogInfo($"PowerToysExtension starting. Args=\"{string.Join(' ', args)}\" ProcArch={RuntimeInformation.ProcessArchitecture} OSArch={RuntimeInformation.OSArchitecture} BaseDir={AppContext.BaseDirectory}");
}
catch
{
// Continue even if logging fails.
}
try
{
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
{
Logger.LogInfo("RegisterProcessAsComServer mode detected.");
ComServer server = new();
ManualResetEvent extensionDisposedEvent = new(false);
try
{
PowerToysExtension extensionInstance = new(extensionDisposedEvent);
Logger.LogInfo("Registering extension via Shmuelie.WinRTServer.");
server.RegisterClass<PowerToysExtension, IExtension>(() => extensionInstance);
server.Start();
Logger.LogInfo("Extension instance registered; waiting for disposal signal.");
extensionDisposedEvent.WaitOne();
Logger.LogInfo("Extension disposed signal received; exiting server loop.");
}
finally
{
server.Stop();
server.UnsafeDispose();
}
}
else
{
Console.WriteLine("Not being launched as a Extension... exiting.");
Logger.LogInfo("Exited: not launched with -RegisterProcessAsComServer.");
}
}
catch (Exception ex)
{
Logger.LogError("Unhandled exception in PowerToysExtension.Main", ex);
throw;
}
finally
{
}
}
}

View File

@@ -0,0 +1,6 @@
# PowerToys Command Palette Extension
This folder is exposed to the Windows Command Palette host via the
`PublicFolder` attribute in the AppExtension registration. It intentionally
contains only documentation today, but can be used for additional metadata in
the future without requiring code changes.

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="PowerToysExtension.app" />
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
packageName="Microsoft.PowerToys.SparseApp"
applicationId="PowerToys.CmdPalExtension"
publisher="CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US" />
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<ws2:dpiAwareness xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</ws2:dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>