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