Adding support for usage value and thus joystick/gamepad.
Adding generic support for direction pad.
1.1 --- a/HidDevice.cs Sun Mar 15 09:53:37 2015 +0100
1.2 +++ b/HidDevice.cs Sun Mar 15 12:44:58 2015 +0100
1.3 @@ -300,11 +300,11 @@
1.4 /// </summary>
1.5 /// <param name="aCaps"></param>
1.6 /// <returns></returns>
1.7 - public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps)
1.8 + static public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps)
1.9 {
1.10 - if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), Capabilities.UsagePage))
1.11 + if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), aCaps.UsagePage))
1.12 {
1.13 - Type usageType=Utils.UsageType((UsagePage)Capabilities.UsagePage);
1.14 + Type usageType = Utils.UsageType((UsagePage)aCaps.UsagePage);
1.15 string name = Enum.GetName(usageType, aCaps.NotRange.Usage);
1.16 if (name == null)
1.17 {
2.1 --- a/HidEvent.cs Sun Mar 15 09:53:37 2015 +0100
2.2 +++ b/HidEvent.cs Sun Mar 15 12:44:58 2015 +0100
2.3 @@ -12,6 +12,22 @@
2.4 namespace Hid
2.5 {
2.6 /// <summary>
2.7 + /// We provide utility functions to interpret gamepad dpad state.
2.8 + /// </summary>
2.9 + public enum DirectionPadState
2.10 + {
2.11 + Rest=-1,
2.12 + Up=0,
2.13 + UpRight=1,
2.14 + Right=2,
2.15 + DownRight=3,
2.16 + Down=4,
2.17 + DownLeft=5,
2.18 + Left=6,
2.19 + UpLeft=7
2.20 + }
2.21 +
2.22 + /// <summary>
2.23 /// Represent a HID event.
2.24 /// TODO: Rename this into HidRawInput?
2.25 /// </summary>
2.26 @@ -39,6 +55,10 @@
2.27 public ushort UsageCollection { get; private set; }
2.28 public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
2.29 public List<ushort> Usages { get; private set; }
2.30 + /// <summary>
2.31 + /// Sorted in the same order as Device.InputValueCapabilities.
2.32 + /// </summary>
2.33 + public Dictionary<HIDP_VALUE_CAPS,uint> UsageValues { get; private set; }
2.34 //TODO: We need a collection of input report
2.35 public byte[] InputReport { get; private set; }
2.36 //
2.37 @@ -86,6 +106,7 @@
2.38 Timer = new System.Timers.Timer();
2.39 Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
2.40 Usages = new List<ushort>();
2.41 + UsageValues = new Dictionary<HIDP_VALUE_CAPS,uint>();
2.42 OnHidEventRepeat += aRepeatDelegate;
2.43
2.44 if (aMessage.Msg != Const.WM_INPUT)
2.45 @@ -159,45 +180,8 @@
2.46
2.47 //Copy HID input into our buffer
2.48 Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid);
2.49 -
2.50 - //Print HID input report in our debug output
2.51 - //string hidDump = "HID input report: " + InputReportString();
2.52 - //Debug.WriteLine(hidDump);
2.53 -
2.54 - //Do proper parsing of our HID report
2.55 - //First query our usage count
2.56 - uint usageCount = 0;
2.57 - Win32.USAGE_AND_PAGE[] usages = null;
2.58 - Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
2.59 - if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
2.60 - {
2.61 - //Allocate a large enough buffer
2.62 - usages = new Win32.USAGE_AND_PAGE[usageCount];
2.63 - //...and fetch our usages
2.64 - status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
2.65 - if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
2.66 - {
2.67 - Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
2.68 - }
2.69 - }
2.70 - else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
2.71 - {
2.72 - Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
2.73 - }
2.74 -
2.75 - Debug.WriteLine("Usage count: " + usageCount.ToString());
2.76 -
2.77 - //Copy usages into this event
2.78 - if (usages != null)
2.79 - {
2.80 - foreach (USAGE_AND_PAGE up in usages)
2.81 - {
2.82 - //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
2.83 - //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
2.84 - //Add this usage to our list
2.85 - Usages.Add(up.Usage);
2.86 - }
2.87 - }
2.88 + //
2.89 + ProcessInputReport(InputReport);
2.90 }
2.91 }
2.92 else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
2.93 @@ -240,6 +224,153 @@
2.94 IsValid = true;
2.95 }
2.96
2.97 + /// <summary>
2.98 + ///
2.99 + /// </summary>
2.100 + private void ProcessInputReport(byte[] aInputReport)
2.101 + {
2.102 + //Print HID input report in our debug output
2.103 + //string hidDump = "HID input report: " + InputReportString();
2.104 + //Debug.WriteLine(hidDump);
2.105 +
2.106 + //Get all our usages, those are typically the buttons currently pushed on a gamepad.
2.107 + //For a remote control it's usually just the one button that was pushed.
2.108 + GetUsages(aInputReport);
2.109 +
2.110 + //Now process direction pad (d-pad, dpad) and axes
2.111 + GetUsageValues(aInputReport);
2.112 + }
2.113 +
2.114 + /// <summary>
2.115 + /// Typically fetches values of a joystick/gamepad axis and dpad directions.
2.116 + /// </summary>
2.117 + /// <param name="aInputReport"></param>
2.118 + private void GetUsageValues(byte[] aInputReport)
2.119 + {
2.120 + if (Device.InputValueCapabilities == null)
2.121 + {
2.122 + return;
2.123 + }
2.124 +
2.125 + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
2.126 + {
2.127 + if (caps.IsRange)
2.128 + {
2.129 + //What should we do with those guys?
2.130 + continue;
2.131 + }
2.132 +
2.133 + //Now fetch and add our usage value
2.134 + uint usageValue = 0;
2.135 + 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);
2.136 + if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS)
2.137 + {
2.138 + UsageValues[caps]=usageValue;
2.139 + }
2.140 + }
2.141 + }
2.142 +
2.143 + /// <summary>
2.144 + /// Get all our usages, those are typically the buttons currently pushed on a gamepad.
2.145 + /// For a remote control it's usually just the one button that was pushed.
2.146 + /// </summary>
2.147 + private void GetUsages(byte[] aInputReport)
2.148 + {
2.149 + //Do proper parsing of our HID report
2.150 + //First query our usage count
2.151 + uint usageCount = 0;
2.152 + Win32.USAGE_AND_PAGE[] usages = null;
2.153 + Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
2.154 + if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
2.155 + {
2.156 + //Allocate a large enough buffer
2.157 + usages = new Win32.USAGE_AND_PAGE[usageCount];
2.158 + //...and fetch our usages
2.159 + status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
2.160 + if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
2.161 + {
2.162 + Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
2.163 + }
2.164 + }
2.165 + else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
2.166 + {
2.167 + Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
2.168 + }
2.169 +
2.170 + Debug.WriteLine("Usage count: " + usageCount.ToString());
2.171 +
2.172 + //Copy usages into this event
2.173 + if (usages != null)
2.174 + {
2.175 + foreach (USAGE_AND_PAGE up in usages)
2.176 + {
2.177 + //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
2.178 + //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
2.179 + //Add this usage to our list
2.180 + Usages.Add(up.Usage);
2.181 + }
2.182 + }
2.183 + }
2.184 +
2.185 + /// <summary>
2.186 + ///
2.187 + /// </summary>
2.188 + /// <param name="aUsagePage"></param>
2.189 + /// <param name="Usage"></param>
2.190 + /// <returns></returns>
2.191 + public uint GetUsageValue(ushort aUsagePage, ushort aUsage)
2.192 + {
2.193 + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
2.194 + {
2.195 + if (caps.IsRange)
2.196 + {
2.197 + //What should we do with those guys?
2.198 + continue;
2.199 + }
2.200 +
2.201 + //Check if we have a match
2.202 + if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
2.203 + {
2.204 + return UsageValues[caps];
2.205 + }
2.206 + }
2.207 +
2.208 + return 0;
2.209 + }
2.210 +
2.211 + /// <summary>
2.212 + ///
2.213 + /// </summary>
2.214 + /// <param name="aUsagePage"></param>
2.215 + /// <param name="aUsage"></param>
2.216 + /// <returns></returns>
2.217 + public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage)
2.218 + {
2.219 + int i = -1;
2.220 + foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
2.221 + {
2.222 + i++;
2.223 + if (caps.IsRange)
2.224 + {
2.225 + //What should we do with those guys?
2.226 + continue;
2.227 + }
2.228 +
2.229 + //Check if we have a match
2.230 + if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
2.231 + {
2.232 + return i;
2.233 + }
2.234 + }
2.235 +
2.236 + return i;
2.237 + }
2.238 +
2.239 +
2.240 + /// <summary>
2.241 + /// TODO: Move this to another level?
2.242 + /// </summary>
2.243 + /// <param name="aInterval"></param>
2.244 public void StartRepeatTimer(double aInterval)
2.245 {
2.246 if (Timer == null)
2.247 @@ -275,6 +406,41 @@
2.248 }
2.249
2.250 /// <summary>
2.251 + /// Provide the state of the dpad or hat switch if any.
2.252 + /// If no dpad is found we return 'at rest'.
2.253 + /// </summary>
2.254 + /// <returns></returns>
2.255 + public DirectionPadState GetDirectionPadState()
2.256 + {
2.257 + int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
2.258 + if (index < 0)
2.259 + {
2.260 + //No hat switch found
2.261 + return DirectionPadState.Rest;
2.262 + }
2.263 +
2.264 + HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index];
2.265 + if (caps.IsRange)
2.266 + {
2.267 + //Defensive
2.268 + return DirectionPadState.Rest;
2.269 + }
2.270 +
2.271 + uint dpadUsageValue = UsageValues[caps];
2.272 +
2.273 + if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax)
2.274 + {
2.275 + //Out of range means at rest
2.276 + return DirectionPadState.Rest;
2.277 + }
2.278 +
2.279 + //Normalize value to start at zero
2.280 + //TODO: more error check here?
2.281 + DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin);
2.282 + return res;
2.283 + }
2.284 +
2.285 + /// <summary>
2.286 /// Print information about this device to our debug output.
2.287 /// </summary>
2.288 public void DebugWrite()
2.289 @@ -356,6 +522,52 @@
2.290 }
2.291 }
2.292
2.293 + //If we are a gamepad display axis and dpad values
2.294 + if (Device.IsGamePad)
2.295 + {
2.296 + //uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
2.297 + //usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText;
2.298 +
2.299 + if (usageText != "")
2.300 + {
2.301 + //Add a separator
2.302 + usageText += " (Buttons)";
2.303 + }
2.304 +
2.305 + if (usageText != "")
2.306 + {
2.307 + //Add a separator
2.308 + usageText += ", ";
2.309 + }
2.310 +
2.311 + usageText += GetDirectionPadState().ToString();
2.312 +
2.313 + foreach (KeyValuePair<HIDP_VALUE_CAPS, uint> entry in UsageValues)
2.314 + {
2.315 + if (entry.Key.IsRange)
2.316 + {
2.317 + continue;
2.318 + }
2.319 +
2.320 + Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage);
2.321 + if (usageType == null)
2.322 + {
2.323 + //TODO: check why this is happening on Logitech rumble gamepad 2.
2.324 + //Probably some of our axis are hiding in there.
2.325 + continue;
2.326 + }
2.327 + string name = Enum.GetName(usageType, entry.Key.NotRange.Usage);
2.328 +
2.329 + if (usageText != "")
2.330 + {
2.331 + //Add a separator
2.332 + usageText += ", ";
2.333 + }
2.334 + usageText += entry.Value.ToString("X") + " ("+ name +")";
2.335 + }
2.336 + }
2.337 +
2.338 + //Now create our list item
2.339 ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
2.340 return item;
2.341 }
3.1 --- a/RawInput.cs Sun Mar 15 09:53:37 2015 +0100
3.2 +++ b/RawInput.cs Sun Mar 15 12:44:58 2015 +0100
3.3 @@ -215,7 +215,7 @@
3.4 {
3.5 foreach (HIDP_VALUE_CAPS caps in hidDevice.InputValueCapabilities)
3.6 {
3.7 - string des = hidDevice.InputValueCapabilityDescription(caps);
3.8 + string des = Hid.HidDevice.InputValueCapabilityDescription(caps);
3.9 if (des != null)
3.10 {
3.11 node.Nodes.Add(des);
4.1 --- a/Win32Hid.cs Sun Mar 15 09:53:37 2015 +0100
4.2 +++ b/Win32Hid.cs Sun Mar 15 12:44:58 2015 +0100
4.3 @@ -52,7 +52,7 @@
4.4 ///Report: PCHAR->CHAR*
4.5 ///ReportLength: ULONG->unsigned int
4.6 [System.Runtime.InteropServices.DllImportAttribute("hid.dll", EntryPoint = "HidP_GetUsageValue", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
4.7 - 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);
4.8 + 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);
4.9
4.10
4.11