Click here to Skip to main content
Click here to Skip to main content

Using Raw Input from C# to handle multiple keyboards

, , 8 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Windows XP supports multiple keyboards, but by default, the .Net Framework will treat them all as one. This article explains how to use the Windows API Raw Input methods to support multiple keyboards from a C# application.

Sample Image - rawinput.jpg

Introduction
Background
Support for different devices
Using the code
Implementing a Windows API Raw Input handler

        Registering raw input devices
        Retrieving and processing raw input
        Retrieving the list of input devices
        Getting information on specific devices
        Reading device information from the Registry

Conclusion
Sources

Introduction

There was a time when you were lucky if a PC had so much as a mouse, but today, it is common to have a wide variety of Human Interface Devices (HIDs) ranging from game controllers to touch screens. In particular, users can connect more than one keyboard to their PCs. However, the usual keyboard programming methods in the .NET Framework offer no way to differentiate the input from different keyboards. Any application handling KeyPress events will receive the input from all connected keyboards as if they were a single device.

Windows XP and above now support a "raw input" API which allows programs to handle the input from any connected human interface devices directly. Intercepting this information and filtering it for keyboards enables an application to identify which device triggered the message. For example, this could allow two different windows to respond to input from different keyboards.

This article and the enclosed code demonstrate how to handle raw input in order to process keystrokes and identify which device they come from. The Rawinput.dll file in the attached zip contains the raw input API wrapper; copy this dll to your own project and follow the instructions in "Using the code" if you want to use it without running the sample application.

Background

I recently published an article on implementing a low-level keyboard hook in C#[^] using the SetWindowsHookEx and related methods from user32.dll. While looking for a solution to handle multiple keyboards, Steve Messer[^] came across my article and we discussed whether my code could be adapted to his needs. In fact, it turned out that it couldn't, and that the Raw Input API was the solution.

Unfortunately, there are very few keyboard-related Raw Input samples online, so when Steve had finished a working sample of his code, I offered to write this article so that future .NET developers faced with this problem wouldn't have to look far to find the solution. While I have made minor adjustments to the code, it is primarily Steve's work and I thank him for sharing it. Note: as of March 2007, you can also download Steve's WPF sample illustrating the use of WndProc in Windows Vista. However, this article only describes the Windows XP source code.

Please note that this will only work on Windows XP or later in a non-Terminal Server environment, and the attached sample projects are for Visual Studio 2005. The latest update was developed using Visual Studio 2012 on Windows 8 64 bit. I do not believe the update has any dependencies on anything higher that .Net 2.0 so you can simply add the files to any version of VS that you might have.

Support for different devices

The attached code is a generic solution that mostly mirrors the sample code given on MSDN. Different devices will work in different ways, and you may need to amend the code to suit the keyboards you are using. Unfortunately, we won't always be able to help with device-specific queries, as we won't have the same devices you have. Steve Messer has tested the code with different keyboards, however, and is confident that it will work with most devices provided they are correctly installed.

Using the code

Most of the code related to raw input handling is encapsulated in the RawKeyboard and RawInput classes which reside in the rawinput.dll. Using it is a matter of implementing three simple steps:

1. Add a reference to rawinput.dll to your project.

Then add the following using statement.

using RawInput_dll;

2. Instantiate an RawInput object

The RawInput class's constructor takes one argument, which is the handle to the current window.

RawInput rawinput = new RawInput(Handle);

The handle is required. When you register for rawinput the input is sent to the given handle. If you don't provide a handle then rawinput doesn't have a window to send messages to. The rawinput class now inherits from Native Window and overrides WndProc for you.

3. Handle the KeyPressed event

When a key is pressed, the RawKeyboard class raises a custom KeyPressed event containing an InputEventArgs. This needs to be handled by a method of the type DeviceEventHandler, which can be set up as follows:

rawinput.KeyPressed += OnKeyPressed;

The method that handles the event can then perform whatever actions are required based on the contents of the KeyControlEventArgs argument. The sample application attached to this article simply uses the values to populate a dialog box.

4. Optional Settings to control message handling and capture messages only if your application is top-most

_rawinput.CaptureOnlyIfTopMostWindow = true;    // Otherwise default behavior is to capture always
_rawinput.AddMessageFilter();                   // Adding a message filter will cause keypresses to be handled

Now, the RawInput class works by intercepting messages to the registered window handle in order to process the WM_INPUT messages containing raw input data. The window listening for raw input will therefore need to override its own WndProc and pass all its messages to the instantiated RawKeyboard object.

protected override void WndProc(ref Message message)
{
  switch(message.Msg)
  {
    case Win32.WM_INPUT:
    _keyboardDriver.ProcessRawInput(message.LParam);
    break;
  }
  base.WndProc(ref message);
}

The rest of this article describes how to handle "raw input" from a C# application, as illustrated by the RawInput and RawKeyboard class's in the sample application.

Implementing a Windows API Raw Input handler

MSDN identifies "raw input" [^] as being the raw data supplied by an interface device. In the case of a keyboard, this data is normally intercepted by Windows and translated into the information provided by Key events in the .NET Framework. For example, the Windows manager translates the device-specific data about keystrokes into virtual keys.

However, the normal Windows manager doesn't provide any information about which device received the keystroke; it just bundles events from all keyboards into one category and behaves as if there were just one keyboard.

This is where the Raw Input API is useful. It allows an application to receive data directly from the device, with minimal intervention from Windows. Part of the information it provides is the identity of the device that triggered the event.

The user32.dll in Windows XP, Vista, and Windows 8 contains the following methods for handling raw input:

  • RegisterRawInputDevices allows the application to register the input devices it wants to monitor.
  • GetRawInputData retrieves the data from the input device.
  • GetRawInputDeviceList retrieves the list of input devices attached to the system.
  • GetRawInputDeviceInfo retrieves information on a device.

The following sections give an overview of how these four methods are used to process raw data from keyboards.

Registering raw input devices

By default, no application receives raw input. The first step is therefore to register the input devices that will be providing the desired raw data, and associate them with the window that will be handling this data.

To do this, the RegisterRawInputDevices method is imported from user32.dll:

[DllImport("User32.dll")]
extern static bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);

To determine which devices should be registered, the method accepts an array of RAWINPUTDEVICE structures. The other two arguments are the number of items in the array, and the number of bytes in a RAWINPUTDEVICE structure.

