Skip to content

Commit d7e3114

Browse files
add basic gamepad support
1 parent 5a5a760 commit d7e3114

File tree

3 files changed

+211
-64
lines changed

3 files changed

+211
-64
lines changed

code/io/joy-sdl.cpp

Lines changed: 199 additions & 62 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,41 @@ 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+
} else {
860+
_button[i].DownTimestamp = UI_TIMESTAMP::invalid();
861+
}
862+
}
863+
864+
} else {
865+
_button.resize(0);
866+
mprintf(("Failed to get number of buttons for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
867+
}
830868
}
831869

832-
// Initialize hats
833-
auto hatNum = SDL_GetNumJoystickHats(_joystick);
870+
// Initialize hats (consider gamepads to always have one hat)
871+
auto hatNum = _isGamepad ? 1 : SDL_GetNumJoystickHats(_joystick);
834872
if (hatNum >= 0) {
835873
_hat.resize(static_cast<size_t>(hatNum));
836874
for (auto i = 0; i < hatNum; ++i) {
837-
std::bitset<4> hatset = SDL_GetJoystickHat(_joystick, i);
838-
auto hatval = convertSDLHat(SDL_GetJoystickHat(_joystick, i));
875+
auto hatval = _isGamepad ? HAT_CENTERED : convertSDLHat(SDL_GetJoystickHat(_joystick, i));
839876
_hat[i].Value = hatval;
840877

841878
// Reset timestampts
@@ -847,21 +884,23 @@ namespace joystick
847884
}
848885

849886
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-
}
887+
std::bitset<4> hatset = SDL_GetJoystickHat(_joystick, i);
863888

864-
// Set the 8-pos timestamp
889+
// Set the 4-pos timestamp(s)
890+
if ((hatset[HAT_DOWN])) {
891+
_hat[i].DownTimestamp4[HAT_DOWN] = ui_timestamp();
892+
}
893+
if ((hatset[HAT_UP])) {
894+
_hat[i].DownTimestamp4[HAT_UP] = ui_timestamp();
895+
}
896+
if ((hatset[HAT_LEFT])) {
897+
_hat[i].DownTimestamp4[HAT_LEFT] = ui_timestamp();
898+
}
899+
if ((hatset[HAT_RIGHT])) {
900+
_hat[i].DownTimestamp4[HAT_RIGHT] = ui_timestamp();
901+
}
902+
903+
// Set the 8-pos timestamp
865904
_hat[i].DownTimestamp8[hatval] = ui_timestamp();
866905
}
867906
}
@@ -871,30 +910,50 @@ namespace joystick
871910
}
872911
}
873912

874-
SDL_Joystick *Joystick::getDevice()
913+
SDL_Joystick *Joystick::getJoystick()
875914
{
876915
return _joystick;
877916
}
878917

918+
SDL_Gamepad *Joystick::getGamepad()
919+
{
920+
return _gamepad;
921+
}
922+
879923
void Joystick::handleJoyEvent(const SDL_Event &evt)
880924
{
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;
925+
// gamepads also get joy events, so make sure we ignore those
926+
if (isGamepad()) {
927+
switch (evt.type) {
928+
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
929+
handleAxisEvent(evt.gaxis);
930+
break;
931+
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
932+
case SDL_EVENT_GAMEPAD_BUTTON_UP:
933+
handleButtonEvent(evt.gbutton);
934+
break;
935+
default:
936+
break;
937+
}
938+
} else {
939+
switch (evt.type)
940+
{
941+
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
942+
handleAxisEvent(evt.jaxis);
943+
break;
944+
case SDL_EVENT_JOYSTICK_BALL_MOTION:
945+
handleBallEvent(evt.jball);
946+
break;
947+
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
948+
case SDL_EVENT_JOYSTICK_BUTTON_UP:
949+
handleButtonEvent(evt.jbutton);
950+
break;
951+
case SDL_EVENT_JOYSTICK_HAT_MOTION:
952+
handleHatEvent(evt.jhat);
953+
break;
954+
default:
955+
break;
956+
}
898957
}
899958
}
900959

@@ -907,6 +966,28 @@ namespace joystick
907966
_axisValues[axis] = evt.value;
908967
}
909968

969+
// gamepad version of event
970+
void Joystick::handleAxisEvent(const SDL_GamepadAxisEvent &evt)
971+
{
972+
auto axis = evt.axis;
973+
auto value = evt.value;
974+
975+
Assertion(axis < numAxes(), "SDL event contained invalid axis index!");
976+
977+
// Triggers range from 0..32767 so we need to scale the value for FSO
978+
// since it expects a full -32768..32767 range. Note that precision is
979+
// lost in the scaling so only scale if it's not a min/max trigger value
980+
if ((axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {
981+
if (value == 0) {
982+
value = -32768;
983+
} else if (value < 32767) {
984+
value = static_cast<decltype(value)>((value * 2) - 32768);
985+
}
986+
}
987+
988+
_axisValues[axis] = value;
989+
}
990+
910991
void Joystick::handleButtonEvent(const SDL_JoyButtonEvent &evt)
911992
{
912993
auto button = evt.button;
@@ -922,6 +1003,58 @@ namespace joystick
9221003
}
9231004
}
9241005

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

10201153
mprintf(("Initializing Joystick...\n"));
10211154

1022-
if ( !SDL_InitSubSystem(SDL_INIT_JOYSTICK) )
1155+
// NOTE: gamepad depends on joystick, so this handles both
1156+
if ( !SDL_InitSubSystem(SDL_INIT_GAMEPAD) )
10231157
{
10241158
mprintf((" Could not initialize joystick: %s\n", SDL_GetError()));
10251159
return false;
10261160
}
10271161

1028-
// enable event processing of the joystick
1029-
SDL_SetJoystickEventsEnabled(true);
1030-
10311162
if ( !SDL_HasJoystick() )
10321163
{
10331164
mprintf((" No joysticks found\n"));
@@ -1053,6 +1184,12 @@ namespace joystick
10531184
addEventListener(SDL_EVENT_JOYSTICK_ADDED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
10541185
addEventListener(SDL_EVENT_JOYSTICK_REMOVED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
10551186

1187+
// Gamepad events. NOTE: This is on top of joystick events, so both will be fired for gamepads!
1188+
// (we can ignore add/remove events here since the normal joystick ones will do it)
1189+
addEventListener(SDL_EVENT_GAMEPAD_AXIS_MOTION, DEFAULT_LISTENER_WEIGHT, axis_event_handler);
1190+
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_DOWN, DEFAULT_LISTENER_WEIGHT, button_event_handler);
1191+
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_UP, DEFAULT_LISTENER_WEIGHT, button_event_handler);
1192+
10561193
// Search for the correct stick
10571194
if (Using_in_game_options)
10581195
{
@@ -1141,7 +1278,7 @@ namespace joystick
11411278
// Automatically frees joystick resources
11421279
joysticks.clear();
11431280

1144-
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
1281+
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
11451282
}
11461283

11471284
json_t* getJsonArray() {

0 commit comments

Comments
 (0)