I stitched together from code I found in internet myself WH_KEYBOARD_LL
helper class:
Problem this had is now solved, thanks to Mattias S, following YourUtils.cs is fixed.
Put the following code to some of your utils libs, let it be YourUtils.cs:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace YOURUTILSNAMESPACE.Keyboard
{
/// <summary>
/// Listens keyboard globally.
///
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
/// </summary>
public class KeyboardListener : IDisposable
{
/// <summary>
/// Creates global keyboard listener.
/// </summary>
public KeyboardListener()
{
// We have to store the HookCallback, so that it is not garbage collected runtime
hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc;
// Set the hook
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
// Assign the asynchronous callback event
hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync);
}
/// <summary>
/// Destroys global keyboard listener.
/// </summary>
~KeyboardListener()
{
Dispose();
}
/// <summary>
/// Fired when any of the keys is pressed down.
/// </summary>
public event RawKeyEventHandler KeyDown;
/// <summary>
/// Fired when any of the keys is released.
/// </summary>
public event RawKeyEventHandler KeyUp;
#region Inner workings
/// <summary>
/// Hook ID
/// </summary>
private IntPtr hookId = IntPtr.Zero;
/// <summary>
/// Asynchronous callback hook.
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode);
/// <summary>
/// Actual callback hook.
///
/// <remarks>Calls asynchronously the asyncCallback.</remarks>
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP)
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent) wParam.ToUInt32(), Marshal.ReadInt32(lParam), null, null);
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
/// <summary>
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed.
/// </summary>
private KeyboardCallbackAsync hookedKeyboardCallbackAsync;
/// <summary>
/// Contains the hooked callback in runtime.
/// </summary>
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
/// <summary>
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events.
/// </summary>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode)
{
switch (keyEvent)
{
// KeyDown events
case InterceptKeys.KeyEvent.WM_KEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, true));
break;
// KeyUp events
case InterceptKeys.KeyEvent.WM_KEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, true));
break;
default:
break;
}
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposes the hook.
/// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks>
/// </summary>
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
#endregion
}
/// <summary>
/// Raw KeyEvent arguments.
/// </summary>
public class RawKeyEventArgs : EventArgs
{
/// <summary>
/// VKCode of the key.
/// </summary>
public int VKCode;
/// <summary>
/// WPF Key of the key.
/// </summary>
public Key Key;
/// <summary>
/// Is the hitted key system key.
/// </summary>
public bool IsSysKey;
/// <summary>
/// Create raw keyevent arguments.
/// </summary>
/// <param name="VKCode"></param>
/// <param name="isSysKey"></param>
public RawKeyEventArgs(int VKCode, bool isSysKey)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
/// <summary>
/// Raw keyevent handler.
/// </summary>
/// <param name="sender">sender</param>
/// <param name="args">raw keyevent arguments</param>
public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
#region WINAPI Helper class
/// <summary>
/// Winapi Key interception helper class.
/// </summary>
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;
public enum KeyEvent : int {
WM_KEYDOWN = 256,
WM_KEYUP = 257,
WM_SYSKEYUP = 261,
WM_SYSKEYDOWN = 260
}
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
#endregion
}
(The above method is asynchronous, meaning it won't stop the execution of callback method. If you for some reason want to intercept system keys (creating e.g. Alt+Tab replacement) and preventing windows of handling them you must change the BeginInvoke
to Invoke
. Also adding Handled
property to KeyDown, KeyUp events to see if it is being handled. After that you either call the CallNextHookEx
or not.)
Which I use like this:
App.xaml:
<Application ...
Startup="Application_Startup"
Exit="Application_Exit">
...
App.xaml.cs:
public partial class App : Application
{
KeyboardListener KListener = new KeyboardListener();
private void Application_Startup(object sender, StartupEventArgs e)
{
KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
Console.WriteLine(args.Key.ToString());
}
private void Application_Exit(object sender, ExitEventArgs e)
{
KListener.Dispose();
}
}
The problem is fixed, and can be seen in YourUtils.cs code at line // We have to store the HookCallback, so that it is not garbage collected runtime
Update There is other (improved) version available as GitHub forkable Gist though it does a bit different thing so I don't update the code above.