Skip to content

Instantly share code, notes, and snippets.

@Ciantic
Created July 11, 2010 17:33
Show Gist options
  • Star 64 You must be signed in to star a gist
  • Fork 26 You must be signed in to fork a gist
  • Save Ciantic/471698 to your computer and use it in GitHub Desktop.
Save Ciantic/471698 to your computer and use it in GitHub Desktop.
C# Keyboard listener
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using System.Windows.Threading;
using System.Collections.Generic;
namespace Ownskit.Utils
{
/// <summary>
/// Listens keyboard globally.
///
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
/// </summary>
public class KeyboardListener : IDisposable
{
/// <summary>
/// Creates global keyboard listener.
/// </summary>
public KeyboardListener()
{
// Dispatcher thread handling the KeyDown/KeyUp events.
this.dispatcher = Dispatcher.CurrentDispatcher;
// We have to store the LowLevelKeyboardProc, 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);
}
private Dispatcher dispatcher;
/// <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="character">Character</param>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character);
/// <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)
{
string chars = "";
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)
{
// Captures the character(s) pressed only on WM_KEYDOWN
chars = InterceptKeys.VKCodeToString((uint)Marshal.ReadInt32(lParam),
(wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN));
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars, 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>
/// <param name="character">Character as string.</param>
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character)
{
switch (keyEvent)
{
// KeyDown events
case InterceptKeys.KeyEvent.WM_KEYDOWN:
if (KeyDown != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, false, character));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN:
if (KeyDown != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, true, character));
break;
// KeyUp events
case InterceptKeys.KeyEvent.WM_KEYUP:
if (KeyUp != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, false, character));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYUP:
if (KeyUp != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, true, character));
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>
/// Convert to string.
/// </summary>
/// <returns>Returns string representation of this key, if not possible empty string is returned.</returns>
public override string ToString()
{
return Character;
}
/// <summary>
/// Unicode character of key pressed.
/// </summary>
public string Character;
/// <summary>
/// Create raw keyevent arguments.
/// </summary>
/// <param name="VKCode"></param>
/// <param name="isSysKey"></param>
/// <param name="Character">Character</param>
public RawKeyEventArgs(int VKCode, bool isSysKey, string Character)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Character = Character;
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;
/// <summary>
/// Key event
/// </summary>
public enum KeyEvent : int {
/// <summary>
/// Key down
/// </summary>
WM_KEYDOWN = 256,
/// <summary>
/// Key up
/// </summary>
WM_KEYUP = 257,
/// <summary>
/// System key up
/// </summary>
WM_SYSKEYUP = 261,
/// <summary>
/// System key down
/// </summary>
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);
#region Convert VKCode to string
// Note: Sometimes single VKCode represents multiple chars, thus string.
// E.g. typing "^1" (notice that when pressing 1 the both characters appear,
// because of this behavior, "^" is called dead key)
[DllImport("user32.dll")]
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);
[DllImport("user32.dll")]
private static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetKeyboardLayout(uint dwLayout);
[DllImport("User32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
private static uint lastVKCode = 0;
private static uint lastScanCode = 0;
private static byte[] lastKeyState = new byte[255];
private static bool lastIsDead = false;
/// <summary>
/// Convert VKCode to Unicode.
/// <remarks>isKeyDown is required for because of keyboard state inconsistencies!</remarks>
/// </summary>
/// <param name="VKCode">VKCode</param>
/// <param name="isKeyDown">Is the key down event?</param>
/// <returns>String representing single unicode character.</returns>
public static string VKCodeToString(uint VKCode, bool isKeyDown)
{
// ToUnicodeEx needs StringBuilder, it populates that during execution.
System.Text.StringBuilder sbString = new System.Text.StringBuilder(5);
byte[] bKeyState = new byte[255];
bool bKeyStateStatus;
bool isDead = false;
// Gets the current windows window handle, threadID, processID
IntPtr currentHWnd = GetForegroundWindow();
uint currentProcessID;
uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID);
// This programs Thread ID
uint thisProgramThreadId = GetCurrentThreadId();
// Attach to active thread so we can get that keyboard state
if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID , true))
{
// Current state of the modifiers in keyboard
bKeyStateStatus = GetKeyboardState(bKeyState);
// Detach
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false);
}
else
{
// Could not attach, perhaps it is this process?
bKeyStateStatus = GetKeyboardState(bKeyState);
}
// On failure we return empty string.
if (!bKeyStateStatus)
return "";
// Gets the layout of keyboard
IntPtr HKL = GetKeyboardLayout(currentWindowThreadID);
// Maps the virtual keycode
uint lScanCode = MapVirtualKeyEx(VKCode, 0, HKL);
// Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also.
if (!isKeyDown)
return "";
// Converts the VKCode to unicode
int relevantKeyCountInBuffer = ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, sbString.Capacity, (uint)0, HKL);
string ret = "";
switch (relevantKeyCountInBuffer)
{
// Dead keys (^,`...)
case -1:
isDead = true;
// We must clear the buffer because ToUnicodeEx messed it up, see below.
ClearKeyboardBuffer(VKCode, lScanCode, HKL);
break;
case 0:
break;
// Single character in buffer
case 1:
ret = sbString[0].ToString();
break;
// Two or more (only two of them is relevant)
case 2:
default:
ret = sbString.ToString().Substring(0, 2);
break;
}
// We inject the last dead key back, since ToUnicodeEx removed it.
// More about this peculiar behavior see e.g:
// http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_23453780.html
// http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx
// http://blogs.msdn.com/michkap/archive/2007/10/27/5717859.aspx
if (lastVKCode != 0 && lastIsDead)
{
System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5);
ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL);
lastVKCode = 0;
return ret;
}
// Save these
lastScanCode = lScanCode;
lastVKCode = VKCode;
lastIsDead = isDead;
lastKeyState = (byte[])bKeyState.Clone();
return ret;
}
private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(10);
int rc;
do {
byte[] lpKeyStateNull = new Byte[255];
rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
} while(rc < 0);
}
#endregion
}
#endregion
}
@Ciantic
Copy link
Author

Ciantic commented Jul 11, 2010

Usage in WPF:

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());
        Console.WriteLine(args.ToString()); // Prints the text of pressed button, takes in account big and small letters. E.g. "Shift+a" => "A"
    }

    private void Application_Exit(object sender, ExitEventArgs e)
    {
        KListener.Dispose();
    }
}
// ...

@sekoyo
Copy link

sekoyo commented Apr 30, 2011

much appreciated worked first time thanks!

@bjarkeeck
Copy link

KeyUp event is not working for me :(

@Rewopp
Copy link

Rewopp commented Sep 17, 2014

Thanks alot! The best and cleanest Keyboard listener i've came across!!
Perfect for my card reader project, excelent dead keys handling, no more double accents for me! :)

@BrainInBlack
Copy link

We're using this code for our upcoming project: https://github.com/beStrangeGames/relink

If desired, we can add you to the credits, just drop me a note ;)

@ktamero
Copy link

ktamero commented Mar 27, 2015

Is there any way to ignore/block keys in certain circumstances with this code?

@Urutar
Copy link

Urutar commented May 7, 2015

The dead key handling is really neat, although there is a small quirk (in lines 398 - 405):

Writing uppercase characters with accents is not possible since the press of Shift sets [lastVKCode = 0], thus preventing to react with a proper character after a dead key
-> a check for [!string.IsNullOrEmpty(ret)] rectifies that
-> lastIsDead should also be set to false in that case, since the last key was Shift and the dead key was already handled

if (lastVKCode != 0 && lastIsDead)
{
    System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5);
    DLL_Import.ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL);

    if (!string.IsNullOrEmpty(ret))
    {
        lastVKCode = 0;
        lastIsDead = false;
    }
    return ret;
}

I noticed yet another issue:
While the user sees all text absolutely correct, the call to ToUnicodeEx(...) does return a wrong character, specifically after the user used the shift key to write an uppercase character.
The shift key seems to lag one character behind after it was used, aka if I write an uppercase 'A', the next character returned is also an uppercase 'A', although it shouldn't.

@hrzafer
Copy link

hrzafer commented Nov 1, 2015

Nice implementation. Thanks. I have a question. When a sequence of keys are pressed, is there a way to get the resulting string. For example, when users types 'a', 'b', 'c', 'backspace' and 'd', does this class has any method which returns "abd"? Thanks

@nmanumr
Copy link

nmanumr commented Jun 16, 2016

Thanks a lot and i have a question that how can we check that two buttons are pressed like ctrl + a etc..

@kenarsuleyman
Copy link

