HidEvent.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@27
     1
using System;
sl@27
     2
using System.Windows.Forms;
sl@27
     3
using System.Runtime.InteropServices;
sl@27
     4
using System.Diagnostics;
sl@27
     5
using System.Text;
sl@27
     6
using Microsoft.Win32.SafeHandles;
sl@27
     7
using Win32;
sl@27
     8
using System.Collections.Generic;
sl@41
     9
using System.Timers;
sl@27
    10
sl@27
    11
sl@27
    12
namespace Hid
sl@27
    13
{
sl@27
    14
    /// <summary>
StephaneLenclud@73
    15
    /// We provide utility functions to interpret gamepad dpad state.
StephaneLenclud@73
    16
    /// </summary>
StephaneLenclud@73
    17
    public enum DirectionPadState
StephaneLenclud@73
    18
    {
StephaneLenclud@73
    19
        Rest=-1,
StephaneLenclud@73
    20
        Up=0,
StephaneLenclud@73
    21
        UpRight=1,
StephaneLenclud@73
    22
        Right=2,
StephaneLenclud@73
    23
        DownRight=3,
StephaneLenclud@73
    24
        Down=4,
StephaneLenclud@73
    25
        DownLeft=5,
StephaneLenclud@73
    26
        Left=6,
StephaneLenclud@73
    27
        UpLeft=7
StephaneLenclud@73
    28
    }
StephaneLenclud@73
    29
StephaneLenclud@73
    30
    /// <summary>
sl@27
    31
    /// Represent a HID event.
StephaneLenclud@54
    32
    /// TODO: Rename this into HidRawInput?
sl@27
    33
    /// </summary>
StephaneLenclud@54
    34
    public class HidEvent : IDisposable
sl@27
    35
    {
sl@27
    36
        public bool IsValid { get; private set; }
StephaneLenclud@54
    37
        public bool IsForeground { get; private set; }
StephaneLenclud@54
    38
        public bool IsBackground { get { return !IsForeground; } }
sl@27
    39
        public bool IsMouse { get; private set; }
sl@27
    40
        public bool IsKeyboard { get; private set; }
StephaneLenclud@72
    41
        /// <summary>
StephaneLenclud@72
    42
        /// If this not a mouse or keyboard event then it's a generic HID event.
StephaneLenclud@72
    43
        /// </summary>
sl@27
    44
        public bool IsGeneric { get; private set; }
sl@29
    45
        public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
StephaneLenclud@49
    46
        public bool IsButtonUp { get { return Usages.Count == 0; } }
sl@44
    47
        public bool IsRepeat { get { return RepeatCount != 0; } }
sl@44
    48
        public uint RepeatCount { get; private set; }
sl@27
    49
sl@27
    50
        public HidDevice Device { get; private set; }
StephaneLenclud@72
    51
        public RAWINPUT RawInput { get {return iRawInput;} } 
StephaneLenclud@72
    52
        private RAWINPUT iRawInput;
sl@27
    53
sl@27
    54
        public ushort UsagePage { get; private set; }
sl@27
    55
        public ushort UsageCollection { get; private set; }
sl@31
    56
        public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
StephaneLenclud@49
    57
        public List<ushort> Usages { get; private set; }
StephaneLenclud@73
    58
        /// <summary>
StephaneLenclud@73
    59
        /// Sorted in the same order as Device.InputValueCapabilities.
StephaneLenclud@73
    60
        /// </summary>
StephaneLenclud@73
    61
        public Dictionary<HIDP_VALUE_CAPS,uint> UsageValues { get; private set; }
StephaneLenclud@54
    62
        //TODO: We need a collection of input report
StephaneLenclud@54
    63
        public byte[] InputReport { get; private set; }
StephaneLenclud@54
    64
        //
StephaneLenclud@54
    65
        public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
sl@41
    66
        public event HidEventRepeatDelegate OnHidEventRepeat;
sl@27
    67
sl@41
    68
        private System.Timers.Timer Timer { get; set; }
sl@44
    69
        public DateTime Time { get; private set; }
sl@44
    70
        public DateTime OriginalTime { get; private set; }
sl@41
    71
sl@43
    72
        //Compute repeat delay and speed based on system settings
sl@43
    73
        //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
sl@43
    74
        private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
sl@43
    75
        private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
sl@43
    76
sl@42
    77
        /// <summary>
sl@42
    78
        /// Tells whether this event has already been disposed of.
sl@42
    79
        /// </summary>
sl@42
    80
        public bool IsStray { get { return Timer == null; } }
sl@42
    81
sl@43
    82
        /// <summary>
sl@43
    83
        /// We typically dispose of events as soon as we get the corresponding key up signal.
sl@43
    84
        /// </summary>
sl@41
    85
        public void Dispose()
sl@41
    86
        {
sl@41
    87
            Timer.Enabled = false;
sl@41
    88
            Timer.Dispose();
sl@43
    89
            //Mark this event as a stray
sl@41
    90
            Timer = null;
sl@41
    91
        }
sl@27
    92
sl@27
    93
        /// <summary>
sl@27
    94
        /// Initialize an HidEvent from a WM_INPUT message
sl@27
    95
        /// </summary>
sl@27
    96
        /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
sl@41
    97
        public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
sl@27
    98
        {
sl@44
    99
            RepeatCount = 0;
sl@27
   100
            IsValid = false;
sl@27
   101
            IsKeyboard = false;
sl@27
   102
            IsGeneric = false;
StephaneLenclud@54
   103
sl@44
   104
            Time = DateTime.Now;
sl@44
   105
            OriginalTime = DateTime.Now;
sl@41
   106
            Timer = new System.Timers.Timer();
sl@44
   107
            Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
sl@27
   108
            Usages = new List<ushort>();
StephaneLenclud@73
   109
            UsageValues = new Dictionary<HIDP_VALUE_CAPS,uint>();
sl@41
   110
            OnHidEventRepeat += aRepeatDelegate;
sl@27
   111
sl@27
   112
            if (aMessage.Msg != Const.WM_INPUT)
sl@27
   113
            {
sl@27
   114
                //Has to be a WM_INPUT message
sl@27
   115
                return;
sl@27
   116
            }
sl@27
   117
sl@27
   118
            if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
sl@27
   119
            {
sl@27
   120
                IsForeground = true;
sl@27
   121
            }
sl@27
   122
            else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
sl@27
   123
            {
sl@27
   124
                IsForeground = false;
sl@27
   125
            }
sl@27
   126
sl@27
   127
            //Declare some pointers
sl@27
   128
            IntPtr rawInputBuffer = IntPtr.Zero;
sl@27
   129
sl@27
   130
            try
sl@27
   131
            {
sl@27
   132
                //Fetch raw input
StephaneLenclud@72
   133
                iRawInput = new RAWINPUT();
StephaneLenclud@72
   134
                if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref iRawInput, ref rawInputBuffer))
sl@27
   135
                {
StephaneLenclud@70
   136
                    Debug.WriteLine("GetRawInputData failed!");
sl@27
   137
                    return;
sl@27
   138
                }
sl@27
   139
StephaneLenclud@70
   140
                //Our device can actually be null.
StephaneLenclud@70
   141
                //This is notably happening for some keyboard events
StephaneLenclud@72
   142
                if (RawInput.header.hDevice != IntPtr.Zero)
StephaneLenclud@70
   143
                {
StephaneLenclud@70
   144
                    //Get various information about this HID device
StephaneLenclud@72
   145
                    Device = new Hid.HidDevice(RawInput.header.hDevice);
StephaneLenclud@70
   146
                }
sl@27
   147
StephaneLenclud@72
   148
                if (RawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID)  //Check that our raw input is HID                        
sl@27
   149
                {
sl@27
   150
                    IsGeneric = true;
sl@27
   151
sl@27
   152
                    Debug.WriteLine("WM_INPUT source device is HID.");
sl@27
   153
                    //Get Usage Page and Usage
sl@27
   154
                    //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
StephaneLenclud@53
   155
                    UsagePage = Device.Info.hid.usUsagePage;
StephaneLenclud@53
   156
                    UsageCollection = Device.Info.hid.usUsage;
sl@27
   157
StephaneLenclud@72
   158
                    if (!(RawInput.hid.dwSizeHid > 1     //Make sure our HID msg size more than 1. In fact the first ushort is irrelevant to us for now
StephaneLenclud@72
   159
                        && RawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
sl@27
   160
                    {
sl@27
   161
                        return;
sl@27
   162
                    }
sl@27
   163
sl@27
   164
                    //Allocate a buffer for one HID input
StephaneLenclud@72
   165
                    InputReport = new byte[RawInput.hid.dwSizeHid];
sl@27
   166
StephaneLenclud@72
   167
                    Debug.WriteLine("Raw input contains " + RawInput.hid.dwCount + " HID input report(s)");
sl@27
   168
sl@27
   169
                    //For each HID input report in our raw input
StephaneLenclud@72
   170
                    for (int i = 0; i < RawInput.hid.dwCount; i++)
sl@27
   171
                    {
sl@27
   172
                        //Compute the address from which to copy our HID input
sl@27
   173
                        int hidInputOffset = 0;
sl@27
   174
                        unsafe
sl@27
   175
                        {
sl@27
   176
                            byte* source = (byte*)rawInputBuffer;
StephaneLenclud@72
   177
                            source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (RawInput.hid.dwSizeHid * i);
sl@27
   178
                            hidInputOffset = (int)source;
sl@27
   179
                        }
sl@27
   180
sl@27
   181
                        //Copy HID input into our buffer
StephaneLenclud@72
   182
                        Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid);
StephaneLenclud@73
   183
                        //
StephaneLenclud@73
   184
                        ProcessInputReport(InputReport);
sl@27
   185
                    }
sl@27
   186
                }
StephaneLenclud@72
   187
                else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
sl@27
   188
                {
sl@27
   189
                    IsMouse = true;
sl@27
   190
StephaneLenclud@54
   191
                    Debug.WriteLine("WM_INPUT source device is Mouse.");
sl@27
   192
                    // do mouse handling...
sl@27
   193
                }
StephaneLenclud@72
   194
                else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
sl@27
   195
                {
sl@27
   196
                    IsKeyboard = true;
sl@27
   197
sl@27
   198
                    Debug.WriteLine("WM_INPUT source device is Keyboard.");
sl@27
   199
                    // do keyboard handling...
StephaneLenclud@70
   200
                    if (Device != null)
StephaneLenclud@70
   201
                    {
StephaneLenclud@70
   202
                        Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
StephaneLenclud@70
   203
                        Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
StephaneLenclud@70
   204
                        Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
StephaneLenclud@70
   205
                        Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
StephaneLenclud@70
   206
                        Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
StephaneLenclud@70
   207
                        Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
StephaneLenclud@70
   208
                    }
sl@27
   209
                }
sl@27
   210
            }
sl@27
   211
            finally
sl@27
   212
            {
sl@27
   213
                //Always executed when leaving our try block
sl@27
   214
                Marshal.FreeHGlobal(rawInputBuffer);
sl@27
   215
            }
sl@27
   216
sl@47
   217
            //
sl@47
   218
            if (IsButtonDown)
sl@41
   219
            {
StephaneLenclud@54
   220
                //TODO: Make this optional
StephaneLenclud@63
   221
                //StartRepeatTimer(iRepeatDelay);
sl@41
   222
            }
StephaneLenclud@54
   223
sl@27
   224
            IsValid = true;
sl@27
   225
        }
sl@27
   226
StephaneLenclud@73
   227
        /// <summary>
StephaneLenclud@73
   228
        /// 
StephaneLenclud@73
   229
        /// </summary>
StephaneLenclud@73
   230
        private void ProcessInputReport(byte[] aInputReport)
StephaneLenclud@73
   231
        {
StephaneLenclud@73
   232
            //Print HID input report in our debug output
StephaneLenclud@73
   233
            //string hidDump = "HID input report: " + InputReportString();
StephaneLenclud@73
   234
            //Debug.WriteLine(hidDump);
StephaneLenclud@73
   235
StephaneLenclud@73
   236
            //Get all our usages, those are typically the buttons currently pushed on a gamepad.
StephaneLenclud@73
   237
            //For a remote control it's usually just the one button that was pushed.
StephaneLenclud@73
   238
            GetUsages(aInputReport);
StephaneLenclud@73
   239
StephaneLenclud@73
   240
            //Now process direction pad (d-pad, dpad) and axes
StephaneLenclud@73
   241
            GetUsageValues(aInputReport);
StephaneLenclud@73
   242
        }
StephaneLenclud@73
   243
StephaneLenclud@73
   244
        /// <summary>
StephaneLenclud@73
   245
        /// Typically fetches values of a joystick/gamepad axis and dpad directions.
StephaneLenclud@73
   246
        /// </summary>
StephaneLenclud@73
   247
        /// <param name="aInputReport"></param>
StephaneLenclud@73
   248
        private void GetUsageValues(byte[] aInputReport)
StephaneLenclud@73
   249
        {
StephaneLenclud@73
   250
            if (Device.InputValueCapabilities == null)
StephaneLenclud@73
   251
            {
StephaneLenclud@73
   252
                return;
StephaneLenclud@73
   253
            }
StephaneLenclud@73
   254
StephaneLenclud@73
   255
            foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
StephaneLenclud@73
   256
            {                
StephaneLenclud@73
   257
                if (caps.IsRange)
StephaneLenclud@73
   258
                {
StephaneLenclud@73
   259
                    //What should we do with those guys?
StephaneLenclud@73
   260
                    continue;
StephaneLenclud@73
   261
                }
StephaneLenclud@73
   262
StephaneLenclud@73
   263
                //Now fetch and add our usage value
StephaneLenclud@73
   264
                uint usageValue = 0;
StephaneLenclud@73
   265
                Win32.HidStatus status = Win32.Function.HidP_GetUsageValue(Win32.HIDP_REPORT_TYPE.HidP_Input, caps.UsagePage, caps.LinkCollection, caps.NotRange.Usage, ref usageValue, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
StephaneLenclud@73
   266
                if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@73
   267
                {
StephaneLenclud@73
   268
                    UsageValues[caps]=usageValue;
StephaneLenclud@73
   269
                }
StephaneLenclud@73
   270
            }
StephaneLenclud@73
   271
        }
StephaneLenclud@73
   272
StephaneLenclud@73
   273
        /// <summary>
StephaneLenclud@73
   274
        /// Get all our usages, those are typically the buttons currently pushed on a gamepad.
StephaneLenclud@73
   275
        /// For a remote control it's usually just the one button that was pushed.
StephaneLenclud@73
   276
        /// </summary>
StephaneLenclud@73
   277
        private void GetUsages(byte[] aInputReport)
StephaneLenclud@73
   278
        {
StephaneLenclud@73
   279
            //Do proper parsing of our HID report
StephaneLenclud@73
   280
            //First query our usage count
StephaneLenclud@73
   281
            uint usageCount = 0;
StephaneLenclud@73
   282
            Win32.USAGE_AND_PAGE[] usages = null;
StephaneLenclud@73
   283
            Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
StephaneLenclud@73
   284
            if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
StephaneLenclud@73
   285
            {
StephaneLenclud@73
   286
                //Allocate a large enough buffer 
StephaneLenclud@73
   287
                usages = new Win32.USAGE_AND_PAGE[usageCount];
StephaneLenclud@73
   288
                //...and fetch our usages
StephaneLenclud@73
   289
                status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
StephaneLenclud@73
   290
                if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@73
   291
                {
StephaneLenclud@73
   292
                    Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
StephaneLenclud@73
   293
                }
StephaneLenclud@73
   294
            }
StephaneLenclud@73
   295
            else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@73
   296
            {
StephaneLenclud@73
   297
                Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
StephaneLenclud@73
   298
            }
StephaneLenclud@73
   299
StephaneLenclud@73
   300
            Debug.WriteLine("Usage count: " + usageCount.ToString());
StephaneLenclud@73
   301
StephaneLenclud@73
   302
            //Copy usages into this event
StephaneLenclud@73
   303
            if (usages != null)
StephaneLenclud@73
   304
            {
StephaneLenclud@73
   305
                foreach (USAGE_AND_PAGE up in usages)
StephaneLenclud@73
   306
                {
StephaneLenclud@73
   307
                    //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
StephaneLenclud@73
   308
                    //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
StephaneLenclud@73
   309
                    //Add this usage to our list
StephaneLenclud@73
   310
                    Usages.Add(up.Usage);
StephaneLenclud@73
   311
                }
StephaneLenclud@73
   312
            }
StephaneLenclud@73
   313
        }
StephaneLenclud@73
   314
StephaneLenclud@73
   315
        /// <summary>
StephaneLenclud@73
   316
        /// 
StephaneLenclud@73
   317
        /// </summary>
StephaneLenclud@73
   318
        /// <param name="aUsagePage"></param>
StephaneLenclud@73
   319
        /// <param name="Usage"></param>
StephaneLenclud@73
   320
        /// <returns></returns>
StephaneLenclud@73
   321
        public uint GetUsageValue(ushort aUsagePage, ushort aUsage)
StephaneLenclud@73
   322
        {
StephaneLenclud@73
   323
            foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
StephaneLenclud@73
   324
            {                
StephaneLenclud@73
   325
                if (caps.IsRange)
StephaneLenclud@73
   326
                {
StephaneLenclud@73
   327
                    //What should we do with those guys?
StephaneLenclud@73
   328
                    continue;
StephaneLenclud@73
   329
                }
StephaneLenclud@73
   330
StephaneLenclud@73
   331
                //Check if we have a match
StephaneLenclud@73
   332
                if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
StephaneLenclud@73
   333
                {
StephaneLenclud@73
   334
                    return UsageValues[caps];
StephaneLenclud@73
   335
                }
StephaneLenclud@73
   336
            }
StephaneLenclud@73
   337
StephaneLenclud@73
   338
            return 0;
StephaneLenclud@73
   339
        }
StephaneLenclud@73
   340
StephaneLenclud@73
   341
        /// <summary>
StephaneLenclud@73
   342
        /// 
StephaneLenclud@73
   343
        /// </summary>
StephaneLenclud@73
   344
        /// <param name="aUsagePage"></param>
StephaneLenclud@73
   345
        /// <param name="aUsage"></param>
StephaneLenclud@73
   346
        /// <returns></returns>
StephaneLenclud@73
   347
        public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage)
StephaneLenclud@73
   348
        {
StephaneLenclud@73
   349
            int i = -1;
StephaneLenclud@73
   350
            foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
StephaneLenclud@73
   351
            {
StephaneLenclud@73
   352
                i++;
StephaneLenclud@73
   353
                if (caps.IsRange)
StephaneLenclud@73
   354
                {
StephaneLenclud@73
   355
                    //What should we do with those guys?
StephaneLenclud@73
   356
                    continue;
StephaneLenclud@73
   357
                }
StephaneLenclud@73
   358
StephaneLenclud@73
   359
                //Check if we have a match
StephaneLenclud@73
   360
                if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
StephaneLenclud@73
   361
                {
StephaneLenclud@73
   362
                    return i;
StephaneLenclud@73
   363
                }
StephaneLenclud@73
   364
            }
StephaneLenclud@73
   365
StephaneLenclud@73
   366
            return i;
StephaneLenclud@73
   367
        }        
StephaneLenclud@73
   368
StephaneLenclud@73
   369
StephaneLenclud@73
   370
        /// <summary>
StephaneLenclud@73
   371
        /// TODO: Move this to another level?
StephaneLenclud@73
   372
        /// </summary>
StephaneLenclud@73
   373
        /// <param name="aInterval"></param>
sl@41
   374
        public void StartRepeatTimer(double aInterval)
sl@41
   375
        {
sl@41
   376
            if (Timer == null)
sl@41
   377
            {
sl@41
   378
                return;
sl@41
   379
            }
sl@41
   380
            Timer.Enabled = false;
sl@44
   381
            //Initial delay do not use auto reset
sl@44
   382
            //After our initial delay however we do setup our timer one more time using auto reset
StephaneLenclud@54
   383
            Timer.AutoReset = (RepeatCount != 0);
StephaneLenclud@54
   384
            Timer.Interval = aInterval;
StephaneLenclud@54
   385
            Timer.Enabled = true;
sl@41
   386
        }
sl@27
   387
sl@44
   388
        static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
sl@41
   389
        {
sl@42
   390
            if (aHidEvent.IsStray)
sl@42
   391
            {
sl@42
   392
                //Skip events if canceled
sl@42
   393
                return;
sl@42
   394
            }
sl@44
   395
sl@44
   396
            aHidEvent.RepeatCount++;
sl@44
   397
            aHidEvent.Time = DateTime.Now;
StephaneLenclud@54
   398
            if (aHidEvent.RepeatCount == 1)
sl@44
   399
            {
sl@44
   400
                //Re-Start our timer only after the initial delay 
sl@44
   401
                aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
sl@44
   402
            }
sl@44
   403
sl@44
   404
            //Broadcast our repeat event
sl@44
   405
            aHidEvent.OnHidEventRepeat(aHidEvent);
sl@41
   406
        }
sl@27
   407
StephaneLenclud@54
   408
        /// <summary>
StephaneLenclud@73
   409
        /// Provide the state of the dpad or hat switch if any.
StephaneLenclud@73
   410
        /// If no dpad is found we return 'at rest'.
StephaneLenclud@73
   411
        /// </summary>
StephaneLenclud@73
   412
        /// <returns></returns>
StephaneLenclud@73
   413
        public DirectionPadState GetDirectionPadState()
StephaneLenclud@73
   414
        {
StephaneLenclud@73
   415
            int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
StephaneLenclud@73
   416
            if (index < 0)
StephaneLenclud@73
   417
            {
StephaneLenclud@73
   418
                //No hat switch found
StephaneLenclud@73
   419
                return DirectionPadState.Rest;
StephaneLenclud@73
   420
            }
StephaneLenclud@73
   421
StephaneLenclud@73
   422
            HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index];
StephaneLenclud@73
   423
            if (caps.IsRange)
StephaneLenclud@73
   424
            {
StephaneLenclud@73
   425
                //Defensive
StephaneLenclud@73
   426
                return DirectionPadState.Rest;
StephaneLenclud@73
   427
            }
StephaneLenclud@73
   428
StephaneLenclud@73
   429
            uint dpadUsageValue = UsageValues[caps]; 
StephaneLenclud@73
   430
StephaneLenclud@73
   431
            if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax)
