Merge pull request #161 from thefiddler/win_xinput

[Win] HID-based IJoystickDriver2; improve XInput2 IGamePadDriver
This commit is contained in:
thefiddler 2014-09-18 01:14:50 +02:00
commit 41f1f92cdf
26 changed files with 2002 additions and 1149 deletions

View file

@ -101,6 +101,8 @@ namespace Examples
public static void Main(string[] args)
{
Trace.Listeners.Add(new ConsoleTraceListener());
Tests.GameWindowStates.Main();
return;
if (args.Length > 0)
{

View file

@ -160,7 +160,31 @@ namespace OpenTK.Input
/// <returns>A <see cref="System.String"/> that represents the current <see cref="OpenTK.Input.GamePadButtons"/>.</returns>
public override string ToString()
{
return Convert.ToString((int)buttons, 2).PadLeft(10, '0');
System.Text.StringBuilder sb = new System.Text.StringBuilder();
if (A == ButtonState.Pressed)
sb.Append("A");
if (B == ButtonState.Pressed)
sb.Append("B");
if (X == ButtonState.Pressed)
sb.Append("X");
if (Y == ButtonState.Pressed)
sb.Append("Y");
if (Back == ButtonState.Pressed)
sb.Append("Bk");
if (Start == ButtonState.Pressed)
sb.Append("St");
if (BigButton == ButtonState.Pressed)
sb.Append("Gd");
if (LeftShoulder == ButtonState.Pressed)
sb.Append("L");
if (RightShoulder == ButtonState.Pressed)
sb.Append("R");
if (LeftStick == ButtonState.Pressed)
sb.Append("Ls");
if (RightStick == ButtonState.Pressed)
sb.Append("Rs");
return sb.ToString();
}
/// <summary>

View file

@ -40,16 +40,18 @@ namespace OpenTK.Input
GamePadAxes axes;
byte gamepad_type;
bool is_connected;
bool is_mapped;
#region Constructors
internal GamePadCapabilities(GamePadType type, GamePadAxes axes, Buttons buttons, bool is_connected)
internal GamePadCapabilities(GamePadType type, GamePadAxes axes, Buttons buttons, bool is_connected, bool is_mapped)
: this()
{
gamepad_type = (byte)type;
this.axes = axes;
this.buttons = buttons;
this.is_connected = is_connected;
this.is_mapped = is_mapped;
}
#endregion
@ -317,6 +319,15 @@ namespace OpenTK.Input
get { return is_connected; }
}
/// <summary>
/// Gets a <see cref="System.Boolean"/> value describing whether a valid button configuration
/// exists for this <c>GamePad</c> in the GamePad configuration database.
/// </summary>
public bool IsMapped
{
get { return is_mapped; }
}
/// <param name="left">A <see cref="GamePadCapabilities"/> structure to test for equality.</param>
/// <param name="right">A <see cref="GamePadCapabilities"/> structure to test for equality.</param>
public static bool operator ==(GamePadCapabilities left, GamePadCapabilities right)
@ -338,11 +349,12 @@ namespace OpenTK.Input
public override string ToString()
{
return String.Format(
"{{Type: {0}; Axes: {1}; Buttons: {2}; Connected: {3}}}",
"{{Type: {0}; Axes: {1}; Buttons: {2}; {3}; {4}}}",
GamePadType,
Convert.ToString((int)axes, 2),
Convert.ToString((int)buttons, 2),
IsConnected);
IsMapped ? "Mapped" : "Unmapped",
IsConnected ? "Connected" : "Disconnected");
}
/// <summary>
@ -355,6 +367,7 @@ namespace OpenTK.Input
return
buttons.GetHashCode() ^
is_connected.GetHashCode() ^
is_mapped.GetHashCode() ^
gamepad_type.GetHashCode();
}
@ -386,6 +399,7 @@ namespace OpenTK.Input
return
buttons == other.buttons &&
is_connected == other.is_connected &&
is_mapped == other.is_mapped &&
gamepad_type == other.gamepad_type;
}

View file

