HidEvent.cs
author StephaneLenclud
Wed, 04 Mar 2015 20:52:24 +0100
changeset 70 e0a7b35f90dd
parent 66 3461ee6800e3
child 72 471b1d45c46a
permissions -rw-r--r--
Consolidating for keyboard and mouse device support.
Adding tests tab.
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>
sl@27
    15
    /// Represent a HID event.
StephaneLenclud@54
    16
    /// TODO: Rename this into HidRawInput?
sl@27
    17
    /// </summary>
StephaneLenclud@54
    18
    public class HidEvent : IDisposable
sl@27
    19
    {
sl@27
    20
        public bool IsValid { get; private set; }
StephaneLenclud@54
    21
        public bool IsForeground { get; private set; }
StephaneLenclud@54
    22
        public bool IsBackground { get { return !IsForeground; } }
sl@27
    23
        public bool IsMouse { get; private set; }
sl@27
    24
        public bool IsKeyboard { get; private set; }
sl@27
    25
        public bool IsGeneric { get; private set; }
sl@29
    26
        public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
StephaneLenclud@49
    27
        public bool IsButtonUp { get { return Usages.Count == 0; } }
sl@44
    28
        public bool IsRepeat { get { return RepeatCount != 0; } }
sl@44
    29
        public uint RepeatCount { get; private set; }
sl@27
    30
sl@27
    31
        public HidDevice Device { get; private set; }
sl@27
    32
sl@27
    33
        public ushort UsagePage { get; private set; }
sl@27
    34
        public ushort UsageCollection { get; private set; }
sl@31
    35
        public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
StephaneLenclud@49
    36
        public List<ushort> Usages { get; private set; }
StephaneLenclud@54
    37
        //TODO: We need a collection of input report
StephaneLenclud@54
    38
        public byte[] InputReport { get; private set; }
StephaneLenclud@54
    39
        //
StephaneLenclud@54
    40
        public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
sl@41
    41
        public event HidEventRepeatDelegate OnHidEventRepeat;
sl@27
    42
sl@41
    43
        private System.Timers.Timer Timer { get; set; }
sl@44
    44
        public DateTime Time { get; private set; }
sl@44
    45
        public DateTime OriginalTime { get; private set; }
sl@41
    46
sl@43
    47
        //Compute repeat delay and speed based on system settings
sl@43
    48
        //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
sl@43
    49
        private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
sl@43
    50
        private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
sl@43
    51
sl@42
    52
        /// <summary>
sl@42
    53
        /// Tells whether this event has already been disposed of.
sl@42
    54
        /// </summary>
sl@42
    55
        public bool IsStray { get { return Timer == null; } }
sl@42
    56
sl@43
    57
        /// <summary>
sl@43
    58
        /// We typically dispose of events as soon as we get the corresponding key up signal.
sl@43
    59
        /// </summary>
sl@41
    60
        public void Dispose()
sl@41
    61
        {
sl@41
    62
            Timer.Enabled = false;
sl@41
    63
            Timer.Dispose();
sl@43
    64
            //Mark this event as a stray
sl@41
    65
            Timer = null;
sl@41
    66
        }
sl@27
    67
sl@27
    68
        /// <summary>
sl@27
    69
        /// Initialize an HidEvent from a WM_INPUT message
sl@27
    70
        /// </summary>
sl@27
    71
        /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
sl@41
    72
        public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
sl@27
    73
        {
sl@44
    74
            RepeatCount = 0;
sl@27
    75
            IsValid = false;
sl@27
    76
            IsKeyboard = false;
sl@27
    77
            IsGeneric = false;
StephaneLenclud@54
    78
sl@27
    79
sl@44
    80
            Time = DateTime.Now;
sl@44
    81
            OriginalTime = DateTime.Now;
sl@41
    82
            Timer = new System.Timers.Timer();
sl@44
    83
            Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
sl@27
    84
            Usages = new List<ushort>();
sl@41
    85
            OnHidEventRepeat += aRepeatDelegate;
sl@27
    86
sl@27
    87
            if (aMessage.Msg != Const.WM_INPUT)
sl@27
    88
            {
sl@27
    89
                //Has to be a WM_INPUT message
sl@27
    90
                return;
sl@27
    91
            }
sl@27
    92
sl@27
    93
            if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
sl@27
    94
            {
sl@27
    95
                IsForeground = true;
sl@27
    96
            }
sl@27
    97
            else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
sl@27
    98
            {
sl@27
    99
                IsForeground = false;
sl@27
   100
            }
sl@27
   101
sl@27
   102
            //Declare some pointers
sl@27
   103
            IntPtr rawInputBuffer = IntPtr.Zero;
sl@27
   104
sl@27
   105
            try
sl@27
   106
            {
sl@27
   107
                //Fetch raw input
sl@27
   108
                RAWINPUT rawInput = new RAWINPUT();
StephaneLenclud@60
   109
                if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
sl@27
   110
                {
StephaneLenclud@70
   111
                    Debug.WriteLine("GetRawInputData failed!");
sl@27
   112
                    return;
sl@27
   113
                }
sl@27
   114
StephaneLenclud@70
   115
                //Our device can actually be null.
StephaneLenclud@70
   116
                //This is notably happening for some keyboard events
StephaneLenclud@70
   117
                if (rawInput.header.hDevice != IntPtr.Zero)
StephaneLenclud@70
   118
                {
StephaneLenclud@70
   119
                    //Get various information about this HID device
StephaneLenclud@70
   120
                    Device = new Hid.HidDevice(rawInput.header.hDevice);
StephaneLenclud@70
   121
                }
sl@27
   122
StephaneLenclud@60
   123
                if (rawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID)  //Check that our raw input is HID                        
sl@27
   124
                {
sl@27
   125
                    IsGeneric = true;
sl@27
   126
sl@27
   127
                    Debug.WriteLine("WM_INPUT source device is HID.");
sl@27
   128
                    //Get Usage Page and Usage
sl@27
   129
                    //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
StephaneLenclud@53
   130
                    UsagePage = Device.Info.hid.usUsagePage;
StephaneLenclud@53
   131
                    UsageCollection = Device.Info.hid.usUsage;
sl@27
   132
sl@27
   133
                    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
sl@27
   134
                        && rawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
sl@27
   135
                    {
sl@27
   136
                        return;
sl@27
   137
                    }
sl@27
   138
sl@27
   139
                    //Allocate a buffer for one HID input
StephaneLenclud@54
   140
                    InputReport = new byte[rawInput.hid.dwSizeHid];
sl@27
   141
sl@27
   142
                    Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
sl@27
   143
sl@27
   144
                    //For each HID input report in our raw input
sl@27
   145
                    for (int i = 0; i < rawInput.hid.dwCount; i++)
sl@27
   146
                    {
sl@27
   147
                        //Compute the address from which to copy our HID input
sl@27
   148
                        int hidInputOffset = 0;
sl@27
   149
                        unsafe
sl@27
   150
                        {
sl@27
   151
                            byte* source = (byte*)rawInputBuffer;
sl@27
   152
                            source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
sl@27
   153
                            hidInputOffset = (int)source;
sl@27
   154
                        }
sl@27
   155
sl@27
   156
                        //Copy HID input into our buffer
StephaneLenclud@54
   157
                        Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)rawInput.hid.dwSizeHid);
sl@27
   158
sl@27
   159
                        //Print HID input report in our debug output
StephaneLenclud@49
   160
                        //string hidDump = "HID input report: " + InputReportString();
StephaneLenclud@49
   161
                        //Debug.WriteLine(hidDump);
sl@27
   162
StephaneLenclud@49
   163
                        //Do proper parsing of our HID report
StephaneLenclud@54
   164
                        //First query our usage count
StephaneLenclud@54
   165
                        uint usageCount = 0;
StephaneLenclud@49
   166
                        Win32.USAGE_AND_PAGE[] usages = null;
StephaneLenclud@54
   167
                        Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
StephaneLenclud@54
   168
                        if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
StephaneLenclud@54
   169
                        {
StephaneLenclud@54
   170
                            //Allocate a large enough buffer 
StephaneLenclud@54
   171
                            usages = new Win32.USAGE_AND_PAGE[usageCount];
StephaneLenclud@54
   172
                            //...and fetch our usages
StephaneLenclud@52
   173
                            status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, InputReport, (uint)InputReport.Length);
StephaneLenclud@54
   174
                            if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@54
   175
                            {
StephaneLenclud@54
   176
                                Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
StephaneLenclud@54
   177
                            }
StephaneLenclud@54
   178
                        }
StephaneLenclud@54
   179
                        else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
StephaneLenclud@54
   180
                        {
StephaneLenclud@54
   181
                            Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
StephaneLenclud@54
   182
                        }
StephaneLenclud@49
   183
StephaneLenclud@54
   184
                        Debug.WriteLine("Usage count: " + usageCount.ToString());
StephaneLenclud@49
   185
StephaneLenclud@52
   186
                        //Copy usages into this event
StephaneLenclud@54
   187
                        if (usages != null)
StephaneLenclud@54
   188
                        {
StephaneLenclud@54
   189
                            foreach (USAGE_AND_PAGE up in usages)
StephaneLenclud@54
   190
                            {
StephaneLenclud@54
   191
                                //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
StephaneLenclud@54
   192
                                //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
StephaneLenclud@54
   193
                                //Add this usage to our list
StephaneLenclud@54
   194
                                Usages.Add(up.Usage);
StephaneLenclud@54
   195
                            }
StephaneLenclud@54
   196
                        }
sl@27
   197
                    }
sl@27
   198
                }
