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