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