- 
      
- 
        Save Ciantic/471698 to your computer and use it in GitHub Desktop. 
| 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 | |
| } | 
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"
First, thank you for this good implementation of a solution for a keyboard listener. I have two questions about it:
- I have a (real) keyboard and two NFC readers. How do I know which device the 'keystroke' (or input) is coming from?
- 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
@AdarshChiniwar Did you solve the problem with the different results reading with the barscan code when the application has focused? I am having the same issue. Thanks!
Very helpful @Ciantic. Thank you.
Great! Thank you so much!!!