Adding support for usage value and thus joystick/gamepad.
authorStephaneLenclud
Sun, 15 Mar 2015 12:44:58 +0100
changeset 735262f4b7c4ad
parent 72 471b1d45c46a
child 74 e5e903ee681d
Adding support for usage value and thus joystick/gamepad.
Adding generic support for direction pad.
HidDevice.cs
HidEvent.cs
RawInput.cs
Win32Hid.cs
     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