StephaneLenclud@73
   432
            {
StephaneLenclud@73
   433
                //Out of range means at rest
StephaneLenclud@73
   434
                return DirectionPadState.Rest;
StephaneLenclud@73
   435
            }
StephaneLenclud@73
   436
StephaneLenclud@73
   437
            //Normalize value to start at zero
StephaneLenclud@73
   438
            //TODO: more error check here?
StephaneLenclud@73
   439
            DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin);            
StephaneLenclud@73
   440
            return res;
StephaneLenclud@73
   441
        }
StephaneLenclud@73
   442
StephaneLenclud@73
   443
        /// <summary>
StephaneLenclud@54
   444
        /// Print information about this device to our debug output.
StephaneLenclud@54
   445
        /// </summary>
StephaneLenclud@54
   446
        public void DebugWrite()
StephaneLenclud@54
   447
        {
StephaneLenclud@54
   448
            if (!IsValid)
StephaneLenclud@54
   449
            {
StephaneLenclud@54
   450
                Debug.WriteLine("==== Invalid HidEvent");
StephaneLenclud@54
   451
                return;
StephaneLenclud@54
   452
            }
StephaneLenclud@70
   453
StephaneLenclud@70
   454
            if (Device!=null)
StephaneLenclud@70
   455
            {
StephaneLenclud@70
   456
                Device.DebugWrite();
StephaneLenclud@70
   457
            }
StephaneLenclud@70
   458
            
StephaneLenclud@54
   459
            if (IsGeneric) Debug.WriteLine("==== Generic");
StephaneLenclud@54
   460
            if (IsKeyboard) Debug.WriteLine("==== Keyboard");
StephaneLenclud@54
   461
            if (IsMouse) Debug.WriteLine("==== Mouse");
StephaneLenclud@54
   462
            Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
StephaneLenclud@54
   463
            Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
StephaneLenclud@54
   464
            Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
StephaneLenclud@54
   465
            Debug.WriteLine("==== InputReport: 0x" + InputReportString());
StephaneLenclud@54
   466
            foreach (ushort usage in Usages)
StephaneLenclud@54
   467
            {
StephaneLenclud@54
   468
                Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
StephaneLenclud@54
   469
            }
StephaneLenclud@54
   470
        }
