HidDevice.cs
author StephaneLenclud
Sun, 15 Feb 2015 23:06:54 +0100
changeset 66 3461ee6800e3
parent 65 09f1435bfb94
child 69 b27d6b8527e2
permissions -rw-r--r--
Consistent naming fixes in our usage tables enumerations.
     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         /// <summary>
    51         /// Class constructor will fetch this object properties from HID sub system.
    52         /// </summary>
    53         /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
    54         public HidDevice(IntPtr hRawInputDevice)
    55         {
    56             //Try construct and rollback if needed
    57             try
    58             {
    59                 Construct(hRawInputDevice);
    60             }
    61             catch (System.Exception ex)
    62             {
    63                 //Just rollback and propagate
    64                 Dispose();
    65                 throw ex;
    66             }
    67         }
    68 
    69 
    70         /// <summary>
    71         /// Make sure dispose is called even if the user forgot about it.
    72         /// </summary>
    73         ~HidDevice()
    74         {
    75             Dispose();
    76         }
    77 
    78         /// <summary>
    79         /// Private constructor.
    80         /// </summary>
    81         /// <param name="hRawInputDevice"></param>
    82         private void Construct(IntPtr hRawInputDevice)
    83         {
    84             PreParsedData = IntPtr.Zero;
    85             iInputButtonCapabilities = null;
    86             iInputValueCapabilities = null;
    87 
    88             //Fetch various information defining the given HID device
    89             Name = Win32.RawInput.GetDeviceName(hRawInputDevice);
    90 
    91             //Fetch device info
    92             iInfo = new RID_DEVICE_INFO();
    93             if (!Win32.RawInput.GetDeviceInfo(hRawInputDevice, ref iInfo))
    94             {
    95                 throw new Exception("HidDevice: GetDeviceInfo failed: " + Marshal.GetLastWin32Error().ToString());
    96             }
    97 
    98             //Open our device from the device name/path
    99             SafeFileHandle handle = Win32.Function.CreateFile(Name,
   100                 Win32.FileAccess.NONE,
   101                 Win32.FileShare.FILE_SHARE_READ | Win32.FileShare.FILE_SHARE_WRITE,
   102                 IntPtr.Zero,
   103                 Win32.CreationDisposition.OPEN_EXISTING,
   104                 Win32.FileFlagsAttributes.FILE_FLAG_OVERLAPPED,
   105                 IntPtr.Zero
   106                 );
   107 
   108             //Check if CreateFile worked
   109             if (handle.IsInvalid)
   110             {
   111                 throw new Exception("HidDevice: CreateFile failed: " + Marshal.GetLastWin32Error().ToString());
   112             }
   113 
   114             //Get manufacturer string
   115             StringBuilder manufacturerString = new StringBuilder(256);
   116             if (Win32.Function.HidD_GetManufacturerString(handle, manufacturerString, manufacturerString.Capacity))
   117             {
   118                 Manufacturer = manufacturerString.ToString();
   119             }
   120 
   121             //Get product string
   122             StringBuilder productString = new StringBuilder(256);
   123             if (Win32.Function.HidD_GetProductString(handle, productString, productString.Capacity))
   124             {
   125                 Product = productString.ToString();
   126             }
   127 
   128             //Get attributes
   129             Win32.HIDD_ATTRIBUTES attributes = new Win32.HIDD_ATTRIBUTES();
   130             if (Win32.Function.HidD_GetAttributes(handle, ref attributes))
   131             {
   132                 VendorId = attributes.VendorID;
   133                 ProductId = attributes.ProductID;
   134                 Version = attributes.VersionNumber;
   135             }
   136 
   137             handle.Close();
   138 
   139             SetFriendlyName();            
   140 
   141             //Get our HID descriptor pre-parsed data
   142             PreParsedData = Win32.RawInput.GetPreParsedData(hRawInputDevice);
   143 
   144             if (PreParsedData == IntPtr.Zero)
   145             {
   146                 //We are done then.
   147                 //Some devices don't have pre-parsed data.
   148                 return;
   149             }
   150 
   151             //Get capabilities
   152             HidStatus status = Win32.Function.HidP_GetCaps(PreParsedData, ref iCapabilities);
   153             if (status != HidStatus.HIDP_STATUS_SUCCESS)
   154             {
   155                 throw new Exception("HidDevice: HidP_GetCaps failed: " + status.ToString());
   156             }
   157 
   158             SetInputCapabilitiesDescription();
   159 
   160             //Get input button caps if needed
   161             if (Capabilities.NumberInputButtonCaps > 0)
   162             {
   163                 iInputButtonCapabilities = new HIDP_BUTTON_CAPS[Capabilities.NumberInputButtonCaps];
   164                 ushort buttonCapabilitiesLength = Capabilities.NumberInputButtonCaps;
   165                 status = Win32.Function.HidP_GetButtonCaps(HIDP_REPORT_TYPE.HidP_Input, iInputButtonCapabilities, ref buttonCapabilitiesLength, PreParsedData);
   166                 if (status != HidStatus.HIDP_STATUS_SUCCESS || buttonCapabilitiesLength != Capabilities.NumberInputButtonCaps)
   167                 {
   168                     throw new Exception("HidDevice: HidP_GetButtonCaps failed: " + status.ToString());
   169                 }
   170             }
   171 
   172             //Get input value caps if needed
   173             if (Capabilities.NumberInputValueCaps > 0)
   174             {
   175                 iInputValueCapabilities = new HIDP_VALUE_CAPS[Capabilities.NumberInputValueCaps];
   176                 ushort valueCapabilitiesLength = Capabilities.NumberInputValueCaps;
   177                 status = Win32.Function.HidP_GetValueCaps(HIDP_REPORT_TYPE.HidP_Input, iInputValueCapabilities, ref valueCapabilitiesLength, PreParsedData);
   178                 if (status != HidStatus.HIDP_STATUS_SUCCESS || valueCapabilitiesLength != Capabilities.NumberInputValueCaps)
   179                 {
   180                     throw new Exception("HidDevice: HidP_GetValueCaps failed: " + status.ToString());
   181                 }
   182             }
   183         }
   184 
   185         /// <summary>
   186         /// 
   187         /// </summary>
   188         void SetInputCapabilitiesDescription()
   189         {
   190             InputCapabilitiesDescription = "[ Input Capabilities ] Button: " + Capabilities.NumberInputButtonCaps + " - Value: " + Capabilities.NumberInputValueCaps + " - Data indices: " + Capabilities.NumberInputDataIndices;
   191         }
   192 
   193         /// <summary>
   194         /// 
   195         /// </summary>
   196         private void SetFriendlyName()
   197         {
   198             //Work out proper suffix for our device root node.
   199             //That allows users to see in a glance what kind of device this is.
   200             string suffix = "";
   201             Type usageCollectionType = null;
   202             if (Info.dwType == RawInputDeviceType.RIM_TYPEHID)
   203             {
   204                 //Process usage page
   205                 if (Enum.IsDefined(typeof(Hid.UsagePage), Info.hid.usUsagePage))
   206                 {
   207                     //We know this usage page, add its name
   208                     Hid.UsagePage usagePage = (Hid.UsagePage)Info.hid.usUsagePage;
   209                     suffix += " ( " + usagePage.ToString() + ", ";
   210                     usageCollectionType = Hid.Utils.UsageCollectionType(usagePage);
   211                 }
   212                 else
   213                 {
   214                     //We don't know this usage page, add its value
   215                     suffix += " ( 0x" + Info.hid.usUsagePage.ToString("X4") + ", ";
   216                 }
   217 
   218                 //Process usage collection
   219                 //We don't know this usage page, add its value
   220                 if (usageCollectionType == null || !Enum.IsDefined(usageCollectionType, Info.hid.usUsage))
   221                 {
   222                     //Show Hexa
   223                     suffix += "0x" + Info.hid.usUsage.ToString("X4") + " )";
   224                 }
   225                 else
   226                 {
   227                     //We know this usage page, add its name
   228                     suffix += Enum.GetName(usageCollectionType, Info.hid.usUsage) + " )";
   229                 }
   230             }
   231             else if (Info.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
   232             {
   233                 suffix = " - Keyboard";
   234             }
   235             else if (Info.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
   236             {
   237                 suffix = " - Mouse";
   238             }
   239 
   240             if (Product != null && Product.Length > 1)
   241             {
   242                 //Add the devices with a proper name at the top
   243                 FriendlyName = Product + suffix;
   244             }
   245             else
   246             {
   247                 //Add other once at the bottom
   248                 FriendlyName = "0x" + ProductId.ToString("X4") + suffix;
   249             }
   250 
   251         }
   252 
   253         /// <summary>
   254         /// Dispose is just for unmanaged clean-up.
   255         /// Make sure calling disposed multiple times does not crash.
   256         /// See: http://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface/538238#538238
   257         /// </summary>
   258         public void Dispose()
   259         {
   260             Marshal.FreeHGlobal(PreParsedData);
   261             PreParsedData = IntPtr.Zero;
   262         }
   263 
   264         /// <summary>
   265         /// 
   266         /// </summary>
   267         /// <param name="aCaps"></param>
   268         /// <returns></returns>
   269         public string InputValueCapabilityDescription(HIDP_VALUE_CAPS aCaps)
   270         {
   271             if (!aCaps.IsRange && Enum.IsDefined(typeof(UsagePage), Capabilities.UsagePage))
   272             {
   273                 return "Input Value: " + Enum.GetName(Utils.UsageType((UsagePage)Capabilities.UsagePage), aCaps.NotRange.Usage);
   274             }
   275 
   276             return null;
   277         }
   278 
   279         public bool IsGamePad
   280         {
   281             get
   282             {
   283                 return ((UsagePage)iCapabilities.UsagePage == UsagePage.GenericDesktopControls && (UsageCollection.GenericDesktop)iCapabilities.Usage == UsageCollection.GenericDesktop.GamePad);
   284             }
   285         }
   286 
   287 
   288         /// <summary>
   289         /// Print information about this device to our debug output.
   290         /// </summary>
   291         public void DebugWrite()
   292         {
   293             Debug.WriteLine("================ HID =========================================================================================");
   294             Debug.WriteLine("==== Name: " + Name);
   295             Debug.WriteLine("==== Manufacturer: " + Manufacturer);
   296             Debug.WriteLine("==== Product: " + Product);
   297             Debug.WriteLine("==== VendorID: 0x" + VendorId.ToString("X4"));
   298             Debug.WriteLine("==== ProductID: 0x" + ProductId.ToString("X4"));
   299             Debug.WriteLine("==== Version: " + Version.ToString());
   300             Debug.WriteLine("==============================================================================================================");
   301         }
   302 
   303     }
   304 
   305 }