diff --git a/Source/OpenTK/Input/JoystickDevice.cs b/Source/OpenTK/Input/JoystickDevice.cs index 4611f11d..f84097a9 100644 --- a/Source/OpenTK/Input/JoystickDevice.cs +++ b/Source/OpenTK/Input/JoystickDevice.cs @@ -163,7 +163,7 @@ namespace OpenTK.Input : base(id, axes, buttons) { } - internal TDetail Details { get { return details; } set { details = value; } } + internal TDetail Details; } #endregion diff --git a/Source/OpenTK/Platform/Windows/WinMMJoystick.cs b/Source/OpenTK/Platform/Windows/WinMMJoystick.cs index 4487d352..a1a39c77 100644 --- a/Source/OpenTK/Platform/Windows/WinMMJoystick.cs +++ b/Source/OpenTK/Platform/Windows/WinMMJoystick.cs @@ -30,6 +30,8 @@ using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using OpenTK.Input; +using System.Security; +using Microsoft.Win32; namespace OpenTK.Platform.Windows { @@ -40,6 +42,10 @@ namespace OpenTK.Platform.Windows List sticks = new List(); IList sticks_readonly; + static readonly string RegistryJoyConfig = @"Joystick%dConfiguration"; + static readonly string RegistryJoyName = @"Joystick%dOEMName"; + static readonly string RegstryJoyCurrent = @"CurrentJoystickSettings"; + bool disposed; #endregion @@ -50,13 +56,13 @@ namespace OpenTK.Platform.Windows { sticks_readonly = sticks.AsReadOnly(); - int number = 0, max_sticks = 25; - while (number < max_sticks) + // WinMM supports up to 16 joysticks. + int number = 0; + while (number < UnsafeNativeMethods.joyGetNumDevs()) { JoystickDevice stick = OpenJoystick(number++); if (stick != null) { - stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons)", number, stick.Axis.Count, stick.Button.Count); sticks.Add(stick); } } @@ -75,21 +81,50 @@ namespace OpenTK.Platform.Windows if (result != JoystickError.NoError) return null; - stick = new JoystickDevice(number, caps.NumAxes, caps.NumButtons); - stick.Details = new WinMMJoyDetails(caps.NumAxes); + 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(num_axes); + + // Make sure to reverse the vertical axes, so that +1 points up and -1 points down. + // I don't know if this an artifact of my joypad, but RAxis is the second horizontal and + // ZAxis is the second vertical axis here (Saitek P880). int axis = 0; if (axis < caps.NumAxes) { stick.Details.Min[axis] = caps.XMin; stick.Details.Max[axis] = caps.XMax; axis++; } if (axis < caps.NumAxes) - { stick.Details.Min[axis] = caps.YMin; stick.Details.Max[axis] = caps.YMax; axis++; } - if (axis < caps.NumAxes) - { stick.Details.Min[axis] = caps.ZMin; stick.Details.Max[axis] = caps.ZMax; axis++; } + { stick.Details.Min[axis] = caps.YMax; stick.Details.Max[axis] = caps.YMin; 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.ZMax; stick.Details.Max[axis] = caps.ZMin; 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.VMin; stick.Details.Max[axis] = caps.VMax; axis++; } + { stick.Details.Min[axis] = caps.VMax; stick.Details.Max[axis] = caps.VMin; 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; + } + + // 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(); + } return stick; } @@ -117,20 +152,76 @@ namespace OpenTK.Platform.Windows info.Flags = JoystickFlags.All; UnsafeNativeMethods.joyGetPosEx(js.Id, ref info); + int num_axes = js.Axis.Count; + if ((js.Details.PovType & PovType.Exists) != 0) + num_axes -= 2; // Because the last two axis are used for POV input. + int axis = 0; - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.XPos, axis)); axis++; } - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.YPos, axis)); axis++; } - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.ZPos, axis)); axis++; } - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.RPos, axis)); axis++; } - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.UPos, axis)); axis++; } - if (axis < js.Axis.Count) + if (axis < num_axes) { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.VPos, axis)); axis++; } + if ((js.Details.PovType & PovType.Exists) != 0) + { + float x = 0, y = 0; + + // 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.Pov != JoystickPovPosition.Centered) + { + if (info.Pov > (int)JoystickPovPosition.Left || info.Pov < (int)JoystickPovPosition.Right) + { y = 1; } + if ((info.Pov > (int)JoystickPovPosition.Forward) && (info.Pov < (int)JoystickPovPosition.Backward)) + { x = 1; } + if ((info.Pov > (int)JoystickPovPosition.Right) && (info.Pov < (int)JoystickPovPosition.Left)) + { y = -1; } + if (info.Pov > (int)JoystickPovPosition.Backward) + { x = -1; } + } + //if ((js.Details.PovType & PovType.Discrete) != 0) + //{ + // if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Centered) + // { x = 0; y = 0; } + // else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Forward) + // { x = 0; y = -1; } + // else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Left) + // { x = -1; y = 0; } + // else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Backward) + // { x = 0; y = 1; } + // else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Right) + // { x = 1; y = 0; } + //} + //else if ((js.Details.PovType & PovType.Continuous) != 0) + //{ + // if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Centered) + // { + // // This approach moves the hat on a circle, not a square as it should. + // float angle = (float)(System.Math.PI * info.Pov / 18000.0 + System.Math.PI / 2); + // x = (float)System.Math.Cos(angle); + // y = (float)System.Math.Sin(angle); + // if (x < 0.001) + // x = 0; + // if (y < 0.001) + // y = 0; + // } + //} + //else + // throw new NotImplementedException("Please post an issue report at http://www.opentk.com/issues"); + + js.SetAxis((JoystickAxis)axis++, x); + js.SetAxis((JoystickAxis)axis++, y); + } + int button = 0; while (button < js.Button.Count) { @@ -195,13 +286,32 @@ namespace OpenTK.Platform.Windows //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 Pid; + public ushort ProductId; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] - public string Pname; + public string ProductName; public int XMin; public int XMax; public int YMin; @@ -217,7 +327,7 @@ namespace OpenTK.Platform.Windows public int UMax; public int VMin; public int VMax; - public int Caps; + public JoystCapsFlags Capabilities; public int MaxAxes; public int NumAxes; public int MaxButtons; @@ -261,10 +371,25 @@ namespace OpenTK.Platform.Windows static class UnsafeNativeMethods { - [DllImport("Winmm.dll")] + [DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] public static extern JoystickError joyGetDevCaps(int uJoyID, out JoyCaps pjc, int cbjc); - [DllImport("Winmm.dll")] + [DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] public static extern uint joyGetPosEx(int uJoyID, ref JoyInfoEx pji); + [DllImport("Winmm.dll"), SuppressUnmanagedCodeSecurity] + public static extern int joyGetNumDevs(); + } + + #endregion + + #region enum PovType + + [Flags] + enum PovType + { + None = 0x0, + Exists = 0x1, + Discrete = 0x2, + Continuous = 0x4 } #endregion @@ -274,11 +399,13 @@ namespace OpenTK.Platform.Windows struct WinMMJoyDetails { public readonly float[] Min, Max; // Minimum and maximum offset of each axis. + public PovType PovType; public WinMMJoyDetails(int num_axes) { Min = new float[num_axes]; Max = new float[num_axes]; + PovType = PovType.None; } public float CalculateOffset(float pos, int axis)