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