sl@27: using System; sl@27: using System.Windows.Forms; sl@27: using System.Runtime.InteropServices; sl@27: using System.Diagnostics; sl@27: using System.Text; sl@27: using Microsoft.Win32.SafeHandles; sl@27: using Win32; sl@27: using System.Collections.Generic; sl@41: using System.Timers; sl@27: sl@27: sl@27: namespace Hid sl@27: { sl@27: /// sl@27: /// Represent a HID event. sl@27: /// sl@41: public class HidEvent: IDisposable sl@27: { sl@27: public bool IsValid { get; private set; } sl@42: public bool IsForeground { get; private set; } sl@27: public bool IsBackground { get{return !IsForeground;} } sl@27: public bool IsMouse { get; private set; } sl@27: public bool IsKeyboard { get; private set; } sl@27: public bool IsGeneric { get; private set; } sl@29: public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } } sl@29: public bool IsButtonUp { get { return Usages.Count == 1 && Usages[0] == 0; } } sl@42: public bool IsRepeat { get; private set; } sl@27: sl@27: public HidDevice Device { get; private set; } sl@27: sl@27: public ushort UsagePage { get; private set; } sl@27: public ushort UsageCollection { get; private set; } sl@31: public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } } sl@41: public List Usages { get; private set; } sl@41: public delegate void HidEventRepeatDelegate(HidEvent aHidEvent); sl@41: public event HidEventRepeatDelegate OnHidEventRepeat; sl@27: sl@41: private System.Timers.Timer Timer { get; set; } sl@41: sl@43: //Compute repeat delay and speed based on system settings sl@43: //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs sl@43: private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay); sl@43: private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed; sl@43: sl@42: /// sl@42: /// Tells whether this event has already been disposed of. sl@42: /// sl@42: public bool IsStray { get { return Timer == null; } } sl@42: sl@43: /// sl@43: /// We typically dispose of events as soon as we get the corresponding key up signal. sl@43: /// sl@41: public void Dispose() sl@41: { sl@41: Timer.Enabled = false; sl@41: Timer.Dispose(); sl@43: //Mark this event as a stray sl@41: Timer = null; sl@41: } sl@27: sl@27: /// sl@27: /// Initialize an HidEvent from a WM_INPUT message sl@27: /// sl@27: /// Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice sl@41: public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate) sl@27: { sl@42: IsRepeat = false; sl@27: IsValid = false; sl@27: IsKeyboard = false; sl@27: IsGeneric = false; sl@27: sl@41: Timer = new System.Timers.Timer(); sl@27: Usages = new List(); sl@41: OnHidEventRepeat += aRepeatDelegate; sl@27: sl@27: if (aMessage.Msg != Const.WM_INPUT) sl@27: { sl@27: //Has to be a WM_INPUT message sl@27: return; sl@27: } sl@27: sl@27: if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT) sl@27: { sl@27: IsForeground = true; sl@27: } sl@27: else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK) sl@27: { sl@27: IsForeground = false; sl@27: } sl@27: sl@27: //Declare some pointers sl@27: IntPtr rawInputBuffer = IntPtr.Zero; sl@27: //My understanding is that this is basically our HID descriptor sl@27: IntPtr preParsedData = IntPtr.Zero; sl@27: sl@27: try sl@27: { sl@27: //Fetch raw input sl@27: RAWINPUT rawInput = new RAWINPUT(); sl@27: if (!RawInput.GetRawInputData(aMessage.LParam, ref rawInput, ref rawInputBuffer)) sl@27: { sl@27: return; sl@27: } sl@27: sl@27: //Fetch device info sl@27: RID_DEVICE_INFO deviceInfo = new RID_DEVICE_INFO(); sl@27: if (!RawInput.GetDeviceInfo(rawInput.header.hDevice, ref deviceInfo)) sl@27: { sl@27: return; sl@27: } sl@27: sl@27: //Get various information about this HID device sl@27: Device = new Hid.HidDevice(rawInput.header.hDevice); sl@27: sl@27: if (rawInput.header.dwType == Const.RIM_TYPEHID) //Check that our raw input is HID sl@27: { sl@27: IsGeneric = true; sl@27: sl@27: Debug.WriteLine("WM_INPUT source device is HID."); sl@27: //Get Usage Page and Usage sl@27: //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4")); sl@27: UsagePage = deviceInfo.hid.usUsagePage; sl@27: UsageCollection = deviceInfo.hid.usUsage; sl@27: sl@27: preParsedData = RawInput.GetPreParsedData(rawInput.header.hDevice); sl@27: sl@27: 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 sl@27: && rawInput.hid.dwCount > 0)) //Check that we have at least one HID msg sl@27: { sl@27: return; sl@27: } sl@27: sl@27: //Allocate a buffer for one HID input sl@27: byte[] hidInputReport = new byte[rawInput.hid.dwSizeHid]; sl@27: sl@27: Debug.WriteLine("Raw input contains " + rawInput.hid.dwCount + " HID input report(s)"); sl@27: sl@27: //For each HID input report in our raw input sl@27: for (int i = 0; i < rawInput.hid.dwCount; i++) sl@27: { sl@27: //Compute the address from which to copy our HID input sl@27: int hidInputOffset = 0; sl@27: unsafe sl@27: { sl@27: byte* source = (byte*)rawInputBuffer; sl@27: source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (rawInput.hid.dwSizeHid * i); sl@27: hidInputOffset = (int)source; sl@27: } sl@27: sl@27: //Copy HID input into our buffer sl@27: Marshal.Copy(new IntPtr(hidInputOffset), hidInputReport, 0, (int)rawInput.hid.dwSizeHid); sl@27: sl@27: //Print HID input report in our debug output sl@27: string hidDump = "HID input report: "; sl@27: foreach (byte b in hidInputReport) sl@27: { sl@27: hidDump += b.ToString("X2"); sl@27: } sl@27: Debug.WriteLine(hidDump); sl@27: sl@27: //Proper parsing now sl@27: uint usageCount = 1; //Assuming a single usage per input report. Is that correct? sl@27: Win32.USAGE_AND_PAGE[] usages = new Win32.USAGE_AND_PAGE[usageCount]; sl@27: Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, preParsedData, hidInputReport, (uint)hidInputReport.Length); sl@27: if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) sl@27: { sl@27: Debug.WriteLine("Could not parse HID data!"); sl@27: } sl@27: else sl@27: { sl@27: //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4")); sl@27: //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4")); sl@27: //Add this usage to our list sl@27: Usages.Add(usages[0].Usage); sl@27: } sl@27: } sl@27: sl@27: } sl@27: else if (rawInput.header.dwType == Const.RIM_TYPEMOUSE) sl@27: { sl@27: IsMouse = true; sl@27: sl@27: Debug.WriteLine("WM_INPUT source device is Mouse."); sl@27: // do mouse handling... sl@27: } sl@27: else if (rawInput.header.dwType == Const.RIM_TYPEKEYBOARD) sl@27: { sl@27: IsKeyboard = true; sl@27: sl@27: Debug.WriteLine("WM_INPUT source device is Keyboard."); sl@27: // do keyboard handling... sl@27: Debug.WriteLine("Type: " + deviceInfo.keyboard.dwType.ToString()); sl@27: Debug.WriteLine("SubType: " + deviceInfo.keyboard.dwSubType.ToString()); sl@27: Debug.WriteLine("Mode: " + deviceInfo.keyboard.dwKeyboardMode.ToString()); sl@27: Debug.WriteLine("Number of function keys: " + deviceInfo.keyboard.dwNumberOfFunctionKeys.ToString()); sl@27: Debug.WriteLine("Number of indicators: " + deviceInfo.keyboard.dwNumberOfIndicators.ToString()); sl@27: Debug.WriteLine("Number of keys total: " + deviceInfo.keyboard.dwNumberOfKeysTotal.ToString()); sl@27: } sl@27: } sl@27: finally sl@27: { sl@27: //Always executed when leaving our try block sl@27: Marshal.FreeHGlobal(rawInputBuffer); sl@27: Marshal.FreeHGlobal(preParsedData); sl@27: } sl@27: sl@41: if (Usages[0]!=0) sl@41: { sl@43: StartRepeatTimer(iRepeatDelay); sl@41: } sl@41: sl@27: IsValid = true; sl@27: } sl@27: sl@27: /// sl@27: /// Print information about this device to our debug output. sl@27: /// sl@27: public void DebugWrite() sl@27: { sl@27: if (!IsValid) sl@27: { sl@27: Debug.WriteLine("==== Invalid HidEvent"); sl@27: return; sl@27: } sl@27: Device.DebugWrite(); sl@27: if (IsGeneric) Debug.WriteLine("==== Generic"); sl@27: if (IsKeyboard) Debug.WriteLine("==== Keyboard"); sl@27: if (IsMouse) Debug.WriteLine("==== Mouse"); sl@27: Debug.WriteLine("==== Foreground: " + IsForeground.ToString()); sl@27: Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4")); sl@27: Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4")); sl@27: foreach (ushort usage in Usages) sl@27: { sl@27: Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4")); sl@27: } sl@27: } sl@27: sl@41: public void StartRepeatTimer(double aInterval) sl@41: { sl@41: if (Timer == null) sl@41: { sl@41: return; sl@41: } sl@41: Timer.Enabled = false; sl@41: Timer.AutoReset = false; sl@41: Timer.Interval = aInterval; sl@41: Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this); sl@41: Timer.Enabled = true; sl@41: } sl@27: sl@41: private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent) sl@41: { sl@42: if (aHidEvent.IsStray) sl@42: { sl@42: //Skip events if canceled sl@42: return; sl@42: } sl@42: aHidEvent.IsRepeat = true; sl@43: StartRepeatTimer(iRepeatSpeed); sl@41: OnHidEventRepeat(aHidEvent); sl@41: } sl@27: sl@42: public ListViewItem ToListViewItem() sl@42: { sl@42: //TODO: What to do with multiple usage sl@42: string usage = ""; sl@42: UsagePage usagePage = (UsagePage)UsagePage; sl@42: switch (usagePage) sl@42: { sl@42: case Hid.UsagePage.Consumer: sl@42: usage = ((Hid.UsageTables.ConsumerControl)Usages[0]).ToString(); sl@42: break; sl@42: sl@42: case Hid.UsagePage.WindowsMediaCenterRemoteControl: sl@42: usage = ((Hid.UsageTables.WindowsMediaCenterRemoteControl)Usages[0]).ToString(); sl@42: break; sl@42: sl@42: } sl@42: sl@42: ListViewItem item = new ListViewItem(new[] { usage, UsagePage.ToString("X2"), UsageCollection.ToString("X2"), IsRepeat.ToString() }); sl@42: return item; sl@42: } sl@42: sl@27: } sl@27: sl@27: }