# HG changeset patch # User StephaneLenclud # Date 1426419898 -3600 # Node ID 5262f4b7c4ad762175cba7e5d1f01d7cda2fa449 # Parent 471b1d45c46a0d752967cadfc8d3901a92a09bfe Adding support for usage value and thus joystick/gamepad. Adding generic support for direction pad. diff -r 471b1d45c46a -r 5262f4b7c4ad HidDevice.cs --- a/HidDevice.cs Sun Mar 15 09:53:37 2015 +0100 +++ b/HidDevice.cs Sun Mar 15 12:44:58 2015 +0100 @@ -300,11 +300,11 @@ /// /// /// - public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps) + static public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps) { - if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), Capabilities.UsagePage)) + if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), aCaps.UsagePage)) { - Type usageType=Utils.UsageType((UsagePage)Capabilities.UsagePage); + Type usageType = Utils.UsageType((UsagePage)aCaps.UsagePage); string name = Enum.GetName(usageType, aCaps.NotRange.Usage); if (name == null) { diff -r 471b1d45c46a -r 5262f4b7c4ad HidEvent.cs --- a/HidEvent.cs Sun Mar 15 09:53:37 2015 +0100 +++ b/HidEvent.cs Sun Mar 15 12:44:58 2015 +0100 @@ -12,6 +12,22 @@ namespace Hid { /// + /// We provide utility functions to interpret gamepad dpad state. + /// + public enum DirectionPadState + { + Rest=-1, + Up=0, + UpRight=1, + Right=2, + DownRight=3, + Down=4, + DownLeft=5, + Left=6, + UpLeft=7 + } + + /// /// Represent a HID event. /// TODO: Rename this into HidRawInput? /// @@ -39,6 +55,10 @@ public ushort UsageCollection { get; private set; } public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } } public List Usages { get; private set; } + /// + /// Sorted in the same order as Device.InputValueCapabilities. + /// + public Dictionary UsageValues { get; private set; } //TODO: We need a collection of input report public byte[] InputReport { get; private set; } // @@ -86,6 +106,7 @@ Timer = new System.Timers.Timer(); Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this); Usages = new List(); + UsageValues = new Dictionary(); OnHidEventRepeat += aRepeatDelegate; if (aMessage.Msg != Const.WM_INPUT) @@ -159,45 +180,8 @@ //Copy HID input into our buffer Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid); - - //Print HID input report in our debug output - //string hidDump = "HID input report: " + InputReportString(); - //Debug.WriteLine(hidDump); - - //Do proper parsing of our HID report - //First query our usage count - uint usageCount = 0; - Win32.USAGE_AND_PAGE[] usages = null; - Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length); - if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL) - { - //Allocate a large enough buffer - usages = new Win32.USAGE_AND_PAGE[usageCount]; - //...and fetch our usages - status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length); - if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) - { - Debug.WriteLine("Second pass could not parse HID data: " + status.ToString()); - } - } - else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) - { - Debug.WriteLine("First pass could not parse HID data: " + status.ToString()); - } - - Debug.WriteLine("Usage count: " + usageCount.ToString()); - - //Copy usages into this event - if (usages != null) - { - foreach (USAGE_AND_PAGE up in usages) - { - //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4")); - //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4")); - //Add this usage to our list - Usages.Add(up.Usage); - } - } + // + ProcessInputReport(InputReport); } } else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE) @@ -240,6 +224,153 @@ IsValid = true; } + /// + /// + /// + private void ProcessInputReport(byte[] aInputReport) + { + //Print HID input report in our debug output + //string hidDump = "HID input report: " + InputReportString(); + //Debug.WriteLine(hidDump); + + //Get all our usages, those are typically the buttons currently pushed on a gamepad. + //For a remote control it's usually just the one button that was pushed. + GetUsages(aInputReport); + + //Now process direction pad (d-pad, dpad) and axes + GetUsageValues(aInputReport); + } + + /// + /// Typically fetches values of a joystick/gamepad axis and dpad directions. + /// + /// + private void GetUsageValues(byte[] aInputReport) + { + if (Device.InputValueCapabilities == null) + { + return; + } + + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) + { + if (caps.IsRange) + { + //What should we do with those guys? + continue; + } + + //Now fetch and add our usage value + uint usageValue = 0; + Win32.HidStatus status = Win32.Function.HidP_GetUsageValue(Win32.HIDP_REPORT_TYPE.HidP_Input, caps.UsagePage, caps.LinkCollection, caps.NotRange.Usage, ref usageValue, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); + if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS) + { + UsageValues[caps]=usageValue; + } + } + } + + /// + /// Get all our usages, those are typically the buttons currently pushed on a gamepad. + /// For a remote control it's usually just the one button that was pushed. + /// + private void GetUsages(byte[] aInputReport) + { + //Do proper parsing of our HID report + //First query our usage count + uint usageCount = 0; + Win32.USAGE_AND_PAGE[] usages = null; + Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); + if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL) + { + //Allocate a large enough buffer + usages = new Win32.USAGE_AND_PAGE[usageCount]; + //...and fetch our usages + status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); + if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) + { + Debug.WriteLine("Second pass could not parse HID data: " + status.ToString()); + } + } + else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) + { + Debug.WriteLine("First pass could not parse HID data: " + status.ToString()); + } + + Debug.WriteLine("Usage count: " + usageCount.ToString()); + + //Copy usages into this event + if (usages != null) + { + foreach (USAGE_AND_PAGE up in usages) + { + //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4")); + //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4")); + //Add this usage to our list + Usages.Add(up.Usage); + } + } + } + + /// + /// + /// + /// + /// + /// + public uint GetUsageValue(ushort aUsagePage, ushort aUsage) + { + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) + { + if (caps.IsRange) + { + //What should we do with those guys? + continue; + } + + //Check if we have a match + if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage) + { + return UsageValues[caps]; + } + } + + return 0; + } + + /// + /// + /// + /// + /// + /// + public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage) + { + int i = -1; + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) + { + i++; + if (caps.IsRange) + { + //What should we do with those guys? + continue; + } + + //Check if we have a match + if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage) + { + return i; + } + } + + return i; + } + + + /// + /// TODO: Move this to another level? + /// + /// public void StartRepeatTimer(double aInterval) { if (Timer == null) @@ -275,6 +406,41 @@ } /// + /// Provide the state of the dpad or hat switch if any. + /// If no dpad is found we return 'at rest'. + /// + /// + public DirectionPadState GetDirectionPadState() + { + int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch); + if (index < 0) + { + //No hat switch found + return DirectionPadState.Rest; + } + + HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index]; + if (caps.IsRange) + { + //Defensive + return DirectionPadState.Rest; + } + + uint dpadUsageValue = UsageValues[caps]; + + if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax) + { + //Out of range means at rest + return DirectionPadState.Rest; + } + + //Normalize value to start at zero + //TODO: more error check here? + DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin); + return res; + } + + /// /// Print information about this device to our debug output. /// public void DebugWrite() @@ -356,6 +522,52 @@ } } + //If we are a gamepad display axis and dpad values + if (Device.IsGamePad) + { + //uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch); + //usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText; + + if (usageText != "") + { + //Add a separator + usageText += " (Buttons)"; + } + + if (usageText != "") + { + //Add a separator + usageText += ", "; + } + + usageText += GetDirectionPadState().ToString(); + + foreach (KeyValuePair entry in UsageValues) + { + if (entry.Key.IsRange) + { + continue; + } + + Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage); + if (usageType == null) + { + //TODO: check why this is happening on Logitech rumble gamepad 2. + //Probably some of our axis are hiding in there. + continue; + } + string name = Enum.GetName(usageType, entry.Key.NotRange.Usage); + + if (usageText != "") + { + //Add a separator + usageText += ", "; + } + usageText += entry.Value.ToString("X") + " ("+ name +")"; + } + } + + //Now create our list item ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") }); return item; } diff -r 471b1d45c46a -r 5262f4b7c4ad RawInput.cs --- a/RawInput.cs Sun Mar 15 09:53:37 2015 +0100 +++ b/RawInput.cs Sun Mar 15 12:44:58 2015 +0100 @@ -215,7 +215,7 @@ { foreach (HIDP_VALUE_CAPS caps in hidDevice.InputValueCapabilities) { - string des = hidDevice.InputValueCapabilityDescription(caps); + string des = Hid.HidDevice.InputValueCapabilityDescription(caps); if (des != null) { node.Nodes.Add(des); diff -r 471b1d45c46a -r 5262f4b7c4ad Win32Hid.cs --- a/Win32Hid.cs Sun Mar 15 09:53:37 2015 +0100 +++ b/Win32Hid.cs Sun Mar 15 12:44:58 2015 +0100 @@ -52,7 +52,7 @@ ///Report: PCHAR->CHAR* ///ReportLength: ULONG->unsigned int [System.Runtime.InteropServices.DllImportAttribute("hid.dll", EntryPoint = "HidP_GetUsageValue", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)] - public static extern HidStatus HidP_GetUsageValue(HIDP_REPORT_TYPE ReportType, ushort UsagePage, ushort LinkCollection, ushort Usage, ref uint UsageValue, System.IntPtr PreparsedData, System.IntPtr Report, uint ReportLength); + public static extern HidStatus HidP_GetUsageValue(HIDP_REPORT_TYPE ReportType, ushort UsagePage, ushort LinkCollection, ushort Usage, ref uint UsageValue, System.IntPtr PreparsedData, [MarshalAs(UnmanagedType.LPArray)] byte[] Report, uint ReportLength);