diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a917fb2 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("USB Barcode Scanner")] +[assembly: AssemblyDescription("USB Barcode Scanner")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("BasselTech")] +[assembly: AssemblyProduct("USB Barcode Scanner")] +[assembly: AssemblyCopyright("Copyright © BasselTech. All rights reserved.")] +[assembly: AssemblyTrademark("BasselTech")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ec90583a-a843-42ad-ad7b-e8096ca65871")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/UsbBarcodeScanner.cs b/UsbBarcodeScanner.cs new file mode 100644 index 0000000..1a4bf11 --- /dev/null +++ b/UsbBarcodeScanner.cs @@ -0,0 +1,201 @@ +using System; +using System.Runtime.InteropServices; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Text; + +namespace BasselTech +{ + namespace UsbBarcodeScanner + { + public class BarcodeScannedEventArgs : EventArgs + { + public BarcodeScannedEventArgs(string barcode) + { + Barcode = barcode; + } + + public string Barcode { get; } + } + + public class UsbBarcodeScanner + { + #region WinAPI Declarations + + [DllImport("kernel32.dll")] + private static extern IntPtr LoadLibrary(string lpLibFileName); + + [DllImport("user32.dll")] + private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hInstance, uint threadId); + + [DllImport("user32.dll")] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll")] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId); + + [DllImport("user32.dll")] + private static extern IntPtr GetKeyboardLayout(uint idThread); + + [DllImport("user32.dll")] + static extern bool GetKeyboardState(byte[] lpKeyState); + + [DllImport("user32.dll")] + static extern uint MapVirtualKey(uint uCode, uint uMapType); + + [DllImport("user32.dll")] + static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl); + + #endregion + + #region Delegates and Constants + + private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); + private const int WH_KEYBOARD_LL = 13; + private const int WM_KEYDOWN = 0x0100; + + #endregion + + #region Private Fields + + private static IntPtr _hookId = IntPtr.Zero; + private readonly List _keys = new List(); + private readonly Timer _timer = new Timer(); + + #endregion + + #region Events + + public event EventHandler BarcodeScanned; + + #endregion + + #region Constructor + + public UsbBarcodeScanner() + { + _timer.Interval = 20; + _timer.Tick += (sender, args) => _keys.Clear(); + _timer.Stop(); + } + + public void Start() + { + if (IsCapturing()) + return; + _hookId = SetHook(KeyboardHookCallback); + _timer.Start(); + } + + public void Stop() + { + if (!IsCapturing()) + return; + UnhookWindowsHookEx(_hookId); + _hookId = IntPtr.Zero; + _timer.Stop(); + _keys.Clear(); + } + + public bool IsCapturing() + { + return _hookId != IntPtr.Zero; + } + + #endregion + + #region Private Methods + + private IntPtr SetHook(LowLevelKeyboardProc proc) + { + using (var curProcess = System.Diagnostics.Process.GetCurrentProcess()) + using (var curModule = curProcess.MainModule) + { + return SetWindowsHookEx(WH_KEYBOARD_LL, proc, LoadLibrary("user32"), 0); + } + } + + private string GetBarcodeString() + { + var barcodeBuilder = new StringBuilder(); + var shiftFlag = false; + + foreach (var key in _keys) + { + if (key == Keys.ShiftKey || key == Keys.LShiftKey || key == Keys.RShiftKey) + { + shiftFlag = true; + continue; + } + + barcodeBuilder.Append(KeyCodeToUnicode(key, shiftFlag)); + shiftFlag = false; + } + + return barcodeBuilder.ToString(); + } + + private string KeyCodeToUnicode(Keys key, bool shiftFlag) + { + var lpKeyState = new byte[255]; + GetKeyboardState(lpKeyState); + + if (shiftFlag) + lpKeyState[(int)Keys.ShiftKey] = 0x80; + + var wVirtKey = (uint)key; + var wScanCode = MapVirtualKey(wVirtKey, 0); + + var pwszBuff = new StringBuilder(); + ToUnicodeEx(wVirtKey, wScanCode, lpKeyState, pwszBuff, 5, 0, GetKeyboardLayout(0)); + + return pwszBuff.ToString(); + } + + private IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) + { + // Extract the virtual key code + var vkCode = Marshal.ReadInt32(lParam); + + // Convert the virtual key code to a Keys enum value + var key = (Keys)vkCode; + + _timer.Stop(); + if (key == Keys.Enter) + { + if (_keys.Count > 0) + { + var barcode = GetBarcodeString(); + BarcodeScanned?.Invoke(this, new BarcodeScannedEventArgs(barcode)); + } + _keys.Clear(); + } + else + { + _keys.Add(key); + _timer.Start(); + } + } + + return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); + } + + #endregion + + #region Destructor + + ~UsbBarcodeScanner() + { + Stop(); + } + + #endregion + } + + } +} diff --git a/usb-barcode-scanner-lib.csproj b/usb-barcode-scanner-lib.csproj new file mode 100644 index 0000000..fe417b0 --- /dev/null +++ b/usb-barcode-scanner-lib.csproj @@ -0,0 +1,45 @@ + + + + + Debug + AnyCPU + {EC90583A-A843-42AD-AD7B-E8096CA65871} + Library + Properties + BasselTech + USB-Barcode-Scanner + v4.0 + 512 + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/usb-barcode-scanner-lib.sln b/usb-barcode-scanner-lib.sln new file mode 100644 index 0000000..5a4b38c --- /dev/null +++ b/usb-barcode-scanner-lib.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.34729.46 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "usb-barcode-scanner-lib", "usb-barcode-scanner-lib.csproj", "{EC90583A-A843-42AD-AD7B-E8096CA65871}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC90583A-A843-42AD-AD7B-E8096CA65871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC90583A-A843-42AD-AD7B-E8096CA65871}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC90583A-A843-42AD-AD7B-E8096CA65871}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC90583A-A843-42AD-AD7B-E8096CA65871}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {94256B36-EC0D-4FB9-A29F-A5403E1BBCCB} + EndGlobalSection +EndGlobal