[Win] Cache WinMM joystick capabilities

This commit is contained in:
Stefanos A. 2014-02-01 16:01:33 +01:00
commit 2d110728aa

View file

@ -27,12 +27,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using OpenTK.Input;
using System.Security;
using Microsoft.Win32;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Win32;
using OpenTK.Input;
namespace OpenTK.Platform.Windows namespace OpenTK.Platform.Windows
{ {
@ -40,9 +40,18 @@ namespace OpenTK.Platform.Windows
{ {
#region Fields #region Fields
readonly object sync = new object();
List<JoystickDevice> sticks = new List<JoystickDevice>(); List<JoystickDevice> sticks = new List<JoystickDevice>();
IList<JoystickDevice> sticks_readonly; IList<JoystickDevice> sticks_readonly;
// Matches a WinMM device index to a specific stick above
readonly Dictionary<int, int> index_to_stick =
new Dictionary<int, int>();
// Matches a player index to a WinMM device index
readonly Dictionary<int, int> player_to_index =
new Dictionary<int, int>();
// Todo: Read the joystick name from the registry. // Todo: Read the joystick name from the registry.
//static readonly string RegistryJoyConfig = @"Joystick%dConfiguration"; //static readonly string RegistryJoyConfig = @"Joystick%dConfiguration";
//static readonly string RegistryJoyName = @"Joystick%dOEMName"; //static readonly string RegistryJoyName = @"Joystick%dOEMName";
@ -57,16 +66,24 @@ namespace OpenTK.Platform.Windows
public WinMMJoystick() public WinMMJoystick()
{ {
sticks_readonly = sticks.AsReadOnly(); sticks_readonly = sticks.AsReadOnly();
RefreshDevices();
}
#endregion
#region Internal Members
internal void RefreshDevices()
{
for (int i = 0; i < sticks.Count; i++)
{
CloseJoystick(i);
}
// WinMM supports up to 16 joysticks. // WinMM supports up to 16 joysticks.
int number = 0; for (int i = 0; i < UnsafeNativeMethods.joyGetNumDevs(); i++)
while (number < UnsafeNativeMethods.joyGetNumDevs())
{ {
JoystickDevice<WinMMJoyDetails> stick = OpenJoystick(number++); OpenJoystick(i);
if (stick != null)
{
sticks.Add(stick);
}
} }
} }
@ -75,35 +92,48 @@ namespace OpenTK.Platform.Windows
#region Private Members #region Private Members
JoystickDevice<WinMMJoyDetails> OpenJoystick(int number) JoystickDevice<WinMMJoyDetails> OpenJoystick(int number)
{
lock (sync)
{ {
JoystickDevice<WinMMJoyDetails> stick = null; JoystickDevice<WinMMJoyDetails> stick = null;
JoyCaps caps; JoyCaps caps;
JoystickError result = UnsafeNativeMethods.joyGetDevCaps(number, out caps, JoyCaps.SizeInBytes); JoystickError result = UnsafeNativeMethods.joyGetDevCaps(number, out caps, JoyCaps.SizeInBytes);
if (result != JoystickError.NoError) if (result == JoystickError.NoError)
return null; {
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; int num_axes = caps.NumAxes;
if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0) if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
num_axes += 2; num_axes += 2;
stick = new JoystickDevice<WinMMJoyDetails>(number, num_axes, caps.NumButtons); stick = new JoystickDevice<WinMMJoyDetails>(number, num_axes, caps.NumButtons);
stick.Details = new WinMMJoyDetails(num_axes); stick.Details = new WinMMJoyDetails(joycaps);
// Make sure to reverse the vertical axes, so that +1 points up and -1 points down. // Make sure to reverse the vertical axes, so that +1 points up and -1 points down.
int axis = 0; for (int axis = 0; axis < caps.NumAxes; axis++)
if (axis < caps.NumAxes) {
{ stick.Details.Min[axis] = caps.XMin; stick.Details.Max[axis] = caps.XMax; axis++; } stick.Details.Min[axis] = caps.GetMin(axis);
if (axis < caps.NumAxes) stick.Details.Max[axis] = caps.GetMax(axis);
{ stick.Details.Min[axis] = caps.YMax; stick.Details.Max[axis] = caps.YMin; axis++; } }
if (axis < caps.NumAxes)
{ stick.Details.Min[axis] = caps.ZMax; stick.Details.Max[axis] = caps.ZMin; axis++; }
if (axis < caps.NumAxes)
{ stick.Details.Min[axis] = caps.RMin; stick.Details.Max[axis] = caps.RMax; axis++; }
if (axis < caps.NumAxes)
{ stick.Details.Min[axis] = caps.UMin; stick.Details.Max[axis] = caps.UMax; axis++; }
if (axis < caps.NumAxes)
{ stick.Details.Min[axis] = caps.VMax; stick.Details.Max[axis] = caps.VMin; axis++; }
if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0) if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
{ {
@ -114,7 +144,7 @@ namespace OpenTK.Platform.Windows
stick.Details.PovType |= PovType.Continuous; stick.Details.PovType |= PovType.Continuous;
} }
#warning "Implement joystick name detection for WinMM." // 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); 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! // Todo: Try to get the device name from the registry. Oh joy!
//string key_path = String.Format("{0}\\{1}\\{2}", RegistryJoyConfig, caps.RegKey, RegstryJoyCurrent); //string key_path = String.Format("{0}\\{1}\\{2}", RegistryJoyConfig, caps.RegKey, RegstryJoyCurrent);
@ -128,24 +158,58 @@ namespace OpenTK.Platform.Windows
// key.Close(); // key.Close();
//} //}
if (stick != null)
Debug.Print("Found joystick on device number {0}", number); Debug.Print("Found joystick on device number {0}", number);
return stick; index_to_stick.Add(number, sticks.Count);
player_to_index.Add(player_to_index.Count, number);
sticks.Add(stick);
} }
void UnplugJoystick(int index) return stick;
}
}
void UnplugJoystick(int player_index)
{ {
// Reset the system configuration. Without this, // Reset the system configuration. Without this,
// joysticks that are reconnected on different // joysticks that are reconnected on different
// ports are given different ids, making it // ports are given different ids, making it
// impossible to reconnect a disconnected user. // impossible to reconnect a disconnected user.
UnsafeNativeMethods.joyConfigChanged(0); UnsafeNativeMethods.joyConfigChanged(0);
Debug.Print("[Win] WinMM joystick {0} unplugged", index); Debug.Print("[Win] WinMM joystick {0} unplugged", player_index);
CloseJoystick(player_index);
} }
bool IsValid(int index) void CloseJoystick(int player_index)
{ {
return index >= 0 && index < UnsafeNativeMethods.joyGetNumDevs(); lock (sync)
{
if (IsValid(player_index))
{
int device_index = player_to_index[player_index];
JoystickDevice<WinMMJoyDetails> stick =
sticks[index_to_stick[device_index]] as JoystickDevice<WinMMJoyDetails>;
if (stick != null)
{
index_to_stick.Remove(device_index);
player_to_index.Remove(player_index);
}
}
}
}
bool IsValid(int player_index)
{
if (player_to_index.ContainsKey(player_index))
{
return true;
}
//else if (index >= 0 && index < UnsafeNativeMethods.joyGetNumDevs())
//{
// return OpenJoystick(index) != null;
//}
return false;
} }
static short CalculateOffset(int pos, int min, int max) static short CalculateOffset(int pos, int min, int max)
@ -169,6 +233,8 @@ namespace OpenTK.Platform.Windows
} }
public void Poll() public void Poll()
{
lock (sync)
{ {
foreach (JoystickDevice<WinMMJoyDetails> js in sticks) foreach (JoystickDevice<WinMMJoyDetails> js in sticks)
{ {
@ -255,88 +321,107 @@ namespace OpenTK.Platform.Windows
} }
} }
} }
}
#endregion #endregion
#region IJoystickDriver2 Members #region IJoystickDriver2 Members
public JoystickCapabilities GetCapabilities(int index) public JoystickCapabilities GetCapabilities(int player_index)
{ {
if (IsValid(index)) lock (sync)
{ {
JoyCaps mmcaps; if (IsValid(player_index))
JoystickError result = UnsafeNativeMethods.joyGetDevCaps(index, out mmcaps, JoyCaps.SizeInBytes);
if (result == JoystickError.NoError)
{ {
JoystickCapabilities caps = new JoystickCapabilities( int device_index = player_to_index[player_index];
mmcaps.NumAxes, JoystickDevice<WinMMJoyDetails> stick =
mmcaps.NumButtons, sticks[index_to_stick[device_index]] as JoystickDevice<WinMMJoyDetails>;
(mmcaps.Capabilities & JoystCapsFlags.HasPov) != 0 ? 1 : 0,
true); return stick.Details.Capabilities;
//if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
// gpcaps.DPadCount++;
return caps;
}
else if (result == JoystickError.Unplugged)
{
UnplugJoystick(index);
}
}
else
{
Debug.Print("[Win] Invalid WinMM joystick device {0}", index);
} }
return new JoystickCapabilities(); return new JoystickCapabilities();
} }
}
public JoystickState GetState(int index) public JoystickState GetState(int player_index)
{
lock (sync)
{ {
JoystickState state = new JoystickState(); JoystickState state = new JoystickState();
if (IsValid(index)) if (IsValid(player_index))
{ {
JoyInfoEx info = new JoyInfoEx(); int device_index = player_to_index[player_index];
info.Size = JoyInfoEx.SizeInBytes; int index = index_to_stick[device_index];
info.Flags = JoystickFlags.All; JoystickDevice<WinMMJoyDetails> stick =
sticks[index] as JoystickDevice<WinMMJoyDetails>;
JoystickError result = UnsafeNativeMethods.joyGetPosEx(index, ref info); // 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) if (result == JoystickError.NoError)
{ {
JoyCaps caps; for (int i = 0; i < stick.Details.Capabilities.AxisCount; i++)
result = UnsafeNativeMethods.joyGetDevCaps(index, out caps, JoyCaps.SizeInBytes);
if (result == JoystickError.NoError)
{ {
state.SetAxis(JoystickAxis.Axis0, CalculateOffset(info.XPos, caps.XMin, caps.XMax)); state.SetAxis(JoystickAxis.Axis0 + i, CalculateOffset(info.GetAxis(i), stick.Details.Min[i], stick.Details.Max[i]));
state.SetAxis(JoystickAxis.Axis1, CalculateOffset(info.YPos, caps.YMin, caps.YMax)); }
state.SetAxis(JoystickAxis.Axis2, CalculateOffset(info.ZPos, caps.ZMin, caps.ZMax));
state.SetAxis(JoystickAxis.Axis3, CalculateOffset(info.RPos, caps.RMin, caps.RMax));
state.SetAxis(JoystickAxis.Axis4, CalculateOffset(info.UPos, caps.UMin, caps.UMax));
state.SetAxis(JoystickAxis.Axis5, CalculateOffset(info.VPos, caps.VMin, caps.VMax));
for (int i = 0; i < 16; i++) for (int i = 0; i < stick.Details.Capabilities.ButtonCount; i++)
{ {
state.SetButton(JoystickButton.Button0 + i, (info.Buttons & 1 << i) != 0); state.SetButton(JoystickButton.Button0 + i, (info.Buttons & 1 << i) != 0);
} }
state.SetIsConnected(true); state.SetIsConnected(true);
} }
} else if (result == JoystickError.Unplugged)
if (result == JoystickError.Unplugged)
{ {
UnplugJoystick(index); UnplugJoystick(player_index);
} }
} }
else else
{ {
Debug.Print("[Win] Invalid WinMM joystick device {0}", index); // Use joyGetPosEx
JoyInfoEx info = new JoyInfoEx();
info.Size = JoyInfoEx.SizeInBytes;
info.Flags = JoystickFlags.All;
JoystickError result = UnsafeNativeMethods.joyGetPosEx(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);
}
}
} }
return state; return state;
} }
}
public Guid GetGuid(int index) public Guid GetGuid(int index)
{
lock (sync)
{ {
Guid guid = new Guid(); Guid guid = new Guid();
@ -347,6 +432,7 @@ namespace OpenTK.Platform.Windows
return guid; return guid;
} }
}
#endregion #endregion
@ -423,6 +509,7 @@ namespace OpenTK.Platform.Windows
Backward = 18000, Backward = 18000,
Left = 27000 Left = 27000
} }
struct JoyCaps struct JoyCaps
{ {
public ushort Mid; public ushort Mid;
@ -459,6 +546,53 @@ namespace OpenTK.Platform.Windows
{ {
SizeInBytes = Marshal.SizeOf(default(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 struct JoyInfoEx
@ -475,8 +609,10 @@ namespace OpenTK.Platform.Windows
public uint Buttons; public uint Buttons;
public uint ButtonNumber; public uint ButtonNumber;
public int Pov; public int Pov;
#pragma warning disable 0169
uint Reserved1; uint Reserved1;
uint Reserved2; uint Reserved2;
#pragma warning restore 0169
public static readonly int SizeInBytes; public static readonly int SizeInBytes;
@ -484,6 +620,20 @@ namespace OpenTK.Platform.Windows
{ {
SizeInBytes = Marshal.SizeOf(default(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 static class UnsafeNativeMethods
@ -491,6 +641,8 @@ namespace OpenTK.Platform.Windows
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] [DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern JoystickError joyGetDevCaps(int uJoyID, out JoyCaps pjc, int cbjc); public static extern JoystickError joyGetDevCaps(int uJoyID, out JoyCaps pjc, int cbjc);
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] [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); public static extern JoystickError joyGetPosEx(int uJoyID, ref JoyInfoEx pji);
[DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] [DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity]
public static extern int joyGetNumDevs(); public static extern int joyGetNumDevs();
@ -517,14 +669,16 @@ namespace OpenTK.Platform.Windows
struct WinMMJoyDetails struct WinMMJoyDetails
{ {
public readonly float[] Min, Max; // Minimum and maximum offset of each axis. public readonly int[] Min, Max; // Minimum and maximum offset of each axis.
public PovType PovType; public PovType PovType;
public JoystickCapabilities Capabilities;
public WinMMJoyDetails(int num_axes) public WinMMJoyDetails(JoystickCapabilities caps)
: this()
{ {
Min = new float[num_axes]; Min = new int[caps.AxisCount];
Max = new float[num_axes]; Max = new int[caps.AxisCount];
PovType = PovType.None; Capabilities = caps;
} }
public float CalculateOffset(float pos, int axis) public float CalculateOffset(float pos, int axis)