The RAWINPUTDEVICE structure is defined in Windows.h for C++ projects, here is has been redefined for use in C#.

[StructLayout(LayoutKind.Sequential)]
internal struct RawInputDevice
{
   internal HidUsagePage UsagePage;
   internal HidUsage Usage;
   internal RawInputDeviceFlags Flags;
   internal IntPtr Target;

   public override string ToString()
   {
      return string.Format("{0}/{1}, flags: {2}, target: {3}", UsagePage, Usage, Flags, Target);
   }
}

Each RAWINPUTDEVICE structure added to the array contains information on a type of device which interests the application. For example, it is possible to register keyboards and telephony devices. The structure uses the following information:

  • Usage Page: The top level HID "usage page". For most HIDs, including the keyboard, this is 0x01.
  • Usage ID: A number indicating which precise type of device should be monitored. For the keyboard, this is 0x06. (A list of Usage Page and Usage ID values can be found in this MSDN article on HIDs[^])
  • Flags: These determine how the data should be handled, and whether some types should be ignored. A list of possible values is given in the MSDN article[^], and the constants they represent are defined in Windows.h (there's a copy of it here[^] if you don't already have one).
  • Target Handle: The handle of the window which will be monitoring data from this particular type of device.

In this case, we are only interested in keyboards, so the array only has one member and is set up as follows:

RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];

rid[0].usUsagePage  = 0x01;
rid[0].usUsage      = 0x06;
rid[0].dwFlags      = RIDEV_INPUTSINK; 
rid[0].hwndTarget   = hwnd;

Here, the code only defines the RIDEV_INPUTSINK flag, which means that the window will always receive the input messages, even if it is no longer has the focus. This will enable two windows to respond to events from different keyboards, even though at least one of them won't be active.

With the array ready to be used, the method can be called to register the window's interest in any devices which identify themselves as keyboards:

RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]))

Once the type of device has been registered this way, the application can begin to process the data using the GetRawInputData method described in the next section.

Retrieving and processing raw input

When the type of device is registered, the application begins to receive raw input. Whenever a registered device is used, Windows generates a WM_INPUT message containing the unprocessed data from the device.

Each window whose handle is associated with a registered device as described in the previous section must therefore check the messages it receives and take appropriate action when a WM_INPUT one is detected. In the sample application, the Rawnput class takes care of checking for WM_INPUT messages, so all the native window does is override its base WndProc method to get access to the messages, and pass the handle that generated the input to the RawKeyboard object's ProcessRawInput for processing:

 protected override void WndProc(ref Message message)
 {
    switch (message.Msg)
    {
       case Win32.WM_INPUT:
       {
           // Should never get here if you are using PreMessageFiltering
           _keyboardDriver.ProcessRawInput(message.LParam);
       }
       break;
       
       base.WndProc(ref message);
 }

The WndProc method in RawInput filters the messages, calling ProcessRawInput whenever a WM_INPUT is received. Any other type of message will fall through to the call to the base WndProc, so the application will respond to other events normally.

ProcessRawInput then uses the GetRawInputData method to retrieve the contents of the message and translate it into meaningful information.

Retrieving the information from the message

In order to process the data in WM_INPUT messages, the GetRawInputData method is imported from user32.dll:

[DllImport("User32.dll")]
internal static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, {Out] IntPtr pData, ref uint size, int sizeHeader);

The method uses the following parameters:

  • hRawInput
    The handle to the RAWINPUT structure containing the data, as provided by the lParam in a WM_INPUT message.
  • command
    A flag which sets whether to retrieve the input data or the header information from the RAWINPUT structure. Possible values are RID_INPUT (0x10000003) or RID_HEADER (0x10000005) respectively.
  • pData:
    Depending on the desired result, this can be one of two things:
    • If pData is set to IntrPtr.Zero, the size of the buffer required to contain the data is returned in the pcbSize variable.
    • Otherwise, pData must be a pointer to allocated memory that can hold the RAWINPUT structure provided by the WM_INPUT message. When the method call returns, the contents of the allocated memory will be either the message's header information or input data, depending on the value of uiCommand.
  • size
    A variable that returns or specifies the size of the data pointed to by pData.
  • sizeHeader
    The size of a RAWINPUTHEADER structure.

In order to ensure that enough memory is allocated to store the desired information, the GetRawInputData method should first be called with pData set to IntPtr.Zero.

int dwSize = 0;

Win32.GetRawInputData( hdevice, DataCommand.RID_INPUT, IntPtr.Zero, ref dwSize, Marshal.SizeOf(typeof(Rawinputheader)));

Following this call, the value of dwSize will correspond to the number of bytes needed to store the raw input data (as indicated by the use of the RID_INPUT flag).

Now that the size of the inputdata , GetRawInputData can be called again to populate the _rawBuffer with the RAWINPUT structure from the current message. If it succeeds, the method returns the size of the data it retrieved, so it is worth checking that this matches the result of the previous call before continuing.

