Friday, April 8, 2011

How to distinguish between multiple input devices in C#

I have a barcode scanner (which acts like a keyboard) and of course I have a keyboard too hooked up to a computer. The software is accepting input from both the scanner and the keyboard. I need to accept only the scanner's input. The code is written in C#. Is there a way to "disable" input from the keyboard and only accept input from the scanner?

Note: Keyboard is part of a laptop...so it cannot be unplugged. Also, I tried putting the following code protected override Boolean ProcessDialogKey(System.Windows.Forms.Keys keyData) { return true; } But then along with ignoring the keystrokes from the keyboard, the barcode scanner input is also ignored.

I cannot have the scanner send sentinal characters as, the scanner is being used by other applications and adding a sentinal character stream would mean modifying other code.

Also, I cannot use the timing method of determining if the input came from a barcode scanner (if its a bunch of characters followed by a pause) since the barcodes scanned could potentially be single character barcodes.

Yes, I am reading data from a stream.

I am trying to follow along with the article: Distinguishing Barcode Scanners from the Keyboard in WinForms. However I have the following questions:

  1. I get an error NativeMethods is inaccessible due to its protection level. It seems as though I need to import a dll; is this correct? If so, how do I do it?
  2. Which protected override void WndProc(ref Message m) definition should I use, there are two implementations in the article?
  3. Am getting an error related to [SecurityPermission( SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] error CS0246: The type or namespace name 'SecurityPermission' could not be found (are you missing a using directive or an assembly reference?). How do I resolve this error?
  4. There is also an error on the line containing: if ((from hardwareId in hardwareIds where deviceName.Contains(hardwareId) select hardwareId).Count() > 0) Error is error CS1026: ) expected.
  5. Should I be placing all the code in the article in one .cs file called BarcodeScannerListener.cs?

Followup questions about C# solution source code posted by Nicholas Piasecki on http://nicholas.piasecki.name/blog/2009/02/distinguishing-barcode-scanners-from-the-keyboard-in-winforms/:

  1. I was not able to open the solution in VS 2005, so I downloaded Visual C# 2008 Express Edition, and the code ran. However, after hooking up my barcode scanner and scanning a barcode, the program did not recognize the scan. I put a break point in OnBarcodeScanned method but it never got hit. I did change the App.config with the id of my Barcode scanner obtained using Device Manager. There seems to be 2 deviceNames with HID#Vid_0536&Pid_01c1 (which is obtained from Device Manager when the scanner is hooked up). I don't know if this is causing the scanning not to work. When iterating over the deviceNames, here is the list of devices I found (using the debugger):

"\??\HID#Vid_0536&Pid_01c1&MI_01#9&25ca5370&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"

"\??\HID#Vid_0536&Pid_01c1&MI_00#9&38e10b9&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"

"\??\HID#Vid_413c&Pid_2101&MI_00#8&1966e83d&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"

"\??\HID#Vid_413c&Pid_3012#7&960fae0&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}"
"\??\Root#RDP_KBD#0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}" "\??\ACPI#PNP0303#4&2f94427b&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}" "\??\Root#RDP_MOU#0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}" "\??\ACPI#PNP0F13#4&2f94427b&0#{378de44c-56ef-11d1-bc8c-00a0c91405dd}"

So there are 2 entries for HID#Vid_0536&Pid_01c1; could that be causing the scanning not to work?

OK so it seems that I had to figure out a way to not depend on the ASCII 0x04 character being sent by the scanner...since my scanner does not send that character. After that, the barcode scanned event is fired and the popup with the barcode is shown. So thanks Nicholas for your help.