StephaneLenclud@60
   199
                else if (rawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
sl@27
   200
                {
sl@27
   201
                    IsMouse = true;
sl@27
   202
StephaneLenclud@54
   203
                    Debug.WriteLine("WM_INPUT source device is Mouse.");
sl@27
   204
                    // do mouse handling...
sl@27
   205
                }
StephaneLenclud@60
   206
                else if (rawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
sl@27
   207
                {
sl@27
   208
                    IsKeyboard = true;
sl@27
   209
sl@27
   210
                    Debug.WriteLine("WM_INPUT source device is Keyboard.");
sl@27
   211
                    // do keyboard handling...
StephaneLenclud@70
   212
                    if (Device != null)
StephaneLenclud@70
   213
                    {
StephaneLenclud@70
   214
                        Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
StephaneLenclud@70
   215
                        Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
StephaneLenclud@70
   216
                        Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
StephaneLenclud@70
   217
                        Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
StephaneLenclud@70
   218
                        Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
StephaneLenclud@70
   219
                        Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
StephaneLenclud@70
   220
                    }
sl@27
   221
                }
sl@27
   222
            }
sl@27
   223
            finally
sl@27
   224
            {
sl@27
   225
                //Always executed when leaving our try block
sl@27
   226
                Marshal.FreeHGlobal(rawInputBuffer);
sl@27
   227
            }
sl@27
   228
sl@47
   229
            //
sl@47
   230
            if (IsButtonDown)
sl@41
   231
            {
StephaneLenclud@54
   232
                //TODO: Make this optional
StephaneLenclud@63
   233
                //StartRepeatTimer(iRepeatDelay);
sl@41
   234
            }
StephaneLenclud@54
   235
sl@27
   236
            IsValid = true;
sl@27
   237
        }
