Skip to content

Commit 1be2848

Browse files
add basic gamepad support
1 parent cc37fb9 commit 1be2848

File tree

3 files changed

+199
-62
lines changed

3 files changed

+199
-62
lines changed

code/io/joy-sdl.cpp

Lines changed: 187 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ void setPlayerJoystick(Joystick* stick, short cid)
224224
if (pJoystick[cid] != nullptr) {
225225
mprintf((" Using '%s' as Joy-%i\n", pJoystick[cid]->getName().c_str(), cid));
226226
mprintf(("\n"));
227+
mprintf((" Is gamepad: %s\n", pJoystick[cid]->isGamepad() ? "YES" : "NO"));
227228
mprintf((" Number of axes: %d\n", pJoystick[cid]->numAxes()));
228229
mprintf((" Number of buttons: %d\n", pJoystick[cid]->numButtons()));
229230
mprintf((" Number of hats: %d\n", pJoystick[cid]->numHats()));
@@ -573,7 +574,12 @@ namespace joystick
573574
Joystick::Joystick(int id) :
574575
_id(id)
575576
{
576-
_joystick = SDL_OpenJoystick(id);
577+
if (SDL_IsGamepad(id)) {
578+
_gamepad = SDL_OpenGamepad(id);
579+
_joystick = SDL_GetGamepadJoystick(_gamepad);
580+
} else {
581+
_joystick = SDL_OpenJoystick(id);
582+
}
577583

578584
if (_joystick == nullptr) {
579585
SCP_stringstream msg;
@@ -585,14 +591,20 @@ namespace joystick
585591
}
586592

587593
Joystick::Joystick(Joystick &&other) noexcept :
588-
_joystick(nullptr)
594+
_joystick(nullptr), _gamepad(nullptr)
589595
{
590596
*this = std::move(other);
591597
}
592598

593599
Joystick::~Joystick()
594600
{
595-
if (_joystick != nullptr)
601+
if (_gamepad != nullptr)
602+
{
603+
SDL_CloseGamepad(_gamepad); // also closes joystick
604+
_gamepad = nullptr;
605+
_joystick = nullptr;
606+
}
607+
else if (_joystick != nullptr)
596608
{
597609
SDL_CloseJoystick(_joystick);
598610
_joystick = nullptr;
@@ -606,6 +618,7 @@ namespace joystick
606618

607619
std::swap(_id, other._id);
608620
std::swap(_joystick, other._joystick);
621+
std::swap(_gamepad, other._gamepad);
609622

610623
fillValues();
611624

@@ -614,7 +627,7 @@ namespace joystick
614627

615628
bool Joystick::isAttached() const
616629
{
617-
return SDL_JoystickConnected(_joystick);
630+
return isGamepad() ? SDL_GamepadConnected(_gamepad) : SDL_JoystickConnected(_joystick);
618631
}
619632

620633
bool Joystick::isHaptic() const
@@ -776,22 +789,35 @@ namespace joystick
776789

777790
void Joystick::fillValues()
778791
{
792+
// To avoid some weirdness and build compatiblity issues we always use
793+
// _joystick here rather than comparable _gamepad functions
794+
779795
_name.assign(SDL_GetJoystickName(_joystick));
780796
_guidStr = getJoystickGUID(_joystick);
781797
_isHaptic = SDL_IsJoystickHaptic(_joystick);
782798
_isGamepad = SDL_IsGamepad(_id);
783799

784800
// Initialize values of the axes
785-
auto numSticks = SDL_GetNumJoystickAxes(_joystick);
786-
if (numSticks >= 0) {
787-
_axisValues.resize(static_cast<size_t>(numSticks));
788-
for (auto i = 0; i < numSticks; ++i) {
789-
_axisValues[i] = SDL_GetJoystickAxis(_joystick, i);
801+
if (_isGamepad) {
802+
// gamepads may not have all axes, but they don't necessarily match
803+
// the number or index of what's reported by the joystick api either
804+
_axisValues.resize(static_cast<size_t>(SDL_GAMEPAD_AXIS_COUNT));
805+
for (size_t i = 0; i < _axisValues.size(); ++i) {
806+
// will return 0 (center) if axis not supported
807+
_axisValues[i] = SDL_GetGamepadAxis(_gamepad, static_cast<SDL_GamepadAxis>(i));
790808
}
791-
792809
} else {
793-
_axisValues.resize(0);
794-
mprintf(("Failed to get number of axes for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
810+
auto numSticks = SDL_GetNumJoystickAxes(_joystick);
811+
if (numSticks >= 0) {
812+
_axisValues.resize(static_cast<size_t>(numSticks));
813+
for (auto i = 0; i < numSticks; ++i) {
814+
_axisValues[i] = SDL_GetJoystickAxis(_joystick, i);
815+
}
816+
817+
} else {
818+
_axisValues.resize(0);
819+
mprintf(("Failed to get number of axes for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
820+
}
795821
}
796822

797823
// Initialize ball values
@@ -812,30 +838,43 @@ namespace joystick
812838
}
813839

814840
// Initialize buttons
815-
auto buttonNum = SDL_GetNumJoystickButtons(_joystick);
816-
if (buttonNum >= 0) {
817-
_button.resize(static_cast<size_t>(buttonNum));
818-
for (auto i = 0; i < buttonNum; ++i) {
819-
if (SDL_GetJoystickButton(_joystick, i)) {
841+
if (_isGamepad) {
842+
// gamepads may not support all buttons, but they don't necessarily match
843+
// the number or index of what's reported by the joystick api either
844+
_button.resize(static_cast<size_t>(SDL_GAMEPAD_BUTTON_COUNT));
845+
for (size_t i = 0; i < _button.size(); ++i) {
846+
if (SDL_GetGamepadButton(_gamepad, static_cast<SDL_GamepadButton>(i))) {
820847
_button[i].DownTimestamp = ui_timestamp();
821-
822848
} else {
823849
_button[i].DownTimestamp = UI_TIMESTAMP::invalid();
824850
}
825851
}
826-
827852
} else {
828-
_button.resize(0);
829-
mprintf(("Failed to get number of buttons for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
853+
auto buttonNum = SDL_GetNumJoystickButtons(_joystick);
854+
if (buttonNum >= 0) {
855+
_button.resize(static_cast<size_t>(buttonNum));
856+
for (auto i = 0; i < buttonNum; ++i) {
857+
if (SDL_GetJoystickButton(_joystick, i)) {
858+
_button[i].DownTimestamp = ui_timestamp();
859+
860+
} else {
861+
_button[i].DownTimestamp = UI_TIMESTAMP::invalid();
862+
}
863+
}
864+
865+
} else {
866+
_button.resize(0);
867+
mprintf(("Failed to get number of buttons for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
868+
}
830869
}
831870

832871
// Initialize hats
833872
auto hatNum = SDL_GetNumJoystickHats(_joystick);
873+
if (_isGamepad) hatNum = 1; // consider gamepads to always have one hat
834874
if (hatNum >= 0) {
835875
_hat.resize(static_cast<size_t>(hatNum));
836876
for (auto i = 0; i < hatNum; ++i) {
837-
std::bitset<4> hatset = SDL_GetJoystickHat(_joystick, i);
838-
auto hatval = convertSDLHat(SDL_GetJoystickHat(_joystick, i));
877+
auto hatval = _isGamepad ? HAT_CENTERED : convertSDLHat(SDL_GetJoystickHat(_joystick, i));
839878
_hat[i].Value = hatval;
840879

841880
// Reset timestampts
@@ -847,21 +886,23 @@ namespace joystick
847886
}
848887

849888
if (_hat[i].Value != HAT_CENTERED) {
850-
// Set the 4-pos timestamp(s)
851-
if ((hatset[HAT_DOWN])) {
852-
_hat[i].DownTimestamp4[HAT_DOWN] = ui_timestamp();
853-
}
854-
if ((hatset[HAT_UP])) {
855-
_hat[i].DownTimestamp4[HAT_UP] = ui_timestamp();
856-
}
857-
if ((hatset[HAT_LEFT])) {
858-
_hat[i].DownTimestamp4[HAT_LEFT] = ui_timestamp();
859-
}
860-
if ((hatset[HAT_RIGHT])) {
861-
_hat[i].DownTimestamp4[HAT_RIGHT] = ui_timestamp();
862-
}
889+
std::bitset<4> hatset = SDL_GetJoystickHat(_joystick, i);
890+
891+
// Set the 4-pos timestamp(s)
892+
if ((hatset[HAT_DOWN])) {
893+
_hat[i].DownTimestamp4[HAT_DOWN] = ui_timestamp();
894+
}
895+
if ((hatset[HAT_UP])) {
896+
_hat[i].DownTimestamp4[HAT_UP] = ui_timestamp();
897+
}
898+
if ((hatset[HAT_LEFT])) {
899+
_hat[i].DownTimestamp4[HAT_LEFT] = ui_timestamp();
900+
}
901+
if ((hatset[HAT_RIGHT])) {
902+
_hat[i].DownTimestamp4[HAT_RIGHT] = ui_timestamp();
903+
}
863904

864-
// Set the 8-pos timestamp
905+
// Set the 8-pos timestamp
865906
_hat[i].DownTimestamp8[hatval] = ui_timestamp();
866907
}
867908
}
@@ -871,30 +912,50 @@ namespace joystick
871912
}
872913
}
873914

874-
SDL_Joystick *Joystick::getDevice()
915+
SDL_Joystick *Joystick::getJoystick()
875916
{
876917
return _joystick;
877918
}
878919

920+
SDL_Gamepad *Joystick::getGamepad()
921+
{
922+
return _gamepad;
923+
}
924+
879925
void Joystick::handleJoyEvent(const SDL_Event &evt)
880926
{
881-
switch (evt.type)
882-
{
883-
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
884-
handleAxisEvent(evt.jaxis);
885-
break;
886-
case SDL_EVENT_JOYSTICK_BALL_MOTION:
887-
handleBallEvent(evt.jball);
888-
break;
889-
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
890-
case SDL_EVENT_JOYSTICK_BUTTON_UP:
891-
handleButtonEvent(evt.jbutton);
892-
break;
893-
case SDL_EVENT_JOYSTICK_HAT_MOTION:
894-
handleHatEvent(evt.jhat);
895-
break;
896-
default:
897-
break;
927+
// gamepads also get joy events, so make sure we ignore those
928+
if (isGamepad()) {
929+
switch (evt.type) {
930+
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
931+
handleAxisEvent(evt.gaxis);
932+
break;
933+
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
934+
case SDL_EVENT_GAMEPAD_BUTTON_UP:
935+
handleButtonEvent(evt.gbutton);
936+
break;
937+
default:
938+
break;
939+
}
940+
} else {
941+
switch (evt.type)
942+
{
943+
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
944+
handleAxisEvent(evt.jaxis);
945+
break;
946+
case SDL_EVENT_JOYSTICK_BALL_MOTION:
947+
handleBallEvent(evt.jball);
948+
break;
949+
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
950+
case SDL_EVENT_JOYSTICK_BUTTON_UP:
951+
handleButtonEvent(evt.jbutton);
952+
break;
953+
case SDL_EVENT_JOYSTICK_HAT_MOTION:
954+
handleHatEvent(evt.jhat);
955+
break;
956+
default:
957+
break;
958+
}
898959
}
899960
}
900961

@@ -907,6 +968,16 @@ namespace joystick
907968
_axisValues[axis] = evt.value;
908969
}
909970

971+
// gamepad version of event (same code, but different struct)
972+
void Joystick::handleAxisEvent(const SDL_GamepadAxisEvent &evt)
973+
{
974+
auto axis = evt.axis;
975+
976+
Assertion(axis < numAxes(), "SDL event contained invalid axis index!");
977+
978+
_axisValues[axis] = evt.value;
979+
}
980+
910981
void Joystick::handleButtonEvent(const SDL_JoyButtonEvent &evt)
911982
{
912983
auto button = evt.button;
@@ -922,6 +993,58 @@ namespace joystick
922993
}
923994
}
924995

996+
// gamepad version of event (we deal with dpad->hat translation here too)
997+
void Joystick::handleButtonEvent(const SDL_GamepadButtonEvent &evt)
998+
{
999+
auto button = evt.button;
1000+
auto down = evt.down;
1001+
1002+
Assertion(button < numButtons(), "SDL event contained invalid button index!");
1003+
1004+
// treat dpad as hat
1005+
if (button >= SDL_GAMEPAD_BUTTON_DPAD_UP && button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
1006+
HatPosition hatpos;
1007+
1008+
if (numHats() != 1) {
1009+
return;
1010+
}
1011+
1012+
switch (button) {
1013+
case SDL_GAMEPAD_BUTTON_DPAD_UP:
1014+
hatpos = HAT_UP;
1015+
break;
1016+
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
1017+
hatpos = HAT_DOWN;
1018+
break;
1019+
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
1020+
hatpos = HAT_LEFT;
1021+
break;
1022+
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
1023+
hatpos = HAT_RIGHT;
1024+
break;
1025+
default:
1026+
return;
1027+
}
1028+
1029+
// Set current values
1030+
_hat[0].Value = hatpos;
1031+
1032+
_hat[0].DownTimestamp4[hatpos] = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
1033+
_hat[0].DownTimestamp8[hatpos] = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
1034+
1035+
if (down) {
1036+
++_hat[0].DownCount4[hatpos];
1037+
++_hat[0].DownCount8[hatpos];
1038+
}
1039+
} else {
1040+
_button[button].DownTimestamp = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
1041+
1042+
if (down) {
1043+
++_button[button].DownCount;
1044+
}
1045+
}
1046+
}
1047+
9251048
void Joystick::handleHatEvent(const SDL_JoyHatEvent &evt)
9261049
{
9271050
auto hat = evt.hat;
@@ -1019,15 +1142,13 @@ namespace joystick
10191142

10201143
mprintf(("Initializing Joystick...\n"));
10211144

1022-
if ( !SDL_InitSubSystem(SDL_INIT_JOYSTICK) )
1145+
// NOTE: gamepad depends on joystick, so this handles both
1146+
if ( !SDL_InitSubSystem(SDL_INIT_GAMEPAD) )
10231147
{
10241148
mprintf((" Could not initialize joystick: %s\n", SDL_GetError()));
10251149
return false;
10261150
}
10271151

1028-
// enable event processing of the joystick
1029-
SDL_SetJoystickEventsEnabled(true);
1030-
10311152
if ( !SDL_HasJoystick() )
10321153
{
10331154
mprintf((" No joysticks found\n"));
@@ -1053,6 +1174,12 @@ namespace joystick
10531174
addEventListener(SDL_EVENT_JOYSTICK_ADDED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
10541175
addEventListener(SDL_EVENT_JOYSTICK_REMOVED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
10551176

1177+
// Gamepad events. NOTE: This is on top of joystick events, so both will be fired for gamepads!
1178+
// (we can ignore add/remove events here since the normal joystick ones will do it)
1179+
addEventListener(SDL_EVENT_GAMEPAD_AXIS_MOTION, DEFAULT_LISTENER_WEIGHT, axis_event_handler);
1180+
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_DOWN, DEFAULT_LISTENER_WEIGHT, button_event_handler);
1181+
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_UP, DEFAULT_LISTENER_WEIGHT, button_event_handler);
1182+
10561183
// Search for the correct stick
10571184
if (Using_in_game_options)
10581185
{
@@ -1141,7 +1268,7 @@ namespace joystick
11411268
// Automatically frees joystick resources
11421269
joysticks.clear();
11431270

1144-
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
1271+
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
11451272
}
11461273

11471274
json_t* getJsonArray() {

0 commit comments

Comments
 (0)