HidDevice.cs
author StephaneLenclud
Sun, 15 Mar 2015 09:53:37 +0100
changeset 72 471b1d45c46a
parent 71 c28dd93b94c5
child 73 5262f4b7c4ad
permissions -rw-r--r--
Adding button count property to HID device.
Adding device list refresh button.
Better reporting of input value capabilities description.
     1 using System;
     2 using System.Windows.Forms;
     3 using System.Runtime.InteropServices;
     4 using System.Diagnostics;
     5 using System.Text;
     6 using Microsoft.Win32.SafeHandles;
     7 using Win32;
     8 
     9 namespace Hid
    10 {
    11     /// <summary>
    12     /// Represent a HID device.
    13     /// Rename to RawInputDevice?
    14     /// </summary>
    15     public class HidDevice: IDisposable
    16     {
    17         /// <summary>
    18         /// Unique name of that HID device.
    19         /// Notably used as input to CreateFile.
    20         /// </summary>
    21         public string Name { get; private set; }
    22 
    23         /// <summary>
    24         /// Friendly name that people should be able to read.
    25         /// </summary>
    26         public string FriendlyName { get; private set; }
    27 
    28         //
    29         public string Manufacturer { get; private set; }
    30         public string Product { get; private set; }
    31         public ushort VendorId { get; private set; }
    32         public ushort ProductId { get; private set; }
    33         public ushort Version { get; private set; }
    34         //Pre-parsed HID descriptor
    35         public IntPtr PreParsedData {get; private set;}
    36         //Info
    37         public RID_DEVICE_INFO Info { get {return iInfo;} }
    38         private RID_DEVICE_INFO iInfo;
    39         //Capabilities
    40         public HIDP_CAPS Capabilities { get { return iCapabilities; } }
    41         private HIDP_CAPS iCapabilities;
    42         public string InputCapabilitiesDescription { get; private set; }
    43         //Input Button Capabilities
    44         public HIDP_BUTTON_CAPS[] InputButtonCapabilities { get { return iInputButtonCapabilities; } }
    45         private HIDP_BUTTON_CAPS[] iInputButtonCapabilities;
    46         //Input Value Capabilities
    47         public HIDP_VALUE_CAPS[] InputValueCapabilities { get { return iInputValueCapabilities; } }
    48         private HIDP_VALUE_CAPS[] iInputValueCapabilities;
    49 
    50         //
    51         public int ButtonCount { get; private set; }
    52 
    53         /// <summary>
    54         /// Class constructor will fetch this object properties from HID sub system.
    55         /// </summary>
    56         /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
    57         public HidDevice(IntPtr hRawInputDevice)
    58         {
    59             //Try construct and rollback if needed
    60             try
    61             {
    62                 Construct(hRawInputDevice);
    63             }
    64             catch (System.Exception ex)
    65             {
    66                 //Just rollback and propagate
    67                 Dispose();
    68                 throw ex;
    69             }
    70         }
    71 
    72 
    73         /// <summary>
    74         /// Make sure dispose is called even if the user forgot about it.
    75         /// </summary>
    76         ~HidDevice()
    77         {
    78             Dispose();
    79         }
    80 
    81         /// <summary>
    82         /// Private constructor.
    83         /// </summary>
    84         /// <param name="hRawInputDevice"></param>
    85         private void Construct(IntPtr hRawInputDevice)
    86         {
    87             PreParsedData = IntPtr.Zero;
    88             iInputButtonCapabilities = null;
    89             iInputValueCapabilities = null;
    90 
    91             //Fetch various information defining the given HID device
    92             Name = Win32.RawInput.GetDeviceName(hRawInputDevice);
    93 
    94             //Fetch device info
    95             iInfo = new RID_DEVICE_INFO();
    96             if (!Win32.RawInput.GetDeviceInfo(hRawInputDevice, ref iInfo))
    97             {
    98                 throw new Exception("HidDevice: GetDeviceInfo failed: " + Marshal.GetLastWin32Error().ToString());
    99             }
   100 
   101             //Open our device from the device name/path
   102             SafeFileHandle handle = Win32.Function.CreateFile(Name,
   103                 Win32.FileAccess.NONE,
   104                 Win32.FileShare.FILE_SHARE_READ | Win32.FileShare.FILE_SHARE_WRITE,
   105                 IntPtr.Zero,
   106                 Win32.CreationDisposition.OPEN_EXISTING,
   107                 Win32.FileFlagsAttributes.FILE_FLAG_OVERLAPPED,
   108                 IntPtr.Zero
   109                 );
   110 
   111             //Check if CreateFile worked
   112             if (handle.IsInvalid)
   113             {
   114                 throw new Exception("HidDevice: CreateFile failed: " + Marshal.GetLastWin32Error().ToString());
   115             }
   116 
   117             //Get manufacturer string
   118             StringBuilder manufacturerString = new StringBuilder(256);
   119             if (Win32.Function.HidD_GetManufacturerString(handle, manufacturerString, manufacturerString.Capacity))
   120             {
   121                 Manufacturer = manufacturerString.ToString();
   122             }
   123 
   124             //Get product string
   125             StringBuilder productString = new StringBuilder(256);
   126             if (Win32.Function.HidD_GetProductString(handle, productString, productString.Capacity))
   127             {
   128                 Product = productString.ToString();
   129             }
   130 
   131             //Get attributes
   132             Win32.HIDD_ATTRIBUTES attributes = new Win32.HIDD_ATTRIBUTES();
   133             if (Win32.Function.HidD_GetAttributes(handle, ref attributes))
   134             {
   135                 VendorId = attributes.VendorID;
   136                 ProductId = attributes.ProductID;
   137                 Version = attributes.VersionNumber;
   138             }
   139 
   140             handle.Close();
   141 
   142             SetFriendlyName();            
   143 
   144             //Get our HID descriptor pre-parsed data
   145             PreParsedData = Win32.RawInput.GetPreParsedData(hRawInputDevice);
   146 
   147             if (PreParsedData == IntPtr.Zero)
   148             {
   149                 //We are done then.
   150                 //Some devices don't have pre-parsed data.
   151                 return;
   152             }
   153 
   154             //Get capabilities
   155             HidStatus status = Win32.Function.HidP_GetCaps(PreParsedData, ref iCapabilities);
   156             if (status != HidStatus.HIDP_STATUS_SUCCESS)
   157             {
   158                 throw new Exception("HidDevice: HidP_GetCaps failed: " + status.ToString());
   159             }
   160 
   161             SetInputCapabilitiesDescription();
   162 
   163             //Get input button caps if needed
   164             if (Capabilities.NumberInputButtonCaps > 0)
   165             {
   166                 iInputButtonCapabilities = new HIDP_BUTTON_CAPS[Capabilities.NumberInputButtonCaps];
   167                 ushort buttonCapabilitiesLength = Capabilities.NumberInputButtonCaps;
   168                 status = Win32.Function.HidP_GetButtonCaps(HIDP_REPORT_TYPE.HidP_Input, iInputButtonCapabilities, ref buttonCapabilitiesLength, PreParsedData);
   169                 if (status != HidStatus.HIDP_STATUS_SUCCESS || buttonCapabilitiesLength != Capabilities.NumberInputButtonCaps)
   170                 {
   171                     throw new Exception("HidDevice: HidP_GetButtonCaps failed: " + status.ToString());
   172                 }
   173 
   174                 ComputeButtonCount();
   175             }
   176 
   177             //Get input value caps if needed
   178             if (Capabilities.NumberInputValueCaps > 0)
   179             {
   180                 iInputValueCapabilities = new HIDP_VALUE_CAPS[Capabilities.NumberInputValueCaps];
   181                 ushort valueCapabilitiesLength = Capabilities.NumberInputValueCaps;
   182                 status = Win32.Function.HidP_GetValueCaps(HIDP_REPORT_TYPE.HidP_Input, iInputValueCapabilities, ref valueCapabilitiesLength, PreParsedData);
   183                 if (status != HidStatus.HIDP_STATUS_SUCCESS || valueCapabilitiesLength != Capabilities.NumberInputValueCaps)
   184                 {
   185                     throw new Exception("HidDevice: HidP_GetValueCaps failed: " + status.ToString());
   186                 }
   187             }
   188         }
   189 
   190 
   191         /// <summary>
   192         /// Useful for gamepads.
   193         /// </summary>
   194         void ComputeButtonCount()
   195         {
   196             ButtonCount = 0;
   197             foreach (HIDP_BUTTON_CAPS bc in iInputButtonCapabilities)
   198             {
   199                 if (bc.IsRange)
   200                 {
   201                     ButtonCount += (bc.Range.UsageMax - bc.Range.UsageMin + 1);
   202                 }
   203             }            
   204         }
   205 
   206 
   207         /// <summary>
   208         /// 
   209         /// </summary>
   210         void SetInputCapabilitiesDescription()
   211         {
   212             InputCapabilitiesDescription = "[ Input Capabilities ] Button: " + Capabilities.NumberInputButtonCaps + " - Value: " + Capabilities.NumberInputValueCaps + " - Data indices: " + Capabilities.NumberInputDataIndices;
   213         }
   214 
   215         /// <summary>
   216         /// 
   217         /// </summary>
   218         private void SetFriendlyName()
   219         {
   220             //Work out proper suffix for our device root node.
   221             //That allows users to see in a glance what kind of device this is.
   222             string suffix = "";
   223             Type usageCollectionType = null;
   224             if (Info.dwType == RawInputDeviceType.RIM_TYPEHID)
   225             {
   226                 //Process usage page
   227                 if (Enum.IsDefined(typeof(Hid.UsagePage), Info.hid.usUsagePage))
   228                 {
   229                     //We know this usage page, add its name
   230                     Hid.UsagePage usagePage = (Hid.UsagePage)Info.hid.usUsagePage;
   231                     suffix += " ( " + usagePage.ToString() + ", ";
   232                     usageCollectionType = Hid.Utils.UsageCollectionType(usagePage);
   233                 }
   234                 else
   235                 {
   236                     //We don't know this usage page, add its value
   237                     suffix += " ( 0x" + Info.hid.usUsagePage.ToString("X4") + ", ";
   238                 }
   239 
   240                 //Process usage collection
   241                 //We don't know this usage page, add its value
   242                 if (usageCollectionType == null || !Enum.IsDefined(usageCollectionType, Info.hid.usUsage))
   243                 {
   244                     //Show Hexa
   245                     suffix += "0x" + Info.hid.usUsage.ToString("X4") + " )";
   246                 }
   247                 else
   248                 {
   249                     //We know this usage page, add its name
   250                     suffix += Enum.GetName(usageCollectionType, Info.hid.usUsage) + " )";
   251                 }
   252             }
   253             else if (Info.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
   254             {
   255                 suffix = " - Keyboard";
   256             }
   257             else if (Info.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
   258             {
   259                 suffix = " - Mouse";
   260             }
   261 
   262             if (Product != null && Product.Length > 1)
   263             {
   264                 //This device as a proper name, use it
   265                 FriendlyName = Product + suffix;
   266             }
   267             else
   268             {   
   269                 //Extract friendly name from name
   270                 char[] delimiterChars = { '#', '&'};
   271                 string[] words = Name.Split(delimiterChars);
   272                 if (words.Length >= 2)
   273                 {
   274                     //Use our name sub-string to describe this device
   275                     FriendlyName = words[1] + " - 0x" + ProductId.ToString("X4") + suffix;
   276                 }
   277                 else
   278                 {
   279                     //No proper name just use the device ID instead
   280                     FriendlyName = "0x" + ProductId.ToString("X4") + suffix;
   281                 }
   282             }
   283 
   284         }
   285 
   286         /// <summary>
   287         /// Dispose is just for unmanaged clean-up.
   288         /// Make sure calling disposed multiple times does not crash.
   289         /// See: http://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface/538238#538238
   290         /// </summary>
   291         public void Dispose()
   292         {
   293             Marshal.FreeHGlobal(PreParsedData);
   294             PreParsedData = IntPtr.Zero;
   295         }
   296 
   297         /// <summary>
   298         /// Provide a description for the given capabilities.
   299         /// Notably describes axis on a gamepad/joystick.
   300         /// </summary>
   301         /// <param name="aCaps"></param>
   302         /// <returns></returns>
   303         public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps)
   304         {
   305             if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), Capabilities.UsagePage))
   306             {
   307                 Type usageType=Utils.UsageType((UsagePage)Capabilities.UsagePage);
   308                 string name = Enum.GetName(usageType, aCaps.NotRange.Usage);
   309                 if (name == null)
   310                 {
   311                     //Could not find that usage in our enum.
   312                     //Provide a relevant warning instead.
   313                     name = "Usage 0x" + aCaps.NotRange.Usage.ToString("X2") + " not defined in " + usageType.Name;
   314                 }
   315                 else
   316                 {
   317                     //Prepend our usage type name
   318                     name = usageType.Name + "." + name;
   319                 }
   320                 return "Input Value: " + name;
   321             }
   322 
   323             return null;
   324         }
   325 
   326         public bool IsGamePad
   327         {
   328             get
   329             {
   330                 return ((UsagePage)iCapabilities.UsagePage == UsagePage.GenericDesktopControls && (UsageCollection.GenericDesktop)iCapabilities.Usage == UsageCollection.GenericDesktop.GamePad);
   331             }
   332         }
   333 
   334 
   335         /// <summary>
   336         /// Print information about this device to our debug output.
   337         /// </summary>
   338         public void DebugWrite()
   339         {
   340             Debug.WriteLine("================ HID =========================================================================================");
   341             Debug.WriteLine("==== Name: " + Name);
   342             Debug.WriteLine("==== Manufacturer: " + Manufacturer);
   343             Debug.WriteLine("==== Product: " + Product);
   344             Debug.WriteLine("==== VendorID: 0x" + VendorId.ToString("X4"));
   345             Debug.WriteLine("==== ProductID: 0x" + ProductId.ToString("X4"));
   346             Debug.WriteLine("==== Version: " + Version.ToString());
   347             Debug.WriteLine("==============================================================================================================");
   348         }
   349 
   350     }
   351 
   352 }