1 |
using System; |
2 |
using System.Diagnostics; |
3 |
using System.Runtime.InteropServices; |
4 |
using System.Runtime.CompilerServices; |
5 |
using System.Windows.Input; |
6 |
using System.Windows.Threading; |
7 |
using System.Collections.Generic; |
8 |
|
9 |
namespace Ownskit.Utils |
10 |
{ |
11 |
|
12 |
// Obtained From: https://gist.github.com/471698 |
13 |
// Modifications used from: http://stackoverflow.com/questions/3920315/ignore-keyboard-input |
14 |
|
15 |
/// <summary> |
16 |
/// Listens keyboard globally. |
17 |
/// |
18 |
/// <remarks>Uses WH_KEYBOARD_LL.</remarks> |
19 |
/// </summary> |
20 |
public class KeyboardListener : IDisposable |
21 |
{ |
22 |
private bool _EnableKeyboard = true; |
23 |
|
24 |
public void DisableKeyboard() |
25 |
{ |
26 |
this._EnableKeyboard = false; |
27 |
} |
28 |
public void EnableKeyboard() |
29 |
{ |
30 |
this._EnableKeyboard = true; |
31 |
} |
32 |
/// <summary> |
33 |
/// Creates global keyboard listener. |
34 |
/// </summary> |
35 |
public KeyboardListener() |
36 |
{ |
37 |
this.EnableKeyboard(); |
38 |
// Dispatcher thread handling the KeyDown/KeyUp events. |
39 |
this.dispatcher = Dispatcher.CurrentDispatcher; |
40 |
|
41 |
// We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime |
42 |
hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc; |
43 |
|
44 |
// Set the hook |
45 |
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc); |
46 |
|
47 |
// Assign the asynchronous callback event |
48 |
hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync); |
49 |
} |
50 |
|
51 |
private Dispatcher dispatcher; |
52 |
|
53 |
/// <summary> |
54 |
/// Destroys global keyboard listener. |
55 |
/// </summary> |
56 |
~KeyboardListener() |
57 |
{ |
58 |
Dispose(); |
59 |
} |
60 |
|
61 |
/// <summary> |
62 |
/// Fired when any of the keys is pressed down. |
63 |
/// </summary> |
64 |
public event RawKeyEventHandler KeyDown; |
65 |
|
66 |
/// <summary> |
67 |
/// Fired when any of the keys is released. |
68 |
/// </summary> |
69 |
public event RawKeyEventHandler KeyUp; |
70 |
|
71 |
#region Inner workings |
72 |
|
73 |
/// <summary> |
74 |
/// Hook ID |
75 |
/// </summary> |
76 |
private IntPtr hookId = IntPtr.Zero; |
77 |
|
78 |
/// <summary> |
79 |
/// Asynchronous callback hook. |
80 |
/// </summary> |
81 |
/// <param name="character">Character</param> |
82 |
/// <param name="keyEvent">Keyboard event</param> |
83 |
/// <param name="vkCode">VKCode</param> |
84 |
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character); |
85 |
|
86 |
/// <summary> |
87 |
/// Actual callback hook. |
88 |
/// |
89 |
/// <remarks>Calls asynchronously the asyncCallback.</remarks> |
90 |
/// </summary> |
91 |
/// <param name="nCode"></param> |
92 |
/// <param name="wParam"></param> |
93 |
/// <param name="lParam"></param> |
94 |
/// <returns></returns> |
95 |
[MethodImpl(MethodImplOptions.NoInlining)] |
96 |
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) |
97 |
{ |
98 |
string chars = ""; |
99 |
|
100 |
if (nCode >= 0) |
101 |
if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || |
102 |
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP || |
103 |
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN || |
104 |
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP) |
105 |
{ |
106 |
// Captures the character(s) pressed only on WM_KEYDOWN |
107 |
chars = InterceptKeys.VKCodeToString((uint)Marshal.ReadInt32(lParam), |
108 |
(wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || |
109 |
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN)); |
110 |
|
111 |
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars, null, null); |
112 |
} |
113 |
|
114 |
return _EnableKeyboard ? InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam) : (IntPtr)1; |
115 |
} |
116 |
|
117 |
/// <summary> |
118 |
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed. |
119 |
/// </summary> |
120 |
private KeyboardCallbackAsync hookedKeyboardCallbackAsync; |
121 |
|
122 |
/// <summary> |
123 |
/// Contains the hooked callback in runtime. |
124 |
/// </summary> |
125 |
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc; |
126 |
|
127 |
/// <summary> |
128 |
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events. |
129 |
/// </summary> |
130 |
/// <param name="keyEvent">Keyboard event</param> |
131 |
/// <param name="vkCode">VKCode</param> |
132 |
/// <param name="character">Character as string.</param> |
133 |
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character) |
134 |
{ |
135 |
switch (keyEvent) |
136 |
{ |
137 |
// KeyDown events |
138 |
case InterceptKeys.KeyEvent.WM_KEYDOWN: |
139 |
if (KeyDown != null) |
140 |
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, false, character)); |
141 |
break; |
142 |
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN: |
143 |
if (KeyDown != null) |
144 |
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, true, character)); |
145 |
break; |
146 |
|
147 |
// KeyUp events |
148 |
case InterceptKeys.KeyEvent.WM_KEYUP: |
149 |
if (KeyUp != null) |
150 |
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, false, character)); |
151 |
break; |
152 |
case InterceptKeys.KeyEvent.WM_SYSKEYUP: |
153 |
if (KeyUp != null) |
154 |
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, true, character)); |
155 |
break; |
156 |
|
157 |
default: |
158 |
break; |
159 |
} |
160 |
} |
161 |
|
162 |
#endregion |
163 |
|
164 |
#region IDisposable Members |
165 |
|
166 |
/// <summary> |
167 |
/// Disposes the hook. |
168 |
/// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks> |
169 |
/// </summary> |
170 |
public void Dispose() |
171 |
{ |
172 |
InterceptKeys.UnhookWindowsHookEx(hookId); |
173 |
} |
174 |
|
175 |
#endregion |
176 |
} |
177 |
|
178 |
/// <summary> |
179 |
/// Raw KeyEvent arguments. |
180 |
/// </summary> |
181 |
public class RawKeyEventArgs : EventArgs |
182 |
{ |
183 |
/// <summary> |
184 |
/// VKCode of the key. |
185 |
/// </summary> |
186 |
public int VKCode; |
187 |
|
188 |
/// <summary> |
189 |
/// WPF Key of the key. |
190 |
/// </summary> |
191 |
public Key Key; |
192 |
|
193 |
/// <summary> |
194 |
/// Is the hitted key system key. |
195 |
/// </summary> |
196 |
public bool IsSysKey; |
197 |
|
198 |
/// <summary> |
199 |
/// Convert to string. |
200 |
/// </summary> |
201 |
/// <returns>Returns string representation of this key, if not possible empty string is returned.</returns> |
202 |
public override string ToString() |
203 |
{ |
204 |
return Character; |
205 |
} |
206 |
|
207 |
/// <summary> |
208 |
/// Unicode character of key pressed. |
209 |
/// </summary> |
210 |
public string Character; |
211 |
|
212 |
/// <summary> |
213 |
/// Create raw keyevent arguments. |
214 |
/// </summary> |
215 |
/// <param name="VKCode"></param> |
216 |
/// <param name="isSysKey"></param> |
217 |
/// <param name="Character">Character</param> |
218 |
public RawKeyEventArgs(int VKCode, bool isSysKey, string Character) |
219 |
{ |
220 |
this.VKCode = VKCode; |
221 |
this.IsSysKey = isSysKey; |
222 |
this.Character = Character; |
223 |
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode); |
224 |
} |
225 |
|
226 |
} |
227 |
|
228 |
/// <summary> |
229 |
/// Raw keyevent handler. |
230 |
/// </summary> |
231 |
/// <param name="sender">sender</param> |
232 |
/// <param name="args">raw keyevent arguments</param> |
233 |
public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args); |
234 |
|
235 |
#region WINAPI Helper class |
236 |
/// <summary> |
237 |
/// Winapi Key interception helper class. |
238 |
/// </summary> |
239 |
internal static class InterceptKeys |
240 |
{ |
241 |
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); |
242 |
public static int WH_KEYBOARD_LL = 13; |
243 |
|
244 |
/// <summary> |
245 |
/// Key event |
246 |
/// </summary> |
247 |
public enum KeyEvent : int { |
248 |
/// <summary> |
249 |
/// Key down |
250 |
/// </summary> |
251 |
WM_KEYDOWN = 256, |
252 |
|
253 |
/// <summary> |
254 |
/// Key up |
255 |
/// </summary> |
256 |
WM_KEYUP = 257, |
257 |
|
258 |
/// <summary> |
259 |
/// System key up |
260 |
/// </summary> |
261 |
WM_SYSKEYUP = 261, |
262 |
|
263 |
/// <summary> |
264 |
/// System key down |
265 |
/// </summary> |
266 |
WM_SYSKEYDOWN = 260 |
267 |
} |
268 |
|
269 |
public static IntPtr SetHook(LowLevelKeyboardProc proc) |
270 |
{ |
271 |
using (Process curProcess = Process.GetCurrentProcess()) |
272 |
using (ProcessModule curModule = curProcess.MainModule) |
273 |
{ |
274 |
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); |
275 |
} |
276 |
} |
277 |
|
278 |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] |
279 |
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); |
280 |
|
281 |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] |
282 |
[return: MarshalAs(UnmanagedType.Bool)] |
283 |
public static extern bool UnhookWindowsHookEx(IntPtr hhk); |
284 |
|
285 |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] |
286 |
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); |
287 |
|
288 |
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] |
289 |
public static extern IntPtr GetModuleHandle(string lpModuleName); |
290 |
|
291 |
#region Convert VKCode to string |
292 |
// Note: Sometimes single VKCode represents multiple chars, thus string. |
293 |
// E.g. typing "^1" (notice that when pressing 1 the both characters appear, |
294 |
// because of this behavior, "^" is called dead key) |
295 |
|
296 |
[DllImport("user32.dll")] |
297 |
private static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl); |
298 |
|
299 |
[DllImport("user32.dll")] |
300 |
private static extern bool GetKeyboardState(byte[] lpKeyState); |
301 |
|
302 |
[DllImport("user32.dll")] |
303 |
private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl); |
304 |
|
305 |
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] |
306 |
private static extern IntPtr GetKeyboardLayout(uint dwLayout); |
307 |
|
308 |
[DllImport("User32.dll")] |
309 |
private static extern IntPtr GetForegroundWindow(); |
310 |
|
311 |
[DllImport("User32.dll")] |
312 |
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); |
313 |
|
314 |
[DllImport("user32.dll")] |
315 |
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); |
316 |
|
317 |
[DllImport("kernel32.dll")] |
318 |
private static extern uint GetCurrentThreadId(); |
319 |
|
320 |
private static uint lastVKCode = 0; |
321 |
private static uint lastScanCode = 0; |
322 |
private static byte[] lastKeyState = new byte[255]; |
323 |
private static bool lastIsDead = false; |
324 |
|
325 |
/// <summary> |
326 |
/// Convert VKCode to Unicode. |
327 |
/// <remarks>isKeyDown is required for because of keyboard state inconsistencies!</remarks> |
328 |
/// </summary> |
329 |
/// <param name="VKCode">VKCode</param> |
330 |
/// <param name="isKeyDown">Is the key down event?</param> |
331 |
/// <returns>String representing single unicode character.</returns> |
332 |
public static string VKCodeToString(uint VKCode, bool isKeyDown) |
333 |
{ |
334 |
// ToUnicodeEx needs StringBuilder, it populates that during execution. |
335 |
System.Text.StringBuilder sbString = new System.Text.StringBuilder(5); |
336 |
|
337 |
byte[] bKeyState = new byte[255]; |
338 |
bool bKeyStateStatus; |
339 |
bool isDead = false; |
340 |
|
341 |
// Gets the current windows window handle, threadID, processID |
342 |
IntPtr currentHWnd = GetForegroundWindow(); |
343 |
uint currentProcessID; |
344 |
uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID); |
345 |
|
346 |
// This programs Thread ID |
347 |
uint thisProgramThreadId = GetCurrentThreadId(); |
348 |
|
349 |
// Attach to active thread so we can get that keyboard state |
350 |
if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID , true)) |
351 |
{ |
352 |
// Current state of the modifiers in keyboard |
353 |
bKeyStateStatus = GetKeyboardState(bKeyState); |
354 |
|
355 |
// Detach |
356 |
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false); |
357 |
} |
358 |
else |
359 |
{ |
360 |
// Could not attach, perhaps it is this process? |
361 |
bKeyStateStatus = GetKeyboardState(bKeyState); |
362 |
} |
363 |
|
364 |
// On failure we return empty string. |
365 |
if (!bKeyStateStatus) |
366 |
return ""; |
367 |
|
368 |
// Gets the layout of keyboard |
369 |
IntPtr HKL = GetKeyboardLayout(currentWindowThreadID); |
370 |
|
371 |
// Maps the virtual keycode |
372 |
uint lScanCode = MapVirtualKeyEx(VKCode, 0, HKL); |
373 |
|
374 |
// Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also. |
375 |
if (!isKeyDown) |
376 |
return ""; |
377 |
|
378 |
// Converts the VKCode to unicode |
379 |
int relevantKeyCountInBuffer = ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, sbString.Capacity, (uint)0, HKL); |
380 |
|
381 |
string ret = ""; |
382 |
|
383 |
switch (relevantKeyCountInBuffer) |
384 |
{ |
385 |
// Dead keys (^,`...) |
386 |
case -1: |
387 |
isDead = true; |
388 |
|
389 |
// We must clear the buffer because ToUnicodeEx messed it up, see below. |
390 |
ClearKeyboardBuffer(VKCode, lScanCode, HKL); |
391 |
break; |
392 |
|
393 |
case 0: |
394 |
break; |
395 |
|
396 |
// Single character in buffer |
397 |
case 1: |
398 |
ret = sbString[0].ToString(); |
399 |
break; |
400 |
|
401 |
// Two or more (only two of them is relevant) |
402 |
case 2: |
403 |
default: |
404 |
ret = sbString.ToString().Substring(0, 2); |
405 |
break; |
406 |
} |
407 |
|
408 |
// We inject the last dead key back, since ToUnicodeEx removed it. |
409 |
// More about this peculiar behavior see e.g: |
410 |
// http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_23453780.html |
411 |
// http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx |
412 |
// http://blogs.msdn.com/michkap/archive/2007/10/27/5717859.aspx |
413 |
if (lastVKCode != 0 && lastIsDead) |
414 |
{ |
415 |
System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5); |
416 |
ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL); |
417 |
lastVKCode = 0; |
418 |
|
419 |
return ret; |
420 |
} |
421 |
|
422 |
// Save these |
423 |
lastScanCode = lScanCode; |
424 |
lastVKCode = VKCode; |
425 |
lastIsDead = isDead; |
426 |
lastKeyState = (byte[])bKeyState.Clone(); |
427 |
|
428 |
return ret; |
429 |
} |
430 |
|
431 |
private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl) |
432 |
{ |
433 |
System.Text.StringBuilder sb = new System.Text.StringBuilder(10); |
434 |
|
435 |
int rc; |
436 |
do { |
437 |
byte[] lpKeyStateNull = new Byte[255]; |
438 |
rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl); |
439 |
} while(rc < 0); |
440 |
} |
441 |
#endregion |
442 |
} |
443 |
#endregion |
444 |
} |