Now using SharpLib.Hid and SharpLib.Win32 as namespaces.
2 // Copyright (C) 2014-2015 Stéphane Lenclud.
4 // This file is part of SharpLibHid.
6 // SharpDisplayManager is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
11 // SharpDisplayManager is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with SharpDisplayManager. If not, see <http://www.gnu.org/licenses/>.
22 using System.Windows.Forms;
23 using System.Runtime.InteropServices;
24 using System.Diagnostics;
26 using Microsoft.Win32.SafeHandles;
28 using System.Collections.Generic;
30 using SharpLib.Hid.Usage;
33 namespace SharpLib.Hid
36 /// We provide utility functions to interpret gamepad dpad state.
38 public enum DirectionPadState
52 /// Represent a HID event.
53 /// TODO: Rename this into HidRawInput?
55 public class HidEvent : IDisposable
57 public bool IsValid { get; private set; }
58 public bool IsForeground { get; private set; }
59 public bool IsBackground { get { return !IsForeground; } }
60 public bool IsMouse { get; private set; }
61 public bool IsKeyboard { get; private set; }
63 /// If this not a mouse or keyboard event then it's a generic HID event.
65 public bool IsGeneric { get; private set; }
66 public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
67 public bool IsButtonUp { get { return Usages.Count == 0; } }
68 public bool IsRepeat { get { return RepeatCount != 0; } }
69 public uint RepeatCount { get; private set; }
71 public HidDevice Device { get; private set; }
72 public RAWINPUT RawInput { get {return iRawInput;} }
73 private RAWINPUT iRawInput;
75 public ushort UsagePage { get; private set; }
76 public ushort UsageCollection { get; private set; }
77 public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
78 public List<ushort> Usages { get; private set; }
80 /// Sorted in the same order as Device.InputValueCapabilities.
82 public Dictionary<HIDP_VALUE_CAPS,uint> UsageValues { get; private set; }
83 //TODO: We need a collection of input report
84 public byte[] InputReport { get; private set; }
86 public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
87 public event HidEventRepeatDelegate OnHidEventRepeat;
89 private System.Timers.Timer Timer { get; set; }
90 public DateTime Time { get; private set; }
91 public DateTime OriginalTime { get; private set; }
93 //Compute repeat delay and speed based on system settings
94 //Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
95 private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
96 private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
99 /// Tells whether this event has already been disposed of.
101 public bool IsStray { get { return Timer == null; } }
104 /// We typically dispose of events as soon as we get the corresponding key up signal.
106 public void Dispose()
108 Timer.Enabled = false;
110 //Mark this event as a stray
115 /// Initialize an HidEvent from a WM_INPUT message
117 /// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
118 public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
126 OriginalTime = DateTime.Now;
127 Timer = new System.Timers.Timer();
128 Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
129 Usages = new List<ushort>();
130 UsageValues = new Dictionary<HIDP_VALUE_CAPS,uint>();
131 OnHidEventRepeat += aRepeatDelegate;
133 if (aMessage.Msg != Const.WM_INPUT)
135 //Has to be a WM_INPUT message
139 if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
143 else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
145 IsForeground = false;
148 //Declare some pointers
149 IntPtr rawInputBuffer = IntPtr.Zero;
154 iRawInput = new RAWINPUT();
155 if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref iRawInput, ref rawInputBuffer))
157 Debug.WriteLine("GetRawInputData failed!");
161 //Our device can actually be null.
162 //This is notably happening for some keyboard events
163 if (RawInput.header.hDevice != IntPtr.Zero)
165 //Get various information about this HID device
166 Device = new HidDevice(RawInput.header.hDevice);
169 if (RawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID) //Check that our raw input is HID
173 Debug.WriteLine("WM_INPUT source device is HID.");
174 //Get Usage Page and Usage
175 //Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
176 UsagePage = Device.Info.hid.usUsagePage;
177 UsageCollection = Device.Info.hid.usUsage;
179 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
180 && RawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
185 //Allocate a buffer for one HID input
186 InputReport = new byte[RawInput.hid.dwSizeHid];
188 Debug.WriteLine("Raw input contains " + RawInput.hid.dwCount + " HID input report(s)");
190 //For each HID input report in our raw input
191 for (int i = 0; i < RawInput.hid.dwCount; i++)
193 //Compute the address from which to copy our HID input
194 int hidInputOffset = 0;
197 byte* source = (byte*)rawInputBuffer;
198 source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (RawInput.hid.dwSizeHid * i);
199 hidInputOffset = (int)source;
202 //Copy HID input into our buffer
203 Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid);
205 ProcessInputReport(InputReport);
208 else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
212 Debug.WriteLine("WM_INPUT source device is Mouse.");
213 // do mouse handling...
215 else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
219 Debug.WriteLine("WM_INPUT source device is Keyboard.");
220 // do keyboard handling...
223 Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
224 Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
225 Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
226 Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
227 Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
228 Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
234 //Always executed when leaving our try block
235 Marshal.FreeHGlobal(rawInputBuffer);
241 //TODO: Make this optional
242 //StartRepeatTimer(iRepeatDelay);
251 private void ProcessInputReport(byte[] aInputReport)
253 //Print HID input report in our debug output
254 //string hidDump = "HID input report: " + InputReportString();
255 //Debug.WriteLine(hidDump);
257 //Get all our usages, those are typically the buttons currently pushed on a gamepad.
258 //For a remote control it's usually just the one button that was pushed.
259 GetUsages(aInputReport);
261 //Now process direction pad (d-pad, dpad) and axes
262 GetUsageValues(aInputReport);
266 /// Typically fetches values of a joystick/gamepad axis and dpad directions.
268 /// <param name="aInputReport"></param>
269 private void GetUsageValues(byte[] aInputReport)
271 if (Device.InputValueCapabilities == null)
276 foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
280 //What should we do with those guys?
284 //Now fetch and add our usage value
286 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);
287 if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS)
289 UsageValues[caps]=usageValue;
295 /// Get all our usages, those are typically the buttons currently pushed on a gamepad.
296 /// For a remote control it's usually just the one button that was pushed.
298 private void GetUsages(byte[] aInputReport)
300 //Do proper parsing of our HID report
301 //First query our usage count
303 Win32.USAGE_AND_PAGE[] usages = null;
304 Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
305 if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
307 //Allocate a large enough buffer
308 usages = new Win32.USAGE_AND_PAGE[usageCount];
309 //...and fetch our usages
310 status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
311 if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
313 Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
316 else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
318 Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
321 Debug.WriteLine("Usage count: " + usageCount.ToString());
323 //Copy usages into this event
326 foreach (USAGE_AND_PAGE up in usages)
328 //Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
329 //Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
330 //Add this usage to our list
331 Usages.Add(up.Usage);
339 /// <param name="aUsagePage"></param>
340 /// <param name="Usage"></param>
341 /// <returns></returns>
342 public uint GetUsageValue(ushort aUsagePage, ushort aUsage)
344 foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
348 //What should we do with those guys?
352 //Check if we have a match
353 if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
355 return UsageValues[caps];
365 /// <param name="aUsagePage"></param>
366 /// <param name="aUsage"></param>
367 /// <returns></returns>
368 public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage)
371 foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
376 //What should we do with those guys?
380 //Check if we have a match
381 if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
392 /// TODO: Move this to another level?
394 /// <param name="aInterval"></param>
395 public void StartRepeatTimer(double aInterval)
401 Timer.Enabled = false;
402 //Initial delay do not use auto reset
403 //After our initial delay however we do setup our timer one more time using auto reset
404 Timer.AutoReset = (RepeatCount != 0);
405 Timer.Interval = aInterval;
406 Timer.Enabled = true;
409 static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
411 if (aHidEvent.IsStray)
413 //Skip events if canceled
417 aHidEvent.RepeatCount++;
418 aHidEvent.Time = DateTime.Now;
419 if (aHidEvent.RepeatCount == 1)
421 //Re-Start our timer only after the initial delay
422 aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
425 //Broadcast our repeat event
426 aHidEvent.OnHidEventRepeat(aHidEvent);
430 /// Provide the state of the dpad or hat switch if any.
431 /// If no dpad is found we return 'at rest'.
433 /// <returns></returns>
434 public DirectionPadState GetDirectionPadState()
436 int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)GenericDesktop.HatSwitch);
439 //No hat switch found
440 return DirectionPadState.Rest;
443 HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index];
447 return DirectionPadState.Rest;
450 uint dpadUsageValue = UsageValues[caps];
452 if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax)
454 //Out of range means at rest
455 return DirectionPadState.Rest;
458 //Normalize value to start at zero
459 //TODO: more error check here?
460 DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin);
465 /// Print information about this device to our debug output.
467 public void DebugWrite()
471 Debug.WriteLine("==== Invalid HidEvent");
480 if (IsGeneric) Debug.WriteLine("==== Generic");
481 if (IsKeyboard) Debug.WriteLine("==== Keyboard");
482 if (IsMouse) Debug.WriteLine("==== Mouse");
483 Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
484 Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
485 Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
486 Debug.WriteLine("==== InputReport: 0x" + InputReportString());
487 foreach (ushort usage in Usages)
489 Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
496 /// <returns></returns>
497 public string InputReportString()
499 if (InputReport == null)
505 foreach (byte b in InputReport)
507 hidDump += b.ToString("X2");
514 /// Create a list view item describing this HidEvent
516 /// <returns></returns>
517 public ListViewItem ToListViewItem()
519 string usageText = "";
521 foreach (ushort usage in Usages)
529 UsagePage usagePage = (UsagePage)UsagePage;
532 case Hid.UsagePage.Consumer:
533 usageText += ((ConsumerControl)usage).ToString();
536 case Hid.UsagePage.WindowsMediaCenterRemoteControl:
537 usageText += ((WindowsMediaCenterRemoteControl)usage).ToString();
541 usageText += usage.ToString("X2");
546 //If we are a gamepad display axis and dpad values
547 if (Device.IsGamePad)
549 //uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
550 //usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText;
555 usageText += " (Buttons)";
564 usageText += GetDirectionPadState().ToString();
566 foreach (KeyValuePair<HIDP_VALUE_CAPS, uint> entry in UsageValues)
568 if (entry.Key.IsRange)
573 Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage);
574 if (usageType == null)
576 //TODO: check why this is happening on Logitech rumble gamepad 2.
577 //Probably some of our axis are hiding in there.
580 string name = Enum.GetName(usageType, entry.Key.NotRange.Usage);
587 usageText += entry.Value.ToString("X") + " ("+ name +")";
591 //Now create our list item
592 ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });