Draft implementation of proper HID input report parsing based on HID descriptor.
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 }