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