@ -34,6 +34,8 @@ namespace OpenTK.Input
{
class GamePadConfigurationDatabase
{
internal const string UnmappedName = "Unmapped Controller";
readonly Dictionary<Guid, string> Configurations = new Dictionary<Guid, string>();
internal GamePadConfigurationDatabase()
@ -61,8 +63,19 @@ namespace OpenTK.Input
// 3. This notice may not be removed or altered from any source distribution.
#endregion
// Default (unknown) configuration
if (!Configuration.RunningOnSdl2)
{
Add("00000000000000000000000000000000,Unmapped Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,");
}
else
{
// Old SDL2 mapping for XInput devices (pre SDL-2.0.4)
Add("00000000000000000000000000000000,XInput Controller,a:b10,b:b11,back:b5,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,guide:b14,leftshoulder:b8,leftstick:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:a5,rightx:a3,righty:a2,start:b4,x:b12,y:b13,");
}
// Windows - XInput
Add("00000000000000000000000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,");
Add("78696e70757400000000000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,");
// Windows
Add("341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,");
@ -70,6 +83,7 @@ namespace OpenTK.Input
Add("6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,");
Add("6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,"); // Guide button doesn't seem to be sent in DInput mode.
Add("4d6963726f736f66742050432d6a6f79,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a5,righty:a4,x:b1,y:b2,");
Add("010906a3000000000000504944564944,P880 Controller,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b5,x:b0,y:b1,");
Add("88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,");
Add("4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,");
Add("25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,");

View file

@ -110,8 +110,8 @@ namespace OpenTK.Input
public override string ToString()
{
return String.Format(
"{{Sticks: {0}; Buttons: {1}; DPad: {2}; IsConnected: {3}}}",
ThumbSticks, Buttons, DPad, IsConnected);
"{{Sticks: {0}; Triggers: {1}; Buttons: {2}; DPad: {3}; IsConnected: {4}}}",
ThumbSticks, Triggers, Buttons, DPad, IsConnected);
}
/// <summary>

View file

@ -86,7 +86,7 @@ namespace OpenTK.Input
public override string ToString()
{
return String.Format(
"{{Left: {0:f2}; Right: {1:f2}}}",
"({0:f2}; {1:f2})",
Left, Right);
}

View file

@ -29,6 +29,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace OpenTK.Input
@ -48,11 +49,18 @@ namespace OpenTK.Input
internal JoystickCapabilities(int axis_count, int button_count, int hat_count, bool is_connected)
{
if (axis_count < 0 || axis_count > JoystickState.MaxAxes)
throw new ArgumentOutOfRangeException("axis_count");
Debug.Print("[{0}] Axis count {1} out of range (0, {2})",
typeof(JoystickCapabilities).Name, axis_count, JoystickState.MaxAxes);
if (button_count < 0 || button_count > JoystickState.MaxButtons)
throw new ArgumentOutOfRangeException("axis_count");
Debug.Print("[{0}] Button count {1} out of range (0, {2})",
typeof(JoystickCapabilities).Name, button_count, JoystickState.MaxButtons);
if (hat_count < 0 || hat_count > JoystickState.MaxHats)
throw new ArgumentOutOfRangeException("hat_count");
Debug.Print("[{0}] Hat count {1} out of range (0, {2})",
typeof(JoystickCapabilities).Name, hat_count, JoystickState.MaxHats);
axis_count = MathHelper.Clamp(axis_count, 0, JoystickState.MaxAxes);
button_count = MathHelper.Clamp(button_count, 0, JoystickState.MaxButtons);
hat_count = MathHelper.Clamp(hat_count, 0, JoystickState.MaxHats);
this.axis_count = (byte)axis_count;
this.button_count = (byte)button_count;
@ -62,6 +70,15 @@ namespace OpenTK.Input
#endregion
#region Internal Members
internal void SetIsConnected(bool value)
{
is_connected = value;
}
#endregion
#region Public Members
/// <summary>
@ -95,6 +112,7 @@ namespace OpenTK.Input
public bool IsConnected
{
get { return is_connected; }
private set { is_connected = value; }
}
/// <summary>
@ -104,7 +122,7 @@ namespace OpenTK.Input
public override string ToString()
{
return String.Format(
"{{Axes: {0}; Buttons: {1}; Hats: {2}; IsConnected: {2}}}",
"{{Axes: {0}; Buttons: {1}; Hats: {2}; IsConnected: {3}}}",
AxisCount, ButtonCount, HatCount, IsConnected);
}

View file

@ -223,6 +223,11 @@ namespace OpenTK.Input
}
}
internal void ClearButtons()
{
buttons = 0;
}
internal void SetButton(JoystickButton button, bool value)
{
int index = (int)button;

View file

@ -162,11 +162,14 @@
<Compile Include="Math\Matrix4x2d.cs" />
<Compile Include="Math\Matrix4x3.cs" />
<Compile Include="Math\Matrix4x3d.cs" />
<Compile Include="Platform\Common\Hid.cs" />
<Compile Include="Platform\DisplayDeviceBase.cs" />
<Compile Include="Platform\Egl\EglUnixContext.cs" />
<Compile Include="Platform\Egl\EglWinContext.cs" />
<Compile Include="Platform\MappedGamePadDriver.cs" />
<Compile Include="Platform\Windows\Bindings\HidProtocol.cs" />
<Compile Include="Platform\Windows\WinInputBase.cs" />
<Compile Include="Platform\Windows\WinRawJoystick.cs" />
<Compile Include="Platform\Windows\XInputJoystick.cs" />
<Compile Include="Platform\X11\Bindings\DL.cs" />
<Compile Include="Platform\X11\Bindings\INotify.cs" />
@ -325,9 +328,6 @@
<Compile Include="Platform\Windows\WinGLContext.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Platform\Windows\WinMMJoystick.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Platform\Windows\WglHelper.cs">
<SubType>Code</SubType>
</Compile>

View file

@ -0,0 +1,270 @@
#region License
//
// Hid.cs
//
// Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2014 Stefanos Apostolopoulos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using OpenTK.Input;
namespace OpenTK.Platform.Common
{
class HidHelper
{
/// <summary>
/// Scales the specified value linearly between min and max.
/// </summary>
/// <param name="value">The value to scale</param>
/// <param name="value_min">The minimum expected value (inclusive)</param>
/// <param name="value_max">The maximum expected value (inclusive)</param>
/// <param name="result_min">The minimum output value (inclusive)</param>
/// <param name="result_max">The maximum output value (inclusive)</param>
/// <returns>The value, scaled linearly between min and max</returns>
public static int ScaleValue(int value, int value_min, int value_max,
int result_min, int result_max)
{
if (value_min >= value_max || result_min >= result_max)
throw new ArgumentOutOfRangeException();
MathHelper.Clamp(value, value_min, value_max);
int range = result_max - result_min;
long temp = (value - value_min) * range; // need long to avoid overflow
return (int)(temp / (value_max - value_min) + result_min);
}
public static JoystickAxis TranslateJoystickAxis(HIDPage page, int usage)
{
switch (page)
{
case HIDPage.GenericDesktop:
switch ((HIDUsageGD)usage)
{
case HIDUsageGD.X:
return JoystickAxis.Axis0;
case HIDUsageGD.Y:
return JoystickAxis.Axis1;
case HIDUsageGD.Z:
return JoystickAxis.Axis2;
case HIDUsageGD.Rz:
return JoystickAxis.Axis3;
case HIDUsageGD.Rx:
return JoystickAxis.Axis4;
case HIDUsageGD.Ry:
return JoystickAxis.Axis5;
case HIDUsageGD.Slider:
return JoystickAxis.Axis6;
case HIDUsageGD.Dial:
return JoystickAxis.Axis7;
case HIDUsageGD.Wheel:
return JoystickAxis.Axis8;
}
break;
case HIDPage.Simulation:
switch ((HIDUsageSim)usage)
{
case HIDUsageSim.Rudder:
return JoystickAxis.Axis9;
case HIDUsageSim.Throttle:
return JoystickAxis.Axis10;
}
break;
}
Debug.Print("[Input] Unknown axis with HID page/usage {0}/{1}", page, usage);
return 0;
}
}
enum HIDPage : ushort
{
Undefined = 0x00,
GenericDesktop = 0x01,
Simulation = 0x02,
VR = 0x03,
Sport = 0x04,
Game = 0x05,
// Reserved 0x06
KeyboardOrKeypad = 0x07, // USB Device Class Definition for Human Interface Devices (HID). Note: the usage type for all key codes is Selector (Sel).
LEDs = 0x08,
Button = 0x09,
Ordinal = 0x0A,
Telephony = 0x0B,
Consumer = 0x0C,
Digitizer = 0x0D,
// Reserved 0x0E
PID = 0x0F, // USB Physical Interface Device definitions for force feedback and related devices.
Unicode = 0x10,
// Reserved 0x11 - 0x13
AlphanumericDisplay = 0x14,
// Reserved 0x15 - 0x7F
// Monitor 0x80 - 0x83 USB Device Class Definition for Monitor Devices
// Power 0x84 - 0x87 USB Device Class Definition for Power Devices
PowerDevice = 0x84, // Power Device Page
BatterySystem = 0x85, // Battery System Page
// Reserved 0x88 - 0x8B
BarCodeScanner = 0x8C, // (Point of Sale) USB Device Class Definition for Bar Code Scanner Devices
WeighingDevice = 0x8D, // (Point of Sale) USB Device Class Definition for Weighing Devices
Scale = 0x8D, // (Point of Sale) USB Device Class Definition for Scale Devices
MagneticStripeReader = 0x8E,
// ReservedPointofSalepages 0x8F
CameraControl = 0x90, // USB Device Class Definition for Image Class Devices
Arcade = 0x91, // OAAF Definitions for arcade and coinop related Devices
// Reserved 0x92 - 0xFEFF
// VendorDefined 0xFF00 - 0xFFFF
VendorDefinedStart = 0xFF00
}
// Consumer electronic devices
enum HIDUsageCD
{
ACPan = 0x0238
}
// Generic desktop usage
enum HIDUsageGD : ushort
{
Pointer = 0x01, // Physical Collection
Mouse = 0x02, // Application Collection
// 0x03 Reserved
Joystick = 0x04, // Application Collection
GamePad = 0x05, // Application Collection
Keyboard = 0x06, // Application Collection
Keypad = 0x07, // Application Collection
MultiAxisController = 0x08, // Application Collection
// 0x09 - 0x2F Reserved
X = 0x30, // Dynamic Value
Y = 0x31, // Dynamic Value
Z = 0x32, // Dynamic Value
Rx = 0x33, // Dynamic Value
Ry = 0x34, // Dynamic Value
Rz = 0x35, // Dynamic Value
Slider = 0x36, // Dynamic Value
Dial = 0x37, // Dynamic Value
Wheel = 0x38, // Dynamic Value
Hatswitch = 0x39, // Dynamic Value
CountedBuffer = 0x3A, // Logical Collection
ByteCount = 0x3B, // Dynamic Value
MotionWakeup = 0x3C, // One-Shot Control
Start = 0x3D, // On/Off Control
Select = 0x3E, // On/Off Control
// 0x3F Reserved
Vx = 0x40, // Dynamic Value
Vy = 0x41, // Dynamic Value
Vz = 0x42, // Dynamic Value
Vbrx = 0x43, // Dynamic Value
Vbry = 0x44, // Dynamic Value
Vbrz = 0x45, // Dynamic Value
Vno = 0x46, // Dynamic Value
// 0x47 - 0x7F Reserved
SystemControl = 0x80, // Application Collection
SystemPowerDown = 0x81, // One-Shot Control
SystemSleep = 0x82, // One-Shot Control
SystemWakeUp = 0x83, // One-Shot Control
SystemContextMenu = 0x84, // One-Shot Control
SystemMainMenu = 0x85, // One-Shot Control
SystemAppMenu = 0x86, // One-Shot Control
SystemMenuHelp = 0x87, // One-Shot Control
SystemMenuExit = 0x88, // One-Shot Control
SystemMenu = 0x89, // Selector
SystemMenuRight = 0x8A, // Re-Trigger Control
SystemMenuLeft = 0x8B, // Re-Trigger Control
SystemMenuUp = 0x8C, // Re-Trigger Control
SystemMenuDown = 0x8D, // Re-Trigger Control
// 0x8E - 0x8F Reserved
DPadUp = 0x90, // On/Off Control
DPadDown = 0x91, // On/Off Control
DPadRight = 0x92, // On/Off Control
DPadLeft = 0x93, // On/Off Control
// 0x94 - 0xFFFF Reserved
Reserved = 0xFFFF
}
enum HIDUsageSim : ushort
{
FlightSimulationDevice = 0x01, // Application Collection
AutomobileSimulationDevice = 0x02, // Application Collection
TankSimulationDevice = 0x03, // Application Collection
SpaceshipSimulationDevice = 0x04, // Application Collection
SubmarineSimulationDevice = 0x05, // Application Collection
SailingSimulationDevice = 0x06, // Application Collection
MotorcycleSimulationDevice = 0x07, // Application Collection
SportsSimulationDevice = 0x08, // Application Collection
AirplaneSimulationDevice = 0x09, // Application Collection
HelicopterSimulationDevice = 0x0A, // Application Collection
MagicCarpetSimulationDevice = 0x0B, // Application Collection
BicycleSimulationDevice = 0x0C, // Application Collection
// 0x0D - 0x1F Reserved
FlightControlStick = 0x20, // Application Collection
FlightStick = 0x21, // Application Collection
CyclicControl = 0x22, // Physical Collection
CyclicTrim = 0x23, // Physical Collection
FlightYoke = 0x24, // Application Collection
TrackControl = 0x25, // Physical Collection
// 0x26 - 0xAF Reserved
Aileron = 0xB0, // Dynamic Value
AileronTrim = 0xB1, // Dynamic Value
AntiTorqueControl = 0xB2, // Dynamic Value
AutopilotEnable = 0xB3, // On/Off Control
ChaffRelease = 0xB4, // One-Shot Control
CollectiveControl = 0xB5, // Dynamic Value
DiveBrake = 0xB6, // Dynamic Value
ElectronicCountermeasures = 0xB7, // On/Off Control
Elevator = 0xB8, // Dynamic Value
ElevatorTrim = 0xB9, // Dynamic Value
Rudder = 0xBA, // Dynamic Value
Throttle = 0xBB, // Dynamic Value
FlightCommunications = 0xBC, // On/Off Control
FlareRelease = 0xBD, // One-Shot Control
LandingGear = 0xBE, // On/Off Control
ToeBrake = 0xBF, // Dynamic Value
Trigger = 0xC0, // Momentary Control
WeaponsArm = 0xC1, // On/Off Control
Weapons = 0xC2, // Selector
WingFlaps = 0xC3, // Dynamic Value
Accelerator = 0xC4, // Dynamic Value
Brake = 0xC5, // Dynamic Value
Clutch = 0xC6, // Dynamic Value
Shifter = 0xC7, // Dynamic Value
Steering = 0xC8, // Dynamic Value
TurretDirection = 0xC9, // Dynamic Value
BarrelElevation = 0xCA, // Dynamic Value
DivePlane = 0xCB, // Dynamic Value
Ballast = 0xCC, // Dynamic Value
BicycleCrank = 0xCD, // Dynamic Value
HandleBars = 0xCE, // Dynamic Value
FrontBrake = 0xCF, // Dynamic Value
RearBrake = 0xD0, // Dynamic Value
// 0xD1 - 0xFFFF Reserved
Reserved = 0xFFFF
}
}

View file

@ -41,7 +41,7 @@ namespace OpenTK.Platform
// that is added.
class DeviceCollection<T> : IEnumerable<T>
{
readonly Dictionary<int, int> Map = new Dictionary<int, int>();
readonly Dictionary<long, int> Map = new Dictionary<long, int>();
readonly List<T> Devices = new List<T>();
#region IEnumerable<T> Members
@ -64,7 +64,15 @@ namespace OpenTK.Platform
#region Public Members
public void Add(int id, T device)
/// \internal
/// <summary>
/// Adds or replaces a device based on its hardware id.
/// A zero-based device index will be generated automatically
/// for the first available device slot.
/// </summary>
/// <param name="id">The hardware id for the device.</param>
/// <param name="device">The device instance.</param>
public void Add(long id, T device)
{
if (!Map.ContainsKey(id))
{
@ -75,7 +83,7 @@ namespace OpenTK.Platform
Devices[Map[id]] = device;
}
public void Remove(int id)
public void Remove(long id)
{
if (!TryRemove(id))
{
@ -83,7 +91,7 @@ namespace OpenTK.Platform
}
}
public bool TryRemove(int id)
public bool TryRemove(long id)
{
if (!Map.ContainsKey(id))
{
@ -107,7 +115,7 @@ namespace OpenTK.Platform
}
}
public T FromHardwareId(int id)
public T FromHardwareId(long id)
{
if (Map.ContainsKey(id))
{

View file

@ -44,7 +44,7 @@ namespace OpenTK.Platform
IGraphicsContext CreateGLContext(ContextHandle handle, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags);
GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext();
OpenTK.Input.IKeyboardDriver2 CreateKeyboardDriver();
OpenTK.Input.IMouseDriver2 CreateMouseDriver();

View file

@ -30,6 +30,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using OpenTK.Input;
using OpenTK.Platform.Common;
namespace OpenTK.Platform.MacOS
{
@ -755,7 +756,7 @@ namespace OpenTK.Platform.MacOS
case HIDUsageGD.Dial:
case HIDUsageGD.Wheel:
short offset = GetJoystickAxis(val, elem);
JoystickAxis axis = TranslateJoystickAxis(usage);
JoystickAxis axis = HidHelper.TranslateJoystickAxis(page, usage);
if (axis >= JoystickAxis.Axis0 && axis <= JoystickAxis.Last)
{
joy.State.SetAxis(axis, offset);
@ -779,7 +780,7 @@ namespace OpenTK.Platform.MacOS
case HIDUsageSim.Rudder:
case HIDUsageSim.Throttle:
short offset = GetJoystickAxis(val, elem);
JoystickAxis axis = TranslateJoystickAxis(usage);
JoystickAxis axis = HidHelper.TranslateJoystickAxis(page, usage);
if (axis >= JoystickAxis.Axis0 && axis <= JoystickAxis.Last)
{
joy.State.SetAxis(axis, offset);
@ -806,51 +807,7 @@ namespace OpenTK.Platform.MacOS
int max = NativeMethods.IOHIDElementGetLogicalMax(element).ToInt32();
int min = NativeMethods.IOHIDElementGetLogicalMin(element).ToInt32();
int offset = NativeMethods.IOHIDValueGetIntegerValue(val).ToInt32();
if (offset < min)
offset = min;
if (offset > max)
offset = max;
const int range = short.MaxValue - short.MinValue + 1;
const int half_range = short.MaxValue + 1;
return (short)((offset - min) * range / (max - min) + half_range);
}
static JoystickAxis TranslateJoystickAxis(int usage)
{
switch (usage)
{
case (int)HIDUsageGD.X:
return JoystickAxis.Axis0;
case (int)HIDUsageGD.Y:
return JoystickAxis.Axis1;
case (int)HIDUsageGD.Z:
return JoystickAxis.Axis2;
case (int)HIDUsageGD.Rz:
return JoystickAxis.Axis3;
case (int)HIDUsageGD.Rx:
return JoystickAxis.Axis4;
case (int)HIDUsageGD.Ry:
return JoystickAxis.Axis5;
case (int)HIDUsageGD.Slider:
return JoystickAxis.Axis6;
case (int)HIDUsageGD.Dial:
return JoystickAxis.Axis7;
case (int)HIDUsageGD.Wheel:
return JoystickAxis.Axis8;
case (int)HIDUsageSim.Rudder:
return JoystickAxis.Axis9;
case (int)HIDUsageSim.Throttle:
return JoystickAxis.Axis10;
default:
Debug.Print("[Mac] Unknown axis with HID usage {0}", usage);
return 0;
}
return (short)HidHelper.ScaleValue(offset, min, max, short.MinValue, short.MaxValue);
}
static bool GetJoystickButton(IOHIDValueRef val, IOHIDElementRef element)
@ -1209,169 +1166,6 @@ namespace OpenTK.Platform.MacOS
Calibrated // [-1, +1]
}
enum HIDPage
{
Undefined = 0x00,
GenericDesktop = 0x01,
Simulation = 0x02,
VR = 0x03,
Sport = 0x04,
Game = 0x05,
/* Reserved 0x06 */
KeyboardOrKeypad = 0x07, /* USB Device Class Definition for Human Interface Devices (HID). Note: the usage type for all key codes is Selector (Sel). */
LEDs = 0x08,
Button = 0x09,
Ordinal = 0x0A,
Telephony = 0x0B,
Consumer = 0x0C,
Digitizer = 0x0D,
/* Reserved 0x0E */
PID = 0x0F, /* USB Physical Interface Device definitions for force feedback and related devices. */
Unicode = 0x10,
/* Reserved 0x11 - 0x13 */
AlphanumericDisplay = 0x14,
/* Reserved 0x15 - 0x7F */
/* Monitor 0x80 - 0x83 USB Device Class Definition for Monitor Devices */
/* Power 0x84 - 0x87 USB Device Class Definition for Power Devices */
PowerDevice = 0x84, /* Power Device Page */
BatterySystem = 0x85, /* Battery System Page */
/* Reserved 0x88 - 0x8B */
BarCodeScanner = 0x8C, /* (Point of Sale) USB Device Class Definition for Bar Code Scanner Devices */
WeighingDevice = 0x8D, /* (Point of Sale) USB Device Class Definition for Weighing Devices */
Scale = 0x8D, /* (Point of Sale) USB Device Class Definition for Scale Devices */
MagneticStripeReader = 0x8E,
/* ReservedPointofSalepages 0x8F */
CameraControl = 0x90, /* USB Device Class Definition for Image Class Devices */
Arcade = 0x91, /* OAAF Definitions for arcade and coinop related Devices */
/* Reserved 0x92 - 0xFEFF */
/* VendorDefined 0xFF00 - 0xFFFF */
VendorDefinedStart = 0xFF00
}
// Consumer electronic devices
enum HIDUsageCD
{
ACPan = 0x0238
}
// Generic desktop usage
enum HIDUsageGD
{
Pointer = 0x01, /* Physical Collection */
Mouse = 0x02, /* Application Collection */
/* 0x03 Reserved */
Joystick = 0x04, /* Application Collection */
GamePad = 0x05, /* Application Collection */
Keyboard = 0x06, /* Application Collection */
Keypad = 0x07, /* Application Collection */
MultiAxisController = 0x08, /* Application Collection */
/* 0x09 - 0x2F Reserved */
X = 0x30, /* Dynamic Value */
Y = 0x31, /* Dynamic Value */
Z = 0x32, /* Dynamic Value */
Rx = 0x33, /* Dynamic Value */
Ry = 0x34, /* Dynamic Value */
Rz = 0x35, /* Dynamic Value */
Slider = 0x36, /* Dynamic Value */
Dial = 0x37, /* Dynamic Value */
Wheel = 0x38, /* Dynamic Value */
Hatswitch = 0x39, /* Dynamic Value */
CountedBuffer = 0x3A, /* Logical Collection */
ByteCount = 0x3B, /* Dynamic Value */
MotionWakeup = 0x3C, /* One-Shot Control */
Start = 0x3D, /* On/Off Control */
Select = 0x3E, /* On/Off Control */
/* 0x3F Reserved */
Vx = 0x40, /* Dynamic Value */
Vy = 0x41, /* Dynamic Value */
Vz = 0x42, /* Dynamic Value */
Vbrx = 0x43, /* Dynamic Value */
Vbry = 0x44, /* Dynamic Value */
Vbrz = 0x45, /* Dynamic Value */
Vno = 0x46, /* Dynamic Value */
/* 0x47 - 0x7F Reserved */
SystemControl = 0x80, /* Application Collection */
SystemPowerDown = 0x81, /* One-Shot Control */
SystemSleep = 0x82, /* One-Shot Control */
SystemWakeUp = 0x83, /* One-Shot Control */
SystemContextMenu = 0x84, /* One-Shot Control */
SystemMainMenu = 0x85, /* One-Shot Control */
SystemAppMenu = 0x86, /* One-Shot Control */
SystemMenuHelp = 0x87, /* One-Shot Control */
SystemMenuExit = 0x88, /* One-Shot Control */
SystemMenu = 0x89, /* Selector */
SystemMenuRight = 0x8A, /* Re-Trigger Control */
SystemMenuLeft = 0x8B, /* Re-Trigger Control */
SystemMenuUp = 0x8C, /* Re-Trigger Control */
SystemMenuDown = 0x8D, /* Re-Trigger Control */
/* 0x8E - 0x8F Reserved */
DPadUp = 0x90, /* On/Off Control */
DPadDown = 0x91, /* On/Off Control */
DPadRight = 0x92, /* On/Off Control */
DPadLeft = 0x93, /* On/Off Control */
/* 0x94 - 0xFFFF Reserved */
Reserved = 0xFFFF
}
enum HIDUsageSim
{
FlightSimulationDevice = 0x01, /* Application Collection */
AutomobileSimulationDevice = 0x02, /* Application Collection */
TankSimulationDevice = 0x03, /* Application Collection */
SpaceshipSimulationDevice = 0x04, /* Application Collection */
SubmarineSimulationDevice = 0x05, /* Application Collection */
SailingSimulationDevice = 0x06, /* Application Collection */
MotorcycleSimulationDevice = 0x07, /* Application Collection */
SportsSimulationDevice = 0x08, /* Application Collection */
AirplaneSimulationDevice = 0x09, /* Application Collection */
HelicopterSimulationDevice = 0x0A, /* Application Collection */
MagicCarpetSimulationDevice = 0x0B, /* Application Collection */
BicycleSimulationDevice = 0x0C, /* Application Collection */
/* 0x0D - 0x1F Reserved */
FlightControlStick = 0x20, /* Application Collection */
FlightStick = 0x21, /* Application Collection */
CyclicControl = 0x22, /* Physical Collection */
CyclicTrim = 0x23, /* Physical Collection */
FlightYoke = 0x24, /* Application Collection */
TrackControl = 0x25, /* Physical Collection */
/* 0x26 - 0xAF Reserved */
Aileron = 0xB0, /* Dynamic Value */
AileronTrim = 0xB1, /* Dynamic Value */
AntiTorqueControl = 0xB2, /* Dynamic Value */
AutopilotEnable = 0xB3, /* On/Off Control */
ChaffRelease = 0xB4, /* One-Shot Control */
CollectiveControl = 0xB5, /* Dynamic Value */
DiveBrake = 0xB6, /* Dynamic Value */
ElectronicCountermeasures = 0xB7, /* On/Off Control */
Elevator = 0xB8, /* Dynamic Value */
ElevatorTrim = 0xB9, /* Dynamic Value */
Rudder = 0xBA, /* Dynamic Value */
Throttle = 0xBB, /* Dynamic Value */
FlightCommunications = 0xBC, /* On/Off Control */
FlareRelease = 0xBD, /* One-Shot Control */
LandingGear = 0xBE, /* On/Off Control */
ToeBrake = 0xBF, /* Dynamic Value */
Trigger = 0xC0, /* Momentary Control */
WeaponsArm = 0xC1, /* On/Off Control */
Weapons = 0xC2, /* Selector */
WingFlaps = 0xC3, /* Dynamic Value */
Accelerator = 0xC4, /* Dynamic Value */
Brake = 0xC5, /* Dynamic Value */
Clutch = 0xC6, /* Dynamic Value */
Shifter = 0xC7, /* Dynamic Value */
Steering = 0xC8, /* Dynamic Value */
TurretDirection = 0xC9, /* Dynamic Value */
BarrelElevation = 0xCA, /* Dynamic Value */
DivePlane = 0xCB, /* Dynamic Value */
Ballast = 0xCC, /* Dynamic Value */
BicycleCrank = 0xCD, /* Dynamic Value */
HandleBars = 0xCE, /* Dynamic Value */
FrontBrake = 0xCF, /* Dynamic Value */
RearBrake = 0xD0, /* Dynamic Value */
/* 0xD1 - 0xFFFF Reserved */
Reserved = 0xFFFF
}
enum HIDButton
{
Button_1 = 0x01, /* (primary/trigger) */

View file

@ -108,7 +108,12 @@ namespace OpenTK.Platform
// Todo: if SDL2 GameController config is ever updated to
// distinguish between negative/positive axes, then update
// the following line to support both.
pad.SetAxis(map.Target.Axis, pressed ? short.MaxValue : (short)0);
short value = pressed ?
short.MaxValue :
(map.Target.Axis & (GamePadAxes.LeftTrigger | GamePadAxes.RightTrigger)) != 0 ?
short.MinValue :
(short)0;
pad.SetAxis(map.Target.Axis, value);
break;
case ConfigurationType.Button:
@ -150,7 +155,12 @@ namespace OpenTK.Platform
// Todo: if SDL2 GameController config is ever updated to
// distinguish between negative/positive axes, then update
// the following line to support both.
pad.SetAxis(map.Target.Axis, pressed ? short.MaxValue : (short)0);
short value = pressed ?
short.MaxValue :
(map.Target.Axis & (GamePadAxes.LeftTrigger | GamePadAxes.RightTrigger)) != 0 ?
short.MinValue :
(short)0;
pad.SetAxis(map.Target.Axis, value);
break;
case ConfigurationType.Button:
@ -194,7 +204,8 @@ namespace OpenTK.Platform
GamePadType.GamePad, // Todo: detect different types
mapped_axes,
mapped_buttons,
true);
joy.IsConnected,
configuration.Name != GamePadConfigurationDatabase.UnmappedName);
}
else
{

View file

@ -1542,6 +1542,16 @@ namespace OpenTK.Platform.SDL2
}
}
// The Guid(byte[]) constructor swaps the first 4+2+2 bytes.
// Compensate for that, otherwise we will not be able to match
// the Guids in the configuration database.
if (BitConverter.IsLittleEndian)
{
Array.Reverse(data, 0, 4);
Array.Reverse(data, 4, 2);
Array.Reverse(data, 6, 2);
}
return new Guid(data);
}
}

View file

@ -1,12 +1,33 @@
#region --- License ---
/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
* Contributions from Erik Ylvisaker
* See license.txt for license info
*/
#region License
//
// WinRawJoystick.cs
//
// Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2006 Stefanos Apostolopoulos
// Copyright (c) 2007 Erik Ylvisaker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
#region --- Using Directives ---
using System;
#if !MINIMAL
using System.Drawing;
@ -14,8 +35,7 @@ using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Security;
#endregion
using OpenTK.Platform.Common;
/* TODO: Update the description of TimeBeginPeriod and other native methods. Update Timer. */
@ -74,6 +94,7 @@ namespace OpenTK.Platform.Windows
using TIMERPROC = Functions.TimerProc;
using REGSAM = System.UInt32;
using System.Diagnostics;
#endregion
@ -1480,16 +1501,6 @@ namespace OpenTK.Platform.Windows
/// <para>If Data is not large enough for the data, the function returns -1. If Data is NULL, the function returns a value of zero. In both of these cases, Size is set to the minimum size required for the Data buffer.</para>
/// <para>Call GetLastError to identify any other errors.</para>
/// </returns>
[CLSCompliant(false)]
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true)]
internal static extern UINT GetRawInputDeviceInfo(
HANDLE Device,
[MarshalAs(UnmanagedType.U4)] RawInputDeviceInfoEnum Command,
[In, Out] LPVOID Data,
[In, Out] ref UINT Size
);
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true)]
internal static extern INT GetRawInputDeviceInfo(
@ -1499,6 +1510,16 @@ namespace OpenTK.Platform.Windows
[In, Out] ref INT Size
);
[CLSCompliant(false)]
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true)]
internal static extern INT GetRawInputDeviceInfo(
HANDLE Device,
[MarshalAs(UnmanagedType.U4)] RawInputDeviceInfoEnum Command,
[In, Out] byte[] Data,
[In, Out] ref INT Size
);
/// <summary>
/// Gets information about the raw input device.
/// </summary>
@ -1626,6 +1647,52 @@ namespace OpenTK.Platform.Windows
INT SizeHeader
);
internal static int GetRawInputData(IntPtr raw, out RawInputHeader header)
{
int size = RawInputHeader.SizeInBytes;
unsafe
{
fixed (RawInputHeader* pheader = &header)
{
if (GetRawInputData(raw, GetRawInputDataEnum.HEADER,
(IntPtr)pheader, ref size, API.RawInputHeaderSize) != RawInputHeader.SizeInBytes)
{
Debug.Print("[Error] Failed to retrieve raw input header. Error: {0}",
Marshal.GetLastWin32Error());
}
}
}
return size;
}
internal static int GetRawInputData(IntPtr raw, out RawInput data)
{
int size = RawInput.SizeInBytes;
unsafe
{
fixed (RawInput* pdata = &data)
{
GetRawInputData(raw, GetRawInputDataEnum.INPUT,
(IntPtr)pdata, ref size, API.RawInputHeaderSize);
}
}
return size;
}
internal static int GetRawInputData(IntPtr raw, byte[] data)
{
int size = data.Length;
unsafe
{
fixed (byte* pdata = data)
{
GetRawInputData(raw, GetRawInputDataEnum.INPUT,
(IntPtr)pdata, ref size, API.RawInputHeaderSize);
}
}
return size;
}
#endregion
#region IntPtr NextRawInputStructure(IntPtr data)
@ -2424,8 +2491,7 @@ namespace OpenTK.Platform.Windows
/// <summary>
/// Top level collection Usage page for the raw input device.
/// </summary>
//internal USHORT UsagePage;
internal SHORT UsagePage;
internal HIDPage UsagePage;
/// <summary>
/// Top level collection Usage for the raw input device.
/// </summary>
@ -2443,6 +2509,22 @@ namespace OpenTK.Platform.Windows
/// </summary>
internal HWND Target;
public RawInputDevice(HIDUsageGD usage, RawInputDeviceFlags flags, HWND target)
{
UsagePage = HIDPage.GenericDesktop;
Usage = (short)usage;
Flags = flags;
Target = target;
}
public RawInputDevice(HIDUsageSim usage, RawInputDeviceFlags flags, HWND target)
{
UsagePage = HIDPage.Simulation;
Usage = (short)usage;
Flags = flags;
Target = target;
}
public override string ToString()
{
return String.Format("{0}/{1}, flags: {2}, window: {3}", UsagePage, Usage, Flags, Target);
@ -2494,6 +2576,9 @@ namespace OpenTK.Platform.Windows
{
public RawInputHeader Header;
public RawInputData Data;
public static readonly int SizeInBytes =
BlittableValueType<RawInput>.Stride;
}
[StructLayout(LayoutKind.Explicit)]
@ -2505,6 +2590,9 @@ namespace OpenTK.Platform.Windows
internal RawKeyboard Keyboard;
[FieldOffset(0)]
internal RawHID HID;
public static readonly int SizeInBytes =
BlittableValueType<RawInputData>.Stride;
}
#endregion
@ -2537,6 +2625,9 @@ namespace OpenTK.Platform.Windows
/// Value passed in the wParam parameter of the WM_INPUT message.
/// </summary>
internal WPARAM Param;
public static readonly int SizeInBytes =
BlittableValueType<RawInputHeader>.Stride;
}
#endregion
@ -2715,16 +2806,31 @@ namespace OpenTK.Platform.Windows
/// <summary>
/// Size, in bytes, of each HID input in bRawData.
/// </summary>
internal DWORD SizeHid;
internal DWORD Size;
/// <summary>
/// Number of HID inputs in bRawData.
/// </summary>
internal DWORD Count;
// The RawData field must be marshalled manually.
///// <summary>
///// Raw input data as an array of bytes.
///// </summary>
//internal IntPtr RawData;
/// <summary>
/// Raw input data as an array of bytes.
/// </summary>
internal byte RawData;
internal byte this[int index]
{
get
{
if (index < 0 || index > Size * Count)
throw new ArgumentOutOfRangeException("index");
unsafe
{
fixed (byte* data = &RawData)
{
return *(data + index);
}
}
}
}
}
#endregion
@ -2741,7 +2847,7 @@ namespace OpenTK.Platform.Windows
/// <summary>
/// Size, in bytes, of the RawInputDeviceInfo structure.
/// </summary>
DWORD Size = Marshal.SizeOf(typeof(RawInputDeviceInfo));
internal DWORD Size = Marshal.SizeOf(typeof(RawInputDeviceInfo));
/// <summary>
/// Type of raw input data.
/// </summary>