Thanks, did anyone manage to fix issue that Urutar noticed?
If i try to write uppercase "A" with Shift+A combination, it catches lowercase "a" and if i press lowercase "a" after shift+a combination it catches uppercase "A" instead of lowercase.

@videvfr
Copy link

videvfr commented Jan 5, 2017

Firstly, very good job for this KeyboardListener

In my current project I use InputSimulator API (http://inputsimulator.codeplex.com) to create virtual keyboard, because user has no real keyboard plugged on his computer.
So certain view uses virtual keyboard to send key. At same time I use your keyboard listener API to hook key pressed on keyboard.
No problem here, everyting is OK when user press key by key.

My problem is next
I wanted to simulate a "special key" that can send a string (ex "keyboard"), using a method 'TextEntry' in 'IKeyboardSimulator' interface
Ex: keyboard.TextEntry("E_SC"); (this is chain I want to send)
In notepad, ok, I receive string chain correctly
But with keyboardListener API, I do not receive 'E_SC' but 'CCCC'

I hope I have been clear, and you can help me about this problem.

Thanks, Vincent

@matwachich
Copy link

From AttachThreadInput documentation:

Note that key state, which can be ascertained by calls to the GetKeyState or GetKeyboardState function, is reset after a call to AttachThreadInput.

Why this isn't a problem?

@atrdev-rgb
Copy link

"A" with Shift+A combination in "WPF"

       case 1:
                ret = sbString[0].ToString();
                if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
                {
                ret = ret.ToUpper();
                }
                break;

@amirdandy
Copy link

Just i can Say Thank a lot

@nfsecurity
Copy link

Works like a charm, part of this code is being used in our project called The Fraud Explorer, very good job, thank you.

@BlankNick
Copy link

I'm having problem that @Urutar mentioned. I'm using it for barcode scanner that has HID input. It somehow messes up Shift key. For example if I scan barcode which has 10 numbers. Some of them become symbols that come with shift. And I can't find reason why its happening.

Sometimes it works just fine and sometimes it starts messing up shift like it missed where it should be and put it somewhere else. It looks like missed shift gets stack and pushed out like later and its from my 10 numbers that I scanned half starts to be symbols.

Also @Urutar mention if you write shift + a. It might print out a and when you type again a it prints out A with shift, but it happens only sometimes. Can't find any reasons why it's happening... Maybe someone knows how to solve it?

@Ciantic
Copy link
Author

Ciantic commented Dec 5, 2019

With Microsoft breaking those MSDN links, we may never know. Shift (and other) problems sounds like dead key handling is not working properly, if you don't need dead key handling just remove that code.

@BlankNick
Copy link

With Microsoft breaking those MSDN links, we may never know. Shift (and other) problems sounds like dead key handling is not working properly, if you don't need dead key handling just remove that code.

Yea with MS breaking those links it sucks man...

I tried to disable dead key code, but same is happening. The problem is somewhere else not in dead keys. Maybe you have any idea where the problem is? Because its definitely the best method in internet for keyboard listening. It would be nice to fix this somehow I'm sure that it would help a lot of people.

@trannguyenhoangtien
Copy link

Great! Thank you so much!!!

@AdarshChiniwar
Copy link

I am trying to integrate a barcode scanner in WPF application, whenever i scan a code when the main window is out of focus i get the correct result. but if window is focused the scanned data from keyboard hook is not proper. Please someone help me with this`

public class KeyboardHook : IDisposable
{
#region Private Members
private string PrefixString { get; set; }
System.Timers.Timer g_MagneticReader;
const int WH_KEYBOARD_LL = 13;
const int WM_KEYDOWN = 0x0100;
const int WM_KEYUP = 0x101;
const int WM_SYSKEYDOWN = 0x104;
const int WM_SYSKEYUP = 0x105;
private LowLevelKeyboardProc keyboardProc;
private IntPtr hookId = IntPtr.Zero;

    const UInt32 SWP_NOSIZE = 0x0001;
    const UInt32 SWP_NOMOVE = 0x0002;
    const UInt32 SWP_SHOWWINDOW = 0x0040;
    private IList<Keys> MagneticStrip { get; set; }

    public delegate void sendDataToWebORT(DeviceData value);
    public event sendDataToWebORT notifyScannedData;
    #endregion

    #region Constructor
    public KeyboardHook(string prefixString)
    {
        if (prefixString.Contains('~'))
        {
            string[] getPrefixString = prefixString.Split('~');
            PrefixString = getPrefixString[1];

        }
        System.Windows.Application.Current.Dispatcher.Invoke(() =>
        {
            this.keyboardProc = HookCallback;
            hookId = SetHook(this.keyboardProc);
            g_MagneticReader = new System.Timers.Timer();
            g_MagneticReader.Elapsed += G_MagneticReader_Elapsed;
            g_MagneticReader.Interval = 200;
            MagneticStrip = new List<Keys>();
        });


    }
    #endregion

    #region Dll's
    private delegate IntPtr LowLevelKeyboardProc(
    int nCode, IntPtr wParam, IntPtr lParam);


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
                                               LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
                                                IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

    [DllImport("user32.dll")]
    private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

    [DllImport("user32.dll")]
    static extern int MapVirtualKey(uint uCode, uint uMapType);

    [DllImport("user32.dll")]
    private static extern int ToAscii(uint uVirtKey, uint uScanCode, byte[] lpKeyState, [Out] StringBuilder lpChar, uint uFlags);
    #endregion

    
    #region Functions
    private void G_MagneticReader_Elapsed(object sender, ElapsedEventArgs e)
    {
        g_MagneticReader.Stop();
        //var enteredtext = GetStringFromKeys(MagneticStrip);
        var enteredtext = finalOutput.ToString();

        if (null != notifyScannedData)
        {
            //enteredtext.Clear();
            //enteredtext.Append("sqa4170832612");
            Logger.Default.LogInfo("Scanning Started - Start" + enteredtext.ToString(), nameof(KeyboardHook));
            //Logger.Info("Prefix string is - " + _PrefixString);
            //Logger.Info("Before removing prefix 1- " + enteredtext.ToString());
            //Logger.Info("_IsQRKeyboard- " + _IsQRKeyboard.ToString());
            string valueCheck = enteredtext.ToString();
            //if (enteredtext.Length > 12 && !_IsQRKeyboard)
            //{
            //    DeviceData obj = new DeviceData();
            //    if (enteredtext.ToString().StartsWith("5") && enteredtext.ToString().EndsWith("/"))
            //    {

            //        obj.Data = enteredtext.ToString().ToUpper().Substring(1, enteredtext.ToString().Length - 2);
            //        obj.device = DevicesSupported.mcr;
            //        obj.messageType = DeviceMessage.MagneticData;
            //    }
            //    else if (enteredtext.ToString().StartsWith("%") && enteredtext.ToString().EndsWith("?"))
            //    {

            //        obj.Data = enteredtext.ToString().ToUpper().Substring(1, enteredtext.ToString().Length - 2);
            //        obj.device = DevicesSupported.mcr;
            //        obj.messageType = DeviceMessage.MagneticData;
            //    }
            //    else
            //    {
            //        obj.Data = enteredtext.ToString().ToUpper();
            //        obj.device = DevicesSupported.mcr;
            //        obj.messageType = DeviceMessage.MagneticData;
            //    }
            //    KeyCombinationPressed.Invoke(obj);
            //}
           if (!string.IsNullOrEmpty(PrefixString) && valueCheck.ToLower().Contains(PrefixString.ToLower()))
            {
                Logger.Default.LogInfo("Before removing prefix - " + enteredtext.ToString(), nameof(KeyboardHook));
                enteredtext = enteredtext.Replace(PrefixString.ToLower(), "");
                Logger.Default.LogInfo("After removing prefix -  " + enteredtext.ToString(), nameof(KeyboardHook));
                DeviceData obj = new DeviceData();
                obj.data = enteredtext.ToString();
                notifyScannedData.Invoke(obj);
            }
            //else
            //{
            //    Logger.Default.LogInfo("Before removing prefix - " + enteredtext.ToStrinA#%
            //    g(), nameof(KeyboardHook));
            //    DeviceData obj = new DeviceData();
            //    obj.data = enteredtext.ToString();
            //    notifyScannedData.Invoke(obj);
            //}

        }
        finalOutput.Clear();
        MagneticStrip.Clear();
        //KeyCombinationPressed.Invoke(new DeviceData() { Data = "true", device = DevicesSupported.mcr, messageType = DeviceMessage.MagneticDataEnded });
    }
    public void Dispose()
    {
        UnhookWindowsHookEx(hookId);
    }
    StringBuilder GetStringFromKeys(IList<Keys> input)
    {
        StringBuilder output = new StringBuilder();
        System.Windows.Application.Current.Dispatcher.Invoke(() =>
        {
            byte[] keyState = new byte[256];


            foreach (ushort vk in input)
            {
                //if (vk == 13)
                //{
                //    break;
                //}

                AppendChar(output, vk, ref keyState);


            }
        });

        return output;
    }
    [DllImport("User32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll")]
    private static extern uint GetCurrentThreadId();

    [DllImport("user32.dll")]
    private static extern bool GetKeyboardState(byte[] lpKeyState);

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetKeyboardLayout(uint dwLayout);

    [DllImport("user32.dll")]
    private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl);
    [DllImport("user32.dll")]
    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);

    private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder(10);

        int rc;
        do
        {
            byte[] lpKeyStateNull = new Byte[255];
            rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
        } while (rc < 0);
    }

   
    private static uint lastVKCode = 0;
    private static uint lastScanCode = 0;
    private static byte[] lastKeyState = new byte[255];
    private static bool lastIsDead = false;
    private object lockobj = new object();
    MetroWindow MainWindow;
    public void GetMainWindowInstance(MetroWindow mainWindow)
    {
        MainWindow = mainWindow;
    }
    private void AppendChar(StringBuilder output, uint vKey, ref byte[] keyState)
    {
        lock (lockobj)
        { 
            Keyboard.ClearFocus();
            MainWindow.Focusable = false;
            MainWindow.Topmost = false;
            MainWindow.IsEnabled = true;
         

            //App.Current.MainWindow.IsActive = false;
            g_MagneticReader.Stop();
            bool bKeyStateStatus;
            bool isDead = false;
            byte[] bKeyState = new byte[255];
            System.Text.StringBuilder sbString = new System.Text.StringBuilder(5);
            // Gets the current windows window handle, threadID, processID

            //dummyWindow.Topmost = false;

            IntPtr windowHandle =
        new WindowInteropHelper(App.DummyWindow).Handle;
            IntPtr currentHWnd = GetForegroundWindow();
            uint currentProcessID;
            uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID);
            // This programs Thread ID
            uint thisProgramThreadId = GetCurrentThreadId();
            if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, true))
            {
                // Current state of the modifiers in keyboard
                bKeyStateStatus = GetKeyboardState(keyState);

                // Detach
                AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false);
                Logger.Default.LogInfo($"Execute 1 {bKeyStateStatus}", nameof(KeyboardHook));
            }
            else
            {


                // Detach
                //AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false);
                //Logger.Default.LogInfo($"Execute 1 {bKeyStateStatus}", nameof(KeyboardHook));

                // Could not attach, perhaps it is this process?
                bKeyStateStatus = GetKeyboardState(keyState);
                //AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false);
                Logger.Default.LogInfo($"Execute 2 {bKeyStateStatus}", nameof(KeyboardHook));
            }
            if (!bKeyStateStatus)
                return;

            // Gets the layout of keyboard
            IntPtr HKL = GetKeyboardLayout(currentWindowThreadID);
            uint lScanCode = MapVirtualKeyEx(vKey, 0, HKL);

            //if (!isKeyDown)
            //    return ;
            //int n = ToAscii(vKey, 0, bKeyState, sbString, 0);
            int relevantKeyCountInBuffer = ToUnicodeEx(vKey, lScanCode, keyState, sbString, sbString.Capacity, (uint)0, HKL);

            string ret = "";
            switch (relevantKeyCountInBuffer)
            {
                // Dead keys (^,`...)
                case -1:
                    isDead = true;

                    // We must clear the buffer because ToUnicodeEx messed it up, see below.
                    ClearKeyboardBuffer(vKey, lScanCode, HKL);
                    break;

                case 0:
                    break;

                // Single character in buffer
                case 1:

                    ret = sbString[0].ToString();
                    if ((System.Windows.Input.Keyboard.Modifiers & System.Windows.Input.ModifierKeys.Shift) == System.Windows.Input.ModifierKeys.Shift)
                    {
                        ret = ret.ToUpper();
                    }
                    Logger.Default.LogInfo($"scanned string {ret}", nameof(KeyboardHook));
                    //else
                    //{
                    //    ret = ret.ToLower();
                    //}
                    output.Append(ret);
                    break;


                // Two or more (only two of them is relevant)
                case 2:
                default:
                    ret = sbString.ToString().Substring(0, 2);
                    break;
            }
            if (lastVKCode != 0 && lastIsDead)
            {
                System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5);
                ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL);
                lastVKCode = 0;


            }

            lastScanCode = lScanCode;
            lastVKCode = vKey;
            lastIsDead = isDead;
            lastKeyState = (byte[])keyState.Clone();
            g_MagneticReader.Start();
            //MainWindow.Focusable = true;
            //dummyWindow.Close();
        }
      














        //if (MapVirtualKey(vKey, 2) == 0)
        //{
        //    keyState[vKey] = 0x80;
        //}
        //else
        //{
        //    StringBuilder chr = new StringBuilder(2);
        //    int n = ToAscii(vKey, 0, keyState, chr, 0);

        //    if (n > 0)
        //        output.Append(chr.ToString(0, n));

        //    keyState = new byte[256];
        //}
    }
    [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
    public static extern int ToUnicode(
uint virtualKey, uint scanCode, byte[] keyStates,
[MarshalAs(UnmanagedType.LPArray)][Out] char[] chars,
int charMaxCount, uint flags);

    private IntPtr HookCallback(
     int nCode, IntPtr wParam, IntPtr lParam)
    {
        try
        {
            if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN))
            //IGNORE Down events and take UP events
            //if (nCode >= 0 && (wParam == (IntPtr)WM_KEYUP))
            {
                //g_MagneticReader.Stop();
                int vkCode = Marshal.ReadInt32(lParam);
                string keyPressed = string.Empty;
                bool IsKeyReqired = false;
                if (vkCode == (int)Keys.LShiftKey)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "LS";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.RShiftKey)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "RS";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.ShiftKey)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "SK";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.Shift)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "S";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.CapsLock)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "CP";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.RControlKey)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "RCK";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.LControlKey)
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + "LCK";
                    IsKeyReqired = true;
                }
                else if (vkCode == (int)Keys.Space)
                {
                    keyPressed = " ";
                    IsKeyReqired = true;
                }
                else
                {
                    keyPressed = (wParam == (IntPtr)WM_KEYDOWN) ? "Down " : "UP " + ((Keys)vkCode).ToString();
                }

                //// 
                //if (MagneticStrip.Count == 0)
                //{
                //    //if (KeyCombinationPressed != null) new Thread(() => {
                //    //    //KeyCombinationPressed.Invoke(new DeviceData() { Data = "true", device = DevicesSupported.mcr, messageType = DeviceMessage.MagneticDataStarted });
                //    //}).Start();
                //}
                if (IsKeyReqired || wParam == (IntPtr)WM_KEYUP)
                {
                    MagneticStrip.Add((Keys)vkCode);
                }

                //g_MagneticReader.Start();
                Logger.Default.LogInfo("sending data" + (uint)vkCode, nameof(KeyboardHook));

                byte[] keyState = new byte[256];
                AppendChar(finalOutput, (uint)vkCode, ref keyState);
            }
        }
        catch (Exception ex)
        {
            return IntPtr.Zero;
        }

        return CallNextHookEx(hookId, nCode, wParam, lParam);
    }
    private StringBuilder finalOutput = new StringBuilder();
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    private IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            uint thisProgramThreadId = GetCurrentThreadId();
            IntPtr hInstance = LoadLibrary("User32");
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                                hInstance, 0);
        }
    }
    #endregion

}

When window is not focused - "L4009100Z234111399344#2023-04-11 13:15:00#111 Apfelkuchen buy one get one#232"

When window is focused - "L$)09100z23411139934432023-04-11 13;15;003111 apfelkuchen buy one get one3232"

@motenidan
Copy link

motenidan commented May 26, 2023

First, thank you for this good implementation of a solution for a keyboard listener. I have two questions about it:

  1. I have a (real) keyboard and two NFC readers. How do I know which device the 'keystroke' (or input) is coming from?
  2. recreating StringBuilder instances for each use is extremely expensive and every good developer can't read over these lines of code without it hurting ;-) Yes. I know... The method (VKCodeToString) is declared statically, but any another solution would really be better at this point.
    Nevertheless, I would be very grateful for a solution to problem 1. ;)
    Thanks & Greetings, Thomas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment