Restructured hotkey handling

This commit is contained in:
Peter Kirmeier 2023-05-26 00:34:31 +02:00
parent 9961e53c14
commit 1c22c86d3e
6 changed files with 432 additions and 405 deletions

262
Helpers/GlobalHotkeys.cs Normal file
View file

@ -0,0 +1,262 @@
// <copyright file="GlobalHotkeys.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
//
// Copyright (c) 2023-2023 Peter Kirmeier
namespace SystemTrayMenu.Helpers
{
using System;
using System.Text;
using System.Windows.Input;
using SystemTrayMenu.DllImports;
using SystemTrayMenu.Utilities;
internal static class GlobalHotkeys
{
private static readonly object LastIdLock = new();
private static int lastId = 0;
/// <summary>
/// Registers a global hotkey in a thread safe manner.
/// Throws an InvalidOperationException on error.
/// The caller needs to call UnregisterHotkey to free up ressources.
/// </summary>
/// <param name="hWnd">Window handle receiving the events.</param>
/// <param name="modifiers">Hotkey modifiers.</param>
/// <param name="key">Hotkey major key.</param>
/// <returns>ID of registered key.</returns>
internal static int RegisterHotkeyGlobal(IntPtr hWnd, ModifierKeys modifiers, Key key)
{
lock (LastIdLock)
{
lastId = RegisterHotkeyLocal(hWnd, lastId + 1, modifiers, key);
}
return lastId;
}
/// <summary>
/// Registers a global hotkey in a thread safe manner.
/// Throws an InvalidOperationException on error.
/// The caller needs to call UnregisterHotkey to free up ressources.
/// </summary>
/// <param name="hWnd">Window handle receiving the events.</param>
/// <param name="hotKeyString">Hotkey string description.</param>
/// <returns>ID of registered key.</returns>
internal static int RegisterHotkeyGlobal(IntPtr hWnd, string hotKeyString)
{
ModifierKeys modifiers = ModifierKeysFromString(hotKeyString);
Key key = KeyFromString(hotKeyString);
return RegisterHotkeyGlobal(hWnd, modifiers, key);
}
/// <summary>
/// Registers a local hotkey (with given ID).
/// Throws an InvalidOperationException on error.
/// The caller needs to call UnregisterHotkey to free up ressources.
/// </summary>
/// <param name="hWnd">Window handle receiving the events.</param>
/// <param name="id">ID for the registration.</param>
/// <param name="modifiers">Hotkey modifiers.</param>
/// <param name="key">Hotkey major key.</param>
/// <returns>ID of registered key.</returns>
internal static int RegisterHotkeyLocal(IntPtr hWnd, int id, ModifierKeys modifiers, Key key)
{
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(key);
if (!NativeMethods.User32RegisterHotKey(hWnd, id, (uint)modifiers, (uint)virtualKeyCode))
{
string errorHint = NativeMethods.GetLastErrorHint();
throw new InvalidOperationException(Translator.GetText("Could not register the hot key.") + " (" + errorHint + ")");
}
return id;
}
/// <summary>
/// Registers a local hotkey (with given ID).
/// Throws an InvalidOperationException on error.
/// The caller needs to call UnregisterHotkey to free up ressources.
/// </summary>
/// <param name="hWnd">Window handle receiving the events.</param>
/// <param name="id">ID for the registration.</param>
/// <param name="hotKeyString">Hotkey string description.</param>
/// <returns>ID of registered key.</returns>
internal static int RegisterHotkeyLocal(IntPtr hWnd, int id, string hotKeyString)
{
ModifierKeys modifiers = ModifierKeysFromString(hotKeyString);
Key key = KeyFromString(hotKeyString);
return RegisterHotkeyLocal(hWnd, id, modifiers, key);
}
/// <summary>
/// Unregisters a local or global hotkey.
/// </summary>
/// <param name="hWnd">Window handle.</param>
/// <param name="id">ID for the registration.</param>
internal static void UnregisterHotkey(IntPtr hWnd, int id) => NativeMethods.User32UnregisterHotKey(hWnd, id);
internal static ModifierKeys ModifierKeysFromString(string modifiersString)
{
ModifierKeys modifiers = ModifierKeys.None;
string upper = modifiersString.ToUpperInvariant();
if (upper.Contains("ALT+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Alt;
}
if (upper.Contains("CTRL+", StringComparison.InvariantCulture) ||
upper.Contains("STRG+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Control;
}
if (upper.Contains("SHIFT+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Shift;
}
// LWin and RWin keys will implicitly set Windows key modifier
if (upper.Contains("WIN+", StringComparison.InvariantCulture) ||
upper.EndsWith("LWIN", StringComparison.InvariantCulture) ||
upper.EndsWith("RWIN", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Windows;
}
return modifiers;
}
internal static Key KeyFromString(string keyString)
{
Key key = Key.None;
if (!string.IsNullOrEmpty(keyString))
{
if (keyString.LastIndexOf('+') > 0)
{
keyString = keyString.Remove(0, keyString.LastIndexOf('+') + 1).Trim();
}
keyString = keyString.
Replace("PgDn", "PageDown", StringComparison.InvariantCulture).
Replace("PgUp", "PageUp", StringComparison.InvariantCulture);
if (!Enum.TryParse(keyString, true, out key))
{
Log.Warn($"{keyString} cannot be parsed", new());
}
}
return key;
}
internal static string HotkeyToLocalizedString(ModifierKeys modifiers, Key key)
{
StringBuilder hotkeyString = new();
if ((modifiers & ModifierKeys.Alt) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftAlt)).Append(" + ");
}
if ((modifiers & ModifierKeys.Control) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftCtrl)).Append(" + ");
}
if ((modifiers & ModifierKeys.Shift) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftShift)).Append(" + ");
}
if ((modifiers & ModifierKeys.Windows) != 0)
{
hotkeyString.Append("Win").Append(" + ");
}
return hotkeyString.ToString() + GetKeyName(key);
}
private static string GetKeyName(Key givenKey)
{
StringBuilder keyName = new();
const uint numpad = 55;
Key virtualKey = givenKey;
string keyString = string.Empty;
// Make VC's to real keys
switch (virtualKey)
{
case Key.Multiply:
if (NativeMethods.User32GetKeyNameText(numpad << 16, keyName, 100) > 0)
{
keyString = keyName.ToString().Replace("*", string.Empty, StringComparison.InvariantCulture).Trim().ToLowerInvariant();
if (keyString.Contains('('))
{
return "* " + keyString;
}
keyString = keyString[..1].ToUpperInvariant() + keyString[1..].ToLowerInvariant();
}
return keyString + " *";
case Key.Divide:
if (NativeMethods.User32GetKeyNameText(numpad << 16, keyName, 100) > 0)
{
keyString = keyName.ToString().Replace("*", string.Empty, StringComparison.InvariantCulture).Trim().ToLowerInvariant();
if (keyString.Contains('('))
{
return "/ " + keyString;
}
keyString = keyString[..1].ToUpperInvariant() + keyString[1..].ToLowerInvariant();
}
return keyString + " /";
}
const uint MAPVK_VK_TO_VSC = 0;
uint scanCode = NativeMethods.User32MapVirtualKey((uint)virtualKey, MAPVK_VK_TO_VSC);
// because MapVirtualKey strips the extended bit for some keys
switch (virtualKey)
{
case Key.Left:
case Key.Up:
case Key.Right:
case Key.Down: // arrow keys
case Key.Prior:
case Key.Next: // page up and page down
case Key.End:
case Key.Home:
case Key.Insert:
case Key.Delete:
case Key.NumLock:
scanCode |= 0x100; // set extended bit
break;
case Key.PrintScreen: // PrintScreen
scanCode = 311;
break;
case Key.Pause: // PrintScreen
scanCode = 69;
break;
}
scanCode |= 0x200;
if (NativeMethods.User32GetKeyNameText(scanCode << 16, keyName, 100) != 0)
{
string visibleName = keyName.ToString();
if (visibleName.Length > 1)
{
visibleName = visibleName[..1] + visibleName[1..].ToLowerInvariant();
}
return visibleName;
}
else
{
return givenKey.ToString();
}
}
}
}

