Add HotKeyListener

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