HidDevice.cs
author StephaneLenclud
Sun, 15 Feb 2015 22:13:33 +0100
changeset 64 8c2380995bb7
parent 63 4b8b058de215
child 65 09f1435bfb94
permissions -rw-r--r--
Adding input capabilities description to device list.
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@58
    50
        
StephaneLenclud@56
    51
        
sl@25
    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@56
   173
            }
StephaneLenclud@58
   174
StephaneLenclud@58
   175
            //Get input value caps if needed
StephaneLenclud@58
   176
            if (Capabilities.NumberInputValueCaps > 0)
StephaneLenclud@58
   177
            {
StephaneLenclud@58
   178
                iInputValueCapabilities = new HIDP_VALUE_CAPS[Capabilities.NumberInputValueCaps];
StephaneLenclud@58
   179
                ushort valueCapabilitiesLength = Capabilities.NumberInputValueCaps;
StephaneLenclud@58
   180
                status = Win32.Function.HidP_GetValueCaps(HIDP_REPORT_TYPE.HidP_Input, iInputValueCapabilities, ref valueCapabilitiesLength, PreParsedData);
StephaneLenclud@58
   181
                if (status != HidStatus.HIDP_STATUS_SUCCESS || valueCapabilitiesLength != Capabilities.NumberInputValueCaps)
StephaneLenclud@58
   182
                {
StephaneLenclud@58
   183
                    throw new Exception("HidDevice: HidP_GetValueCaps failed: " + status.ToString());
StephaneLenclud@58
   184
                }
StephaneLenclud@58
   185
            }
StephaneLenclud@64
   186
        }
StephaneLenclud@58
   187
StephaneLenclud@64
   188
        /// <summary>
StephaneLenclud@64
   189
        /// 
StephaneLenclud@64
   190
        /// </summary>
StephaneLenclud@64
   191
        void SetInputCapabilitiesDescription()
StephaneLenclud@64
   192
        {
StephaneLenclud@64
   193
            InputCapabilitiesDescription = "[ Input Capabilities ] Button: " + Capabilities.NumberInputButtonCaps + " - Value: " + Capabilities.NumberInputValueCaps + " - Data indices: " + Capabilities.NumberInputDataIndices;
StephaneLenclud@63
   194
        }
StephaneLenclud@63
   195
StephaneLenclud@63
   196
        /// <summary>
StephaneLenclud@63
   197
        /// 
StephaneLenclud@63
   198
        /// </summary>
StephaneLenclud@63
   199
        private void SetFriendlyName()
