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