From stackoverflow
  • It depends on the way you are interacting with the device. Anyway it wont be a C# solution, it will be some other library. Are you reading data from a stream? If you are just taking keystrokes, there may be nothing you can do about it.

  • Can you just unplug the keyboard?

    RE Your edit: You can unplug the small ribbon cable on laptops. Just follow the manufacturer's directions for removing the keyboard, pull the ribbon out of the socket, and put the keyboard back in.

    You could also try going into the keyboard control panel and uninstalling the keyboard driver it is using.

  • What I did in a similar situation is distinguish between a scan and a user typing by looking at the speed of the input.

    Lots of characters very close together then a pause is a scan. Anything else is keyboard input.

    I don't know exactly your requirements, so maybe that won't do for you, but it's the best I've got :)

    Adam Davis : Yeah, finding and reading from the device directly is non trivial. You should be able to set the scanner up to give start and stop sequences as well - these sequences would never occur on the keyboard.
  • Sorry, there's only one keyboard input - you'd have to delve into ring0 level stuff to figure out which keyboard (the scanner is a keyboard, as far as windows is concerned) a keypress came from since windows has very tight control over keyboard input. (for purposes of ctrl-alt-del, and security at login)

    You might consider using a USB to PS2 keyboard dongle - you may be able to 'connect' to that HID device directly and ignore other keyboard input.

    But C# isn't going to visit ring0 without an external library.

  • I think you might be able to distinguish multiple keyboards through DirectX API, or if that doesn't work, through raw input API.

  • Seems like you should be able to make the scanner send a code to say - "hey it's me!".

  • You could use the Raw Input API to distinguish between the keyboard and the scanner like I did recently. It doesn't matter how many keyboard or keyboard-like devices you have hooked up; you will see a WM_INPUT before the keystroke is mapped to a device-independent virtual key that you typically see in a KeyDown event.

    Far easier is to do what others have recommended and configure the scanner to send sentinel characters before and after the barcode. (You usually do this by scanning special barcodes in the back of the scanner's user manual.) Then, your main form's KeyPreview event can watch those roll end and swallow the key events for any child control if it's in the middle of a barcode read. Or, if you wanted to be fancier, you could use a low-level keyboard hook with SetWindowsHookEx() to watch for those sentinels and swallow them there (advantage of this is you could still get the event even if your app didn't have focus).

    I couldn't change the sentinel values on our barcode scanners among other things so I had to go the complicated route. Was definitely painful. Keep it simple if you can!

    Amar Patel : I am trying to follow along with the article. However, I get an error NativeMethods is inaccessible due to its protection level. It seems as though I need to import a dll; is this correct? If so, how do I do it? Also, which protected override void WndProc(ref Message m) definition should I use?
    Amar Patel : Also am getting an error related to [SecurityPermission( SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] error CS0246: The type or namespace name 'SecurityPermission' could not be found (are you missing a using directive or an assembly reference?)
    Amar Patel : Along with an error on the line containing: if ((from hardwareId in hardwareIds where deviceName.Contains(hardwareId) select hardwareId).Count() > 0) Error is error CS1026: ) expected
    Amar Patel : Should I be placing all the code in the Distinguishing Barcode Scanners from the Keyboard in WinForms article in one .cs file called BarcodeScannerListener.cs?
    Nicholas Piasecki : NativeMethods is a class that was in my project that contained all of the necessary P/Invoke declarations. You might get the LINQ error if you're not on .NET 3.5. I'll try to de-proprietize the code sometime this week and upload it to my blog. You would use the second WndProc.
    Nicholas Piasecki : Amar, I added a link to a sample file. You should find the blog article easier to follow along with the complete code file. It's at the bottom of the posting. Public domain, no warranties--have at it and good luck!
    Amar Patel : I was not able to open the solution in VS 2005, so I downloaded Visual C# 2008 Express Edition, and the code ran. However, after hooking up my barcode scanner and scanning a barcode, the program did not recognize the scan. I put a break point in OnBarcodeScanned method but it never got hit.
    Amar Patel : I did change the App.config with the id of my Barcode scanner obtained using Device Manager.
    Amar Patel : There seems to be 2 deviceNames with HID#Vid_0536&Pid_01c1 (which is obtained from Device Manager when the scanner is hooked up). I don't know if this is causing the scanning not to work.

1 comments:

Jim Green said...

The barcode scan technology needs to be directly embedded into your terminal device, and the barcode scanner should be consistent with the selected barcode type in order to better implement barcode read.

Post a Comment