StephaneLenclud@49
   471
StephaneLenclud@54
   472
        /// <summary>
StephaneLenclud@54
   473
        /// 
StephaneLenclud@54
   474
        /// </summary>
StephaneLenclud@54
   475
        /// <returns></returns>
StephaneLenclud@54
   476
        public string InputReportString()
StephaneLenclud@54
   477
        {
StephaneLenclud@70
   478
            if (InputReport == null)
StephaneLenclud@70
   479
            {
StephaneLenclud@70
   480
                return "null";
StephaneLenclud@70
   481
            }
StephaneLenclud@70
   482
StephaneLenclud@54
   483
            string hidDump = "";
StephaneLenclud@54
   484
            foreach (byte b in InputReport)
StephaneLenclud@54
   485
            {
StephaneLenclud@54
   486
                hidDump += b.ToString("X2");
StephaneLenclud@54
   487
            }
StephaneLenclud@54
   488
            return hidDump;
StephaneLenclud@54
   489
        }
StephaneLenclud@49
   490
StephaneLenclud@49
   491
StephaneLenclud@54
   492
        /// <summary>
StephaneLenclud@54
   493
        /// Create a list view item describing this HidEvent
StephaneLenclud@54
   494
        /// </summary>
StephaneLenclud@54
   495
        /// <returns></returns>
