Draft implementation of proper HID input report parsing based on HID descriptor.
authorsl
Fri, 05 Dec 2014 22:50:39 +0100
changeset 178f7e35c3bfd1
parent 16 9a3e77655031
child 18 24ac84ab9620
Draft implementation of proper HID input report parsing based on HID descriptor.
RawInput.cs
RemoteControlDevice.cs
RemoteControlSample.csproj
Win32Hid.cs
Win32RawInput.cs
     1.1 --- a/RawInput.cs	Wed Dec 03 21:54:45 2014 +0100
     1.2 +++ b/RawInput.cs	Fri Dec 05 22:50:39 2014 +0100
     1.3 @@ -5,6 +5,9 @@
     1.4  
     1.5  namespace Win32
     1.6  {
     1.7 +    /// <summary>
     1.8 +    /// Provide some utility functions for raw input handling.
     1.9 +    /// </summary>
    1.10      static class RawInput
    1.11      {
    1.12          /// <summary>
    1.13 @@ -90,5 +93,145 @@
    1.14              return success;
    1.15          }
    1.16  
    1.17 +        /// <summary>
    1.18 +        /// 
    1.19 +        /// </summary>
    1.20 +        /// <param name="device"></param>
    1.21 +        /// <returns></returns>
    1.22 +        public static IntPtr GetPreParsedData(IntPtr hDevice)
    1.23 +        {
    1.24 +            uint ppDataSize = 256;
    1.25 +            int result = Win32.Function.GetRawInputDeviceInfo(hDevice, Win32.Const.RIDI_PREPARSEDDATA, IntPtr.Zero, ref ppDataSize);
    1.26 +            if (result != 0)
    1.27 +            {
    1.28 +                Debug.WriteLine("Failed to get raw input pre-parsed data size" + result + Marshal.GetLastWin32Error());
    1.29 +                return IntPtr.Zero;
    1.30 +            }
    1.31 +
    1.32 +            IntPtr ppData = Marshal.AllocHGlobal((int)ppDataSize);
    1.33 +            result = Win32.Function.GetRawInputDeviceInfo(hDevice, Win32.Const.RIDI_PREPARSEDDATA, ppData, ref ppDataSize);
    1.34 +            if (result <= 0)
    1.35 +            {
    1.36 +                Debug.WriteLine("Failed to get raw input pre-parsed data" + result + Marshal.GetLastWin32Error());
    1.37 +                return IntPtr.Zero;
    1.38 +            }
    1.39 +            return ppData;
    1.40 +        }
    1.41 +
    1.42 +        /// <summary>
    1.43 +        /// 
    1.44 +        /// </summary>
    1.45 +        /// <param name="driver"></param>
    1.46 +        /// <param name="device"></param>
    1.47 +        /// <param name="input"></param>
    1.48 +        /// <param name="rawInput"></param>
    1.49 +        /// <param name="usageType"></param>
    1.50 +        /// <param name="usage"></param>
    1.51 +        /// <param name="usageName"></param>
    1.52 +        /// <returns></returns>
    1.53 +        /// 
    1.54 +        /*
    1.55 +        private bool GetUsageFromRawInput(TwinhanHidDriver driver, TwinhanHid device, NativeMethods.RAWINPUT input, IntPtr rawInput, out UsageType usageType, out int usage, out string usageName)
    1.56 +        {
    1.57 +            usageType = 0;
    1.58 +            usage = 0;
    1.59 +            usageName = string.Empty;
    1.60 +            if (input.header.dwType == NativeMethods.RawInputDeviceType.RIM_TYPEKEYBOARD)
    1.61 +            {
    1.62 +                if (input.data.keyboard.Flags == NativeMethods.RawInputKeyboardFlag.RI_KEY_BREAK)
    1.63 +                {
    1.64 +                    _modifiers = 0;
    1.65 +                    // Key up event. We don't handle repeats, so ignore this.
    1.66 +                    return false;
    1.67 +                }
    1.68 +                NativeMethods.VirtualKey vk = input.data.keyboard.VKey;
    1.69 +                if (vk == NativeMethods.VirtualKey.VK_CONTROL)
    1.70 +                {
    1.71 +                    _modifiers |= VirtualKeyModifier.Control;
    1.72 +                    return false;
    1.73 +                }
    1.74 +                if (vk == NativeMethods.VirtualKey.VK_SHIFT)
    1.75 +                {
    1.76 +                    _modifiers |= VirtualKeyModifier.Shift;
    1.77 +                    return false;
    1.78 +                }
    1.79 +                if (vk == NativeMethods.VirtualKey.VK_MENU)
    1.80 +                {
    1.81 +                    _modifiers |= VirtualKeyModifier.Alt;
    1.82 +                    return false;
    1.83 +                }
    1.84 +                usageType = UsageType.Keyboard;
    1.85 +                usage = (int)vk | (int)_modifiers;
    1.86 +                usageName = vk.ToString();
    1.87 +                if (_modifiers != 0)
    1.88 +                {
    1.89 +                    usageName += string.Format(", modifiers = {0}", _modifiers);
    1.90 +                }
    1.91 +            }
    1.92 +            else if (input.header.dwType == NativeMethods.RawInputDeviceType.RIM_TYPEHID)
    1.93 +            {
    1.94 +                if ((!driver.IsTerraTec && device.Name.Contains("Col03")) || (driver.IsTerraTec && device.Name.Contains("Col02")))
    1.95 +                {
    1.96 +                    usageType = UsageType.Raw;
    1.97 +                    usage = Marshal.ReadByte(rawInput, HID_INPUT_DATA_OFFSET + 1);
    1.98 +                    usageName = string.Format("0x{0:x2}", usage);
    1.99 +                }
   1.100 +                else if (device.Name.Contains("Col05"))
   1.101 +                {
   1.102 +                    usageType = UsageType.Ascii;
   1.103 +                    usage = Marshal.ReadByte(rawInput, HID_INPUT_DATA_OFFSET + 1);
   1.104 +                    usageName = string.Format("0x{0:x2}", usage);
   1.105 +                }
   1.106 +                else
   1.107 +                {
   1.108 +                    byte[] report = new byte[input.data.hid.dwSizeHid];
   1.109 +                    Marshal.Copy(IntPtr.Add(rawInput, HID_INPUT_DATA_OFFSET), report, 0, report.Length);
   1.110 +                    uint usageCount = input.data.hid.dwCount;
   1.111 +                    NativeMethods.USAGE_AND_PAGE[] usages = new NativeMethods.USAGE_AND_PAGE[usageCount];
   1.112 +                    NativeMethods.HidStatus status = NativeMethods.HidP_GetUsagesEx(NativeMethods.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, device.PreParsedData, report, (uint)report.Length);
   1.113 +                    if (status != NativeMethods.HidStatus.HIDP_STATUS_SUCCESS)
   1.114 +                    {
   1.115 +                        this.LogError("Twinhan HID remote: failed to get raw input HID usages, device = {0}, status = {1}", device.Name, status);
   1.116 +                        Dump.DumpBinary(rawInput, (int)input.header.dwSize);
   1.117 +                        return false;
   1.118 +                    }
   1.119 +                    if (usageCount > 1)
   1.120 +                    {
   1.121 +                        this.LogWarn("Twinhan HID remote: multiple simultaneous HID usages not supported");
   1.122 +                    }
   1.123 +                    NativeMethods.USAGE_AND_PAGE up = usages[0];
   1.124 +                    HidUsagePage page = (HidUsagePage)up.UsagePage;
   1.125 +                    usage = up.Usage;
   1.126 +                    if (page != HidUsagePage.MceRemote && usage == 0)
   1.127 +                    {
   1.128 +                        // Key up event. We don't handle repeats, so ignore this.
   1.129 +                        return false;
   1.130 +                    }
   1.131 +                    if (page == HidUsagePage.Consumer)
   1.132 +                    {
   1.133 +                        usageType = UsageType.Consumer;
   1.134 +                        usageName = Enum.GetName(typeof(HidConsumerUsage), up.Usage);
   1.135 +                    }
   1.136 +                    else if (page == HidUsagePage.MceRemote)
   1.137 +                    {
   1.138 +                        usageType = UsageType.Mce;
   1.139 +                        usageName = Enum.GetName(typeof(MceRemoteUsage), up.Usage);
   1.140 +                    }
   1.141 +                    else
   1.142 +                    {
   1.143 +                        this.LogError("Twinhan HID remote: unexpected usage page, device = {0}, page = {1}, usage = {2}", device.Name, page, up.Usage);
   1.144 +                        return false;
   1.145 +                    }
   1.146 +                }
   1.147 +            }
   1.148 +            else
   1.149 +            {
   1.150 +                this.LogError("Twinhan HID remote: received input from unsupported input device type, device = {0}, type = {1}", device.Name, input.header.dwType);
   1.151 +                return false;
   1.152 +            }
   1.153 +            return true;
   1.154 +        }
   1.155 +         * */
   1.156 +
   1.157      }
   1.158  }
   1.159 \ No newline at end of file
     2.1 --- a/RemoteControlDevice.cs	Wed Dec 03 21:54:45 2014 +0100
     2.2 +++ b/RemoteControlDevice.cs	Fri Dec 05 22:50:39 2014 +0100
     2.3 @@ -395,8 +395,10 @@
     2.4                  Debug.WriteLine("================BACKGROUND");
     2.5              }
     2.6  
     2.7 -            //Declare a pointer
     2.8 +            //Declare some pointers
     2.9              IntPtr rawInputBuffer = IntPtr.Zero;
    2.10 +            //My understanding is that this is basically our HID descriptor
    2.11 +            IntPtr preParsedData = IntPtr.Zero;
    2.12  
    2.13              try
    2.14              {
    2.15 @@ -421,6 +423,9 @@
    2.16                      //Get Usage Page and Usage
    2.17                      Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
    2.18  
    2.19 +
    2.20 +                    preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice);
    2.21 +
    2.22                      //
    2.23                      HidUsageHandler usagePageHandler=null;
    2.24  
    2.25 @@ -449,7 +454,7 @@
    2.26  
    2.27  
    2.28                      //Allocate a buffer for one HID input
    2.29 -                    byte[] hidInput = new byte[rawInput.hid.dwSizeHid];
    2.30 +                    byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
    2.31  
    2.32                      //For each HID input in our raw input
    2.33                      for (int i = 0; i < rawInput.hid.dwCount; i++)
    2.34 @@ -464,11 +469,11 @@
    2.35                          }
    2.36  
    2.37                          //Copy HID input into our buffer
    2.38 -                        Marshal.Copy(new IntPtr(hidInputOffset), hidInput, 0, rawInput.hid.dwSizeHid);
    2.39 +                        Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
    2.40  
    2.41                          //Print HID raw input in our debug output
    2.42                          string hidDump = "HID " + rawInput.hid.dwCount + "/" + rawInput.hid.dwSizeHid + ":";
    2.43 -                        foreach (byte b in hidInput)
    2.44 +                        foreach (byte b in hidInputReport)
    2.45                          {
    2.46                              hidDump += b.ToString("X2");
    2.47                          }
    2.48 @@ -476,19 +481,40 @@
    2.49                          
    2.50                          ushort usage = 0;
    2.51                          //hidInput[0] //Not sure what's the meaning of the code at offset 0
    2.52 -                        if (hidInput.Length == 2)
    2.53 +                        if (hidInputReport.Length == 2)
    2.54                          {
    2.55                              //Single byte code
    2.56 -                            usage = hidInput[1]; //Get button code
    2.57 +                            usage = hidInputReport[1]; //Get button code
    2.58                          }
    2.59 -                        else if (hidInput.Length > 2) //Defensive
    2.60 +                        else if (hidInputReport.Length > 2) //Defensive
    2.61                          {
    2.62                              //Assuming double bytes code
    2.63 -                            usage = (ushort)((hidInput[2] << 8) + hidInput[1]);
    2.64 +                            usage = (ushort)((hidInputReport[2] << 8) + hidInputReport[1]);
    2.65                          }
    2.66  
    2.67                          Debug.WriteLine("Usage: 0x" + usage.ToString("X4"));
    2.68  
    2.69 +                        //Proper parsing now
    2.70 +                        //byte[] report = new byte[input.data.hid.dwSizeHid];
    2.71 +                        //Marshal.Copy(IntPtr.Add(rawInput, HID_INPUT_DATA_OFFSET), report, 0, report.Length);
    2.72 +                        uint usageCount = rawInput.hid.dwCount;
    2.73 +                        Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
    2.74 +                        Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
    2.75 +                        if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
    2.76 +                        {
    2.77 +                            Debug.WriteLine("Could not parse HID data!");
    2.78 +                            //this.LogError("Twinhan HID remote: failed to get raw input HID usages, device = {0}, status = {1}", device.Name, status);
    2.79 +                            //Dump.DumpBinary(rawInput, (int)input.header.dwSize);
    2.80 +                            //return false;
    2.81 +                        }
    2.82 +                        else
    2.83 +                        {
    2.84 +                            Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
    2.85 +                            Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
    2.86 +                        }
    2.87 +
    2.88 +
    2.89 +
    2.90                          //Call on our Usage Page handler
    2.91                          usagePageHandler(usage);
    2.92                      }
    2.93 @@ -510,6 +536,7 @@
    2.94              {
    2.95                  //Always executed when leaving our try block
    2.96                  Marshal.FreeHGlobal(rawInputBuffer);
    2.97 +                Marshal.FreeHGlobal(preParsedData);
    2.98              }
    2.99  		}
   2.100  
     3.1 --- a/RemoteControlSample.csproj	Wed Dec 03 21:54:45 2014 +0100
     3.2 +++ b/RemoteControlSample.csproj	Fri Dec 05 22:50:39 2014 +0100
     3.3 @@ -132,6 +132,7 @@
     3.4      <Compile Include="RemoteControlDevice.cs">
     3.5        <SubType>Code</SubType>
     3.6      </Compile>
     3.7 +    <Compile Include="Win32Hid.cs" />
     3.8      <Compile Include="Win32RawInput.cs" />
     3.9      <Content Include="App.ico" />
    3.10      <EmbeddedResource Include="Form1.resx">
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/Win32Hid.cs	Fri Dec 05 22:50:39 2014 +0100
     4.3 @@ -0,0 +1,69 @@
     4.4 +using System;
     4.5 +using System.Runtime.InteropServices;
     4.6 +
     4.7 +namespace Win32
     4.8 +{
     4.9 +
    4.10 +    static partial class Function
    4.11 +    {
    4.12 +        [DllImport("hid.dll", CharSet = CharSet.Unicode)]
    4.13 +        public static extern HidStatus HidP_GetUsagesEx(HIDP_REPORT_TYPE ReportType, ushort LinkCollection, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] USAGE_AND_PAGE[] ButtonList, ref uint UsageLength, IntPtr PreparsedData, [MarshalAs(UnmanagedType.LPArray)] byte[] Report, uint ReportLength);
    4.14 +    }
    4.15 +
    4.16 +
    4.17 +    static partial class Macro
    4.18 +    {
    4.19 +
    4.20 +
    4.21 +    }
    4.22 +
    4.23 +
    4.24 +    static partial class Const
    4.25 +    {
    4.26 +
    4.27 +
    4.28 +    }
    4.29 +
    4.30 +
    4.31 +    public enum HIDP_REPORT_TYPE : ushort
    4.32 +    {
    4.33 +        HidP_Input = 0,
    4.34 +        HidP_Output,
    4.35 +        HidP_Feature
    4.36 +    }
    4.37 +
    4.38 +
    4.39 +    public enum HidStatus : uint
    4.40 +    {
    4.41 +        HIDP_STATUS_SUCCESS = 0x110000,
    4.42 +        HIDP_STATUS_NULL = 0x80110001,
    4.43 +        HIDP_STATUS_INVALID_PREPARSED_DATA = 0xc0110001,
    4.44 +        HIDP_STATUS_INVALID_REPORT_TYPE = 0xc0110002,
    4.45 +        HIDP_STATUS_INVALID_REPORT_LENGTH = 0xc0110003,
    4.46 +        HIDP_STATUS_USAGE_NOT_FOUND = 0xc0110004,
    4.47 +        HIDP_STATUS_VALUE_OUT_OF_RANGE = 0xc0110005,
    4.48 +        HIDP_STATUS_BAD_LOG_PHY_VALUES = 0xc0110006,
    4.49 +        HIDP_STATUS_BUFFER_TOO_SMALL = 0xc0110007,
    4.50 +        HIDP_STATUS_INTERNAL_ERROR = 0xc0110008,
    4.51 +        HIDP_STATUS_I8042_TRANS_UNKNOWN = 0xc0110009,
    4.52 +        HIDP_STATUS_INCOMPATIBLE_REPORT_ID = 0xc011000a,
    4.53 +        HIDP_STATUS_NOT_VALUE_ARRAY = 0xc011000b,
    4.54 +        HIDP_STATUS_IS_VALUE_ARRAY = 0xc011000c,
    4.55 +        HIDP_STATUS_DATA_INDEX_NOT_FOUND = 0xc011000d,
    4.56 +        HIDP_STATUS_DATA_INDEX_OUT_OF_RANGE = 0xc011000e,
    4.57 +        HIDP_STATUS_BUTTON_NOT_PRESSED = 0xc011000f,
    4.58 +        HIDP_STATUS_REPORT_DOES_NOT_EXIST = 0xc0110010,
    4.59 +        HIDP_STATUS_NOT_IMPLEMENTED = 0xc0110020,
    4.60 +        HIDP_STATUS_I8242_TRANS_UNKNOWN = 0xc0110009
    4.61 +    }
    4.62 +
    4.63 +
    4.64 +    [StructLayout(LayoutKind.Sequential)]
    4.65 +    public struct USAGE_AND_PAGE
    4.66 +    {
    4.67 +        public ushort Usage;
    4.68 +        public ushort UsagePage;
    4.69 +    };
    4.70 +
    4.71 +
    4.72 +}
    4.73 \ No newline at end of file
     5.1 --- a/Win32RawInput.cs	Wed Dec 03 21:54:45 2014 +0100
     5.2 +++ b/Win32RawInput.cs	Fri Dec 05 22:50:39 2014 +0100
     5.3 @@ -219,9 +219,9 @@
     5.4      internal struct RAWHID
     5.5      {
     5.6          [MarshalAs(UnmanagedType.U4)]
     5.7 -        public int dwSizeHid;
     5.8 +        public uint dwSizeHid;
     5.9          [MarshalAs(UnmanagedType.U4)]
    5.10 -        public int dwCount;
    5.11 +        public uint dwCount;
    5.12          //
    5.13          //BYTE  bRawData[1];
    5.14      }