New color picker module - integrated from github.com/martinchrzan/Col… (#4778)

* New color picker module - integrated from github.com/martinchrzan/ColorPicker

* Trying to fix build in github

* Replaced icon in the settings to use font icon instead of path icon

* Closing ColorPicker.exe when PowerToys process closed, added color picker project into runner dependencies, restoring cursors on exit, added ManagedCommon as a dependency into installer

* User/ryanbod/fix colorpicker release (#5046)

* Changing configuration to x64 instead of AnyCPU.   The previous configuration was preventing the ManagedCommon binary from being loaded in Release.

* Updating MSI Installer with new icons (#4998)

* Adding missed dll into installer

* Fixed potential exception

* Creating settings.json on the first start when there are none, fixed default keyboard shortcut

* Added ColorPicker.exe.config into installer

* Start filewatcher after default settings file is created

* Fixing build

Co-authored-by: ryanbodrug-microsoft <56318517+ryanbodrug-microsoft@users.noreply.github.com>
This commit is contained in:
martinchrzan
2020-07-18 21:27:36 +02:00
committed by GitHub
parent d09253e532
commit bc301f269a
72 changed files with 3654 additions and 6 deletions

View File

@@ -15,6 +15,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner
{0485F45C-EA7A-4BB5-804B-3E8D14699387} = {0485F45C-EA7A-4BB5-804B-3E8D14699387}
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}
{BA58206B-1493-4C75-BFEA-A85768A1E156} = {BA58206B-1493-4C75-BFEA-A85768A1E156}
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
{E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
{DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
@@ -28,6 +29,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
{07C389E3-6BC8-41CF-923E-307B1265FA2D} = {07C389E3-6BC8-41CF-923E-307B1265FA2D}
{655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {655C9AF2-18D3-4DA6-80E4-85504A7722BA}
{89F34AF7-1C34-4A72-AA6E-534BCF972BD9} = {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}
{E6410BFC-B341-498C-8C67-312C20CDD8D5} = {E6410BFC-B341-498C-8C67-312C20CDD8D5}
EndProjectSection
@@ -255,6 +257,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerTest", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedCommon", "src\common\ManagedCommon\ManagedCommon.csproj", "{4AED67B6-55FD-486F-B917-E543DEE2CB3C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ColorPicker", "src\modules\colorPicker\ColorPicker\ColorPicker.vcxproj", "{655C9AF2-18D3-4DA6-80E4-85504A7722BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPickerUI", "src\modules\colorPicker\ColorPickerUI\ColorPickerUI.csproj", "{BA58206B-1493-4C75-BFEA-A85768A1E156}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "colorpicker", "colorpicker", "{1D78B84B-CA39-406C-98F4-71F7EC266CC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Plugin.Program.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Program.UnitTests\Microsoft.Plugin.Program.UnitTests.csproj", "{42851751-CBC8-45A6-97F5-7A0753F7B4D1}"
EndProject
Global
@@ -503,6 +511,14 @@ Global
{4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.Build.0 = Debug|x64
{4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.ActiveCfg = Release|x64
{4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.Build.0 = Release|x64
{655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.ActiveCfg = Debug|x64
{655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.Build.0 = Debug|x64
{655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.ActiveCfg = Release|x64
{655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.Build.0 = Release|x64
{BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.ActiveCfg = Debug|x64
{BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64
{BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64
{BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64
{42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.ActiveCfg = Debug|x64
{42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.Build.0 = Debug|x64
{42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.ActiveCfg = Release|x64
@@ -577,6 +593,9 @@ Global
{E6410BFC-B341-498C-8C67-312C20CDD8D5} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{4AED67B6-55FD-486F-B917-E543DEE2CB3C} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
{BA58206B-1493-4C75-BFEA-A85768A1E156} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
{1D78B84B-CA39-406C-98F4-71F7EC266CC0} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{42851751-CBC8-45A6-97F5-7A0753F7B4D1} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@@ -8,6 +8,7 @@
<?define KeyboardManagerProjectName="KeyboardManager"?>
<?define PowerRenameProjectName="PowerRename"?>
<?define ShortcutGuideProjectName="ShortcutGuide"?>
<?define ColorPickerProjectName="ColorPicker"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?define BinX64Dir="$(var.RepoDir)x64\$(var.Configuration)\" ?>
@@ -241,6 +242,9 @@
<Directory Id="FileExplorerPreviewInstallFolder" Name="FileExplorerPreview" />
<Directory Id="FancyZonesInstallFolder" Name="$(var.FancyZonesProjectName)" />
<Directory Id="KeyboardManagerInstallFolder" Name="$(var.KeyboardManagerProjectName)" />
<Directory Id="ColorPickerInstallFolder" Name="$(var.ColorPickerProjectName)">
<Directory Id="ColorPickerResourcesFolder" Name="Resources"/>
</Directory>
<Directory Id="LauncherInstallFolder" Name="launcher">
<Directory Id="AssetsFolder" Name="Assets" />
<Directory Id="LauncherImagesFolder" Name="Images" />
@@ -583,6 +587,22 @@
</Component>
</DirectoryRef>
<DirectoryRef Id="ColorPickerInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)">
<Component Id="Module_ColorPicker" Guid="8A52A69E-37B2-4BEA-9D73-77763066052F" Win64="yes">
<?foreach File in ColorPicker.dll;ColorPicker.exe;ColorPicker.exe.config;Microsoft.Bcl.AsyncInterfaces.dll;Microsoft.Expression.Interactions.dll;Microsoft.PowerToys.Settings.UI.Lib.dll;PowerToysInterop.dll;System.Buffers.dll;System.Memory.dll;System.Numerics.Vectors.dll;System.Text.Encodings.Web.dll;System.Text.Json.dll;System.Threading.Tasks.Extensions.dll;System.ValueTuple.dll;System.Windows.Interactivity.dll;Telemetry.dll;ManagedCommon.dll;System.Runtime.CompilerServices.Unsafe.dll?>
<File Id="ColorPickerFile_$(var.File)" Source="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)\$(var.File)" />
<?endforeach?>
</Component>
</DirectoryRef>
<DirectoryRef Id="ColorPickerResourcesFolder" FileSource="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)\Resources">
<Component Id="Module_ColorPicker_Resources" Guid="7544BD0F-1DB6-4C53-89D3-ADAD472FDCC1">
<?foreach File in colorPicker.cur;icon.ico?>
<File Id="ColorPickerFile_$(var.File)" Source="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)\Resources\$(var.File)" />
<?endforeach?>
</Component>
</DirectoryRef>
<DirectoryRef Id="FileExplorerPreviewInstallFolder" FileSource="$(var.RepoDir)\modules\FileExplorerPreview\">
<Component Id="Module_PowerPreview" Guid="FF1700D5-1B07-4E07-9A62-4D206645EEA9" Win64="yes">
<!-- Component to include PowerPreview Module Source dll's -->
@@ -741,6 +761,8 @@
<ComponentRef Id="Module_PowerPreview" />
<ComponentRef Id="Module_PowerPreview_PerUserRegistry" />
<ComponentRef Id="Module_KeyboardManager" />
<ComponentRef Id="Module_ColorPicker" />
<ComponentRef Id="Module_ColorPicker_Resources"/>
<ComponentRef Id="SettingsV2" />
<ComponentRef Id="SettingsV2Assets" />
<ComponentRef Id="SettingsV2Controls" />

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public enum ColorRepresentationType
{
HEX = 0,
RGB = 1,
}
public class ColorPickerProperties
{
public ColorPickerProperties()
{
ActivationShortcut = new HotkeySettings(false, true, false, false, "Break", 3);
ChangeCursor = true;
}
public HotkeySettings ActivationShortcut { get; set; }
[JsonPropertyName("changecursor")]
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool ChangeCursor { get; set; }
[JsonPropertyName("copiedcolorrepresentation")]
public ColorRepresentationType CopiedColorRepresentation { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public class ColorPickerSettings : BasePTModuleSettings
{
public const string ModuleName = "ColorPicker";
public ColorPickerProperties properties { get; set; }
public ColorPickerSettings()
{
properties = new ColorPickerProperties();
version = "1";
name = ModuleName;
}
public override string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
public virtual void Save()
{
// Save settings to file
var options = new JsonSerializerOptions
{
WriteIndented = true,
};
SettingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName);
}
}
}

View File

@@ -96,6 +96,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
}
private bool keyboardManager = true;
[JsonPropertyName("Keyboard Manager")]
public bool KeyboardManager
{
@@ -124,7 +125,23 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
this.powerLauncher = value;
}
}
}
}
private bool colorPicker = true;
[JsonPropertyName("ColorPicker")]
public bool ColorPicker
{
get => this.colorPicker;
set
{
if (this.colorPicker != value)
{
LogTelemetryEvent(value);
this.colorPicker = value;
}
}
}
public string ToJsonString()
{

View File

@@ -111,6 +111,7 @@
<Compile Include="Helpers\ResourceExtensions.cs" />
<Compile Include="Services\ActivationService.cs" />
<Compile Include="Services\NavigationService.cs" />
<Compile Include="ViewModels\ColorPickerViewModel.cs" />
<Compile Include="ViewModels\Commands\ButtonClickCommand.cs" />
<Compile Include="ViewModels\GeneralViewModel.cs" />
<Compile Include="ViewModels\FancyZonesViewModel.cs" />
@@ -121,6 +122,9 @@
<Compile Include="ViewModels\PowerLauncherViewModel.cs" />
<Compile Include="ViewModels\ShellViewModel.cs" />
<Compile Include="ViewModels\ShortcutGuideViewModel.cs" />
<Compile Include="Views\ColorPickerPage.xaml.cs">
<DependentUpon>ColorPickerPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\GeneralPage.xaml.cs">
<DependentUpon>GeneralPage.xaml</DependentUpon>
</Compile>
@@ -241,6 +245,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\ColorPickerPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\GeneralPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@@ -157,6 +157,10 @@
<value>Image Resizer</value>
<comment>Navigation view item name for Image Resizer</comment>
</data>
<data name="Shell_ColorPicker.Content" xml:space="preserve">
<value>Color Picker</value>
<comment>Navigation view item name for Color Picker</comment>
</data>
<data name="Shell_KeyboardManager.Content" xml:space="preserve">
<value>Keyboard Manager</value>
<comment>Navigation view item name for Keyboard Manager</comment>
@@ -193,6 +197,21 @@
<value>Remap shortcuts</value>
<comment>Keyboard Manager remap shortcuts header</comment>
</data>
<data name="ColorPicker_Description.Text" xml:space="preserve">
<value>Quick and simple system-wide color picker</value>
</data>
<data name="ColorPicker_EnableColorPicker.Header" xml:space="preserve">
<value>Enable Color Picker</value>
</data>
<data name="ColorPicker_ChangeCursor.Header" xml:space="preserve">
<value>Change cursor when picking a color</value>
</data>
<data name="ColorPicker_CopiedColorRepresentation.Header" xml:space="preserve">
<value>Copied color representation</value>
</data>
<data name="ColorPicker_ActivationShortcut.Header" xml:space="preserve">
<value>Activation shortcut</value>
</data>
<data name="PowerLauncher_Description.Text" xml:space="preserve">
<value>A quick launcher that has additional capabilities without sacrificing performance.</value>
</data>
@@ -569,6 +588,9 @@
<data name="About_KeyboardManager.Text" xml:space="preserve">
<value>About Keyboard Manager</value>
</data>
<data name="About_ColorPicker.Text" xml:space="preserve">
<value>About Color Picker</value>
</data>
<data name="About_PowerLauncher.Text" xml:space="preserve">
<value>About PowerToys Run</value>
</data>

View File

@@ -0,0 +1,121 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Views;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class ColorPickerViewModel : Observable
{
private ColorPickerSettings _colorPickerSettings;
private bool _isEnabled;
public ColorPickerViewModel()
{
if (SettingsUtils.SettingsExists(ColorPickerSettings.ModuleName))
{
_colorPickerSettings = SettingsUtils.GetSettings<ColorPickerSettings>(ColorPickerSettings.ModuleName);
}
else
{
_colorPickerSettings = new ColorPickerSettings();
}
if (SettingsUtils.SettingsExists())
{
var generalSettings = SettingsUtils.GetSettings<GeneralSettings>();
_isEnabled = generalSettings.Enabled.ColorPicker;
}
}
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
// grab the latest version of settings
var generalSettings = SettingsUtils.GetSettings<GeneralSettings>();
generalSettings.Enabled.ColorPicker = value;
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettings);
ShellPage.DefaultSndMSGCallback(outgoing.ToString());
}
}
}
public bool ChangeCursor
{
get
{
return _colorPickerSettings.properties.ChangeCursor;
}
set
{
if (_colorPickerSettings.properties.ChangeCursor != value)
{
_colorPickerSettings.properties.ChangeCursor = value;
OnPropertyChanged(nameof(ChangeCursor));
NotifySettingsChanged();
}
}
}
public HotkeySettings ActivationShortcut
{
get
{
return _colorPickerSettings.properties.ActivationShortcut;
}
set
{
if (_colorPickerSettings.properties.ActivationShortcut != value)
{
_colorPickerSettings.properties.ActivationShortcut = value;
OnPropertyChanged(nameof(ActivationShortcut));
NotifySettingsChanged();
}
}
}
public int CopiedColorRepresentationIndex
{
get
{
return (int)_colorPickerSettings.properties.CopiedColorRepresentation;
}
set
{
if (_colorPickerSettings.properties.CopiedColorRepresentation != (ColorRepresentationType)value)
{
_colorPickerSettings.properties.CopiedColorRepresentation = (ColorRepresentationType)value;
OnPropertyChanged(nameof(CopiedColorRepresentationIndex));
NotifySettingsChanged();
}
}
}
private void NotifySettingsChanged()
{
ShellPage.DefaultSndMSGCallback(
string.Format("{{ \"powertoys\": {{ \"{0}\": {1} }} }}", ColorPickerSettings.ModuleName, JsonSerializer.Serialize(_colorPickerSettings)));
}
}
}

View File

@@ -0,0 +1,107 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Views.ColorPickerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Custom="using:Microsoft.PowerToys.Settings.UI.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid ColumnSpacing="{StaticResource DefaultColumnSpacing}" RowSpacing="{StaticResource DefaultRowSpacing}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutVisualStates">
<VisualState x:Name="WideLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource WideLayoutMinWidth}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="1" />
<Setter Target="SidePanel.(Grid.Row)" Value="0" />
<Setter Target="SidePanel.Width" Value="{StaticResource SidePanelWidth}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="SmallLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource SmallLayoutMinWidth}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="0" />
<Setter Target="SidePanel.(Grid.Row)" Value="1" />
<Setter Target="SidePanel.Width" Value="Auto" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="ColorPicker_Description"
TextWrapping="Wrap"/>
<ToggleSwitch x:Uid="ColorPicker_EnableColorPicker"
IsOn="{Binding IsEnabled, Mode=TwoWay}"
Margin="{StaticResource MediumTopMargin}"/>
<Custom:HotkeySettingsControl x:Uid="ColorPicker_ActivationShortcut"
Width="240"
HorizontalAlignment="Left"
Margin="{StaticResource SmallTopMargin}"
HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}"/>
<ComboBox x:Uid="ColorPicker_CopiedColorRepresentation"
SelectedIndex="{Binding CopiedColorRepresentationIndex, Mode=TwoWay}"
Width="240"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}">
<ComboBoxItem Content="HEX - #FFAA00"/>
<ComboBoxItem Content="RGB - RGB(100, 50, 75)"/>
</ComboBox>
<ToggleSwitch x:Uid="ColorPicker_ChangeCursor"
IsOn="{Binding ChangeCursor, Mode=TwoWay}"
Margin="{StaticResource MediumTopMargin}"
IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
<StackPanel
x:Name="SidePanel"
Orientation="Vertical"
HorizontalAlignment="Left"
Width="{StaticResource SidePanelWidth}"
Grid.Column="1">
<TextBlock
x:Uid="About_ColorPicker"
Style="{StaticResource SettingsGroupTitleStyle}"
Margin="{StaticResource XSmallBottomMargin}"/>
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_ColorPicker">
<TextBlock x:Uid="Module_overview" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/issues">
<TextBlock x:Uid="Give_Feedback" />
</HyperlinkButton>
<TextBlock
x:Uid="AttributionTitle"
Style="{StaticResource SettingsGroupTitleStyle}" />
<HyperlinkButton
NavigateUri="https://github.com/martinchrzan/ColorPicker/">
<TextBlock Text="Martin Chrzan's Color Picker" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</Grid>
</Page>

View File

@@ -0,0 +1,17 @@
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Windows.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class ColorPickerPage : Page
{
public ColorPickerViewModel ViewModel { get; set; }
public ColorPickerPage()
{
ViewModel = new ColorPickerViewModel();
DataContext = ViewModel;
InitializeComponent();
}
}
}

View File

@@ -94,6 +94,12 @@
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<winui:NavigationViewItem x:Uid="Shell_ColorPicker" helpers:NavHelper.NavigateTo="views:ColorPickerPage">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEF3C;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
</winui:NavigationView.MenuItems>
<i:Interaction.Behaviors>
<behaviors:NavigationViewHeaderBehavior

View File

@@ -159,6 +159,7 @@
<Compile Include="Generated Files\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="ViewModelTests\ColorPicker.cs" />
<Compile Include="ViewModelTests\FancyZones.cs" />
<Compile Include="ViewModelTests\General.cs" />
<Compile Include="ModelsTests\HelperTest.cs" />

View File

@@ -0,0 +1,59 @@
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using System.Text.Json;
namespace ViewModelTests
{
[TestClass]
public class ColorPicker
{
private const string ModuleName = "ColorPicker";
[TestInitialize]
public void Setup()
{
var generalSettings = new GeneralSettings();
var colorPickerSettings = new ColorPickerSettings();
SettingsUtils.SaveSettings(generalSettings.ToJsonString());
SettingsUtils.SaveSettings(colorPickerSettings.ToJsonString(), colorPickerSettings.name, ModuleName + ".json");
}
[TestCleanup]
public void CleanUp()
{
string generalSettings_file_name = string.Empty;
if (SettingsUtils.SettingsFolderExists(generalSettings_file_name))
{
DeleteFolder(generalSettings_file_name);
}
if (SettingsUtils.SettingsFolderExists(ModuleName))
{
DeleteFolder(ModuleName);
}
}
[TestMethod]
public void ColorPickerIsEnabledByDefault()
{
var viewModel = new ColorPickerViewModel();
ShellPage.DefaultSndMSGCallback = msg =>
{
OutGoingGeneralSettings snd = JsonSerializer.Deserialize<OutGoingGeneralSettings>(msg);
Assert.IsTrue(snd.GeneralSettings.Enabled.ColorPicker);
};
Assert.IsTrue(viewModel.IsEnabled);
}
private static void DeleteFolder(string powertoy)
{
Directory.Delete(Path.Combine(SettingsUtils.LocalApplicationDataFolder(), $"Microsoft\\PowerToys\\{powertoy}"), true);
}
}
}

View File

@@ -80,7 +80,6 @@ namespace ViewModelTests
viewModel.OpenPowerLauncher = openPowerLauncher;
viewModel.OpenFileLocation = openFileLocation;
viewModel.OpenConsole = openConsole;
viewModel.CopyPathLocation = copyFileLocation;
Assert.AreEqual(4, sendCallbackMock.TimesSent);

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{655c9af2-18d3-4da6-80e4-85504a7722ba}</ProjectGuid>
<RootNamespace>ColorPicker</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
<ProjectName>ColorPicker</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\$(ProjectName)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\$(ProjectName)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;COLORPICKER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(CIBuild)'!='true'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200514.2\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,188 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <common/common.h>
#include <interface/powertoy_module_interface.h>
#include "trace.h"
#include <common\settings_objects.h>
#include <common\os-detection\os-detect.h>
extern "C" IMAGE_DOS_HEADER __ImageBase;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
struct ModuleSettings{
} g_settings;
class ColorPicker : public PowertoyModuleIface
{
private:
bool m_enabled = false;
std::wstring app_name;
HANDLE m_hProcess;
// Time to wait for process to close after sending WM_CLOSE signal
static const int MAX_WAIT_MILLISEC = 10000;
public:
ColorPicker()
{
app_name = L"ColorPicker";
}
~ColorPicker()
{
if (m_enabled)
{
}
m_enabled = false;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the display name of the powertoy, this will be cached by the runner
virtual const wchar_t* get_name() override
{
return app_name.c_str();
}
virtual const wchar_t** get_events() override
{
static const wchar_t* events[] = { nullptr };
return events;
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(L"Color picker");
// settings.set_description(GET_RESOURCE_STRING(IDS_LAUNCHER_SETTINGS_DESC));
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_ColorPicker");
return settings.serialize_to_buffer(buffer, buffer_size);
}
virtual void call_custom_action(const wchar_t* action) override
{
}
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config);
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
values.save_to_settings_file();
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception ex)
{
// Improper JSON.
}
}
virtual void enable(){
// use only with new settings?
if (UseNewSettings())
{
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\ColorPicker\\ColorPicker.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
ShellExecuteExW(&sei);
m_hProcess = sei.hProcess;
m_enabled = true;
}
};
virtual void disable() {
if (m_enabled)
{
terminateProcess();
}
m_enabled = false;
}
virtual bool is_enabled() override
{
return m_enabled;
}
virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override
{
return 0;
}
static BOOL CALLBACK requestMainWindowClose(HWND nextWindow, LPARAM closePid)
{
DWORD windowPid;
GetWindowThreadProcessId(nextWindow, &windowPid);
if (windowPid == (DWORD)closePid)
::PostMessage(nextWindow, WM_CLOSE, 0, 0);
return true;
}
void terminateProcess()
{
DWORD processID = GetProcessId(m_hProcess);
EnumWindows(&requestMainWindowClose, processID);
const DWORD result = WaitForSingleObject(m_hProcess, MAX_WAIT_MILLISEC);
if (result == WAIT_TIMEOUT)
{
TerminateProcess(m_hProcess, 1);
}
}
/* Register helper class to handle system menu items related actions. */
virtual void register_system_menu_helper(PowertoySystemMenuIface* helper) {}
/* Handle action on system menu item. */
virtual void signal_system_menu_action(const wchar_t* name) {}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new ColorPicker();
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200514.2" targetFramework="native" />
</packages>

View File

@@ -0,0 +1,4 @@
#include "pch.h"
#pragma comment(lib, "windowsapp")
#pragma comment(lib, "shlwapi.lib")

View File

@@ -0,0 +1,8 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <common/common.h>
#include <ProjectTelemetry.h>
#include <shellapi.h>
#include <Shlwapi.h>

View File

@@ -0,0 +1,29 @@
#include "pch.h"
#include "trace.h"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider()
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider()
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::MyEvent()
{
TraceLoggingWrite(
g_hProvider,
"PowerToyName::Event::MyEvent",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -0,0 +1,8 @@
#pragma once
class Trace
{
public:
static void RegisterProvider();
static void UnregisterProvider();
static void MyEvent();
};

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@@ -0,0 +1,78 @@
<?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="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
PerMonitor
</dpiAwareness>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@@ -0,0 +1,14 @@
<Application x:Class="ColorPickerUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ColorPickerUI"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles.xaml"/>
<ResourceDictionary Source="Resources/ViewModelViewMappings.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,76 @@
using System;
using System.Threading;
using System.Windows;
using ColorPicker.Helpers;
using ColorPicker.Mouse;
using ManagedCommon;
namespace ColorPickerUI
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private Mutex _instanceMutex = null;
private static string[] _args;
private int _powerToysPid;
[STAThread]
public static void Main(string[] args)
{
_args = args;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
try
{
var application = new App();
application.InitializeComponent();
application.Run();
}
catch (Exception ex)
{
Logger.LogError("Unhandled exception", ex);
CursorManager.RestoreOriginalCursors();
}
}
protected override void OnStartup(StartupEventArgs e)
{
// allow only one instance of color picker
bool createdNew;
_instanceMutex = new Mutex(true, @"Global\ColorPicker", out createdNew);
if (!createdNew)
{
_instanceMutex = null;
Application.Current.Shutdown();
return;
}
if(_args.Length > 0)
{
_ = int.TryParse(_args[0], out _powerToysPid);
}
RunnerHelper.WaitForPowerToysRunner(_powerToysPid, () => {
Environment.Exit(0);
});
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
if (_instanceMutex != null)
_instanceMutex.ReleaseMutex();
CursorManager.RestoreOriginalCursors();
base.OnExit(e);
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.LogError("Unhandled exception", (e.ExceptionObject is Exception) ? (e.ExceptionObject as Exception) : new Exception());
CursorManager.RestoreOriginalCursors();
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;
namespace ColorPicker.Behaviors
{
public class AppearAnimationBehavior : Behavior<Window>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
Appear();
AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;
}
private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (AssociatedObject.IsVisible)
{
Appear();
}
else
{
Hide();
}
}
private void Appear()
{
var opacityAppear = new DoubleAnimation(0, 1.0, new Duration(TimeSpan.FromMilliseconds(250)));
opacityAppear.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
var resize = new DoubleAnimation(0, 180, new Duration(TimeSpan.FromMilliseconds(250)));
resize.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
AssociatedObject.BeginAnimation(Window.OpacityProperty, opacityAppear);
AssociatedObject.BeginAnimation(Window.WidthProperty, resize);
}
private void Hide()
{
var opacityAppear = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(1)));
var resize = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(1)));
AssociatedObject.BeginAnimation(Window.OpacityProperty, opacityAppear);
AssociatedObject.BeginAnimation(Window.WidthProperty, resize);
}
}
}

View File

@@ -0,0 +1,85 @@
using System.Windows;
using System.Windows.Interactivity;
using ColorPicker.Helpers;
using ColorPicker.Mouse;
namespace ColorPicker.Behaviors
{
public class ChangeWindowPositionBehavior : Behavior<Window>
{
// color window should not get into these zones, only mouse to avoid window getting outsize of monitor
private const int MonitorRightSideDeadZone = 200;
private const int MonitorBottomSideDeadZone = 100;
private const int YOffset = 10;
private const int XOffset = 5;
private Point _lastMousePosition;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
var mouseInfoProvider = Bootstrapper.Container.GetExportedValue<IMouseInfoProvider>();
SetWindowPosition(mouseInfoProvider.CurrentPosition);
mouseInfoProvider.MousePositionChanged += (s, mousePosition) =>
{
SetWindowPosition(mousePosition);
};
AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;
}
private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
SetWindowPosition(_lastMousePosition);
}
}
private void SetWindowPosition(Point mousePosition)
{
_lastMousePosition = mousePosition;
var dpi = MonitorResolutionHelper.GetCurrentMonitorDpi();
var mousePositionScaled = new Point(mousePosition.X / dpi.DpiScaleX, mousePosition.Y / dpi.DpiScaleX);
var monitorBounds = GetBoundsOfMonitorWithMouseIn(mousePosition);
var windowLeft = mousePositionScaled.X + XOffset;
var windowTop = mousePositionScaled.Y + YOffset;
if ((windowLeft + MonitorRightSideDeadZone) > monitorBounds.Right)
{
windowLeft -= MonitorRightSideDeadZone - ((int)monitorBounds.Right - windowLeft);
}
if ((windowTop + MonitorBottomSideDeadZone / dpi.DpiScaleX) > monitorBounds.Bottom / dpi.DpiScaleX)
{
windowTop -= MonitorBottomSideDeadZone / dpi.DpiScaleX - (((int)monitorBounds.Bottom / dpi.DpiScaleX - windowTop));
}
AssociatedObject.Left = windowLeft;
AssociatedObject.Top = windowTop;
}
private static Rect GetBoundsOfMonitorWithMouseIn(Point mousePosition)
{
foreach (var monitor in MonitorResolutionHelper.AllMonitors)
{
if (monitor.Bounds.Contains(new Point(mousePosition.X, mousePosition.Y)))
{
return monitor.Bounds;
}
}
Logger.LogWarning("Failed to get monitor bounds for mouse position" + mousePosition.X + "," + mousePosition.Y);
return new Rect(0, 0, 0, 0);
}
}
}

