Click here to Skip to main content
15,888,610 members
Articles / Programming Languages / C#

Using Raw Input from C# to handle multiple keyboards

,
Rate me:
Please Sign up or sign in to vote.
4.94/5 (138 votes)
9 Mar 2015LGPL318 min read 2.4M   69.1K   250  
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.
using System;
using System.IO;
using System.Globalization;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace RawInput_dll
{
    static public class Win32
    {
        public static int LoWord(int dwValue)
        {
            return (dwValue & 0xFFFF);
        }

        public static int HiWord(Int64 dwValue)
        {
            return (int)(dwValue >> 16) & ~FAPPCOMMANDMASK;
        }

        public static ushort LowWord(uint val)
        {
            return (ushort)val;
        }

        public static ushort HighWord(uint val)
        {
            return (ushort)(val >> 16);
        }

        public static uint BuildWParam(ushort low, ushort high)
        {
            return ((uint)high << 16) | low;
        }

        // ReSharper disable InconsistentNaming
        public const int KEYBOARD_OVERRUN_MAKE_CODE = 0xFF;
        public const int WM_APPCOMMAND = 0x0319;
        private const int FAPPCOMMANDMASK = 0xF000;
        internal const int FAPPCOMMANDMOUSE = 0x8000;
        internal const int FAPPCOMMANDOEM = 0x1000;

        public const int WM_KEYDOWN = 0x0100;
        public const int WM_KEYUP = 0x0101;
        internal const int WM_SYSKEYDOWN = 0x0104;
        internal const int WM_INPUT = 0x00FF;
        internal const int WM_USB_DEVICECHANGE = 0x0219;

        internal const int VK_SHIFT = 0x10;

        internal const int RI_KEY_MAKE = 0x00;      // Key Down
        internal const int RI_KEY_BREAK = 0x01;     // Key Up
        internal const int RI_KEY_E0 = 0x02;        // Left version of the key
        internal const int RI_KEY_E1 = 0x04;        // Right version of the key. Only seems to be set for the Pause/Break key.
        
        internal const int VK_CONTROL = 0x11;
        internal const int VK_MENU = 0x12;
        internal const int VK_ZOOM = 0xFB;
        internal const int VK_LSHIFT = 0xA0;
        internal const int VK_RSHIFT = 0xA1;
        internal const int VK_LCONTROL = 0xA2;
        internal const int VK_RCONTROL = 0xA3;
        internal const int VK_LMENU = 0xA4;
        internal const int VK_RMENU = 0xA5;
        
        internal const int SC_SHIFT_R = 0x36;     
        internal const int SC_SHIFT_L = 0x2a;   
        internal const int RIM_INPUT = 0x00; 
        // ReSharper restore InconsistentNaming
        
        [DllImport("User32.dll", SetLastError = true)]
        internal static extern int GetRawInputData(IntPtr hRawInput, DataCommand command, [Out] out InputData buffer, [In, Out] ref int size, int cbSizeHeader);

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

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern uint GetRawInputDeviceInfo(IntPtr hDevice, RawInputDeviceInfo command, IntPtr pData, ref uint size);

        [DllImport("user32.dll")]
        private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint command, ref DeviceInfo data, ref uint dataSize);

      
        [DllImport("User32.dll", SetLastError = true)]
        internal static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint numberDevices, uint size);

        [DllImport("User32.dll", SetLastError = true)]
        internal static extern bool RegisterRawInputDevices(RawInputDevice[] pRawInputDevice, uint numberDevices, uint size);
        
        [DllImport("user32.dll", SetLastError = true)]
        internal static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr notificationFilter, DeviceNotification flags);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool UnregisterDeviceNotification(IntPtr handle);

        public static void DeviceAudit()
        {
            var file = new FileStream("DeviceAudit.txt", FileMode.Create, FileAccess.Write);
            var sw = new StreamWriter(file);

            var keyboardNumber = 0;
            uint deviceCount = 0;
            var dwSize = (Marshal.SizeOf(typeof(Rawinputdevicelist)));

            if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0)
            {
                var pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize * deviceCount));
                GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);

                for (var i = 0; i < deviceCount; i++)
                {
                    uint pcbSize = 0;

                    // On Window 8 64bit when compiling against .Net > 3.5 using .ToInt32 you will generate an arithmetic overflow. Leave as it is for 32bit/64bit applications
                    var rid = (Rawinputdevicelist)Marshal.PtrToStructure(new IntPtr((pRawInputDeviceList.ToInt64() + (dwSize * i))), typeof(Rawinputdevicelist));

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

                    if (pcbSize <= 0)
                    {
                        sw.WriteLine("pcbSize: " + pcbSize);
                        sw.WriteLine(Marshal.GetLastWin32Error());
                        return;
                    }

                    var size = (uint)Marshal.SizeOf(typeof(DeviceInfo));
                    var di = new DeviceInfo {Size = Marshal.SizeOf(typeof (DeviceInfo))};
                    
                    if (GetRawInputDeviceInfo(rid.hDevice, (uint) RawInputDeviceInfo.RIDI_DEVICEINFO, ref di, ref size) <= 0)
                    {
                        sw.WriteLine(Marshal.GetLastWin32Error());
                        return;
                    }
                   
                    var pData = Marshal.AllocHGlobal((int)pcbSize);
                    GetRawInputDeviceInfo(rid.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, pData, ref pcbSize);
                    var deviceName = Marshal.PtrToStringAnsi(pData);

                    if (rid.dwType == DeviceType.RimTypekeyboard || rid.dwType == DeviceType.RimTypeHid)
                    {
                        var deviceDesc = GetDeviceDescription(deviceName);

                        var dInfo = new KeyPressEvent
                        {
                            DeviceName = Marshal.PtrToStringAnsi(pData),
                            DeviceHandle = rid.hDevice,
                            DeviceType = GetDeviceType(rid.dwType),
                            Name = deviceDesc,
                            Source = keyboardNumber++.ToString(CultureInfo.InvariantCulture)
                        };

                        sw.WriteLine(dInfo.ToString());
                        sw.WriteLine(di.ToString());
                        sw.WriteLine(di.KeyboardInfo.ToString());
                        sw.WriteLine(di.HIDInfo.ToString());
                        //sw.WriteLine(di.MouseInfo.ToString());
                        sw.WriteLine("=========================================================================================================");
                    }

                    Marshal.FreeHGlobal(pData);
                }

                Marshal.FreeHGlobal(pRawInputDeviceList);

                sw.Flush();
                sw.Close();
                file.Close();

                return;
            }

            throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public static string GetDeviceType(uint device)
        {
            string deviceType;
            switch (device)
            {
                case DeviceType.RimTypemouse: deviceType = "MOUSE"; break;
                case DeviceType.RimTypekeyboard: deviceType = "KEYBOARD"; break;
                case DeviceType.RimTypeHid: deviceType = "HID"; break;
                default: deviceType = "UNKNOWN"; break;
            }

            return deviceType;
        }

        public static string GetDeviceDescription(string device)
        {
            string deviceDesc;
            try
            {
                var deviceKey = RegistryAccess.GetDeviceKey(device);
                deviceDesc = deviceKey.GetValue("DeviceDesc").ToString();
                deviceDesc = deviceDesc.Substring(deviceDesc.IndexOf(';') + 1);
            }
            catch (Exception)
            {
                deviceDesc = "Device is malformed unable to look up in the registry";
            }
            
            //var deviceClass = RegistryAccess.GetClassType(deviceKey.GetValue("ClassGUID").ToString());
            //isKeyboard = deviceClass.ToUpper().Equals( "KEYBOARD" );

            return deviceDesc;
        }

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

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


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

Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions