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