if( Win32.GetRawInputData(hdevice, DataCommand.RID_INPUT, out _rawBuffer, ref dwSize, Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize)
//do something with the data

 Processing the data

As mentioned above, the WM_INPUT message contains raw data encapsulated in a RAWINPUT structure. As with the RAWINPUTDEVICE structure described in the previous section, this structure is redefined in the RawInput dll as follows.

[StructLayout(LayoutKind.Explicit)]
public struct RawData
{
   [FieldOffset(0)]
   internal Rawmouse mouse;
   [FieldOffset(0)]
   internal Rawkeyboard keyboard;
   [FieldOffset(0)]
   internal Rawhid hid;
}

[StructLayout(LayoutKind.Sequential)]
public struct  InputData
{
   public Rawinputheader header;           // 64 bit header size is 24  32 bit the header size is 16
   public RawData data;                    // Creating the rest in a struct allows the header size to align correctly for 32 or 64 bit
}

[StructLayout(LayoutKind.Sequential)]
public struct Rawinputheader
{
   public uint dwType;                     // Type of raw input (RIM_TYPEHID 2, RIM_TYPEKEYBOARD 1, RIM_TYPEMOUSE 0)
   public uint dwSize;                     // Size in bytes of the entire input packet of data. This includes RAWINPUT plus possible extra input reports in the RAWHID variable length array. 
   public IntPtr hDevice;                  // A handle to the device generating the raw input data. 
   public IntPtr wParam;                   // RIM_INPUT 0 if input occurred while application was in the foreground else RIM_INPUTSINK 1 if it was not.

   public override string ToString()
   {
       return string.Format("RawInputHeader\n dwType : {0}\n dwSize : {1}\n hDevice : {2}\n wParam : {3}", dwType, dwSize, hDevice, wParam);
   }
}

Following the second call to GetRawInputData (see previous section), the raw structure will contain the following information:

A RAWINPUTHEADER structure called header, which contains information on the message and the device that triggered it.

A second structure of type RAWKEYBOARD called keyboard. This could also be a RAWMOUSE or RAWHID structure called mouse or hid, depending on the type of device.

Its members return the following information:

  • dwType
    The type of raw input the message represents. The values can be RIM_TYPEHID (2), RIM_TYPEKEYBOARD (1), or RIM_TYPEMOUSE (0).
  • dwSize
    The size of all the information in the message (header and input data included).
  • hDevice
    The handle of the device which triggered the message.
  • wParam
    The wParam data from the WM_INPUT message.

The second structure will be a RAWMOUSE, a RAWKEYBOARD, or a RAWHID type. For the sake of completeness, the Rawinput dll does contain definitions for RAWMOUSE and RAWHID, though it is only designed to process keyboard information.

The keyboard information is provided by a RAWKEYBOARD structure, laid out as follows.

[StructLayout(LayoutKind.Sequential)]
internal struct Rawkeyboard
{
   public ushort Makecode;                 // Scan code from the key depression
   public ushort Flags;                    // One or more of RI_KEY_MAKE, RI_KEY_BREAK, RI_KEY_E0, RI_KEY_E1
   public ushort Reserved;                 // Always 0    
   public ushort VKey;                     // Virtual Key Code
   public uint Message;                    // Corresponding Windows message for exmaple (WM_KEYDOWN, WM_SYASKEYDOWN etc)
   public uint ExtraInformation;           // The device-specific addition information for the event (seems to always be zero for keyboards)

   public override string ToString()
   {
       return string.Format("Rawkeyboard\n Makecode: {0}\n Makecode(hex) : {0:X}\n Flags: {1}\n Reserved: {2}\n VKeyName: {3}\n Message: {4}\n ExtraInformation {5}\n", 
                                           Makecode, Flags, Reserved, VKey, Message, ExtraInformation);
   }
}

The next step is to filter the message to see if it is a key down event. This could just as easily be a check for a key up event; the point here is to filter the messages so that the same keystroke isn't processed for both key down and key up events.

private const int WM_KEYDOWN     = 0x0100;
private const int WM_SYSKEYDOWN  = 0x0104;
...
if (raw.keyboard.Message == WM_KEYDOWN || 
    raw.keyboard.Message == WM_SYSKEYDOWN)
{
  //Do something like...
  int vkey = raw.keyboard.vkey;
  MessageBox.Show(vkey.ToString());
}

At this point, the RawKeyboard class retrieves further information about the message and the device that triggered it, and raises its custom KeyPressed event. The following sections describe how to get information on the devices.

Retrieving the list of input devices

Although this step isn't required to handle raw input, the list of input devices can be useful. The sample application retrieves a list of devices, filters it for keyboards, and then returns the number of keyboards. This is part of the information returned by the InputEventArgs in the RawKeyboard class's KeyPressed event.

The first step is to import the necessary method from user32.dll:

[DllImport("User32.dll")]
extern static uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize);

The method's arguments are as follows:

  • pRawInputDeviceList: Depending on the desired result, this can be one of two things:
    • IntPtr.Zero if the purpose is only to retrieve the number of devices.
    • A pointer to an array of RAWINPUTDEVICELIST structures if the purpose of the method call is to retrieve the complete list of devices.
  • uiNumDevices: A reference to an unsigned integer to store the number of devices.
    • If the pRawInputDeviceList argument is IntPtr.Zero, then this variable will return the number of devices.
    • If the pRawInputDeviceList argument is a pointer to an array, then this variable must contain the size of the array. This allows the method to allocate memory appropriately. If uiNumDevices is less than the size of the array in this case, the method will return the size of the array, but an "insufficient buffer" error will occur and the method will fail.
  • cbSize: The size of a RAWINPUTDEVICELIST structure.

In order to ensure that the first and second arguments are correctly configured when the list of devices is required, the method should be set up in three stages.

First, it should be called with pRawInputDeviceList set to IntPtr.Zero. This will ensure that the variable in the second argument (deviceCount here) is filled with the correct number of devices. The result of this call should be checked, as an error means that the code can proceed no further.

uint deviceCount = 0;
int dwSize = (Marshal.SizeOf( typeof( RAWINPUTDEVICELIST )));

if( GetRawInputDeviceList( IntPtr.Zero, ref deviceCount, (uint)dwSize ) == 0 )
{
  //continue retrieving the information (see below)
}
else
{
  //handle the error or throw an exception
}

Once the deviceCount variable contains the right value, the correct amount of memory can be allocated and associated with a pointer:

IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize * deviceCount ));

And the method can be called again, this time to fill the allocated memory with an array of RAWINPUTDEVICELIST structures:

GetRawInputDeviceList( pRawInputDeviceList, ref deviceCount, (uint)dwSize );

The pRawInputDeviceList data can then be converted into individual RAWINPUTDEVICELIST structures. In the example below, a for loop has been used to iterate through the devices, so i represents the position of the current device in the array.

for( int i = 0; i < deviceCount; i++ )
{
  RAWINPUTDEVICELIST rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure(
         new IntPtr(( pRawInputDeviceList.ToInt32() + ( dwSize * i ))), 
         typeof( RAWINPUTDEVICELIST ));
//do something with the information (see section on GetRawInputDeviceInfo)
}

When any subsequent processing is completed, the memory should be deallocated.

Marshal.FreeHGlobal( pRawInputDeviceList );

Getting information on specific devices

Once GetRawInputDeviceList has been used to retrieve an array of RAWINPUTDEVICELIST structures as well as the number of items in the array, it is possible to use GetRawInputDeviceInfo to retrieve specific information on each device.

First, the method is imported from user32.dll:

[DllImport("User32.dll")]
extern static uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);

