From cee6557fb4778f3426bb0a7749729238e1cf887e Mon Sep 17 00:00:00 2001 From: Daniel Clark Date: Tue, 4 Mar 2025 17:00:00 +0000 Subject: [PATCH] Add HotKeyListener --- Toolkit.Foundation/Cache.cs | 114 ++---- Toolkit.Foundation/DefaultHostBuilder.cs | 1 - Toolkit.Foundation/ModifierKey.cs | 9 + Toolkit.Foundation/VirtualKey.cs | 348 +++++++++--------- Toolkit.UI.WinUI/RectExtensions.cs | 8 + Toolkit.UI.WinUI/WindowExtensions.cs | 65 ++-- Toolkit.Windows/HotKeyDescriptor.cs | 5 + Toolkit.Windows/HotKeyListener.cs | 49 +++ Toolkit.Windows/HotKeyManager.cs | 57 +++ Toolkit.Windows/HotKeyPressedEventArgs.cs | 3 + ...barButtonMonitor.cs => IHotKeyListener.cs} | 2 +- Toolkit.Windows/IHotKeyManager.cs | 11 + .../{IPointer.cs => IPointerListener.cs} | 2 +- Toolkit.Windows/ITaskbarButtonListener.cs | 7 + Toolkit.Windows/NativeMethods.txt | 4 +- Toolkit.Windows/NotifyIcon.cs | 2 +- .../{Pointer.cs => PointerListener.cs} | 26 +- Toolkit.Windows/TaskbarButton.cs | 2 +- ...tonMonitor.cs => TaskbarButtonListener.cs} | 6 +- 19 files changed, 424 insertions(+), 297 deletions(-) create mode 100644 Toolkit.Foundation/ModifierKey.cs create mode 100644 Toolkit.UI.WinUI/RectExtensions.cs create mode 100644 Toolkit.Windows/HotKeyDescriptor.cs create mode 100644 Toolkit.Windows/HotKeyListener.cs create mode 100644 Toolkit.Windows/HotKeyManager.cs create mode 100644 Toolkit.Windows/HotKeyPressedEventArgs.cs rename Toolkit.Windows/{ITaskbarButtonMonitor.cs => IHotKeyListener.cs} (69%) create mode 100644 Toolkit.Windows/IHotKeyManager.cs rename Toolkit.Windows/{IPointer.cs => IPointerListener.cs} (72%) create mode 100644 Toolkit.Windows/ITaskbarButtonListener.cs rename Toolkit.Windows/{Pointer.cs => PointerListener.cs} (93%) rename Toolkit.Windows/{TaskbarButtonMonitor.cs => TaskbarButtonListener.cs} (97%) diff --git a/Toolkit.Foundation/Cache.cs b/Toolkit.Foundation/Cache.cs index 1d16ee3..9646dce 100644 --- a/Toolkit.Foundation/Cache.cs +++ b/Toolkit.Foundation/Cache.cs @@ -22,48 +22,27 @@ public class Cache(IComparer comparer) : public void Clear() => items.Clear(); - public bool Contains(TValue item) - { - int index = items.IndexOf(item); - if (index >= 0) - { - return true; - } - - return false; - } + public bool Contains(TValue item) => + items.IndexOf(item) >= 0; public IEnumerator GetEnumerator() => items.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + items.GetEnumerator(); public int IndexOf(TValue item) => items.IndexOf(item); - public bool Remove(TValue item) + public bool Remove(TValue item) => + items.Remove(item); + + public bool TryGetValue(TValue key, + out TValue? item) { - int index = items.IndexOf(item); - if (index >= 0) - { - items.RemoveAt(index); - return true; - } - - return false; - } - - public bool TryGetValue(TValue key, out TValue? item) - { - int index = items.IndexOf(key); - if (index >= 0) - { - item = items[index]; - return true; - } - - item = default; - return false; + var index = items.IndexOf(key); + item = index >= 0 ? items[index] : default; + return index >= 0; } private int FindInsertIndex(TValue item) @@ -94,20 +73,20 @@ public class Cache(IComparer comparer) : } } -public class Cache(IComparer comparer) : +public class Cache(IComparer? comparer = null) : ICache - where TKey : - notnull - where TValue : - notnull + where TKey : notnull + where TValue : notnull { + private readonly IComparer comparer = comparer ?? Comparer.Default; private readonly List> items = []; public TValue? this[TKey key] { get { - int index = items.BinarySearch(new KeyValuePair(key, default), + int index = items.BinarySearch( + new KeyValuePair(key, default), new KeyValuePairComparer(comparer)); if (index >= 0) @@ -119,7 +98,8 @@ public class Cache(IComparer comparer) : } set { - int index = items.BinarySearch(new KeyValuePair(key, default), + int index = items.BinarySearch( + new KeyValuePair(key, default), new KeyValuePairComparer(comparer)); if (index >= 0) @@ -135,7 +115,8 @@ public class Cache(IComparer comparer) : public void Add(TKey key, TValue value) { - int index = items.BinarySearch(new KeyValuePair(key, default), + int index = items.BinarySearch( + new KeyValuePair(key, default), new KeyValuePairComparer(comparer)); if (index < 0) @@ -148,53 +129,28 @@ public class Cache(IComparer comparer) : public void Clear() => items.Clear(); - public bool Contains(TKey key) - { - int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); - if (index >= 0) - { - items.RemoveAt(index); - return true; - } - return false; - } + public bool Contains(TKey key) => + items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0) >= 0; - public IEnumerator> GetEnumerator() => - items.GetEnumerator(); + public IEnumerator> GetEnumerator() => + items.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public int IndexOf(TKey key) => + public int IndexOf(TKey key) => items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); - public bool Remove(TKey key) - { - int index = items.FindIndex(kvp => comparer.Compare(kvp.Key, key) == 0); - if (index >= 0) - { - items.RemoveAt(index); - return true; - } - return false; - } + public bool Remove(TKey key) => + items.RemoveAll(kvp => comparer.Compare(kvp.Key, key) == 0) > 0; - public bool TryGetValue(TKey key, out TValue? value) - { - int index = items.BinarySearch(new KeyValuePair(key, default(TValue)), - new KeyValuePairComparer(comparer)); + public bool TryGetValue(TKey key, out TValue? value) => + (items.BinarySearch(new KeyValuePair(key, default), + new KeyValuePairComparer(comparer)) is int index && index >= 0) + ? (value = items[index].Value, true).Item2 + : (value = default, false).Item2; - if (index >= 0) - { - value = items[index].Value; - return true; - } - - value = default; - return false; - } - - private class KeyValuePairComparer(IComparer comparer) : + private class KeyValuePairComparer(IComparer comparer) : IComparer> { private readonly IComparer comparer = comparer ?? Comparer.Default; diff --git a/Toolkit.Foundation/DefaultHostBuilder.cs b/Toolkit.Foundation/DefaultHostBuilder.cs index 13255de..ed79e59 100644 --- a/Toolkit.Foundation/DefaultHostBuilder.cs +++ b/Toolkit.Foundation/DefaultHostBuilder.cs @@ -1,7 +1,6 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using System; namespace Toolkit.Foundation; diff --git a/Toolkit.Foundation/ModifierKey.cs b/Toolkit.Foundation/ModifierKey.cs new file mode 100644 index 0000000..6751f85 --- /dev/null +++ b/Toolkit.Foundation/ModifierKey.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public enum ModifierKey : uint +{ + Alt = 0x00000001, + Ctrl = 0x00000002, + Shift = 0x00000004, + Win = 0x00000008, +} diff --git a/Toolkit.Foundation/VirtualKey.cs b/Toolkit.Foundation/VirtualKey.cs index d02ae56..ddcd736 100644 --- a/Toolkit.Foundation/VirtualKey.cs +++ b/Toolkit.Foundation/VirtualKey.cs @@ -1,175 +1,181 @@ namespace Toolkit.Foundation; -public enum VirtualKey +public enum VirtualKey : uint { - None = 0, - LeftButton = 1, - RightButton = 2, - Cancel = 3, - MiddleButton = 4, - XButton1 = 5, - XButton2 = 6, - Back = 8, - Tab = 9, - Clear = 12, - Enter = 13, - Shift = 16, - Control = 17, - Menu = 18, - Pause = 19, - CapitalLock = 20, - Kana = 21, - Hangul = 21, - Junja = 23, - Final = 24, - Hanja = 25, - Kanji = 25, - Escape = 27, - Convert = 28, - NonConvert = 29, - Accept = 30, - ModeChange = 31, - Space = 32, - PageUp = 33, - PageDown = 34, - End = 35, - Home = 36, - Left = 37, - Up = 38, - Right = 39, - Down = 40, - Select = 41, - Print = 42, - Execute = 43, - Snapshot = 44, - Insert = 45, - Delete = 46, - Help = 47, - Number0 = 48, - Number1 = 49, - Number2 = 50, - Number3 = 51, - Number4 = 52, - Number5 = 53, - Number6 = 54, - Number7 = 55, - Number8 = 56, - Number9 = 57, - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LeftWindows = 91, - RightWindows = 92, - Application = 93, - Sleep = 95, - NumberPad0 = 96, - NumberPad1 = 97, - NumberPad2 = 98, - NumberPad3 = 99, - NumberPad4 = 100, - NumberPad5 = 101, - NumberPad6 = 102, - NumberPad7 = 103, - NumberPad8 = 104, - NumberPad9 = 105, - Multiply = 106, - Add = 107, - Separator = 108, - Subtract = 109, - Decimal = 110, - Divide = 111, - F1 = 112, - F2 = 113, - F3 = 114, - F4 = 115, - F5 = 116, - F6 = 117, - F7 = 118, - F8 = 119, - F9 = 120, - F10 = 121, - F11 = 122, - F12 = 123, - F13 = 124, - F14 = 125, - F15 = 126, - F16 = 127, - F17 = 128, - F18 = 129, - F19 = 130, - F20 = 131, - F21 = 132, - F22 = 133, - F23 = 134, - F24 = 135, - NavigationView = 136, - NavigationMenu = 137, - NavigationUp = 138, - NavigationDown = 139, - NavigationLeft = 140, - NavigationRight = 141, - NavigationAccept = 142, - NavigationCancel = 143, - NumberKeyLock = 144, - Scroll = 145, - LeftShift = 160, - RightShift = 161, - LeftControl = 162, - RightControl = 163, - LeftMenu = 164, - RightMenu = 165, - GoBack = 166, - GoForward = 167, - Refresh = 168, - Stop = 169, - Search = 170, - Favorites = 171, - GoHome = 172, - GamepadA = 195, - GamepadB = 196, - GamepadX = 197, - GamepadY = 198, - GamepadRightShoulder = 199, - GamepadLeftShoulder = 200, - GamepadLeftTrigger = 201, - GamepadRightTrigger = 202, - GamepadDPadUp = 203, - GamepadDPadDown = 204, - GamepadDPadLeft = 205, - GamepadDPadRight = 206, - GamepadMenu = 207, - GamepadView = 208, - GamepadLeftThumbstickButton = 209, - GamepadRightThumbstickButton = 210, - GamepadLeftThumbstickUp = 211, - GamepadLeftThumbstickDown = 212, - GamepadLeftThumbstickRight = 213, - GamepadLeftThumbstickLeft = 214, - GamepadRightThumbstickUp = 215, - GamepadRightThumbstickDown = 216, - GamepadRightThumbstickRight = 217, - GamepadRightThumbstickLeft = 218 + None = 0x0000, + + Back = 0x0008, + Tab = 0x0009, + Clear = 0x000C, + Enter = 0x000D, + Shift = 0x0010, + Control = 0x0011, + Menu = 0x0012, + Pause = 0x0013, + CapitalLock = 0x0014, + Kana = 0x0015, + Hangul = 0x0015, + Junja = 0x0017, + Final = 0x0018, + Hanja = 0x0019, + Kanji = 0x0019, + Escape = 0x001B, + Convert = 0x001C, + NonConvert = 0x001D, + Accept = 0x001E, + ModeChange = 0x001F, + Space = 0x0020, + PageUp = 0x0021, + PageDown = 0x0022, + End = 0x0023, + Home = 0x0024, + Left = 0x0025, + Up = 0x0026, + Right = 0x0027, + Down = 0x0028, + Select = 0x0029, + Print = 0x002A, + Execute = 0x002B, + Snapshot = 0x002C, + Insert = 0x002D, + Delete = 0x002E, + Help = 0x002F, + + Number0 = 0x0030, + Number1 = 0x0031, + Number2 = 0x0032, + Number3 = 0x0033, + Number4 = 0x0034, + Number5 = 0x0035, + Number6 = 0x0036, + Number7 = 0x0037, + Number8 = 0x0038, + Number9 = 0x0039, + + A = 0x0041, + B = 0x0042, + C = 0x0043, + D = 0x0044, + E = 0x0045, + F = 0x0046, + G = 0x0047, + H = 0x0048, + I = 0x0049, + J = 0x004A, + K = 0x004B, + L = 0x004C, + M = 0x004D, + N = 0x004E, + O = 0x004F, + P = 0x0050, + Q = 0x0051, + R = 0x0052, + S = 0x0053, + T = 0x0054, + U = 0x0055, + V = 0x0056, + W = 0x0057, + X = 0x0058, + Y = 0x0059, + Z = 0x005A, + + LeftWindows = 0x005B, + RightWindows = 0x005C, + Application = 0x005D, + Sleep = 0x005F, + + NumberPad0 = 0x0060, + NumberPad1 = 0x0061, + NumberPad2 = 0x0062, + NumberPad3 = 0x0063, + NumberPad4 = 0x0064, + NumberPad5 = 0x0065, + NumberPad6 = 0x0066, + NumberPad7 = 0x0067, + NumberPad8 = 0x0068, + NumberPad9 = 0x0069, + + Multiply = 0x006A, + Add = 0x006B, + Separator = 0x006C, + Subtract = 0x006D, + Decimal = 0x006E, + Divide = 0x006F, + + F1 = 0x0070, + F2 = 0x0071, + F3 = 0x0072, + F4 = 0x0073, + F5 = 0x0074, + F6 = 0x0075, + F7 = 0x0076, + F8 = 0x0077, + F9 = 0x0078, + F10 = 0x0079, + F11 = 0x007A, + F12 = 0x007B, + F13 = 0x007C, + F14 = 0x007D, + F15 = 0x007E, + F16 = 0x007F, + F17 = 0x0080, + F18 = 0x0081, + F19 = 0x0082, + F20 = 0x0083, + F21 = 0x0084, + F22 = 0x0085, + F23 = 0x0086, + F24 = 0x0087, + + NavigationView = 0x0088, + NavigationMenu = 0x0089, + NavigationUp = 0x008A, + NavigationDown = 0x008B, + NavigationLeft = 0x008C, + NavigationRight = 0x008D, + NavigationAccept = 0x008E, + NavigationCancel = 0x008F, + + NumberKeyLock = 0x0090, + Scroll = 0x0091, + + LeftShift = 0x00A0, + RightShift = 0x00A1, + LeftControl = 0x00A2, + RightControl = 0x00A3, + LeftMenu = 0x00A4, + RightMenu = 0x00A5, + + GoBack = 0x00A6, + GoForward = 0x00A7, + Refresh = 0x00A8, + Stop = 0x00A9, + Search = 0x00AA, + Favorites = 0x00AB, + GoHome = 0x00AC, + + GamepadA = 0x00C3, + GamepadB = 0x00C4, + GamepadX = 0x00C5, + GamepadY = 0x00C6, + GamepadRightShoulder = 0x00C7, + GamepadLeftShoulder = 0x00C8, + GamepadLeftTrigger = 0x00C9, + GamepadRightTrigger = 0x00CA, + GamepadDPadUp = 0x00CB, + GamepadDPadDown = 0x00CC, + GamepadDPadLeft = 0x00CD, + GamepadDPadRight = 0x00CE, + GamepadMenu = 0x00CF, + GamepadView = 0x00D0, + GamepadLeftThumbstickButton = 0x00D1, + GamepadRightThumbstickButton = 0x00D2, + GamepadLeftThumbstickUp = 0x00D3, + GamepadLeftThumbstickDown = 0x00D4, + GamepadLeftThumbstickRight = 0x00D5, + GamepadLeftThumbstickLeft = 0x00D6, + GamepadRightThumbstickUp = 0x00D7, + GamepadRightThumbstickDown = 0x00D8, + GamepadRightThumbstickRight = 0x00D9, + GamepadRightThumbstickLeft = 0x00DA, } \ No newline at end of file diff --git a/Toolkit.UI.WinUI/RectExtensions.cs b/Toolkit.UI.WinUI/RectExtensions.cs new file mode 100644 index 0000000..cfc924b --- /dev/null +++ b/Toolkit.UI.WinUI/RectExtensions.cs @@ -0,0 +1,8 @@ +using Rect = Windows.Foundation.Rect; + +namespace Toolkit.UI.WinUI; + +public static class RectExtensions +{ + public static Rect ToWindowsRect(this Windows.Rect rect) => new(rect.X, rect.Y, rect.Width, rect.Height); +} diff --git a/Toolkit.UI.WinUI/WindowExtensions.cs b/Toolkit.UI.WinUI/WindowExtensions.cs index 7cb3c99..69ac59d 100644 --- a/Toolkit.UI.WinUI/WindowExtensions.cs +++ b/Toolkit.UI.WinUI/WindowExtensions.cs @@ -8,9 +8,10 @@ using System.Drawing; using Windows.Win32.UI.Shell; using Windows.Win32.UI.WindowsAndMessaging; using Toolkit.Windows; -using Rect = Windows.Foundation.Rect; using WinUIEx; using Windows.Graphics; +using Rect = Windows.Foundation.Rect; +using System; namespace Toolkit.UI.WinUI; @@ -18,6 +19,14 @@ public static partial class WindowExtensions { private static SUBCLASSPROC? SubClassDelegate; + public static uint GetDpi(this Window window) + { + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return 0; + + return PInvoke.GetDpiForWindow(new HWND(handle)); + } + public static void Hide(this Window window) { nint handle = WindowNative.GetWindowHandle(window); @@ -26,7 +35,7 @@ public static partial class WindowExtensions WindowHelper.HideWindow(new HWND(handle)); } - public static void IsShownInSwitchers(this Window window, + public static void SetIsShownInSwitchers(this Window window, bool value) { if (window.AppWindow is AppWindow appWindow) @@ -35,17 +44,6 @@ public static partial class WindowExtensions } } - public static void SetSize(this Window window, - int width, - int height) - { - nint handle = WindowNative.GetWindowHandle(window); - if (handle == 0) return; - - float value = PInvoke.GetDpiForWindow(new HWND(handle)) / 96f; - window.AppWindow.Resize(new SizeInt32((int)(width * (double)value), (int)(height * (double)value))); - } - public static void MoveAndResize(this Window window, Rect rect) { @@ -87,19 +85,32 @@ public static partial class WindowExtensions nint handle = WindowNative.GetWindowHandle(window); if (handle == 0) return; - WindowHelper.SetForegroundWindow(new HWND(handle)); + PInvoke.SetForegroundWindow(new HWND(handle)); } - public static void SetTopMost(this Window window, - bool value) + public static void SetIsMaximizable(this Window window, bool value) => + window.UpdateOverlappedPresenter(presenter => presenter.IsMaximizable = value); + + public static void SetIsMinimizable(this Window window, bool value) => + window.UpdateOverlappedPresenter(presenter => presenter.IsMinimizable = value); + + public static void SetIsResizable(this Window window, bool value) => + window.UpdateOverlappedPresenter(presenter => presenter.IsResizable = value); + + public static void SetSize(this Window window, + int width, + int height) { - if (window.AppWindow is AppWindow appWindow && - appWindow.Presenter is OverlappedPresenter presenter) - { - presenter.IsAlwaysOnTop = value; - } + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return; + + float value = PInvoke.GetDpiForWindow(new HWND(handle)) / 96f; + window.AppWindow.Resize(new SizeInt32((int)(width * (double)value), (int)(height * (double)value))); } + public static void SetIsTopMost(this Window window, bool value) => + window.UpdateOverlappedPresenter(presenter => presenter.IsAlwaysOnTop = value); + public static void SetTransparency(this Window window, bool value) { @@ -136,7 +147,6 @@ public static partial class WindowExtensions WINDOW_EX_STYLE exStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); _ = PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)(exStyle & ~WINDOW_EX_STYLE.WS_EX_LAYERED)); } - private static unsafe void EnableTransparency(HWND hWnd) { SubClassDelegate = new SUBCLASSPROC(WindowSubClass); @@ -147,12 +157,21 @@ public static partial class WindowExtensions (int)(exStyle | WINDOW_EX_STYLE.WS_EX_LAYERED)); COLORREF blackColor = new COLORREF((uint)ToWin32(Color.Black)); - _ = PInvoke.SetLayeredWindowAttributes(hWnd, blackColor, 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | + _ = PInvoke.SetLayeredWindowAttributes(hWnd, blackColor, 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); } private static int ToWin32(Color c) => c.B << 16 | c.G << 8 | c.R; + private static void UpdateOverlappedPresenter(this Window window, + Action action) + { + if (window.AppWindow.Presenter is OverlappedPresenter presenter) + { + action(presenter); + } + } + private static unsafe LRESULT WindowSubClass(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData) { diff --git a/Toolkit.Windows/HotKeyDescriptor.cs b/Toolkit.Windows/HotKeyDescriptor.cs new file mode 100644 index 0000000..3b21000 --- /dev/null +++ b/Toolkit.Windows/HotKeyDescriptor.cs @@ -0,0 +1,5 @@ +using Toolkit.Foundation; + +namespace Toolkit.Windows; + +public record HotKeyDescriptor(ModifierKey Modifiers, VirtualKey VirtualKey); diff --git a/Toolkit.Windows/HotKeyListener.cs b/Toolkit.Windows/HotKeyListener.cs new file mode 100644 index 0000000..056b3dc --- /dev/null +++ b/Toolkit.Windows/HotKeyListener.cs @@ -0,0 +1,49 @@ +using CommunityToolkit.Mvvm.Messaging; +using Toolkit.Foundation; + +namespace Toolkit.Windows; + +public class HotKeyListener(ICache cache, + IMessenger messenger) : + IHotKeyListener, + IRecipient +{ + private bool isDisposed; + + ~HotKeyListener() + { + Dispose(false); + } + + public void Initialize() + { + messenger.RegisterAll(this); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + } + } + + public void Receive(WndProcEventArgs message) + { + const int WM_HOTKEY = 0x0312; + if (message.Message == WM_HOTKEY) + { + int key = (int)message.WParam; + if (cache.Contains(key)) + { + messenger.Send(new HotKeyPressedEventArgs(key)); + } + } + } +} diff --git a/Toolkit.Windows/HotKeyManager.cs b/Toolkit.Windows/HotKeyManager.cs new file mode 100644 index 0000000..12ed846 --- /dev/null +++ b/Toolkit.Windows/HotKeyManager.cs @@ -0,0 +1,57 @@ +using Toolkit.Foundation; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; + +namespace Toolkit.Windows; + +public class HotKeyManager(IWndProc wndProc, + ICache cache) : + IHotKeyManager +{ + public void Add(int key, HotKeyDescriptor descriptor) + { + HOT_KEY_MODIFIERS modifiers = 0; + + if ((descriptor.Modifiers & ModifierKey.Alt) == ModifierKey.Alt) + modifiers |= HOT_KEY_MODIFIERS.MOD_ALT; + if ((descriptor.Modifiers & ModifierKey.Ctrl) == ModifierKey.Ctrl) + modifiers |= HOT_KEY_MODIFIERS.MOD_CONTROL; + if ((descriptor.Modifiers & ModifierKey.Shift) == ModifierKey.Shift) + modifiers |= HOT_KEY_MODIFIERS.MOD_SHIFT; + if ((descriptor.Modifiers & ModifierKey.Win) == ModifierKey.Win) + modifiers |= HOT_KEY_MODIFIERS.MOD_WIN; + + uint vk = (uint)descriptor.VirtualKey; + if (PInvoke.RegisterHotKey(new HWND(wndProc.Handle), key, modifiers, vk)) + { + cache.Add(key, descriptor); + } + } + + public bool Contains(int key) => + cache.Contains(key); + + public void Remove(int key) + { + PInvoke.UnregisterHotKey(new HWND(wndProc.Handle), key); + cache.Remove(key); + } + + public void Dispose() + { + HWND hwnd = new(wndProc.Handle); + foreach (KeyValuePair item in cache) + { + PInvoke.UnregisterHotKey(hwnd, item.Key); + } + + cache.Clear(); + GC.SuppressFinalize(this); + } + + ~HotKeyManager() + { + Dispose(); + } +} diff --git a/Toolkit.Windows/HotKeyPressedEventArgs.cs b/Toolkit.Windows/HotKeyPressedEventArgs.cs new file mode 100644 index 0000000..91f0274 --- /dev/null +++ b/Toolkit.Windows/HotKeyPressedEventArgs.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Windows; + +public record HotKeyPressedEventArgs(int Key); diff --git a/Toolkit.Windows/ITaskbarButtonMonitor.cs b/Toolkit.Windows/IHotKeyListener.cs similarity index 69% rename from Toolkit.Windows/ITaskbarButtonMonitor.cs rename to Toolkit.Windows/IHotKeyListener.cs index 6c623a2..21ec386 100644 --- a/Toolkit.Windows/ITaskbarButtonMonitor.cs +++ b/Toolkit.Windows/IHotKeyListener.cs @@ -2,6 +2,6 @@ namespace Toolkit.Windows; -public interface ITaskbarButtonMonitor : +public interface IHotKeyListener : IInitialization, IDisposable; diff --git a/Toolkit.Windows/IHotKeyManager.cs b/Toolkit.Windows/IHotKeyManager.cs new file mode 100644 index 0000000..14d5767 --- /dev/null +++ b/Toolkit.Windows/IHotKeyManager.cs @@ -0,0 +1,11 @@ +namespace Toolkit.Windows; + +public interface IHotKeyManager : + IDisposable +{ + void Add(int key, HotKeyDescriptor descriptor); + + bool Contains(int key); + + void Remove(int key); +} diff --git a/Toolkit.Windows/IPointer.cs b/Toolkit.Windows/IPointerListener.cs similarity index 72% rename from Toolkit.Windows/IPointer.cs rename to Toolkit.Windows/IPointerListener.cs index f0990b7..dae80cb 100644 --- a/Toolkit.Windows/IPointer.cs +++ b/Toolkit.Windows/IPointerListener.cs @@ -2,6 +2,6 @@ namespace Toolkit.Windows; -public interface IPointer : +public interface IPointerListener : IInitialization, IDisposable; \ No newline at end of file diff --git a/Toolkit.Windows/ITaskbarButtonListener.cs b/Toolkit.Windows/ITaskbarButtonListener.cs new file mode 100644 index 0000000..d1d6225 --- /dev/null +++ b/Toolkit.Windows/ITaskbarButtonListener.cs @@ -0,0 +1,7 @@ +using Toolkit.Foundation; + +namespace Toolkit.Windows; + +public interface ITaskbarButtonListener : + IInitialization, + IDisposable; diff --git a/Toolkit.Windows/NativeMethods.txt b/Toolkit.Windows/NativeMethods.txt index cf8ad71..d1fe4a5 100644 --- a/Toolkit.Windows/NativeMethods.txt +++ b/Toolkit.Windows/NativeMethods.txt @@ -48,4 +48,6 @@ TranslateMessage DispatchMessage PostQuitMessage DestroyWindow -GetModuleHandle \ No newline at end of file +GetModuleHandle +RegisterHotKey +UnregisterHotKey \ No newline at end of file diff --git a/Toolkit.Windows/NotifyIcon.cs b/Toolkit.Windows/NotifyIcon.cs index 0603311..ec1fa7c 100644 --- a/Toolkit.Windows/NotifyIcon.cs +++ b/Toolkit.Windows/NotifyIcon.cs @@ -62,7 +62,7 @@ public class NotifyIcon(IWndProc wndProc, GC.SuppressFinalize(this); } - public unsafe PointerLocation GetPointerPosition() + public static unsafe PointerLocation GetPointerPosition() { Point point = new(); _ = PInvoke.GetCursorPos(&point); diff --git a/Toolkit.Windows/Pointer.cs b/Toolkit.Windows/PointerListener.cs similarity index 93% rename from Toolkit.Windows/Pointer.cs rename to Toolkit.Windows/PointerListener.cs index 6a044d6..c63c180 100644 --- a/Toolkit.Windows/Pointer.cs +++ b/Toolkit.Windows/PointerListener.cs @@ -1,14 +1,13 @@ using CommunityToolkit.Mvvm.Messaging; using System.Diagnostics.CodeAnalysis; -using System.Drawing; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.WindowsAndMessaging; namespace Toolkit.Windows; -public class Pointer(IMessenger messenger) : - IPointer +public class PointerListener(IMessenger messenger) : + IPointerListener { private bool isDisposed; private bool isPointerDrag; @@ -16,7 +15,7 @@ public class Pointer(IMessenger messenger) : private HOOKPROC? mouseEventDelegate; private UnhookWindowsHookExSafeHandle? mouseHandle; - ~Pointer() + ~PointerListener() { Dispose(false); } @@ -27,25 +26,22 @@ public class Pointer(IMessenger messenger) : GC.SuppressFinalize(this); } - public unsafe void Initialize() => - InitializeHook(); + public unsafe void Initialize() + { + mouseEventDelegate = new HOOKPROC(MouseProc); + mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, + PInvoke.GetModuleHandle("user32.dll"), 0); + } protected virtual void Dispose(bool disposing) { if (!isDisposed) { - RemoveHook(); + Remove(); isDisposed = true; } } - private unsafe void InitializeHook() - { - mouseEventDelegate = new HOOKPROC(MouseProc); - mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, - PInvoke.GetModuleHandle("user32.dll"), 0); - } - private LRESULT MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) @@ -82,7 +78,7 @@ public class Pointer(IMessenger messenger) : return PInvoke.CallNextHookEx(mouseHandle, nCode, wParam, lParam); } - private unsafe void RemoveHook() + private unsafe void Remove() { if (mouseHandle is not null && mouseHandle.DangerousGetHandle() != nint.Zero) { diff --git a/Toolkit.Windows/TaskbarButton.cs b/Toolkit.Windows/TaskbarButton.cs index 416fd81..25c856b 100644 --- a/Toolkit.Windows/TaskbarButton.cs +++ b/Toolkit.Windows/TaskbarButton.cs @@ -42,7 +42,7 @@ public class TaskbarButton : public void Receive(PointerReleasedEventArgs args) { - if (!isDrag && isWithinBounds) + if (args.Button is PointerButton.Left && !isDrag && isWithinBounds) { messenger.Send(new TaskbarButtonInvokedEventArgs(this)); } diff --git a/Toolkit.Windows/TaskbarButtonMonitor.cs b/Toolkit.Windows/TaskbarButtonListener.cs similarity index 97% rename from Toolkit.Windows/TaskbarButtonMonitor.cs rename to Toolkit.Windows/TaskbarButtonListener.cs index 8714915..757abad 100644 --- a/Toolkit.Windows/TaskbarButtonMonitor.cs +++ b/Toolkit.Windows/TaskbarButtonListener.cs @@ -4,8 +4,8 @@ using UIAutomationClient; namespace Toolkit.Windows; -public class TaskbarButtonMonitor : - ITaskbarButtonMonitor +public class TaskbarButtonListener : + ITaskbarButtonListener { private readonly IDispatcherTimer dispatcherTimer; private readonly IDispatcherTimerFactory dispatcherTimerFactory; @@ -19,7 +19,7 @@ public class TaskbarButtonMonitor : private IUIAutomationElement? taskListElement; private IntPtr taskListHandle; - public TaskbarButtonMonitor(ITaskbarList taskbarList, + public TaskbarButtonListener(ITaskbarList taskbarList, IMessenger messenger, IDispatcherTimerFactory dispatcherTimerFactory, IServiceFactory serviceFactory,