Merge remote-tracking branch 'upstream/main' into dev/migrie/merge-upstream-again

This commit is contained in:
Mike Griese
2025-03-13 09:32:31 -05:00
22 changed files with 977 additions and 313 deletions

View File

@@ -582,6 +582,7 @@ GUITHREADINFO
GValue
gwl
GWLP
GWLSTYLE
hangeul
Hanzi
Hardlines
@@ -1705,6 +1706,9 @@ TLayout
tlb
tlbimp
tlc
TPMLEFTALIGN
TPMRETURNCMD
TMPVAR
TNP
Toolhelp
toolkitconverters
@@ -1916,6 +1920,7 @@ WMI
WMICIM
wmimgmt
wmp
WMSYSCOMMAND
wnd
WNDCLASS
WNDCLASSEX

View File

@@ -702,6 +702,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.UITests", "src\modules\Hosts\Hosts.UITests\Hosts.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", "src\modules\registrypreview\RegistryPreview.FuzzTests\RegistryPreview.FuzzTests.csproj", "{5702B3CC-8575-48D5-83D8-15BB42269CD3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2558,6 +2560,14 @@ Global
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.ActiveCfg = Debug|ARM64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.Build.0 = Debug|ARM64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.ActiveCfg = Debug|x64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.Build.0 = Debug|x64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.ActiveCfg = Release|ARM64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.Build.0 = Release|ARM64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.ActiveCfg = Release|x64
{5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2826,6 +2836,7 @@ Global
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{5702B3CC-8575-48D5-83D8-15BB42269CD3} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -46,13 +46,13 @@ internal static class Logger
Logger.Log(log);
}
internal static void Log(Exception e)
internal static void Log(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
if (e is not KnownException)
{
string exText = e.ToString();
Log($"!Exception!: {exText}");
Log($"!Exception!: {exText}", memberName, sourceFilePath, sourceLineNumber);
if (DateTime.UtcNow.Hour != lastHour)
{
@@ -77,18 +77,18 @@ internal static class Logger
private const string HeaderRECEIVED =
"Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}";
internal static void LogDebug(string log, bool clearLog = false)
internal static void LogDebug(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
#if DEBUG
Log(log, clearLog);
Log(log, clearLog, memberName, sourceFilePath, sourceLineNumber);
#endif
}
internal static void Log(string log, bool clearLog = false)
internal static void Log(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
log = DateTime.Now.ToString("MM/dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + $"({Thread.CurrentThread.ManagedThreadId})" + log;
ManagedCommon.Logger.LogInfo(log);
ManagedCommon.Logger.LogInfo(log, memberName, sourceFilePath, sourceLineNumber);
lock (AllLogsLock)
{
if (clearLog)

View File

@@ -2,13 +2,11 @@
x:Class="ColorPickerUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
StartupUri="MainWindow.xaml">
StartupUri="MainWindow.xaml"
ThemeMode="System">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Dark" />
<ui:ControlsDictionary />
<ResourceDictionary Source="Resources/Styles.xaml" />
<ResourceDictionary Source="Resources/ViewModelViewMappings.xaml" />
</ResourceDictionary.MergedDictionaries>

View File

@@ -1,4 +1,4 @@
<ui:FluentWindow
<Window
x:Class="ColorPicker.ColorEditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -7,41 +7,51 @@
xmlns:e="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:ColorPicker.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Width="440"
Height="380"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
AutomationProperties.Name="{x:Static p:Resources.cp_editor}"
ExtendsContentIntoTitleBar="True"
ResizeMode="NoResize"
Topmost="True"
WindowCornerPreference="Default"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<e:Interaction.Behaviors>
<behaviors:CloseZoomWindowBehavior />
</e:Interaction.Behaviors>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ui:TitleBar
x:Name="TitleBar"
Title="{x:Static p:Resources.CP_Title}"
Grid.Row="0"
Height="32"
Padding="16,0,16,0"
ShowMaximize="False"
ShowMinimize="False">
<ui:TitleBar.Icon>
<ui:ImageIcon Source="pack://application:,,,/Assets/ColorPicker/icon.ico" />
</ui:TitleBar.Icon>
</ui:TitleBar>
<ContentPresenter
x:Name="contentPresenter"
Grid.Row="1"
Content="{Binding Content}" />
</Grid>
</ui:FluentWindow>
<Border x:Name="MainBorder" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel
Grid.Row="0"
Background="Transparent"
MouseLeftButtonDown="TitleBar_MouseDown"
MouseRightButtonUp="TitleBar_MouseRightClick">
<Image
Width="20"
Height="20"
Margin="10,5,5,8"
Source="pack://application:,,,/Assets/ColorPicker/icon.ico" />
<TextBlock
Margin="10"
VerticalAlignment="Center"
Text="{x:Static p:Resources.CP_Title}" />
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button
Width="50"
Height="30"
Margin="10,-10,-2,1"
Click="Close_Click"
Content="&#xE711;"
FontFamily="{StaticResource IconFontFamily}"
Style="{StaticResource TitleBarCloseButtonStyle}" />
</StackPanel>
</DockPanel>
<ContentPresenter
x:Name="contentPresenter"
Grid.Row="1"
Content="{Binding Content}" />
</Grid>
</Border>
</Window>

View File

@@ -3,25 +3,31 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Shell;
using ColorPicker.Helpers;
using ControlzEx.Theming;
using ManagedCommon;
using Wpf.Ui.Controls;
using Microsoft.Diagnostics.Tracing.Parsers.ClrPrivate;
using Microsoft.Win32;
using Windows.Graphics;
namespace ColorPicker
{
/// <summary>
/// Interaction logic for ColorEditorWindow.xaml
/// </summary>
public partial class ColorEditorWindow : FluentWindow
public partial class ColorEditorWindow : Window
{
private readonly AppStateHandler _appStateHandler;
public ColorEditorWindow(AppStateHandler appStateHandler)
{
InitializeComponent();
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this);
WindowBackdropType = OSVersionHelper.IsWindows11() ? WindowBackdropType.Mica : WindowBackdropType = WindowBackdropType.None;
_appStateHandler = appStateHandler;
Closing += ColorEditorWindow_Closing;
@@ -35,7 +41,143 @@ namespace ColorPicker
protected override void OnSourceInitialized(EventArgs e)
{
WindowChrome.SetWindowChrome(
this,
new WindowChrome
{
CaptionHeight = 0,
CornerRadius = default,
GlassFrameThickness = new Thickness(-1),
ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
UseAeroCaptionButtons = false,
});
if (OSVersionHelper.IsWindows11())
{
// ResizeMode="NoResize" removes rounded corners. So force them to rounded.
IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
DWMWINDOWATTRIBUTE attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
DWM_WINDOW_CORNER_PREFERENCE preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
}
else
{
// On Windows10 ResizeMode="NoResize" removes the border so we add a new one.
MainBorder.BorderThickness = new System.Windows.Thickness(0.5);
}
// Hide then Show with WindowStyle="None" will remove the Mica effect. So manually remove the titlebar.
RemoveWindowTitlebarContents();
base.OnSourceInitialized(e);
}
public void RemoveWindowTitlebarContents()
{
IntPtr handle = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
if (handle == IntPtr.Zero)
{
return;
}
int windowStyleLong = GetWindowLong(handle, GWLSTYLE);
windowStyleLong &= ~(int)WindowStyles.WS_SYSMENU;
IntPtr result = SetWindowLong(handle, GWLSTYLE, windowStyleLong);
if (result.ToInt64() == 0)
{
int error = Marshal.GetLastWin32Error();
Logger.LogError($"SetWindowLong error {error}");
}
}
private void Close_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.DragMove();
}
}
private void TitleBar_MouseRightClick(object sender, MouseButtonEventArgs e)
{
IntPtr hwnd = new WindowInteropHelper(this).Handle;
// Get the mouse position relative to the screen
Point mousePosition = e.GetPosition(this);
Point screenPoint = PointToScreen(mousePosition);
// Display the system menu at the current mouse position
IntPtr hMenu = GetSystemMenu(hwnd, false);
if (hMenu != IntPtr.Zero)
{
int command = TrackPopupMenu(
hMenu,
TPMLEFTALIGN | TPMRETURNCMD,
(int)screenPoint.X,
(int)screenPoint.Y,
0,
hwnd,
IntPtr.Zero);
if (command > 0)
{
SendMessage(hwnd, WMSYSCOMMAND, new IntPtr(command), IntPtr.Zero);
}
}
}
private const int WMSYSCOMMAND = 0x0112;
private const int TPMLEFTALIGN = 0x0000;
private const int TPMRETURNCMD = 0x0100;
// The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
public enum DWMWINDOWATTRIBUTE
{
DWMWA_WINDOW_CORNER_PREFERENCE = 33,
}
public enum DWM_WINDOW_CORNER_PREFERENCE
{
DWMWCP_DEFAULT = 0,
DWMWCP_DONOTROUND = 1,
DWMWCP_ROUND = 2,
DWMWCP_ROUNDSMALL = 3,
}
// Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
[DllImport("dwmapi.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
internal static extern void DwmSetWindowAttribute(
IntPtr hwnd,
DWMWINDOWATTRIBUTE attribute,
ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute,
uint cbAttribute);
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern int TrackPopupMenu(IntPtr hMenu, int uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
public const int GWLSTYLE = -16;
[Flags]
public enum WindowStyles : uint
{
WS_SYSMENU = 0x00080000, // System menu (close/maximize/minimize button area)
}
}
}

View File

@@ -38,7 +38,6 @@
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="WPF-UI" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">

View File

@@ -6,7 +6,6 @@
xmlns:local="clr-namespace:ColorPicker"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:ColorPicker.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="ReadonlyTextBoxStyle" TargetType="{x:Type TextBox}">
@@ -66,7 +65,6 @@
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
IsTabStop="{TemplateBinding ScrollViewer.IsTabStop}"
Style="{StaticResource DefaultTextBoxScrollViewerStyle}"
TextElement.Foreground="{TemplateBinding Foreground}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" />
</Grid>
@@ -173,7 +171,7 @@
Style="{StaticResource SubtleButtonStyle}"
ToolTipService.ToolTip="{x:Static p:Resources.Copy_to_clipboard}">
<Button.Content>
<ui:SymbolIcon FontSize="20" Symbol="Copy20" />
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="&#xE8C8;" />
</Button.Content>
</Button>
</Grid>

View File

@@ -6,7 +6,6 @@
xmlns:helpers="clr-namespace:ColorPicker.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:ColorPicker.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
AutomationProperties.Name="{x:Static p:Resources.Color_Palette}"
FocusManager.IsFocusScope="True"
KeyboardNavigation.TabNavigation="Once"
@@ -213,175 +212,177 @@
Style="{DynamicResource ColorShadeButtonStyle}"
ToolTipService.ToolTip="{x:Static p:Resources.Selected_color_tooltip}">
<ui:Flyout
<Popup
x:Name="DetailsFlyout"
Margin="24,0,0,0"
AllowsTransparency="True"
Closed="DetailsFlyout_Closed"
Opened="DetailsFlyout_Opened"
Placement="Top">
<Grid x:Name="detailsGrid" KeyboardNavigation.TabNavigation="Contained">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="44" />
<ColumnDefinition Width="86" />
<ColumnDefinition Width="86" />
<ColumnDefinition Width="86" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
Placement="Top"
StaysOpen="False">
<Border
Padding="15"
Background="{DynamicResource ApplicationBackgroundBrush}"
BorderBrush="{DynamicResource SurfaceStrokeColorFlyoutBrush}"
BorderThickness="1"
CornerRadius="16">
<Grid x:Name="detailsGrid" KeyboardNavigation.TabNavigation="Contained">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="44" />
<ColumnDefinition Width="86" />
<ColumnDefinition Width="86" />
<ColumnDefinition Width="86" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="H"
TextAlignment="Center" />
<TextBlock
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="H"
TextAlignment="Center" />
<Slider
x:Name="HueGradientSlider"
Grid.Column="1"
Grid.ColumnSpan="3"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Static p:Resources.Hue_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="HueGradientSlider_ValueChanged" />
<Slider
x:Name="HueGradientSlider"
Grid.Column="1"
Grid.ColumnSpan="3"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Static p:Resources.Hue_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="HueGradientSlider_ValueChanged" />
<TextBlock
Grid.Row="2"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="S"
TextAlignment="Center" />
<TextBlock
Grid.Row="2"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="S"
TextAlignment="Center" />
<Slider
x:Name="SaturationGradientSlider"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="3"
AutomationProperties.Name="{x:Static p:Resources.Saturation_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="SaturationGradientSlider_ValueChanged">
<Slider.Background>
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
<GradientStop x:Name="SaturationStartColor" Color="Black" />
<GradientStop x:Name="SaturationStopColor" Offset="1" Color="Red" />
</LinearGradientBrush>
</Slider.Background>
</Slider>
<Slider
x:Name="SaturationGradientSlider"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="3"
AutomationProperties.Name="{x:Static p:Resources.Saturation_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="SaturationGradientSlider_ValueChanged">
<Slider.Background>
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
<GradientStop x:Name="SaturationStartColor" Color="Black" />
<GradientStop x:Name="SaturationStopColor" Offset="1" Color="Red" />
</LinearGradientBrush>
</Slider.Background>
</Slider>
<TextBlock
Grid.Row="4"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="V"
TextAlignment="Center" />
<TextBlock
Grid.Row="4"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="V"
TextAlignment="Center" />
<Slider
x:Name="ValueGradientSlider"
Grid.Row="4"
Grid.Column="1"
Grid.ColumnSpan="3"
AutomationProperties.Name="{x:Static p:Resources.Value_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="ValueGradientSlider_ValueChanged">
<Slider.Background>
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
<GradientStop x:Name="ValueStartColor" Color="Black" />
<GradientStop x:Name="ValueStopColor" Offset="1" Color="Red" />
</LinearGradientBrush>
</Slider.Background>
</Slider>
<Slider
x:Name="ValueGradientSlider"
Grid.Row="4"
Grid.Column="1"
Grid.ColumnSpan="3"
AutomationProperties.Name="{x:Static p:Resources.Value_slider}"
IsMoveToPointEnabled="True"
Maximum="289"
Minimum="0"
ValueChanged="ValueGradientSlider_ValueChanged">
<Slider.Background>
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
<GradientStop x:Name="ValueStartColor" Color="Black" />
<GradientStop x:Name="ValueStopColor" Offset="1" Color="Red" />
</LinearGradientBrush>
</Slider.Background>
</Slider>
<TextBlock
Grid.Row="6"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="RGB"
TextAlignment="Center" />
<TextBlock
Grid.Row="6"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="RGB"
TextAlignment="Center" />
<ui:NumberBox
x:Name="RNumberBox"
Grid.Row="6"
Grid.Column="1"
AutomationProperties.Name="{x:Static p:Resources.Red_value}"
Maximum="255"
Minimum="0"
SpinButtonPlacementMode="Compact"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<TextBox
x:Name="RNumberBox"
Grid.Row="6"
Grid.Column="1"
AutomationProperties.Name="{x:Static p:Resources.Red_value}"
PreviewTextInput="TextBox_PreviewTextInput"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<TextBox
x:Name="GNumberBox"
Grid.Row="6"
Grid.Column="2"
Margin="4,0,0,0"
AutomationProperties.Name="{x:Static p:Resources.Green_value}"
PreviewTextInput="TextBox_PreviewTextInput"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<TextBox
x:Name="BNumberBox"
Grid.Row="6"
Grid.Column="3"
Margin="4,0,0,0"
AutomationProperties.Name="{x:Static p:Resources.Blue_value}"
PreviewTextInput="TextBox_PreviewTextInput"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<ui:NumberBox
x:Name="GNumberBox"
Grid.Row="6"
Grid.Column="2"
Margin="4,0,0,0"
AutomationProperties.Name="{x:Static p:Resources.Green_value}"
Maximum="255"
Minimum="0"
SpinButtonPlacementMode="Compact"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<TextBlock
Grid.Row="8"
Width="38"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="HEX"
TextAlignment="Center" />
<TextBox
x:Name="HexCode"
Grid.Row="8"
Grid.Column="1"
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Static p:Resources.Hex_value}"
CharacterCasing="Lower"
GotKeyboardFocus="HexCode_GotKeyboardFocus"
MaxLength="7"
TextChanged="HexCode_TextChanged"
TextWrapping="Wrap" />
<ui:NumberBox
x:Name="BNumberBox"
Grid.Row="6"
Grid.Column="3"
Margin="4,0,0,0"
AutomationProperties.Name="{x:Static p:Resources.Blue_value}"
Maximum="255"
Minimum="0"
SpinButtonPlacementMode="Compact"
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
<Button
x:Name="OKButton"
Grid.Row="9"
Grid.ColumnSpan="4"
Margin="0,32,0,0"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Static p:Resources.Select}"
Click="OKButton_Click"
Content="{x:Static p:Resources.Select}"
Style="{StaticResource CustomAccentButtonStyle}" />
<TextBlock
Grid.Row="8"
Width="38"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="HEX"
TextAlignment="Center" />
<TextBox
x:Name="HexCode"
Grid.Row="8"
Grid.Column="1"
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
AutomationProperties.Name="{x:Static p:Resources.Hex_value}"
CharacterCasing="Lower"
GotKeyboardFocus="HexCode_GotKeyboardFocus"
MaxLength="7"
TextChanged="HexCode_TextChanged"
TextWrapping="Wrap" />
<ui:Button
x:Name="OKButton"
Grid.Row="9"
Grid.ColumnSpan="4"
Margin="0,32,0,0"
HorizontalAlignment="Stretch"
Appearance="Primary"
AutomationProperties.Name="{x:Static p:Resources.Select}"
Click="OKButton_Click"
Content="{x:Static p:Resources.Select}" />
</Grid>
</ui:Flyout>
</Grid>
</Border>
</Popup>
</Button>

View File

@@ -14,7 +14,6 @@ using System.Windows.Media.Animation;
using ColorPicker.Helpers;
using ManagedCommon;
using Wpf.Ui.Controls;
using static System.Net.Mime.MediaTypeNames;
@@ -76,9 +75,9 @@ namespace ColorPicker.Controls
control._ignoreRGBChanges = true;
control.HexCode.Text = ColorToHex(newColor);
control.RNumberBox.Value = newColor.R;
control.GNumberBox.Value = newColor.G;
control.BNumberBox.Value = newColor.B;
control.RNumberBox.Text = newColor.R.ToString(CultureInfo.InvariantCulture);
control.GNumberBox.Text = newColor.G.ToString(CultureInfo.InvariantCulture);
control.BNumberBox.Text = newColor.B.ToString(CultureInfo.InvariantCulture);
control.SetColorFromTextBoxes(System.Drawing.Color.FromArgb(newColor.R, newColor.G, newColor.B));
control._ignoreRGBChanges = false;
@@ -175,9 +174,9 @@ namespace ColorPicker.Controls
if (!_ignoreRGBChanges)
{
RNumberBox.Value = currentColor.R;
GNumberBox.Value = currentColor.G;
BNumberBox.Value = currentColor.B;
RNumberBox.Text = currentColor.R.ToString(CultureInfo.InvariantCulture);
GNumberBox.Text = currentColor.G.ToString(CultureInfo.InvariantCulture);
BNumberBox.Text = currentColor.B.ToString(CultureInfo.InvariantCulture);
}
_currentColor = currentColor;
@@ -231,7 +230,7 @@ namespace ColorPicker.Controls
{
SelectedColorChangedCommand.Execute(_currentColor);
SessionEventHelper.Event.EditorColorAdjusted = true;
DetailsFlyout.Hide();
DetailsFlyout.IsOpen = false;
}
private void DetailsFlyout_Closed(object sender, object e)
@@ -357,15 +356,19 @@ namespace ColorPicker.Controls
(sender as System.Windows.Controls.TextBox).SelectAll();
}
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !System.Text.RegularExpressions.Regex.IsMatch(e.Text, "^[0-9]+$");
}
private void RGBNumberBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (!_ignoreRGBChanges)
{
var numberBox = sender as NumberBox;
byte r = numberBox.Name == "RNumberBox" ? GetValueFromNumberBox(numberBox) : (byte)RNumberBox.Value;
byte g = numberBox.Name == "GNumberBox" ? GetValueFromNumberBox(numberBox) : (byte)GNumberBox.Value;
byte b = numberBox.Name == "BNumberBox" ? GetValueFromNumberBox(numberBox) : (byte)BNumberBox.Value;
var numberBox = sender as TextBox;
byte r = numberBox.Name == "RNumberBox" ? GetValueFromNumberBox(numberBox, _currentColor.R) : _currentColor.R;
byte g = numberBox.Name == "GNumberBox" ? GetValueFromNumberBox(numberBox, _currentColor.G) : _currentColor.G;
byte b = numberBox.Name == "BNumberBox" ? GetValueFromNumberBox(numberBox, _currentColor.B) : _currentColor.B;
_ignoreRGBChanges = true;
SetColorFromTextBoxes(System.Drawing.Color.FromArgb(r, g, b));
@@ -379,22 +382,24 @@ namespace ColorPicker.Controls
/// </summary>
/// <param name="numberBox">numberBox control which value we want to get</param>
/// <returns>Validated value as per numberbox conditions, if content is invalid it returns previous value</returns>
private static byte GetValueFromNumberBox(NumberBox numberBox)
private static byte GetValueFromNumberBox(TextBox numberBox, byte previousValue)
{
int minimum = 0;
int maximum = 255;
double? parsedValue = ParseDouble(numberBox.Text);
if (parsedValue != null)
{
var parsedValueByte = (byte)parsedValue;
if (parsedValueByte >= numberBox.Minimum && parsedValueByte <= numberBox.Maximum)
if (parsedValueByte >= minimum && parsedValueByte <= maximum)
{
return parsedValueByte;
}
}
// not valid input, return previous value
return (byte)numberBox.Value;
return previousValue;
}
public static double? ParseDouble(string text)

View File

@@ -138,14 +138,6 @@ namespace ColorPicker.Helpers
Application.Current.MainWindow.Opacity = 0;
Application.Current.MainWindow.Visibility = Visibility.Visible;
_colorPickerShown = true;
// HACK: WPF UI theme watcher removes the composition target background color, among other weird stuff.
// https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L280
// So we set it back with https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L191
// And also reapply the intended backdrop.
// This hack fixes: https://github.com/microsoft/PowerToys/issues/31725
Wpf.Ui.Controls.WindowBackdrop.RemoveBackground(Application.Current.MainWindow);
Wpf.Ui.Controls.WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, Wpf.Ui.Controls.WindowBackdropType.None);
}
}

View File

@@ -7,7 +7,6 @@ using System.Windows;
using System.Windows.Interop;
using ColorPicker.ViewModelContracts;
using Wpf.Ui.Controls;
namespace ColorPicker
{
@@ -20,7 +19,6 @@ namespace ColorPicker
{
Closing += MainWindow_Closing;
Bootstrapper.InitializeContainer(this);
Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, WindowBackdropType.None);
InitializeComponent();
DataContext = this;
Show(); // Call show just to make sure source is initialized at startup.

View File

@@ -3,6 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:ColorPicker.Converters"
xmlns:helpers="clr-namespace:ColorPicker.Helpers">
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
<!-- CONVERTERS -->
<BooleanToVisibilityConverter x:Key="bool2VisibilityConverter" />
<converters:BoolToInvertedVisibilityConverter x:Key="bool2InvertedVisibilityConverter" />
@@ -11,6 +13,102 @@
<converters:NumberToVisibilityConverter x:Key="numberToVisibilityConverter" />
<converters:NumberToInvertedVisibilityConverter x:Key="numberToInvertedVisibilityConverter" />
<!--
Copied from https://github.com/dotnet/wpf/blob/v9.0.1/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Fluent/Styles/Button.xaml
This can be removed and use AccentButtonStyle when https://github.com/dotnet/wpf/issues/10413 gets fixed.
-->
<Style x:Key="CustomAccentButtonStyle" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Border.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border
x:Name="ContentBorder"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TitleBarCloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border
x:Name="ContentBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ContentBorder" Property="Background" Value="Red" />
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SubtleButtonStyle" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />

View File

@@ -6,7 +6,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:ColorPicker.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
x:Name="colorEditorControl"
mc:Ignorable="d">
<Grid>
@@ -22,21 +21,27 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:Button
<Button
Height="36"
VerticalAlignment="Top"
AutomationProperties.Name="{x:Static p:Resources.Pick_color}"
Command="{Binding OpenColorPickerCommand}"
TabIndex="0"
ToolTipService.ToolTip="{x:Static p:Resources.Pick_color}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon FontSize="18" Symbol="Eyedropper20" />
<TextBlock Margin="8,0,0,0" Text="{x:Static p:Resources.New_Label}" />
</StackPanel>
</Button.Content>
</ui:Button>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="0,4,0,0"
FontFamily="{StaticResource IconFontFamily}"
Text="&#xef3c;" />
<!-- The label text -->
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{x:Static p:Resources.New_Label}" />
</StackPanel>
</Button>
<ListView
x:Name="HistoryColors"
@@ -67,19 +72,19 @@
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}"
Header="{x:Static p:Resources.Remove}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Delete24" />
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="&#xE74D;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static p:Resources.Export_by}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="ArrowExportLtr24" />
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="&#xEDE1;" />
</MenuItem.Icon>
<MenuItem
Command="{Binding ExportColorsGroupedByColorCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}"
Header="{x:Static p:Resources.Export_by_color}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Color20" />
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="&#xE790;" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
@@ -87,7 +92,7 @@
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}"
Header="{x:Static p:Resources.Export_by_format}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Code20" />
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="&#xE943;" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
@@ -195,7 +200,10 @@
TabIndex="2"
ToolTipService.ToolTip="{x:Static p:Resources.Open_settings}">
<Button.Content>
<ui:SymbolIcon FontSize="18" Symbol="Settings20" />
<TextBlock
FontFamily="{StaticResource IconFontFamily}"
FontSize="18"
Text="&#xE713;" />
</Button.Content>
</Button>
</Grid>
@@ -263,7 +271,11 @@
VerticalAlignment="Center"
Orientation="Vertical"
Visibility="{Binding ColorsHistory.Count, Converter={StaticResource numberToInvertedVisibilityConverter}}">
<ui:SymbolIcon FontSize="40" Symbol="Eyedropper20" />
<TextBlock
HorizontalAlignment="Center"
FontFamily="{StaticResource IconFontFamily}"
FontSize="36"
Text="&#xEF3C;" />
<TextBlock
Margin="24,16,24,16"
HorizontalAlignment="Center"
@@ -281,23 +293,22 @@
Height="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{DynamicResource {x:Static SystemColors.AccentColorBrushKey}}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
Opacity="0">
<Border.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorPrimary}" />
</Border.Background>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.LiveSetting="Assertive"
AutomationProperties.Name="{x:Static p:Resources.Copied_to_clipboard}"
Orientation="Horizontal">
<ui:SymbolIcon
<TextBlock
Margin="0,0,8,0"
FontFamily="{StaticResource IconFontFamily}"
FontSize="20"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
Symbol="Copy20" />
Text="&#xE8C8;" />
<TextBlock
FontSize="16"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"

