Skip to content

Commit 547c769

Browse files
committed
GlobalKeyboardListener now tracks and reports modifier keys state
(imported from Perforce changelist 4168)
1 parent ffba504 commit 547c769

File tree

1 file changed

+112
-41
lines changed

1 file changed

+112
-41
lines changed

Src/GlobalKeyboardListener.cs

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,39 @@ namespace RT.Util
99
/// <summary>Manages a global low-level keyboard hook.</summary>
1010
public sealed class GlobalKeyboardListener
1111
{
12-
/// <summary>
13-
/// The collections of keys to watch for. This is ignored if <see cref="HookAllKeys"/> is set to true.
14-
/// </summary>
12+
/// <summary>The collections of keys to watch for. This is ignored if <see cref="HookAllKeys" /> is set to true.</summary>
1513
public List<Keys> HookedKeys { get { return _hookedKeys; } }
1614
private List<Keys> _hookedKeys = new List<Keys>();
1715

1816
/// <summary>
19-
/// Gets or sets a value indicating whether all keys are listened for. If this is set to true, <see cref="HookedKeys"/> is ignored.
20-
/// </summary>
17+
/// Gets or sets a value indicating whether all keys are listened for. If this is set to true, <see
18+
/// cref="HookedKeys" /> is ignored.</summary>
2119
public bool HookAllKeys { get; set; }
2220

23-
/// <summary>
24-
/// Handle to the hook, need this to unhook and call the next hook
25-
/// </summary>
21+
/// <summary>Handle to the hook, need this to unhook and call the next hook</summary>
2622
private IntPtr _hHook = IntPtr.Zero;
2723

24+
/// <summary>Current state of each modifier key.</summary>
25+
private bool _ctrl, _alt, _shift, _win;
26+
2827
#region Events
29-
/// <summary>
30-
/// Occurs when one of the hooked keys is pressed.
31-
/// </summary>
28+
/// <summary>Occurs when one of the hooked keys is pressed.</summary>
3229
public event GlobalKeyEventHandler KeyDown;
33-
/// <summary>
34-
/// Occurs when one of the hooked keys is released.
35-
/// </summary>
30+
/// <summary>Occurs when one of the hooked keys is released.</summary>
3631
public event GlobalKeyEventHandler KeyUp;
3732
#endregion
3833

3934
#region Constructors and Destructors
4035
/// <summary>
41-
/// Initializes a new instance of the <see cref="GlobalKeyboardListener"/> class and installs the keyboard hook.
42-
/// </summary>
36+
/// Initializes a new instance of the <see cref="GlobalKeyboardListener" /> class and installs the keyboard hook.</summary>
4337
public GlobalKeyboardListener()
4438
{
4539
hook();
4640
}
4741

4842
/// <summary>
49-
/// Releases unmanaged resources and performs other cleanup operations before the
50-
/// <see cref="GlobalKeyboardListener"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
51-
/// </summary>
43+
/// Releases unmanaged resources and performs other cleanup operations before the <see
44+
/// cref="GlobalKeyboardListener" /> is reclaimed by garbage collection and uninstalls the keyboard hook.</summary>
5245
~GlobalKeyboardListener()
5346
{
5447
unhook();
@@ -58,28 +51,28 @@ public GlobalKeyboardListener()
5851
private WinAPI.KeyboardHookProc _hook;
5952

6053
#region Public Methods
61-
/// <summary>
62-
/// Installs the global hook
63-
/// </summary>
54+
/// <summary>Installs the global hook</summary>
6455
private void hook()
6556
{
6657
IntPtr hInstance = WinAPI.LoadLibrary("User32");
6758
_hook = new WinAPI.KeyboardHookProc(hookProc);
6859
_hHook = WinAPI.SetWindowsHookEx(WinAPI.WH_KEYBOARD_LL, _hook, hInstance, 0);
6960
}
7061

71-
/// <summary>
72-
/// Uninstalls the global hook
73-
/// </summary>
62+
/// <summary>Uninstalls the global hook</summary>
7463
private void unhook()
7564
{
7665
WinAPI.UnhookWindowsHookEx(_hHook);
7766
}
7867

79-
/// <summary>The callback for the keyboard hook.</summary>
80-
/// <param name="code">The hook code. If this is &lt; 0, the callback shouldn’t do anyting.</param>
81-
/// <param name="wParam">The event type. Only <c>WM_(SYS)?KEY(DOWN|UP)</c> events are handled.</param>
82-
/// <param name="lParam">Information about the key pressed/released.</param>
68+
/// <summary>
69+
/// The callback for the keyboard hook.</summary>
70+
/// <param name="code">
71+
/// The hook code. If this is &lt; 0, the callback shouldn’t do anyting.</param>
72+
/// <param name="wParam">
73+
/// The event type. Only <c>WM_(SYS)?KEY(DOWN|UP)</c> events are handled.</param>
74+
/// <param name="lParam">
75+
/// Information about the key pressed/released.</param>
8376
private int hookProc(int code, int wParam, ref WinAPI.KeyboardHookStruct lParam)
8477
{
8578
if (code >= 0)
@@ -88,41 +81,119 @@ private int hookProc(int code, int wParam, ref WinAPI.KeyboardHookStruct lParam)
8881

8982
if (HookAllKeys || _hookedKeys.Contains(key))
9083
{
91-
var kea = new GlobalKeyEventArgs(key, lParam.scanCode);
92-
93-
if ((wParam == WinAPI.WM_KEYDOWN || wParam == WinAPI.WM_SYSKEYDOWN) && (KeyDown != null))
94-
KeyDown(this, kea);
95-
else if ((wParam == WinAPI.WM_KEYUP || wParam == WinAPI.WM_SYSKEYUP) && (KeyUp != null))
96-
KeyUp(this, kea);
97-
98-
if (kea.Handled)
99-
return 1;
84+
if ((wParam == WinAPI.WM_KEYDOWN || wParam == WinAPI.WM_SYSKEYDOWN))
85+
{
86+
switch (key)
87+
{
88+
case Keys.ControlKey:
89+
case Keys.LControlKey:
90+
case Keys.RControlKey: _ctrl = true; break;
91+
92+
case Keys.Menu:
93+
case Keys.LMenu:
94+
case Keys.RMenu: _alt = true; break;
95+
96+
case Keys.ShiftKey:
97+
case Keys.LShiftKey:
98+
case Keys.RShiftKey: _shift = true; break;
99+
100+
case Keys.LWin:
101+
case Keys.RWin: _win = true; break;
102+
}
103+
if (KeyDown != null)
104+
{
105+
var kea = new GlobalKeyEventArgs(key, lParam.scanCode, new ModifierKeysState(_ctrl, _alt, _shift, _win));
106+
KeyDown(this, kea);
107+
if (kea.Handled)
108+
return 1;
109+
}
110+
}
111+
else if ((wParam == WinAPI.WM_KEYUP || wParam == WinAPI.WM_SYSKEYUP))
112+
{
113+
switch (key)
114+
{
115+
case Keys.ControlKey:
116+
case Keys.LControlKey:
117+
case Keys.RControlKey: _ctrl = false; break;
118+
119+
case Keys.Menu:
120+
case Keys.LMenu:
121+
case Keys.RMenu: _alt = false; break;
122+
123+
case Keys.ShiftKey:
124+
case Keys.LShiftKey:
125+
case Keys.RShiftKey: _shift = false; break;
126+
127+
case Keys.LWin:
128+
case Keys.RWin: _win = false; break;
129+
}
130+
if (KeyUp != null)
131+
{
132+
var kea = new GlobalKeyEventArgs(key, lParam.scanCode, new ModifierKeysState(_ctrl, _alt, _shift, _win));
133+
KeyUp(this, kea);
134+
if (kea.Handled)
135+
return 1;
136+
}
137+
}
100138
}
101139
}
102140
return WinAPI.CallNextHookEx(_hHook, code, wParam, ref lParam);
103141
}
104142
#endregion
105143
}
106144

