2 // The Open Toolkit Library License
4 // Copyright (c) 2006 - 2013 Stefanos Apostolopoulos for the Open Toolkit library.
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights to
9 // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 // the Software, and to permit persons to whom the Software is furnished to do
11 // so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in all
14 // copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 // OTHER DEALINGS IN THE SOFTWARE.
27 using System.Collections.Generic;
28 using System.Diagnostics;
31 namespace OpenTK.Platform.SDL2
33 internal class Sdl2JoystickDriver : IJoystickDriver2, IGamePadDriver, IDisposable
35 private const float RangeMultiplier = 1.0f / 32768.0f;
36 private readonly MappedGamePadDriver gamepad_driver = new MappedGamePadDriver();
37 private bool disposed;
39 private class Sdl2JoystickDetails
41 public IntPtr Handle { get; set; }
42 public Guid Guid { get; set; }
43 public int InstanceId { get; set; }
44 public int PacketNumber { get; set; }
45 public int HatCount { get; set; }
46 public int BallCount { get; set; }
47 public bool IsConnected { get; set; }
48 public readonly JoystickHatState[] Hat =
49 new JoystickHatState[JoystickState.MaxHats];
52 // For IJoystickDriver2 implementation
53 private readonly List<JoystickDevice> joysticks = new List<JoystickDevice>(4);
55 private readonly Dictionary<int, int> sdl_instanceid_to_joysticks = new Dictionary<int, int>();
57 #if USE_SDL2_GAMECONTROLLER
60 public IntPtr Handle { get; private set; }
61 public GamePadState State;
62 public GamePadCapabilities Capabilities;
64 public Sdl2GamePad(IntPtr handle)
70 int last_controllers_instance = 0;
71 readonly List<Sdl2GamePad> controllers = new List<Sdl2GamePad>(4);
72 readonly Dictionary<int, int> sdl_instanceid_to_controllers = new Dictionary<int, int>();
75 public Sdl2JoystickDriver()
79 private JoystickDevice<Sdl2JoystickDetails> OpenJoystick(int id)
81 JoystickDevice<Sdl2JoystickDetails> joystick = null;
87 IntPtr handle = SDL.JoystickOpen(id);
88 if (handle != IntPtr.Zero)
90 num_axes = SDL.JoystickNumAxes(handle);
91 num_buttons = SDL.JoystickNumButtons(handle);
92 num_hats = SDL.JoystickNumHats(handle);
93 num_balls = SDL.JoystickNumBalls(handle);
95 joystick = new JoystickDevice<Sdl2JoystickDetails>(id, num_axes, num_buttons);
96 joystick.Description = SDL.JoystickName(handle);
97 joystick.Details.Handle = handle;
98 joystick.Details.InstanceId = SDL.JoystickInstanceID(handle);
99 joystick.Details.Guid = SDL.JoystickGetGUID(handle).ToGuid();
100 joystick.Details.HatCount = num_hats;
101 joystick.Details.BallCount = num_balls;
103 Debug.Print("[SDL2] Joystick device {0} opened successfully. ", id);
104 Debug.Print("\t\t'{0}' has {1} axes, {2} buttons, {3} hats, {4} balls",
105 joystick.Description, joystick.Axis.Count, joystick.Button.Count,
106 joystick.Details.HatCount, joystick.Details.BallCount);
110 Debug.Print("[SDL2] Failed to open joystick device {0}", id);
116 private bool IsJoystickValid(int id)
118 return id >= 0 && id < joysticks.Count;
121 private bool IsJoystickInstanceValid(int instance_id)
123 return sdl_instanceid_to_joysticks.ContainsKey(instance_id);
126 private OpenTK.Input.HatPosition TranslateHat(HatPosition value)
128 if ((value & HatPosition.LeftUp) == HatPosition.LeftUp)
130 return OpenTK.Input.HatPosition.UpLeft;
133 if ((value & HatPosition.RightUp) == HatPosition.RightUp)
135 return OpenTK.Input.HatPosition.UpRight;
138 if ((value & HatPosition.LeftDown) == HatPosition.LeftDown)
140 return OpenTK.Input.HatPosition.DownLeft;
143 if ((value & HatPosition.RightDown) == HatPosition.RightDown)
145 return OpenTK.Input.HatPosition.DownRight;
148 if ((value & HatPosition.Up) == HatPosition.Up)
150 return OpenTK.Input.HatPosition.Up;
153 if ((value & HatPosition.Right) == HatPosition.Right)
155 return OpenTK.Input.HatPosition.Right;
158 if ((value & HatPosition.Down) == HatPosition.Down)
160 return OpenTK.Input.HatPosition.Down;
163 if ((value & HatPosition.Left) == HatPosition.Left)
165 return OpenTK.Input.HatPosition.Left;
168 return OpenTK.Input.HatPosition.Centered;
171 #if USE_SDL2_GAMECONTROLLER
172 bool IsControllerValid(int id)
174 return id >= 0 && id < controllers.Count;
177 bool IsControllerInstanceValid(int instance_id)
179 return sdl_instanceid_to_controllers.ContainsKey(instance_id);
182 GamePadAxes GetBoundAxes(IntPtr gamecontroller)
184 GamePadAxes axes = 0;
185 axes |= IsAxisBind(gamecontroller, GameControllerAxis.LeftX) ? GamePadAxes.LeftX : 0;
186 axes |= IsAxisBind(gamecontroller, GameControllerAxis.LeftY) ? GamePadAxes.LeftY : 0;
187 axes |= IsAxisBind(gamecontroller, GameControllerAxis.RightX) ? GamePadAxes.RightX : 0;
188 axes |= IsAxisBind(gamecontroller, GameControllerAxis.RightY) ? GamePadAxes.RightY : 0;
189 axes |= IsAxisBind(gamecontroller, GameControllerAxis.TriggerLeft) ? GamePadAxes.LeftTrigger : 0;
190 axes |= IsAxisBind(gamecontroller, GameControllerAxis.TriggerRight) ? GamePadAxes.RightTrigger : 0;
194 Buttons GetBoundButtons(IntPtr gamecontroller)
197 buttons |= IsButtonBind(gamecontroller, GameControllerButton.A) ? Buttons.A : 0;
198 buttons |= IsButtonBind(gamecontroller, GameControllerButton.B) ? Buttons.B : 0;
199 buttons |= IsButtonBind(gamecontroller, GameControllerButton.X) ? Buttons.X : 0;
200 buttons |= IsButtonBind(gamecontroller, GameControllerButton.Y) ? Buttons.Y : 0;
201 buttons |= IsButtonBind(gamecontroller, GameControllerButton.START) ? Buttons.Start : 0;
202 buttons |= IsButtonBind(gamecontroller, GameControllerButton.BACK) ? Buttons.Back : 0;
203 buttons |= IsButtonBind(gamecontroller, GameControllerButton.LEFTSHOULDER) ? Buttons.LeftShoulder : 0;
204 buttons |= IsButtonBind(gamecontroller, GameControllerButton.RIGHTSHOULDER) ? Buttons.RightShoulder : 0;
205 buttons |= IsButtonBind(gamecontroller, GameControllerButton.LEFTSTICK) ? Buttons.LeftStick : 0;
206 buttons |= IsButtonBind(gamecontroller, GameControllerButton.RIGHTSTICK) ? Buttons.RightStick : 0;
207 buttons |= IsButtonBind(gamecontroller, GameControllerButton.GUIDE) ? Buttons.BigButton : 0;
208 buttons |= IsButtonBind(gamecontroller, GameControllerButton.DPAD_DOWN) ? Buttons.DPadDown : 0;
209 buttons |= IsButtonBind(gamecontroller, GameControllerButton.DPAD_UP) ? Buttons.DPadUp : 0;
210 buttons |= IsButtonBind(gamecontroller, GameControllerButton.DPAD_LEFT) ? Buttons.DPadLeft : 0;
211 buttons |= IsButtonBind(gamecontroller, GameControllerButton.DPAD_RIGHT) ? Buttons.DPadRight : 0;
215 bool IsAxisBind(IntPtr gamecontroller, GameControllerAxis axis)
217 GameControllerButtonBind bind =
218 SDL.GameControllerGetBindForAxis(gamecontroller, axis);
219 return bind.BindType == GameControllerBindType.Axis;
222 bool IsButtonBind(IntPtr gamecontroller, GameControllerButton button)
224 GameControllerButtonBind bind =
225 SDL.GameControllerGetBindForButton(gamecontroller, button);
226 return bind.BindType == GameControllerBindType.Button;
229 GamePadAxes TranslateAxis(GameControllerAxis axis)
233 case GameControllerAxis.LeftX:
234 return GamePadAxes.LeftX;
236 case GameControllerAxis.LeftY:
237 return GamePadAxes.LeftY;
239 case GameControllerAxis.RightX:
240 return GamePadAxes.RightX;
242 case GameControllerAxis.RightY:
243 return GamePadAxes.RightY;
245 case GameControllerAxis.TriggerLeft:
246 return GamePadAxes.LeftTrigger;
248 case GameControllerAxis.TriggerRight:
249 return GamePadAxes.RightTrigger;
252 throw new ArgumentOutOfRangeException(
253 String.Format("[SDL] Unknown axis {0}", axis));
257 Buttons TranslateButton(GameControllerButton button)
261 case GameControllerButton.A:
264 case GameControllerButton.B:
267 case GameControllerButton.X:
270 case GameControllerButton.Y:
273 case GameControllerButton.LEFTSHOULDER:
274 return Buttons.LeftShoulder;
276 case GameControllerButton.RIGHTSHOULDER:
277 return Buttons.RightShoulder;
279 case GameControllerButton.LEFTSTICK:
280 return Buttons.LeftStick;
282 case GameControllerButton.RIGHTSTICK:
283 return Buttons.RightStick;
285 case GameControllerButton.DPAD_UP:
286 return Buttons.DPadUp;
288 case GameControllerButton.DPAD_DOWN:
289 return Buttons.DPadDown;
291 case GameControllerButton.DPAD_LEFT:
292 return Buttons.DPadLeft;
294 case GameControllerButton.DPAD_RIGHT:
295 return Buttons.DPadRight;
297 case GameControllerButton.BACK:
300 case GameControllerButton.START:
301 return Buttons.Start;
303 case GameControllerButton.GUIDE:
304 return Buttons.BigButton;
307 Debug.Print("[SDL2] Unknown button {0}", button);
313 public void ProcessJoystickEvent(JoyDeviceEvent ev)
318 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
324 case EventType.JOYDEVICEADDED:
326 IntPtr handle = SDL.JoystickOpen(id);
327 if (handle != IntPtr.Zero)
329 JoystickDevice<Sdl2JoystickDetails> joystick = OpenJoystick(id);
331 int instance_id = joystick.Details.InstanceId;
334 if (joystick != null)
336 joystick.Details.IsConnected = true;
337 if (device_id < joysticks.Count)
339 joysticks[device_id] = joystick;
343 joysticks.Add(joystick);
346 sdl_instanceid_to_joysticks.Add(instance_id, device_id);
352 case EventType.JOYDEVICEREMOVED:
353 if (IsJoystickInstanceValid(id))
355 int instance_id = id;
356 int device_id = sdl_instanceid_to_joysticks[instance_id];
358 JoystickDevice<Sdl2JoystickDetails> joystick = (JoystickDevice<Sdl2JoystickDetails>)joysticks[device_id];
359 joystick.Details.IsConnected = false;
361 sdl_instanceid_to_joysticks.Remove(instance_id);
365 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
371 public void ProcessJoystickEvent(JoyAxisEvent ev)
374 if (IsJoystickInstanceValid(id))
376 int index = sdl_instanceid_to_joysticks[id];
377 JoystickDevice<Sdl2JoystickDetails> joystick = (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
378 float value = ev.Value * RangeMultiplier;
379 joystick.SetAxis(ev.Axis, value);
380 joystick.Details.PacketNumber = Math.Max(0, unchecked(joystick.Details.PacketNumber + 1));
384 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
388 public void ProcessJoystickEvent(JoyBallEvent ev)
391 if (IsJoystickInstanceValid(id))
393 int index = sdl_instanceid_to_joysticks[id];
394 JoystickDevice<Sdl2JoystickDetails> joystick = (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
395 // Todo: does it make sense to support balls?
396 joystick.Details.PacketNumber = Math.Max(0, unchecked(joystick.Details.PacketNumber + 1));
400 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
404 public void ProcessJoystickEvent(JoyButtonEvent ev)
407 if (IsJoystickInstanceValid(id))
409 int index = sdl_instanceid_to_joysticks[id];
410 JoystickDevice<Sdl2JoystickDetails> joystick = (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
411 joystick.SetButton(ev.Button, ev.State == State.Pressed);
412 joystick.Details.PacketNumber = Math.Max(0, unchecked(joystick.Details.PacketNumber + 1));
416 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
420 public void ProcessJoystickEvent(JoyHatEvent ev)
423 if (IsJoystickInstanceValid(id))
425 int index = sdl_instanceid_to_joysticks[id];
426 JoystickDevice<Sdl2JoystickDetails> joystick = (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
427 if (ev.Hat >= 0 && ev.Hat < JoystickState.MaxHats)
429 joystick.Details.Hat[ev.Hat] = new JoystickHatState(TranslateHat(ev.Value));
433 Debug.Print("[SDL2] Hat {0} out of range [0, {1}]", ev.Hat, JoystickState.MaxHats);
435 joystick.Details.PacketNumber = Math.Max(0, unchecked(joystick.Details.PacketNumber + 1));
439 Debug.Print("[SDL2] Invalid joystick id {0} in {1}", id, ev.Type);
443 #if USE_SDL2_GAMECONTROLLER
444 public void ProcessControllerEvent(ControllerDeviceEvent ev)
449 Debug.Print("[SDL2] Invalid controller id {0} in {1}", id, ev.Type);
455 case EventType.CONTROLLERDEVICEADDED:
456 IntPtr handle = SDL.GameControllerOpen(id);
457 if (handle != IntPtr.Zero)
459 // The id variable here corresponds to a device_id between 0 and Sdl.NumJoysticks().
460 // It is only used in the ADDED event. All other events use an instance_id which increases
461 // monotonically in each ADDED event.
462 // The idea is that device_id refers to the n-th connected joystick, whereas instance_id
463 // refers to the actual hardware device behind the n-th joystick.
464 // Yes, it's confusing.
466 int instance_id = last_controllers_instance++;
468 Sdl2GamePad pad = new Sdl2GamePad(handle);
470 IntPtr joystick = SDL.GameControllerGetJoystick(handle);
471 if (joystick != IntPtr.Zero)
473 pad.Capabilities = new GamePadCapabilities(
475 GetBoundAxes(joystick),
476 GetBoundButtons(joystick),
478 pad.State.SetConnected(true);
480 // Connect this device and add the relevant device index
481 if (controllers.Count <= id)
483 controllers.Add(pad);
487 controllers[device_id] = pad;
490 sdl_instanceid_to_controllers.Add(instance_id, device_id);
494 Debug.Print("[SDL2] Failed to retrieve joystick from game controller. Error: {0}", SDL.GetError());
499 case EventType.CONTROLLERDEVICEREMOVED:
500 if (IsControllerInstanceValid(id))
502 int instance_id = id;
503 int device_id = sdl_instanceid_to_controllers[instance_id];
505 controllers[device_id].State.SetConnected(false);
506 sdl_instanceid_to_controllers.Remove(device_id);
510 Debug.Print("[SDL2] Invalid game controller instance {0} in {1}", id, ev.Type);
514 case EventType.CONTROLLERDEVICEREMAPPED:
515 if (IsControllerInstanceValid(id))
517 // Todo: what should we do in this case?
521 Debug.Print("[SDL2] Invalid game controller instance {0} in {1}", id, ev.Type);
527 public void ProcessControllerEvent(ControllerAxisEvent ev)
529 int instance_id = ev.Which;
530 if (IsControllerInstanceValid(instance_id))
532 int id = sdl_instanceid_to_controllers[instance_id];
533 controllers[id].State.SetAxis(TranslateAxis(ev.Axis), ev.Value);
537 Debug.Print("[SDL2] Invalid game controller instance {0} in {1}", instance_id, ev.Type);
541 public void ProcessControllerEvent(ControllerButtonEvent ev)
543 int instance_id = ev.Which;
544 if (IsControllerInstanceValid(instance_id))
546 int id = sdl_instanceid_to_controllers[instance_id];
547 controllers[id].State.SetButton(TranslateButton(ev.Button), ev.State == State.Pressed);
551 Debug.Print("[SDL2] Invalid game controller instance {0} in {1}", instance_id, ev.Type);
556 #if USE_SDL2_GAMECONTOLLER
557 public GamePadCapabilities GetCapabilities(int index)
559 if (IsControllerValid(index))
561 return controllers[index].Capabilities;
563 return new GamePadCapabilities();
566 public GamePadState GetState(int index)
568 if (IsControllerValid(index))
570 return controllers[index].State;
572 return new GamePadState();
575 public string GetName(int index)
580 public GamePadCapabilities GetCapabilities(int index)
582 return gamepad_driver.GetCapabilities(index);
585 public GamePadState GetState(int index)
587 return gamepad_driver.GetState(index);
590 public string GetName(int index)
592 return gamepad_driver.GetName(index);
595 public bool SetVibration(int index, float left, float right)
601 JoystickState IJoystickDriver2.GetState(int index)
603 JoystickState state = new JoystickState();
604 if (IsJoystickValid(index))
606 JoystickDevice<Sdl2JoystickDetails> joystick =
607 (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
609 for (int i = 0; i < joystick.Axis.Count; i++)
611 state.SetAxis(i, (short)(joystick.Axis[i] * short.MaxValue + 0.5f));
614 for (int i = 0; i < joystick.Button.Count; i++)
616 state.SetButton(i, joystick.Button[i]);
619 for (int i = 0; i < joystick.Details.HatCount; i++)
621 state.SetHat(JoystickHat.Hat0 + i, joystick.Details.Hat[i]);
624 state.SetIsConnected(joystick.Details.IsConnected);
625 state.SetPacketNumber(joystick.Details.PacketNumber);
631 JoystickCapabilities IJoystickDriver2.GetCapabilities(int index)
633 if (IsJoystickValid(index))
635 JoystickDevice<Sdl2JoystickDetails> joystick =
636 (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
638 return new JoystickCapabilities(
640 joystick.Button.Count,
641 joystick.Details.HatCount,
642 joystick.Details.IsConnected);
644 return new JoystickCapabilities();
647 Guid IJoystickDriver2.GetGuid(int index)
649 Guid guid = new Guid();
650 if (IsJoystickValid(index))
652 JoystickDevice<Sdl2JoystickDetails> joystick =
653 (JoystickDevice<Sdl2JoystickDetails>)joysticks[index];
655 return joystick.Details.Guid;
660 private void Dispose(bool manual)
666 Debug.Print("Disposing {0}", GetType());
668 foreach (var j in joysticks)
670 var joystick = (JoystickDevice<Sdl2JoystickDetails>)j;
671 IntPtr handle = joystick.Details.Handle;
672 SDL.JoystickClose(handle);
679 Debug.Print("{0} leaked, did you forget to call Dispose()?", GetType());
685 public void Dispose()
688 GC.SuppressFinalize(this);
691 ~Sdl2JoystickDriver()