diff --git a/PowerToys.sln b/PowerToys.sln
index ca8964e86f..0447aadd42 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -831,6 +831,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.ModuleContracts",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Awake.ModuleServices", "src\modules\awake\Awake.ModuleServices\Awake.ModuleServices.csproj", "{2141FF78-5F51-ED6B-E11B-C7079CCA1456}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPicker.ModuleServices", "src\modules\colorPicker\ColorPicker.ModuleServices\ColorPicker.ModuleServices.csproj", "{C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -5273,6 +5275,22 @@ Global
{2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x64.Build.0 = Release|x64
{2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.ActiveCfg = Release|x64
{2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.Build.0 = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|Any CPU.Build.0 = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|ARM64.Build.0 = Debug|ARM64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x64.ActiveCfg = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x64.Build.0 = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x86.ActiveCfg = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x86.Build.0 = Debug|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|Any CPU.ActiveCfg = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|Any CPU.Build.0 = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|ARM64.ActiveCfg = Release|ARM64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|ARM64.Build.0 = Release|ARM64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x64.ActiveCfg = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x64.Build.0 = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x86.ActiveCfg = Release|x64
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -5607,6 +5625,7 @@ Global
{D52AAF95-DE88-49EA-B28A-10E382BCD4AB} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{094E65D5-585E-4898-B465-97A47CD44380} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{2141FF78-5F51-ED6B-E11B-C7079CCA1456} = {127F38E0-40AA-4594-B955-5616BF206882}
+ {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs
new file mode 100644
index 0000000000..f7849bb4e5
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs
@@ -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 System.Windows.Forms;
+using ColorPicker.ModuleServices;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+namespace PowerToysExtension.Commands;
+
+///
+/// Copies a saved color in a chosen format.
+///
+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
+ {
+ Clipboard.SetText(_copyValue);
+ return CommandResult.ShowToast($"Copied {_copyValue}");
+ }
+ catch (Exception ex)
+ {
+ return CommandResult.ShowToast($"Failed to copy color: {ex.Message}");
+ }
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs
new file mode 100644
index 0000000000..6982c5dffe
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs
@@ -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;
+
+///
+/// Opens the Color Picker picker session via shared event.
+///
+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}");
+ }
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs
new file mode 100644
index 0000000000..3370268611
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs
@@ -0,0 +1,40 @@
+// 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.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+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 iconData = IconInfo.FromStream(ms.AsRandomAccessStream()).Dark as IconData;
+ return iconData != null ? new IconInfo(iconData, iconData) : new IconInfo("\u25CF");
+ }
+ catch
+ {
+ // Fallback to a simple colored glyph when drawing fails.
+ return new IconInfo("\u25CF"); // Black circle glyph
+ }
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
index c5f0f021bf..89214fd45e 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
@@ -57,6 +57,7 @@
+
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
index 7eb11172de..821b3b1c92 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
@@ -7,6 +7,7 @@ using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Pages;
namespace PowerToysExtension.Modules;
@@ -17,19 +18,39 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
var title = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleDisplayName();
var icon = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleIcon();
+ var commands = new List();
+
+ // Quick actions under MoreCommands.
var more = new List
{
+ new CommandContextItem(new OpenColorPickerCommand()),
new CommandContextItem(new CopyColorCommand()),
+ new CommandContextItem(new ColorPickerSavedColorsPage()),
};
- var item = new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.ColorPicker, title))
+ commands.Add(new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.ColorPicker, title))
{
Title = title,
Subtitle = "Open Color Picker settings",
Icon = icon,
MoreCommands = more.ToArray(),
- };
+ });
- return [item];
+ // 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;
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs
new file mode 100644
index 0000000000..6193ee2a3d
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs
@@ -0,0 +1,111 @@
+// 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 = IconHelpers.FromRelativePath("Assets\\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 = IconHelpers.FromRelativePath("Assets\\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();
+ }
+
+ 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);
+
+ return (IListItem)new CommandItem(new CopySavedColorCommand(saved, copyValue))
+ {
+ 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)
+ {
+ // Prefer RGBA, then RGB, otherwise fallback to hex.
+ var rgba = saved.Formats.FirstOrDefault(f => f.Format.Equals("RGBA", StringComparison.OrdinalIgnoreCase));
+ if (rgba is not null)
+ {
+ return rgba.Value;
+ }
+
+ var rgb = saved.Formats.FirstOrDefault(f => f.Format.Equals("RGB", StringComparison.OrdinalIgnoreCase));
+ if (rgb is not null)
+ {
+ return rgb.Value;
+ }
+
+ return 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;
+ }
+}
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs
new file mode 100644
index 0000000000..90e71e6f18
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs
@@ -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);
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj
new file mode 100644
index 0000000000..1efe5cdc8d
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ enable
+ enable
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs
new file mode 100644
index 0000000000..c68ef4ad36
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs
@@ -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;
+
+///
+/// Provides programmatic control for Color Picker actions.
+///
+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 LaunchAsync(CancellationToken cancellationToken = default)
+ {
+ // Default launch -> open picker.
+ return OpenPickerAsync(cancellationToken);
+ }
+
+ public Task OpenPickerAsync(CancellationToken cancellationToken = default)
+ {
+ return SignalEventAsync(Constants.ShowColorPickerSharedEvent(), "Color Picker");
+ }
+
+ public Task>> 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>(Array.Empty()));
+ }
+
+ using var stream = File.OpenRead(historyPath);
+ var colors = JsonSerializer.Deserialize(stream, ColorPickerServiceJsonContext.Default.ListString) ?? new List();
+
+ var settingsUtils = new SettingsUtils();
+ var settings = settingsUtils.GetSettingsOrDefault(ColorPickerSettings.ModuleName);
+
+ var results = new List(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>(results));
+ }
+ catch (OperationCanceledException)
+ {
+ return Task.FromResult(OperationResults.Fail>("Reading saved colors was cancelled."));
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(OperationResults.Fail>($"Failed to read saved colors: {ex.Message}"));
+ }
+ }
+
+ private static Task 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 BuildFormats(Color color, ColorPickerSettings settings)
+ {
+ var formats = new List();
+ 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;
+ }
+}
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs
new file mode 100644
index 0000000000..f26e9009d3
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs
@@ -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))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(SavedColor))]
+[JsonSerializable(typeof(ColorFormatValue))]
+[JsonSerializable(typeof(ColorPickerSettings))]
+internal sealed partial class ColorPickerServiceJsonContext : JsonSerializerContext
+{
+}
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs
new file mode 100644
index 0000000000..4ad2ca3da3
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs
@@ -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 OpenPickerAsync(CancellationToken cancellationToken = default);
+
+ Task>> GetSavedColorsAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs
new file mode 100644
index 0000000000..3697129aa0
--- /dev/null
+++ b/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs
@@ -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 Formats);