mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-06-07 09:28:03 +08:00
[ColorPicker]Use Hotkey when started from runner (#17265)
* [ColorPicker]Use Hotkey when started from runner * fix spellchecker
This commit is contained in:
parent
80e9fc0c43
commit
1be880438a
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@ -1938,6 +1938,7 @@ Subdir
|
|||||||
subfolder
|
subfolder
|
||||||
subkey
|
subkey
|
||||||
SUBLANG
|
SUBLANG
|
||||||
|
submenu
|
||||||
subquery
|
subquery
|
||||||
substr
|
substr
|
||||||
Sul
|
Sul
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
using ColorPicker.Settings;
|
using ColorPicker.Settings;
|
||||||
using ColorPicker.ViewModelContracts;
|
using ColorPicker.ViewModelContracts;
|
||||||
using Common.UI;
|
using Common.UI;
|
||||||
@ -21,6 +22,9 @@ namespace ColorPicker.Helpers
|
|||||||
private bool _colorPickerShown;
|
private bool _colorPickerShown;
|
||||||
private object _colorPickerVisibilityLock = new object();
|
private object _colorPickerVisibilityLock = new object();
|
||||||
|
|
||||||
|
private HwndSource _hwndSource;
|
||||||
|
private const int _globalHotKeyId = 0x0001;
|
||||||
|
|
||||||
// Blocks using the escape key to close the color picker editor when the adjust color flyout is open.
|
// Blocks using the escape key to close the color picker editor when the adjust color flyout is open.
|
||||||
public static bool BlockEscapeKeyClosingColorPickerEditor { get; set; }
|
public static bool BlockEscapeKeyClosingColorPickerEditor { get; set; }
|
||||||
|
|
||||||
@ -56,6 +60,12 @@ namespace ColorPicker.Helpers
|
|||||||
{
|
{
|
||||||
ShowColorPicker();
|
ShowColorPicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the escape key to close Color Picker locally when being spawn from PowerToys, since Keyboard Hooks from the KeyboardMonitor are heavy.
|
||||||
|
if (!(System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
||||||
|
{
|
||||||
|
SetupEscapeGlobalKeyShortcut();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +84,12 @@ namespace ColorPicker.Helpers
|
|||||||
HideColorPicker();
|
HideColorPicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the escape key to close Color Picker locally when being spawn from PowerToys, since Keyboard Hooks from the KeyboardMonitor are heavy.
|
||||||
|
if (!(System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
||||||
|
{
|
||||||
|
ClearEscapeGlobalKeyShortcut();
|
||||||
|
}
|
||||||
|
|
||||||
SessionEventHelper.End();
|
SessionEventHelper.End();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -190,5 +206,67 @@ namespace ColorPicker.Helpers
|
|||||||
{
|
{
|
||||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ColorPicker);
|
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ColorPicker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void RegisterWindowHandle(System.Windows.Interop.HwndSource hwndSource)
|
||||||
|
{
|
||||||
|
_hwndSource = hwndSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA1801 // Review unused parameters
|
||||||
|
public IntPtr ProcessWindowMessages(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
|
||||||
|
#pragma warning restore CA1801 // Review unused parameters
|
||||||
|
{
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case NativeMethods.WM_HOTKEY:
|
||||||
|
if (!BlockEscapeKeyClosingColorPickerEditor)
|
||||||
|
{
|
||||||
|
handled = EndUserSession();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If escape key is blocked it means a submenu is open.
|
||||||
|
// Send the escape key to the Window to close that submenu.
|
||||||
|
// Description for LPARAM in https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup#parameters
|
||||||
|
// It's basically some modifiers + scancode for escape (1) + number of repetitions (1)
|
||||||
|
handled = true;
|
||||||
|
handled &= NativeMethods.PostMessage(_hwndSource.Handle, NativeMethods.WM_KEYDOWN, (IntPtr)NativeMethods.VK_ESCAPE, (IntPtr)0x00010001);
|
||||||
|
handled &= NativeMethods.PostMessage(_hwndSource.Handle, NativeMethods.WM_KEYUP, (IntPtr)NativeMethods.VK_ESCAPE, (IntPtr)0xC0010001);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetupEscapeGlobalKeyShortcut()
|
||||||
|
{
|
||||||
|
if (_hwndSource == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hwndSource.AddHook(ProcessWindowMessages);
|
||||||
|
if (!NativeMethods.RegisterHotKey(_hwndSource.Handle, _globalHotKeyId, NativeMethods.MOD_NOREPEAT, NativeMethods.VK_ESCAPE))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Couldn't register the hotkey for Esc.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearEscapeGlobalKeyShortcut()
|
||||||
|
{
|
||||||
|
if (_hwndSource == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NativeMethods.UnregisterHotKey(_hwndSource.Handle, _globalHotKeyId))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Couldn't unregister the hotkey for Esc.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_hwndSource.RemoveHook(ProcessWindowMessages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,40 +83,37 @@ namespace ColorPicker.Keyboard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
var name = Helper.GetKeyName((uint)virtualCode);
|
||||||
|
|
||||||
|
// If the last key pressed is a modifier key, then currentlyPressedKeys cannot possibly match with _activationKeys
|
||||||
|
// because _activationKeys contains exactly 1 non-modifier key. Hence, there's no need to check if `name` is a
|
||||||
|
// modifier key or to do any additional processing on it.
|
||||||
|
if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown || e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown)
|
||||||
{
|
{
|
||||||
var name = Helper.GetKeyName((uint)virtualCode);
|
// Check pressed modifier keys.
|
||||||
|
AddModifierKeys(currentlyPressedKeys);
|
||||||
|
|
||||||
// If the last key pressed is a modifier key, then currentlyPressedKeys cannot possibly match with _activationKeys
|
currentlyPressedKeys.Add(name);
|
||||||
// because _activationKeys contains exactly 1 non-modifier key. Hence, there's no need to check if `name` is a
|
}
|
||||||
// modifier key or to do any additional processing on it.
|
|
||||||
if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown || e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown)
|
currentlyPressedKeys.Sort();
|
||||||
|
|
||||||
|
if (currentlyPressedKeys.Count == 0 && _previouslyPressedKeys.Count != 0)
|
||||||
|
{
|
||||||
|
// no keys pressed, we can enable activation shortcut again
|
||||||
|
_activationShortcutPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_previouslyPressedKeys = currentlyPressedKeys;
|
||||||
|
|
||||||
|
if (ArraysAreSame(currentlyPressedKeys, _activationKeys))
|
||||||
|
{
|
||||||
|
// avoid triggering this action multiple times as this will be called nonstop while keys are pressed
|
||||||
|
if (!_activationShortcutPressed)
|
||||||
{
|
{
|
||||||
// Check pressed modifier keys.
|
_activationShortcutPressed = true;
|
||||||
AddModifierKeys(currentlyPressedKeys);
|
|
||||||
|
|
||||||
currentlyPressedKeys.Add(name);
|
_appStateHandler.StartUserSession();
|
||||||
}
|
|
||||||
|
|
||||||
currentlyPressedKeys.Sort();
|
|
||||||
|
|
||||||
if (currentlyPressedKeys.Count == 0 && _previouslyPressedKeys.Count != 0)
|
|
||||||
{
|
|
||||||
// no keys pressed, we can enable activation shortcut again
|
|
||||||
_activationShortcutPressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_previouslyPressedKeys = currentlyPressedKeys;
|
|
||||||
|
|
||||||
if (ArraysAreSame(currentlyPressedKeys, _activationKeys))
|
|
||||||
{
|
|
||||||
// avoid triggering this action multiple times as this will be called nonstop while keys are pressed
|
|
||||||
if (!_activationShortcutPressed)
|
|
||||||
{
|
|
||||||
_activationShortcutPressed = true;
|
|
||||||
|
|
||||||
_appStateHandler.StartUserSession();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +166,7 @@ namespace ColorPicker.Keyboard
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
// TODO: dispose managed state (managed objects)
|
// TODO: dispose managed state (managed objects)
|
||||||
_keyboardHook.Dispose();
|
_keyboardHook?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
SizeToContent="WidthAndHeight"
|
SizeToContent="WidthAndHeight"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
|
SourceInitialized="MainWindowSourceInitialized"
|
||||||
AutomationProperties.Name="Color Picker">
|
AutomationProperties.Name="Color Picker">
|
||||||
<e:Interaction.Behaviors>
|
<e:Interaction.Behaviors>
|
||||||
<behaviors:ChangeWindowPositionBehavior/>
|
<behaviors:ChangeWindowPositionBehavior/>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
using ColorPicker.ViewModelContracts;
|
using ColorPicker.ViewModelContracts;
|
||||||
|
|
||||||
namespace ColorPicker
|
namespace ColorPicker
|
||||||
@ -19,6 +20,7 @@ namespace ColorPicker
|
|||||||
Bootstrapper.InitializeContainer(this);
|
Bootstrapper.InitializeContainer(this);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
Show(); // Call show just to make sure source is initialized at startup.
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,5 +32,10 @@ namespace ColorPicker
|
|||||||
Closing -= MainWindow_Closing;
|
Closing -= MainWindow_Closing;
|
||||||
Bootstrapper.Dispose();
|
Bootstrapper.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MainWindowSourceInitialized(object sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
this.MainViewModel.RegisterWindowHandle(HwndSource.FromHwnd(new WindowInteropHelper(this).Handle));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,26 @@ namespace ColorPicker
|
|||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
public const int VK_RWIN = 0x5C;
|
public const int VK_RWIN = 0x5C;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
|
public const int VK_ESCAPE = 0x1B;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
|
public const int WM_HOTKEY = 0x0312;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
|
public const int WM_KEYDOWN = 0x0100;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
|
public const int WM_KEYUP = 0x0101;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")]
|
||||||
|
public const uint MOD_NOREPEAT = 0x4000;
|
||||||
|
|
||||||
public delegate bool MonitorEnumProc(
|
public delegate bool MonitorEnumProc(
|
||||||
IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);
|
IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);
|
||||||
|
|
||||||
@ -88,6 +108,15 @@ namespace ColorPicker
|
|||||||
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
|
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
|
||||||
internal static extern bool SystemParametersInfo(int uiAction, int uiParam, IntPtr pvParam, int fWinIni);
|
internal static extern bool SystemParametersInfo(int uiAction, int uiParam, IntPtr pvParam, int fWinIni);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
internal static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
internal static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||||
|
internal static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Interop object")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Interop object")]
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
internal struct POINT
|
internal struct POINT
|
||||||
|
@ -27,5 +27,7 @@ namespace ColorPicker.ViewModelContracts
|
|||||||
/// Gets a value indicating whether gets the show color name
|
/// Gets a value indicating whether gets the show color name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ShowColorName { get; }
|
bool ShowColorName { get; }
|
||||||
|
|
||||||
|
void RegisterWindowHandle(System.Windows.Interop.HwndSource hwndSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,15 @@ namespace ColorPicker.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
_userSettings.ShowColorName.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(ShowColorName)); };
|
_userSettings.ShowColorName.PropertyChanged += (s, e) => { OnPropertyChanged(nameof(ShowColorName)); };
|
||||||
keyboardMonitor?.Start();
|
|
||||||
|
// Only start a local keyboard low level hook if running as a standalone.
|
||||||
|
// Otherwise, the global keyboard hook from runner will be used to activate Color Picker through ShowColorPickerSharedEvent
|
||||||
|
// and the Escape key will be registered as a shortcut by appStateHandler when ColorPicker is being used.
|
||||||
|
// This is much lighter than using a local low level keyboard hook.
|
||||||
|
if ((System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
||||||
|
{
|
||||||
|
keyboardMonitor?.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -163,5 +171,10 @@ namespace ColorPicker.ViewModels
|
|||||||
/// <param name="e">The new values for the zoom</param>
|
/// <param name="e">The new values for the zoom</param>
|
||||||
private void MouseInfoProvider_OnMouseWheel(object sender, Tuple<Point, bool> e)
|
private void MouseInfoProvider_OnMouseWheel(object sender, Tuple<Point, bool> e)
|
||||||
=> _zoomWindowHelper.Zoom(e.Item1, e.Item2);
|
=> _zoomWindowHelper.Zoom(e.Item1, e.Item2);
|
||||||
|
|
||||||
|
public void RegisterWindowHandle(System.Windows.Interop.HwndSource hwndSource)
|
||||||
|
{
|
||||||
|
_appStateHandler.RegisterWindowHandle(hwndSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user