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