View file

@ -0,0 +1,288 @@
#region License
//
// HidProtocol.cs
//
// Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2014 Stefanos Apostolopoulos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using OpenTK.Platform.Common;
namespace OpenTK.Platform.Windows
{
class HidProtocol
{
const string lib = "hid.dll";
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetButtonCaps")]
public static extern HidProtocolStatus GetButtonCaps(HidProtocolReportType hidProtocolReportType,
IntPtr button_caps, ref ushort p, [In] byte[] preparsed_data);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetButtonCaps")]
public static extern HidProtocolStatus GetButtonCaps(HidProtocolReportType hidProtocolReportType,
[Out] HidProtocolButtonCaps[] button_caps, ref ushort p, [In] byte[] preparsed_data);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetCaps")]
public static extern HidProtocolStatus GetCaps([In] byte[] preparsed_data, ref HidProtocolCaps capabilities);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetData")]
public static extern HidProtocolStatus GetData(HidProtocolReportType type,
IntPtr data, ref int data_length,
[In] byte[] preparsed_data, IntPtr report, int report_length);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetData")]
public static extern HidProtocolStatus GetData(HidProtocolReportType type,
[Out] HidProtocolData[] data, ref int data_length,
[In] byte[] preparsed_data, IntPtr report, int report_length);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetLinkCollectionNodes")]
public static extern HidProtocolStatus GetLinkCollectionNodes(
[Out] HidProtocolLinkCollectionNode[] LinkCollectionNodes,
ref int LinkCollectionNodesLength,
[In] byte[] PreparsedData);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetScaledUsageValue")]
static public extern HidProtocolStatus GetScaledUsageValue(HidProtocolReportType type,
HIDPage usage_page, short link_collection, short usage, ref int usage_value,
[In] byte[] preparsed_data, IntPtr report, int report_length);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetSpecificButtonCaps")]
public static extern HidProtocolStatus GetSpecificButtonCaps(HidProtocolReportType ReportType,
HIDPage UsagePage, ushort LinkCollection, ushort Usage,
[Out] HidProtocolButtonCaps[] ButtonCaps, ref ushort ButtonCapsLength,
[In] byte[] PreparsedData);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetUsages")]
unsafe public static extern HidProtocolStatus GetUsages(HidProtocolReportType type,
HIDPage usage_page, short link_collection, short* usage_list, ref int usage_length,
[In] byte[] preparsed_data, IntPtr report, int report_length);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetUsageValue")]
public static extern HidProtocolStatus GetUsageValue(HidProtocolReportType type,
HIDPage usage_page, short link_collection, short usage, ref uint usage_value,
[In] byte[] preparsed_data, IntPtr report, int report_length);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetValueCaps")]
public static extern HidProtocolStatus GetValueCaps(HidProtocolReportType type,
IntPtr caps, ref ushort caps_length, [In] byte[] preparsed_data);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_GetValueCaps")]
public static extern HidProtocolStatus GetValueCaps(HidProtocolReportType type,
[Out] HidProtocolValueCaps[] caps, ref ushort caps_length, [In] byte[] preparsed_data);
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, SetLastError = true, EntryPoint = "HidP_MaxDataListLength")]
public static extern int MaxDataListLength(HidProtocolReportType type, [In] byte[] preparsed_data);
}
enum HidProtocolCollectionType : byte
{
}
enum HidProtocolReportType : ushort
{
Input,
Output,
Feature
}
enum HidProtocolStatus : uint
{
Success = 0x00110000,
Null = 0x80110001,
InvalidPreparsedData = 0xc0110001,
InvalidReportType = 0xc0110002,
InvalidReportLength = 0xc0110003,
UsageNotFound = 0xc0110004,
ValueOutOfRange = 0xc0110005,
BadLogPhyValues = 0xc0110006,
BufferTooSmall = 0xc0110007,
InternallError = 0xc0110008,
I8042TransNotKnown = 0xc0110009,
IncompatibleReportId = 0xc011000a,
NotValueArray = 0xc011000b,
IsValueArray = 0xc011000c,
DataIndexNotFound = 0xc011000d,
DataIndexOutOfRange = 0xc011000e,
ButtonNotPressed = 0xc011000f,
ReportDoesNotExist = 0xc0110010,
NotImplemented = 0xc0110020,
}
[StructLayout(LayoutKind.Explicit)]
struct HidProtocolButtonCaps
{
[FieldOffset(0)] public HIDPage UsagePage;
[FieldOffset(2)] public byte ReportID;
[FieldOffset(3), MarshalAs(UnmanagedType.U1)] public bool IsAlias;
[FieldOffset(4)] public short BitField;
[FieldOffset(6)] public short LinkCollection;
[FieldOffset(8)] public short LinkUsage;
[FieldOffset(10)] public short LinkUsagePage;
[FieldOffset(12), MarshalAs(UnmanagedType.U1)] public bool IsRange;
[FieldOffset(13), MarshalAs(UnmanagedType.U1)] public bool IsStringRange;
[FieldOffset(14), MarshalAs(UnmanagedType.U1)] public bool IsDesignatorRange;
[FieldOffset(15), MarshalAs(UnmanagedType.U1)] public bool IsAbsolute;
//[FieldOffset(16)] unsafe fixed int Reserved[10]; // no need when LayoutKind.Explicit
[FieldOffset(56)] public HidProtocolRange Range;
[FieldOffset(56)] public HidProtocolNotRange NotRange;
}
[StructLayout(LayoutKind.Sequential)]
struct HidProtocolCaps
{
public short Usage;
public short UsagePage;
public ushort InputReportByteLength;
public ushort OutputReportByteLength;
public ushort FeatureReportByteLength;
unsafe fixed ushort Reserved[17];
public ushort NumberLinkCollectionNodes;
public ushort NumberInputButtonCaps;
public ushort NumberInputValueCaps;
public ushort NumberInputDataIndices;
public ushort NumberOutputButtonCaps;
public ushort NumberOutputValueCaps;
public ushort NumberOutputDataIndices;
public ushort NumberFeatureButtonCaps;
public ushort NumberFeatureValueCaps;
public ushort NumberFeatureDataIndices;
}
[StructLayout(LayoutKind.Explicit)]
struct HidProtocolData
{
[FieldOffset(0)] public short DataIndex;
//[FieldOffset(2)] public short Reserved;
[FieldOffset(4)] public int RawValue;
[FieldOffset(4), MarshalAs(UnmanagedType.U1)] public bool On;
}
[StructLayout(LayoutKind.Sequential)]
struct HidProtocolNotRange
{
public short Usage;
short Reserved1;
public short StringIndex;
short Reserved2;
public short DesignatorIndex;
short Reserved3;
public short DataIndex;
short Reserved4;
}
[StructLayout(LayoutKind.Sequential)]
struct HidProtocolLinkCollectionNode
{
public ushort LinkUsage;
public HIDPage LinkUsagePage;
public ushort Parent;
public ushort NumberOfChildren;
public ushort NextSibling;
public ushort FirstChild;
int bitfield;
public IntPtr UserContext;
public HidProtocolCollectionType CollectionType
{
get
{
return (HidProtocolCollectionType)(bitfield & 0xff);
}
}
public bool IsAlias
{
get
{
return (bitfield & 0x100) == 1;
}
}
}
struct HidProtocolRange
{
#pragma warning disable 0649
public short UsageMin;
public short UsageMax;
public short StringMin;
public short StringMax;
public short DesignatorMin;
public short DesignatorMax;
public short DataIndexMin;
public short DataIndexMax;
#pragma warning restore 0649
}
[StructLayout(LayoutKind.Explicit)]
struct HidProtocolValueCaps
{
[FieldOffset(0)] public HIDPage UsagePage;
[FieldOffset(2)] public byte ReportID;
[FieldOffset(3), MarshalAs(UnmanagedType.U1)] public bool IsAlias;
[FieldOffset(4)] public ushort BitField;
[FieldOffset(6)] public short LinkCollection;
[FieldOffset(8)] public ushort LinkUsage;
[FieldOffset(10)] public ushort LinkUsagePage;
[FieldOffset(12), MarshalAs(UnmanagedType.U1)] public bool IsRange;
[FieldOffset(13), MarshalAs(UnmanagedType.U1)] public bool IsStringRange;
[FieldOffset(14), MarshalAs(UnmanagedType.U1)] public bool IsDesignatorRange;
[FieldOffset(15), MarshalAs(UnmanagedType.U1)] public bool IsAbsolute;
[FieldOffset(16), MarshalAs(UnmanagedType.U1)] public bool HasNull;
[FieldOffset(17)] byte Reserved;
[FieldOffset(18)] public short BitSize;
[FieldOffset(20)] public short ReportCount;
//[FieldOffset(22)] ushort Reserved2a;
//[FieldOffset(24)] ushort Reserved2b;
//[FieldOffset(26)] ushort Reserved2c;
//[FieldOffset(28)] ushort Reserved2d;
//[FieldOffset(30)] ushort Reserved2e;
[FieldOffset(32)] public int UnitsExp;
[FieldOffset(36)] public int Units;
[FieldOffset(40)] public int LogicalMin;
[FieldOffset(44)] public int LogicalMax;
[FieldOffset(48)] public int PhysicalMin;
[FieldOffset(52)] public int PhysicalMax;
[FieldOffset(56)] public HidProtocolRange Range;
[FieldOffset(56)] public HidProtocolNotRange NotRange;
}
}