View File

@@ -0,0 +1,37 @@
using ColorPicker.Helpers;
using System.Windows;
using System.Windows.Interactivity;
namespace ColorPicker.Behaviors
{
public class CloseZoomWindowBehavior : Behavior<Window>
{
private ZoomWindowHelper _zoomWindowHelper;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
_zoomWindowHelper = Bootstrapper.Container.GetExportedValue<ZoomWindowHelper>();
}
private void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_zoomWindowHelper.CloseZoomWindow();
}
private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Escape)
{
_zoomWindowHelper.CloseZoomWindow();
}
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;
namespace ColorPicker.Behaviors
{
public class MoveWindowBehavior : Behavior<Window>
{
public static DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(double), typeof(MoveWindowBehavior), new PropertyMetadata(new PropertyChangedCallback(LeftPropertyChanged)));
private static void LeftPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var sender = ((MoveWindowBehavior)d).AssociatedObject;
var move = new DoubleAnimation(sender.Left, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
sender.BeginAnimation(Window.LeftProperty, move, HandoffBehavior.SnapshotAndReplace);
}
public static DependencyProperty TopProperty = DependencyProperty.Register("Top", typeof(double), typeof(MoveWindowBehavior), new PropertyMetadata(new PropertyChangedCallback(TopPropertyChanged)));
private static void TopPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var sender = ((MoveWindowBehavior)d).AssociatedObject;
var move = new DoubleAnimation(sender.Top, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
sender.BeginAnimation(Window.TopProperty, move, HandoffBehavior.SnapshotAndReplace);
}
public double Left
{
get
{
return (double)GetValue(LeftProperty);
}
set
{
SetValue(LeftProperty, value);
}
}
public double Top
{
get
{
return (double)GetValue(TopProperty);
}
set
{
SetValue(TopProperty, value);
}
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;
namespace ColorPicker.Behaviors
{
public class ResizeBehavior : Behavior<FrameworkElement>
{
public static DependencyProperty WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(WidthPropertyChanged)));
private static void WidthPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var sender = ((ResizeBehavior)d).AssociatedObject;
var move = new DoubleAnimation(sender.Width, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
move.Completed += (s, e1) => {
sender.BeginAnimation(FrameworkElement.WidthProperty, null); sender.Width = (double)e.NewValue;
};
move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
sender.BeginAnimation(FrameworkElement.WidthProperty, move, HandoffBehavior.SnapshotAndReplace);
}
public static DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(HeightPropertyChanged)));
private static void HeightPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var sender = ((ResizeBehavior)d).AssociatedObject;
var move = new DoubleAnimation(sender.Height, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
move.Completed += (s, e1) => { sender.BeginAnimation(FrameworkElement.HeightProperty, null); sender.Height = (double)e.NewValue; };
move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
sender.BeginAnimation(FrameworkElement.HeightProperty, move, HandoffBehavior.SnapshotAndReplace);
}
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
}
}
public double Height
{
get
{
return (double)GetValue(HeightProperty);
}
set
{
SetValue(HeightProperty, value);
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace ColorPicker
{
public static class Bootstrapper
{
public static CompositionContainer Container { get; private set; }
public static void InitializeContainer(object initPoint)
{
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
Container = new CompositionContainer(catalog);
Container.SatisfyImportsOnce(initPoint);
}
public static void Dispose()
{
Container.Dispose();
}
}
}

View File

@@ -0,0 +1,186 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BA58206B-1493-4C75-BFEA-A85768A1E156}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>ColorPicker</RootNamespace>
<AssemblyName>ColorPicker</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>App.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\..\..\..\x64\Debug\modules\ColorPicker\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>..\..\..\..\x64\Release\modules\ColorPicker\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="Settings\IUserSettings.cs" />
<Compile Include="Settings\SettingItem.cs" />
<Compile Include="Settings\UserSettings.cs" />
<Compile Include="Win32Apis.cs" />
<Compile Include="ZoomWindow.xaml.cs">
<DependentUpon>ZoomWindow.xaml</DependentUpon>
</Compile>
<Page Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="Bootstrapper.cs" />
<Compile Include="ViewModelContracts\IMainViewModel.cs" />
<Compile Include="ViewModelContracts\IZoomViewModel.cs" />
<Compile Include="ViewModels\MainViewModel.cs" />
<Compile Include="ViewModels\ZoomViewModel.cs" />
<Compile Include="Views\MainView.xaml.cs">
<DependentUpon>MainView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ZoomView.xaml.cs">
<DependentUpon>ZoomView.xaml</DependentUpon>
</Compile>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Behaviors\AppearAnimationBehavior.cs" />
<Compile Include="Behaviors\ChangeWindowPositionBehavior.cs" />
<Compile Include="Behaviors\CloseZoomWindowBehavior.cs" />
<Compile Include="Behaviors\MoveWindowBehavior.cs" />
<Compile Include="Behaviors\ResizeBehavior.cs" />
<Compile Include="Common\RelayCommand.cs" />
<Compile Include="Common\ViewModelBase.cs" />
<Compile Include="Helpers\AppStateHandler.cs" />
<Compile Include="Helpers\Logger.cs" />
<Compile Include="Helpers\MonitorResolutionHelper.cs" />
<Compile Include="Helpers\ZoomWindowHelper.cs" />
<Compile Include="Keyboard\GlobalKeyboardHook.cs" />
<Compile Include="Keyboard\GlobalKeyboardHookEventArgs.cs" />
<Compile Include="Keyboard\KeyboardMonitor.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="Resources\Styles.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Resources\ViewModelViewMappings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ZoomView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ZoomWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Mouse\CursorManager.cs" />
<Compile Include="Mouse\IMouseInfoProvider.cs" />
<Compile Include="Mouse\MouseHook.cs" />
<Compile Include="Mouse\MouseInfoProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="App.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Windows.Interactivity.WPF">
<Version>2.0.20525</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="Resources\colorPicker.cur">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Resources\icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj">
<Project>{4AED67B6-55FD-486F-B917-E543DEE2CB3C}</Project>
<Name>ManagedCommon</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\core\Microsoft.PowerToys.Settings.UI.Lib\Microsoft.PowerToys.Settings.UI.Lib.csproj">
<Project>{b1bcc8c6-46b5-4bfa-8f22-20f32d99ec6a}</Project>
<Name>Microsoft.PowerToys.Settings.UI.Lib</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,36 @@
using System;
using System.Windows.Input;
namespace ColorPicker.Common
{
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Action execute)
{
_canExecute = x => true;
_execute = x => { execute.Invoke(); };
}
public RelayCommand(Action<object> execute) : this(x => true, execute)
{
}
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ColorPicker.Common
{
/// <summary>
/// Base for view models to provide property changed notifications
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.ComponentModel.Composition;
using System.Windows;
namespace ColorPicker.Helpers
{
[Export(typeof(AppStateHandler))]
public class AppStateHandler
{
[ImportingConstructor]
public AppStateHandler()
{
Application.Current.MainWindow.Closed += MainWindow_Closed;
}
public event EventHandler AppShown;
public event EventHandler AppHidden;
public event EventHandler AppClosed;
public void ShowColorPicker()
{
AppShown?.Invoke(this, EventArgs.Empty);
Application.Current.MainWindow.Opacity = 0;
Application.Current.MainWindow.Visibility = Visibility.Visible;
}
public void HideColorPicker()
{
Application.Current.MainWindow.Opacity = 0;
Application.Current.MainWindow.Visibility = Visibility.Collapsed;
AppHidden?.Invoke(this, EventArgs.Empty);
}
public static void SetTopMost()
{
Application.Current.MainWindow.Topmost = false;
Application.Current.MainWindow.Topmost = true;
}
private void MainWindow_Closed(object sender, EventArgs e)
{
AppClosed?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
namespace ColorPicker.Helpers
{
public static class Logger
{
private static string ApplicationLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ColorPicker");
static Logger()
{
if (!Directory.Exists(ApplicationLogPath))
{
Directory.CreateDirectory(ApplicationLogPath);
}
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.CurrentCulture) + ".txt");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
}
public static void LogError(string message)
{
Log(message, "ERROR");
}
public static void LogError(string message, Exception ex)
{
Log(message + Environment.NewLine +
ex?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
ex?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
ex?.StackTrace,
"ERROR");
}
public static void LogWarning(string message)
{
Log(message, "WARNING");
}
public static void LogInfo(string message)
{
Log(message, "INFO");
}
private static void Log(string message, string type)
{
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
Trace.Indent();
Trace.WriteLine(GetCallerInfo());
Trace.WriteLine(message);
Trace.Unindent();
}
private static string GetCallerInfo()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType.Name;
return "[Method]: " + methodName.Name + " [Class]: " + className;
}
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using static ColorPicker.Win32Apis;
namespace ColorPicker.Helpers
{
public class MonitorResolutionHelper
{
public static readonly HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
private MonitorResolutionHelper(IntPtr monitor, IntPtr hdc)
{
var info = new MonitorInfoEx();
GetMonitorInfo(new HandleRef(null, monitor), info);
Bounds = new System.Windows.Rect(
info.rcMonitor.left,
info.rcMonitor.top,
info.rcMonitor.right - info.rcMonitor.left,
info.rcMonitor.bottom - info.rcMonitor.top);
WorkingArea = new System.Windows.Rect(
info.rcWork.left,
info.rcWork.top,
info.rcWork.right - info.rcWork.left,
info.rcWork.bottom - info.rcWork.top);
IsPrimary = (info.dwFlags & MonitorinfofPrimary) != 0;
Name = new string(info.szDevice).TrimEnd((char)0);
}
public static DpiScale GetCurrentMonitorDpi()
{
return VisualTreeHelper.GetDpi(Application.Current.MainWindow);
}
public static IEnumerable<MonitorResolutionHelper> AllMonitors
{
get
{
var closure = new MonitorEnumCallback();
var proc = new MonitorEnumProc(closure.Callback);
EnumDisplayMonitors(NullHandleRef, IntPtr.Zero, proc, IntPtr.Zero);
return closure.Monitors.Cast<MonitorResolutionHelper>();
}
}
public System.Windows.Rect Bounds { get; private set; }
public System.Windows.Rect WorkingArea { get; private set; }
public string Name { get; private set; }
public bool IsPrimary { get; private set; }
public static bool HasMultipleMonitors()
{
return AllMonitors.Count() > 1;
}
private class MonitorEnumCallback
{
public MonitorEnumCallback()
{
Monitors = new ArrayList();
}
public ArrayList Monitors { get; private set; }
public bool Callback(
IntPtr monitor,
IntPtr hdc,
IntPtr lprcMonitor,
IntPtr lparam)
{
Monitors.Add(new MonitorResolutionHelper(monitor, hdc));
return true;
}
}
}
}

View File

@@ -0,0 +1,196 @@
using ColorPicker.ViewModelContracts;
using System;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace ColorPicker.Helpers
{
[Export(typeof(ZoomWindowHelper))]
public class ZoomWindowHelper
{
private const int ZoomFactor = 2;
private const int BaseZoomImageSize = 50;
private const int MaxZoomLevel = 3;
private const int MinZoomLevel = 0;
private int _currentZoomLevel = 0;
private int _previousZoomLevel = 0;
private readonly IZoomViewModel _zoomViewModel;
private readonly AppStateHandler _appStateHandler;
private ZoomWindow _zoomWindow;
private double _lastLeft;
private double _lastTop;
private double _previousScaledX;
private double _previousScaledY;
[ImportingConstructor]
public ZoomWindowHelper(IZoomViewModel zoomViewModel, AppStateHandler appStateHandler)
{
_zoomViewModel = zoomViewModel;
_appStateHandler = appStateHandler;
_appStateHandler.AppClosed += AppStateHandler_AppClosed;
_appStateHandler.AppHidden += AppStateHandler_AppClosed;
}
public void Zoom(System.Windows.Point position, bool zoomIn)
{
if (zoomIn && _currentZoomLevel < MaxZoomLevel)
{
_previousZoomLevel = _currentZoomLevel;
_currentZoomLevel++;
}
else if (!zoomIn && _currentZoomLevel > MinZoomLevel)
{
_previousZoomLevel = _currentZoomLevel;
_currentZoomLevel--;
}
else
{
return;
}
SetZoomImage(position);
}
public void CloseZoomWindow()
{
_currentZoomLevel = 0;
_previousZoomLevel = 0;
HideZoomWindow();
}
private void SetZoomImage(System.Windows.Point point)
{
if (_currentZoomLevel == 0)
{
HideZoomWindow();
return;
}
// we just started zooming, copy screen area
if (_previousZoomLevel == 0)
{
var x = (int)point.X - BaseZoomImageSize / 2;
var y = (int)point.Y - BaseZoomImageSize / 2;
var rect = new Rectangle(x, y, BaseZoomImageSize, BaseZoomImageSize);
var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
var g = Graphics.FromImage(bmp);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
var bitmapImage = BitmapToImageSource(bmp);
_zoomViewModel.ZoomArea = bitmapImage;
_zoomViewModel.ZoomFactor = 1;
}
else
{
var enlarge = (_currentZoomLevel - _previousZoomLevel) > 0 ? true : false;
var currentZoomFactor = enlarge ? ZoomFactor : 1.0 / ZoomFactor;
_zoomViewModel.ZoomFactor *= currentZoomFactor;
}
ShowZoomWindow((int)point.X, (int)point.Y);
}
private BitmapSource BitmapToImageSource(Bitmap bitmap)
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.StreamSource = memory;
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.EndInit();
return bitmapimage;
}
}
private void HideZoomWindow()
{
if (_zoomWindow != null)
{
_zoomWindow.Hide();
}
}
private void ShowZoomWindow(int x, int y)
{
if (_zoomWindow == null)
{
_zoomWindow = new ZoomWindow();
_zoomWindow.Content = _zoomViewModel;
_zoomWindow.Loaded += ZoomWindow_Loaded;
_zoomWindow.IsVisibleChanged += ZoomWindow_IsVisibleChanged;
}
// we just started zooming, remember where we opened zoom window
if (_currentZoomLevel == 1 && _previousZoomLevel == 0)
{
var dpi = MonitorResolutionHelper.GetCurrentMonitorDpi();
_previousScaledX = x / dpi.DpiScaleX;
_previousScaledY = y / dpi.DpiScaleY;
}
_lastLeft = Math.Floor(_previousScaledX - ((BaseZoomImageSize * Math.Pow(ZoomFactor, _currentZoomLevel - 1)) / 2));
_lastTop = Math.Floor(_previousScaledY - ((BaseZoomImageSize * Math.Pow(ZoomFactor, _currentZoomLevel - 1)) / 2));
var justShown = false;
if (!_zoomWindow.IsVisible)
{
_zoomWindow.Left = _lastLeft;
_zoomWindow.Top = _lastTop;
_zoomViewModel.Height = BaseZoomImageSize;
_zoomViewModel.Width = BaseZoomImageSize;
_zoomWindow.Show();
justShown = true;
// make sure color picker window is on top of just opened zoom window
AppStateHandler.SetTopMost();
}
// dirty hack - sometimes when we just show a window on a second monitor with different DPI,
// window position is not set correctly on a first time, we need to "ping" it again to make it appear on the proper location
if (justShown)
{
_zoomWindow.Left = _lastLeft + 1;
_zoomWindow.Top = _lastTop + 1;
}
_zoomWindow.DesiredLeft = _lastLeft;
_zoomWindow.DesiredTop = _lastTop;
_zoomViewModel.DesiredHeight = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
_zoomViewModel.DesiredWidth = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
}
private void ZoomWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// need to set at this point again, to avoid issues moving between screens with different scaling
if ((bool)e.NewValue)
{
_zoomWindow.Left = _lastLeft;
_zoomWindow.Top = _lastTop;
}
}
private void ZoomWindow_Loaded(object sender, RoutedEventArgs e)
{
// need to call it again at load time, because it does was not dpi aware at the first time of Show() call
_zoomWindow.Left = _lastLeft;
_zoomWindow.Top = _lastTop;
}
private void AppStateHandler_AppClosed(object sender, EventArgs e)
{
CloseZoomWindow();
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using static ColorPicker.Win32Apis;
namespace ColorPicker.Keyboard
{
internal class GlobalKeyboardHook : IDisposable
{
private IntPtr _windowsHookHandle;
private IntPtr _user32LibraryHandle;
private HookProc _hookProc;
public GlobalKeyboardHook()
{
_windowsHookHandle = IntPtr.Zero;
_user32LibraryHandle = IntPtr.Zero;
_hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.
_user32LibraryHandle = LoadLibrary("User32");
if (_user32LibraryHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
if (_windowsHookHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
}
internal event EventHandler<GlobalKeyboardHookEventArgs> KeyboardPressed;
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// because we can unhook only in the same thread, not in garbage collector thread
if (_windowsHookHandle != IntPtr.Zero)
{
if (!UnhookWindowsHookEx(_windowsHookHandle))
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_windowsHookHandle = IntPtr.Zero;
// ReSharper disable once DelegateSubtraction
_hookProc -= LowLevelKeyboardProc;
}
}
if (_user32LibraryHandle != IntPtr.Zero)
{
if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1.
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_user32LibraryHandle = IntPtr.Zero;
}
}
~GlobalKeyboardHook()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public enum KeyboardState
{
KeyDown = 0x0100,
KeyUp = 0x0101,
SysKeyDown = 0x0104,
SysKeyUp = 0x0105
}
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
bool fEatKeyStroke = false;
var wparamTyped = wParam.ToInt32();
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
{
object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o;
var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wparamTyped);
EventHandler<GlobalKeyboardHookEventArgs> handler = KeyboardPressed;
handler?.Invoke(this, eventArguments);
fEatKeyStroke = eventArguments.Handled;
}
return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
}
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel;
using static ColorPicker.Win32Apis;
namespace ColorPicker.Keyboard
{
internal class GlobalKeyboardHookEventArgs : HandledEventArgs
{
internal GlobalKeyboardHook.KeyboardState KeyboardState { get; private set; }
internal LowLevelKeyboardInputEvent KeyboardData { get; private set; }
internal GlobalKeyboardHookEventArgs(
LowLevelKeyboardInputEvent keyboardData,
GlobalKeyboardHook.KeyboardState keyboardState)
{
KeyboardData = keyboardData;
KeyboardState = keyboardState;
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using ColorPicker.Helpers;
using ColorPicker.Settings;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
namespace ColorPicker.Keyboard
{
[Export(typeof(KeyboardMonitor))]
public class KeyboardMonitor
{
private readonly AppStateHandler _appStateHandler;
private readonly IUserSettings _userSettings;
private List<string> _currentlyPressedKeys = new List<string>();
private List<string> _activationKeys = new List<string>();
private GlobalKeyboardHook _keyboardHook;
[ImportingConstructor]
public KeyboardMonitor(AppStateHandler appStateHandler, IUserSettings userSettings)
{
_appStateHandler = appStateHandler;
_userSettings = userSettings;
_userSettings.ActivationShortcut.PropertyChanged += ActivationShortcut_PropertyChanged;
SetActivationKeys();
}
public void Start()
{
_keyboardHook = new GlobalKeyboardHook();
_keyboardHook.KeyboardPressed += Hook_KeyboardPressed;
}
private void SetActivationKeys()
{
_activationKeys.Clear();
if (!string.IsNullOrEmpty(_userSettings.ActivationShortcut.Value))
{
var keys = _userSettings.ActivationShortcut.Value.Split('+');
foreach (var key in keys)
{
_activationKeys.Add(key.Trim());
}
_activationKeys.Sort();
}
}
private void ActivationShortcut_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
SetActivationKeys();
}
private void Hook_KeyboardPressed(object sender, GlobalKeyboardHookEventArgs e)
{
var virtualCode = e.KeyboardData.VirtualCode;
var name = Helper.GetKeyName((uint)virtualCode);
// we got modifier with additional info such as "Ctrl (left)" - get rid of parenthesess
if (name.IndexOf("(", StringComparison.OrdinalIgnoreCase) > 0 && name.Length > 1)
{
name = name.Substring(0, name.IndexOf("(", StringComparison.OrdinalIgnoreCase)).Trim();
}
if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown || e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown)
{
if (!_currentlyPressedKeys.Contains(name))
{
_currentlyPressedKeys.Add(name);
}
}
else if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyUp || e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyUp)
{
if (_currentlyPressedKeys.Contains(name))
{
_currentlyPressedKeys.Remove(name);
}
}
_currentlyPressedKeys.Sort();
if (ArraysAreSame(_currentlyPressedKeys, _activationKeys))
{
_appStateHandler.ShowColorPicker();
_currentlyPressedKeys.Clear();
}
}
private static bool ArraysAreSame(List<string> first, List<string> second)
{
if (first.Count != second.Count || (first.Count == 0 && second.Count == 0))
{
return false;
}
for (int i = 0; i < first.Count; i++)
{
if (first[i] != second[i])
{
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
<Window x:Class="ColorPicker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ColorPickerUI"
mc:Ignorable="d"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:ColorPicker.Behaviors"
Title="Color Picker" Height="50" Width="180" WindowStyle="None" Opacity="0.01" ShowInTaskbar="False" ResizeMode="NoResize" Topmost="True" Background="Transparent" AllowsTransparency="True">
<e:Interaction.Behaviors>
<behaviors:ChangeWindowPositionBehavior/>
<behaviors:AppearAnimationBehavior/>
</e:Interaction.Behaviors>
<ContentControl x:Name="MainView" Content="{Binding MainViewModel}"/>
</Window>

View File

@@ -0,0 +1,31 @@
using ColorPicker.ViewModelContracts;
using System.ComponentModel.Composition;
using System.Windows;
namespace ColorPicker
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Closing += MainWindow_Closing;
Bootstrapper.InitializeContainer(this);
InitializeComponent();
DataContext = this;
Hide();
}
[Import]
public IMainViewModel MainViewModel { get; set; }
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Closing -= MainWindow_Closing;
Bootstrapper.Dispose();
}
}
}

View File

@@ -0,0 +1,80 @@
using ColorPicker.Helpers;
using Microsoft.Win32;
using System;
using System.IO;
namespace ColorPicker.Mouse
{
public static class CursorManager
{
private const string CursorsRegistryPath = @"HKEY_CURRENT_USER\Control Panel\Cursors\";
private const string ArrowRegistryName = "Arrow";
private const string IBeamRegistryName = "IBeam";
private const string CrosshairRegistryName = "Crosshair";
private const string HandRegistryName = "Hand";
private const string ColorPickerCursorName = "Resources\\colorPicker.cur";
private static string _originalArrowCursorPath;
private static string _originalIBeamCursorPath;
private static string _originalCrosshairCursorPath;
private static string _originalHandCursorPath;
public static void SetColorPickerCursor()
{
BackupOriginalCursors();
var colorPickerCursorPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ColorPickerCursorName);
ChangeCursor(colorPickerCursorPath, ArrowRegistryName);
ChangeCursor(colorPickerCursorPath, IBeamRegistryName);
}
public static void RestoreOriginalCursors()
{
ChangeCursor(_originalArrowCursorPath, ArrowRegistryName);
ChangeCursor(_originalIBeamCursorPath, IBeamRegistryName);
}
private static void ChangeCursor(string curFile, string cursorRegistryName)
{
try
{
if(curFile != null)
{
Registry.SetValue(CursorsRegistryPath, cursorRegistryName, curFile);
Win32Apis.SystemParametersInfo(SPI_SETCURSORS, 0, new IntPtr(0), SPIF_SENDCHANGE);
}
else
{
Logger.LogInfo("Cursor file path was null");
}
}
catch (Exception ex)
{
Logger.LogError("Failed to change cursor", ex);
}
}
private static void BackupOriginalCursors()
{
if (string.IsNullOrEmpty(_originalArrowCursorPath))
{
_originalArrowCursorPath = (string)Registry.GetValue(CursorsRegistryPath, ArrowRegistryName, string.Empty);
}
if (string.IsNullOrEmpty(_originalIBeamCursorPath))
{
_originalIBeamCursorPath = (string)Registry.GetValue(CursorsRegistryPath, IBeamRegistryName, string.Empty);
}
if (string.IsNullOrEmpty(_originalCrosshairCursorPath))
{
_originalCrosshairCursorPath = (string)Registry.GetValue(CursorsRegistryPath, CrosshairRegistryName, string.Empty);
}
if (string.IsNullOrEmpty(_originalHandCursorPath))
{
_originalHandCursorPath = (string)Registry.GetValue(CursorsRegistryPath, HandRegistryName, string.Empty);
}
}
const int SPI_SETCURSORS = 0x0057;
const int SPIF_SENDCHANGE = 0x02;
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Drawing;
namespace ColorPicker.Mouse
{
public interface IMouseInfoProvider
{
event EventHandler<Color> MouseColorChanged;
event EventHandler<System.Windows.Point> MousePositionChanged;
// position and bool indicating zoom in or zoom out
event EventHandler<Tuple<System.Windows.Point, bool>> OnMouseWheel;
event MouseUpEventHandler OnMouseDown;
System.Windows.Point CurrentPosition { get; }
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Input;
using static ColorPicker.Win32Apis;
namespace ColorPicker.Mouse
{
public delegate void MouseUpEventHandler(object sender, System.Drawing.Point p);
internal class MouseHook
{
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_MOUSEWHEEL = 0x020A;
private const int WHEEL_DELTA = 120;
private IntPtr _mouseHookHandle;
private HookProc _mouseDelegate;
private event MouseUpEventHandler MouseDown;
public event MouseUpEventHandler OnMouseDown
{
add
{
Subscribe();
MouseDown += value;
}
remove
{
MouseDown -= value;
Unsubscribe();
}
}
private event MouseWheelEventHandler MouseWheel;
public event MouseWheelEventHandler OnMouseWheel
{
add
{
Subscribe();
MouseWheel += value;
}
remove
{
MouseWheel -= value;
Unsubscribe();
}
}
private void Unsubscribe()
{
if (_mouseHookHandle != IntPtr.Zero)
{
var result = UnhookWindowsHookEx(_mouseHookHandle);
_mouseHookHandle = IntPtr.Zero;
_mouseDelegate = null;
if (!result)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
}
private void Subscribe()
{
if (_mouseHookHandle == IntPtr.Zero)
{
_mouseDelegate = MouseHookProc;
_mouseHookHandle = SetWindowsHookEx(WH_MOUSE_LL,
_mouseDelegate,
GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
0);
if (_mouseHookHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}
}
private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
MSLLHOOKSTRUCT mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
if (wParam.ToInt32() == WM_LBUTTONDOWN)
{
if (MouseDown != null)
{
MouseDown.Invoke(null, new System.Drawing.Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
}
}
if (wParam.ToInt32() == WM_MOUSEWHEEL)
{
if (MouseWheel != null)
{
MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
MouseWheel.Invoke(null, new MouseWheelEventArgs(mouseDev, Environment.TickCount, (int)mouseHookStruct.mouseData >> 16));
}
}
}
return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
}
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Input;
using System.Windows.Threading;
using ColorPicker.Helpers;
using ColorPicker.Settings;
using static ColorPicker.Win32Apis;
namespace ColorPicker.Mouse
{
[Export(typeof(IMouseInfoProvider))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class MouseInfoProvider : IMouseInfoProvider
{
private const int MousePullInfoIntervalInMs = 10;
private readonly DispatcherTimer _timer = new DispatcherTimer();
private readonly MouseHook _mouseHook;
private readonly IUserSettings _userSettings;
private System.Windows.Point _previousMousePosition = new System.Windows.Point(-1, 1);
private Color _previousColor = Color.Transparent;
[ImportingConstructor]
public MouseInfoProvider(AppStateHandler appStateMonitor, IUserSettings userSettings)
{
_timer.Interval = TimeSpan.FromMilliseconds(MousePullInfoIntervalInMs);
_timer.Tick += Timer_Tick;
appStateMonitor.AppShown += AppStateMonitor_AppShown;
appStateMonitor.AppClosed += AppStateMonitor_AppClosed;
appStateMonitor.AppHidden += AppStateMonitor_AppClosed;
_mouseHook = new MouseHook();
_userSettings = userSettings;
}
public event EventHandler<Color> MouseColorChanged;
public event EventHandler<System.Windows.Point> MousePositionChanged;
public event EventHandler<Tuple<System.Windows.Point, bool>> OnMouseWheel;
public event MouseUpEventHandler OnMouseDown;
public System.Windows.Point CurrentPosition
{
get
{
return _previousMousePosition;
}
}
private void Timer_Tick(object sender, EventArgs e)
{
UpdateMouseInfo();
}
private void UpdateMouseInfo()
{
var mousePosition = GetCursorPosition();
if (_previousMousePosition != mousePosition)
{
_previousMousePosition = mousePosition;
MousePositionChanged?.Invoke(this, mousePosition);
}
var color = GetPixelColor(mousePosition);
if (_previousColor != color)
{
_previousColor = color;
MouseColorChanged?.Invoke(this, color);
}
}
private static Color GetPixelColor(System.Windows.Point mousePosition)
{
var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
var g = Graphics.FromImage(bmp);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
return bmp.GetPixel(0, 0);
}
private static System.Windows.Point GetCursorPosition()
{
GetCursorPos(out PointInter lpPoint);
return (System.Windows.Point)lpPoint;
}
private void AppStateMonitor_AppClosed(object sender, EventArgs e)
{
_timer.Stop();
_previousMousePosition = new System.Windows.Point(-1, 1);
_mouseHook.OnMouseDown -= MouseHook_OnMouseDown;
_mouseHook.OnMouseWheel -= MouseHook_OnMouseWheel;
if (_userSettings.ChangeCursor.Value)
{
CursorManager.RestoreOriginalCursors();
}
}
private void AppStateMonitor_AppShown(object sender, EventArgs e)
{
UpdateMouseInfo();
if (!_timer.IsEnabled)
{
_timer.Start();
}
_mouseHook.OnMouseDown += MouseHook_OnMouseDown;
_mouseHook.OnMouseWheel += MouseHook_OnMouseWheel;
if (_userSettings.ChangeCursor.Value)
{
CursorManager.SetColorPickerCursor();
}
}
private void MouseHook_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta == 0)
{
return;
}
var zoomIn = e.Delta > 0;
OnMouseWheel?.Invoke(this, new Tuple<System.Windows.Point, bool>(_previousMousePosition, zoomIn));
}
private void MouseHook_OnMouseDown(object sender, Point p)
{
OnMouseDown?.Invoke(this, p);
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ColorPickerUI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ColorPickerUI")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ColorPicker.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorPicker.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ColorPicker.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.6.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@@ -0,0 +1,5 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--CONVERTERS-->
<BooleanToVisibilityConverter x:Key="bool2VisibilityConverter" />
</ResourceDictionary>

View File

@@ -0,0 +1,12 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:ColorPicker.ViewModels"
xmlns:views="clr-namespace:ColorPicker.Views">
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
<views:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ZoomViewModel}">
<views:ZoomView/>
</DataTemplate>
</ResourceDictionary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,13 @@
using Microsoft.PowerToys.Settings.UI.Lib;
namespace ColorPicker.Settings
{
public interface IUserSettings
{
SettingItem<string> ActivationShortcut { get; }
SettingItem<bool> ChangeCursor { get; }
SettingItem<ColorRepresentationType> CopiedColorRepresentation { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.ComponentModel;
namespace ColorPicker.Settings
{
public sealed class SettingItem<T> : INotifyPropertyChanged
{
private T _value;
public SettingItem(T startValue)
{
_value = startValue;
}
public T Value
{
get
{
return _value;
}
set
{
_value = value;
OnValueChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnValueChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}

View File

@@ -0,0 +1,88 @@
using System.IO;
using System.ComponentModel.Composition;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
using ColorPicker.Helpers;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace ColorPicker.Settings
{
[Export(typeof(IUserSettings))]
public class UserSettings : IUserSettings
{
private const string ColorPickerModuleName = "ColorPicker";
private const string DefaultActivationShortcut = "Ctrl + Break";
private const int MaxNumberOfRetry = 5;
private FileSystemWatcher _watcher;
private object _loadingSettingsLock = new object();
[ImportingConstructor]
public UserSettings()
{
ChangeCursor = new SettingItem<bool>(true);
ActivationShortcut = new SettingItem<string>(DefaultActivationShortcut);
CopiedColorRepresentation = new SettingItem<ColorRepresentationType>(ColorRepresentationType.HEX);
LoadSettingsFromJson();
_watcher = Helper.GetFileWatcher(ColorPickerModuleName, "settings.json", LoadSettingsFromJson);
}
public SettingItem<string> ActivationShortcut { get; private set; }
public SettingItem<bool> ChangeCursor { get; private set; }
public SettingItem<ColorRepresentationType> CopiedColorRepresentation { get; set; }
private void LoadSettingsFromJson()
{
// TODO this IO call should by Async, update GetFileWatcher helper to support async
lock (_loadingSettingsLock)
{
{
var retry = true;
var retryCount = 0;
while (retry)
{
try
{
retryCount++;
if (!SettingsUtils.SettingsExists(ColorPickerModuleName))
{
Logger.LogInfo("ColorPicker settings.json was missing, creating a new one");
var defaultColorPickerSettings = new ColorPickerSettings();
defaultColorPickerSettings.Save();
}
var settings = SettingsUtils.GetSettings<ColorPickerSettings>(ColorPickerModuleName);
if (settings != null)
{
ChangeCursor.Value = settings.properties.ChangeCursor;
ActivationShortcut.Value = settings.properties.ActivationShortcut.ToString();
CopiedColorRepresentation.Value = settings.properties.CopiedColorRepresentation;
}
retry = false;
}
catch (IOException ex)
{
if (retryCount > MaxNumberOfRetry)
{
retry = false;
}
Logger.LogError("Failed to read changed settings", ex);
Thread.Sleep(500);
}
catch(Exception ex)
{
Logger.LogError("Failed to read changed settings", ex);
}
}
};
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Windows.Media;
namespace ColorPicker.ViewModelContracts
{
public interface IMainViewModel
{
string HexColor { get; }
string RgbColor { get; }
Brush ColorBrush { get; }
}
}

View File

@@ -0,0 +1,19 @@
using System.Windows.Media.Imaging;
namespace ColorPicker.ViewModelContracts
{
public interface IZoomViewModel
{
BitmapSource ZoomArea { get; set; }
double ZoomFactor { get; set; }
double DesiredWidth { get; set; }
double DesiredHeight { get; set; }
double Width { get; set; }
double Height { get; set; }
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using ColorPicker.Common;
using ColorPicker.Helpers;
using ColorPicker.Keyboard;
using ColorPicker.Mouse;
using ColorPicker.Settings;
using ColorPicker.ViewModelContracts;
using Microsoft.PowerToys.Settings.UI.Lib;
namespace ColorPicker.ViewModels
{
[Export(typeof(IMainViewModel))]
public class MainViewModel : ViewModelBase, IMainViewModel
{
private string _hexColor;
private string _rgbColor;
private Brush _colorBrush;
private readonly ZoomWindowHelper _zoomWindowHelper;
private readonly AppStateHandler _appStateHandler;
private readonly IUserSettings _userSettings;
[ImportingConstructor]
public MainViewModel(
IMouseInfoProvider mouseInfoProvider,
ZoomWindowHelper zoomWindowHelper,
AppStateHandler appStateHandler,
KeyboardMonitor keyboardMonitor,
IUserSettings userSettings)
{
_zoomWindowHelper = zoomWindowHelper;
_appStateHandler = appStateHandler;
_userSettings = userSettings;
mouseInfoProvider.MouseColorChanged += Mouse_ColorChanged;
mouseInfoProvider.OnMouseDown += MouseInfoProvider_OnMouseDown;
mouseInfoProvider.OnMouseWheel += MouseInfoProvider_OnMouseWheel;
keyboardMonitor.Start();
}
public string HexColor
{
get
{
return _hexColor;
}
private set
{
_hexColor = value;
OnPropertyChanged();
}
}
public string RgbColor
{
get
{
return _rgbColor;
}
private set
{
_rgbColor = value;
OnPropertyChanged();
}
}
public Brush ColorBrush
{
get
{
return _colorBrush;
}
private set
{
_colorBrush = value;
OnPropertyChanged();
}
}
private void Mouse_ColorChanged(object sender, System.Drawing.Color color)
{
HexColor = ColorToHex(color);
RgbColor = ColorToRGB(color);
ColorBrush = new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
}
private void MouseInfoProvider_OnMouseDown(object sender, System.Drawing.Point p)
{
string colorRepresentationToCopy = string.Empty;
switch (_userSettings.CopiedColorRepresentation.Value)
{
case ColorRepresentationType.HEX:
colorRepresentationToCopy = HexColor;
break;
case ColorRepresentationType.RGB:
colorRepresentationToCopy = RgbColor;
break;
default:
break;
}
CopyToClipboard(colorRepresentationToCopy);
_appStateHandler.HideColorPicker();
}
private static void CopyToClipboard(string colorRepresentationToCopy)
{
if (!string.IsNullOrEmpty(colorRepresentationToCopy))
{
// nasty hack - sometimes clipboard can be in use and it will raise and exception
for (int i = 0; i < 10; i++)
{
try
{
Clipboard.SetText(colorRepresentationToCopy);
break;
}
catch (COMException ex)
{
const uint CLIPBRD_E_CANT_OPEN = 0x800401D0;
if ((uint)ex.ErrorCode != CLIPBRD_E_CANT_OPEN)
{
Logger.LogError("Failed to set text into clipboard", ex);
}
}
System.Threading.Thread.Sleep(10);
}
}
}
private void MouseInfoProvider_OnMouseWheel(object sender, Tuple<Point, bool> e)
{
_zoomWindowHelper.Zoom(e.Item1, e.Item2);
}
private static string ColorToHex(System.Drawing.Color c)
{
return "#" + c.R.ToString("X2", CultureInfo.InvariantCulture) + c.G.ToString("X2", CultureInfo.InvariantCulture) + c.B.ToString("X2", CultureInfo.InvariantCulture);
}
private static string ColorToRGB(System.Drawing.Color c)
{
return "RGB(" + c.R.ToString(CultureInfo.InvariantCulture) + "," + c.G.ToString(CultureInfo.InvariantCulture) + "," + c.B.ToString(CultureInfo.InvariantCulture) + ")";
}
}
}

View File

@@ -0,0 +1,101 @@
using ColorPicker.Common;
using ColorPicker.ViewModelContracts;
using System.ComponentModel.Composition;
using System.Windows.Media.Imaging;
namespace ColorPicker.ViewModels
{
[Export(typeof(IZoomViewModel))]
public class ZoomViewModel : ViewModelBase, IZoomViewModel
{
private BitmapSource _zoomArea;
private double _zoomFactor = 1;
private double _desiredWidth;
private double _desiredHeight;
private double _width;
private double _height;
[ImportingConstructor]
public ZoomViewModel()
{
}
public BitmapSource ZoomArea
{
get
{
return _zoomArea;
}
set
{
_zoomArea = value;
OnPropertyChanged();
}
}
public double ZoomFactor
{
get
{
return _zoomFactor;
}
set
{
_zoomFactor = value;
OnPropertyChanged();
}
}
public double DesiredWidth
{
get
{
return _desiredWidth;
}
set
{
_desiredWidth = value;
OnPropertyChanged();
}
}
public double DesiredHeight
{
get
{
return _desiredHeight;
}
set
{
_desiredHeight = value;
OnPropertyChanged();
}
}
public double Width
{
get
{
return _width;
}
set
{
_width = value;
OnPropertyChanged();
}
}
public double Height
{
get
{
return _height;
}
set
{
_height = value;
OnPropertyChanged();
}
}
}
}

View File

@@ -0,0 +1,32 @@
<UserControl x:Class="ColorPicker.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ColorPicker.Views"
xmlns:viewModel="clr-namespace:ColorPicker.ViewModels"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="180"
d:DataContext="{d:DesignInstance viewModel:MainViewModel, IsDesignTimeCreatable=True}">
<Grid Background="Transparent" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.RowSpan="2" Grid.ColumnSpan="2" Grid.Column="1" Background="#202020" BorderThickness="1" BorderBrush="Black" />
<Border Background="{Binding ColorBrush}" BorderBrush="Black" Grid.Column="1" BorderThickness="1" Grid.RowSpan="2" x:Name="ColorBorder"/>
<TextBlock Margin="7,5,5,5" Foreground="White" Grid.Row="0" Grid.Column="2" Text="{Binding HexColor}"/>
<TextBlock Margin="7,0,0,0" Foreground="White" Grid.Row="1" Grid.Column="2" Text="{Binding RgbColor}"/>
<Border BorderBrush="#505050" Grid.Column="2" Margin="0,1,1,1" Grid.RowSpan="2" BorderThickness="3,0,3,0" />
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ColorPicker.Views
{
/// <summary>
/// Interaction logic for MainView.xaml
/// </summary>
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,16 @@
<UserControl x:Class="ColorPicker.Views.ZoomView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ColorPicker.Views"
mc:Ignorable="d"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:ColorPicker.Behaviors"
d:DesignHeight="450" d:DesignWidth="800" BorderBrush="Black" BorderThickness="2" Focusable="False">
<Image Source="{Binding ZoomArea}" RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Fill" Width="{Binding Width, Mode=TwoWay}" Height="{Binding Height, Mode=TwoWay}">
<e:Interaction.Behaviors>
<behaviors:ResizeBehavior Width="{Binding DesiredWidth}" Height="{Binding DesiredHeight}"/>
</e:Interaction.Behaviors>
</Image>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ColorPicker.Views
{
/// <summary>
/// Interaction logic for ZoomView.xaml
/// </summary>
public partial class ZoomView : UserControl
{
public ZoomView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,135 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace ColorPicker
{
public static class Win32Apis
{
public const int WH_KEYBOARD_LL = 13;
public const int VkSnapshot = 0x2c;
public const int KfAltdown = 0x2000;
public const int LlkhfAltdown = (KfAltdown >> 8);
public const int MonitorinfofPrimary = 0x00000001;
public delegate bool MonitorEnumProc(
IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
internal static extern bool FreeLibrary(IntPtr hModule);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr GetModuleHandle(string name);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
internal static extern bool GetMonitorInfo(
HandleRef hmonitor, [In, Out] MonitorInfoEx info);
[DllImport("user32.dll", ExactSpelling = true)]
[ResourceExposure(ResourceScope.None)]
internal static extern bool EnumDisplayMonitors(
HandleRef hdc, IntPtr rcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);
[DllImport("user32.dll")]
internal static extern bool GetCursorPos(out PointInter lpPoint);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
internal static extern bool SystemParametersInfo(int uiAction, int uiParam, IntPtr pvParam, int fWinIni);
[StructLayout(LayoutKind.Sequential)]
internal struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
internal struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PointInter
{
public int X;
public int Y;
public static explicit operator System.Windows.Point(PointInter point) => new System.Windows.Point(point.X, point.Y);
}
[StructLayout(LayoutKind.Sequential)]
internal struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
internal class MonitorInfoEx
{
internal int cbSize = Marshal.SizeOf(typeof(MonitorInfoEx));
internal Rect rcMonitor = default;
internal Rect rcWork = default;
internal int dwFlags = 0;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
internal char[] szDevice = new char[32];
}
[StructLayout(LayoutKind.Sequential)]
internal struct LowLevelKeyboardInputEvent
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
public int VirtualCode;
/// <summary>
/// A hardware scan code for the key.
/// </summary>
public int HardwareScanCode;
/// <summary>
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level.
/// </summary>
public int Flags;
/// <summary>
/// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message.
/// </summary>
public int TimeStamp;
/// <summary>
/// Additional information associated with the message.
/// </summary>
public IntPtr AdditionalInformation;
}
}
}

View File

@@ -0,0 +1,15 @@
<Window x:Class="ColorPicker.ZoomWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ColorPicker"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:ColorPicker.Behaviors"
mc:Ignorable="d"
Title="Zoom window" WindowStyle="None" SizeToContent="WidthAndHeight" Topmost="True" ShowInTaskbar="False" ResizeMode="NoResize" Focusable="False">
<e:Interaction.Behaviors>
<behaviors:CloseZoomWindowBehavior/>
<behaviors:MoveWindowBehavior Left="{Binding DesiredLeft, Mode=TwoWay}" Top="{Binding DesiredTop}"/>
</e:Interaction.Behaviors>
</Window>

View File

@@ -0,0 +1,52 @@
using System.ComponentModel;
using System.Windows;
namespace ColorPicker
{
/// <summary>
/// Interaction logic for ZoomWindow.xaml
/// </summary>
public partial class ZoomWindow : Window, INotifyPropertyChanged
{
private double _left;
private double _top;
public ZoomWindow()
{
InitializeComponent();
DataContext = this;
}
public double DesiredLeft
{
get
{
return _left;
}
set
{
_left = value;
NotifyPropertyChanged(nameof(DesiredLeft));
}
}
public double DesiredTop
{
get
{
return _top;
}
set
{
_top = value;
NotifyPropertyChanged(nameof(DesiredTop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -129,7 +129,7 @@ int runner(bool isProcessElevated)
chdir_current_executable();
// Load Powertoys DLLs
const std::array<std::wstring_view, 7> knownModules = {
const std::array<std::wstring_view, 8> knownModules = {
L"modules/FancyZones/fancyzones.dll",
L"modules/FileExplorerPreview/powerpreview.dll",
L"modules/ImageResizer/ImageResizerExt.dll",
@@ -137,6 +137,7 @@ int runner(bool isProcessElevated)
L"modules/Launcher/Microsoft.Launcher.dll",
L"modules/PowerRename/PowerRenameExt.dll",
L"modules/ShortcutGuide/ShortcutGuide.dll",
L"modules/ColorPicker/ColorPicker.dll",
};
for (const auto & moduleSubdir : knownModules)