mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-06-07 01:08:18 +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
|
||||
subkey
|
||||
SUBLANG
|
||||
submenu
|
||||
subquery
|
||||
substr
|
||||
Sul
|
||||
|
@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ColorPicker.Settings;
|
||||
using ColorPicker.ViewModelContracts;
|
||||
using Common.UI;
|
||||
@ -21,6 +22,9 @@ namespace ColorPicker.Helpers
|
||||
private bool _colorPickerShown;
|
||||
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.
|
||||
public static bool BlockEscapeKeyClosingColorPickerEditor { get; set; }
|
||||
|
||||
@ -56,6 +60,12 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
return true;
|
||||
@ -190,5 +206,67 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
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
|
||||
// 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.Add(name);
|
||||
}
|
||||
|
||||
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.
|
||||
AddModifierKeys(currentlyPressedKeys);
|
||||
_activationShortcutPressed = true;
|
||||
|
||||
currentlyPressedKeys.Add(name);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
_appStateHandler.StartUserSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,7 +166,7 @@ namespace ColorPicker.Keyboard
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
_keyboardHook.Dispose();
|
||||
_keyboardHook?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
|
@ -14,6 +14,7 @@
|
||||
Background="Transparent"
|
||||
SizeToContent="WidthAndHeight"
|
||||
AllowsTransparency="True"
|
||||
SourceInitialized="MainWindowSourceInitialized"
|
||||
AutomationProperties.Name="Color Picker">
|
||||
<e:Interaction.Behaviors>
|
||||
<behaviors:ChangeWindowPositionBehavior/>
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using ColorPicker.ViewModelContracts;
|
||||
|
||||
namespace ColorPicker
|
||||
@ -19,6 +20,7 @@ namespace ColorPicker
|
||||
Bootstrapper.InitializeContainer(this);
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Show(); // Call show just to make sure source is initialized at startup.
|
||||
Hide();
|
||||
}
|
||||
|
||||
@ -30,5 +32,10 @@ namespace ColorPicker
|
||||
Closing -= MainWindow_Closing;
|
||||
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")]
|
||||
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(
|
||||
IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);
|
||||
|
||||
@ -88,6 +108,15 @@ namespace ColorPicker
|
||||
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
|
||||
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")]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct POINT
|
||||
|
@ -27,5 +27,7 @@ namespace ColorPicker.ViewModelContracts
|
||||
/// Gets a value indicating whether gets the show color name
|
||||
/// </summary>
|
||||
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)); };
|
||||
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>
|
||||
@ -163,5 +171,10 @@ namespace ColorPicker.ViewModels
|
||||
/// <param name="e">The new values for the zoom</param>
|
||||
private void MouseInfoProvider_OnMouseWheel(object sender, Tuple<Point, bool> e)
|
||||
=> _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