mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
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:
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Helpers;
|
||||
|
||||
namespace PowerToysExtension.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the PowerToys settings application deep linked to a specific module.
|
||||
/// </summary>
|
||||
internal sealed partial class OpenPowerToysSettingsCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _moduleName;
|
||||
private readonly string _settingsKey;
|
||||
|
||||
internal OpenPowerToysSettingsCommand(string moduleName, string settingsKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(moduleName))
|
||||
{
|
||||
throw new ArgumentException("Module name is required", nameof(moduleName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settingsKey))
|
||||
{
|
||||
throw new ArgumentException("Settings key is required", nameof(settingsKey));
|
||||
}
|
||||
|
||||
_moduleName = moduleName;
|
||||
_settingsKey = settingsKey;
|
||||
Name = $"Open {_moduleName} settings";
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
var powerToysPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.exe");
|
||||
if (string.IsNullOrEmpty(powerToysPath))
|
||||
{
|
||||
return CommandResult.ShowToast("Unable to locate PowerToys.");
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(powerToysPath)
|
||||
{
|
||||
Arguments = $"--open-settings={_settingsKey}",
|
||||
UseShellExecute = false,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CommandResult.ShowToast($"Opening {_moduleName} settings failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]]),
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())];
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user