ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/NexusPowerControl/trunk/NexusPowerControl/keyboardlistener.cs
Revision: 14
Committed: Sat Oct 22 23:37:10 2011 UTC (11 years, 7 months ago) by william
File size: 16265 byte(s)
Log Message:
** set .Net Framework 4 rather than .Net Framework 4 Client Profile
Add keyboard hook support from
    // Obtained From: https://gist.github.com/471698
    // Modifications used from: http://stackoverflow.com/questions/3920315/ignore-keyboard-input

File Contents

# Content
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 }