Its arguments are as follows:

  • hDevice
    The device handle returned in the corresponding RAWINPUTDEVICELIST structure.
  • uiCommand A flag to set what type of data will be returned in pData. Possible values are RIDI_PREPARSEDDATA (0x20000005 - returns previously parsed data), RIDI_DEVICENAME (0x20000007 - a string containing the device name), or RIDI_DEVICEINFO (0x2000000b - an RIDI_DEVICE_INFO structure)
  • pData: Depending on the desired result, this can be one of two things:
    • If pData is set to IntrPtr.Zero, the size of the buffer required to contain the data is returned in the pcbSize variable.
    • Otherwise, pData must be a pointer to allocated memory that can hold the type of data specified by uiCommand.
      (Note: if uiCommand is set to RIDI_DEVICEINFO, then the cbSize member of the RIDI_DEVICE_INFO structure must be set to the size of the structure)
  • pcbSize
    A variable that returns or specifies the size of the data pointed to by pData. If uiCommand is RIDI_DEVICENAME, pcbSize will indicate the number of characters in the string. Otherwise, it indicates the number of bytes in the data.

The example code uses a for loop to iterate through the available devices as indicated by the deviceCount variable. At the start of each loop, a RAWINPUTDEVICELIST structure called rid is filled with the information on the current device (see GetRawInputDeviceList section above).

In order to ensure that enough memory is allocated to store the desired information, the GetRawInputDeviceInfo method should first be called with pData set to IntPtr.Zero. The handle in the hDevice parameter is provided by the rid structure containing information on the current device in the loop.

uint pcbSize = 0;

GetRawInputDeviceInfo( rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize );

In this example, the purpose is to find out the device name, which will be used to look up information on the device in the Registry.

Following this call, the value of pcbSize will correspond to the number of characters needed to store the device name. Once the code has checked that pcbSize is greater than 0, the appropriate amount of memory can be allocated.

IntPtr pData = Marshal.AllocHGlobal( (int)pcbSize );

And the method can be called again, this time to fill the allocated memory with the device name. The data can then be converted into a C# string for ease of use.

string deviceName; 

GetRawInputDeviceInfo( rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize );
deviceName = (string)Marshal.PtrToStringAnsi( pData );

The rest of the code then retrieves information about the device and checks the Registry to retrieve device information.

Reading device information from the Registry

Following the above code, deviceName will have a value similar to the following:

\\??\\ACPI#PNP0303#3&13c0b0c5&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}

This string mirrors the device's entry in the Registry; parsing it therefore allows us to find the relevant Registry key, which contains further information on the device. So the first step is to break down the relevant part of the string:

// remove the \??\
item = item.Substring( 4 ); 

string[] split = item.Split( '#' );

string id_01 = split[0];    // ACPI (Class code)
string id_02 = split[1];    // PNP0303 (SubClass code)
string id_03 = split[2];    // 3&13c0b0c5&0 (Protocol code)
// The final part is the class GUID and is not needed here

The Class code, SubClass code and Protocol retrieved this way correspond to the device's path under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet, so the next stage is to open that key:

RegistryKey OurKey = Registry.LocalMachine;

string findme = string.Format(@"System\CurrentControlSet\Enum\{0}\{1}\{2}", id_01, id_02, id_03 );

The information we are interested in is the device's friendly description:

string deviceDesc  = (string)OurKey.GetValue( "DeviceDesc" );

All that is left then is to deallocate any allocated memory and do something with the data that has been retrieved.

What's new? 

 1. Pre message filtering. The allows the messages that aren't WM_INPUT messages to behave normally.  ProcessRawInput returns true if it successfully decodes a keypress event else false. Returning true causing the message to not be sent to the WndProc method.       

private class PreMessageFilter : IMessageFilter
{
    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg != Win32.WM_INPUT)
        {
           // Allow any non WM_INPUT message to pass through
           return false;
        }
        
        return _keyboardDriver.ProcessRawInput(m.LParam);
    }
}

It is activated by the following code in the RawInput class

public void AddMessageFilter()
{
   if (null != _filter) return;

   _filter = new PreMessageFilter();
    Application.AddMessageFilter(_filter);
}

 2. Capture keypresses if top-most window. The wParam of the _rawBuffer.header tells us if the input occurred while the application was in the foreground. 

public static bool InputInForeground(IntPtr wparam)
{
   return wparam.ToInt32() == RIM_INPUT;
}

 3. Determine if the ctrl and alt keypress are the left or right version. When the RI_KEY_E0 flag is set it said that the EO bit is set, which means the right version of a key was pressed. The shift key is a little different. It is a right shift if the makecode has the SC_SHIFT_R bit set.

var isE0BitSet = ((flags & Win32.RI_KEY_E0) != 0);

private static int VirtualKeyCorrection(int virtualKey, bool isE0BitSet, int makeCode)
{
   var correctedVKey = virtualKey;

   if (_rawBuffer.header.hDevice == IntPtr.Zero)
   {
        // When hDevice is 0 and the vkey is VK_CONTROL indicates the ZOOM key
        if (_rawBuffer.data.keyboard.VKey == Win32.VK_CONTROL)
        {
	   correctedVKey = Win32.VK_ZOOM;
        }
   }
   else
   {
       switch (virtualKey)
       {
	   // Right-hand CTRL and ALT have their e0 bit set 
	   case Win32.VK_CONTROL:
	   	correctedVKey = isE0BitSet ? Win32.VK_RCONTROL : Win32.VK_LCONTROL;
		break;
	   case Win32.VK_MENU:
		correctedVKey = isE0BitSet ? Win32.VK_RMENU : Win32.VK_LMENU;
		break;
	   case Win32.VK_SHIFT:
		correctedVKey = makeCode == Win32.SC_SHIFT_R ? Win32.VK_RSHIFT : Win32.VK_LSHIFT;
		break;
	   default:
		correctedVKey = virtualKey;
		break;
	}
    }
   
    return correctedVKey;
}

Conclusion

Although the .NET Framework offers methods for most common purposes, the Raw Input API offers a more flexible approach to device data. The enclosed code and the explanations in this article will hopefully prove a useful starting point for anyone looking to handle multiple keyboards in an XP or Vista based application.

Sources

This article gives an overview of the different steps required to implement the Raw Input API. For further information on handling raw input:

