HidEvent.cs
author StephaneLenclud
Sat, 14 Feb 2015 22:11:36 +0100
changeset 50 e6c103ebb155
parent 47 bd068db7779a
child 52 2f34ceaf0692
permissions -rw-r--r--
Adding some hid.dll imports.
     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 == 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 byte[] InputReport { get; private set; }
    37 		//
    38 		public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
    39         public event HidEventRepeatDelegate OnHidEventRepeat;
    40 
    41         private System.Timers.Timer Timer { get; set; }
    42         public DateTime Time { get; private set; }
    43         public DateTime OriginalTime { get; private set; }
    44 
    45         //Compute repeat delay and speed based on system settings
    46         //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
    47         private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
    48         private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
    49 
    50         /// <summary>
    51         /// Tells whether this event has already been disposed of.
    52         /// </summary>
    53         public bool IsStray { get { return Timer == null; } }
    54 
    55         /// <summary>
    56         /// We typically dispose of events as soon as we get the corresponding key up signal.
    57         /// </summary>
    58         public void Dispose()
    59         {
    60             Timer.Enabled = false;
    61             Timer.Dispose();
    62             //Mark this event as a stray
    63             Timer = null;
    64         }
    65 
    66         /// <summary>
    67         /// Initialize an HidEvent from a WM_INPUT message
    68         /// </summary>
    69         /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
    70         public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
    71         {
    72             RepeatCount = 0;
    73             IsValid = false;
    74             IsKeyboard = false;
    75             IsGeneric = false;
    76             
    77 
    78             Time = DateTime.Now;
    79             OriginalTime = DateTime.Now;
    80             Timer = new System.Timers.Timer();
    81             Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
    82             Usages = new List<ushort>();
    83             OnHidEventRepeat += aRepeatDelegate;
    84 
    85             if (aMessage.Msg != Const.WM_INPUT)
    86             {
    87                 //Has to be a WM_INPUT message
    88                 return;
    89             }
    90 
    91             if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
    92             {
    93                 IsForeground = true;
    94             }
    95             else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
    96             {
    97                 IsForeground = false;
    98             }
    99 
   100             //Declare some pointers
   101             IntPtr rawInputBuffer = IntPtr.Zero;
   102             //My understanding is that this is basically our HID descriptor 
   103             IntPtr preParsedData = IntPtr.Zero;
   104 
   105             try
   106             {
   107                 //Fetch raw input
   108                 RAWINPUT rawInput = new RAWINPUT();
   109                 if (!Win32.Utils.RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer))
   110                 {
   111                     return;
   112                 }
   113 
   114                 //Fetch device info
   115                 RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO();
   116                 if (!Win32.Utils.RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo))
   117                 {
   118                     return;
   119                 }
   120 
   121                 //Get various information about this HID device
   122                 Device = new Hid.HidDevice(rawInput.header.hDevice);                
   123 
   124                 if (rawInput.header.dwType == Const.RIM_TYPEHID)  //Check that our raw input is HID                        
   125                 {
   126                     IsGeneric = true;
   127 
   128                     Debug.WriteLine("WM_INPUT source device is HID.");
   129                     //Get Usage Page and Usage
   130                     //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
   131                     UsagePage = deviceInfo.hid.usUsagePage;
   132                     UsageCollection = deviceInfo.hid.usUsage;
   133 
   134                     preParsedData = Win32.Utils.RawInput.GetPreParsedData(rawInput.header.hDevice);
   135 
   136                     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
   137                         && rawInput.hid.dwCount > 0))    //Check that we have at least one HID msg
   138                     {
   139                         return;
   140                     }
   141 
   142                     //Allocate a buffer for one HID input
   143 					InputReport = new byte[rawInput.hid.dwSizeHid];
   144 
   145                     Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)");
   146 
   147                     //For each HID input report in our raw input
   148                     for (int i = 0; i < rawInput.hid.dwCount; i++)
   149                     {
   150                         //Compute the address from which to copy our HID input
   151                         int hidInputOffset = 0;
   152                         unsafe
   153                         {
   154                             byte* source = (byte*)rawInputBuffer;
   155                             source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i);
   156                             hidInputOffset = (int)source;
   157                         }
   158 
   159                         //Copy HID input into our buffer
   160 						Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)rawInput.hid.dwSizeHid);
   161 
   162                         //Print HID input report in our debug output
   163                         //string hidDump = "HID input report: " + InputReportString();
   164                         //Debug.WriteLine(hidDump);
   165 
   166                         //Do proper parsing of our HID report
   167 						//First query our usage count
   168                         uint usageCount = 0; 
   169                         Win32.USAGE_AND_PAGE[] usages = null;
   170 						Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, InputReport, (uint)InputReport.Length);
   171 						if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
   172 						{
   173 							//Allocate a large enough buffer 
   174 							usages = new Win32.USAGE_AND_PAGE[usageCount];
   175 							//...and fetch our usages
   176 							status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, InputReport, (uint)InputReport.Length);
   177 							if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
   178 							{
   179 								Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
   180 							}
   181 						}
   182 						else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) 
   183 						{
   184 							Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
   185 						}
   186 
   187 						Debug.WriteLine("Usage count: " + usageCount.ToString());
   188 
   189 						if (usages != null)
   190 						{
   191 							foreach (USAGE_AND_PAGE up in usages)
   192 							{
   193 								//Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
   194 								//Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
   195 								//Add this usage to our list
   196 								Usages.Add(up.Usage);
   197 							}
   198 						}
   199                     }
   200                 }
   201                 else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE)
   202                 {
   203                     IsMouse = true;
   204 
   205                     Debug.WriteLine("WM_INPUT source device is Mouse.");                    
   206                     // do mouse handling...
   207                 }
   208                 else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD)
   209                 {
   210                     IsKeyboard = true;
   211 
   212                     Debug.WriteLine("WM_INPUT source device is Keyboard.");
   213                     // do keyboard handling...
   214                     Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString());
   215                     Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString());
   216                     Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString());
   217                     Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString());
   218                     Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString());
   219                     Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString());
   220                 }
   221             }
   222             finally
   223             {
   224                 //Always executed when leaving our try block
   225                 Marshal.FreeHGlobal(rawInputBuffer);
   226                 Marshal.FreeHGlobal(preParsedData);
   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 			Device.DebugWrite();
   284 			if (IsGeneric) Debug.WriteLine("==== Generic");
   285 			if (IsKeyboard) Debug.WriteLine("==== Keyboard");
   286 			if (IsMouse) Debug.WriteLine("==== Mouse");
   287 			Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
   288 			Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
   289 			Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
   290 			Debug.WriteLine("==== InputReport: 0x" + InputReportString());
   291 			foreach (ushort usage in Usages)
   292 			{
   293 				Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
   294 			}
   295 		}
   296 
   297 		/// <summary>
   298 		/// 
   299 		/// </summary>
   300 		/// <returns></returns>
   301 		public string InputReportString()
   302 		{
   303 			string hidDump = "";
   304 			foreach (byte b in InputReport)
   305 			{
   306 				hidDump += b.ToString("X2");
   307 			}
   308 			return hidDump;
   309 		}
   310 
   311 
   312 		/// <summary>
   313 		/// Create a list view item describing this HidEvent
   314 		/// </summary>
   315 		/// <returns></returns>
   316         public ListViewItem ToListViewItem()
   317         {
   318             string usageText = "";
   319 
   320 			foreach (ushort usage in Usages)
   321 			{
   322 				if (usageText != "")
   323 				{
   324 					//Add a separator
   325 					usageText += ", ";
   326 				}
   327 
   328 				UsagePage usagePage = (UsagePage)UsagePage;
   329 				switch (usagePage)
   330 				{
   331 					case Hid.UsagePage.Consumer:
   332 						usageText += ((Hid.UsageTables.ConsumerControl)usage).ToString();
   333 						break;
   334 
   335 					case Hid.UsagePage.WindowsMediaCenterRemoteControl:
   336 						usageText += ((Hid.UsageTables.WindowsMediaCenterRemoteControl)usage).ToString();
   337 						break;
   338 
   339 					default:
   340 						usageText += usage.ToString("X2");
   341 						break;
   342 				}				
   343 			}
   344 
   345 			ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
   346             return item;
   347         }
   348 
   349     }
   350 
   351 }