HidEvent.cs
author sl
Wed, 24 Dec 2014 14:31:16 +0100
changeset 43 5c7f34c627b9
parent 42 219e9a418456
child 44 63a5f4c05179
permissions -rw-r--r--
Fixing repeat delay and speed computation.
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.
sl@27
    16
    /// </summary>
sl@41
    17
    public class HidEvent: IDisposable
sl@27
    18
    {
sl@27
    19
        public bool IsValid { get; private set; }
sl@42
    20
        public bool IsForeground { get; private set; }        
sl@27
    21
        public bool IsBackground { get{return !IsForeground;} }
sl@27
    22
        public bool IsMouse { get; private set; }
sl@27
    23
        public bool IsKeyboard { get; private set; }
sl@27
    24
        public bool IsGeneric { get; private set; }
sl@29
    25
        public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
sl@29
    26
        public bool IsButtonUp { get { return Usages.Count == 1 && Usages[0] == 0; } }
sl@42
    27
        public bool IsRepeat { get; private set; }
sl@27
    28
sl@27
    29
        public HidDevice Device { get; private set; }
sl@27
    30
sl@27
    31
        public ushort UsagePage { get; private set; }
sl@27
    32
        public ushort UsageCollection { get; private set; }
sl@31
    33
        public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
sl@41
    34
        public List<ushort> Usages { get; private set; }        
sl@41
    35
        public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
sl@41
    36
        public event HidEventRepeatDelegate OnHidEventRepeat;
sl@27
    37
sl@41
    38
        private System.Timers.Timer Timer { get; set; }
sl@41
    39
sl@43
    40
        //Compute repeat delay and speed based on system settings
sl@43
    41
        //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
sl@43
    42
        private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
sl@43
    43
        private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
sl@43
    44
sl@42
    45
        /// <summary>
sl@42
    46
        /// Tells whether this event has already been disposed of.
sl@42
    47
        /// </summary>
sl@42
    48
        public bool IsStray { get { return Timer == null; } }
sl@42
    49
sl@43
    50
        /// <summary>
sl@43
    51
        /// We typically dispose of events as soon as we get the corresponding key up signal.
sl@43
    52
        /// </summary>
sl@41
    53
        public void Dispose()
sl@41
    54
        {
sl@41
    55
            Timer.Enabled = false;
sl@41
    56
            Timer.Dispose();
sl@43
    57
            //Mark this event as a stray
sl@41
    58
            Timer = null;
sl@41
    59
        }
sl@27
    60
sl@27
    61
        /// <summary>
sl@27
    62
        /// Initialize an HidEvent from a WM_INPUT message
sl@27
    63
        /// </summary>
sl@27
    64
        /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
sl@41
    65
        public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
sl@27
    66
        {
sl@42
    67
            IsRepeat = false;
sl@27
    68
            IsValid = false;
sl@27
    69
            IsKeyboard = false;
sl@27
    70
            IsGeneric = false;
sl@27
    71
sl@41
    72
            Timer = new System.Timers.Timer();
sl@27
    73
            Usages = new List<ushort>();
sl@41
    74
            OnHidEventRepeat += aRepeatDelegate;
sl@27
    75
sl@27
    76
            if (aMessage.Msg != Const.WM_INPUT)
sl@27
    77
            {
sl@27
    78
                //Has to be a WM_INPUT message
sl@27
    79
                return;
sl@27
    80
            }
sl@27
    81
sl@27
    82
            if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
sl@27
    83
            {
sl@27
    84
                IsForeground = true;
sl@27
    85
            }
sl@27
    86
            else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
sl@27
    87
            {
sl@27
    88
                IsForeground = false;
sl@27
    89
            }
sl@27
    90
sl@27
    91
            //Declare some pointers
sl@27
    92
            IntPtr rawInputBuffer = IntPtr.Zero;
sl@27
    93
            //My understanding is that this is basically our HID descriptor 
sl@27
    94
            IntPtr preParsedData = IntPtr.Zero;
sl@27
    95
sl@27
    96
            try
sl@27
    97
            {
sl@27
    98
                //Fetch raw input
sl@27
    99
                RAWINPUT rawInput = new RAWINPUT();
sl@27
   100
                if (!RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
sl@27
   101
                {
sl@27
   102
                    return;
sl@27
   103
                }
sl@27
   104
sl@27
   105
                //Fetch device info
sl@27
   106
                RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
sl@27
   107
                if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
sl@27
   108
                {
sl@27
   109
                    return;
sl@27
   110
                }
sl@27
   111
sl@27
   112
                //Get various information about this HID device
sl@27
   113
                Device = new Hid.HidDevice(rawInput.header.hDevice);                
sl@27
   114
sl@27
   115
                if (rawInput.header.dwType == Const.RIM_TYPEHID)  //Check that our raw input is HID                        
sl@27
   116
                {
sl@27
   117
                    IsGeneric = true;
sl@27
   118
sl@27
   119
                    Debug.WriteLine("WM_INPUT source device is HID.");
sl@27
   120
                    //Get Usage Page and Usage
sl@27
   121
                    //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
sl@27
   122
                    UsagePage = deviceInfo.hid.usUsagePage;
sl@27
   123
                    UsageCollection = deviceInfo.hid.usUsage;
sl@27
   124
sl@27
   125
                    preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice);
sl@27
   126
sl@27
   127
                    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
   128
                        && rawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
sl@27
   129
                    {
sl@27
   130
                        return;
sl@27
   131
                    }
sl@27
   132
sl@27
   133
                    //Allocate a buffer for one HID input
sl@27
   134
                    byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid];
sl@27
   135
sl@27
   136
                    Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
sl@27
   137
sl@27
   138
                    //For each HID input report in our raw input
sl@27
   139
                    for (int i = 0; i < rawInput.hid.dwCount; i++)
sl@27
   140
                    {
sl@27
   141
                        //Compute the address from which to copy our HID input
sl@27
   142
                        int hidInputOffset = 0;
sl@27
   143
                        unsafe
sl@27
   144
                        {
sl@27
   145
                            byte* source = (byte*)rawInputBuffer;
sl@27
   146
                            source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
sl@27
   147
                            hidInputOffset = (int)source;
sl@27
   148
                        }
sl@27
   149
sl@27
   150
                        //Copy HID input into our buffer
sl@27
   151
                        Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid);
sl@27
   152
sl@27
   153
                        //Print HID input report in our debug output
sl@27
   154
                        string hidDump = "HID input report: ";
sl@27
   155
                        foreach (byte b in hidInputReport)
sl@27
   156
                        {
sl@27
   157
                            hidDump += b.ToString("X2");
sl@27
   158
                        }
sl@27
   159
                        Debug.WriteLine(hidDump);
sl@27
   160
sl@27
   161
                        //Proper parsing now
sl@27
   162
                        uint usageCount = 1; //Assuming a single usage per input report. Is that correct?
sl@27
   163
                        Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount];
sl@27
   164
                        Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length);
sl@27
   165
                        if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
sl@27
   166
                        {
sl@27
   167
                            Debug.WriteLine("Could not parse HID data!");
sl@27
   168
                        }
sl@27
   169
                        else
sl@27
   170
                        {
sl@27
   171
                            //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
sl@27
   172
                            //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
sl@27
   173
                            //Add this usage to our list
sl@27
   174
                            Usages.Add(usages[0].Usage);
sl@27
   175
                        }
sl@27
   176
                    }
