/[NexusPowerControl]/trunk/NexusPowerControl/keyboardlistener.cs
ViewVC logotype

Annotation of /trunk/NexusPowerControl/keyboardlistener.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 14 - (hide annotations) (download)
Sat Oct 22 23:37:10 2011 UTC (8 years, 4 months ago) by william
File size: 16265 byte(s)
** 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

1 william 14 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     }

  ViewVC Help
Powered by ViewVC 1.1.22