sl@42
   496
        public ListViewItem ToListViewItem()
sl@42
   497
        {
StephaneLenclud@49
   498
            string usageText = "";
sl@42
   499
StephaneLenclud@54
   500
            foreach (ushort usage in Usages)
StephaneLenclud@54
   501
            {
StephaneLenclud@54
   502
                if (usageText != "")
StephaneLenclud@54
   503
                {
StephaneLenclud@54
   504
                    //Add a separator
StephaneLenclud@54
   505
                    usageText += ", ";
StephaneLenclud@54
   506
                }
sl@42
   507
StephaneLenclud@54
   508
                UsagePage usagePage = (UsagePage)UsagePage;
StephaneLenclud@54
   509
                switch (usagePage)
StephaneLenclud@54
   510
                {
StephaneLenclud@54
   511
                    case Hid.UsagePage.Consumer:
StephaneLenclud@66
   512
                        usageText += ((Hid.Usage.ConsumerControl)usage).ToString();
StephaneLenclud@54
   513
                        break;
sl@42
   514
StephaneLenclud@54
   515
                    case Hid.UsagePage.WindowsMediaCenterRemoteControl:
StephaneLenclud@66
   516
                        usageText += ((Hid.Usage.WindowsMediaCenterRemoteControl)usage).ToString();
StephaneLenclud@54
   517
                        break;
StephaneLenclud@49
   518
StephaneLenclud@54
   519
                    default:
StephaneLenclud@54
   520
                        usageText += usage.ToString("X2");
StephaneLenclud@54
   521
                        break;
StephaneLenclud@54
   522
                }
StephaneLenclud@54
   523
            }
StephaneLenclud@49
   524
StephaneLenclud@73
   525
            //If we are a gamepad display axis and dpad values
StephaneLenclud@73
   526
            if (Device.IsGamePad)
StephaneLenclud@73
   527
            {
StephaneLenclud@73
   528
                //uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
StephaneLenclud@73
   529
                //usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText;
StephaneLenclud@73
   530
              
StephaneLenclud@73
   531
                if (usageText != "")
StephaneLenclud@73
   532
                {
StephaneLenclud@73
   533
                    //Add a separator
StephaneLenclud@73
   534
                    usageText += " (Buttons)";
StephaneLenclud@73
   535
                }
StephaneLenclud@73
   536
StephaneLenclud@73
   537
                if (usageText != "")
StephaneLenclud@73
   538
                {
StephaneLenclud@73
   539
                    //Add a separator
StephaneLenclud@73
   540
                    usageText += ", ";
StephaneLenclud@73
   541
                }
StephaneLenclud@73
   542
StephaneLenclud@73
   543
                usageText += GetDirectionPadState().ToString();
StephaneLenclud@73
   544
StephaneLenclud@73
   545
                foreach (KeyValuePair<HIDP_VALUE_CAPS, uint> entry in UsageValues)
StephaneLenclud@73
   546
                {
StephaneLenclud@73
   547
                    if (entry.Key.IsRange)
StephaneLenclud@73
   548
                    {
StephaneLenclud@73
   549
                        continue;
StephaneLenclud@73
   550
                    }
StephaneLenclud@73
   551
StephaneLenclud@73
   552
                    Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage);
StephaneLenclud@73
   553
                    if (usageType == null)
StephaneLenclud@73
   554
                    {
StephaneLenclud@73
   555
                        //TODO: check why this is happening on Logitech rumble gamepad 2.
StephaneLenclud@73
   556
                        //Probably some of our axis are hiding in there.
StephaneLenclud@73
   557
                        continue;
StephaneLenclud@73
   558
                    }
StephaneLenclud@73
   559
                    string name = Enum.GetName(usageType, entry.Key.NotRange.Usage);
StephaneLenclud@73
   560
StephaneLenclud@73
   561
                    if (usageText != "")
StephaneLenclud@73
   562
                    {
StephaneLenclud@73
   563
                        //Add a separator
StephaneLenclud@73
   564
                        usageText += ", ";
StephaneLenclud@73
   565
                    }
StephaneLenclud@73
   566
                    usageText += entry.Value.ToString("X") + " ("+ name +")";        
StephaneLenclud@73
   567
                }
StephaneLenclud@73
   568
            }
StephaneLenclud@73
   569
StephaneLenclud@73
   570
            //Now create our list item
StephaneLenclud@54
   571
            ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
sl@42
   572
            return item;
sl@42
   573
        }
sl@42
   574
sl@27
   575
    }
sl@27
   576
sl@27
   577
}