sl@27
   177
sl@27
   178
                }
sl@27
   179
                else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
sl@27
   180
                {
sl@27
   181
                    IsMouse = true;
sl@27
   182
sl@27
   183
                    Debug.WriteLine("WM_INPUT source device is Mouse.");                    
sl@27
   184
                    // do mouse handling...
sl@27
   185
                }
sl@27
   186
                else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
sl@27
   187
                {
sl@27
   188
                    IsKeyboard = true;
sl@27
   189
sl@27
   190
                    Debug.WriteLine("WM_INPUT source device is Keyboard.");
sl@27
   191
                    // do keyboard handling...
sl@27
   192
                    Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
sl@27
   193
                    Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
sl@27
   194
                    Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
sl@27
   195
                    Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
sl@27
   196
                    Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
sl@27
   197
                    Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
sl@27
   198
                }
sl@27
   199
            }
sl@27
   200
            finally
sl@27
   201
            {
sl@27
   202
                //Always executed when leaving our try block
sl@27
   203
                Marshal.FreeHGlobal(rawInputBuffer);
sl@27
   204
                Marshal.FreeHGlobal(preParsedData);
sl@27
   205
            }
sl@27
   206
sl@41
   207
            if (Usages[0]!=0)
sl@41
   208
            {
sl@43
   209
                StartRepeatTimer(iRepeatDelay);
sl@41
   210
            }
sl@41
   211
            
sl@27
   212
            IsValid = true;
sl@27
   213
        }
sl@27
   214
sl@27
   215
        /// <summary>
sl@27
   216
        /// Print information about this device to our debug output.
sl@27
   217
        /// </summary>
sl@27
   218
        public void DebugWrite()
sl@27
   219
        {
sl@27
   220
            if (!IsValid)
sl@27
   221
            {
sl@27
   222
                Debug.WriteLine("==== Invalid HidEvent");
sl@27
   223
                return;
sl@27
   224
            }
sl@27
   225
            Device.DebugWrite();
sl@27
   226
            if (IsGeneric) Debug.WriteLine("==== Generic");
sl@27
   227
            if (IsKeyboard) Debug.WriteLine("==== Keyboard");
sl@27
   228
            if (IsMouse) Debug.WriteLine("==== Mouse");
sl@27
   229
            Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
sl@27
   230
            Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
sl@27
   231
            Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
sl@27
   232
            foreach (ushort usage in Usages)
sl@27
   233
            {
sl@27
   234
                Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
sl@27
   235
            }
sl@27
   236
        }
sl@27
   237
sl@41
   238
        public void StartRepeatTimer(double aInterval)
sl@41
   239
        {
sl@41
   240
            if (Timer == null)
sl@41
   241
            {
sl@41
   242
                return;
sl@41
   243
            }
sl@41
   244
            Timer.Enabled = false;
sl@41
   245
            Timer.AutoReset = false;
sl@41
   246
            Timer.Interval = aInterval;
sl@41
   247
            Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);            
sl@41
   248
            Timer.Enabled = true;
sl@41
   249
        }
sl@27
   250
sl@41
   251
        private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
sl@41
   252
        {
sl@42
   253
            if (aHidEvent.IsStray)
sl@42
   254
            {
sl@42
   255
                //Skip events if canceled
sl@42
   256
                return;
sl@42
   257
            }
sl@42
   258
            aHidEvent.IsRepeat = true;
sl@43
   259
            StartRepeatTimer(iRepeatSpeed);            
sl@41
   260
            OnHidEventRepeat(aHidEvent);
sl@41
   261
        }
sl@27
   262
sl@42
   263
        public ListViewItem ToListViewItem()
sl@42
   264
        {
sl@42
   265
            //TODO: What to do with multiple usage
sl@42
   266
            string usage = "";
sl@42
   267
            UsagePage usagePage = (UsagePage)UsagePage;
sl@42
   268
            switch (usagePage)
sl@42
   269
            {
sl@42
   270
                case Hid.UsagePage.Consumer:
sl@42
   271
                    usage = ((Hid.UsageTables.ConsumerControl)Usages[0]).ToString();
sl@42
   272
                    break;
sl@42
   273
sl@42
   274
                case Hid.UsagePage.WindowsMediaCenterRemoteControl:
sl@42
   275
                    usage = ((Hid.UsageTables.WindowsMediaCenterRemoteControl)Usages[0]).ToString();
sl@42
   276
                    break;
sl@42
   277
sl@42
   278
            }
sl@42
   279
sl@42
   280
            ListViewItem item = new ListViewItem(new[] { usage, UsagePage.ToString("X2"), UsageCollection.ToString("X2"), IsRepeat.ToString() });
sl@42
   281
            return item;
sl@42
   282
        }
sl@42
   283
sl@27
   284
    }
sl@27
   285
sl@27
   286
}