mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +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,7 @@
|
||||
// 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 ColorPicker.ModuleServices;
|
||||
|
||||
public sealed record ColorFormatValue(string Format, string Value);
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="ColorFormatValue.cs" />
|
||||
<Compile Include="ColorPickerService.cs" />
|
||||
<Compile Include="IColorPickerService.cs" />
|
||||
<Compile Include="ColorPickerServiceJsonContext.cs" />
|
||||
<Compile Include="SavedColor.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\PowerToys.ModuleContracts\PowerToys.ModuleContracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,157 @@
|
||||
// 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.Drawing;
|
||||
using System.Text.Json;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using PowerToys.Interop;
|
||||
using PowerToys.ModuleContracts;
|
||||
|
||||
namespace ColorPicker.ModuleServices;
|
||||
|
||||
/// <summary>
|
||||
/// Provides programmatic control for Color Picker actions.
|
||||
/// </summary>
|
||||
public sealed class ColorPickerService : ModuleServiceBase, IColorPickerService
|
||||
{
|
||||
public static ColorPickerService Instance { get; } = new();
|
||||
|
||||
public override string Key => SettingsDeepLink.SettingsWindow.ColorPicker.ToString();
|
||||
|
||||
protected override SettingsDeepLink.SettingsWindow SettingsWindow => SettingsDeepLink.SettingsWindow.ColorPicker;
|
||||
|
||||
public override Task<OperationResult> LaunchAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Default launch -> open picker.
|
||||
return OpenPickerAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<OperationResult> OpenPickerAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return SignalEventAsync(Constants.ShowColorPickerSharedEvent(), "Color Picker");
|
||||
}
|
||||
|
||||
public Task<OperationResult<IReadOnlyList<SavedColor>>> GetSavedColorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var historyPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "ColorPicker", "colorHistory.json");
|
||||
if (!File.Exists(historyPath))
|
||||
{
|
||||
return Task.FromResult(OperationResults.Ok<IReadOnlyList<SavedColor>>(Array.Empty<SavedColor>()));
|
||||
}
|
||||
|
||||
using var stream = File.OpenRead(historyPath);
|
||||
var colors = JsonSerializer.Deserialize(stream, ColorPickerServiceJsonContext.Default.ListString) ?? new List<string>();
|
||||
|
||||
var settingsUtils = SettingsUtils.Default;
|
||||
var settings = settingsUtils.GetSettingsOrDefault<ColorPickerSettings>(ColorPickerSettings.ModuleName);
|
||||
|
||||
var results = new List<SavedColor>(colors.Count);
|
||||
foreach (var entry in colors)
|
||||
{
|
||||
if (!TryParseArgb(entry, out var color))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var formats = BuildFormats(color, settings);
|
||||
var hex = $"#{color.R:X2}{color.G:X2}{color.B:X2}";
|
||||
|
||||
results.Add(new SavedColor(
|
||||
hex,
|
||||
color.A,
|
||||
color.R,
|
||||
color.G,
|
||||
color.B,
|
||||
formats));
|
||||
}
|
||||
|
||||
return Task.FromResult(OperationResults.Ok<IReadOnlyList<SavedColor>>(results));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Task.FromResult(OperationResults.Fail<IReadOnlyList<SavedColor>>("Reading saved colors was cancelled."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult(OperationResults.Fail<IReadOnlyList<SavedColor>>($"Failed to read saved colors: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<OperationResult> SignalEventAsync(string eventName, string actionDescription)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
if (!eventHandle.Set())
|
||||
{
|
||||
return Task.FromResult(OperationResult.Fail($"Failed to signal {actionDescription}."));
|
||||
}
|
||||
|
||||
return Task.FromResult(OperationResult.Ok());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult(OperationResult.Fail($"Failed to signal {actionDescription}: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseArgb(string value, out Color color)
|
||||
{
|
||||
color = Color.Empty;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parts = value.Split('|');
|
||||
if (parts.Length != 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (byte.TryParse(parts[0], out var a) &&
|
||||
byte.TryParse(parts[1], out var r) &&
|
||||
byte.TryParse(parts[2], out var g) &&
|
||||
byte.TryParse(parts[3], out var b))
|
||||
{
|
||||
color = Color.FromArgb(a, r, g, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ColorFormatValue> BuildFormats(Color color, ColorPickerSettings settings)
|
||||
{
|
||||
var formats = new List<ColorFormatValue>();
|
||||
foreach (var kvp in settings.Properties.VisibleColorFormats)
|
||||
{
|
||||
var formatName = kvp.Key;
|
||||
var (isVisible, formatString) = kvp.Value;
|
||||
if (!isVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var formatted = ColorFormatHelper.GetStringRepresentation(color, formatString);
|
||||
if (formatName.Equals("HEX", StringComparison.OrdinalIgnoreCase) && !formatted.StartsWith('#'))
|
||||
{
|
||||
formatted = "#" + formatted;
|
||||
}
|
||||
|
||||
formats.Add(new ColorFormatValue(formatName, formatted));
|
||||
}
|
||||
|
||||
return formats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace ColorPicker.ModuleServices;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(List<SavedColor>))]
|
||||
[JsonSerializable(typeof(SavedColor))]
|
||||
[JsonSerializable(typeof(ColorFormatValue))]
|
||||
[JsonSerializable(typeof(ColorPickerSettings))]
|
||||
internal sealed partial class ColorPickerServiceJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using PowerToys.ModuleContracts;
|
||||
|
||||
namespace ColorPicker.ModuleServices;
|
||||
|
||||
public interface IColorPickerService : IModuleService
|
||||
{
|
||||
Task<OperationResult> OpenPickerAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<OperationResult<IReadOnlyList<SavedColor>>> GetSavedColorsAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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 ColorPicker.ModuleServices;
|
||||
|
||||
public sealed record SavedColor(string Hex, byte A, byte R, byte G, byte B, IReadOnlyList<ColorFormatValue> Formats);
|
||||
@@ -205,7 +205,7 @@ namespace ColorPicker.Helpers
|
||||
|
||||
private void ColorEditorViewModel_OpenSettingsRequested(object sender, EventArgs e)
|
||||
{
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ColorPicker, false);
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ColorPicker);
|
||||
}
|
||||
|
||||
internal void RegisterWindowHandle(System.Windows.Interop.HwndSource hwndSource)
|
||||
|
||||
Reference in New Issue
Block a user