193 lines
5.2 KiB
C#
193 lines
5.2 KiB
C#
using System.Runtime.InteropServices;
|
|
using System.ComponentModel;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
|
|
namespace TaskbarLauncher.Services;
|
|
|
|
public sealed class HotkeyService : IDisposable
|
|
{
|
|
private const int WmHotkey = 0x0312;
|
|
private const int HotkeyId = 9001;
|
|
|
|
private HwndSource? source;
|
|
private Window? window;
|
|
|
|
public event EventHandler? Pressed;
|
|
|
|
public void Register(string hotkey)
|
|
{
|
|
ParseHotkey(hotkey, out uint modifiers, out uint key);
|
|
Unregister();
|
|
|
|
window = new Window
|
|
{
|
|
Width = 0,
|
|
Height = 0,
|
|
ShowInTaskbar = false,
|
|
WindowStyle = WindowStyle.None,
|
|
AllowsTransparency = true,
|
|
Opacity = 0,
|
|
Left = -10000,
|
|
Top = -10000
|
|
};
|
|
|
|
window.SourceInitialized += (_, _) =>
|
|
{
|
|
var helper = new WindowInteropHelper(window);
|
|
source = HwndSource.FromHwnd(helper.Handle);
|
|
source?.AddHook(WndProc);
|
|
|
|
if (!RegisterHotKey(helper.Handle, HotkeyId, modifiers, key))
|
|
{
|
|
int error = Marshal.GetLastWin32Error();
|
|
throw new InvalidOperationException(
|
|
$"Windows could not register hotkey '{Normalize(hotkey)}'. It may already be used by another app.",
|
|
new Win32Exception(error));
|
|
}
|
|
};
|
|
|
|
window.Show();
|
|
window.Hide();
|
|
}
|
|
|
|
public void Unregister()
|
|
{
|
|
if (window is not null)
|
|
{
|
|
var helper = new WindowInteropHelper(window);
|
|
if (helper.Handle != IntPtr.Zero)
|
|
{
|
|
UnregisterHotKey(helper.Handle, HotkeyId);
|
|
}
|
|
}
|
|
|
|
source?.RemoveHook(WndProc);
|
|
source = null;
|
|
window?.Close();
|
|
window = null;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Unregister();
|
|
}
|
|
|
|
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
|
{
|
|
if (msg == WmHotkey && wParam.ToInt32() == HotkeyId)
|
|
{
|
|
Pressed?.Invoke(this, EventArgs.Empty);
|
|
handled = true;
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
public static string Normalize(string hotkey)
|
|
{
|
|
ParseHotkey(hotkey, out uint modifiers, out uint key);
|
|
Key parsedKey = KeyInterop.KeyFromVirtualKey((int)key);
|
|
List<string> parts = [];
|
|
|
|
if ((modifiers & 0x0002) != 0)
|
|
{
|
|
parts.Add("Ctrl");
|
|
}
|
|
|
|
if ((modifiers & 0x0001) != 0)
|
|
{
|
|
parts.Add("Alt");
|
|
}
|
|
|
|
if ((modifiers & 0x0004) != 0)
|
|
{
|
|
parts.Add("Shift");
|
|
}
|
|
|
|
if ((modifiers & 0x0008) != 0)
|
|
{
|
|
parts.Add("Win");
|
|
}
|
|
|
|
parts.Add(parsedKey.ToString());
|
|
return string.Join("+", parts);
|
|
}
|
|
|
|
private static void ParseHotkey(string hotkey, out uint modifiers, out uint key)
|
|
{
|
|
modifiers = 0;
|
|
key = 0;
|
|
|
|
string[] parts = hotkey
|
|
.Replace(",", "+", StringComparison.Ordinal)
|
|
.Replace(" ", "+", StringComparison.Ordinal)
|
|
.Split('+', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
foreach (string part in parts)
|
|
{
|
|
if (part.Equals("Ctrl", StringComparison.OrdinalIgnoreCase) || part.Equals("Control", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
modifiers |= 0x0002;
|
|
}
|
|
else if (part.Equals("Alt", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
modifiers |= 0x0001;
|
|
}
|
|
else if (part.Equals("Shift", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
modifiers |= 0x0004;
|
|
}
|
|
else if (part.Equals("Win", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
modifiers |= 0x0008;
|
|
}
|
|
else if (TryParseKey(part, out Key parsedKey))
|
|
{
|
|
key = (uint)KeyInterop.VirtualKeyFromKey(parsedKey);
|
|
}
|
|
else
|
|
{
|
|
throw new FormatException($"'{part}' is not a valid hotkey key.");
|
|
}
|
|
}
|
|
|
|
if (modifiers == 0)
|
|
{
|
|
throw new FormatException("Hotkeys must include at least one modifier: Ctrl, Alt, Shift, or Win.");
|
|
}
|
|
|
|
if (key == 0)
|
|
{
|
|
throw new FormatException("Hotkeys must include a final key, such as Space, S, F12, or D1.");
|
|
}
|
|
}
|
|
|
|
private static bool TryParseKey(string value, out Key key)
|
|
{
|
|
if (Enum.TryParse(value, true, out key))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (value.Length == 1 && char.IsLetter(value[0]))
|
|
{
|
|
return Enum.TryParse(value.ToUpperInvariant(), out key);
|
|
}
|
|
|
|
if (value.Length == 1 && char.IsDigit(value[0]))
|
|
{
|
|
return Enum.TryParse($"D{value}", out key);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
|
}
|