diff --git a/Source/Examples/Main.cs b/Source/Examples/Main.cs index 84dba6d7..8e68e09f 100644 --- a/Source/Examples/Main.cs +++ b/Source/Examples/Main.cs @@ -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) { diff --git a/Source/OpenTK/Input/GamePadButtons.cs b/Source/OpenTK/Input/GamePadButtons.cs index 676fb1f9..3f866e89 100644 --- a/Source/OpenTK/Input/GamePadButtons.cs +++ b/Source/OpenTK/Input/GamePadButtons.cs @@ -160,7 +160,31 @@ namespace OpenTK.Input /// A that represents the current . 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(); } /// diff --git a/Source/OpenTK/Input/GamePadCapabilities.cs b/Source/OpenTK/Input/GamePadCapabilities.cs index 269081e8..9947d835 100644 --- a/Source/OpenTK/Input/GamePadCapabilities.cs +++ b/Source/OpenTK/Input/GamePadCapabilities.cs @@ -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; } } + /// + /// Gets a value describing whether a valid button configuration + /// exists for this GamePad in the GamePad configuration database. + /// + public bool IsMapped + { + get { return is_mapped; } + } + /// A structure to test for equality. /// A structure to test for equality. 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"); } /// @@ -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; } diff --git a/Source/OpenTK/Input/GamePadConfigurationDatabase.cs b/Source/OpenTK/Input/GamePadConfigurationDatabase.cs index 292cd10b..098435b9 100644 --- a/Source/OpenTK/Input/GamePadConfigurationDatabase.cs +++ b/Source/OpenTK/Input/GamePadConfigurationDatabase.cs @@ -34,6 +34,8 @@ namespace OpenTK.Input { class GamePadConfigurationDatabase { + internal const string UnmappedName = "Unmapped Controller"; + readonly Dictionary Configurations = new Dictionary(); 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,"); diff --git a/Source/OpenTK/Input/GamePadState.cs b/Source/OpenTK/Input/GamePadState.cs index 7d0f0630..2ab66fe0 100644 --- a/Source/OpenTK/Input/GamePadState.cs +++ b/Source/OpenTK/Input/GamePadState.cs @@ -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); } /// diff --git a/Source/OpenTK/Input/GamePadTriggers.cs b/Source/OpenTK/Input/GamePadTriggers.cs index adbc1d16..da93c4e6 100644 --- a/Source/OpenTK/Input/GamePadTriggers.cs +++ b/Source/OpenTK/Input/GamePadTriggers.cs @@ -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); } diff --git a/Source/OpenTK/Input/JoystickCapabilities.cs b/Source/OpenTK/Input/JoystickCapabilities.cs index 1e5f88ab..89b027d2 100644 --- a/Source/OpenTK/Input/JoystickCapabilities.cs +++ b/Source/OpenTK/Input/JoystickCapabilities.cs @@ -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 /// @@ -95,6 +112,7 @@ namespace OpenTK.Input public bool IsConnected { get { return is_connected; } + private set { is_connected = value; } } /// @@ -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); } diff --git a/Source/OpenTK/Input/JoystickState.cs b/Source/OpenTK/Input/JoystickState.cs index a2dd64cc..1ecb562b 100644 --- a/Source/OpenTK/Input/JoystickState.cs +++ b/Source/OpenTK/Input/JoystickState.cs @@ -223,6 +223,11 @@ namespace OpenTK.Input } } + internal void ClearButtons() + { + buttons = 0; + } + internal void SetButton(JoystickButton button, bool value) { int index = (int)button; diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index cd3df5fe..ef234f47 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -162,11 +162,14 @@ + + + @@ -325,9 +328,6 @@ Code - - Code - Code diff --git a/Source/OpenTK/Platform/Common/Hid.cs b/Source/OpenTK/Platform/Common/Hid.cs new file mode 100644 index 00000000..bf33d463 --- /dev/null +++ b/Source/OpenTK/Platform/Common/Hid.cs @@ -0,0 +1,270 @@ +#region License +// +// Hid.cs +// +// Author: +// Stefanos A. +// +// 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 + { + /// + /// Scales the specified value linearly between min and max. + /// + /// The value to scale + /// The minimum expected value (inclusive) + /// The maximum expected value (inclusive) + /// The minimum output value (inclusive) + /// The maximum output value (inclusive) + /// The value, scaled linearly between min and max + 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 + } +} diff --git a/Source/OpenTK/Platform/DeviceCollection.cs b/Source/OpenTK/Platform/DeviceCollection.cs index 0c70f356..04ac1015 100644 --- a/Source/OpenTK/Platform/DeviceCollection.cs +++ b/Source/OpenTK/Platform/DeviceCollection.cs @@ -41,7 +41,7 @@ namespace OpenTK.Platform // that is added. class DeviceCollection : IEnumerable { - readonly Dictionary Map = new Dictionary(); + readonly Dictionary Map = new Dictionary(); readonly List Devices = new List(); #region IEnumerable Members @@ -64,7 +64,15 @@ namespace OpenTK.Platform #region Public Members - public void Add(int id, T device) + /// \internal + /// + /// 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. + /// + /// The hardware id for the device. + /// The device instance. + 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)) { diff --git a/Source/OpenTK/Platform/IPlatformFactory.cs b/Source/OpenTK/Platform/IPlatformFactory.cs index a126c13d..b42e1ab4 100644 --- a/Source/OpenTK/Platform/IPlatformFactory.cs +++ b/Source/OpenTK/Platform/IPlatformFactory.cs @@ -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(); diff --git a/Source/OpenTK/Platform/MacOS/HIDInput.cs b/Source/OpenTK/Platform/MacOS/HIDInput.cs index 4c3e4541..e4a4614d 100755 --- a/Source/OpenTK/Platform/MacOS/HIDInput.cs +++ b/Source/OpenTK/Platform/MacOS/HIDInput.cs @@ -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) */ diff --git a/Source/OpenTK/Platform/MappedGamePadDriver.cs b/Source/OpenTK/Platform/MappedGamePadDriver.cs index 5d55b779..aab0f3e5 100644 --- a/Source/OpenTK/Platform/MappedGamePadDriver.cs +++ b/Source/OpenTK/Platform/MappedGamePadDriver.cs @@ -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 { diff --git a/Source/OpenTK/Platform/SDL2/Sdl2.cs b/Source/OpenTK/Platform/SDL2/Sdl2.cs index a987c3bf..4690e7d9 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2.cs @@ -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); } } diff --git a/Source/OpenTK/Platform/Windows/API.cs b/Source/OpenTK/Platform/Windows/API.cs index d8fa2955..05d5e73c 100644 --- a/Source/OpenTK/Platform/Windows/API.cs +++ b/Source/OpenTK/Platform/Windows/API.cs @@ -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. +// +// 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 /// 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. /// Call GetLastError to identify any other errors. /// - [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 + ); + /// /// Gets information about the raw input device. /// @@ -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 /// /// Top level collection Usage page for the raw input device. /// - //internal USHORT UsagePage; - internal SHORT UsagePage; + internal HIDPage UsagePage; /// /// Top level collection Usage for the raw input device. /// @@ -2443,6 +2509,22 @@ namespace OpenTK.Platform.Windows /// 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.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.Stride; } #endregion @@ -2537,6 +2625,9 @@ namespace OpenTK.Platform.Windows /// Value passed in the wParam parameter of the WM_INPUT message. /// internal WPARAM Param; + + public static readonly int SizeInBytes = + BlittableValueType.Stride; } #endregion @@ -2715,16 +2806,31 @@ namespace OpenTK.Platform.Windows /// /// Size, in bytes, of each HID input in bRawData. /// - internal DWORD SizeHid; + internal DWORD Size; /// /// Number of HID inputs in bRawData. /// internal DWORD Count; - // The RawData field must be marshalled manually. - ///// - ///// Raw input data as an array of bytes. - ///// - //internal IntPtr RawData; + /// + /// Raw input data as an array of bytes. + /// + 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 /// /// Size, in bytes, of the RawInputDeviceInfo structure. /// - DWORD Size = Marshal.SizeOf(typeof(RawInputDeviceInfo)); + internal DWORD Size = Marshal.SizeOf(typeof(RawInputDeviceInfo)); /// /// Type of raw input data. /// diff --git a/Source/OpenTK/Platform/Windows/Bindings/HidProtocol.cs b/Source/OpenTK/Platform/Windows/Bindings/HidProtocol.cs new file mode 100644 index 00000000..a5418ad5 --- /dev/null +++ b/Source/OpenTK/Platform/Windows/Bindings/HidProtocol.cs @@ -0,0 +1,288 @@ +#region License +// +// HidProtocol.cs +// +// Author: +// Stefanos A. +// +// 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; + } +} diff --git a/Source/OpenTK/Platform/Windows/WinFactory.cs b/Source/OpenTK/Platform/Windows/WinFactory.cs index 877377ab..d09b4de2 100644 --- a/Source/OpenTK/Platform/Windows/WinFactory.cs +++ b/Source/OpenTK/Platform/Windows/WinFactory.cs @@ -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); diff --git a/Source/OpenTK/Platform/Windows/WinGLNative.cs b/Source/OpenTK/Platform/Windows/WinGLNative.cs index eb348c89..10460dd0 100644 --- a/Source/OpenTK/Platform/Windows/WinGLNative.cs +++ b/Source/OpenTK/Platform/Windows/WinGLNative.cs @@ -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); diff --git a/Source/OpenTK/Platform/Windows/WinInputBase.cs b/Source/OpenTK/Platform/Windows/WinInputBase.cs index 7d0865f1..701abd02 100644 --- a/Source/OpenTK/Platform/Windows/WinInputBase.cs +++ b/Source/OpenTK/Platform/Windows/WinInputBase.cs @@ -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; } diff --git a/Source/OpenTK/Platform/Windows/WinMMJoystick.cs b/Source/OpenTK/Platform/Windows/WinMMJoystick.cs deleted file mode 100644 index ff3e6351..00000000 --- a/Source/OpenTK/Platform/Windows/WinMMJoystick.cs +++ /dev/null @@ -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 sticks = new List(); - - // Matches a WinMM device index to a specific stick above - readonly Dictionary index_to_stick = - new Dictionary(); - // Matches a player index to a WinMM device index - readonly Dictionary player_to_index = - new Dictionary(); - - // 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 OpenJoystick(int number) - { - lock (sync) - { - JoystickDevice 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(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 stick = - sticks[index_to_stick[device_index]] as JoystickDevice; - - 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 stick = - sticks[index_to_stick[device_index]] as JoystickDevice; - - 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 stick = - sticks[index] as JoystickDevice; - - // 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 - } -} diff --git a/Source/OpenTK/Platform/Windows/WinRawInput.cs b/Source/OpenTK/Platform/Windows/WinRawInput.cs index 0e99bc96..4575153e 100644 --- a/Source/OpenTK/Platform/Windows/WinRawInput.cs +++ b/Source/OpenTK/Platform/Windows/WinRawInput.cs @@ -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; } diff --git a/Source/OpenTK/Platform/Windows/WinRawJoystick.cs b/Source/OpenTK/Platform/Windows/WinRawJoystick.cs new file mode 100644 index 00000000..9d8bb48a --- /dev/null +++ b/Source/OpenTK/Platform/Windows/WinRawJoystick.cs @@ -0,0 +1,830 @@ +#region License +// +// WinRawJoystick.cs +// +// Author: +// Stefanos A. +// +// 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 AxisCaps = + new List(); + internal readonly List ButtonCaps = + new List(); + internal readonly bool IsXInput; + internal readonly int XInputIndex; + + readonly Dictionary axes = + new Dictionary(); + readonly Dictionary buttons = + new Dictionary(); + readonly Dictionary hats = + new Dictionary(); + + #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 Devices = new DeviceCollection(); + + 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 + } +} diff --git a/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs b/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs index fcfc4a6b..223f68be 100644 --- a/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs +++ b/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs @@ -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)) { diff --git a/Source/OpenTK/Platform/Windows/WinRawMouse.cs b/Source/OpenTK/Platform/Windows/WinRawMouse.cs index c942a36f..b1e0d447 100644 --- a/Source/OpenTK/Platform/Windows/WinRawMouse.cs +++ b/Source/OpenTK/Platform/Windows/WinRawMouse.cs @@ -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)) { diff --git a/Source/OpenTK/Platform/Windows/XInputJoystick.cs b/Source/OpenTK/Platform/Windows/XInputJoystick.cs index ef38e087..2e21adfe 100644 --- a/Source/OpenTK/Platform/Windows/XInputJoystick.cs +++ b/Source/OpenTK/Platform/Windows/XInputJoystick.cs @@ -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)); }