View file

@ -40,7 +40,11 @@ namespace OpenTK.Platform.Windows
class WinFactory : PlatformFactoryBase
{
readonly object SyncRoot = new object();
IInputDriver2 inputDriver;
// The input drivers must be constructed lazily, *after* the
// WinFactory constructor has finished running. The reason is
// that they call WinFactory methods internally.
WinRawInput rawinput_driver; // For keyboard and mouse input
internal static IntPtr OpenGLHandle { get; private set; }
const string OpenGLName = "OPENGL32.DLL";
@ -112,41 +116,45 @@ namespace OpenTK.Platform.Windows
public override OpenTK.Input.IKeyboardDriver2 CreateKeyboardDriver()
{
return InputDriver.KeyboardDriver;
return RawInputDriver.KeyboardDriver;
}
public override OpenTK.Input.IMouseDriver2 CreateMouseDriver()
{
return InputDriver.MouseDriver;
return RawInputDriver.MouseDriver;
}
public override OpenTK.Input.IGamePadDriver CreateGamePadDriver()
{
return InputDriver.GamePadDriver;
return new MappedGamePadDriver();
}
public override IJoystickDriver2 CreateJoystickDriver()
{
return InputDriver.JoystickDriver;
return RawInputDriver.JoystickDriver;
}
#endregion
IInputDriver2 InputDriver
#region Private Members
WinRawInput RawInputDriver
{
get
{
lock (SyncRoot)
{
if (inputDriver == null)
if (rawinput_driver == null)
{
inputDriver = new WinRawInput();
rawinput_driver = new WinRawInput();
}
return inputDriver;
return rawinput_driver;
}
}
}
#endregion
#region IDisposable Members
protected override void Dispose(bool manual)
@ -155,7 +163,7 @@ namespace OpenTK.Platform.Windows
{
if (manual)
{
InputDriver.Dispose();
rawinput_driver.Dispose();
}
base.Dispose(manual);

View file

@ -83,7 +83,7 @@ namespace OpenTK.Platform.Windows
const ClassStyle DefaultClassStyle = ClassStyle.OwnDC;
const long ExtendedBit = 1 << 24; // Used to distinguish left and right control, alt and enter keys.
public static readonly uint ShiftLeftScanCode = Functions.MapVirtualKey(VirtualKeys.LSHIFT, 0);
public static readonly uint ShiftRightScanCode = Functions.MapVirtualKey(VirtualKeys.RSHIFT, 0);
public static readonly uint ControlLeftScanCode = Functions.MapVirtualKey(VirtualKeys.LCONTROL, 0);

View file

@ -33,7 +33,7 @@ using OpenTK.Input;
namespace OpenTK.Platform.Windows
{
abstract class WinInputBase : IInputDriver2
abstract class WinInputBase
{
#region Fields
@ -158,11 +158,11 @@ namespace OpenTK.Platform.Windows
#endregion
#region IInputDriver2 Members
#region Public Members
public abstract IMouseDriver2 MouseDriver { get; }
public abstract IKeyboardDriver2 KeyboardDriver { get; }
public abstract IGamePadDriver GamePadDriver { get; }
public abstract IJoystickDriver2 JoystickDriver { get; }

View file

@ -1,622 +0,0 @@
#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2008 the Open Toolkit library, except where noted.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Win32;
using OpenTK.Input;
namespace OpenTK.Platform.Windows
{
sealed class WinMMJoystick : IJoystickDriver2
{
#region Fields
readonly object sync = new object();
List<JoystickDevice> sticks = new List<JoystickDevice>();
// Matches a WinMM device index to a specific stick above
readonly Dictionary<int, int> index_to_stick =
new Dictionary<int, int>();
// Matches a player index to a WinMM device index
readonly Dictionary<int, int> player_to_index =
new Dictionary<int, int>();
// Todo: Read the joystick name from the registry.
//static readonly string RegistryJoyConfig = @"Joystick%dConfiguration";
//static readonly string RegistryJoyName = @"Joystick%dOEMName";
//static readonly string RegstryJoyCurrent = @"CurrentJoystickSettings";
bool disposed;
#endregion
#region Constructors
public WinMMJoystick()
{
RefreshDevices();
}
#endregion
#region Internal Members
internal void RefreshDevices()
{
for (int i = 0; i < sticks.Count; i++)
{
CloseJoystick(i);
}
// WinMM supports up to 16 joysticks.
for (int i = 0; i < UnsafeNativeMethods.joyGetNumDevs(); i++)
{
OpenJoystick(i);
}
}
#endregion
#region Private Members
JoystickDevice<WinMMJoyDetails> OpenJoystick(int number)
{
lock (sync)
{
JoystickDevice<WinMMJoyDetails> stick = null;
JoyCaps caps;
JoystickError result = UnsafeNativeMethods.joyGetDevCaps(number, out caps, JoyCaps.SizeInBytes);
if (result == JoystickError.NoError)
{
if (caps.NumAxes > JoystickState.MaxAxes)
{
Debug.Print("[Input] Device has {0} axes, which is higher than OpenTK maximum {1}. Please report a bug at http://www.opentk.com",
caps.NumAxes, JoystickState.MaxAxes);
caps.NumAxes = JoystickState.MaxAxes;
}
if (caps.NumAxes > JoystickState.MaxButtons)
{
Debug.Print("[Input] Device has {0} buttons, which is higher than OpenTK maximum {1}. Please report a bug at http://www.opentk.com",
caps.NumButtons, JoystickState.MaxButtons);
caps.NumButtons = JoystickState.MaxButtons;
}
JoystickCapabilities joycaps = new JoystickCapabilities(
caps.NumAxes,
caps.NumButtons,
(caps.Capabilities & JoystCapsFlags.HasPov) != 0 ? 1 : 0,
true);
int num_axes = caps.NumAxes;
if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
num_axes += 2;
stick = new JoystickDevice<WinMMJoyDetails>(number, num_axes, caps.NumButtons);
stick.Details = new WinMMJoyDetails(joycaps);
// Make sure to reverse the vertical axes, so that +1 points up and -1 points down.
for (int axis = 0; axis < caps.NumAxes; axis++)
{
if (axis % 2 == 1)
{
stick.Details.Min[axis] = caps.GetMax(axis);
stick.Details.Max[axis] = caps.GetMin(axis);
}
else
{
stick.Details.Min[axis] = caps.GetMin(axis);
stick.Details.Max[axis] = caps.GetMax(axis);
}
}
if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
{
stick.Details.PovType = PovType.Exists;
if ((caps.Capabilities & JoystCapsFlags.HasPov4Dir) != 0)
stick.Details.PovType |= PovType.Discrete;
if ((caps.Capabilities & JoystCapsFlags.HasPovContinuous) != 0)
stick.Details.PovType |= PovType.Continuous;
}
// Todo: Implement joystick name detection for WinMM.
stick.Description = String.Format("Joystick/Joystick #{0} ({1} axes, {2} buttons)", number, stick.Axis.Count, stick.Button.Count);
// Todo: Try to get the device name from the registry. Oh joy!
//string key_path = String.Format("{0}\\{1}\\{2}", RegistryJoyConfig, caps.RegKey, RegstryJoyCurrent);
//RegistryKey key = Registry.LocalMachine.OpenSubKey(key_path, false);
//if (key == null)
// key = Registry.CurrentUser.OpenSubKey(key_path, false);
//if (key == null)
// stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons)", number, stick.Axis.Count, stick.Button.Count);
//else
//{
// key.Close();
//}
Debug.Print("Found joystick on device number {0}", number);
index_to_stick.Add(number, sticks.Count);
player_to_index.Add(player_to_index.Count, number);
sticks.Add(stick);
}
return stick;
}
}
void UnplugJoystick(int player_index)
{
// Reset the system configuration. Without this,
// joysticks that are reconnected on different
// ports are given different ids, making it
// impossible to reconnect a disconnected user.
UnsafeNativeMethods.joyConfigChanged(0);
Debug.Print("[Win] WinMM joystick {0} unplugged", player_index);
CloseJoystick(player_index);
}
void CloseJoystick(int player_index)
{
lock (sync)
{
if (IsValid(player_index))
{
int device_index = player_to_index[player_index];
JoystickDevice<WinMMJoyDetails> stick =
sticks[index_to_stick[device_index]] as JoystickDevice<WinMMJoyDetails>;
if (stick != null)
{
index_to_stick.Remove(device_index);
player_to_index.Remove(player_index);
}
}
}
}
bool IsValid(int player_index)
{
if (player_to_index.ContainsKey(player_index))
{
return true;
}
//else if (index >= 0 && index < UnsafeNativeMethods.joyGetNumDevs())
//{
// return OpenJoystick(index) != null;
//}
return false;
}
static short CalculateOffset(int pos, int min, int max)
{
int offset = (ushort.MaxValue * (pos - min)) / (max - min) - short.MaxValue;
return (short)offset;
}
#endregion
#region IJoystickDriver2 Members
public JoystickCapabilities GetCapabilities(int player_index)
{
lock (sync)
{
if (IsValid(player_index))
{
int device_index = player_to_index[player_index];
JoystickDevice<WinMMJoyDetails> stick =
sticks[index_to_stick[device_index]] as JoystickDevice<WinMMJoyDetails>;
return stick.Details.Capabilities;
}
return new JoystickCapabilities();
}
}
public JoystickState GetState(int player_index)
{
lock (sync)
{
JoystickState state = new JoystickState();
if (IsValid(player_index))
{
int device_index = player_to_index[player_index];
int index = index_to_stick[device_index];
JoystickDevice<WinMMJoyDetails> stick =
sticks[index] as JoystickDevice<WinMMJoyDetails>;
// For joysticks with fewer than three axes or four buttons, we must
// use joyGetPos; otherwise, joyGetPosEx. This is not just a cosmetic
// difference, simple devices will return incorrect results if we use
// joyGetPosEx on them.
if (stick.Details.Capabilities.AxisCount <= 3 || stick.Details.Capabilities.ButtonCount <= 4)
{
// Use joyGetPos
JoyInfo info = new JoyInfo();
JoystickError result = UnsafeNativeMethods.joyGetPos(device_index, ref info);
if (result == JoystickError.NoError)
{
for (int i = 0; i < stick.Details.Capabilities.AxisCount; i++)
{
state.SetAxis(JoystickAxis.Axis0 + i, CalculateOffset(info.GetAxis(i), stick.Details.Min[i], stick.Details.Max[i]));
}
for (int i = 0; i < stick.Details.Capabilities.ButtonCount; i++)
{
state.SetButton(JoystickButton.Button0 + i, (info.Buttons & 1 << i) != 0);
}
state.SetIsConnected(true);
}
else if (result == JoystickError.Unplugged)
{
UnplugJoystick(player_index);
}
}
else
{
// Use joyGetPosEx
JoyInfoEx info_ex = new JoyInfoEx();
info_ex.Size = JoyInfoEx.SizeInBytes;
info_ex.Flags = JoystickFlags.All;
JoystickError result = UnsafeNativeMethods.joyGetPosEx(device_index, ref info_ex);
if (result == JoystickError.NoError)
{
for (int i = 0; i < stick.Details.Capabilities.AxisCount; i++)
{
state.SetAxis(JoystickAxis.Axis0 + i, CalculateOffset(info_ex.GetAxis(i), stick.Details.Min[i], stick.Details.Max[i]));
}
for (int i = 0; i < stick.Details.Capabilities.ButtonCount; i++)
{
state.SetButton(JoystickButton.Button0 + i, (info_ex.Buttons & 1 << i) != 0);
}
for (int i = 0; i < stick.Details.Capabilities.HatCount; i++)
{
// A discrete POV returns specific values for left, right, etc.
// A continuous POV returns an integer indicating an angle in degrees * 100, e.g. 18000 == 180.00 degrees.
// The vast majority of joysticks have discrete POVs, so we'll treat all of them as discrete for simplicity.
if ((JoystickPovPosition)info_ex.Pov != JoystickPovPosition.Centered)
{
HatPosition hatpos = HatPosition.Centered;
if (info_ex.Pov < 4500 || info_ex.Pov >= 31500)
hatpos |= HatPosition.Up;
if (info_ex.Pov >= 4500 && info_ex.Pov < 13500)
hatpos |= HatPosition.Right;
if (info_ex.Pov >= 13500 && info_ex.Pov < 22500)
hatpos |= HatPosition.Down;
if (info_ex.Pov >= 22500 && info_ex.Pov < 31500)
hatpos |= HatPosition.Left;
state.SetHat(JoystickHat.Hat0 + i, new JoystickHatState(hatpos));
}
}
state.SetIsConnected(true);
}
else if (result == JoystickError.Unplugged)
{
UnplugJoystick(player_index);
}
}
}
return state;
}
}
public Guid GetGuid(int index)
{
lock (sync)
{
Guid guid = new Guid();
if (IsValid(index))
{
// Todo: implement WinMM Guid retrieval
}
return guid;
}
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool manual)
{
if (!disposed)
{
if (manual)
{
}
disposed = true;
}
}
~WinMMJoystick()
{
Dispose(false);
}
#endregion
#region UnsafeNativeMethods
[Flags]
enum JoystickFlags
{
X = 0x1,
Y = 0x2,
Z = 0x4,
R = 0x8,
U = 0x10,
V = 0x20,
Pov = 0x40,
Buttons = 0x80,
All = X | Y | Z | R | U | V | Pov | Buttons
}
enum JoystickError : uint
{
NoError = 0,
InvalidParameters = 165,
NoCanDo = 166,
Unplugged = 167
//MM_NoDriver = 6,
//MM_InvalidParameter = 11
}
[Flags]
enum JoystCapsFlags
{
HasZ = 0x1,
HasR = 0x2,
HasU = 0x4,
HasV = 0x8,
HasPov = 0x16,
HasPov4Dir = 0x32,
HasPovContinuous = 0x64
}
enum JoystickPovPosition : ushort
{
Centered = 0xFFFF,
Forward = 0,
Right = 9000,
Backward = 18000,
Left = 27000
}
struct JoyCaps
{
public ushort Mid;
public ushort ProductId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string ProductName;
public int XMin;
public int XMax;
public int YMin;
public int YMax;
public int ZMin;
public int ZMax;
public int NumButtons;
public int PeriodMin;
public int PeriodMax;
public int RMin;
public int RMax;
public int UMin;
public int UMax;
public int VMin;
public int VMax;
public JoystCapsFlags Capabilities;
public int MaxAxes;
public int NumAxes;
public int MaxButtons;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string RegKey;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string OemVxD;
public static readonly int SizeInBytes;
static JoyCaps()
{
SizeInBytes = Marshal.SizeOf(default(JoyCaps));
}
public int GetMin(int i)
{
switch (i)
{
case 0: return XMin;
case 1: return YMin;
case 2: return ZMin;
case 3: return RMin;
case 4: return UMin;
case 5: return VMin;
default: return 0;
}
}
public int GetMax(int i)
{
switch (i)
{
case 0: return XMax;
case 1: return YMax;
case 2: return ZMax;
case 3: return RMax;
case 4: return UMax;
case 5: return VMax;
default: return 0;
}
}
}
struct JoyInfo
{
public int XPos;
public int YPos;
public int ZPos;
public uint Buttons;
public int GetAxis(int i)
{
switch (i)
{
case 0: return XPos;
case 1: return YPos;
case 2: return ZPos;
default: return 0;
}
}
}
struct JoyInfoEx
{
public int Size;
[MarshalAs(UnmanagedType.I4)]
public JoystickFlags Flags;
public int XPos;
public int YPos;
public int ZPos;
public int RPos;
public int UPos;
public int VPos;
public uint Buttons;
public uint ButtonNumber;
public int Pov;
#pragma warning disable 0169
uint Reserved1;
uint Reserved2;
#pragma warning restore 0169
public static readonly int SizeInBytes;
static JoyInfoEx()
{
SizeInBytes = Marshal.SizeOf(default(JoyInfoEx));
}
public int GetAxis(int i)
{
switch (i)
{
case 0: return XPos;
case 1: return YPos;
case 2: return ZPos;
case 3: return RPos;
case 4: return UPos;
case 5: return VPos;
default: return 0;
}
}
}
static class UnsafeNativeMethods
{
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern JoystickError joyGetDevCaps(int uJoyID, out JoyCaps pjc, int cbjc);
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern JoystickError joyGetPos(int uJoyID, ref JoyInfo pji);
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern JoystickError joyGetPosEx(int uJoyID, ref JoyInfoEx pji);
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern int joyGetNumDevs();
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern JoystickError joyConfigChanged(int flags);
}
#endregion
#region enum PovType
[Flags]
enum PovType
{
None = 0x0,
Exists = 0x1,
Discrete = 0x2,
Continuous = 0x4
}
#endregion
#region struct WinMMJoyDetails
struct WinMMJoyDetails
{
public readonly int[] Min, Max; // Minimum and maximum offset of each axis.
public PovType PovType;
public JoystickCapabilities Capabilities;
public WinMMJoyDetails(JoystickCapabilities caps)
: this()
{
Min = new int[caps.AxisCount];
Max = new int[caps.AxisCount];
Capabilities = caps;
}
public float CalculateOffset(float pos, int axis)
{
float offset = (2 * (pos - Min[axis])) / (Max[axis] - Min[axis]) - 1;
if (offset > 1)
return 1;
else if (offset < -1)
return -1;
else if (offset < 0.001f && offset > -0.001f)
return 0;
else
return offset;
}
}
#endregion
}
}

View file

@ -38,12 +38,10 @@ namespace OpenTK.Platform.Windows
#region Fields
// Input event data.
static RawInput data = new RawInput();
WinRawKeyboard keyboard_driver;
WinRawMouse mouse_driver;
IGamePadDriver gamepad_driver;
IJoystickDriver2 joystick_driver;
WinRawJoystick joystick_driver;
IntPtr DevNotifyHandle;
static readonly Guid DeviceInterfaceHid = new Guid("4D1E55B2-F16F-11CF-88CB-001111000030");
@ -88,46 +86,53 @@ namespace OpenTK.Platform.Windows
#region WindowProcedure
// Processes the input Windows Message, routing the buffer to the correct Keyboard, Mouse or HID.
protected override IntPtr WindowProcedure(
protected unsafe override IntPtr WindowProcedure(
IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
{
switch (message)
try
{
case WindowMessage.INPUT:
int size = 0;
// Get the size of the input buffer
Functions.GetRawInputData(lParam, GetRawInputDataEnum.INPUT,
IntPtr.Zero, ref size, API.RawInputHeaderSize);
// Read the actual raw input structure
if (size == Functions.GetRawInputData(lParam, GetRawInputDataEnum.INPUT,
out data, ref size, API.RawInputHeaderSize))
{
switch (data.Header.Type)
switch (message)
{
case WindowMessage.INPUT:
{
case RawInputDeviceType.KEYBOARD:
if (((WinRawKeyboard)KeyboardDriver).ProcessKeyboardEvent(data))
return IntPtr.Zero;
break;
// Retrieve the raw input data buffer
RawInputHeader header;
if (Functions.GetRawInputData(lParam, out header) == RawInputHeader.SizeInBytes)
{
switch (header.Type)
{
case RawInputDeviceType.KEYBOARD:
if (((WinRawKeyboard)KeyboardDriver).ProcessKeyboardEvent(lParam))
return IntPtr.Zero;
break;
case RawInputDeviceType.MOUSE:
if (((WinRawMouse)MouseDriver).ProcessMouseEvent(data))
return IntPtr.Zero;
break;
case RawInputDeviceType.MOUSE:
if (((WinRawMouse)MouseDriver).ProcessMouseEvent(lParam))
return IntPtr.Zero;
break;
case RawInputDeviceType.HID:
break;
case RawInputDeviceType.HID:
if (((WinRawJoystick)JoystickDriver).ProcessEvent(lParam))
return IntPtr.Zero;
break;
}
}
}
}
break;
break;
case WindowMessage.DEVICECHANGE:
((WinRawKeyboard)KeyboardDriver).RefreshDevices();
((WinRawMouse)MouseDriver).RefreshDevices();
((WinMMJoystick)JoystickDriver).RefreshDevices();
break;
case WindowMessage.DEVICECHANGE:
((WinRawKeyboard)KeyboardDriver).RefreshDevices();
((WinRawMouse)MouseDriver).RefreshDevices();
((WinRawJoystick)JoystickDriver).RefreshDevices();
break;
}
return base.WindowProcedure(handle, message, wParam, lParam);
}
catch (Exception e)
{
Debug.Print("[WinRawInput] Caught unhandled exception {0}", e);
return IntPtr.Zero;
}
return base.WindowProcedure(handle, message, wParam, lParam);
}
#endregion
@ -138,17 +143,7 @@ namespace OpenTK.Platform.Windows
{
keyboard_driver = new WinRawKeyboard(Parent.Handle);
mouse_driver = new WinRawMouse(Parent.Handle);
joystick_driver = new WinMMJoystick();
try
{
gamepad_driver = new XInputJoystick();
}
catch (Exception)
{
Debug.Print("[Win] XInput driver not supported, falling back to WinMM");
gamepad_driver = new MappedGamePadDriver();
}
joystick_driver = new WinRawJoystick(Parent.Handle);
DevNotifyHandle = RegisterForDeviceNotifications(Parent);
}
@ -181,9 +176,19 @@ namespace OpenTK.Platform.Windows
#endregion
#endregion
#region GetDeviceList
#region IInputDriver2 Members
public static RawInputDeviceList[] GetDeviceList()
{
int count = WinRawInput.DeviceCount;
RawInputDeviceList[] ridl = new RawInputDeviceList[count];
for (int i = 0; i < count; i++)
ridl[i] = new RawInputDeviceList();
Functions.GetRawInputDeviceList(ridl, ref count, API.RawInputDeviceListSize);
return ridl;
}
#endregion
public override IKeyboardDriver2 KeyboardDriver
{
@ -195,11 +200,6 @@ namespace OpenTK.Platform.Windows
get { return mouse_driver; }
}
public override IGamePadDriver GamePadDriver
{
get { return gamepad_driver; }
}
public override IJoystickDriver2 JoystickDriver
{
get { return joystick_driver; }

View file

@ -0,0 +1,830 @@
#region License
//
// WinRawJoystick.cs
//
// Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2014 Stefanos Apostolopoulos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using OpenTK.Input;
using OpenTK.Platform.Common;
namespace OpenTK.Platform.Windows
{
class WinRawJoystick : IJoystickDriver2
{
class Device
{
public IntPtr Handle;
JoystickCapabilities Capabilities;
JoystickState State;
Guid Guid;
internal readonly List<HidProtocolValueCaps> AxisCaps =
new List<HidProtocolValueCaps>();
internal readonly List<HidProtocolButtonCaps> ButtonCaps =
new List<HidProtocolButtonCaps>();
internal readonly bool IsXInput;
internal readonly int XInputIndex;
readonly Dictionary<int, JoystickAxis> axes =
new Dictionary<int,JoystickAxis>();
readonly Dictionary<int, JoystickButton> buttons =
new Dictionary<int, JoystickButton>();
readonly Dictionary<int, JoystickHat> hats =
new Dictionary<int, JoystickHat>();
#region Constructors
public Device(IntPtr handle, Guid guid, bool is_xinput, int xinput_index)
{
Handle = handle;
Guid = guid;
IsXInput = is_xinput;
XInputIndex = xinput_index;
}
#endregion
#region Public Members
public void ClearButtons()
{
State.ClearButtons();
}
public void SetAxis(short collection, HIDPage page, short usage, short value)
{
JoystickAxis axis = GetAxis(collection, page, usage);
State.SetAxis(axis, value);
}
public void SetButton(short collection, HIDPage page, short usage, bool value)
{
JoystickButton button = GetButton(collection, page, usage);
State.SetButton(button, value);
}
public void SetHat(short collection, HIDPage page, short usage, HatPosition pos)
{
JoystickHat hat = GetHat(collection, page, usage);
State.SetHat(hat, new JoystickHatState(pos));
}
public void SetConnected(bool value)
{
Capabilities.SetIsConnected(value);
State.SetIsConnected(value);
}
public JoystickCapabilities GetCapabilities()
{
Capabilities = new JoystickCapabilities(
axes.Count, buttons.Count, hats.Count,
Capabilities.IsConnected);
return Capabilities;
}
internal void SetCapabilities(JoystickCapabilities caps)
{
Capabilities = caps;
}
public Guid GetGuid()
{
return Guid;
}
public JoystickState GetState()
{
return State;
}
#endregion
#region Private Members
static int MakeKey(short collection, HIDPage page, short usage)
{
byte coll_byte = unchecked((byte)collection);
byte page_byte = unchecked((byte)(((ushort)page & 0xff00) >> 8 | ((ushort)page & 0xff)));
return (coll_byte << 24) | (page_byte << 16) | unchecked((ushort)usage);
}
JoystickAxis GetAxis(short collection, HIDPage page, short usage)
{
int key = MakeKey(collection, page, usage);
if (!axes.ContainsKey(key))
{
JoystickAxis axis = HidHelper.TranslateJoystickAxis(page, usage);
axes.Add(key, axis);
}
return axes[key];
}
JoystickButton GetButton(short collection, HIDPage page, short usage)
{
int key = MakeKey(collection, page, usage);
if (!buttons.ContainsKey(key))
{
buttons.Add(key, JoystickButton.Button0 + buttons.Count);
}
return buttons[key];
}
JoystickHat GetHat(short collection, HIDPage page, short usage)
{
int key = MakeKey(collection, page, usage);
if (!hats.ContainsKey(key))
{
hats.Add(key, JoystickHat.Hat0 + hats.Count);
}
return hats[key];
}
#endregion
}
static readonly string TypeName = typeof(WinRawJoystick).Name;
XInputJoystick XInput = new XInputJoystick();
// Defines which types of HID devices we are interested in
readonly RawInputDevice[] DeviceTypes;
readonly IntPtr Window;
readonly object UpdateLock = new object();
readonly DeviceCollection<Device> Devices = new DeviceCollection<Device>();
byte[] HIDData = new byte[1024];
byte[] PreparsedData = new byte[1024];
HidProtocolData[] DataBuffer = new HidProtocolData[16];
public WinRawJoystick(IntPtr window)
{
Debug.WriteLine("Using WinRawJoystick.");
Debug.Indent();
if (window == IntPtr.Zero)
throw new ArgumentNullException("window");
Window = window;
DeviceTypes = new RawInputDevice[]
{
new RawInputDevice(HIDUsageGD.Joystick, RawInputDeviceFlags.DEVNOTIFY | RawInputDeviceFlags.INPUTSINK, window),
new RawInputDevice(HIDUsageGD.GamePad, RawInputDeviceFlags.DEVNOTIFY | RawInputDeviceFlags.INPUTSINK, window),
};
if (!Functions.RegisterRawInputDevices(DeviceTypes, DeviceTypes.Length, API.RawInputDeviceSize))
{
Debug.Print("[Warning] Raw input registration failed with error: {0}.",
Marshal.GetLastWin32Error());
}
else
{
Debug.Print("[WinRawJoystick] Registered for raw input");
}
RefreshDevices();
Debug.Unindent();
}
#region Public Members
public void RefreshDevices()
{
// Mark all devices as disconnected. We will check which of those
// are connected below.
foreach (var device in Devices)
{
device.SetConnected(false);
}
// Discover joystick devices
int xinput_device_count = 0;
foreach (RawInputDeviceList dev in WinRawInput.GetDeviceList())
{
// Skip non-joystick devices
if (dev.Type != RawInputDeviceType.HID)
continue;
// We use the device handle as the hardware id.
// This works, but the handle will change whenever the
// device is unplugged/replugged. We compensate for this
// by checking device GUIDs, below.
// Note: we cannot use the GUID as the hardware id,
// because it is costly to query (and we need to query
// that every time we process a device event.)
IntPtr handle = dev.Device;
bool is_xinput = IsXInput(handle);
Guid guid = GetDeviceGuid(handle);
long hardware_id = handle.ToInt64();
Device device = Devices.FromHardwareId(hardware_id);
if (device != null)
{
// We have already opened this device, mark it as connected
device.SetConnected(true);
}
else
{
device = new Device(handle, guid, is_xinput,
is_xinput ? xinput_device_count++ : 0);
// This is a new device, query its capabilities and add it
// to the device list
if (!QueryDeviceCaps(device))
{
continue;
}
device.SetConnected(true);
// Check if a disconnected device with identical GUID already exists.
// If so, replace that device with this instance.
Device match = null;
foreach (Device candidate in Devices)
{
if (candidate.GetGuid() == guid && !candidate.GetCapabilities().IsConnected)
{
match = candidate;
}
}
if (match != null)
{
Devices.Remove(match.Handle.ToInt64());
}
Devices.Add(hardware_id, device);
Debug.Print("[{0}] Connected joystick {1} ({2})",
GetType().Name, device.GetGuid(), device.GetCapabilities());
}
}
}
public unsafe bool ProcessEvent(IntPtr raw)
{
// Query the size of the raw HID data buffer
int size = 0;
Functions.GetRawInputData(raw, GetRawInputDataEnum.INPUT, IntPtr.Zero, ref size, RawInputHeader.SizeInBytes);
if (size > HIDData.Length)
{
Array.Resize(ref HIDData, size);
}
// Retrieve the raw HID data buffer
if (Functions.GetRawInputData(raw, HIDData) > 0)
{
fixed (byte* pdata = HIDData)
{
RawInput* rin = (RawInput*)pdata;
IntPtr handle = rin->Header.Device;
Device stick = GetDevice(handle);
if (stick == null)
{
Debug.Print("[WinRawJoystick] Unknown device {0}", handle);
return false;
}
if (stick.IsXInput)
{
return true;
}
if (!GetPreparsedData(handle, ref PreparsedData))
{
return false;
}
// Query current state
// Allocate enough storage to hold the data of the current report
int report_count = HidProtocol.MaxDataListLength(HidProtocolReportType.Input, PreparsedData);
if (report_count == 0)
{
Debug.Print("[WinRawJoystick] HidProtocol.MaxDataListLength() failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
// Fill the data buffer
if (DataBuffer.Length < report_count)
{
Array.Resize(ref DataBuffer, report_count);
}
UpdateAxes(rin, stick);
UpdateButtons(rin, stick);
return true;
}
}
return false;
}
HatPosition GetHatPosition(uint value, HidProtocolValueCaps caps)
{
if (caps.LogicalMax == 8)
return (HatPosition)value;
else
return HatPosition.Centered;
}
unsafe void UpdateAxes(RawInput* rin, Device stick)
{
for (int i = 0; i < stick.AxisCaps.Count; i++)
{
if (stick.AxisCaps[i].IsRange)
{
Debug.Print("[{0}] Axis range collections not implemented. Please report your controller type at http://www.opentk.com",
GetType().Name);
continue;
}
HIDPage page = stick.AxisCaps[i].UsagePage;
short usage = stick.AxisCaps[i].NotRange.Usage;
uint value = 0;
short collection = stick.AxisCaps[i].LinkCollection;
HidProtocolStatus status = HidProtocol.GetUsageValue(
HidProtocolReportType.Input,
page, 0, usage, ref value,
PreparsedData,
new IntPtr((void*)&rin->Data.HID.RawData),
rin->Data.HID.Size);
if (status != HidProtocolStatus.Success)
{
Debug.Print("[{0}] HidProtocol.GetScaledUsageValue() failed. Error: {1}",
GetType().Name, status);
continue;
}
if (page == HIDPage.GenericDesktop && (HIDUsageGD)usage == HIDUsageGD.Hatswitch)
{
stick.SetHat(collection, page, usage, GetHatPosition(value, stick.AxisCaps[i]));
}
else
{
short scaled_value = (short)HidHelper.ScaleValue(
(int)((long)value + stick.AxisCaps[i].LogicalMin),
stick.AxisCaps[i].LogicalMin, stick.AxisCaps[i].LogicalMax,
Int16.MinValue, Int16.MaxValue);
stick.SetAxis(collection, page, usage, scaled_value);
}
}
}
unsafe void UpdateButtons(RawInput* rin, Device stick)
{
stick.ClearButtons();
for (int i = 0; i < stick.ButtonCaps.Count; i++)
{
short* usage_list = stackalloc short[(int)JoystickButton.Last + 1];
int usage_length = (int)JoystickButton.Last;
HIDPage page = stick.ButtonCaps[i].UsagePage;
short collection = stick.ButtonCaps[i].LinkCollection;
HidProtocolStatus status = HidProtocol.GetUsages(
HidProtocolReportType.Input,
page, 0, usage_list, ref usage_length,
PreparsedData,
new IntPtr((void*)&rin->Data.HID.RawData),
rin->Data.HID.Size);
if (status != HidProtocolStatus.Success)
{
Debug.Print("[WinRawJoystick] HidProtocol.GetUsages() failed with {0}",
Marshal.GetLastWin32Error());
continue;
}
for (int j = 0; j < usage_length; j++)
{
short usage = *(usage_list + j);
stick.SetButton(collection, page, usage, true);
}
}
}
#endregion
#region Private Members
static bool GetPreparsedData(IntPtr handle, ref byte[] prepared_data)
{
// Query the size of the _HIDP_PREPARSED_DATA structure for this event.
int preparsed_size = 0;
Functions.GetRawInputDeviceInfo(handle, RawInputDeviceInfoEnum.PREPARSEDDATA,
IntPtr.Zero, ref preparsed_size);
if (preparsed_size == 0)
{
Debug.Print("[WinRawJoystick] Functions.GetRawInputDeviceInfo(PARSEDDATA) failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
// Allocate space for _HIDP_PREPARSED_DATA.
// This is an untyped blob of data.
if (prepared_data.Length < preparsed_size)
{
Array.Resize(ref prepared_data, preparsed_size);
}
if (Functions.GetRawInputDeviceInfo(handle, RawInputDeviceInfoEnum.PREPARSEDDATA,
prepared_data, ref preparsed_size) < 0)
{
Debug.Print("[WinRawJoystick] Functions.GetRawInputDeviceInfo(PARSEDDATA) failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
return true;
}
bool QueryDeviceCaps(Device stick)
{
Debug.Print("[{0}] Querying joystick {1}",
TypeName, stick.GetGuid());
try
{
Debug.Indent();
HidProtocolCaps caps;
if (GetPreparsedData(stick.Handle, ref PreparsedData) &&
GetDeviceCaps(stick, PreparsedData, out caps))
{
if (stick.AxisCaps.Count >= JoystickState.MaxAxes ||
stick.ButtonCaps.Count >= JoystickState.MaxButtons)
{
Debug.Print("Device {0} has {1} and {2} buttons. This might be a touch device - skipping.",
stick.Handle, stick.AxisCaps.Count, stick.ButtonCaps.Count);
return false;
}
for (int i = 0; i < stick.AxisCaps.Count; i++)
{
Debug.Print("Analyzing value collection {0} {1} {2}",
i,
stick.AxisCaps[i].IsRange ? "range" : "",
stick.AxisCaps[i].IsAlias ? "alias" : "");
if (stick.AxisCaps[i].IsRange || stick.AxisCaps[i].IsAlias)
{
Debug.Print("Skipping value collection {0}", i);
continue;
}
HIDPage page = stick.AxisCaps[i].UsagePage;
short collection = stick.AxisCaps[i].LinkCollection;
switch (page)
{
case HIDPage.GenericDesktop:
HIDUsageGD gd_usage = (HIDUsageGD)stick.AxisCaps[i].NotRange.Usage;
switch (gd_usage)
{
case HIDUsageGD.X:
case HIDUsageGD.Y:
case HIDUsageGD.Z:
case HIDUsageGD.Rx:
case HIDUsageGD.Ry:
case HIDUsageGD.Rz:
case HIDUsageGD.Slider:
case HIDUsageGD.Dial:
case HIDUsageGD.Wheel:
Debug.Print("Found axis {0} ({1} / {2})",
JoystickAxis.Axis0 + stick.GetCapabilities().AxisCount,
page, (HIDUsageGD)stick.AxisCaps[i].NotRange.Usage);
stick.SetAxis(collection, page, stick.AxisCaps[i].NotRange.Usage, 0);
break;
case HIDUsageGD.Hatswitch:
Debug.Print("Found hat {0} ({1} / {2})",
JoystickHat.Hat0 + stick.GetCapabilities().HatCount,
page, (HIDUsageGD)stick.AxisCaps[i].NotRange.Usage);
stick.SetHat(collection, page, stick.AxisCaps[i].NotRange.Usage, HatPosition.Centered);
break;
default:
Debug.Print("Unknown usage {0} for page {1}",
gd_usage, page);
break;
}
break;
case HIDPage.Simulation:
switch ((HIDUsageSim)stick.AxisCaps[i].NotRange.Usage)
{
case HIDUsageSim.Rudder:
case HIDUsageSim.Throttle:
Debug.Print("Found simulation axis {0} ({1} / {2})",
JoystickAxis.Axis0 + stick.GetCapabilities().AxisCount,
page, (HIDUsageSim)stick.AxisCaps[i].NotRange.Usage);
stick.SetAxis(collection, page, stick.AxisCaps[i].NotRange.Usage, 0);
break;
}
break;
default:
Debug.Print("Unknown page {0}", page);
break;
}
}
for (int i = 0; i < stick.ButtonCaps.Count; i++)
{
Debug.Print("Analyzing button collection {0} {1} {2}",
i,
stick.ButtonCaps[i].IsRange ? "range" : "",
stick.ButtonCaps[i].IsAlias ? "alias" : "");
if (stick.ButtonCaps[i].IsAlias)
{
Debug.Print("Skipping button collection {0}", i);
continue;
}
bool is_range = stick.ButtonCaps[i].IsRange;
HIDPage page = stick.ButtonCaps[i].UsagePage;
short collection = stick.ButtonCaps[i].LinkCollection;
switch (page)
{
case HIDPage.Button:
if (is_range)
{
for (short usage = stick.ButtonCaps[i].Range.UsageMin; usage <= stick.ButtonCaps[i].Range.UsageMax; usage++)
{
Debug.Print("Found button {0} ({1} / {2})",
JoystickButton.Button0 + stick.GetCapabilities().ButtonCount,
page, usage);
stick.SetButton(collection, page, usage, false);
}
}
else
{
Debug.Print("Found button {0} ({1} / {2})",
JoystickButton.Button0 + stick.GetCapabilities().ButtonCount,
page, stick.ButtonCaps[i].NotRange.Usage);
stick.SetButton(collection, page, stick.ButtonCaps[i].NotRange.Usage, false);
}
break;
default:
Debug.Print("Unknown page {0} for button.", page);
break;
}
}
}
}
finally
{
Debug.Unindent();
}
return true;
}
static bool GetDeviceCaps(Device stick, byte[] preparsed_data, out HidProtocolCaps caps)
{
int axis_caps_count = 0;
int button_caps_count = 0;
// Query joystick capabilities
caps = new HidProtocolCaps();
if (HidProtocol.GetCaps(preparsed_data, ref caps) != HidProtocolStatus.Success)
{
Debug.Print("[WinRawJoystick] HidProtocol.GetCaps() failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
// Make sure our caps arrays are big enough
HidProtocolValueCaps[] axis_caps = new HidProtocolValueCaps[caps.NumberInputValueCaps];
HidProtocolButtonCaps[] button_caps = new HidProtocolButtonCaps[caps.NumberInputButtonCaps];
// Axis capabilities
ushort axis_count = (ushort)axis_caps.Length;
if (HidProtocol.GetValueCaps(HidProtocolReportType.Input,
axis_caps, ref axis_count, preparsed_data) !=
HidProtocolStatus.Success)
{
Debug.Print("[WinRawJoystick] HidProtocol.GetValueCaps() failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
axis_caps_count = (int)axis_count;
// Button capabilities
ushort button_count = (ushort)button_caps.Length;
if (HidProtocol.GetButtonCaps(HidProtocolReportType.Input,
button_caps, ref button_count, preparsed_data) !=
HidProtocolStatus.Success)
{
Debug.Print("[WinRawJoystick] HidProtocol.GetButtonCaps() failed with {0}",
Marshal.GetLastWin32Error());
return false;
}
button_caps_count = (int)button_count;
stick.AxisCaps.Clear();
stick.AxisCaps.AddRange(axis_caps);
stick.ButtonCaps.Clear();
stick.ButtonCaps.AddRange(button_caps);
return true;
}
// Get a DirectInput-compatible Guid
// (equivalent to DIDEVICEINSTANCE guidProduct field)
Guid GetDeviceGuid(IntPtr handle)
{
// Retrieve a RID_DEVICE_INFO struct which contains the VID and PID
RawInputDeviceInfo info = new RawInputDeviceInfo();
int size = info.Size;
if (Functions.GetRawInputDeviceInfo(handle, RawInputDeviceInfoEnum.DEVICEINFO, info, ref size) < 0)
{
Debug.Print("[WinRawJoystick] Functions.GetRawInputDeviceInfo(DEVICEINFO) failed with error {0}",
Marshal.GetLastWin32Error());
return Guid.Empty;
}
// Todo: this Guid format is only valid for USB joysticks.
// Bluetooth devices, such as OUYA controllers, have a totally
// different PID/VID format in DirectInput.
// Do we need to use the same guid or could we simply use PID/VID
// there too? (Test with an OUYA controller.)
int vid = info.Device.HID.VendorId;
int pid = info.Device.HID.ProductId;
return new Guid(
(pid << 16) | vid,
0, 0,
0, 0,
(byte)'P', (byte)'I', (byte)'D',
(byte)'V', (byte)'I', (byte)'D');
}
// Checks whether this is an XInput device.
// XInput devices should be handled through
// the XInput API.
bool IsXInput(IntPtr handle)
{
bool is_xinput = false;
unsafe
{
// Find out how much memory we need to allocate
// for the DEVICENAME string
int size = 0;
if (Functions.GetRawInputDeviceInfo(handle, RawInputDeviceInfoEnum.DEVICENAME, IntPtr.Zero, ref size) < 0 || size == 0)
{
Debug.Print("[WinRawJoystick] Functions.GetRawInputDeviceInfo(DEVICENAME) failed with error {0}",
Marshal.GetLastWin32Error());
return is_xinput;
}
// Allocate memory and retrieve the DEVICENAME string
sbyte* pname = stackalloc sbyte[size + 1];
if (Functions.GetRawInputDeviceInfo(handle, RawInputDeviceInfoEnum.DEVICENAME, (IntPtr)pname, ref size) < 0)
{
Debug.Print("[WinRawJoystick] Functions.GetRawInputDeviceInfo(DEVICENAME) failed with error {0}",
Marshal.GetLastWin32Error());
return is_xinput;
}
// Convert the buffer to a .Net string, and split it into parts
string name = new string(pname);
if (String.IsNullOrEmpty(name))
{
Debug.Print("[WinRawJoystick] Failed to construct device name");
return is_xinput;
}
is_xinput = name.Contains("IG_");
}
return is_xinput;
}
Device GetDevice(IntPtr handle)
{
long hardware_id = handle.ToInt64();
bool is_device_known = false;
lock (UpdateLock)
{
is_device_known = Devices.FromHardwareId(hardware_id) != null;
}
if (!is_device_known)
{
RefreshDevices();
}
lock (UpdateLock)
{
return Devices.FromHardwareId(hardware_id);
}
}
bool IsValid(int index)
{
return Devices.FromIndex(index) != null;
}
#endregion
#region IJoystickDriver2 Members
public JoystickState GetState(int index)
{
lock (UpdateLock)
{
if (IsValid(index))
{
Device dev = Devices.FromIndex(index);
if (dev.IsXInput)
{
return XInput.GetState(dev.XInputIndex);
}
else
{
return dev.GetState();
}
}
return new JoystickState();
}
}
public JoystickCapabilities GetCapabilities(int index)
{
lock (UpdateLock)
{
if (IsValid(index))
{
Device dev = Devices.FromIndex(index);
if (dev.IsXInput)
{
return XInput.GetCapabilities(dev.XInputIndex);
}
else
{
return dev.GetCapabilities();
}
}
return new JoystickCapabilities();
}
}
public Guid GetGuid(int index)
{
lock (UpdateLock)
{
if (IsValid(index))
{
Device dev = Devices.FromIndex(index);
if (dev.IsXInput)
{
return XInput.GetGuid(dev.XInputIndex);
}
else
{
return dev.GetGuid();
}
}
return new Guid();
}
}
#endregion
}
}

View file

@ -33,6 +33,7 @@ using System.Runtime.InteropServices;
using Microsoft.Win32;
#endif
using OpenTK.Input;
using OpenTK.Platform.Common;
namespace OpenTK.Platform.Windows
{
@ -152,51 +153,57 @@ namespace OpenTK.Platform.Windows
}
}
public bool ProcessKeyboardEvent(RawInput rin)
public bool ProcessKeyboardEvent(IntPtr raw)
{
bool processed = false;
bool pressed =
rin.Data.Keyboard.Message == (int)WindowMessage.KEYDOWN ||
rin.Data.Keyboard.Message == (int)WindowMessage.SYSKEYDOWN;
var scancode = rin.Data.Keyboard.MakeCode;
var vkey = rin.Data.Keyboard.VKey;
bool extended0 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E0) != 0;
bool extended1 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E1) != 0;
bool is_valid = true;
ContextHandle handle = new ContextHandle(rin.Header.Device);
KeyboardState keyboard;
if (!rawids.ContainsKey(handle))
RawInput rin;
if (Functions.GetRawInputData(raw, out rin) > 0)
{
RefreshDevices();
bool pressed =
rin.Data.Keyboard.Message == (int)WindowMessage.KEYDOWN ||
rin.Data.Keyboard.Message == (int)WindowMessage.SYSKEYDOWN;
var scancode = rin.Data.Keyboard.MakeCode;
var vkey = rin.Data.Keyboard.VKey;
bool extended0 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E0) != 0;
bool extended1 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E1) != 0;
bool is_valid = true;
ContextHandle handle = new ContextHandle(rin.Header.Device);
KeyboardState keyboard;
if (!rawids.ContainsKey(handle))
{
RefreshDevices();
}
if (keyboards.Count == 0)
return false;
// Note:For some reason, my Microsoft Digital 3000 keyboard reports 0
// as rin.Header.Device for the "zoom-in/zoom-out" buttons.
// That's problematic, because no device has a "0" id.
// As a workaround, we'll add those buttons to the first device (if any).
int keyboard_handle = rawids.ContainsKey(handle) ? rawids[handle] : 0;
keyboard = keyboards[keyboard_handle];
Key key = WinKeyMap.TranslateKey(scancode, vkey, extended0, extended1, out is_valid);
if (is_valid)
{
keyboard.SetKeyState(key, pressed);
processed = true;
}
lock (UpdateLock)
{
keyboards[keyboard_handle] = keyboard;
processed = true;
}
}
if (keyboards.Count == 0)
return false;
// Note:For some reason, my Microsoft Digital 3000 keyboard reports 0
// as rin.Header.Device for the "zoom-in/zoom-out" buttons.
// That's problematic, because no device has a "0" id.
// As a workaround, we'll add those buttons to the first device (if any).
int keyboard_handle = rawids.ContainsKey(handle) ? rawids[handle] : 0;
keyboard = keyboards[keyboard_handle];
Key key = WinKeyMap.TranslateKey(scancode, vkey, extended0, extended1, out is_valid);
if (is_valid)
{
keyboard.SetKeyState(key, pressed);
processed = true;
}
lock (UpdateLock)
{
keyboards[keyboard_handle] = keyboard;
return processed;
}
return processed;
}
#endregion
@ -230,7 +237,7 @@ namespace OpenTK.Platform.Windows
static string GetDeviceName(RawInputDeviceList dev)
{
uint size = 0;
int size = 0;
Functions.GetRawInputDeviceInfo(dev.Device, RawInputDeviceInfoEnum.DEVICENAME, IntPtr.Zero, ref size);
IntPtr name_ptr = Marshal.AllocHGlobal((IntPtr)size);
Functions.GetRawInputDeviceInfo(dev.Device, RawInputDeviceInfoEnum.DEVICENAME, name_ptr, ref size);
@ -241,13 +248,10 @@ namespace OpenTK.Platform.Windows
static void RegisterKeyboardDevice(IntPtr window, string name)
{
RawInputDevice[] rid = new RawInputDevice[1];
// Keyboard is 1/6 (page/id). See http://www.microsoft.com/whdc/device/input/HID_HWID.mspx
rid[0] = new RawInputDevice();
rid[0].UsagePage = 1;
rid[0].Usage = 6;
rid[0].Flags = RawInputDeviceFlags.INPUTSINK;
rid[0].Target = window;
RawInputDevice[] rid = new RawInputDevice[]
{
new RawInputDevice(HIDUsageGD.Keyboard, RawInputDeviceFlags.INPUTSINK, window)
};
if (!Functions.RegisterRawInputDevices(rid, 1, API.RawInputDeviceSize))
{

View file

@ -31,6 +31,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using OpenTK.Input;
using OpenTK.Platform.Common;
namespace OpenTK.Platform.Windows
{
@ -79,14 +80,8 @@ namespace OpenTK.Platform.Windows
mice[i] = state;
}
int count = WinRawInput.DeviceCount;
RawInputDeviceList[] ridl = new RawInputDeviceList[count];
for (int i = 0; i < count; i++)
ridl[i] = new RawInputDeviceList();
Functions.GetRawInputDeviceList(ridl, ref count, API.RawInputDeviceListSize);
// Discover mouse devices
foreach (RawInputDeviceList dev in ridl)
foreach (RawInputDeviceList dev in WinRawInput.GetDeviceList())
{
ContextHandle id = new ContextHandle(dev.Device);
if (rawids.ContainsKey(id))
@ -154,100 +149,109 @@ namespace OpenTK.Platform.Windows
}
}
public bool ProcessMouseEvent(RawInput rin)
public bool ProcessMouseEvent(IntPtr raw_buffer)
{
RawMouse raw = rin.Data.Mouse;
ContextHandle handle = new ContextHandle(rin.Header.Device);
bool processed = false;
MouseState mouse;
if (!rawids.ContainsKey(handle))
RawInput rin;
if (Functions.GetRawInputData(raw_buffer, out rin) > 0)
{
RefreshDevices();
RawMouse raw = rin.Data.Mouse;
ContextHandle handle = new ContextHandle(rin.Header.Device);
MouseState mouse;
if (!rawids.ContainsKey(handle))
{
RefreshDevices();
}
if (mice.Count == 0)
return false;
// Note:For some reason, my Microsoft Digital 3000 keyboard reports 0
// as rin.Header.Device for the "zoom-in/zoom-out" buttons.
// That's problematic, because no device has a "0" id.
// As a workaround, we'll add those buttons to the first device (if any).
int mouse_handle = rawids.ContainsKey(handle) ? rawids[handle] : 0;
mouse = mice[mouse_handle];
// Set and release capture of the mouse to fix http://www.opentk.com/node/2133, Patch by Artfunkel
if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Left);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Left);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Right);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Right);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Middle);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Middle);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Button1);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Button1);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Button2);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Button2);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.WHEEL) != 0)
mouse.SetScrollRelative(0, (short)raw.ButtonData / 120.0f);
if ((raw.ButtonFlags & RawInputMouseState.HWHEEL) != 0)
mouse.SetScrollRelative((short)raw.ButtonData / 120.0f, 0);
if ((raw.Flags & RawMouseFlags.MOUSE_MOVE_ABSOLUTE) != 0)
{
mouse.X = raw.LastX;
mouse.Y = raw.LastY;
}
else
{ // Seems like MOUSE_MOVE_RELATIVE is the default, unless otherwise noted.
mouse.X += raw.LastX;
mouse.Y += raw.LastY;
}
lock (UpdateLock)
{
mice[mouse_handle] = mouse;
processed = true;
}
}
if (mice.Count == 0)
return false;
// Note:For some reason, my Microsoft Digital 3000 keyboard reports 0
// as rin.Header.Device for the "zoom-in/zoom-out" buttons.
// That's problematic, because no device has a "0" id.
// As a workaround, we'll add those buttons to the first device (if any).
int mouse_handle = rawids.ContainsKey(handle) ? rawids[handle] : 0;
mouse = mice[mouse_handle];
// Set and release capture of the mouse to fix http://www.opentk.com/node/2133, Patch by Artfunkel
if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_DOWN) != 0){
mouse.EnableBit((int)MouseButton.Left);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.LEFT_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Left);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Right);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.RIGHT_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Right);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Middle);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.MIDDLE_BUTTON_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Middle);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Button1);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_4_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Button1);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_DOWN) != 0)
{
mouse.EnableBit((int)MouseButton.Button2);
Functions.SetCapture(Window);
}
if ((raw.ButtonFlags & RawInputMouseState.BUTTON_5_UP) != 0)
{
mouse.DisableBit((int)MouseButton.Button2);
Functions.ReleaseCapture();
}
if ((raw.ButtonFlags & RawInputMouseState.WHEEL) != 0)
mouse.SetScrollRelative(0, (short)raw.ButtonData / 120.0f);
if ((raw.ButtonFlags & RawInputMouseState.HWHEEL) != 0)
mouse.SetScrollRelative((short)raw.ButtonData / 120.0f, 0);
if ((raw.Flags & RawMouseFlags.MOUSE_MOVE_ABSOLUTE) != 0)
{
mouse.X = raw.LastX;
mouse.Y = raw.LastY;
}
else
{ // Seems like MOUSE_MOVE_RELATIVE is the default, unless otherwise noted.
mouse.X += raw.LastX;
mouse.Y += raw.LastY;
}
lock (UpdateLock)
{
mice[mouse_handle] = mouse;
return true;
}
return processed;
}
#endregion
@ -257,7 +261,7 @@ namespace OpenTK.Platform.Windows
static string GetDeviceName(RawInputDeviceList dev)
{
// get name size
uint size = 0;
int size = 0;
Functions.GetRawInputDeviceInfo(dev.Device, RawInputDeviceInfoEnum.DEVICENAME, IntPtr.Zero, ref size);
// get actual name
@ -296,13 +300,10 @@ namespace OpenTK.Platform.Windows
static void RegisterRawDevice(IntPtr window, string device)
{
RawInputDevice[] rid = new RawInputDevice[1];
// Mouse is 1/2 (page/id). See http://www.microsoft.com/whdc/device/input/HID_HWID.mspx
rid[0] = new RawInputDevice();
rid[0].UsagePage = 1;
rid[0].Usage = 2;
rid[0].Flags = RawInputDeviceFlags.INPUTSINK;
rid[0].Target = window;
RawInputDevice[] rid = new RawInputDevice[]
{
new RawInputDevice(HIDUsageGD.Mouse, RawInputDeviceFlags.INPUTSINK, window)
};
if (!Functions.RegisterRawInputDevices(rid, 1, API.RawInputDeviceSize))
{

View file

@ -37,36 +37,86 @@ using System.Diagnostics;
namespace OpenTK.Platform.Windows
{
class XInputJoystick : IGamePadDriver, IDisposable
class XInputJoystick : IJoystickDriver2, IDisposable
{
// All XInput devices use the same Guid
// (only one GamePadConfiguration entry required)
static readonly Guid guid =
new Guid("78696e70757400000000000000000000"); // equiv. to "xinput"
XInput xinput = new XInput();
#region IGamePadDriver Members
#region IJoystickDriver2 Members
public GamePadState GetState(int index)
public JoystickState GetState(int index)
{
XInputState xstate;
XInputErrorCode error = xinput.GetState((XInputUserIndex)index, out xstate);
GamePadState state = new GamePadState();
JoystickState state = new JoystickState();
if (error == XInputErrorCode.Success)
{
state.SetConnected(true);
state.SetIsConnected(true);
state.SetAxis(GamePadAxes.LeftX, xstate.GamePad.ThumbLX);
state.SetAxis(GamePadAxes.LeftY, xstate.GamePad.ThumbLY);
state.SetAxis(GamePadAxes.RightX, xstate.GamePad.ThumbRX);
state.SetAxis(GamePadAxes.RightY, xstate.GamePad.ThumbRY);
state.SetAxis(JoystickAxis.Axis0, (short)xstate.GamePad.ThumbLX);
state.SetAxis(JoystickAxis.Axis1, (short)(-xstate.GamePad.ThumbLY));
state.SetAxis(JoystickAxis.Axis2, (short)Common.HidHelper.ScaleValue(xstate.GamePad.LeftTrigger, 0, byte.MaxValue, short.MinValue, short.MaxValue));
state.SetAxis(JoystickAxis.Axis3, (short)xstate.GamePad.ThumbRX);
state.SetAxis(JoystickAxis.Axis4, (short)(-xstate.GamePad.ThumbRY));
state.SetAxis(JoystickAxis.Axis5, (short)Common.HidHelper.ScaleValue(xstate.GamePad.RightTrigger, 0, byte.MaxValue, short.MinValue, short.MaxValue));
state.SetTriggers(xstate.GamePad.LeftTrigger, xstate.GamePad.RightTrigger);
state.SetButton(JoystickButton.Button0, (xstate.GamePad.Buttons & XInputButtons.A) != 0);
state.SetButton(JoystickButton.Button1, (xstate.GamePad.Buttons & XInputButtons.B) != 0);
state.SetButton(JoystickButton.Button2, (xstate.GamePad.Buttons & XInputButtons.X) != 0);
state.SetButton(JoystickButton.Button3, (xstate.GamePad.Buttons & XInputButtons.Y) != 0);
state.SetButton(JoystickButton.Button4, (xstate.GamePad.Buttons & XInputButtons.LeftShoulder) != 0);
state.SetButton(JoystickButton.Button5, (xstate.GamePad.Buttons & XInputButtons.RightShoulder) != 0);
state.SetButton(JoystickButton.Button6, (xstate.GamePad.Buttons & XInputButtons.Back) != 0);
state.SetButton(JoystickButton.Button7, (xstate.GamePad.Buttons & XInputButtons.Start) != 0);
state.SetButton(JoystickButton.Button8, (xstate.GamePad.Buttons & XInputButtons.LeftThumb) != 0);
state.SetButton(JoystickButton.Button9, (xstate.GamePad.Buttons & XInputButtons.RightThumb) != 0);
state.SetButton(JoystickButton.Button10, (xstate.GamePad.Buttons & XInputButtons.Guide) != 0);
state.SetButton(TranslateButtons(xstate.GamePad.Buttons), true);
state.SetHat(JoystickHat.Hat0, new JoystickHatState(TranslateHat(xstate.GamePad.Buttons)));
}
return state;
}
public GamePadCapabilities GetCapabilities(int index)
private HatPosition TranslateHat(XInputButtons buttons)
{
XInputButtons dir = 0;
dir =XInputButtons.DPadUp | XInputButtons.DPadLeft;
if ((buttons & dir) == dir)
return HatPosition.UpLeft;
dir = XInputButtons.DPadUp | XInputButtons.DPadRight;
if ((buttons & dir) == dir)
return HatPosition.UpRight;
dir = XInputButtons.DPadDown | XInputButtons.DPadLeft;
if ((buttons & dir) == dir)
return HatPosition.DownLeft;
dir = XInputButtons.DPadDown | XInputButtons.DPadRight;
if ((buttons & dir) == dir)
return HatPosition.DownRight;
dir = XInputButtons.DPadUp;
if ((buttons & dir) == dir)
return HatPosition.Up;
dir = XInputButtons.DPadRight;
if ((buttons & dir) == dir)
return HatPosition.Right;
dir = XInputButtons.DPadDown;
if ((buttons & dir) == dir)
return HatPosition.Down;
dir = XInputButtons.DPadLeft;
if ((buttons & dir) == dir)
return HatPosition.Left;
return HatPosition.Centered;
}
public JoystickCapabilities GetCapabilities(int index)
{
XInputDeviceCapabilities xcaps;
XInputErrorCode error = xinput.GetCapabilities(
@ -76,13 +126,13 @@ namespace OpenTK.Platform.Windows
if (error == XInputErrorCode.Success)
{
GamePadType type = TranslateSubType(xcaps.SubType);
Buttons buttons = TranslateButtons(xcaps.GamePad.Buttons);
GamePadAxes axes = TranslateAxes(ref xcaps.GamePad);
//GamePadType type = TranslateSubType(xcaps.SubType);
int buttons = TranslateButtons(xcaps.GamePad.Buttons);
int axes = TranslateAxes(ref xcaps.GamePad);
return new GamePadCapabilities(type, axes, buttons, true);
return new JoystickCapabilities(axes, buttons, 0, true);
}
return new GamePadCapabilities();
return new JoystickCapabilities();
}
public string GetName(int index)
@ -90,47 +140,53 @@ namespace OpenTK.Platform.Windows
return String.Empty;
}
public Guid GetGuid(int index)
{
return guid;
}
public bool SetVibration(int index, float left, float right)
{
return false;
left = MathHelper.Clamp(left, 0.0f, 1.0f);
right = MathHelper.Clamp(right, 0.0f, 1.0f);
XInputVibration vibration = new XInputVibration(
(ushort)(left * UInt16.MaxValue),
(ushort)(right * UInt16.MaxValue));
return xinput.SetState((XInputUserIndex)index, ref vibration) == XInputErrorCode.Success;
}
#endregion
#region Private Members
GamePadAxes TranslateAxes(ref XInputGamePad pad)
int TranslateAxes(ref XInputGamePad pad)
{
GamePadAxes axes = 0;
axes |= pad.ThumbLX != 0 ? GamePadAxes.LeftX : 0;
axes |= pad.ThumbLY != 0 ? GamePadAxes.LeftY : 0;
axes |= pad.LeftTrigger != 0 ? GamePadAxes.LeftTrigger : 0;
axes |= pad.ThumbRX != 0 ? GamePadAxes.RightX : 0;
axes |= pad.ThumbRY != 0 ? GamePadAxes.RightY : 0;
axes |= pad.RightTrigger != 0 ? GamePadAxes.RightTrigger : 0;
return axes;
int count = 0;
count += pad.ThumbLX != 0 ? 1 : 0;
count += pad.ThumbLY != 0 ? 1 : 0;
count += pad.ThumbRX != 0 ? 1 : 0;
count += pad.ThumbRY != 0 ? 1 : 0;
count += pad.LeftTrigger != 0 ? 1 : 0;
count += pad.RightTrigger != 0 ? 1 : 0;
return count;
}
Buttons TranslateButtons(XInputButtons xbuttons)
int NumberOfSetBits(int i)
{
Buttons buttons = 0;
buttons |= (xbuttons & XInputButtons.A) != 0 ? Buttons.A : 0;
buttons |= (xbuttons & XInputButtons.B) != 0 ? Buttons.B : 0;
buttons |= (xbuttons & XInputButtons.X) != 0 ? Buttons.X : 0;
buttons |= (xbuttons & XInputButtons.Y) != 0 ? Buttons.Y : 0;
buttons |= (xbuttons & XInputButtons.Start) != 0 ? Buttons.Start : 0;
buttons |= (xbuttons & XInputButtons.Back) != 0 ? Buttons.Back : 0;
//buttons |= (xbuttons & XInputButtons.BigButton) != 0 ? Buttons.BigButton : 0;
buttons |= (xbuttons & XInputButtons.LeftShoulder) != 0 ? Buttons.LeftShoulder : 0;
buttons |= (xbuttons & XInputButtons.RightShoulder) != 0 ? Buttons.RightShoulder : 0;
buttons |= (xbuttons & XInputButtons.LeftThumb) != 0 ? Buttons.LeftStick : 0;
buttons |= (xbuttons & XInputButtons.RightThumb) != 0 ? Buttons.RightStick : 0;
buttons |= (xbuttons & XInputButtons.DPadDown) != 0 ? Buttons.DPadDown : 0;
buttons |= (xbuttons & XInputButtons.DPadUp) != 0 ? Buttons.DPadUp : 0;
buttons |= (xbuttons & XInputButtons.DPadLeft) != 0 ? Buttons.DPadLeft : 0;
buttons |= (xbuttons & XInputButtons.DPadRight) != 0 ? Buttons.DPadRight : 0;
return buttons;
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
int TranslateButtons(XInputButtons xbuttons)
{
return NumberOfSetBits((int)xbuttons);
}
#if false
// Todo: Implement JoystickType enumeration
GamePadType TranslateSubType(XInputDeviceSubType xtype)
{
switch (xtype)
@ -150,6 +206,7 @@ namespace OpenTK.Platform.Windows
return GamePadType.Unknown;
}
}
#endif
enum XInputErrorCode
{
@ -198,6 +255,7 @@ namespace OpenTK.Platform.Windows
RightThumb = 0x0080,
LeftShoulder = 0x0100,
RightShoulder = 0x0200,
Guide = 0x0400, // Undocumented, requires XInputGetStateEx + XINPUT_1_3.dll or higher
A = 0x1000,
B = 0x2000,
X = 0x4000,
@ -265,8 +323,14 @@ namespace OpenTK.Platform.Windows
struct XInputVibration
{
public short LeftMotorSpeed;
public short RightMotorSpeed;
public ushort LeftMotorSpeed;
public ushort RightMotorSpeed;
public XInputVibration(ushort left, ushort right)
{
LeftMotorSpeed = left;
RightMotorSpeed = right;
}
}
struct XInputDeviceCapabilities
@ -306,7 +370,11 @@ namespace OpenTK.Platform.Windows
// Load the entry points we are interested in from that dll
GetCapabilities = (XInputGetCapabilities)Load("XInputGetCapabilities", typeof(XInputGetCapabilities));
GetState = (XInputGetState)Load("XInputGetState", typeof(XInputGetState));
GetState =
// undocumented XInputGetStateEx with support for the "Guide" button (requires XINPUT_1_3+)
(XInputGetState)Load("XInputGetStateEx", typeof(XInputGetState)) ??
// documented XInputGetState (no support for the "Guide" button)
(XInputGetState)Load("XInputGetState", typeof(XInputGetState));
SetState = (XInputSetState)Load("XInputSetState", typeof(XInputSetState));
}