-
-
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!!!