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