History

  • March 2013 - Updated with most of the requested features added (VS2012)
    • Added handling of keypresses
    • Update devices on USB insertion/removal
    • Added proper 32/64bit support
    • Added option to capture keypresses if topmost window
    • Now gets device type from the raw data instead of the registry
    • Inherits from Native Window which makes the rawinput dll easier to consume
    • Detects the L/R versions of shift, control, and alt.
    • Developed on Windows 8 64bit
    • Add a Device Audit which writes to file all the information about the attached devices provided by RawInput.
  • March 2007 - Added WPF sample in response to user request
  • January 2007 - Original version 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Steve Messer
Software Developer (Senior)
United States United States
No Biography provided

Emma Burrows
Software Developer
United Kingdom United Kingdom
Emma's first steps in programming took place at primary school over thirty years ago, thanks to a TI-99/4A and the LOGO language. Following a Master's degree in English Studies (obtained, strangely enough, with a paper on the birth of the microcomputer), Emma started her career in IT.
 
Over the last ten years, she has worked as a localiser, technical writer, editor, web designer, systems administrator, team leader and support engineer, before finally making the move into software development a few years ago. She is now thrilled on a daily basis that she is getting paid for writing code after doing it for free half her life!

Comments and Discussions

 
QuestionThanks PinmemberDave Ledgett11-Mar-13 23:59 
QuestionRawInput blocking message to other windows Pinmemberfriendsterjoel10-Mar-13 22:19 
AnswerRe: RawInput blocking message to other windows PinmemberSteve Messer11-Mar-13 11:25 
GeneralMy vote of 5 PinmvpMichael Haephrati9-Mar-13 0:37 
GeneralRe: My vote of 5 PinmemberSteve Messer9-Mar-13 5:44 
QuestionClear HID device buffer. PinmemberMember 79212685-Mar-13 12:11 
QuestionNot compatible in remote mode PinmemberMember 447014621-Feb-13 20:34 
AnswerRe: Not compatible in remote mode Pinmembersmesser24-Feb-13 11:53 
GeneralRe: Not compatible in remote mode PinmemberMember 44701461-Mar-13 21:23 
GeneralRe: Not compatible in remote mode PinmemberSteve Messer2-Mar-13 10:38 
GeneralRe: Not compatible in remote mode PinmemberMember 44701465-Mar-13 19:03 
GeneralRe: Not compatible in remote mode PinmemberSteve Messer6-Mar-13 18:12 
NewsRefactor (Enhance) [modified] Pinmembersmesser14-Feb-13 8:21 
QuestionSource code: example using winforms and .net 2 Pinmemberpabloko12-Feb-13 1:16 
Hi, i successfully approached this nice code to winforms with .net 2 but with some crazy modifications, for example, y removed the addhook method
 
Its works fine on x86 and x64 bits if compiled as x86 mode
 
