StephaneLenclud@76
|
1 |
//
|
StephaneLenclud@76
|
2 |
// Copyright (C) 2014-2015 Stéphane Lenclud.
|
StephaneLenclud@76
|
3 |
//
|
StephaneLenclud@76
|
4 |
// This file is part of SharpLibHid.
|
StephaneLenclud@76
|
5 |
//
|
StephaneLenclud@76
|
6 |
// SharpDisplayManager is free software: you can redistribute it and/or modify
|
StephaneLenclud@76
|
7 |
// it under the terms of the GNU General Public License as published by
|
StephaneLenclud@76
|
8 |
// the Free Software Foundation, either version 3 of the License, or
|
StephaneLenclud@76
|
9 |
// (at your option) any later version.
|
StephaneLenclud@76
|
10 |
//
|
StephaneLenclud@76
|
11 |
// SharpDisplayManager is distributed in the hope that it will be useful,
|
StephaneLenclud@76
|
12 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
StephaneLenclud@76
|
13 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
StephaneLenclud@76
|
14 |
// GNU General Public License for more details.
|
StephaneLenclud@76
|
15 |
//
|
StephaneLenclud@76
|
16 |
// You should have received a copy of the GNU General Public License
|
StephaneLenclud@76
|
17 |
// along with SharpDisplayManager. If not, see <http://www.gnu.org/licenses/>.
|
StephaneLenclud@76
|
18 |
//
|
StephaneLenclud@76
|
19 |
|
StephaneLenclud@76
|
20 |
|
sl@27
|
21 |
using System;
|
sl@27
|
22 |
using System.Windows.Forms;
|
sl@27
|
23 |
using System.Runtime.InteropServices;
|
sl@27
|
24 |
using System.Diagnostics;
|
sl@27
|
25 |
using System.Text;
|
sl@27
|
26 |
using Microsoft.Win32.SafeHandles;
|
StephaneLenclud@77
|
27 |
using SharpLib.Win32;
|
sl@27
|
28 |
using System.Collections.Generic;
|
sl@41
|
29 |
using System.Timers;
|
StephaneLenclud@77
|
30 |
using SharpLib.Hid.Usage;
|
sl@27
|
31 |
|
sl@27
|
32 |
|
StephaneLenclud@77
|
33 |
namespace SharpLib.Hid
|
sl@27
|
34 |
{
|
sl@27
|
35 |
/// <summary>
|
StephaneLenclud@73
|
36 |
/// We provide utility functions to interpret gamepad dpad state.
|
StephaneLenclud@73
|
37 |
/// </summary>
|
StephaneLenclud@73
|
38 |
public enum DirectionPadState
|
StephaneLenclud@73
|
39 |
{
|
StephaneLenclud@73
|
40 |
Rest=-1,
|
StephaneLenclud@73
|
41 |
Up=0,
|
StephaneLenclud@73
|
42 |
UpRight=1,
|
StephaneLenclud@73
|
43 |
Right=2,
|
StephaneLenclud@73
|
44 |
DownRight=3,
|
StephaneLenclud@73
|
45 |
Down=4,
|
StephaneLenclud@73
|
46 |
DownLeft=5,
|
StephaneLenclud@73
|
47 |
Left=6,
|
StephaneLenclud@73
|
48 |
UpLeft=7
|
StephaneLenclud@73
|
49 |
}
|
StephaneLenclud@73
|
50 |
|
StephaneLenclud@73
|
51 |
/// <summary>
|
sl@27
|
52 |
/// Represent a HID event.
|
StephaneLenclud@54
|
53 |
/// TODO: Rename this into HidRawInput?
|
sl@27
|
54 |
/// </summary>
|
StephaneLenclud@54
|
55 |
public class HidEvent : IDisposable
|
sl@27
|
56 |
{
|
sl@27
|
57 |
public bool IsValid { get; private set; }
|
StephaneLenclud@54
|
58 |
public bool IsForeground { get; private set; }
|
StephaneLenclud@54
|
59 |
public bool IsBackground { get { return !IsForeground; } }
|
sl@27
|
60 |
public bool IsMouse { get; private set; }
|
sl@27
|
61 |
public bool IsKeyboard { get; private set; }
|
StephaneLenclud@72
|
62 |
/// <summary>
|
StephaneLenclud@72
|
63 |
/// If this not a mouse or keyboard event then it's a generic HID event.
|
StephaneLenclud@72
|
64 |
/// </summary>
|
sl@27
|
65 |
public bool IsGeneric { get; private set; }
|
sl@29
|
66 |
public bool IsButtonDown { get { return Usages.Count == 1 && Usages[0] != 0; } }
|
StephaneLenclud@49
|
67 |
public bool IsButtonUp { get { return Usages.Count == 0; } }
|
sl@44
|
68 |
public bool IsRepeat { get { return RepeatCount != 0; } }
|
sl@44
|
69 |
public uint RepeatCount { get; private set; }
|
sl@27
|
70 |
|
sl@27
|
71 |
public HidDevice Device { get; private set; }
|
StephaneLenclud@72
|
72 |
public RAWINPUT RawInput { get {return iRawInput;} }
|
StephaneLenclud@72
|
73 |
private RAWINPUT iRawInput;
|
sl@27
|
74 |
|
sl@27
|
75 |
public ushort UsagePage { get; private set; }
|
sl@27
|
76 |
public ushort UsageCollection { get; private set; }
|
sl@31
|
77 |
public uint UsageId { get { return ((uint)UsagePage << 16 | (uint)UsageCollection); } }
|
StephaneLenclud@49
|
78 |
public List<ushort> Usages { get; private set; }
|
StephaneLenclud@73
|
79 |
/// <summary>
|
StephaneLenclud@73
|
80 |
/// Sorted in the same order as Device.InputValueCapabilities.
|
StephaneLenclud@73
|
81 |
/// </summary>
|
StephaneLenclud@73
|
82 |
public Dictionary<HIDP_VALUE_CAPS,uint> UsageValues { get; private set; }
|
StephaneLenclud@54
|
83 |
//TODO: We need a collection of input report
|
StephaneLenclud@54
|
84 |
public byte[] InputReport { get; private set; }
|
StephaneLenclud@54
|
85 |
//
|
StephaneLenclud@54
|
86 |
public delegate void HidEventRepeatDelegate(HidEvent aHidEvent);
|
sl@41
|
87 |
public event HidEventRepeatDelegate OnHidEventRepeat;
|
sl@27
|
88 |
|
sl@41
|
89 |
private System.Timers.Timer Timer { get; set; }
|
sl@44
|
90 |
public DateTime Time { get; private set; }
|
sl@44
|
91 |
public DateTime OriginalTime { get; private set; }
|
sl@41
|
92 |
|
sl@43
|
93 |
//Compute repeat delay and speed based on system settings
|
sl@43
|
94 |
//Those computations were taken from the Petzold here: ftp://ftp.charlespetzold.com/ProgWinForms/4%20Custom%20Controls/NumericScan/NumericScan/ClickmaticButton.cs
|
sl@43
|
95 |
private int iRepeatDelay = 250 * (1 + SystemInformation.KeyboardDelay);
|
sl@43
|
96 |
private int iRepeatSpeed = 405 - 12 * SystemInformation.KeyboardSpeed;
|
sl@43
|
97 |
|
sl@42
|
98 |
/// <summary>
|
sl@42
|
99 |
/// Tells whether this event has already been disposed of.
|
sl@42
|
100 |
/// </summary>
|
sl@42
|
101 |
public bool IsStray { get { return Timer == null; } }
|
sl@42
|
102 |
|
sl@43
|
103 |
/// <summary>
|
sl@43
|
104 |
/// We typically dispose of events as soon as we get the corresponding key up signal.
|
sl@43
|
105 |
/// </summary>
|
sl@41
|
106 |
public void Dispose()
|
sl@41
|
107 |
{
|
sl@41
|
108 |
Timer.Enabled = false;
|
sl@41
|
109 |
Timer.Dispose();
|
sl@43
|
110 |
//Mark this event as a stray
|
sl@41
|
111 |
Timer = null;
|
sl@41
|
112 |
}
|
sl@27
|
113 |
|
sl@27
|
114 |
/// <summary>
|
sl@27
|
115 |
/// Initialize an HidEvent from a WM_INPUT message
|
sl@27
|
116 |
/// </summary>
|
sl@27
|
117 |
/// <param name="hRawInputDevice">Device Handle as provided by RAWINPUTHEADER.hDevice, typically accessed as rawinput.header.hDevice</param>
|
sl@41
|
118 |
public HidEvent(Message aMessage, HidEventRepeatDelegate aRepeatDelegate)
|
sl@27
|
119 |
{
|
sl@44
|
120 |
RepeatCount = 0;
|
sl@27
|
121 |
IsValid = false;
|
sl@27
|
122 |
IsKeyboard = false;
|
sl@27
|
123 |
IsGeneric = false;
|
StephaneLenclud@54
|
124 |
|
sl@44
|
125 |
Time = DateTime.Now;
|
sl@44
|
126 |
OriginalTime = DateTime.Now;
|
sl@41
|
127 |
Timer = new System.Timers.Timer();
|
sl@44
|
128 |
Timer.Elapsed += (sender, e) => OnRepeatTimerElapsed(sender, e, this);
|
sl@27
|
129 |
Usages = new List<ushort>();
|
StephaneLenclud@73
|
130 |
UsageValues = new Dictionary<HIDP_VALUE_CAPS,uint>();
|
sl@41
|
131 |
OnHidEventRepeat += aRepeatDelegate;
|
sl@27
|
132 |
|
sl@27
|
133 |
if (aMessage.Msg != Const.WM_INPUT)
|
sl@27
|
134 |
{
|
sl@27
|
135 |
//Has to be a WM_INPUT message
|
sl@27
|
136 |
return;
|
sl@27
|
137 |
}
|
sl@27
|
138 |
|
sl@27
|
139 |
if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUT)
|
sl@27
|
140 |
{
|
sl@27
|
141 |
IsForeground = true;
|
sl@27
|
142 |
}
|
sl@27
|
143 |
else if (Macro.GET_RAWINPUT_CODE_WPARAM(aMessage.WParam) == Const.RIM_INPUTSINK)
|
sl@27
|
144 |
{
|
sl@27
|
145 |
IsForeground = false;
|
sl@27
|
146 |
}
|
sl@27
|
147 |
|
sl@27
|
148 |
//Declare some pointers
|
sl@27
|
149 |
IntPtr rawInputBuffer = IntPtr.Zero;
|
sl@27
|
150 |
|
sl@27
|
151 |
try
|
sl@27
|
152 |
{
|
sl@27
|
153 |
//Fetch raw input
|
StephaneLenclud@72
|
154 |
iRawInput = new RAWINPUT();
|
StephaneLenclud@72
|
155 |
if (!Win32.RawInput.GetRawInputData(aMessage.LParam, ref iRawInput, ref rawInputBuffer))
|
sl@27
|
156 |
{
|
StephaneLenclud@70
|
157 |
Debug.WriteLine("GetRawInputData failed!");
|
sl@27
|
158 |
return;
|
sl@27
|
159 |
}
|
sl@27
|
160 |
|
StephaneLenclud@70
|
161 |
//Our device can actually be null.
|
StephaneLenclud@70
|
162 |
//This is notably happening for some keyboard events
|
StephaneLenclud@72
|
163 |
if (RawInput.header.hDevice != IntPtr.Zero)
|
StephaneLenclud@70
|
164 |
{
|
StephaneLenclud@70
|
165 |
//Get various information about this HID device
|
StephaneLenclud@76
|
166 |
Device = new HidDevice(RawInput.header.hDevice);
|
StephaneLenclud@70
|
167 |
}
|
sl@27
|
168 |
|
StephaneLenclud@72
|
169 |
if (RawInput.header.dwType == Win32.RawInputDeviceType.RIM_TYPEHID) //Check that our raw input is HID
|
sl@27
|
170 |
{
|
sl@27
|
171 |
IsGeneric = true;
|
sl@27
|
172 |
|
sl@27
|
173 |
Debug.WriteLine("WM_INPUT source device is HID.");
|
sl@27
|
174 |
//Get Usage Page and Usage
|
sl@27
|
175 |
//Debug.WriteLine("Usage Page: 0x" + deviceInfo.hid.usUsagePage.ToString("X4") + " Usage ID: 0x" + deviceInfo.hid.usUsage.ToString("X4"));
|
StephaneLenclud@53
|
176 |
UsagePage = Device.Info.hid.usUsagePage;
|
StephaneLenclud@53
|
177 |
UsageCollection = Device.Info.hid.usUsage;
|
sl@27
|
178 |
|
StephaneLenclud@72
|
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
|
StephaneLenclud@72
|
180 |
&& RawInput.hid.dwCount > 0)) //Check that we have at least one HID msg
|
sl@27
|
181 |
{
|
sl@27
|
182 |
return;
|
sl@27
|
183 |
}
|
sl@27
|
184 |
|
sl@27
|
185 |
//Allocate a buffer for one HID input
|
StephaneLenclud@72
|
186 |
InputReport = new byte[RawInput.hid.dwSizeHid];
|
sl@27
|
187 |
|
StephaneLenclud@72
|
188 |
Debug.WriteLine("Raw input contains " + RawInput.hid.dwCount + " HID input report(s)");
|
sl@27
|
189 |
|
sl@27
|
190 |
//For each HID input report in our raw input
|
StephaneLenclud@72
|
191 |
for (int i = 0; i < RawInput.hid.dwCount; i++)
|
sl@27
|
192 |
{
|
sl@27
|
193 |
//Compute the address from which to copy our HID input
|
sl@27
|
194 |
int hidInputOffset = 0;
|
sl@27
|
195 |
unsafe
|
sl@27
|
196 |
{
|
sl@27
|
197 |
byte* source = (byte*)rawInputBuffer;
|
StephaneLenclud@72
|
198 |
source += sizeof(RAWINPUTHEADER) + sizeof(RAWHID) + (RawInput.hid.dwSizeHid * i);
|
sl@27
|
199 |
hidInputOffset = (int)source;
|
sl@27
|
200 |
}
|
sl@27
|
201 |
|
sl@27
|
202 |
//Copy HID input into our buffer
|
StephaneLenclud@72
|
203 |
Marshal.Copy(new IntPtr(hidInputOffset), InputReport, 0, (int)RawInput.hid.dwSizeHid);
|
StephaneLenclud@73
|
204 |
//
|
StephaneLenclud@73
|
205 |
ProcessInputReport(InputReport);
|
sl@27
|
206 |
}
|
sl@27
|
207 |
}
|
StephaneLenclud@72
|
208 |
else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEMOUSE)
|
sl@27
|
209 |
{
|
sl@27
|
210 |
IsMouse = true;
|
sl@27
|
211 |
|
StephaneLenclud@54
|
212 |
Debug.WriteLine("WM_INPUT source device is Mouse.");
|
sl@27
|
213 |
// do mouse handling...
|
sl@27
|
214 |
}
|
StephaneLenclud@72
|
215 |
else if (RawInput.header.dwType == RawInputDeviceType.RIM_TYPEKEYBOARD)
|
sl@27
|
216 |
{
|
sl@27
|
217 |
IsKeyboard = true;
|
sl@27
|
218 |
|
sl@27
|
219 |
Debug.WriteLine("WM_INPUT source device is Keyboard.");
|
sl@27
|
220 |
// do keyboard handling...
|
StephaneLenclud@70
|
221 |
if (Device != null)
|
StephaneLenclud@70
|
222 |
{
|
StephaneLenclud@70
|
223 |
Debug.WriteLine("Type: " + Device.Info.keyboard.dwType.ToString());
|
StephaneLenclud@70
|
224 |
Debug.WriteLine("SubType: " + Device.Info.keyboard.dwSubType.ToString());
|
StephaneLenclud@70
|
225 |
Debug.WriteLine("Mode: " + Device.Info.keyboard.dwKeyboardMode.ToString());
|
StephaneLenclud@70
|
226 |
Debug.WriteLine("Number of function keys: " + Device.Info.keyboard.dwNumberOfFunctionKeys.ToString());
|
StephaneLenclud@70
|
227 |
Debug.WriteLine("Number of indicators: " + Device.Info.keyboard.dwNumberOfIndicators.ToString());
|
StephaneLenclud@70
|
228 |
Debug.WriteLine("Number of keys total: " + Device.Info.keyboard.dwNumberOfKeysTotal.ToString());
|
StephaneLenclud@70
|
229 |
}
|
sl@27
|
230 |
}
|
sl@27
|
231 |
}
|
sl@27
|
232 |
finally
|
sl@27
|
233 |
{
|
sl@27
|
234 |
//Always executed when leaving our try block
|
sl@27
|
235 |
Marshal.FreeHGlobal(rawInputBuffer);
|
sl@27
|
236 |
}
|
sl@27
|
237 |
|
sl@47
|
238 |
//
|
sl@47
|
239 |
if (IsButtonDown)
|
sl@41
|
240 |
{
|
StephaneLenclud@54
|
241 |
//TODO: Make this optional
|
StephaneLenclud@63
|
242 |
//StartRepeatTimer(iRepeatDelay);
|
sl@41
|
243 |
}
|
StephaneLenclud@54
|
244 |
|
sl@27
|
245 |
IsValid = true;
|
sl@27
|
246 |
}
|
sl@27
|
247 |
|
StephaneLenclud@73
|
248 |
/// <summary>
|
StephaneLenclud@73
|
249 |
///
|
StephaneLenclud@73
|
250 |
/// </summary>
|
StephaneLenclud@73
|
251 |
private void ProcessInputReport(byte[] aInputReport)
|
StephaneLenclud@73
|
252 |
{
|
StephaneLenclud@73
|
253 |
//Print HID input report in our debug output
|
StephaneLenclud@73
|
254 |
//string hidDump = "HID input report: " + InputReportString();
|
StephaneLenclud@73
|
255 |
//Debug.WriteLine(hidDump);
|
StephaneLenclud@73
|
256 |
|
StephaneLenclud@73
|
257 |
//Get all our usages, those are typically the buttons currently pushed on a gamepad.
|
StephaneLenclud@73
|
258 |
//For a remote control it's usually just the one button that was pushed.
|
StephaneLenclud@73
|
259 |
GetUsages(aInputReport);
|
StephaneLenclud@73
|
260 |
|
StephaneLenclud@73
|
261 |
//Now process direction pad (d-pad, dpad) and axes
|
StephaneLenclud@73
|
262 |
GetUsageValues(aInputReport);
|
StephaneLenclud@73
|
263 |
}
|
StephaneLenclud@73
|
264 |
|
StephaneLenclud@73
|
265 |
/// <summary>
|
StephaneLenclud@73
|
266 |
/// Typically fetches values of a joystick/gamepad axis and dpad directions.
|
StephaneLenclud@73
|
267 |
/// </summary>
|
StephaneLenclud@73
|
268 |
/// <param name="aInputReport"></param>
|
StephaneLenclud@73
|
269 |
private void GetUsageValues(byte[] aInputReport)
|
StephaneLenclud@73
|
270 |
{
|
StephaneLenclud@73
|
271 |
if (Device.InputValueCapabilities == null)
|
StephaneLenclud@73
|
272 |
{
|
StephaneLenclud@73
|
273 |
return;
|
StephaneLenclud@73
|
274 |
}
|
StephaneLenclud@73
|
275 |
|
StephaneLenclud@73
|
276 |
foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
|
StephaneLenclud@73
|
277 |
{
|
StephaneLenclud@73
|
278 |
if (caps.IsRange)
|
StephaneLenclud@73
|
279 |
{
|
StephaneLenclud@73
|
280 |
//What should we do with those guys?
|
StephaneLenclud@73
|
281 |
continue;
|
StephaneLenclud@73
|
282 |
}
|
StephaneLenclud@73
|
283 |
|
StephaneLenclud@73
|
284 |
//Now fetch and add our usage value
|
StephaneLenclud@73
|
285 |
uint usageValue = 0;
|
StephaneLenclud@73
|
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);
|
StephaneLenclud@73
|
287 |
if (status == Win32.HidStatus.HIDP_STATUS_SUCCESS)
|
StephaneLenclud@73
|
288 |
{
|
StephaneLenclud@73
|
289 |
UsageValues[caps]=usageValue;
|
StephaneLenclud@73
|
290 |
}
|
StephaneLenclud@73
|
291 |
}
|
StephaneLenclud@73
|
292 |
}
|
StephaneLenclud@73
|
293 |
|
StephaneLenclud@73
|
294 |
/// <summary>
|
StephaneLenclud@73
|
295 |
/// Get all our usages, those are typically the buttons currently pushed on a gamepad.
|
StephaneLenclud@73
|
296 |
/// For a remote control it's usually just the one button that was pushed.
|
StephaneLenclud@73
|
297 |
/// </summary>
|
StephaneLenclud@73
|
298 |
private void GetUsages(byte[] aInputReport)
|
StephaneLenclud@73
|
299 |
{
|
StephaneLenclud@73
|
300 |
//Do proper parsing of our HID report
|
StephaneLenclud@73
|
301 |
//First query our usage count
|
StephaneLenclud@73
|
302 |
uint usageCount = 0;
|
StephaneLenclud@73
|
303 |
Win32.USAGE_AND_PAGE[] usages = null;
|
StephaneLenclud@73
|
304 |
Win32.HidStatus status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
|
StephaneLenclud@73
|
305 |
if (status == Win32.HidStatus.HIDP_STATUS_BUFFER_TOO_SMALL)
|
StephaneLenclud@73
|
306 |
{
|
StephaneLenclud@73
|
307 |
//Allocate a large enough buffer
|
StephaneLenclud@73
|
308 |
usages = new Win32.USAGE_AND_PAGE[usageCount];
|
StephaneLenclud@73
|
309 |
//...and fetch our usages
|
StephaneLenclud@73
|
310 |
status = Win32.Function.HidP_GetUsagesEx(Win32.HIDP_REPORT_TYPE.HidP_Input, 0, usages, ref usageCount, Device.PreParsedData, aInputReport, (uint)aInputReport.Length);
|
StephaneLenclud@73
|
311 |
if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
|
StephaneLenclud@73
|
312 |
{
|
StephaneLenclud@73
|
313 |
Debug.WriteLine("Second pass could not parse HID data: " + status.ToString());
|
StephaneLenclud@73
|
314 |
}
|
StephaneLenclud@73
|
315 |
}
|
StephaneLenclud@73
|
316 |
else if (status != Win32.HidStatus.HIDP_STATUS_SUCCESS)
|
StephaneLenclud@73
|
317 |
{
|
StephaneLenclud@73
|
318 |
Debug.WriteLine("First pass could not parse HID data: " + status.ToString());
|
StephaneLenclud@73
|
319 |
}
|
StephaneLenclud@73
|
320 |
|
StephaneLenclud@73
|
321 |
Debug.WriteLine("Usage count: " + usageCount.ToString());
|
StephaneLenclud@73
|
322 |
|
StephaneLenclud@73
|
323 |
//Copy usages into this event
|
StephaneLenclud@73
|
324 |
if (usages != null)
|
StephaneLenclud@73
|
325 |
{
|
StephaneLenclud@73
|
326 |
foreach (USAGE_AND_PAGE up in usages)
|
StephaneLenclud@73
|
327 |
{
|
StephaneLenclud@73
|
328 |
//Debug.WriteLine("UsagePage: 0x" + usages[0].UsagePage.ToString("X4"));
|
StephaneLenclud@73
|
329 |
//Debug.WriteLine("Usage: 0x" + usages[0].Usage.ToString("X4"));
|
StephaneLenclud@73
|
330 |
//Add this usage to our list
|
StephaneLenclud@73
|
331 |
Usages.Add(up.Usage);
|
StephaneLenclud@73
|
332 |
}
|
StephaneLenclud@73
|
333 |
}
|
StephaneLenclud@73
|
334 |
}
|
StephaneLenclud@73
|
335 |
|
StephaneLenclud@73
|
336 |
/// <summary>
|
StephaneLenclud@73
|
337 |
///
|
StephaneLenclud@73
|
338 |
/// </summary>
|
StephaneLenclud@73
|
339 |
/// <param name="aUsagePage"></param>
|
StephaneLenclud@73
|
340 |
/// <param name="Usage"></param>
|
StephaneLenclud@73
|
341 |
/// <returns></returns>
|
StephaneLenclud@73
|
342 |
public uint GetUsageValue(ushort aUsagePage, ushort aUsage)
|
StephaneLenclud@73
|
343 |
{
|
StephaneLenclud@73
|
344 |
foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
|
StephaneLenclud@73
|
345 |
{
|
StephaneLenclud@73
|
346 |
if (caps.IsRange)
|
StephaneLenclud@73
|
347 |
{
|
StephaneLenclud@73
|
348 |
//What should we do with those guys?
|
StephaneLenclud@73
|
349 |
continue;
|
StephaneLenclud@73
|
350 |
}
|
StephaneLenclud@73
|
351 |
|
StephaneLenclud@73
|
352 |
//Check if we have a match
|
StephaneLenclud@73
|
353 |
if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
|
StephaneLenclud@73
|
354 |
{
|
StephaneLenclud@73
|
355 |
return UsageValues[caps];
|
StephaneLenclud@73
|
356 |
}
|
StephaneLenclud@73
|
357 |
}
|
StephaneLenclud@73
|
358 |
|
StephaneLenclud@73
|
359 |
return 0;
|
StephaneLenclud@73
|
360 |
}
|
StephaneLenclud@73
|
361 |
|
StephaneLenclud@73
|
362 |
/// <summary>
|
StephaneLenclud@73
|
363 |
///
|
StephaneLenclud@73
|
364 |
/// </summary>
|
StephaneLenclud@73
|
365 |
/// <param name="aUsagePage"></param>
|
StephaneLenclud@73
|
366 |
/// <param name="aUsage"></param>
|
StephaneLenclud@73
|
367 |
/// <returns></returns>
|
StephaneLenclud@73
|
368 |
public int GetValueCapabilitiesIndex(ushort aUsagePage, ushort aUsage)
|
StephaneLenclud@73
|
369 |
{
|
StephaneLenclud@73
|
370 |
int i = -1;
|
StephaneLenclud@73
|
371 |
foreach (HIDP_VALUE_CAPS caps in Device.InputValueCapabilities)
|
StephaneLenclud@73
|
372 |
{
|
StephaneLenclud@73
|
373 |
i++;
|
StephaneLenclud@73
|
374 |
if (caps.IsRange)
|
StephaneLenclud@73
|
375 |
{
|
StephaneLenclud@73
|
376 |
//What should we do with those guys?
|
StephaneLenclud@73
|
377 |
continue;
|
StephaneLenclud@73
|
378 |
}
|
StephaneLenclud@73
|
379 |
|
StephaneLenclud@73
|
380 |
//Check if we have a match
|
StephaneLenclud@73
|
381 |
if (caps.UsagePage == aUsagePage && caps.NotRange.Usage == aUsage)
|
StephaneLenclud@73
|
382 |
{
|
StephaneLenclud@73
|
383 |
return i;
|
StephaneLenclud@73
|
384 |
}
|
StephaneLenclud@73
|
385 |
}
|
StephaneLenclud@73
|
386 |
|
StephaneLenclud@73
|
387 |
return i;
|
StephaneLenclud@73
|
388 |
}
|
StephaneLenclud@73
|
389 |
|
StephaneLenclud@73
|
390 |
|
StephaneLenclud@73
|
391 |
/// <summary>
|
StephaneLenclud@73
|
392 |
/// TODO: Move this to another level?
|
StephaneLenclud@73
|
393 |
/// </summary>
|
StephaneLenclud@73
|
394 |
/// <param name="aInterval"></param>
|
sl@41
|
395 |
public void StartRepeatTimer(double aInterval)
|
sl@41
|
396 |
{
|
sl@41
|
397 |
if (Timer == null)
|
sl@41
|
398 |
{
|
sl@41
|
399 |
return;
|
sl@41
|
400 |
}
|
sl@41
|
401 |
Timer.Enabled = false;
|
sl@44
|
402 |
//Initial delay do not use auto reset
|
sl@44
|
403 |
//After our initial delay however we do setup our timer one more time using auto reset
|
StephaneLenclud@54
|
404 |
Timer.AutoReset = (RepeatCount != 0);
|
StephaneLenclud@54
|
405 |
Timer.Interval = aInterval;
|
StephaneLenclud@54
|
406 |
Timer.Enabled = true;
|
sl@41
|
407 |
}
|
sl@27
|
408 |
|
sl@44
|
409 |
static private void OnRepeatTimerElapsed(object sender, ElapsedEventArgs e, HidEvent aHidEvent)
|
sl@41
|
410 |
{
|
sl@42
|
411 |
if (aHidEvent.IsStray)
|
sl@42
|
412 |
{
|
sl@42
|
413 |
//Skip events if canceled
|
sl@42
|
414 |
return;
|
sl@42
|
415 |
}
|
sl@44
|
416 |
|
sl@44
|
417 |
aHidEvent.RepeatCount++;
|
sl@44
|
418 |
aHidEvent.Time = DateTime.Now;
|
StephaneLenclud@54
|
419 |
if (aHidEvent.RepeatCount == 1)
|
sl@44
|
420 |
{
|
sl@44
|
421 |
//Re-Start our timer only after the initial delay
|
sl@44
|
422 |
aHidEvent.StartRepeatTimer(aHidEvent.iRepeatSpeed);
|
sl@44
|
423 |
}
|
sl@44
|
424 |
|
sl@44
|
425 |
//Broadcast our repeat event
|
sl@44
|
426 |
aHidEvent.OnHidEventRepeat(aHidEvent);
|
sl@41
|
427 |
}
|
sl@27
|
428 |
|
StephaneLenclud@54
|
429 |
/// <summary>
|
StephaneLenclud@73
|
430 |
/// Provide the state of the dpad or hat switch if any.
|
StephaneLenclud@73
|
431 |
/// If no dpad is found we return 'at rest'.
|
StephaneLenclud@73
|
432 |
/// </summary>
|
StephaneLenclud@73
|
433 |
/// <returns></returns>
|
StephaneLenclud@73
|
434 |
public DirectionPadState GetDirectionPadState()
|
StephaneLenclud@73
|
435 |
{
|
StephaneLenclud@77
|
436 |
int index=GetValueCapabilitiesIndex((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)GenericDesktop.HatSwitch);
|
StephaneLenclud@73
|
437 |
if (index < 0)
|
StephaneLenclud@73
|
438 |
{
|
StephaneLenclud@73
|
439 |
//No hat switch found
|
StephaneLenclud@73
|
440 |
return DirectionPadState.Rest;
|
StephaneLenclud@73
|
441 |
}
|
StephaneLenclud@73
|
442 |
|
StephaneLenclud@73
|
443 |
HIDP_VALUE_CAPS caps=Device.InputValueCapabilities[index];
|
StephaneLenclud@73
|
444 |
if (caps.IsRange)
|
StephaneLenclud@73
|
445 |
{
|
StephaneLenclud@73
|
446 |
//Defensive
|
StephaneLenclud@73
|
447 |
return DirectionPadState.Rest;
|
StephaneLenclud@73
|
448 |
}
|
StephaneLenclud@73
|
449 |
|
StephaneLenclud@73
|
450 |
uint dpadUsageValue = UsageValues[caps];
|
StephaneLenclud@73
|
451 |
|
StephaneLenclud@73
|
452 |
if (dpadUsageValue < caps.LogicalMin || dpadUsageValue > caps.LogicalMax)
|
StephaneLenclud@73
|
453 |
{
|
StephaneLenclud@73
|
454 |
//Out of range means at rest
|
StephaneLenclud@73
|
455 |
return DirectionPadState.Rest;
|
StephaneLenclud@73
|
456 |
}
|
StephaneLenclud@73
|
457 |
|
StephaneLenclud@73
|
458 |
//Normalize value to start at zero
|
StephaneLenclud@73
|
459 |
//TODO: more error check here?
|
StephaneLenclud@73
|
460 |
DirectionPadState res = (DirectionPadState)((int)dpadUsageValue - caps.LogicalMin);
|
StephaneLenclud@73
|
461 |
return res;
|
StephaneLenclud@73
|
462 |
}
|
StephaneLenclud@73
|
463 |
|
StephaneLenclud@73
|
464 |
/// <summary>
|
StephaneLenclud@54
|
465 |
/// Print information about this device to our debug output.
|
StephaneLenclud@54
|
466 |
/// </summary>
|
StephaneLenclud@54
|
467 |
public void DebugWrite()
|
StephaneLenclud@54
|
468 |
{
|
StephaneLenclud@54
|
469 |
if (!IsValid)
|
StephaneLenclud@54
|
470 |
{
|
StephaneLenclud@54
|
471 |
Debug.WriteLine("==== Invalid HidEvent");
|
StephaneLenclud@54
|
472 |
return;
|
StephaneLenclud@54
|
473 |
}
|
StephaneLenclud@70
|
474 |
|
StephaneLenclud@70
|
475 |
if (Device!=null)
|
StephaneLenclud@70
|
476 |
{
|
StephaneLenclud@70
|
477 |
Device.DebugWrite();
|
StephaneLenclud@70
|
478 |
}
|
StephaneLenclud@70
|
479 |
|
StephaneLenclud@54
|
480 |
if (IsGeneric) Debug.WriteLine("==== Generic");
|
StephaneLenclud@54
|
481 |
if (IsKeyboard) Debug.WriteLine("==== Keyboard");
|
StephaneLenclud@54
|
482 |
if (IsMouse) Debug.WriteLine("==== Mouse");
|
StephaneLenclud@54
|
483 |
Debug.WriteLine("==== Foreground: " + IsForeground.ToString());
|
StephaneLenclud@54
|
484 |
Debug.WriteLine("==== UsagePage: 0x" + UsagePage.ToString("X4"));
|
StephaneLenclud@54
|
485 |
Debug.WriteLine("==== UsageCollection: 0x" + UsageCollection.ToString("X4"));
|
StephaneLenclud@54
|
486 |
Debug.WriteLine("==== InputReport: 0x" + InputReportString());
|
StephaneLenclud@54
|
487 |
foreach (ushort usage in Usages)
|
StephaneLenclud@54
|
488 |
{
|
StephaneLenclud@54
|
489 |
Debug.WriteLine("==== Usage: 0x" + usage.ToString("X4"));
|
StephaneLenclud@54
|
490 |
}
|
StephaneLenclud@54
|
491 |
}
|
StephaneLenclud@49
|
492 |
|
StephaneLenclud@54
|
493 |
/// <summary>
|
StephaneLenclud@54
|
494 |
///
|
StephaneLenclud@54
|
495 |
/// </summary>
|
StephaneLenclud@54
|
496 |
/// <returns></returns>
|
StephaneLenclud@54
|
497 |
public string InputReportString()
|
StephaneLenclud@54
|
498 |
{
|
StephaneLenclud@70
|
499 |
if (InputReport == null)
|
StephaneLenclud@70
|
500 |
{
|
StephaneLenclud@70
|
501 |
return "null";
|
StephaneLenclud@70
|
502 |
}
|
StephaneLenclud@70
|
503 |
|
StephaneLenclud@54
|
504 |
string hidDump = "";
|
StephaneLenclud@54
|
505 |
foreach (byte b in InputReport)
|
StephaneLenclud@54
|
506 |
{
|
StephaneLenclud@54
|
507 |
hidDump += b.ToString("X2");
|
StephaneLenclud@54
|
508 |
}
|
StephaneLenclud@54
|
509 |
return hidDump;
|
StephaneLenclud@54
|
510 |
}
|
StephaneLenclud@49
|
511 |
|
StephaneLenclud@49
|
512 |
|
StephaneLenclud@54
|
513 |
/// <summary>
|
StephaneLenclud@54
|
514 |
/// Create a list view item describing this HidEvent
|
StephaneLenclud@54
|
515 |
/// </summary>
|
StephaneLenclud@54
|
516 |
/// <returns></returns>
|
sl@42
|
517 |
public ListViewItem ToListViewItem()
|
sl@42
|
518 |
{
|
StephaneLenclud@49
|
519 |
string usageText = "";
|
sl@42
|
520 |
|
StephaneLenclud@54
|
521 |
foreach (ushort usage in Usages)
|
StephaneLenclud@54
|
522 |
{
|
StephaneLenclud@54
|
523 |
if (usageText != "")
|
StephaneLenclud@54
|
524 |
{
|
StephaneLenclud@54
|
525 |
//Add a separator
|
StephaneLenclud@54
|
526 |
usageText += ", ";
|
StephaneLenclud@54
|
527 |
}
|
sl@42
|
528 |
|
StephaneLenclud@54
|
529 |
UsagePage usagePage = (UsagePage)UsagePage;
|
StephaneLenclud@54
|
530 |
switch (usagePage)
|
StephaneLenclud@54
|
531 |
{
|
StephaneLenclud@77
|
532 |
case Hid.UsagePage.Consumer:
|
StephaneLenclud@76
|
533 |
usageText += ((ConsumerControl)usage).ToString();
|
StephaneLenclud@54
|
534 |
break;
|
sl@42
|
535 |
|
StephaneLenclud@77
|
536 |
case Hid.UsagePage.WindowsMediaCenterRemoteControl:
|
StephaneLenclud@76
|
537 |
usageText += ((WindowsMediaCenterRemoteControl)usage).ToString();
|
StephaneLenclud@54
|
538 |
break;
|
StephaneLenclud@49
|
539 |
|
StephaneLenclud@54
|
540 |
default:
|
StephaneLenclud@54
|
541 |
usageText += usage.ToString("X2");
|
StephaneLenclud@54
|
542 |
break;
|
StephaneLenclud@54
|
543 |
}
|
StephaneLenclud@54
|
544 |
}
|
StephaneLenclud@49
|
545 |
|
StephaneLenclud@73
|
546 |
//If we are a gamepad display axis and dpad values
|
StephaneLenclud@73
|
547 |
if (Device.IsGamePad)
|
StephaneLenclud@73
|
548 |
{
|
StephaneLenclud@73
|
549 |
//uint dpadUsageValue = GetUsageValue((ushort)Hid.UsagePage.GenericDesktopControls, (ushort)Hid.Usage.GenericDesktop.HatSwitch);
|
StephaneLenclud@73
|
550 |
//usageText = dpadUsageValue.ToString("X") + " (dpad), " + usageText;
|
StephaneLenclud@73
|
551 |
|
StephaneLenclud@73
|
552 |
if (usageText != "")
|
StephaneLenclud@73
|
553 |
{
|
StephaneLenclud@73
|
554 |
//Add a separator
|
StephaneLenclud@73
|
555 |
usageText += " (Buttons)";
|
StephaneLenclud@73
|
556 |
}
|
StephaneLenclud@73
|
557 |
|
StephaneLenclud@73
|
558 |
if (usageText != "")
|
StephaneLenclud@73
|
559 |
{
|
StephaneLenclud@73
|
560 |
//Add a separator
|
StephaneLenclud@73
|
561 |
usageText += ", ";
|
StephaneLenclud@73
|
562 |
}
|
StephaneLenclud@73
|
563 |
|
StephaneLenclud@73
|
564 |
usageText += GetDirectionPadState().ToString();
|
StephaneLenclud@73
|
565 |
|
StephaneLenclud@73
|
566 |
foreach (KeyValuePair<HIDP_VALUE_CAPS, uint> entry in UsageValues)
|
StephaneLenclud@73
|
567 |
{
|
StephaneLenclud@73
|
568 |
if (entry.Key.IsRange)
|
StephaneLenclud@73
|
569 |
{
|
StephaneLenclud@73
|
570 |
continue;
|
StephaneLenclud@73
|
571 |
}
|
StephaneLenclud@73
|
572 |
|
StephaneLenclud@73
|
573 |
Type usageType = Utils.UsageType((UsagePage)entry.Key.UsagePage);
|
StephaneLenclud@73
|
574 |
if (usageType == null)
|
StephaneLenclud@73
|
575 |
{
|
StephaneLenclud@73
|
576 |
//TODO: check why this is happening on Logitech rumble gamepad 2.
|
StephaneLenclud@73
|
577 |
//Probably some of our axis are hiding in there.
|
StephaneLenclud@73
|
578 |
continue;
|
StephaneLenclud@73
|
579 |
}
|
StephaneLenclud@73
|
580 |
string name = Enum.GetName(usageType, entry.Key.NotRange.Usage);
|
StephaneLenclud@73
|
581 |
|
StephaneLenclud@73
|
582 |
if (usageText != "")
|
StephaneLenclud@73
|
583 |
{
|
StephaneLenclud@73
|
584 |
//Add a separator
|
StephaneLenclud@73
|
585 |
usageText += ", ";
|
StephaneLenclud@73
|
586 |
}
|
StephaneLenclud@73
|
587 |
usageText += entry.Value.ToString("X") + " ("+ name +")";
|
StephaneLenclud@73
|
588 |
}
|
StephaneLenclud@73
|
589 |
}
|
StephaneLenclud@73
|
590 |
|
StephaneLenclud@73
|
591 |
//Now create our list item
|
StephaneLenclud@54
|
592 |
ListViewItem item = new ListViewItem(new[] { usageText, InputReportString(), UsagePage.ToString("X2"), UsageCollection.ToString("X2"), RepeatCount.ToString(), Time.ToString("HH:mm:ss:fff") });
|
sl@42
|
593 |
return item;
|
sl@42
|
594 |
}
|
sl@42
|
595 |
|
sl@27
|
596 |
}
|
sl@27
|
597 |
|
sl@27
|
598 |
} |