View file

@ -8,15 +8,13 @@ namespace SystemTrayMenu.Helpers
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using SystemTrayMenu.DllImports;
using SystemTrayMenu.UserInterface;
using SystemTrayMenu.Utilities;
// TODO: Move this whole class into mainMenu to spare creating additional Win32 window handles by using Menu's Window directly.
internal class KeyboardHook : IDisposable
{
private readonly HwndSourceHook hook;
private readonly HwndSource hwnd;
private readonly Window window = new(); // TODO: Try using mainMenu to spare creating additional Win32 window handle?
private readonly Window window = new();
private int currentId;
public KeyboardHook()
@ -39,7 +37,7 @@ namespace SystemTrayMenu.Helpers
// unregister all the registered hot keys.
for (int i = currentId; i > 0; i--)
{
NativeMethods.User32UnregisterHotKey(hwnd.Handle, i);
GlobalHotkeys.UnregisterHotkey(hwnd.Handle, i);
}
hwnd.RemoveHook(hook);
@ -48,21 +46,9 @@ namespace SystemTrayMenu.Helpers
hwnd.Dispose();
}
internal void RegisterHotKey(string hotKeyString)
{
ModifierKeys modifiers = HotkeyControl.HotkeyModifiersFromString(hotKeyString);
Key key = HotkeyControl.HotkeyFromString(hotKeyString);
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(key);
int nextId = currentId + 1;
internal void RegisterHotKey(ModifierKeys modifiers, Key key) => currentId = GlobalHotkeys.RegisterHotkeyLocal(hwnd.Handle, currentId + 1, modifiers, key);
if (!NativeMethods.User32RegisterHotKey(hwnd.Handle, nextId, (uint)modifiers, (uint)virtualKeyCode))
{
string errorHint = NativeMethods.GetLastErrorHint();
throw new InvalidOperationException(Translator.GetText("Could not register the hot key.") + " (" + errorHint + ")");
}
currentId = nextId;
}
internal void RegisterHotKey(string hotKeyString) => currentId = GlobalHotkeys.RegisterHotkeyLocal(hwnd.Handle, currentId + 1, hotKeyString);
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{

View file

@ -1,6 +1,8 @@
// <copyright file="HotkeyControl.cs" company="PlaceholderCompany">
// <copyright file="HotkeySelector.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
//
// Origin of some parts: http://www.codeproject.com/KB/buttons/hotkeycontrol.aspx
namespace SystemTrayMenu.UserInterface
{
@ -9,41 +11,34 @@ namespace SystemTrayMenu.UserInterface
using System.Text;
using System.Windows.Controls;
using System.Windows.Input;
using SystemTrayMenu.DllImports;
using SystemTrayMenu.Helpers;
using SystemTrayMenu.Utilities;
/// <summary>
/// A simple control that allows the user to select pretty much any valid hotkey combination
/// See: http://www.codeproject.com/KB/buttons/hotkeycontrol.aspx
/// But is modified to fit in Greenshot, and have localized support.
/// Modfied to fit SystemTrayMenu.
/// </summary>
public sealed class HotkeyControl : TextBox
public sealed class HotkeySelector : TextBox, IDisposable
{
private static readonly IntPtr HotkeyHwnd = (IntPtr)0x0000000000000000;
// Holds the list of hotkeys
private static readonly IDictionary<int, Action> KeyHandlers = new Dictionary<int, Action>();
private static int hotKeyCounter = 1;
// ArrayLists used to enforce the use of proper modifiers.
// Shift+A isn't a valid hotkey, for instance, as it would screw up when the user is typing.
private readonly IList<int> needNonShiftModifier = new List<int>();
private readonly IList<int> needNonAltGrModifier = new List<int>();
private KeyboardHook hook = new();
// These variables store the current hotkey and modifier(s)
private Key hotkey = Key.None;
private ModifierKeys modifiers = ModifierKeys.None;
private Action? handler;
/// <summary>
/// Initializes a new instance of the <see cref="HotkeyControl"/> class.
/// Initializes a new instance of the <see cref="HotkeySelector"/> class.
/// </summary>
public HotkeyControl()
public HotkeySelector()
{
// Disable right-clicking
ContextMenu = new();
ContextMenu.Visibility = System.Windows.Visibility.Collapsed;
ContextMenu.IsEnabled = false;
ContextMenu = new()
{
Visibility = System.Windows.Visibility.Collapsed,
IsEnabled = false,
};
Text = string.Empty;
@ -53,17 +48,34 @@ namespace SystemTrayMenu.UserInterface
PreviewKeyDown += HandlePreviewKeyDown;
PreviewTextInput += HandlePreviewTextInput;
GotFocus += (_, _) =>
{
UnregisterHotKey();
Reassigning = true;
};
LostFocus += (_, _) =>
{
#if TODO // HOTKEY
Settings.Default.HotKey =
new KeysConverter().ConvertToInvariantString(
textBoxHotkey.Hotkey | textBoxHotkey.HotkeyModifiers);
#endif
#if TODO // HOTKEY
/// <summary>
/// Registers all hotkeys as configured, displaying a dialog in case of hotkey conflicts with other tools.
/// </summary>
/// <returns>Whether the hotkeys could be registered to the users content. This also applies if conflicts arise and the user decides to ignore these (i.e. not to register the conflicting hotkey).</returns>
RegisterHotkeys(false);
#else
ReregisterHotKey();
#endif
Reassigning = false;
};
PopulateModifierLists();
}
private enum MapType : uint
{
MAPVK_VK_TO_VSC = 0, // The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If there is no translation, the function returns 0.
MAPVK_VSC_TO_VK = 1, // The uCode parameter is a scan code and is translated into a virtual-key code that does not distinguish between left- and right-hand keys. If there is no translation, the function returns 0.
MAPVK_VK_TO_CHAR = 2, // The uCode parameter is a virtual-key code and is translated into an unshifted character value in the low order word of the return value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation, the function returns 0.
MAPVK_VSC_TO_VK_EX = 3, // The uCode parameter is a scan code and is translated into a virtual-key code that distinguishes between left- and right-hand keys. If there is no translation, the function returns 0.
MAPVK_VK_TO_VSC_EX = 4, // The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If the scan code is an extended scan code, the high byte of the uCode value can contain either 0xe0 or 0xe1 to specify the extended scan code. If there is no translation, the function returns 0.
}
~HotkeySelector() => Dispose();
/// <summary>
/// Gets or sets used to get/set the hotkey (e.g. Key.A).
@ -91,6 +103,8 @@ namespace SystemTrayMenu.UserInterface
}
}
internal bool Reassigning { get; private set; }
public static string HotkeyToString(ModifierKeys modifierKeyCode, Key key)
{
StringBuilder hotkeyString = new();
@ -117,211 +131,55 @@ namespace SystemTrayMenu.UserInterface
return hotkeyString.ToString() + key.ToString();
}
public static string HotkeyToLocalizedString(ModifierKeys modifierKeyCode, Key key)
public void Dispose()
{
StringBuilder hotkeyString = new();
if ((modifierKeyCode & ModifierKeys.Alt) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftAlt)).Append(" + ");
}
if ((modifierKeyCode & ModifierKeys.Control) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftCtrl)).Append(" + ");
}
if ((modifierKeyCode & ModifierKeys.Shift) != 0)
{
hotkeyString.Append(GetKeyName(Key.LeftShift)).Append(" + ");
}
if ((modifierKeyCode & ModifierKeys.Windows) != 0)
{
hotkeyString.Append("Win").Append(" + ");
}
return hotkeyString.ToString() + GetKeyName(key);
}
public static ModifierKeys HotkeyModifiersFromString(string modifiersString)
{
ModifierKeys modifiers = ModifierKeys.None;
if (!string.IsNullOrEmpty(modifiersString))
{
string upper = modifiersString.ToUpperInvariant();
if (upper.Contains("ALT+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Alt;
}
if (upper.Contains("CTRL+", StringComparison.InvariantCulture) ||
upper.Contains("STRG+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Control;
}
if (upper.Contains("SHIFT+", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Shift;
}
// LWin and RWin keys will implicitly set Windows key modifier
if (upper.Contains("WIN+", StringComparison.InvariantCulture) ||
upper.EndsWith("LWIN", StringComparison.InvariantCulture) ||
upper.EndsWith("RWIN", StringComparison.InvariantCulture))
{
modifiers |= ModifierKeys.Windows;
}
}
return modifiers;
}
public static Key HotkeyFromString(string hotkey)
{
Key key = Key.None;
if (!string.IsNullOrEmpty(hotkey))
{
if (hotkey.LastIndexOf('+') > 0)
{
hotkey = hotkey.Remove(0, hotkey.LastIndexOf('+') + 1).Trim();
}
try
{
hotkey = hotkey.
Replace("PgDn", "PageDown", StringComparison.InvariantCulture).
Replace("PgUp", "PageUp", StringComparison.InvariantCulture);
key = (Key)Enum.Parse(typeof(Key), hotkey);
}
catch (ArgumentException ex)
{
Log.Warn($"{hotkey} can not be parsed", ex);
}
}
return key;
hook.Dispose();
GC.SuppressFinalize(this);
}
/// <summary>
/// Register a hotkey.
/// </summary>
/// <param name="modifierKeyCode">The key modifiers .</param>
/// <param name="modifiers">The key modifiers .</param>
/// <param name="key">The virtual key code.</param>
/// <param name="handler">A HotKeyHandler, this will be called to handle the hotkey press.</param>
/// <returns>the hotkey number, -1 if failed.</returns>
public static int RegisterHotKey(ModifierKeys modifierKeyCode, Key key, Action handler)
public int RegisterHotKey(ModifierKeys modifiers, Key key, Action handler)
{
if (key == Key.None)
{
return 0;
}
int virtualKeyCode = KeyInterop.VirtualKeyFromKey(key);
if (NativeMethods.User32RegisterHotKey(HotkeyHwnd, hotKeyCounter, (uint)modifierKeyCode, (uint)virtualKeyCode))
try
{
KeyHandlers.Add(hotKeyCounter, handler);
return hotKeyCounter++;
hook.RegisterHotKey(modifiers, key);
}
else
catch (InvalidOperationException ex)
{
string errorHint = NativeMethods.GetLastErrorHint();
Log.Info($"Couldn't register hotkey modifier {modifierKeyCode} virtualKeyCode {virtualKeyCode} hint: " + errorHint);
Log.Info($"Couldn't register hotkey modifier {modifiers} key {key} ex: " + ex.ToString());
return -1;
}
this.handler = handler;
hook.KeyPressed += (_, _) => handler.Invoke();
return 1;
}
public static void UnregisterHotkeys()
private void ReregisterHotKey()
{
foreach (int hotkey in KeyHandlers.Keys)
if (handler != null)
{
NativeMethods.User32UnregisterHotKey(HotkeyHwnd, hotkey);
RegisterHotKey(modifiers, hotkey, handler);
}
KeyHandlers.Clear();
}
public static string GetKeyName(Key givenKey)
private void UnregisterHotKey()
{
StringBuilder keyName = new();
const uint numpad = 55;
Key virtualKey = givenKey;
string keyString = string.Empty;
// Make VC's to real keys
switch (virtualKey)
{
case Key.Multiply:
if (NativeMethods.User32GetKeyNameText(numpad << 16, keyName, 100) > 0)
{
keyString = keyName.ToString().Replace("*", string.Empty, StringComparison.InvariantCulture).Trim().ToLowerInvariant();
if (keyString.Contains('('))
{
return "* " + keyString;
}
keyString = keyString[..1].ToUpperInvariant() + keyString[1..].ToLowerInvariant();
}
return keyString + " *";
case Key.Divide:
if (NativeMethods.User32GetKeyNameText(numpad << 16, keyName, 100) > 0)
{
keyString = keyName.ToString().Replace("*", string.Empty, StringComparison.InvariantCulture).Trim().ToLowerInvariant();
if (keyString.Contains('('))
{
return "/ " + keyString;
}
keyString = keyString[..1].ToUpperInvariant() + keyString[1..].ToLowerInvariant();
}
return keyString + " /";
}
uint scanCode = NativeMethods.User32MapVirtualKey((uint)virtualKey, (uint)MapType.MAPVK_VK_TO_VSC);
// because MapVirtualKey strips the extended bit for some keys
switch (virtualKey)
{
case Key.Left:
case Key.Up:
case Key.Right:
case Key.Down: // arrow keys
case Key.Prior:
case Key.Next: // page up and page down
case Key.End:
case Key.Home:
case Key.Insert:
case Key.Delete:
case Key.NumLock:
scanCode |= 0x100; // set extended bit
break;
case Key.PrintScreen: // PrintScreen
scanCode = 311;
break;
case Key.Pause: // PrintScreen
scanCode = 69;
break;
}
scanCode |= 0x200;
if (NativeMethods.User32GetKeyNameText(scanCode << 16, keyName, 100) != 0)
{
string visibleName = keyName.ToString();
if (visibleName.Length > 1)
{
visibleName = visibleName[..1] + visibleName[1..].ToLowerInvariant();
}
return visibleName;
}
else
{
return givenKey.ToString();
}
// TODO: Rework to allow deregistration?
hook.Dispose();
hook = new();
}
/// <summary>
@ -331,7 +189,7 @@ namespace SystemTrayMenu.UserInterface
{
hotkey = Key.None;
modifiers = ModifierKeys.None;
Redraw();
Redraw(false);
}
/// <summary>
@ -340,8 +198,8 @@ namespace SystemTrayMenu.UserInterface
/// <param name="hotkey">hotkey.</param>
public void SetHotkey(string hotkey)
{
this.hotkey = HotkeyFromString(hotkey);
modifiers = HotkeyModifiersFromString(hotkey);
this.hotkey = GlobalHotkeys.KeyFromString(hotkey);
modifiers = GlobalHotkeys.ModifierKeysFromString(hotkey);
Redraw(true);
}
@ -373,7 +231,7 @@ namespace SystemTrayMenu.UserInterface
/// Redraws the TextBox when necessary.
/// </summary>
/// <param name="bCalledProgramatically">Specifies whether this function was called by the Hotkey/HotkeyModifiers properties or by the user.</param>
private void Redraw(bool bCalledProgramatically = false)
private void Redraw(bool bCalledProgramatically)
{
// No hotkey set
if (hotkey == Key.None)
@ -430,7 +288,7 @@ namespace SystemTrayMenu.UserInterface
hotkey = Key.None;
}
Text = HotkeyToLocalizedString(modifiers, hotkey);
Text = GlobalHotkeys.HotkeyToLocalizedString(modifiers, hotkey);
}
/// <summary>
@ -497,7 +355,7 @@ namespace SystemTrayMenu.UserInterface
{
modifiers = Keyboard.Modifiers;
hotkey = e.Key;
Redraw();
Redraw(false);
}
}
@ -512,7 +370,7 @@ namespace SystemTrayMenu.UserInterface
{
modifiers = Keyboard.Modifiers;
hotkey = e.Key;
Redraw();
Redraw(false);
}
else if (hotkey == Key.None && modifiers == ModifierKeys.None)
{
@ -528,5 +386,91 @@ namespace SystemTrayMenu.UserInterface
{
e.Handled = true;
}
#if TODO // HOTKEY
/// <summary>
/// Helper method to cleanly register a hotkey.
/// </summary>
/// <param name="failedKeys">failedKeys.</param>
/// <param name="hotkeyString">hotkeyString.</param>
/// <param name="handler">handler.</param>
/// <returns>bool success.</returns>
private static bool RegisterHotkey(StringBuilder failedKeys, string hotkeyString, HotKeyHandler handler)
{
Keys modifierKeyCode = HotkeyModifiersFromString(hotkeyString);
Keys virtualKeyCode = HotkeyFromString(hotkeyString);
if (!Keys.None.Equals(virtualKeyCode))
{
if (RegisterHotKey(modifierKeyCode, virtualKeyCode, handler) < 0)
{
if (failedKeys.Length > 0)
{
failedKeys.Append(", ");
}
failedKeys.Append(hotkeyString);
return false;
}
}
return true;
}
/// <summary>
/// Registers all hotkeys as configured, displaying a dialog in case of hotkey conflicts with other tools.
/// </summary>
/// <param name="ignoreFailedRegistration">if true, a failed hotkey registration will not be reported to the user - the hotkey will simply not be registered.</param>
/// <returns>Whether the hotkeys could be registered to the users content. This also applies if conflicts arise and the user decides to ignore these (i.e. not to register the conflicting hotkey).</returns>
private static bool RegisterHotkeys(bool ignoreFailedRegistration)
{
bool success = true;
StringBuilder failedKeys = new();
if (!RegisterHotkey(
failedKeys,
Settings.Default.HotKey,
handler))
{
success = false;
}
if (!success)
{
if (!ignoreFailedRegistration)
{
success = HandleFailedHotkeyRegistration(failedKeys.ToString());
}
}
return success || ignoreFailedRegistration;
}
/// <summary>
/// Displays a dialog for the user to choose how to handle hotkey registration failures:
/// retry (allowing to shut down the conflicting application before),
/// ignore (not registering the conflicting hotkey and resetting the respective config to "None", i.e. not trying to register it again on next startup)
/// abort (do nothing about it).
/// </summary>
/// <param name="failedKeys">comma separated list of the hotkeys that could not be registered, for display in dialog text.</param>
/// <returns>bool success.</returns>
private static bool HandleFailedHotkeyRegistration(string failedKeys)
{
bool success = false;
string warningTitle = Translator.GetText("Warning");
string message = Translator.GetText("Could not register the hot key.") + failedKeys;
DialogResult dr = MessageBox.Show(message, warningTitle, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Exclamation);
if (dr == DialogResult.Retry)
{
UnregisterHotKey();
success = RegisterHotkeys(false); // TODO: This may end up in endless recursion, better use a loop at caller
}
else if (dr == DialogResult.Ignore)
{
UnregisterHotKey();
success = RegisterHotkeys(true);
}
return success;
}
#endif
}
}

View file

@ -50,7 +50,7 @@
</GroupBox>
<GroupBox Header="{u:Translate 'Hotkey'}">
<DockPanel LastChildFill="False">
<local:HotkeyControl MinWidth="120" VerticalAlignment="Center"/>
<local:HotkeySelector x:Name="textBoxHotkey" Text="None" AcceptsTab="False" MinWidth="200" VerticalAlignment="Center"/>
<Button x:Name="buttonHotkeyDefault" DockPanel.Dock="Right" Content="{u:Translate 'Default'}" Margin="3" MinWidth="76" Click="ButtonHotkeyDefault_Click" VerticalAlignment="Center"/>
</DockPanel>
</GroupBox>

View file

@ -29,9 +29,6 @@ namespace SystemTrayMenu.UserInterface
private static SettingsWindow? settingsForm;
#if TODO // HOTKEY
private bool inHotkey;
#endif
public SettingsWindow()
{
InitializeComponent();
@ -53,26 +50,7 @@ namespace SystemTrayMenu.UserInterface
}
PreviewKeyDown += HandlePreviewKeyDown;
#if TODO // HOTKEY
// Initialize and replace here here, because designer always removes it
InitializeTextBoxHotkeyAndReplacetextBoxHotkeyPlaceholder();
void InitializeTextBoxHotkeyAndReplacetextBoxHotkeyPlaceholder()
{
textBoxHotkey = new HotkeyTextboxControl.HotkeyControl
{
Hotkey = Keys.None,
HotkeyModifiers = Keys.None,
Name = "textBoxHotkey",
Size = new Size(200, 20),
Text = "None",
TabStop = false,
};
textBoxHotkey.Enter += new EventHandler(TextBoxHotkeyEnter);
textBoxHotkey.Leave += new EventHandler(TextBoxHotkey_Leave);
tableLayoutPanelHotkey.Controls.Remove(textBoxHotkeyPlaceholder);
tableLayoutPanelHotkey.Controls.Add(textBoxHotkey, 0, 0);
}
#endif
Translate();
void Translate()
{
@ -107,9 +85,8 @@ namespace SystemTrayMenu.UserInterface
}
checkBoxCheckForUpdates.IsChecked = Settings.Default.CheckForUpdates;
#if TODO // HOTKEY
textBoxHotkey.SetHotkey(Settings.Default.HotKey);
#endif
InitializeLanguage();
void InitializeLanguage()
@ -380,21 +357,6 @@ namespace SystemTrayMenu.UserInterface
Closed += (_, _) => settingsForm = null;
}
#if TODO // HOTKEY
/// <summary>
/// Gets NewHotKey.
/// </summary>
public string NewHotKey { get; } = string.Empty;
/// <summary>
/// Registers all hotkeys as configured, displaying a dialog in case of hotkey conflicts with other tools.
/// </summary>
/// <returns>Whether the hotkeys could be registered to the users content. This also applies if conflicts arise and the user decides to ignore these (i.e. not to register the conflicting hotkey).</returns>
public static bool RegisterHotkeys()
{
return RegisterHotkeys(false);
}
#endif
public static void ShowSingleInstance()
{
if (IsOpen())
@ -413,102 +375,6 @@ namespace SystemTrayMenu.UserInterface
return settingsForm != null;
}
#if TODO // HOTKEY
/// <summary>
/// Helper method to cleanly register a hotkey.
/// </summary>
/// <param name="failedKeys">failedKeys.</param>
/// <param name="hotkeyString">hotkeyString.</param>
/// <param name="handler">handler.</param>
/// <returns>bool success.</returns>
private static bool RegisterHotkey(StringBuilder failedKeys, string hotkeyString, HotKeyHandler handler)
{
Keys modifierKeyCode = HotkeyModifiersFromString(hotkeyString);
Keys virtualKeyCode = HotkeyFromString(hotkeyString);
if (!Keys.None.Equals(virtualKeyCode))
{
if (RegisterHotKey(modifierKeyCode, virtualKeyCode, handler) < 0)
{
if (failedKeys.Length > 0)
{
failedKeys.Append(", ");
}
failedKeys.Append(hotkeyString);
return false;
}
}
return true;
}
private static bool RegisterWrapper(StringBuilder failedKeys, HotKeyHandler handler)
{
bool success = RegisterHotkey(
failedKeys,
Settings.Default.HotKey,
handler);
return success;
}
/// <summary>
/// Registers all hotkeys as configured, displaying a dialog in case of hotkey conflicts with other tools.
/// </summary>
/// <param name="ignoreFailedRegistration">if true, a failed hotkey registration will not be reported to the user - the hotkey will simply not be registered.</param>
/// <returns>Whether the hotkeys could be registered to the users content. This also applies if conflicts arise and the user decides to ignore these (i.e. not to register the conflicting hotkey).</returns>
private static bool RegisterHotkeys(bool ignoreFailedRegistration)
{
bool success = true;
StringBuilder failedKeys = new();
if (!RegisterWrapper(failedKeys, Handler))
{
success = false;
}
if (!success)
{
if (!ignoreFailedRegistration)
{
success = HandleFailedHotkeyRegistration(failedKeys.ToString());
}
}
return success || ignoreFailedRegistration;
}
private static void Handler()
{
}
/// <summary>
/// Displays a dialog for the user to choose how to handle hotkey registration failures:
/// retry (allowing to shut down the conflicting application before),
/// ignore (not registering the conflicting hotkey and resetting the respective config to "None", i.e. not trying to register it again on next startup)
/// abort (do nothing about it).
/// </summary>
/// <param name="failedKeys">comma separated list of the hotkeys that could not be registered, for display in dialog text.</param>
/// <returns>bool success.</returns>
private static bool HandleFailedHotkeyRegistration(string failedKeys)
{
bool success = false;
string warningTitle = Translator.GetText("Warning");
string message = Translator.GetText("Could not register the hot key.") + failedKeys;
DialogResult dr = MessageBox.Show(message, warningTitle, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Exclamation);
if (dr == DialogResult.Retry)
{
UnregisterHotkeys();
success = RegisterHotkeys(false);
}
else if (dr == DialogResult.Ignore)
{
UnregisterHotkeys();
success = RegisterHotkeys(true);
}
return success;
}
#endif
[SupportedOSPlatform("windows")]
private static void AddSetFolderByWindowsContextMenu()
{
@ -579,22 +445,9 @@ namespace SystemTrayMenu.UserInterface
private void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
if (e.Key == Key.Escape && !textBoxHotkey.Reassigning)
{
case Key.Escape:
#if TODO // HOTKEY
if (!inHotkey)
{
DialogResult = DialogResult.Cancel;
}
else
{
return base.ProcessCmdKey(ref msg, keyData);
}
#endif
break;
default:
break;
Close();
}
}
@ -882,27 +735,9 @@ namespace SystemTrayMenu.UserInterface
}
}
#if TODO // HOTKEY
private void TextBoxHotkeyEnter(object sender, EventArgs e)
{
UnregisterHotkeys();
inHotkey = true;
}
private void TextBoxHotkey_Leave(object sender, EventArgs e)
{
Settings.Default.HotKey =
new KeysConverter().ConvertToInvariantString(
textBoxHotkey.Hotkey | textBoxHotkey.HotkeyModifiers);
RegisterHotkeys();
inHotkey = false;
}
#endif
private void ButtonHotkeyDefault_Click(object sender, RoutedEventArgs e)
{
#if TODO // HOTKEY
textBoxHotkey.SetHotkey("Ctrl+Win+LWin");
#endif
textBoxHotkey.SetHotkey((string)Settings.Default.Properties["HotKey"].DefaultValue); // see Settings.Default.HotKey
}
private void ButtonGeneralDefault_Click(object sender, RoutedEventArgs e)

View file

@ -8,7 +8,7 @@ namespace SystemTrayMenu.Utilities
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using SystemTrayMenu.UserInterface;
using SystemTrayMenu.Helpers;
internal static class SingleAppInstance
{
@ -25,8 +25,8 @@ namespace SystemTrayMenu.Utilities
if (Properties.Settings.Default.SendHotkeyInsteadKillOtherInstances)
{
string hotKeyString = Properties.Settings.Default.HotKey;
ModifierKeys modifiers = HotkeyControl.HotkeyModifiersFromString(hotKeyString);
Key hotkey = HotkeyControl.HotkeyFromString(hotKeyString);
ModifierKeys modifiers = GlobalHotkeys.ModifierKeysFromString(hotKeyString);
Key hotkey = GlobalHotkeys.KeyFromString(hotKeyString);
try
{