Can get a try from http://depositfiles.com/files/oisnipt26[^]
 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Security.Permissions;
 

 

 
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
 
        InputDevice id;
        int NumberOfKeyboards;
        Message message = new Message();
        
        public Form1()
        {
            InitializeComponent();
        }
 

        private void _KeyPressed(object sender, InputDevice.KeyControlEventArgs e)
        {
            string[] tokens = e.Keyboard.Name.Split(';');
            string token = tokens[1];
 

            textBox1.Text = e.Keyboard.deviceHandle.ToString();
            textBox2.Text = e.Keyboard.deviceType;
            textBox3.Text = e.Keyboard.deviceName;
            textBox4.Text = e.Keyboard.key.ToString();
            textBox5.Text = e.Keyboard.vKey;
            textBox6.Text = token;
            textBox7.Text = NumberOfKeyboards.ToString();
        }
 

        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message m)
        {
            
                message.HWnd = m.HWnd;
                message.Msg = m.Msg;
                message.LParam = m.LParam;
                message.WParam = m.WParam;
                if (id!=null)
                id.ProcessMessage(message);
            
            base.WndProc(ref m);
        }
 

 
        void StartWndProcHandler()
        {
            IntPtr hwnd = IntPtr.Zero;
            
 
            try
            {
                hwnd = this.Handle;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            /*
            //Get the Hwnd source   
            HwndSource source = HwndSource.FromHwnd(hwnd);
            //Win32 queue sink
            source.AddHook( new HwndSourceHook(WndProc));
            */
 
            id = new InputDevice(hwnd);
            NumberOfKeyboards = id.EnumerateDevices();
            id.KeyPressed += new InputDevice.DeviceEventHandler(_KeyPressed);
        }
 
        private void Form1_Load(object sender, System.EventArgs e)
        {
            StartWndProcHandler();
        }
 
    }
 

    public sealed class InputDevice
    {
        #region const definitions
 
        private const int RIDEV_INPUTSINK = 0x00000100;
        private const int RID_INPUT = 0x10000003;
 
        private const int FAPPCOMMAND_MASK = 0xF000;
        private const int FAPPCOMMAND_MOUSE = 0x8000;
        private const int FAPPCOMMAND_OEM = 0x1000;
 
        private const int RIM_TYPEMOUSE = 0;
        private const int RIM_TYPEKEYBOARD = 1;
        private const int RIM_TYPEHID = 2;
 
        private const int RIDI_DEVICENAME = 0x20000007;
 
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_SYSKEYDOWN = 0x0104;
        private const int WM_INPUT = 0x00FF;
        private const int VK_OEM_CLEAR = 0xFE;
        private const int VK_LAST_KEY = VK_OEM_CLEAR; // this is a made up value used as a sentinal

        #endregion const definitions
 
        #region structs & enums
 
        public enum DeviceType
        {
            Key,
            Mouse,
            OEM
        }
 
        #region Windows.h structure declarations
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWINPUTDEVICELIST
        {
            public IntPtr hDevice;
            [MarshalAs(UnmanagedType.U4)]
            public int dwType;
        }
 
#if VISTA_64
        [StructLayout(LayoutKind.Explicit)]
        internal struct RAWINPUT
        {
            [FieldOffset(0)]
            public RAWINPUTHEADER header;
            [FieldOffset(24)]
            public RAWMOUSE mouse;
            [FieldOffset(24)]
            public RAWKEYBOARD keyboard;
            [FieldOffset(24)]
            public RAWHID hid;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWINPUTHEADER
        {
            [MarshalAs(UnmanagedType.U4)]
            public int dwType;
            [MarshalAs(UnmanagedType.U4)]
            public int dwSize;
            public IntPtr hDevice;
            public IntPtr wParam;
        }
#else
        [StructLayout(LayoutKind.Explicit)]
        internal struct RAWINPUT
        {
            [FieldOffset(0)]
            public RAWINPUTHEADER header;
            [FieldOffset(16)]
            public RAWMOUSE mouse;
            [FieldOffset(16)]
            public RAWKEYBOARD keyboard;
            [FieldOffset(16)]
            public RAWHID hid;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWINPUTHEADER
        {
            [MarshalAs(UnmanagedType.U4)]
            public int dwType;
            [MarshalAs(UnmanagedType.U4)]
            public int dwSize;
            public IntPtr hDevice;
            [MarshalAs(UnmanagedType.U4)]
            public int wParam;
        }
#endif
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWHID
        {
            [MarshalAs(UnmanagedType.U4)]
            public int dwSizHid;
            [MarshalAs(UnmanagedType.U4)]
            public int dwCount;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct BUTTONSSTR
        {
            [MarshalAs(UnmanagedType.U2)]
            public ushort usButtonFlags;
            [MarshalAs(UnmanagedType.U2)]
            public ushort usButtonData;
        }
 
        [StructLayout(LayoutKind.Explicit)]
        internal struct RAWMOUSE
        {
            [MarshalAs(UnmanagedType.U2)]
            [FieldOffset(0)]
            public ushort usFlags;
            [MarshalAs(UnmanagedType.U4)]
            [FieldOffset(4)]
            public uint ulButtons;
            [FieldOffset(4)]
            public BUTTONSSTR buttonsStr;
            [MarshalAs(UnmanagedType.U4)]
            [FieldOffset(8)]
            public uint ulRawButtons;
            [FieldOffset(12)]
            public int lLastX;
            [FieldOffset(16)]
            public int lLastY;
            [MarshalAs(UnmanagedType.U4)]
            [FieldOffset(20)]
            public uint ulExtraInformation;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWKEYBOARD
        {
            [MarshalAs(UnmanagedType.U2)]
            public ushort MakeCode;
            [MarshalAs(UnmanagedType.U2)]
            public ushort Flags;
            [MarshalAs(UnmanagedType.U2)]
            public ushort Reserved;
            [MarshalAs(UnmanagedType.U2)]
            public ushort VKey;
            [MarshalAs(UnmanagedType.U4)]
            public uint Message;
            [MarshalAs(UnmanagedType.U4)]
            public uint ExtraInformation;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct RAWINPUTDEVICE
        {
            [MarshalAs(UnmanagedType.U2)]
            public ushort usUsagePage;
            [MarshalAs(UnmanagedType.U2)]
            public ushort usUsage;
            [MarshalAs(UnmanagedType.U4)]
            public int dwFlags;
            public IntPtr hwndTarget;
        }
        #endregion Windows.h structure declarations
 
        /// <summary>
        /// Class encapsulating the information about a
        /// keyboard event, including the device it
        /// originated with and what key was pressed
        /// </summary>
        public class DeviceInfo
        {
            public string deviceName;
            public string deviceType;
            public IntPtr deviceHandle;
            public string Name;
            public string source;
            public ushort key;
            public string vKey;
        }
 
        #endregion structs & enums
 
        #region DllImports
 
        [DllImport("User32.dll")]
        extern static uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize);
 
        [DllImport("User32.dll")]
        extern static uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
 
        [DllImport("User32.dll")]
        extern static bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
 
        [DllImport("User32.dll")]
        extern static uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
 
        #endregion DllImports
 
        #region Variables and event handling
 
        /// <summary>
        /// List of keyboard devices
        /// Key: the device handle
        /// Value: the device info class
        /// </summary>
        private Hashtable deviceList = new Hashtable();
 
        //Event and delegate
        public delegate void DeviceEventHandler(object sender, KeyControlEventArgs e);
        public event DeviceEventHandler KeyPressed;
 
        /// <summary>
        /// Arguments provided by the handler for the KeyPressed
        /// event.
        /// </summary>
        public class KeyControlEventArgs : EventArgs
        {
            private DeviceInfo m_deviceInfo;
            private DeviceType m_device;
 
            public KeyControlEventArgs(DeviceInfo dInfo, DeviceType device)
            {
                m_deviceInfo = dInfo;
                m_device = device;
            }
 
            public KeyControlEventArgs()
            {
            }
 
            public DeviceInfo Keyboard
            {
                get { return m_deviceInfo; }
                set { m_deviceInfo = value; }
            }
 
            public DeviceType Device
            {
                get { return m_device; }
                set { m_device = value; }
            }
        }
 
        #endregion Variables and event handling
 
        #region InputDevice( IntPtr hwnd )
 
        /// <summary>
        /// InputDevice constructor; registers the raw input devices
        /// for the calling window.
        /// </summary>
        /// <param name="hwnd">Handle of the window listening for key presses</param>
        public InputDevice(IntPtr hwnd)
        {
            RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
 
            rid[0].usUsagePage = 0x01;
            rid[0].usUsage = 0x06;
            rid[0].dwFlags = RIDEV_INPUTSINK;
            rid[0].hwndTarget = hwnd;
 
            if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
            {
                throw new ApplicationException("Failed to register raw input device(s).");
            }
        }
 
        #endregion InputDevice( IntPtr hwnd )
 
        #region ReadReg( string item, ref bool isKeyboard )
 
        /// <summary>
        /// Reads the Registry to retrieve a friendly description
        /// of the device, and whether it is a keyboard.
        /// </summary>
        /// <param name="item">The device name to search for, as provided by GetRawInputDeviceInfo.</param>
        /// <param name="isKeyboard">Determines whether the device's class is "Keyboard". By reference.</param>
        /// <returns>The device description stored in the Registry entry's DeviceDesc value.</returns>
        private static string ReadReg(string item, ref bool isKeyboard)
        {
            // Example Device Identification string
            // @"\??\ACPI#PNP0303#3&13c0b0c5&0#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}";

            // remove the \??\
            item = item.Substring(4);
 
            string[] split = item.Split('#');
 
            string id_01 = split[0];    // ACPI (Class code)
            string id_02 = split[1];    // PNP0303 (SubClass code)
            string id_03 = split[2];    // 3&13c0b0c5&0 (Protocol code)
            //The final part is the class GUID and is not needed here

            //Open the appropriate key as read-only so no permissions
            //are needed.
            RegistryKey OurKey = Registry.LocalMachine;
 
            string findme = string.Format(@"System\CurrentControlSet\Enum\{0}\{1}\{2}", id_01, id_02, id_03);
 
            OurKey = OurKey.OpenSubKey(findme, false);
 
            //Retrieve the desired information and set isKeyboard
            string deviceDesc = (string)OurKey.GetValue("DeviceDesc");
            string deviceClass = (string)OurKey.GetValue("Class");
 
            if (deviceClass.ToUpper().Equals("KEYBOARD"))
            {
                isKeyboard = true;
            }
            else
            {
                isKeyboard = false;
            }
            return deviceDesc;
        }
 
        #endregion ReadReg( string item, ref bool isKeyboard )
 
        #region int EnumerateDevices()
 
        /// <summary>
        /// Iterates through the list provided by GetRawInputDeviceList,
        /// counting keyboard devices and adding them to deviceList.
        /// </summary>
        /// <returns>The number of keyboard devices found.</returns>
        public int EnumerateDevices()
        {
 
            int NumberOfDevices = 0;
            uint deviceCount = 0;
            int dwSize = (Marshal.SizeOf(typeof(RAWINPUTDEVICELIST)));
 
            if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0)
            {
                IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize * deviceCount));
                GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);
 
                for (int i = 0; i < deviceCount; i++)
                {
                    uint pcbSize = 0;
 
                    RAWINPUTDEVICELIST rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure(
                                               new IntPtr((pRawInputDeviceList.ToInt32() + (dwSize * i))),
                                               typeof(RAWINPUTDEVICELIST));
 
                    GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);
 
                    if (pcbSize > 0)
                    {
                        IntPtr pData = Marshal.AllocHGlobal((int)pcbSize);
                        GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize);
                        string deviceName = Marshal.PtrToStringAnsi(pData);
 
                        //The list will include the "root" keyboard and mouse devices
                        //which appear to be the remote access devices used by Terminal
                        //Services or the Remote Desktop - we're not interested in these
                        //so the following code with drop into the next loop iteration
                        if (deviceName.ToUpper().Contains("ROOT"))
                        {
                            continue;
                        }
 
                        //If the device is identified as a keyboard or HID device,
                        //create a DeviceInfo object to store information about it
                        if (rid.dwType == RIM_TYPEKEYBOARD || rid.dwType == RIM_TYPEHID)
                        {
                            DeviceInfo dInfo = new DeviceInfo();
 
                            dInfo.deviceName = Marshal.PtrToStringAnsi(pData);
                            dInfo.deviceHandle = rid.hDevice;
                            dInfo.deviceType = GetDeviceType(rid.dwType);
 
                            //Check the Registry to see whether this is actually a 
                            //keyboard.
                            bool IsKeyboardDevice = false;
 
                            string DeviceDesc = ReadReg(deviceName, ref IsKeyboardDevice);
                            dInfo.Name = DeviceDesc;
 
                            //If it is a keyboard and it isn't already in the list,
                            //add it to the deviceList hashtable and increase the
                            //NumberOfDevices count
                            if (!deviceList.Contains(rid.hDevice) && IsKeyboardDevice)
                            {
                                NumberOfDevices++;
                                deviceList.Add(rid.hDevice, dInfo);
                            }
                        }
                        Marshal.FreeHGlobal(pData);
                    }
                }
 
                Marshal.FreeHGlobal(pRawInputDeviceList);
                return NumberOfDevices;
            }
            else
            {
                throw new ApplicationException("Error!");
            }
        }
 
        #endregion EnumerateDevices()
 
        #region ProcessInputCommand( Message message )
 
        /// <summary>
        /// Processes WM_INPUT messages to retrieve information about any
        /// keyboard events that occur.
        /// </summary>
        /// <param name="message">The WM_INPUT message to process.</param>
        public void ProcessInputCommand(Message message)
        {
            uint dwSize = 0;
 
            // First call to GetRawInputData sets the value of dwSize
            // dwSize can then be used to allocate the appropriate amount of memore,
            // storing the pointer in "buffer".
            GetRawInputData(message.LParam,
                             RID_INPUT, IntPtr.Zero,
                             ref dwSize,
                             (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));
 
            IntPtr buffer = Marshal.AllocHGlobal((int)dwSize);
            try
            {
                // Check that buffer points to something, and if so,
                // call GetRawInputData again to fill the allocated memory
                // with information about the input
                if (buffer != IntPtr.Zero &&
                    GetRawInputData(message.LParam,
                                     RID_INPUT,
                                     buffer,
                                     ref dwSize,
                                     (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize)
                {
                    // Store the message information in "raw", then check
                    // that the input comes from a keyboard device before
                    // processing it to raise an appropriate KeyPressed event.

                    RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
 
                    if (raw.header.dwType == RIM_TYPEKEYBOARD)
                    {
                        if (raw.keyboard.Message == WM_KEYDOWN || raw.keyboard.Message == WM_SYSKEYDOWN)
                        {
 
                            // Retrieve information about the keystroke
                            ushort key = raw.keyboard.VKey;
 
                            // On most keyboards, "extended" keys such as the arrow or page 
                            // keys return two codes - the key's own code, and an "extended key" flag, which
                            // translates to 255. This flag isn't useful to us, so it can be
                            // disregarded.
                            if (key > VK_LAST_KEY)
                            {
                                return;
                            }
 
                            Keys myKey;  // Keys is defined in System.Windows.Forms 
                            int vkey;
 
                            vkey = raw.keyboard.VKey;
 
                            myKey = (Keys)Enum.Parse(typeof(Keys), Enum.GetName(typeof(Keys), vkey));
 
                            // Retrieve information about the device
                            DeviceInfo dInfo = null;
 
                            if (deviceList.Contains(raw.header.hDevice))
                            {
                                dInfo = (DeviceInfo)deviceList[raw.header.hDevice];
 
                                dInfo.vKey = myKey.ToString();
                                dInfo.key = key;
                            }
                            else
                            {
                                Console.WriteLine("Handle :{0} was not in hashtable", raw.header.hDevice);
                                Console.WriteLine("Maybe this device supports more than one handle or usage page.");
                                Console.WriteLine("This is probably not a standard keyboard.");
                            }
 
                            if (KeyPressed != null && dInfo != null)
                            {
                                KeyPressed(this, new KeyControlEventArgs(dInfo, GetDevice(message.LParam.ToInt32())));
                            }
                            else
                            {
                                Console.WriteLine("Received Unknown Key: {0}", key);
                                Console.WriteLine("Possibly an Unknown device");
                            }
                        }
                    }
                }
            }
            finally
            {
                Marshal.FreeHGlobal(buffer);
            }
        }
 
        #endregion ProcessInputCommand( Message message )
 
        #region DeviceType GetDevice( int param )
 
        /// <summary>
        /// Determines what type of device triggered a WM_INPUT message.
        /// (Used in the ProcessInputCommand).
        /// </summary>
        /// <param name="param">The LParam from a WM_INPUT message.</param>
        /// <returns>A DeviceType enum value.</returns>
        private static DeviceType GetDevice(int param)
        {
            DeviceType deviceType;
 
            switch ((((ushort)(param >> 16)) & FAPPCOMMAND_MASK))
            {
                case FAPPCOMMAND_OEM:
                    deviceType = DeviceType.OEM;
                    break;
                case FAPPCOMMAND_MOUSE:
                    deviceType = DeviceType.Mouse;
                    break;
                default:
                    deviceType = DeviceType.Key;
                    break;
            }
            return deviceType;
        }
 
        #endregion DeviceType GetDevice( int param )
 
        #region ProcessMessage(Message message)
 
        /// <summary>
        /// Filters Windows messages for WM_INPUT messages and calls
        /// ProcessInputCommand if necessary.
        /// </summary>
        /// <param name="message">The Windows message.</param>
        public void ProcessMessage(Message message)
        {
            switch (message.Msg)
            {
                case WM_INPUT:
                    {
                        ProcessInputCommand(message);
                    }
                    break;
            }
        }
 
        #endregion ProcessMessage( Message message )
 
        #region GetDeviceType( int device )
 
        /// <summary>
        /// Converts a RAWINPUTDEVICELIST dwType value to a string
        /// describing the device type.
        /// </summary>
        /// <param name="device">A dwType value (RIM_TYPEMOUSE, 
        /// RIM_TYPEKEYBOARD or RIM_TYPEHID).</param>
        /// <returns>A string representation of the input value.</returns>
        private static string GetDeviceType(int device)
        {
            string deviceType;
            switch (device)
            {
                case RIM_TYPEMOUSE: deviceType = "MOUSE"; break;
                case RIM_TYPEKEYBOARD: deviceType = "KEYBOARD"; break;
                case RIM_TYPEHID: deviceType = "HID"; break;
                default: deviceType = "UNKNOWN"; break;
            }
            return deviceType;
        }
 
        #endregion GetDeviceType( int device )
 
    }
}

