1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21// LOVE
22#include "common/config.h"
23#include "Joystick.h"
24#include "common/int.h"
25
26// C++
27#include <algorithm>
28#include <limits>
29
30#ifndef SDL_TICKS_PASSED
31#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
32#endif
33
34namespace love
35{
36namespace joystick
37{
38namespace sdl
39{
40
41Joystick::Joystick(int id)
42 : joyhandle(nullptr)
43 , controller(nullptr)
44 , haptic(nullptr)
45 , instanceid(-1)
46 , id(id)
47 , vibration()
48{
49}
50
51Joystick::Joystick(int id, int joyindex)
52 : joyhandle(nullptr)
53 , controller(nullptr)
54 , haptic(nullptr)
55 , instanceid(-1)
56 , id(id)
57 , vibration()
58{
59 open(joyindex);
60}
61
62Joystick::~Joystick()
63{
64 close();
65}
66
67bool Joystick::open(int deviceindex)
68{
69 close();
70
71 joyhandle = SDL_JoystickOpen(deviceindex);
72
73 if (joyhandle)
74 {
75 instanceid = SDL_JoystickInstanceID(joyhandle);
76
77 // SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
78 char cstr[33];
79
80 SDL_JoystickGUID sdlguid = SDL_JoystickGetGUID(joyhandle);
81 SDL_JoystickGetGUIDString(sdlguid, cstr, (int) sizeof(cstr));
82
83 pguid = std::string(cstr);
84
85 // See if SDL thinks this is a Game Controller.
86 openGamepad(deviceindex);
87
88 // Prefer the Joystick name for consistency.
89 const char *joyname = SDL_JoystickName(joyhandle);
90 if (!joyname && controller)
91 joyname = SDL_GameControllerName(controller);
92
93 if (joyname)
94 name = joyname;
95 }
96
97 return isConnected();
98}
99
100void Joystick::close()
101{
102 if (haptic)
103 SDL_HapticClose(haptic);
104
105 if (controller)
106 SDL_GameControllerClose(controller);
107
108 if (joyhandle)
109 SDL_JoystickClose(joyhandle);
110
111 joyhandle = nullptr;
112 controller = nullptr;
113 haptic = nullptr;
114 instanceid = -1;
115 vibration = Vibration();
116}
117
118bool Joystick::isConnected() const
119{
120 return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
121}
122
123const char *Joystick::getName() const
124{
125 return name.c_str();
126}
127
128int Joystick::getAxisCount() const
129{
130 return isConnected() ? SDL_JoystickNumAxes(joyhandle) : 0;
131}
132
133int Joystick::getButtonCount() const
134{
135 return isConnected() ? SDL_JoystickNumButtons(joyhandle) : 0;
136}
137
138int Joystick::getHatCount() const
139{
140 return isConnected() ? SDL_JoystickNumHats(joyhandle) : 0;
141}
142
143float Joystick::getAxis(int axisindex) const
144{
145 if (!isConnected() || axisindex < 0 || axisindex >= getAxisCount())
146 return 0;
147
148 return clampval(((float) SDL_JoystickGetAxis(joyhandle, axisindex))/32768.0f);
149}
150
151std::vector<float> Joystick::getAxes() const
152{
153 std::vector<float> axes;
154 int count = getAxisCount();
155
156 if (!isConnected() || count <= 0)
157 return axes;
158
159 axes.reserve(count);
160
161 for (int i = 0; i < count; i++)
162 axes.push_back(clampval(((float) SDL_JoystickGetAxis(joyhandle, i))/32768.0f));
163
164 return axes;
165}
166
167Joystick::Hat Joystick::getHat(int hatindex) const
168{
169 Hat h = HAT_INVALID;
170
171 if (!isConnected() || hatindex < 0 || hatindex >= getHatCount())
172 return h;
173
174 getConstant(SDL_JoystickGetHat(joyhandle, hatindex), h);
175
176 return h;
177}
178
179bool Joystick::isDown(const std::vector<int> &buttonlist) const
180{
181 if (!isConnected())
182 return false;
183
184 int numbuttons = getButtonCount();
185
186 for (int button : buttonlist)
187 {
188 if (button < 0 || button >= numbuttons)
189 continue;
190
191 if (SDL_JoystickGetButton(joyhandle, button) == 1)
192 return true;
193 }
194
195 return false;
196}
197
198bool Joystick::openGamepad(int deviceindex)
199{
200 if (!SDL_IsGameController(deviceindex))
201 return false;
202
203 if (isGamepad())
204 {
205 SDL_GameControllerClose(controller);
206 controller = nullptr;
207 }
208
209 controller = SDL_GameControllerOpen(deviceindex);
210 return isGamepad();
211}
212
213bool Joystick::isGamepad() const
214{
215 return controller != nullptr;
216}
217
218float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
219{
220 if (!isConnected() || !isGamepad())
221 return 0.f;
222
223 SDL_GameControllerAxis sdlaxis;
224 if (!getConstant(axis, sdlaxis))
225 return 0.f;
226
227 Sint16 value = SDL_GameControllerGetAxis(controller, sdlaxis);
228
229 return clampval((float) value / 32768.0f);
230}
231
232bool Joystick::isGamepadDown(const std::vector<GamepadButton> &blist) const
233{
234 if (!isConnected() || !isGamepad())
235 return false;
236
237 SDL_GameControllerButton sdlbutton;
238
239 for (GamepadButton button : blist)
240 {
241 if (!getConstant(button, sdlbutton))
242 continue;
243
244 if (SDL_GameControllerGetButton(controller, sdlbutton) == 1)
245 return true;
246 }
247
248 return false;
249}
250
251Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput &input) const
252{
253 Joystick::JoystickInput jinput;
254 jinput.type = INPUT_TYPE_MAX_ENUM;
255
256 if (!isGamepad())
257 return jinput;
258
259 SDL_GameControllerButtonBind sdlbind = {};
260 sdlbind.bindType = SDL_CONTROLLER_BINDTYPE_NONE;
261
262 SDL_GameControllerButton sdlbutton;
263 SDL_GameControllerAxis sdlaxis;
264
265 switch (input.type)
266 {
267 case INPUT_TYPE_BUTTON:
268 if (getConstant(input.button, sdlbutton))
269 sdlbind = SDL_GameControllerGetBindForButton(controller, sdlbutton);
270 break;
271 case INPUT_TYPE_AXIS:
272 if (getConstant(input.axis, sdlaxis))
273 sdlbind = SDL_GameControllerGetBindForAxis(controller, sdlaxis);
274 break;
275 default:
276 break;
277 }
278
279 switch (sdlbind.bindType)
280 {
281 case SDL_CONTROLLER_BINDTYPE_BUTTON:
282 jinput.type = INPUT_TYPE_BUTTON;
283 jinput.button = sdlbind.value.button;
284 break;
285 case SDL_CONTROLLER_BINDTYPE_AXIS:
286 jinput.type = INPUT_TYPE_AXIS;
287 jinput.axis = sdlbind.value.axis;
288 break;
289 case SDL_CONTROLLER_BINDTYPE_HAT:
290 if (getConstant(sdlbind.value.hat.hat_mask, jinput.hat.value))
291 {
292 jinput.type = INPUT_TYPE_HAT;
293 jinput.hat.index = sdlbind.value.hat.hat;
294 }
295 break;
296 case SDL_CONTROLLER_BINDTYPE_NONE:
297 default:
298 break;
299 }
300
301 return jinput;
302}
303
304std::string Joystick::getGamepadMappingString() const
305{
306 char *sdlmapping = nullptr;
307
308 if (controller != nullptr)
309 sdlmapping = SDL_GameControllerMapping(controller);
310
311 if (sdlmapping == nullptr)
312 {
313 SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(pguid.c_str());
314 sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
315 }
316
317 if (sdlmapping == nullptr)
318 return "";
319
320 std::string mappingstr(sdlmapping);
321 SDL_free(sdlmapping);
322
323 // Matches SDL_GameControllerAddMappingsFromRW.
324 if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
325 mappingstr += ",";
326 mappingstr += "platform:" + std::string(SDL_GetPlatform());
327
328 return mappingstr;
329}
330
331void *Joystick::getHandle() const
332{
333 return joyhandle;
334}
335
336std::string Joystick::getGUID() const
337{
338 // SDL2's GUIDs identify *classes* of devices, instead of unique devices.
339 return pguid;
340}
341
342int Joystick::getInstanceID() const
343{
344 return instanceid;
345}
346
347int Joystick::getID() const
348{
349 return id;
350}
351
352void Joystick::getDeviceInfo(int &vendorID, int &productID, int &productVersion) const
353{
354#if SDL_VERSION_ATLEAST(2, 0, 6)
355 if (joyhandle != nullptr)
356 {
357 vendorID = SDL_JoystickGetVendor(joyhandle);
358 productID = SDL_JoystickGetProduct(joyhandle);
359 productVersion = SDL_JoystickGetProductVersion(joyhandle);
360 }
361 else
362#endif
363 {
364 vendorID = 0;
365 productID = 0;
366 productVersion = 0;
367 }
368}
369
370bool Joystick::checkCreateHaptic()
371{
372 if (!isConnected())
373 return false;
374
375 if (!SDL_WasInit(SDL_INIT_HAPTIC) && SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0)
376 return false;
377
378 if (haptic && SDL_HapticIndex(haptic) != -1)
379 return true;
380
381 if (haptic)
382 {
383 SDL_HapticClose(haptic);
384 haptic = nullptr;
385 }
386
387 haptic = SDL_HapticOpenFromJoystick(joyhandle);
388 vibration = Vibration();
389
390 return haptic != nullptr;
391}
392
393bool Joystick::isVibrationSupported()
394{
395#if SDL_VERSION_ATLEAST(2, 0, 18)
396 if (isConnected() && SDL_JoystickHasRumble(joyhandle) == SDL_TRUE)
397 return true;
398#endif
399
400 if (!checkCreateHaptic())
401 return false;
402
403 unsigned int features = SDL_HapticQuery(haptic);
404
405 if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
406 return true;
407
408 // Some gamepad drivers only support left/right motors via a custom effect.
409 if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
410 return true;
411
412 // Test for simple sine wave support as a last resort.
413 if ((features & SDL_HAPTIC_SINE) != 0)
414 return true;
415
416 return false;
417}
418
419bool Joystick::runVibrationEffect()
420{
421 if (vibration.id != -1)
422 {
423 if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
424 {
425 if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
426 return true;
427 }
428
429 // If the effect fails to update, we should destroy and re-create it.
430 SDL_HapticDestroyEffect(haptic, vibration.id);
431 vibration.id = -1;
432 }
433
434 vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
435
436 if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
437 return true;
438
439 return false;
440}
441
442bool Joystick::setVibration(float left, float right, float duration)
443{
444 left = std::min(std::max(left, 0.0f), 1.0f);
445 right = std::min(std::max(right, 0.0f), 1.0f);
446
447 if (left == 0.0f && right == 0.0f)
448 return setVibration();
449
450 if (!isConnected())
451 {
452 vibration.left = vibration.right = 0.0f;
453 vibration.endtime = SDL_HAPTIC_INFINITY;
454 return false;
455 }
456
457 Uint32 length = SDL_HAPTIC_INFINITY;
458 if (duration >= 0.0f)
459 {
460 float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f;
461 length = Uint32(std::min(duration, maxduration) * 1000);
462 }
463
464 bool success = false;
465
466#if SDL_VERSION_ATLEAST(2, 0, 9)
467 if (SDL_JoystickRumble(joyhandle, (Uint16)(left * LOVE_UINT16_MAX), (Uint16)(right * LOVE_UINT16_MAX), length) == 0)
468 success = true;
469#endif
470
471 if (!success && !checkCreateHaptic())
472 return false;
473
474 unsigned int features = SDL_HapticQuery(haptic);
475 int axes = SDL_HapticNumAxes(haptic);
476
477 if (!success && (features & SDL_HAPTIC_LEFTRIGHT) != 0)
478 {
479 memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
480 vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
481
482 vibration.effect.leftright.length = length;
483 vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
484 vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
485
486 success = runVibrationEffect();
487 }
488
489 // Some gamepad drivers only give support for controlling individual motors
490 // through a custom FF effect.
491 if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
492 {
493 // NOTE: this may cause issues with drivers which support custom effects
494 // but aren't similar to https://github.com/d235j/360Controller .
495
496 // Custom effect data is clamped to 0x7FFF in SDL.
497 vibration.data[0] = vibration.data[2] = Uint16(left * 0x7FFF);
498 vibration.data[1] = vibration.data[3] = Uint16(right * 0x7FFF);
499
500 memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
501 vibration.effect.type = SDL_HAPTIC_CUSTOM;
502
503 vibration.effect.custom.length = length;
504 vibration.effect.custom.channels = 2;
505 vibration.effect.custom.period = 10;
506 vibration.effect.custom.samples = 2;
507 vibration.effect.custom.data = vibration.data;
508
509 success = runVibrationEffect();
510 }
511
512 // Fall back to a simple sine wave if all else fails. This only supports a
513 // single strength value.
514 if (!success && (features & SDL_HAPTIC_SINE) != 0)
515 {
516 memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
517 vibration.effect.type = SDL_HAPTIC_SINE;
518
519 vibration.effect.periodic.length = length;
520 vibration.effect.periodic.period = 10;
521
522 float strength = std::max(left, right);
523 vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
524
525 success = runVibrationEffect();
526 }
527
528 if (success)
529 {
530 vibration.left = left;
531 vibration.right = right;
532
533 if (length == SDL_HAPTIC_INFINITY)
534 vibration.endtime = SDL_HAPTIC_INFINITY;
535 else
536 vibration.endtime = SDL_GetTicks() + length;
537 }
538 else
539 {
540 vibration.left = vibration.right = 0.0f;
541 vibration.endtime = SDL_HAPTIC_INFINITY;
542 }
543
544 return success;
545}
546
547bool Joystick::setVibration()
548{
549 bool success = false;
550
551#if SDL_VERSION_ATLEAST(2, 0, 9)
552 if (!success)
553 success = isConnected() && SDL_JoystickRumble(joyhandle, 0, 0, 0) == 0;
554#endif
555
556 if (!success && SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
557 success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
558
559 if (success)
560 vibration.left = vibration.right = 0.0f;
561
562 return success;
563}
564
565void Joystick::getVibration(float &left, float &right)
566{
567 if (vibration.endtime != SDL_HAPTIC_INFINITY)
568 {
569 // With some drivers, the effect physically stops at the right time, but
570 // SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly
571 // stop it once it's done, just to be sure.
572 if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime))
573 {
574 setVibration();
575 vibration.endtime = SDL_HAPTIC_INFINITY;
576 }
577 }
578
579 // Check if the haptic effect has stopped playing.
580 int id = vibration.id;
581 if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1)
582 vibration.left = vibration.right = 0.0f;
583
584 left = vibration.left;
585 right = vibration.right;
586}
587
588bool Joystick::getConstant(Uint8 in, Joystick::Hat &out)
589{
590 return hats.find(in, out);
591}
592
593bool Joystick::getConstant(Joystick::Hat in, Uint8 &out)
594{
595 return hats.find(in, out);
596}
597
598bool Joystick::getConstant(SDL_GameControllerAxis in, Joystick::GamepadAxis &out)
599{
600 return gpAxes.find(in, out);
601}
602
603bool Joystick::getConstant(Joystick::GamepadAxis in, SDL_GameControllerAxis &out)
604{
605 return gpAxes.find(in, out);
606}
607
608bool Joystick::getConstant(SDL_GameControllerButton in, Joystick::GamepadButton &out)
609{
610 return gpButtons.find(in, out);
611}
612
613bool Joystick::getConstant(Joystick::GamepadButton in, SDL_GameControllerButton &out)
614{
615 return gpButtons.find(in, out);
616}
617
618EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM>::Entry Joystick::hatEntries[] =
619{
620 {Joystick::HAT_CENTERED, SDL_HAT_CENTERED},
621 {Joystick::HAT_UP, SDL_HAT_UP},
622 {Joystick::HAT_RIGHT, SDL_HAT_RIGHT},
623 {Joystick::HAT_DOWN, SDL_HAT_DOWN},
624 {Joystick::HAT_LEFT, SDL_HAT_LEFT},
625 {Joystick::HAT_RIGHTUP, SDL_HAT_RIGHTUP},
626 {Joystick::HAT_RIGHTDOWN, SDL_HAT_RIGHTDOWN},
627 {Joystick::HAT_LEFTUP, SDL_HAT_LEFTUP},
628 {Joystick::HAT_LEFTDOWN, SDL_HAT_LEFTDOWN},
629};
630
631EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM> Joystick::hats(Joystick::hatEntries, sizeof(Joystick::hatEntries));
632
633EnumMap<Joystick::GamepadAxis, SDL_GameControllerAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM>::Entry Joystick::gpAxisEntries[] =
634{
635 {Joystick::GAMEPAD_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTX},
636 {Joystick::GAMEPAD_AXIS_LEFTY, SDL_CONTROLLER_AXIS_LEFTY},
637 {Joystick::GAMEPAD_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTX},
638 {Joystick::GAMEPAD_AXIS_RIGHTY, SDL_CONTROLLER_AXIS_RIGHTY},
639 {Joystick::GAMEPAD_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
640 {Joystick::GAMEPAD_AXIS_TRIGGERRIGHT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
641};
642
643EnumMap<Joystick::GamepadAxis, SDL_GameControllerAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM> Joystick::gpAxes(Joystick::gpAxisEntries, sizeof(Joystick::gpAxisEntries));
644
645EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM>::Entry Joystick::gpButtonEntries[] =
646{
647 {Joystick::GAMEPAD_BUTTON_A, SDL_CONTROLLER_BUTTON_A},
648 {Joystick::GAMEPAD_BUTTON_B, SDL_CONTROLLER_BUTTON_B},
649 {Joystick::GAMEPAD_BUTTON_X, SDL_CONTROLLER_BUTTON_X},
650 {Joystick::GAMEPAD_BUTTON_Y, SDL_CONTROLLER_BUTTON_Y},
651 {Joystick::GAMEPAD_BUTTON_BACK, SDL_CONTROLLER_BUTTON_BACK},
652 {Joystick::GAMEPAD_BUTTON_GUIDE, SDL_CONTROLLER_BUTTON_GUIDE},
653 {Joystick::GAMEPAD_BUTTON_START, SDL_CONTROLLER_BUTTON_START},
654 {Joystick::GAMEPAD_BUTTON_LEFTSTICK, SDL_CONTROLLER_BUTTON_LEFTSTICK},
655 {Joystick::GAMEPAD_BUTTON_RIGHTSTICK, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
656 {Joystick::GAMEPAD_BUTTON_LEFTSHOULDER, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
657 {Joystick::GAMEPAD_BUTTON_RIGHTSHOULDER, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
658 {Joystick::GAMEPAD_BUTTON_DPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_UP},
659 {Joystick::GAMEPAD_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
660 {Joystick::GAMEPAD_BUTTON_DPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
661 {Joystick::GAMEPAD_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
662};
663
664EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));
665
666} // sdl
667} // joystick
668} // love
669