107-
/// <summary>Contains arguments for the KeyUp/KeyDown event in a <see cref="GlobalKeyboardListener"/>.</summary>
145+
/// <summary>Encapsulates the current state of modifier keys.</summary>
146+
public struct ModifierKeysState
147+
{
148+
private int _state;
149+
150+
/// <summary>Constructor.</summary>
151+
public ModifierKeysState(bool ctrl = false, bool alt = false, bool shift = false, bool win = false)
152+
{
153+
_state = (ctrl ? 1 : 0) | (alt ? 2 : 0) | (shift ? 4 : 0) | (win ? 8 : 0);
154+
}
155+
156+
/// <summary>Gets the state of the Control key (true if left OR right is down).</summary>
157+
public bool Ctrl { get { return (_state & 1) > 0; } }
158+
/// <summary>Gets the state of the Alt key (true if left OR right is down).</summary>
159+
public bool Alt { get { return (_state & 2) > 0; } }
160+
/// <summary>Gets the state of the Shift key (true if left OR right is down).</summary>
161+
public bool Shift { get { return (_state & 4) > 0; } }
162+
/// <summary>Gets the state of the Windows key (true if left OR right is down).</summary>
163+
public bool Win { get { return (_state & 8) > 0; } }
164+
165+
/// <summary>Compares the modifiers and returns true iff the two are equal.</summary>
166+
public static bool operator ==(ModifierKeysState k1, ModifierKeysState k2) { return k1._state == k2._state; }
167+
/// <summary>Compares the modifiers and returns true iff the two are not equal.</summary>
168+
public static bool operator !=(ModifierKeysState k1, ModifierKeysState k2) { return k1._state != k2._state; }
169+
/// <summary>Override; see base.</summary>
170+
public override bool Equals(object obj) { return (obj is ModifierKeysState) && (this == (ModifierKeysState) obj); }
171+
/// <summary>Override; see base.</summary>
172+
public override int GetHashCode() { return _state; }
173+
}
174+
175+
/// <summary>Contains arguments for the KeyUp/KeyDown event in a <see cref="GlobalKeyboardListener" />.</summary>
108176
public sealed class GlobalKeyEventArgs : EventArgs
109177
{
110178
/// <summary>The virtual-key code of the key being pressed or released.</summary>
111179
public Keys VirtualKeyCode { get; private set; }
112180
/// <summary>The scancode of the key being pressed or released.</summary>
113181
public int ScanCode { get; private set; }
182+
/// <summary>Current state of the modifier keys</summary>
183+
public ModifierKeysState ModifierKeys { get; private set; }
114184
/// <summary>Set this to ‘true’ to prevent further processing of the keystroke (i.e. to ‘swallow’ it).</summary>
115185
public bool Handled { get; set; }
116186

117187
/// <summary>Constructor.</summary>
118-
public GlobalKeyEventArgs(Keys virtualKeyCode, int scanCode)
188+
public GlobalKeyEventArgs(Keys virtualKeyCode, int scanCode, ModifierKeysState modifierKeys)
119189
{
120190
VirtualKeyCode = virtualKeyCode;
121191
ScanCode = scanCode;
192+
ModifierKeys = modifierKeys;
122193
Handled = false;
123194
}
124195
}
125196

126-
/// <summary>Used to trigger the KeyUp/KeyDown events in <see cref="GlobalKeyboardListener"/>.</summary>
197+
/// <summary>Used to trigger the KeyUp/KeyDown events in <see cref="GlobalKeyboardListener" />.</summary>
127198
public delegate void GlobalKeyEventHandler(object sender, GlobalKeyEventArgs e);
128199
}

0 commit comments

Comments
 (0)