QuestionRe: Source code: example using winforms and .net 2 PinmemberMember 79212685-Mar-13 23:04 
QuestionThank you very much!!! Pinmemberfollow3325-Jan-13 6:23 
QuestionSystem.ArgumentOutOfRange Pinmemberyotta24-Jan-13 14:09 
AnswerRe: System.ArgumentOutOfRange PinmemberSteve Messer2-Mar-13 12:05 
QuestionHandled = True does not intercept Pinmemberzcatton21-Nov-12 10:55 
AnswerRe: Handled = True does not intercept Pinmemberpabloko12-Feb-13 1:31 
QuestionAnother windows 8 problem... PinmemberAndrewInEssex1-Nov-12 10:36 
QuestionUsing this on Windows 8 throws error PinmemberSamy198128-Sep-12 1:53 
AnswerRe: Using this on Windows 8 throws error PinmemberSamy19813-Oct-12 0:28 
GeneralRe: Using this on Windows 8 throws error [modified] PinmemberAndrewInEssex30-Oct-12 0:32 
AnswerRe: Using this on Windows 8 throws error Pinmembersmesser12-Feb-13 17:14 
QuestionGetRawInputDeviceInfo in combination with RIDI_DEVICEINFO is not working ... Pinmemberjedike31-Aug-12 2:44 
AnswerRe: GetRawInputDeviceInfo in combination with RIDI_DEVICEINFO is not working ... [modified] Pinmembersmesser24-Feb-13 11:47 
QuestionNewbie wants to run backgroundworker in 'Mouse' version PinmemberMember 935782130-Aug-12 4:00 
QuestionAwesome! thanks for this PinmemberEriol129-Aug-12 4:54 
GeneralHow to get key char Pinmemberknapek27-Jul-12 6:07 
GeneralRe: How to get key char Pinmembersmesser24-Feb-13 5:37 
GeneralAddition ... Pinmemberred_moon3-Jul-12 6:58 
GeneralMy vote of 4 Pinmemberprogrammerdon2-Jul-12 12:01 
GeneralRe: My vote of 4 Pinmembersmesser24-Feb-13 5:33 
GeneralRe: My vote of 4 Pinmemberprogrammerdon29-Mar-13 10:34 
QuestionIt works just on a pc PinmemberMember 887503124-Apr-12 6:00 
AnswerRe: It works just on a pc Pinmembersmesser24-Apr-12 20:46 
GeneralRe: It works just on a pc PinmemberMember 887503125-Apr-12 8:08 
GeneralRe: It works just on a pc Pinmembersmesser25-Apr-12 8:17 
GeneralRe: It works just on a pc PinmemberMember 887503130-Apr-12 7:48 
GeneralRe: It works just on a pc Pinmembersmesser25-Apr-12 8:25 
GeneralRe: It works just on a pc Pinmemberdchurch2414-Jun-12 0:50 
GeneralMy vote of 5 Pinmembermanoj kumar choubey12-Mar-12 22:57 
QuestionReplace Inputs PinmemberRohithxp28-Feb-12 20:50 
GeneralMy vote of 5 Pinmemberlpgray21-Feb-12 6:29 
QuestionCode doesn't work? [modified] PinmemberMarkus Florian9-Feb-12 17:17 
AnswerRe: Code doesn't work? PinmemberMember 106938610-Feb-12 2:59 
AnswerRe: Code doesn't work? PinmemberMember 106938610-Feb-12 3:13 
QuestionDefinitive capture of keyboard PinmemberJemchito5-Jan-12 19:44 
QuestionGreat Article that inspired me to create my own PinmemberGeoffrey Mee18-Dec-11 20:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150123.1 | Last Updated 9 Mar 2013
Article Copyright 2007 by Steve Messer, Emma Burrows
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid