diff -r 72885c950813 -r cdc5f8f1b79e HidEvent.cs --- a/HidEvent.cs Sun Mar 15 16:56:31 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,598 +0,0 @@ -// -// Copyright (C) 2014-2015 Stéphane Lenclud. -// -// This file is part of SharpLibHid. -// -// SharpDisplayManager is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// SharpDisplayManager is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with SharpDisplayManager. If not, see . -// - - -using System; -using System.Windows.Forms; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.Text; -using Microsoft.Win32.SafeHandles; -using SharpLib.Win32; -using System.Collections.Generic; -using System.Timers; -using SharpLib.Hid.Usage; - - -namespace SharpLib.Hid -{ - /// - /// We provide utility functions to interpret gamepad dpad state. - /// - public enum DirectionPadState - { - Rest=-1, - Up=0, - UpRight=1, - Right=2, - DownRight=3, - Down=4, - DownLeft=5, - Left=6, - UpLeft=7 - } - - /// - /// Represent a HID event. - /// TODO: Rename this into HidRawInput? - /// - public class HidEvent : IDisposable - { - public bool IsValid { get; private set; } - public bool IsForeground { get; private set; } - public bool IsBackground { get { return !IsForeground; } } - public bool IsMouse { get; private set; } - public bool IsKeyboard { get; private set; } - /// - /// If this not a mouse or keyboard event then it's a generic HID event. - /// - public bool IsGeneric { get; private set; } - public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } } - public bool IsButtonUp { get { return Usages.Count == 0; } } - public bool IsRepeat { get { return RepeatCount != 0; } } - public uint RepeatCount { get; private set; } - - public HidDevice Device { get; private set; } - public RAWINPUT RawInput { get {return iRawInput;} } - private RAWINPUT iRawInput; - - public ushort UsagePage { get; private set; } - public ushort UsageCollection { get; private set; } - public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } } - public List Usages { get; private set; } - /// - /// Sorted in the same order as Device.InputValueCapabilities. - /// - public Dictionary UsageValues { get; private set; } - //TODO: We need a collection of input report - public byte[] InputReport { get; private set; } - // - public delegate void HidEventRepeatDelegate(HidEvent aHidEvent); - public event HidEventRepeatDelegate OnHidEventRepeat; - - private System.Timers.Timer Timer { get; set; } - public DateTime Time { get; private set; } - public DateTime OriginalTime { get; private set; } - - //Compute repeat delay and speed based on system settings - //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs - private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay); - private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed; - - /// - /// Tells whether this event has already been disposed of. - /// - public bool IsStray { get { return Timer == null; } } - - /// - /// We typically dispose of events as soon as we get the corresponding key up signal. - /// - public void Dispose() - { - Timer.Enabled = false; - Timer.Dispose(); - //Mark this event as a stray - Timer = null; - } - - /// - /// Initialize an HidEvent from a WM_INPUT message - /// - /// Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice - public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate) - { - RepeatCount = 0; - IsValid = false; - IsKeyboard = false; - IsGeneric = false; - - Time = DateTime.Now; - OriginalTime = DateTime.Now; - Timer = new System.Timers.Timer(); - Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this); - Usages = new List(); - UsageValues = new Dictionary(); - OnHidEventRepeat += aRepeatDelegate; - - if (aMessage.Msg != Const.WM_INPUT) - { - //Has to be a WM_INPUT message - return; - } - - if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT) - { - IsForeground = true; - } - else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK) - { - IsForeground = false; - } - - //Declare some pointers - IntPtr rawInputBuffer = IntPtr.Zero; - - try - { - //Fetch raw input - iRawInput = new RAWINPUT(); - if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref iRawInput, ref rawInputBuffer)) - { - Debug.WriteLine("GetRawInputData failed!"); - return; - } - - //Our device can actually be null. - //This is notably happening for some keyboard events - if (RawInput.header.hDevice != IntPtr.Zero) - { - //Get various information about this HID device - Device = new HidDevice(RawInput.header.hDevice); - } - - if (RawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID) //Check that our raw input is HID - { - IsGeneric = true; - - Debug.WriteLine("WM_INPUT source device is HID."); - //Get Usage Page and Usage - //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4")); - UsagePage = Device.Info.hid.usUsagePage; - UsageCollection = Device.Info.hid.usUsage; - - 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 - && RawInput.hid.dwCount > 0)) //Check that we have at least one HID msg - { - return; - } - - //Allocate a buffer for one HID input - InputReport = new byte[RawInput.hid.dwSizeHid]; - - Debug.WriteLine("Raw input contains " + RawInput.hid.dwCount + " HID input report(s)"); - - //For each HID input report in our raw input - for (int i = 0; i < RawInput.hid.dwCount; i++) - { - //Compute the address from which to copy our HID input - int hidInputOffset = 0; - unsafe - { - byte* source = (byte*)rawInputBuffer; - source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (RawInput.hid.dwSizeHid * i); - hidInputOffset = (int)source; - } - - //Copy HID input into our buffer - Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid); - // - ProcessInputReport(InputReport); - } - } - else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE) - { - IsMouse = true; - - Debug.WriteLine("WM_INPUT source device is Mouse."); - // do mouse handling... - } - else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD) - { - IsKeyboard = true; - - Debug.WriteLine("WM_INPUT source device is Keyboard."); - // do keyboard handling... - if (Device != null) - { - Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString()); - Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString()); - Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString()); - Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString()); - Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString()); - Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString()); - } - } - } - finally - { - //Always executed when leaving our try block - Marshal.FreeHGlobal(rawInputBuffer); - } - - // - if (IsButtonDown) - { - //TODO: Make this optional - //StartRepeatTimer(iRepeatDelay); - } - - IsValid = true; - } - - /// - /// - /// - private void ProcessInputReport(byte[] aInputReport) - { - //Print HID input report in our debug output - //string hidDump = "HID input report: " + InputReportString(); - //Debug.WriteLine(hidDump); - - //Get all our usages, those are typically the buttons currently pushed on a gamepad. - //For a remote control it's usually just the one button that was pushed. - GetUsages(aInputReport); - - //Now process direction pad (d-pad, dpad) and axes - GetUsageValues(aInputReport); - } - - /// - /// Typically fetches values of a joystick/gamepad axis and dpad directions. - /// - /// - private void GetUsageValues(byte[] aInputReport) - { - if (Device.InputValueCapabilities == null) - { - return; - } - - foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) - { - if (caps.IsRange) - { - //What should we do with those guys? - continue; - } - - //Now fetch and add our usage value - uint usageValue = 0; - Win32.HidStatus status = Win32.Function.HidP_GetUsageValue(Win32.HIDP_REPORT_TYPE.HidP_Input, caps.UsagePage, caps.LinkCollection, caps.NotRange.Usage, ref usageValue, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); - if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS) - { - UsageValues[caps]=usageValue; - } - } - } - - /// - /// Get all our usages, those are typically the buttons currently pushed on a gamepad. - /// For a remote control it's usually just the one button that was pushed. - /// - private void GetUsages(byte[] aInputReport) - { - //Do proper parsing of our HID report - //First query our usage count - uint usageCount = 0; - Win32.USAGE_AND_PAGE[] usages = null; - Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); - if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL) - { - //Allocate a large enough buffer - usages = new Win32.USAGE_AND_PAGE[usageCount]; - //...and fetch our usages - status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length); - if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) - { - Debug.WriteLine("Second pass could not parse HID data: " + status.ToString()); - } - } - else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS) - { - Debug.WriteLine("First pass could not parse HID data: " + status.ToString()); - } - - Debug.WriteLine("Usage count: " + usageCount.ToString()); - - //Copy usages into this event - if (usages != null) - { - foreach (USAGE_AND_PAGE up in usages) - { - //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4")); - //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4")); - //Add this usage to our list - Usages.Add(up.Usage); - } - } - } - - /// - /// - /// - /// - /// - /// - public uint GetUsageValue(ushort aUsagePage, ushort aUsage) - { - foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) - { - if (caps.IsRange) - { - //What should we do with those guys? - continue; - } - - //Check if we have a match - if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage) - { - return UsageValues[caps]; - } - } - - return 0; - } - - /// - /// - /// - /// - /// - /// - public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage) - { - int i = -1; - foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities) - { - i++; - if (caps.IsRange) - { - //What should we do with those guys? - continue; - } - - //Check if we have a match - if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage) - { - return i; - } - } - - return i; - } - - - /// - /// TODO: Move this to another level? - /// - /// - public void StartRepeatTimer(double aInterval) - { - if (Timer == null) - { - return; - } - Timer.Enabled = false; - //Initial delay do not use auto reset - //After our initial delay however we do setup our timer one more time using auto reset - Timer.AutoReset = (RepeatCount != 0); - Timer.Interval = aInterval; - Timer.Enabled = true; - } - - static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent) - { - if (aHidEvent.IsStray) - { - //Skip events if canceled - return; - } - - aHidEvent.RepeatCount++; - aHidEvent.Time = DateTime.Now; - if (aHidEvent.RepeatCount == 1) - { - //Re-Start our timer only after the initial delay - aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed); - } - - //Broadcast our repeat event - aHidEvent.OnHidEventRepeat(aHidEvent); - } - - /// - /// Provide the state of the dpad or hat switch if any. - /// If no dpad is found we return 'at rest'. - /// - /// - public DirectionPadState GetDirectionPadState() - { - int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)GenericDesktop.HatSwitch); - if (index < 0) - { - //No hat switch found - return DirectionPadState.Rest; - } - - HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index]; - if (caps.IsRange) - { - //Defensive - return DirectionPadState.Rest; - } - - uint dpadUsageValue = UsageValues[caps]; - - if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax) - { - //Out of range means at rest - return DirectionPadState.Rest; - } - - //Normalize value to start at zero - //TODO: more error check here? - DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin); - return res; - } - - /// - /// Print information about this device to our debug output. - /// - public void DebugWrite() - { - if (!IsValid) - { - Debug.WriteLine("==== Invalid HidEvent"); - return; - } - - if (Device!=null) - { - Device.DebugWrite(); - } - - if (IsGeneric) Debug.WriteLine("==== Generic"); - if (IsKeyboard) Debug.WriteLine("==== Keyboard"); - if (IsMouse) Debug.WriteLine("==== Mouse"); - Debug.WriteLine("==== Foreground: " + IsForeground.ToString()); - Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4")); - Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4")); - Debug.WriteLine("==== InputReport: 0x" + InputReportString()); - foreach (ushort usage in Usages) - { - Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4")); - } - } - - /// - /// - /// - /// - public string InputReportString() - { - if (InputReport == null) - { - return "null"; - } - - string hidDump = ""; - foreach (byte b in InputReport) - { - hidDump += b.ToString("X2"); - } - return hidDump; - } - - - /// - /// Create a list view item describing this HidEvent - /// - /// - public ListViewItem ToListViewItem() - { - string usageText = ""; - - foreach (ushort usage in Usages) - { - if (usageText != "") - { - //Add a separator - usageText += ", "; - } - - UsagePage usagePage = (UsagePage)UsagePage; - switch (usagePage) - { - case Hid.UsagePage.Consumer: - usageText += ((ConsumerControl)usage).ToString(); - break; - - case Hid.UsagePage.WindowsMediaCenterRemoteControl: - usageText += ((WindowsMediaCenterRemoteControl)usage).ToString(); - break; - - default: - usageText += usage.ToString("X2"); - break; - } - } - - //If we are a gamepad display axis and dpad values - if (Device.IsGamePad) - { - //uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch); - //usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText; - - if (usageText != "") - { - //Add a separator - usageText += " (Buttons)"; - } - - if (usageText != "") - { - //Add a separator - usageText += ", "; - } - - usageText += GetDirectionPadState().ToString(); - - foreach (KeyValuePair entry in UsageValues) - { - if (entry.Key.IsRange) - { - continue; - } - - Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage); - if (usageType == null) - { - //TODO: check why this is happening on Logitech rumble gamepad 2. - //Probably some of our axis are hiding in there. - continue; - } - string name = Enum.GetName(usageType, entry.Key.NotRange.Usage); - - if (usageText != "") - { - //Add a separator - usageText += ", "; - } - usageText += entry.Value.ToString("X") + " ("+ name +")"; - } - } - - //Now create our list item - ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") }); - return item; - } - - } - -} \ No newline at end of file