sl@27
   238
sl@41
   239
        public void StartRepeatTimer(double aInterval)
sl@41
   240
        {
sl@41
   241
            if (Timer == null)
sl@41
   242
            {
sl@41
   243
                return;
sl@41
   244
            }
sl@41
   245
            Timer.Enabled = false;
sl@44
   246
            //Initial delay do not use auto reset
sl@44
   247
            //After our initial delay however we do setup our timer one more time using auto reset
StephaneLenclud@54
   248
            Timer.AutoReset = (RepeatCount != 0);
StephaneLenclud@54
   249
            Timer.Interval = aInterval;
StephaneLenclud@54
   250
            Timer.Enabled = true;
sl@41
   251
        }
sl@27
   252
sl@44
   253
        static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
sl@41
   254
        {
sl@42
   255
            if (aHidEvent.IsStray)
sl@42
   256
            {
sl@42
   257
                //Skip events if canceled
sl@42
   258
                return;
sl@42
   259
            }
sl@44
   260
sl@44
   261
            aHidEvent.RepeatCount++;
sl@44
   262
            aHidEvent.Time = DateTime.Now;
StephaneLenclud@54
   263
            if (aHidEvent.RepeatCount == 1)
sl@44
   264
            {
sl@44
   265
                //Re-Start our timer only after the initial delay 
sl@44
   266
                aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
sl@44
   267
            }
sl@44
   268
sl@44
   269
            //Broadcast our repeat event
sl@44
   270
            aHidEvent.OnHidEventRepeat(aHidEvent);
sl@41
   271
        }
sl@27
   272
StephaneLenclud@54
   273
        /// <summary>
StephaneLenclud@54
   274
        /// Print information about this device to our debug output.
StephaneLenclud@54
   275
        /// </summary>
StephaneLenclud@54
   276
        public void DebugWrite()