StephaneLenclud@63
   200
        {
StephaneLenclud@63
   201
            //Work out proper suffix for our device root node.
StephaneLenclud@63
   202
            //That allows users to see in a glance what kind of device this is.
StephaneLenclud@63
   203
            string suffix = "";
StephaneLenclud@63
   204
            Type usageCollectionType = null;
StephaneLenclud@63
   205
            if (Info.dwType == RawInputDeviceType.RIM_TYPEHID)
StephaneLenclud@63
   206
            {
StephaneLenclud@63
   207
                //Process usage page
StephaneLenclud@63
   208
                if (Enum.IsDefined(typeof(Hid.UsagePage), Info.hid.usUsagePage))
StephaneLenclud@63
   209
                {
StephaneLenclud@63
   210
                    //We know this usage page, add its name
StephaneLenclud@63
   211
                    Hid.UsagePage usagePage = (Hid.UsagePage)Info.hid.usUsagePage;
StephaneLenclud@63
   212
                    suffix += " ( " + usagePage.ToString() + ", ";
StephaneLenclud@63
   213
                    usageCollectionType = Hid.Utils.UsageCollectionType(usagePage);
StephaneLenclud@63
   214
                }
StephaneLenclud@63
   215
                else
StephaneLenclud@63
   216
                {
StephaneLenclud@63
   217
                    //We don't know this usage page, add its value
StephaneLenclud@63
   218
                    suffix += " ( 0x" + Info.hid.usUsagePage.ToString("X4") + ", ";
StephaneLenclud@63
   219
                }
StephaneLenclud@63
   220
StephaneLenclud@63
   221
                //Process usage collection
StephaneLenclud@63
   222
                //We don't know this usage page, add its value
StephaneLenclud@63
   223
                if (usageCollectionType == null || !Enum.IsDefined(usageCollectionType, Info.hid.usUsage))
StephaneLenclud@63
   224
                {
StephaneLenclud@63
   225
                    //Show Hexa
StephaneLenclud@63
   226
                    suffix += "0x" + Info.hid.usUsage.ToString("X4") + " )";
StephaneLenclud@63
   227
                }
StephaneLenclud@63
   228
                else
StephaneLenclud@63
   229
                {
StephaneLenclud@63
   230
                    //We know this usage page, add its name
StephaneLenclud@63
   231
                    suffix += Enum.GetName(usageCollectionType, Info.hid.usUsage) + " )";
StephaneLenclud@63
   232
                }
StephaneLenclud@63
   233
            }
StephaneLenclud@63
   234
            else if (Info.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
StephaneLenclud@63
   235
            {
StephaneLenclud@63
   236
                suffix = " - Keyboard";
StephaneLenclud@63
   237
            }
StephaneLenclud@63
   238
            else if (Info.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
StephaneLenclud@63
   239
            {
StephaneLenclud@63
   240
                suffix = " - Mouse";
StephaneLenclud@63
   241
            }
StephaneLenclud@63
   242
StephaneLenclud@63
   243
            if (Product != null && Product.Length > 1)
StephaneLenclud@63
   244
            {
StephaneLenclud@63
   245
                //Add the devices with a proper name at the top
StephaneLenclud@63
   246
                FriendlyName = Product + suffix;
StephaneLenclud@63
   247
            }
StephaneLenclud@63
   248
            else
StephaneLenclud@63
   249
            {
StephaneLenclud@63
   250
                //Add other once at the bottom
StephaneLenclud@63
   251
                FriendlyName = "0x" + ProductId.ToString("X4") + suffix;
StephaneLenclud@63
   252
            }
StephaneLenclud@63
   253
StephaneLenclud@52
   254
        }
StephaneLenclud@52
   255
StephaneLenclud@52
   256
        /// <summary>
StephaneLenclud@52
   257
        /// Dispose is just for unmanaged clean-up.
StephaneLenclud@52
   258
        /// Make sure calling disposed multiple times does not crash.
StephaneLenclud@52
   259
        /// See: http://stackoverflow.com/questions/538060/proper-use-of-the-idisposable-interface/538238#538238
StephaneLenclud@52
   260
        /// </summary>
StephaneLenclud@52
   261
        public void Dispose()
StephaneLenclud@52
   262
        {
StephaneLenclud@52
   263
            Marshal.FreeHGlobal(PreParsedData);
StephaneLenclud@52
   264
            PreParsedData = IntPtr.Zero;
StephaneLenclud@52
   265
        }
StephaneLenclud@52
   266
sl@25
   267
        /// <summary>
sl@25
   268
        /// Print information about this device to our debug output.
sl@25
   269
        /// </summary>
sl@25
   270
        public void DebugWrite()
sl@25
   271
        {
sl@25
   272
            Debug.WriteLine("================ HID =========================================================================================");
sl@25
   273
            Debug.WriteLine("==== Name: " + Name);
sl@25
   274
            Debug.WriteLine("==== Manufacturer: " + Manufacturer);
sl@25
   275
            Debug.WriteLine("==== Product: " + Product);
sl@25
   276
            Debug.WriteLine("==== VendorID: 0x" + VendorId.ToString("X4"));
sl@25
   277
            Debug.WriteLine("==== ProductID: 0x" + ProductId.ToString("X4"));
sl@25
   278
            Debug.WriteLine("==== Version: " + Version.ToString());
sl@25
   279
            Debug.WriteLine("==============================================================================================================");
sl@25
   280
        }
sl@25
   281
sl@25
   282
    }
sl@25
   283
sl@25
   284
}