View File

@@ -7,7 +7,6 @@
xmlns:e="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shaders="clr-namespace:ColorPicker.Shaders"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Background="Transparent"
Focusable="False"
mc:Ignorable="d">
@@ -61,7 +60,7 @@
Width="40"
Height="40"
Background="Transparent">
<ui:SymbolIcon Symbol="Dismiss20" />
Text="&#xE094;" />
</Button>
</Border>
</Grid>

View File

@@ -63,6 +63,11 @@ namespace FancyZonesEditor.Models
}
}
if (key == Properties.Resources.Quick_Key_None)
{
return true;
}
SelectedKeys[key] = uuid;
FirePropertyChanged();
return true;

View File

@@ -0,0 +1,200 @@
// 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.Globalization;
using System.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Win32;
using RegistryPreviewUILib;
namespace RegistryPreview.FuzzTests
{
public class FuzzTests
{
private const string REGISTRYHEADER4 = "regedit4";
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
private const string KEYIMAGE = "ms-appx:///Assets/RegistryPreview/folder32.png";
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/RegistryPreview/deleted-folder32.png";
// Case 1: Fuzz test for CheckKeyLineForBrackets
public static void FuzzCheckKeyLineForBrackets(ReadOnlySpan<byte> input)
{
string registryLine;
// Simulate registry file content as registryContent
var registryContent = GenerateRegistryHeader(input);
string[] registryLines = registryContent.Split("\r");
if (registryLines.Length <= 1)
{
return;
}
// REG files have to start with one of two headers and it's case-insensitive
// The header in the registry file is either REGISTRYHEADER4 or REGISTRYHEADER5
registryLine = registryLines[0];
// Check if the registry header is valid
if (!IsValidRegistryHeader(registryLine))
{
return;
}
int index = 1;
registryLine = registryLines[index]; // Extract content after the header
ParseHelper.ProcessRegistryLine(registryLine);
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
{
// remove the - as we won't need it but it will get special treatment in the UI
registryLine = registryLine.Remove(1, 1);
string imageName = DELETEDKEYIMAGE;
// Fuzz test for the CheckKeyLineForBrackets method
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
}
else if (registryLine.StartsWith('['))
{
string imageName = KEYIMAGE;
// Fuzz test for the CheckKeyLineForBrackets method
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
}
else
{
return;
}
}
// Case 2: Fuzz test for StripFirstAndLast
public static void FuzzStripFirstAndLast(ReadOnlySpan<byte> input)
{
string registryLine;
var registryContent = GenerateRegistryHeader(input);
registryContent = registryContent.Replace("\r\n", "\r");
string[] registryLines = registryContent.Split("\r");
if (registryLines.Length <= 1)
{
return;
}
// REG files have to start with one of two headers and it's case-insensitive
registryLine = registryLines[0];
if (!IsValidRegistryHeader(registryLine))
{
return;
}
int index = 1;
registryLine = registryLines[index];
ParseHelper.ProcessRegistryLine(registryLine);
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
{
// remove the - as we won't need it but it will get special treatment in the UI
registryLine = registryLine.Remove(1, 1);
string imageName = DELETEDKEYIMAGE;
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('['))
{
string imageName = KEYIMAGE;
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('"') && registryLine.EndsWith("=-", StringComparison.InvariantCulture))
{
// remove "=-"
registryLine = registryLine[..^2];
// remove the "'s without removing all of them
// Fuzz test for the StripFirstAndLast method
registryLine = ParseHelper.StripFirstAndLast(registryLine);
}
else if (registryLine.StartsWith('"'))
{
int equal = registryLine.IndexOf('=');
if ((equal < 0) || (equal > registryLine.Length - 1))
{
// something is very wrong
return;
}
// set the name and the value
string name = registryLine.Substring(0, equal);
// trim the whitespace and quotes from the name
name = name.Trim();
// Fuzz test for the StripFirstAndLast method
name = ParseHelper.StripFirstAndLast(name);
// Clean out any escaped characters in the value, only for the preview
name = ParseHelper.StripEscapedCharacters(name);
// set the value
string value = registryLine.Substring(equal + 1);
// trim the whitespace from the value
value = value.Trim();
// if the first character is a " then this is a string value, so find the last most " which will avoid comments
if (value.StartsWith('"'))
{
int last = value.LastIndexOf('"');
if (last >= 0)
{
value = value.Substring(0, last + 1);
}
}
if (value.StartsWith('"') && value.EndsWith('"'))
{
value = ParseHelper.StripFirstAndLast(value);
}
}
else
{
return;
}
}
public static string GenerateRegistryHeader(ReadOnlySpan<byte> input)
{
string header = new Random().Next(2) == 0 ? REGISTRYHEADER4 : REGISTRYHEADER5;
string inputText = System.Text.Encoding.UTF8.GetString(input);
string registryContent = header + "\r" + inputText;
return registryContent;
}
private static bool IsValidRegistryHeader(string line)
{
// Convert the line to lowercase once for comparison
switch (line)
{
case REGISTRYHEADER4:
case REGISTRYHEADER5:
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

View File

@@ -0,0 +1,90 @@
// 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.
{
"configVersion": 3,
"entries": [
{
"fuzzer": {
"$type": "libfuzzerDotNet",
"dll": "RegistryPreview.FuzzTests.dll",
"class": "RegistryPreview.FuzzTests.FuzzTests",
"method": "FuzzCheckKeyLineForBrackets",
"FuzzingTargetBinaries": [
"PowerToys.RegistryPreview.dll"
]
},
"adoTemplate": {
// supply the values appropriate to your
// project, where bugs will be filed
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
"skip": false,
"rebootAfterSetup": false,
"oneFuzzJobs": [
// at least one job is required
{
"projectName": "RegistryPreview",
"targetName": "RegistryPreview-dotnet-CheckKeyLineForBrackets-fuzzer"
}
],
"jobDependencies": [
// this should contain, at minimum,
// the DLL and PDB files
// you will need to add any other files required
// (globs are supported)
"RegistryPreview.FuzzTests.dll",
"RegistryPreview.FuzzTests.pdb",
"Microsoft.Windows.SDK.NET.dll",
"WinRT.Runtime.dll"
],
"SdlWorkItemId": 49911822
},
{
"fuzzer": {
"$type": "libfuzzerDotNet",
"dll": "RegistryPreview.FuzzTests.dll",
"class": "RegistryPreview.FuzzTests.FuzzTests",
"method": "FuzzStripFirstAndLast",
"FuzzingTargetBinaries": [
"PowerToys.RegistryPreview.dll"
]
},
"adoTemplate": {
// supply the values appropriate to your
// project, where bugs will be filed
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
"skip": false,
"rebootAfterSetup": false,
"oneFuzzJobs": [
// at least one job is required
{
"projectName": "RegistryPreview",
"targetName": "RegistryPreview-dotnet-StripFirstAndLasts-fuzzer"
}
],
"jobDependencies": [
// this should contain, at minimum,
// the DLL and PDB files
// you will need to add any other files required
// (globs are supported)
"RegistryPreview.FuzzTests.dll",
"RegistryPreview.FuzzTests.pdb",
"Microsoft.Windows.SDK.NET.dll",
"WinRT.Runtime.dll"
],
"SdlWorkItemId": 49911822
}
]
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<Platforms>x64;ARM64</Platforms>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\RegistryPreview.FuzzTests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\RegistryPreviewUILib\ParseHelper.cs" Link="ParseHelper.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="OneFuzzConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,124 @@
// 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 System.Text;
using System.Threading.Tasks;
namespace RegistryPreviewUILib
{
internal static class ParseHelper
{
private const string ERRORIMAGE = "ms-appx:///Assets/RegistryPreview/error32.png";
/// <summary>
/// Checks a Key line for the closing bracket and treat it as an error if it cannot be found
/// </summary>
internal static void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
{
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
int lastBracket = registryLine.LastIndexOf(']');
if (lastBracket == -1)
{
// since we don't have a last bracket yet, add an extra space and continue processing
registryLine += " ";
imageName = ERRORIMAGE;
}
else
{
// having found the last ] and there is text after it, drop the rest of the string on the floor
if (lastBracket < registryLine.Length - 1)
{
registryLine = registryLine.Substring(0, lastBracket + 1);
}
if (CheckForKnownGoodBranches(registryLine) == false)
{
imageName = ERRORIMAGE;
}
}
}
/// <summary>
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
/// </summary>
private static bool CheckForKnownGoodBranches(string key)
{
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_USERS]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_LOCAL_MACHINE]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_CONFIG]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKEY_CLASSES_ROOT\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_USER\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_USERS\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_LOCAL_MACHINE\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_CONFIG\", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith("[HKCR]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKLM]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCC]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKCR\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKLM\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCC\", StringComparison.InvariantCultureIgnoreCase) == false))
{
return false;
}
return true;
}
/// <summary>
/// Rip the first and last character off a string,
/// checking that the string is at least 2 characters long to avoid errors
/// </summary>
internal static string StripFirstAndLast(string line)
{
if (line.Length > 1)
{
line = line.Remove(line.Length - 1, 1);
line = line.Remove(0, 1);
}
return line;
}
/// <summary>
/// Replace any escaped characters in the REG file with their counterparts, for the UX
/// </summary>
internal static string StripEscapedCharacters(string value)
{
value = value.Replace("\\\\", "\\"); // Replace \\ with \ in the UI
value = value.Replace("\\\"", "\""); // Replace \" with " in the UI
return value;
}
// special case for when the registryLine begins with a @ - make some tweaks and
// let the regular processing handle the rest.
internal static string ProcessRegistryLine(string registryLine)
{
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
{
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
// clear the value of the @ Value instead, so it's still a valid line.
registryLine = registryLine.Replace("@=-", "\"(Default)\"=\"\"");
}
else if (registryLine.StartsWith("@=", StringComparison.InvariantCulture))
{
// This is the Value called "(Default)" so we tweak the line for the UX
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
}
return registryLine;
}
}
}

View File

@@ -967,12 +967,7 @@ namespace RegistryPreviewUILib
/// </summary>
private string StripFirstAndLast(string line)
{
if (line.Length > 1)
{
line = line.Remove(line.Length - 1, 1);
line = line.Remove(0, 1);
}
line = ParseHelper.StripFirstAndLast(line);
return line;
}
@@ -1040,27 +1035,7 @@ namespace RegistryPreviewUILib
/// </summary>
private void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
{
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
int lastBracket = registryLine.LastIndexOf(']');
if (lastBracket == -1)
{
// since we don't have a last bracket yet, add an extra space and continue processing
registryLine += " ";
imageName = ERRORIMAGE;
}
else
{
// having found the last ] and there is text after it, drop the rest of the string on the floor
if (lastBracket < registryLine.Length - 1)
{
registryLine = registryLine.Substring(0, lastBracket + 1);
}
if (CheckForKnownGoodBranches(registryLine) == false)
{
imageName = ERRORIMAGE;
}
}
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
}
/// <summary>
@@ -1079,41 +1054,6 @@ namespace RegistryPreviewUILib
return value.TrimEnd();
}
/// <summary>
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
/// </summary>
private bool CheckForKnownGoodBranches(string key)
{
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_USERS]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_LOCAL_MACHINE]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKEY_CURRENT_CONFIG]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKEY_CLASSES_ROOT\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_USER\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_USERS\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_LOCAL_MACHINE\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKEY_CURRENT_CONFIG\", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith("[HKCR]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKU]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKLM]", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith("[HKCC]", StringComparison.InvariantCultureIgnoreCase) == false)
&&
(key.StartsWith(@"[HKCR\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKU\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKLM\", StringComparison.InvariantCultureIgnoreCase) == false &&
key.StartsWith(@"[HKCC\", StringComparison.InvariantCultureIgnoreCase) == false))
{
return false;
}
return true;
}
/// <summary>
/// Turns the Open Key button in the command bar on/off, depending on if a key is selected
/// </summary>