StephaneLenclud@54
   277
        {
StephaneLenclud@54
   278
            if (!IsValid)
StephaneLenclud@54
   279
            {
StephaneLenclud@54
   280
                Debug.WriteLine("==== Invalid HidEvent");
StephaneLenclud@54
   281
                return;
StephaneLenclud@54
   282
            }
StephaneLenclud@70
   283
StephaneLenclud@70
   284
            if (Device!=null)
StephaneLenclud@70
   285
            {
StephaneLenclud@70
   286
                Device.DebugWrite();
StephaneLenclud@70
   287
            }
StephaneLenclud@70
   288
            
StephaneLenclud@54
   289
            if (IsGeneric) Debug.WriteLine("==== Generic");
StephaneLenclud@54
   290
            if (IsKeyboard) Debug.WriteLine("==== Keyboard");
StephaneLenclud@54
   291
            if (IsMouse) Debug.WriteLine("==== Mouse");
StephaneLenclud@54
   292
            Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
StephaneLenclud@54
   293
            Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
StephaneLenclud@54
   294
            Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
StephaneLenclud@54
   295
            Debug.WriteLine("==== InputReport: 0x" + InputReportString());
StephaneLenclud@54
   296
            foreach (ushort usage in Usages)
StephaneLenclud@54
   297
            {
StephaneLenclud@54
   298
                Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
StephaneLenclud@54
   299
            }
StephaneLenclud@54
   300
        }
StephaneLenclud@49
   301
StephaneLenclud@54
   302
        /// <summary>
StephaneLenclud@54
   303
        /// 
StephaneLenclud@54
   304
        /// </summary>
StephaneLenclud@54
   305
        /// <returns></returns>
StephaneLenclud@54
   306
        public string InputReportString()
StephaneLenclud@54
   307
        {
StephaneLenclud@70
   308
            if (InputReport == null)
StephaneLenclud@70
   309
            {
StephaneLenclud@70
   310
                return "null";
StephaneLenclud@70
   311
            }
StephaneLenclud@70
   312
StephaneLenclud@54
   313
            string hidDump = "";
StephaneLenclud@54
   314
            foreach (byte b in InputReport)
StephaneLenclud@54
   315
            {
StephaneLenclud@54
   316
                hidDump += b.ToString("X2");
StephaneLenclud@54
   317
            }
StephaneLenclud@54
   318
            return hidDump;
StephaneLenclud@54
   319
        }
StephaneLenclud@49
   320
StephaneLenclud@49
   321
StephaneLenclud@54
   322
        /// <summary>
StephaneLenclud@54
   323
        /// Create a list view item describing this HidEvent
StephaneLenclud@54
   324
        /// </summary>
StephaneLenclud@54
   325
        /// <returns></returns>
sl@42
   326
        public ListViewItem ToListViewItem()
sl@42
   327
        {
StephaneLenclud@49
   328
            string usageText = "";
sl@42
   329
StephaneLenclud@54
   330
            foreach (ushort usage in Usages)
StephaneLenclud@54
   331
            {
StephaneLenclud@54
   332
                if (usageText != "")
StephaneLenclud@54
   333
                {
StephaneLenclud@54
   334
                    //Add a separator
StephaneLenclud@54
   335
                    usageText += ", ";
StephaneLenclud@54
   336
                }
sl@42
   337
StephaneLenclud@54
   338
                UsagePage usagePage = (UsagePage)UsagePage;
StephaneLenclud@54
   339
                switch (usagePage)
StephaneLenclud@54
   340
                {
StephaneLenclud@54
   341
                    case Hid.UsagePage.Consumer:
StephaneLenclud@66
   342
                        usageText += ((Hid.Usage.ConsumerControl)usage).ToString();
StephaneLenclud@54
   343
                        break;
sl@42
   344
StephaneLenclud@54
   345
                    case Hid.UsagePage.WindowsMediaCenterRemoteControl:
StephaneLenclud@66
   346
                        usageText += ((Hid.Usage.WindowsMediaCenterRemoteControl)usage).ToString();
StephaneLenclud@54
   347
                        break;
StephaneLenclud@49
   348
StephaneLenclud@54
   349
                    default:
StephaneLenclud@54
   350
                        usageText += usage.ToString("X2");
StephaneLenclud@54
   351
                        break;
StephaneLenclud@54
   352
                }
StephaneLenclud@54
   353
            }
StephaneLenclud@49
   354
StephaneLenclud@54
   355
            ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
sl@42
   356
            return item;
sl@42
   357
        }
sl@42
   358
sl@27
   359
    }
sl@27
   360
sl@27
   361
}