mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 17:48:46 -10:00
Use the SDL gamepad API to add full gamepad support. Adds rumble for supported gamepads and changes the FF in-game options to take immediate effect instead of requiring a restart. Additionally this makes changes to allow gamepad triggers to be used as both an axis and a button. This support is specific to gamepads since we can explicitly identify the trigger with the API and act on it accordingly. FSO control config doesn't currently support such an action directly however so we have to hack it in just a bit. Due to this the control binding will show the trigger axis name bound to a button control only while a gamepad is connected, otherwise it shows a button name associated with the trigger.
1483 lines
37 KiB
C++
1483 lines
37 KiB
C++
#include "controlconfig/controlsconfig.h"
|
|
#include "io/joy.h"
|
|
#include "io/joy_ff.h"
|
|
#include "io/timer.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "options/Option.h"
|
|
#include "osapi/osapi.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bitset>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "libs/jansson.h"
|
|
|
|
using namespace io::joystick;
|
|
using namespace os::events;
|
|
|
|
namespace {
|
|
|
|
typedef std::unique_ptr<Joystick> JoystickPtr;
|
|
|
|
SCP_vector<JoystickPtr> joysticks; //!< All joysticks detected by SDL
|
|
std::array<Joystick*, CID_JOY_MAX> pJoystick = {nullptr}; //!< The 4 joysticks FSO are using. pJoystick[0] replaces CurrentJoystick
|
|
|
|
bool initialized = false;
|
|
|
|
/**
|
|
* @brief Compatibility conversion from Button index to HatPosition
|
|
*/
|
|
inline
|
|
HatPosition hatBtnToEnum(int in) {
|
|
Assertion(in >= JOY_NUM_BUTTONS, "Invalid button value passed to hatBtnToEnum: %i", in);
|
|
|
|
in -= JOY_NUM_BUTTONS;
|
|
|
|
switch (in) {
|
|
case 0:
|
|
return HAT_DOWN;
|
|
|
|
case 1:
|
|
return HAT_UP;
|
|
|
|
case 2:
|
|
return HAT_LEFT;
|
|
|
|
case 3:
|
|
return HAT_RIGHT;
|
|
|
|
case 4:
|
|
return HAT_LEFTDOWN;
|
|
|
|
case 5:
|
|
return HAT_LEFTUP;
|
|
|
|
case 6:
|
|
return HAT_RIGHTDOWN;
|
|
|
|
case 7:
|
|
return HAT_RIGHTUP;
|
|
|
|
default:
|
|
// Invalid index
|
|
return HAT_CENTERED;
|
|
}
|
|
};
|
|
|
|
SCP_string getJoystickGUID(SDL_Joystick* stick)
|
|
{
|
|
auto guid = SDL_GetJoystickGUID(stick);
|
|
|
|
const size_t GUID_STR_SIZE = 33;
|
|
SCP_string joystickGUID;
|
|
joystickGUID.resize(GUID_STR_SIZE);
|
|
|
|
SDL_GUIDToString(guid, joystickGUID.data(), static_cast<int>(joystickGUID.size()));
|
|
|
|
// Remove trailing \0
|
|
joystickGUID.resize(GUID_STR_SIZE - 1);
|
|
|
|
// Make sure the GUID is upper case
|
|
SCP_toupper(joystickGUID);
|
|
|
|
return joystickGUID;
|
|
}
|
|
|
|
bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid)
|
|
{
|
|
if (guid.empty()) {
|
|
return false;
|
|
}
|
|
|
|
SCP_string guidStr(guid);
|
|
SCP_toupper(guidStr);
|
|
|
|
if (testStick->getGUID() != guidStr) {
|
|
return false; // GUID doesn't match
|
|
}
|
|
|
|
// Build a list of all joysticks with the right guid
|
|
size_t num_sticks = 0;
|
|
for (auto& stick : joysticks) {
|
|
if (stick->getGUID() == guidStr) {
|
|
++num_sticks;
|
|
}
|
|
}
|
|
|
|
if (num_sticks == 0) {
|
|
// Not the right GUID
|
|
return false;
|
|
}
|
|
if (num_sticks == 1) {
|
|
// Only one option -> this is the right stick
|
|
return true;
|
|
}
|
|
|
|
// Multiple sticks -> check if the device id is the same
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns True if the given stick should be assigned the given cid
|
|
*
|
|
* @param[in] testStick The joystick ptr to test
|
|
* @param[in] cid The cid to test this against
|
|
*
|
|
* @details
|
|
* Should cid be CID_JOY0, it also checks for legacy .ini keys.
|
|
* Should Joy0ID key be null, legacy keys will be tried
|
|
*/
|
|
bool isPlayerJoystick(Joystick* testStick, short cid) {
|
|
SCP_string GUID;
|
|
|
|
SCP_string key_guid;
|
|
|
|
// Select key strings according to cid
|
|
switch (cid) {
|
|
case CID_JOY0:
|
|
key_guid = "Joy0GUID";
|
|
break;
|
|
case CID_JOY1:
|
|
key_guid = "Joy1GUID";
|
|
break;
|
|
case CID_JOY2:
|
|
key_guid = "Joy2GUID";
|
|
break;
|
|
case CID_JOY3:
|
|
key_guid = "Joy3GUID";
|
|
break;
|
|
case CID_NONE:
|
|
case CID_KEYBOARD:
|
|
case CID_MOUSE:
|
|
return false;
|
|
default:
|
|
mprintf(("Unknown CID %i\n", cid));
|
|
return false;
|
|
}
|
|
|
|
GUID = os_config_read_string(nullptr, key_guid.c_str(), "");
|
|
|
|
if ((cid == CID_JOY0) && GUID.empty()) {
|
|
// Check legacy keys
|
|
GUID = os_config_read_string(nullptr, "CurrentJoystickGUID", "");
|
|
}
|
|
|
|
return joystickMatchesGuid(testStick, GUID);
|
|
}
|
|
|
|
/**
|
|
* Reads/deserializes the given json line and retrieves the joystick that it references
|
|
*/
|
|
Joystick* joystick_deserialize(const json_t* value)
|
|
{
|
|
const char* guid;
|
|
int id;
|
|
|
|
json_error_t err;
|
|
if (json_unpack_ex((json_t*)value, &err, 0, "{s:s, s:i}", "guid", &guid, "id", &id) != 0) {
|
|
// throw json_exception(err);
|
|
// Changed by wookieejedi.
|
|
// If errors detected then return nullptr (ie, no joystick),
|
|
// because if we throw errors instead of returning nullptr
|
|
// then the listening functions short circuits and does not run.
|
|
return nullptr;
|
|
}
|
|
|
|
for (auto& test_stick : joysticks) {
|
|
if (joystickMatchesGuid(test_stick.get(), guid)) {
|
|
return test_stick.get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Converts/serializes joystick reference data into a json line
|
|
*/
|
|
json_t* joystick_serializer(Joystick* joystick)
|
|
{
|
|
if (joystick == nullptr) {
|
|
return json_null();
|
|
}
|
|
|
|
return json_pack("{sssi}", "guid", joystick->getGUID().c_str(), "id", -1);
|
|
}
|
|
|
|
/**
|
|
* Assigns the given cid to the given stick
|
|
*
|
|
* @param[in] stick The stick that is being assigned
|
|
* @param[in] cid The cid being assigned, must be a valid short between CID_JOY0 and CID_JOY_MAX
|
|
*/
|
|
void setPlayerJoystick(Joystick* stick, short cid)
|
|
{
|
|
Assert((cid >= CID_JOY0) && (cid < CID_JOY_MAX));
|
|
pJoystick[cid] = stick;
|
|
|
|
if (cid == CID_JOY0) {
|
|
joy_ff_shutdown();
|
|
}
|
|
|
|
if (pJoystick[cid] != nullptr) {
|
|
mprintf((" Using '%s' as Joy-%i\n", pJoystick[cid]->getName().c_str(), cid));
|
|
mprintf(("\n"));
|
|
mprintf((" Is gamepad: %s\n", pJoystick[cid]->isGamepad() ? "YES" : "NO"));
|
|
if (pJoystick[cid]->isGamepad()) {
|
|
auto type_str = SDL_GetGamepadStringForType(SDL_GetGamepadType(pJoystick[cid]->getGamepad()));
|
|
mprintf((" Gamepad type: %s\n", type_str ? type_str : "<unkown>"));
|
|
}
|
|
mprintf((" Number of axes: %d\n", pJoystick[cid]->numAxes()));
|
|
mprintf((" Number of buttons: %d\n", pJoystick[cid]->numButtons()));
|
|
mprintf((" Number of hats: %d\n", pJoystick[cid]->numHats()));
|
|
mprintf((" Number of trackballs: %d\n", pJoystick[cid]->numBalls()));
|
|
mprintf(("\n"));
|
|
|
|
if (cid == CID_JOY0) {
|
|
joy_ff_init();
|
|
}
|
|
} else {
|
|
mprintf((" Joystick %i removed\n", cid));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scripting callback for displaying the given joystick's name
|
|
*/
|
|
SCP_string joystick_display(Joystick* stick)
|
|
{
|
|
if (stick == nullptr) {
|
|
return XSTR("None", 1673);
|
|
}
|
|
return stick->getName();
|
|
}
|
|
|
|
/**
|
|
* Scripting callback for enumerating a SCP_vector with all available joysticks
|
|
*/
|
|
SCP_vector<Joystick*> joystick_enumerator()
|
|
{
|
|
SCP_vector<Joystick*> out;
|
|
out.push_back(nullptr);
|
|
for (auto& stick : joysticks) {
|
|
out.push_back(stick.get());
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Joystick options for the new menu system
|
|
* These should be displayed as a dropdown box type widget
|
|
*/
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
|
auto JoystickOption = options::OptionBuilder<Joystick*>("Input.Joystick",
|
|
std::pair<const char*, int>{"Joystick 0", 1705},
|
|
std::pair<const char*, int>{"The current joystick 0", 1706})
|
|
.category(std::make_pair("Input", 1827)) // Category this option shows up in the scripting heirachy
|
|
.deserializer(joystick_deserialize) // callback for json to C++
|
|
.serializer(joystick_serializer) // callback for C++ to json
|
|
.display(joystick_display) // callback for constructing the display label
|
|
.enumerator(joystick_enumerator) // callback for enumerating/constructing the values this option may take
|
|
.level(options::ExpertLevel::Beginner)
|
|
.default_val(nullptr) // initial/default value for this option
|
|
.flags({options::OptionFlags::ForceMultiValueSelection})
|
|
.importance(100)
|
|
.change_listener([](Joystick* joy, bool) {
|
|
setPlayerJoystick(joy, CID_JOY0);
|
|
return true;
|
|
})
|
|
.finish();
|
|
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
|
auto JoystickOption1 = options::OptionBuilder<Joystick*>("Input.Joystick1",
|
|
std::pair<const char*, int>{"Joystick 1", 1707},
|
|
std::pair<const char*, int>{"The current joystick 1", 1708})
|
|
.category(std::make_pair("Input", 1827))
|
|
.deserializer(joystick_deserialize)
|
|
.serializer(joystick_serializer)
|
|
.display(joystick_display)
|
|
.enumerator(joystick_enumerator)
|
|
.level(options::ExpertLevel::Beginner)
|
|
.default_val(nullptr)
|
|
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
|
.importance(90)
|
|
.change_listener([](Joystick* joy, bool) {
|
|
setPlayerJoystick(joy, CID_JOY1);
|
|
return true;
|
|
})
|
|
.finish();
|
|
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
|
auto JoystickOption2 = options::OptionBuilder<Joystick*>("Input.Joystick2",
|
|
std::pair<const char*, int>{"Joystick 2", 1709},
|
|
std::pair<const char*, int>{"The current joystick 2", 1710})
|
|
.category(std::make_pair("Input", 1827))
|
|
.deserializer(joystick_deserialize)
|
|
.serializer(joystick_serializer)
|
|
.display(joystick_display)
|
|
.enumerator(joystick_enumerator)
|
|
.level(options::ExpertLevel::Beginner)
|
|
.default_val(nullptr)
|
|
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
|
.importance(80)
|
|
.change_listener([](Joystick* joy, bool) {
|
|
setPlayerJoystick(joy, CID_JOY2);
|
|
return true;
|
|
})
|
|
.finish();
|
|
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
|
auto JoystickOption3 = options::OptionBuilder<Joystick*>("Input.Joystick3",
|
|
std::pair<const char*, int>{"Joystick 3", 1711},
|
|
std::pair<const char*, int>{"The current joystick 3", 1712})
|
|
.category(std::make_pair("Input", 1827))
|
|
.deserializer(joystick_deserialize)
|
|
.serializer(joystick_serializer)
|
|
.display(joystick_display)
|
|
.enumerator(joystick_enumerator)
|
|
.level(options::ExpertLevel::Beginner)
|
|
.default_val(nullptr)
|
|
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
|
.importance(70)
|
|
.change_listener([](Joystick* joy, bool) {
|
|
setPlayerJoystick(joy, CID_JOY3);
|
|
return true;
|
|
})
|
|
.finish();
|
|
|
|
HatPosition convertSDLHat(int val)
|
|
{
|
|
switch (val)
|
|
{
|
|
case SDL_HAT_CENTERED:
|
|
return HAT_CENTERED;
|
|
case SDL_HAT_UP:
|
|
return HAT_UP;
|
|
case SDL_HAT_RIGHT:
|
|
return HAT_RIGHT;
|
|
case SDL_HAT_DOWN:
|
|
return HAT_DOWN;
|
|
case SDL_HAT_LEFT:
|
|
return HAT_LEFT;
|
|
case SDL_HAT_RIGHTUP:
|
|
return HAT_RIGHTUP;
|
|
case SDL_HAT_RIGHTDOWN:
|
|
return HAT_RIGHTDOWN;
|
|
case SDL_HAT_LEFTUP:
|
|
return HAT_LEFTUP;
|
|
case SDL_HAT_LEFTDOWN:
|
|
return HAT_LEFTDOWN;
|
|
default:
|
|
return HAT_CENTERED;
|
|
}
|
|
}
|
|
|
|
void enumerateJoysticks(SCP_vector<JoystickPtr>& outVec)
|
|
{
|
|
int num;
|
|
|
|
auto sticks = SDL_GetJoysticks(&num);
|
|
|
|
outVec.clear();
|
|
outVec.reserve(static_cast<size_t>(num));
|
|
|
|
mprintf(("Printing joystick info:\n"));
|
|
|
|
for (auto i = 0; i < num; ++i) {
|
|
try
|
|
{
|
|
auto ptr = JoystickPtr(new Joystick(sticks[i]));
|
|
ptr->printInfo();
|
|
|
|
outVec.push_back(std::move(ptr));
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
mprintf((" An error occured while attempting to enumerate joystick %i.\n", i));
|
|
mprintf((" %s\n", e.what()));
|
|
mprintf((" %s\n", SDL_GetError()));
|
|
SDL_ClearError();
|
|
}
|
|
}
|
|
}
|
|
|
|
Joystick *getJoystickForID(SDL_JoystickID id)
|
|
{
|
|
for (auto iter = joysticks.begin(); iter != joysticks.end(); ++iter)
|
|
{
|
|
if ((*iter)->getID() == id)
|
|
{
|
|
return (*iter).get();
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool axis_event_handler(const SDL_Event &evt)
|
|
{
|
|
auto stick = getJoystickForID(evt.jaxis.which);
|
|
|
|
if (stick != nullptr)
|
|
{
|
|
stick->handleJoyEvent(evt);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ball_event_handler(const SDL_Event &evt)
|
|
{
|
|
auto stick = getJoystickForID(evt.jball.which);
|
|
|
|
if (stick != nullptr)
|
|
{
|
|
stick->handleJoyEvent(evt);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool button_event_handler(const SDL_Event &evt)
|
|
{
|
|
auto stick = getJoystickForID(evt.jbutton.which);
|
|
|
|
if (stick != nullptr)
|
|
{
|
|
stick->handleJoyEvent(evt);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool hat_event_handler(const SDL_Event &evt)
|
|
{
|
|
auto stick = getJoystickForID(evt.jhat.which);
|
|
|
|
if (stick != nullptr)
|
|
{
|
|
stick->handleJoyEvent(evt);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool device_event_handler(const SDL_Event &evt)
|
|
{
|
|
auto &joyDeviceEvent = evt.jdevice;
|
|
|
|
auto evtType = evt.type;
|
|
|
|
if (evtType == SDL_EVENT_JOYSTICK_ADDED)
|
|
{
|
|
// A new joystick has been added, add it to our list and check if it's the configured joystick
|
|
Joystick *addedStick;
|
|
{
|
|
auto device = SDL_OpenJoystick(joyDeviceEvent.which);
|
|
|
|
if (device == nullptr)
|
|
{
|
|
mprintf(("Failed to open connecting joystick: %s\n", SDL_GetError()));
|
|
return true;
|
|
}
|
|
|
|
// Add the new device to our list
|
|
auto added = JoystickPtr(new Joystick(joyDeviceEvent.which));
|
|
|
|
for (auto iter = joysticks.begin(); iter != joysticks.end(); ++iter)
|
|
{
|
|
// must use GUID here as joystick IDs are never reused
|
|
if ((*iter)->getGUID() == added->getGUID())
|
|
{
|
|
// Already have this stick
|
|
*(*iter) = std::move(*added);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
addedStick = added.get();
|
|
// This is a new stick so we can output it's information
|
|
mprintf(("A new joystick has been connected:\n"));
|
|
addedStick->printInfo();
|
|
|
|
joysticks.push_back(std::move(added));
|
|
}
|
|
|
|
// Is true if all of the pJoysticks are bound
|
|
bool all_bound = std::all_of(pJoystick.begin(), pJoystick.end(), [](Joystick* pJoy){ return pJoy != nullptr; });
|
|
|
|
if (all_bound)
|
|
{
|
|
// We already have all valid joystick devices, bail early
|
|
return true;
|
|
} // Else, at least one player stick is unbound
|
|
|
|
// Bind the added stick, if we want it
|
|
if (Using_in_game_options)
|
|
{
|
|
// New menu system lets players select joysticks in-game
|
|
Joystick* value[CID_JOY_MAX];
|
|
value[0] = JoystickOption->getValue();
|
|
value[1] = JoystickOption1->getValue();
|
|
value[2] = JoystickOption2->getValue();
|
|
value[3] = JoystickOption3->getValue();
|
|
|
|
|
|
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
|
{
|
|
if (value[i] == addedStick)
|
|
{
|
|
mprintf(("Joystick %i connected\n", i));
|
|
setPlayerJoystick(addedStick, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Legacy system requires setting joysticks in the launcher
|
|
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
|
{
|
|
if (isPlayerJoystick(addedStick, i))
|
|
{
|
|
mprintf(("Joystick %i connected\n", i));
|
|
setPlayerJoystick(addedStick, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (evtType == SDL_EVENT_JOYSTICK_REMOVED)
|
|
{
|
|
bool any_bound = std::any_of(pJoystick.begin(), pJoystick.end(), [](Joystick* pJoy){ return pJoy != nullptr; });
|
|
|
|
if (!any_bound)
|
|
{
|
|
// Nothing bound, ignore
|
|
return true;
|
|
}
|
|
|
|
// Else, Find if any of our joysticks were lost
|
|
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
|
{
|
|
if (pJoystick[i] != nullptr && joyDeviceEvent.which == pJoystick[i]->getID())
|
|
{
|
|
// We just lost our joystick, reset the data
|
|
mprintf(("Joystick %i disconnected\n", i));
|
|
setPlayerJoystick(nullptr, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
namespace io
|
|
{
|
|
namespace joystick
|
|
{
|
|
Joystick::Joystick(int id) :
|
|
_id(id)
|
|
{
|
|
if (SDL_IsGamepad(id)) {
|
|
_gamepad = SDL_OpenGamepad(id);
|
|
_joystick = SDL_GetGamepadJoystick(_gamepad);
|
|
} else {
|
|
_gamepad = nullptr;
|
|
_joystick = SDL_OpenJoystick(id);
|
|
}
|
|
|
|
if (_joystick == nullptr) {
|
|
SCP_stringstream msg;
|
|
msg << "Failed to open joystick with id " << id << ", get a coder!";
|
|
throw std::runtime_error(msg.str());
|
|
}
|
|
|
|
fillValues();
|
|
}
|
|
|
|
Joystick::Joystick(Joystick &&other) noexcept :
|
|
_joystick(nullptr), _gamepad(nullptr)
|
|
{
|
|
*this = std::move(other);
|
|
}
|
|
|
|
Joystick::~Joystick()
|
|
{
|
|
if (_gamepad != nullptr)
|
|
{
|
|
SDL_CloseGamepad(_gamepad); // also closes joystick
|
|
_gamepad = nullptr;
|
|
_joystick = nullptr;
|
|
}
|
|
else if (_joystick != nullptr)
|
|
{
|
|
SDL_CloseJoystick(_joystick);
|
|
_joystick = nullptr;
|
|
}
|
|
}
|
|
|
|
Joystick &Joystick::operator=(Joystick &&other) noexcept
|
|
{
|
|
if (this == &other)
|
|
return *this;
|
|
|
|
std::swap(_id, other._id);
|
|
std::swap(_joystick, other._joystick);
|
|
std::swap(_gamepad, other._gamepad);
|
|
|
|
fillValues();
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool Joystick::isAttached() const
|
|
{
|
|
return isGamepad() ? SDL_GamepadConnected(_gamepad) : SDL_JoystickConnected(_joystick);
|
|
}
|
|
|
|
bool Joystick::isHaptic() const
|
|
{
|
|
return _isHaptic;
|
|
}
|
|
|
|
bool Joystick::isGamepad() const
|
|
{
|
|
return _isGamepad;
|
|
}
|
|
|
|
Sint16 Joystick::getAxis(int index) const
|
|
{
|
|
Assertion(index >= 0 && index < numAxes(), "Invalid index %d!", index);
|
|
|
|
return _axisValues[index];
|
|
}
|
|
|
|
bool Joystick::isButtonDown(int index) const
|
|
{
|
|
Assertion(index >= 0 && index < numButtons(), "Invalid index %d!", index);
|
|
|
|
return _button[index].DownTimestamp.isValid();
|
|
}
|
|
|
|
float Joystick::getButtonDownTime(int index) const
|
|
{
|
|
Assertion(index >= 0 && index < numButtons(), "Invalid index %d!", index);
|
|
|
|
if (_button[index].DownTimestamp.isValid())
|
|
{
|
|
auto diff = ui_timestamp_since(_button[index].DownTimestamp);
|
|
|
|
return static_cast<float>(diff) / MILLISECONDS_PER_SECOND;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
int Joystick::getButtonDownCount(int index, bool reset)
|
|
{
|
|
Assertion(index >= 0 && index < numButtons(), "Invalid index %d!", index);
|
|
|
|
auto val = _button[index].DownCount;
|
|
|
|
if (reset)
|
|
{
|
|
_button[index].DownCount = 0;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
coord2d Joystick::getBall(int index) const
|
|
{
|
|
Assertion(index >= 0 && index < numBalls(), "Invalid index %d!", index);
|
|
|
|
return _ballValues[index];
|
|
}
|
|
|
|
HatPosition Joystick::getHatPosition(int index) const
|
|
{
|
|
Assertion(index >= 0 && index < numHats(), "Invalid index %d!", index);
|
|
|
|
return _hat[index].Value;
|
|
}
|
|
|
|
int Joystick::getHatDownCount(int index, HatPosition pos, bool ext, bool reset) {
|
|
Assertion(index >= 0 && index < numHats(), "Invalid index %d!", index);
|
|
|
|
int val;
|
|
|
|
if (pos == HAT_CENTERED) {
|
|
return 0;
|
|
}
|
|
|
|
if (ext) {
|
|
// Use 8-position
|
|
val = _hat[index].DownCount8[pos];
|
|
|
|
if (reset) {
|
|
_hat[index].DownCount8[pos] = 0;
|
|
}
|
|
} else {
|
|
// Use 4-position
|
|
val = _hat[index].DownCount4[pos];
|
|
|
|
if (reset) {
|
|
_hat[index].DownCount4[pos] = 0;
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
float Joystick::getHatDownTime(int index, HatPosition pos, bool ext) const
|
|
{
|
|
Assertion(index >= 0 && index < numHats(), "Invalid index %d!", index);
|
|
|
|
if (pos == HAT_CENTERED) {
|
|
return 0.0f;
|
|
}
|
|
|
|
UI_TIMESTAMP val;
|
|
|
|
if (ext) {
|
|
// Use 8-position
|
|
val = _hat[index].DownTimestamp8[pos];
|
|
|
|
} else {
|
|
// Use 4-position
|
|
val = _hat[index].DownTimestamp4[pos];
|
|
}
|
|
|
|
if (!val.isValid()) {
|
|
return 0.0f;
|
|
}
|
|
|
|
auto diff = ui_timestamp_since(val);
|
|
|
|
return static_cast<float>(diff) / MILLISECONDS_PER_SECOND;
|
|
}
|
|
|
|
int Joystick::numAxes() const
|
|
{
|
|
return static_cast<int>(_axisValues.size());
|
|
}
|
|
|
|
int Joystick::numBalls() const
|
|
{
|
|
return static_cast<int>(_ballValues.size());
|
|
}
|
|
|
|
int Joystick::numButtons() const
|
|
{
|
|
return static_cast<int>(_button.size());
|
|
}
|
|
|
|
int Joystick::numHats() const
|
|
{
|
|
return static_cast<int>(_hat.size());
|
|
}
|
|
|
|
SCP_string Joystick::getGUID() const
|
|
{
|
|
return _guidStr;
|
|
}
|
|
|
|
SCP_string Joystick::getName() const
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
SDL_JoystickID Joystick::getID() const
|
|
{
|
|
return _id;
|
|
}
|
|
|
|
void Joystick::fillValues()
|
|
{
|
|
// To avoid some weirdness and build compatibility issues we always use
|
|
// _joystick here rather than comparable _gamepad functions
|
|
|
|
_name.assign(SDL_GetJoystickName(_joystick));
|
|
_guidStr = getJoystickGUID(_joystick);
|
|
_isHaptic = SDL_IsJoystickHaptic(_joystick);
|
|
_isGamepad = SDL_IsGamepad(_id);
|
|
|
|
// Initialize values of the axes
|
|
if (_isGamepad) {
|
|
// gamepads may not have all axes, but they don't necessarily match
|
|
// the number or index of what's reported by the joystick api either
|
|
_axisValues.resize(static_cast<size_t>(SDL_GAMEPAD_AXIS_COUNT));
|
|
for (size_t i = 0; i < _axisValues.size(); ++i) {
|
|
// will return 0 (center) if axis not supported
|
|
_axisValues[i] = SDL_GetGamepadAxis(_gamepad, static_cast<SDL_GamepadAxis>(i));
|
|
}
|
|
} else {
|
|
auto numSticks = SDL_GetNumJoystickAxes(_joystick);
|
|
if (numSticks >= 0) {
|
|
_axisValues.resize(static_cast<size_t>(numSticks));
|
|
for (auto i = 0; i < numSticks; ++i) {
|
|
_axisValues[i] = SDL_GetJoystickAxis(_joystick, i);
|
|
}
|
|
|
|
} else {
|
|
_axisValues.resize(0);
|
|
mprintf(("Failed to get number of axes for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
// Initialize ball values
|
|
auto ballNum = SDL_GetNumJoystickBalls(_joystick);
|
|
if (ballNum >= 0) {
|
|
_ballValues.resize(static_cast<size_t>(ballNum));
|
|
|
|
for (auto i = 0; i < ballNum; ++i) {
|
|
coord2d coord;
|
|
if ( !SDL_GetJoystickBall(_joystick, i, &coord.x, &coord.y) ) {
|
|
mprintf(("Failed to get ball %d value for joystick %s: %s\n", i, _name.c_str(), SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
} else {
|
|
mprintf(("Failed to get number of ball axes for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
|
|
_ballValues.resize(0);
|
|
}
|
|
|
|
// Initialize buttons
|
|
if (_isGamepad) {
|
|
// gamepads may not support all buttons, but they don't necessarily match
|
|
// the number or index of what's reported by the joystick api either
|
|
// NOTE: +2 added so we can map left & right triggers to a button
|
|
_button.resize(static_cast<size_t>(SDL_GAMEPAD_BUTTON_COUNT + 2));
|
|
for (size_t i = 0; i < _button.size(); ++i) {
|
|
if (SDL_GetGamepadButton(_gamepad, static_cast<SDL_GamepadButton>(i))) {
|
|
_button[i].DownTimestamp = ui_timestamp();
|
|
} else {
|
|
_button[i].DownTimestamp = UI_TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
} else {
|
|
auto buttonNum = SDL_GetNumJoystickButtons(_joystick);
|
|
if (buttonNum >= 0) {
|
|
_button.resize(static_cast<size_t>(buttonNum));
|
|
for (auto i = 0; i < buttonNum; ++i) {
|
|
if (SDL_GetJoystickButton(_joystick, i)) {
|
|
_button[i].DownTimestamp = ui_timestamp();
|
|
} else {
|
|
_button[i].DownTimestamp = UI_TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
|
|
} else {
|
|
_button.resize(0);
|
|
mprintf(("Failed to get number of buttons for joystick %s: %s\n", _name.c_str(), SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
// Initialize hats (consider gamepads to always have one hat)
|
|
auto hatNum = _isGamepad ? 1 : SDL_GetNumJoystickHats(_joystick);
|
|
if (hatNum >= 0) {
|
|
_hat.resize(static_cast<size_t>(hatNum));
|
|
for (auto i = 0; i < hatNum; ++i) {
|
|
auto hatval = _isGamepad ? HAT_CENTERED : convertSDLHat(SDL_GetJoystickHat(_joystick, i));
|
|
_hat[i].Value = hatval;
|
|
|
|
// Reset timestampts
|
|
for (auto j = 0; j < 4; ++j) {
|
|
_hat[i].DownTimestamp4[j] = UI_TIMESTAMP::invalid();
|
|
}
|
|
for (auto j = 0; j < 8; ++j) {
|
|
_hat[i].DownTimestamp8[j] = UI_TIMESTAMP::invalid();
|
|
}
|
|
|
|
if (_hat[i].Value != HAT_CENTERED) {
|
|
std::bitset<4> hatset = SDL_GetJoystickHat(_joystick, i);
|
|
|
|
// Set the 4-pos timestamp(s)
|
|
if ((hatset[HAT_DOWN])) {
|
|
_hat[i].DownTimestamp4[HAT_DOWN] = ui_timestamp();
|
|
}
|
|
if ((hatset[HAT_UP])) {
|
|
_hat[i].DownTimestamp4[HAT_UP] = ui_timestamp();
|
|
}
|
|
if ((hatset[HAT_LEFT])) {
|
|
_hat[i].DownTimestamp4[HAT_LEFT] = ui_timestamp();
|
|
}
|
|
if ((hatset[HAT_RIGHT])) {
|
|
_hat[i].DownTimestamp4[HAT_RIGHT] = ui_timestamp();
|
|
}
|
|
|
|
// Set the 8-pos timestamp
|
|
_hat[i].DownTimestamp8[hatval] = ui_timestamp();
|
|
}
|
|
}
|
|
} else {
|
|
_hat.resize(0);
|
|
mprintf(("Failed to get number of hats for joystick %s: %s", _name.c_str(), SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
SDL_Joystick *Joystick::getJoystick()
|
|
{
|
|
return _joystick;
|
|
}
|
|
|
|
SDL_Gamepad *Joystick::getGamepad()
|
|
{
|
|
return _gamepad;
|
|
}
|
|
|
|
void Joystick::handleJoyEvent(const SDL_Event &evt)
|
|
{
|
|
// gamepads also get joy events, so make sure we ignore those
|
|
if (isGamepad()) {
|
|
switch (evt.type) {
|
|
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
|
handleAxisEvent(evt.gaxis);
|
|
break;
|
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
|
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
|
handleButtonEvent(evt.gbutton);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
switch (evt.type)
|
|
{
|
|
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
|
|
handleAxisEvent(evt.jaxis);
|
|
break;
|
|
case SDL_EVENT_JOYSTICK_BALL_MOTION:
|
|
handleBallEvent(evt.jball);
|
|
break;
|
|
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
|
|
case SDL_EVENT_JOYSTICK_BUTTON_UP:
|
|
handleButtonEvent(evt.jbutton);
|
|
break;
|
|
case SDL_EVENT_JOYSTICK_HAT_MOTION:
|
|
handleHatEvent(evt.jhat);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Joystick::handleAxisEvent(const SDL_JoyAxisEvent &evt)
|
|
{
|
|
auto axis = evt.axis;
|
|
|
|
Assertion(axis < numAxes(), "SDL event contained invalid axis index!");
|
|
|
|
_axisValues[axis] = evt.value;
|
|
}
|
|
|
|
// gamepad version of event
|
|
void Joystick::handleAxisEvent(const SDL_GamepadAxisEvent &evt)
|
|
{
|
|
auto axis = evt.axis;
|
|
auto value = evt.value;
|
|
|
|
Assertion(axis < numAxes(), "SDL event contained invalid axis index!");
|
|
|
|
if ((axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) {
|
|
// Triggers range from 0..32767 so we need to scale the value for FSO
|
|
// since it expects a full -32768..32767 range. Note that precision is
|
|
// lost in the scaling so only scale if it's not a min/max trigger value
|
|
if (value == 0) {
|
|
value = -32768;
|
|
} else if (value < 32767) {
|
|
value = static_cast<decltype(value)>((value * 2) - 32768);
|
|
}
|
|
|
|
// We can also take this opportunity to map triggers to a button
|
|
int button = SDL_GAMEPAD_BUTTON_COUNT + (axis - SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
|
|
bool down = (value >= 0);
|
|
|
|
Assertion(button < numButtons(), "Gamepad trigger button is out of bounds!");
|
|
|
|
// We also need to avoid bumping DownCount for every tiny axis movement.
|
|
if ( !down || (_button[button].DownCount == 0) ) {
|
|
_button[button].DownTimestamp = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
|
|
|
|
if (down) {
|
|
++_button[button].DownCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
_axisValues[axis] = value;
|
|
}
|
|
|
|
void Joystick::handleButtonEvent(const SDL_JoyButtonEvent &evt)
|
|
{
|
|
auto button = evt.button;
|
|
auto down = evt.down;
|
|
|
|
Assertion(button < numButtons(), "SDL event contained invalid button index!");
|
|
|
|
_button[button].DownTimestamp = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
|
|
|
|
if (down)
|
|
{
|
|
++_button[button].DownCount;
|
|
}
|
|
}
|
|
|
|
// gamepad version of event (we deal with dpad->hat translation here too)
|
|
void Joystick::handleButtonEvent(const SDL_GamepadButtonEvent &evt)
|
|
{
|
|
auto button = evt.button;
|
|
auto down = evt.down;
|
|
|
|
Assertion(button < numButtons(), "SDL event contained invalid button index!");
|
|
|
|
// treat dpad as hat
|
|
if (button >= SDL_GAMEPAD_BUTTON_DPAD_UP && button <= SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
|
|
HatPosition hatpos;
|
|
|
|
if (numHats() != 1) {
|
|
return;
|
|
}
|
|
|
|
switch (button) {
|
|
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
|
hatpos = HAT_UP;
|
|
break;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
|
hatpos = HAT_DOWN;
|
|
break;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
|
hatpos = HAT_LEFT;
|
|
break;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
|
hatpos = HAT_RIGHT;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Set current values
|
|
_hat[0].Value = hatpos;
|
|
|
|
_hat[0].DownTimestamp4[hatpos] = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
|
|
_hat[0].DownTimestamp8[hatpos] = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
|
|
|
|
if (down) {
|
|
++_hat[0].DownCount4[hatpos];
|
|
++_hat[0].DownCount8[hatpos];
|
|
}
|
|
} else {
|
|
_button[button].DownTimestamp = down ? ui_timestamp() : UI_TIMESTAMP::invalid();
|
|
|
|
if (down) {
|
|
++_button[button].DownCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Joystick::handleHatEvent(const SDL_JoyHatEvent &evt)
|
|
{
|
|
auto hat = evt.hat;
|
|
|
|
Assertion(hat < numHats(), "SDL event contained invalid hat index!");
|
|
|
|
std::bitset<4> hatset = evt.value;
|
|
auto hatpos = convertSDLHat(evt.value);
|
|
|
|
// Set current values
|
|
_hat[hat].Value = hatpos;
|
|
|
|
// Reset inactive hat positions
|
|
for (auto i = 0; i < 4; ++i) {
|
|
if (!hatset[i]) {
|
|
_hat[hat].DownTimestamp4[i] = UI_TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
for (auto i = 0; i < 8; ++i) {
|
|
if (i != hatpos) {
|
|
_hat[hat].DownTimestamp8[i] = UI_TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
|
|
if (hatpos != HAT_CENTERED) {
|
|
// Set the 4-pos timestamps and down counts if the timestamp is not set
|
|
if ((hatset[HAT_DOWN]) && (!_hat[hat].DownTimestamp4[HAT_DOWN].isValid())) {
|
|
_hat[hat].DownTimestamp4[HAT_DOWN] = ui_timestamp();
|
|
++_hat[hat].DownCount4[HAT_DOWN];
|
|
}
|
|
if ((hatset[HAT_UP]) && (!_hat[hat].DownTimestamp4[HAT_UP].isValid())) {
|
|
_hat[hat].DownTimestamp4[HAT_UP] = ui_timestamp();
|
|
++_hat[hat].DownCount4[HAT_UP];
|
|
}
|
|
if ((hatset[HAT_LEFT]) && (!_hat[hat].DownTimestamp4[HAT_LEFT].isValid())) {
|
|
_hat[hat].DownTimestamp4[HAT_LEFT] = ui_timestamp();
|
|
++_hat[hat].DownCount4[HAT_LEFT];
|
|
}
|
|
if ((hatset[HAT_RIGHT]) && (!_hat[hat].DownTimestamp4[HAT_RIGHT].isValid())) {
|
|
_hat[hat].DownTimestamp4[HAT_RIGHT] = ui_timestamp();
|
|
++_hat[hat].DownCount4[HAT_RIGHT];
|
|
}
|
|
|
|
// Set the 8-pos timestamp and down count
|
|
_hat[hat].DownTimestamp8[hatpos] = ui_timestamp();
|
|
++_hat[hat].DownCount8[hatpos];
|
|
}
|
|
}
|
|
|
|
void Joystick::handleBallEvent(const SDL_JoyBallEvent &evt)
|
|
{
|
|
auto ball = evt.ball;
|
|
|
|
Assertion(ball < numBalls(), "SDL event contained invalid ball index!");
|
|
|
|
coord2d newVal;
|
|
newVal.x = evt.xrel;
|
|
newVal.y = evt.yrel;
|
|
|
|
_ballValues[ball] = newVal;
|
|
}
|
|
|
|
void Joystick::printInfo() {
|
|
mprintf((" Joystick name: %s\n", getName().c_str()));
|
|
mprintf((" Joystick GUID: %s\n", getGUID().c_str()));
|
|
mprintf((" Joystick ID: %d\n", getID()));
|
|
}
|
|
|
|
json_t* Joystick::getJSON() {
|
|
json_t* object;
|
|
|
|
object = json_pack("{s:s, s:s, s:i, s:i, s:i, s:i, s:i, s:i, s:b}",
|
|
"name", getName().c_str(), // s:s
|
|
"guid", getGUID().c_str(), // s:s
|
|
"id", static_cast<int>( getID() ), // s:i
|
|
"device", -1, // s:i
|
|
"num_axes", numAxes(), // s:i
|
|
"num_balls", numBalls(), // s:i
|
|
"num_buttons", numButtons(), // s:i
|
|
"num_hats", numHats(), // s:i
|
|
"is_haptic", _isHaptic // s:b
|
|
);
|
|
|
|
return object;
|
|
}
|
|
|
|
bool init(bool no_register)
|
|
{
|
|
using namespace os::events;
|
|
|
|
if (initialized)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
mprintf(("Initializing Joystick...\n"));
|
|
|
|
// NOTE: gamepad depends on joystick, so this handles both
|
|
if ( !SDL_InitSubSystem(SDL_INIT_GAMEPAD) )
|
|
{
|
|
mprintf((" Could not initialize joystick: %s\n", SDL_GetError()));
|
|
return false;
|
|
}
|
|
|
|
if ( !SDL_HasJoystick() )
|
|
{
|
|
mprintf((" No joysticks found\n"));
|
|
}
|
|
|
|
// Get the initial list of connected joysticks
|
|
enumerateJoysticks(joysticks);
|
|
|
|
if (no_register) {
|
|
// bail before registering anything
|
|
return false;
|
|
}
|
|
|
|
// Register all the event handlers
|
|
addEventListener(SDL_EVENT_JOYSTICK_AXIS_MOTION, DEFAULT_LISTENER_WEIGHT, axis_event_handler);
|
|
addEventListener(SDL_EVENT_JOYSTICK_BALL_MOTION, DEFAULT_LISTENER_WEIGHT, ball_event_handler);
|
|
|
|
addEventListener(SDL_EVENT_JOYSTICK_BUTTON_DOWN, DEFAULT_LISTENER_WEIGHT, button_event_handler);
|
|
addEventListener(SDL_EVENT_JOYSTICK_BUTTON_UP, DEFAULT_LISTENER_WEIGHT, button_event_handler);
|
|
|
|
addEventListener(SDL_EVENT_JOYSTICK_HAT_MOTION, DEFAULT_LISTENER_WEIGHT, hat_event_handler);
|
|
|
|
addEventListener(SDL_EVENT_JOYSTICK_ADDED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
|
|
addEventListener(SDL_EVENT_JOYSTICK_REMOVED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
|
|
|
|
// Gamepad events. NOTE: This is on top of joystick events, so both will be fired for gamepads!
|
|
// (we can ignore add/remove events here since the normal joystick ones will do it)
|
|
addEventListener(SDL_EVENT_GAMEPAD_AXIS_MOTION, DEFAULT_LISTENER_WEIGHT, axis_event_handler);
|
|
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_DOWN, DEFAULT_LISTENER_WEIGHT, button_event_handler);
|
|
addEventListener(SDL_EVENT_GAMEPAD_BUTTON_UP, DEFAULT_LISTENER_WEIGHT, button_event_handler);
|
|
|
|
// Search for the correct stick
|
|
if (Using_in_game_options)
|
|
{
|
|
// The new options system is in use
|
|
setPlayerJoystick(JoystickOption->getValue(), CID_JOY0);
|
|
setPlayerJoystick(JoystickOption1->getValue(), CID_JOY1);
|
|
setPlayerJoystick(JoystickOption2->getValue(), CID_JOY2);
|
|
setPlayerJoystick(JoystickOption3->getValue(), CID_JOY3);
|
|
}
|
|
else
|
|
{
|
|
// Fall back to the legacy config values
|
|
for (auto& stick : joysticks)
|
|
{
|
|
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
|
{
|
|
if (isPlayerJoystick(stick.get(), i))
|
|
{
|
|
// Joystick found
|
|
setPlayerJoystick(stick.get(), i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool used = false;
|
|
for (const auto pJoy : pJoystick) {
|
|
if (pJoy != nullptr) {
|
|
used = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!used) {
|
|
mprintf((" No joystick is being used.\n"));
|
|
}
|
|
|
|
initialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t getJoystickCount()
|
|
{
|
|
return joysticks.size();
|
|
}
|
|
|
|
Joystick *getJoystick(size_t index)
|
|
{
|
|
Assertion(index < getJoystickCount(), "Invalid joystick index " SIZE_T_ARG "!", index);
|
|
|
|
return joysticks[index].get();
|
|
}
|
|
|
|
Joystick *getPlayerJoystick(short cid)
|
|
{
|
|
if ((cid >= CID_JOY0) && (cid < CID_JOY_MAX))
|
|
{
|
|
return pJoystick[cid];
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
int getPlayerJoystickCount() {
|
|
int count = 0;
|
|
|
|
for (auto pJoy : pJoystick) {
|
|
if (pJoy != nullptr) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
joy_ff_shutdown();
|
|
|
|
initialized = false;
|
|
std::for_each(pJoystick.begin(), pJoystick.end(), [](Joystick*& pJoy){ pJoy = nullptr; });
|
|
|
|
// Automatically frees joystick resources
|
|
joysticks.clear();
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
|
|
}
|
|
|
|
json_t* getJsonArray() {
|
|
// Do a full init of the SDL_JOYSTICK systems
|
|
init();
|
|
|
|
json_t* array = json_array();
|
|
|
|
// Get the JSON info of each detected joystick and attach it to array
|
|
for (auto& joystick : joysticks) {
|
|
json_t* object = joystick->getJSON();
|
|
|
|
if (object != nullptr) {
|
|
json_array_append_new(array, object);
|
|
}
|
|
}
|
|
|
|
// Do a full de-init of the SDL_JOYSTICK systems
|
|
shutdown();
|
|
|
|
return array;
|
|
}
|
|
|
|
void printJoyJSON() {
|
|
// Create root
|
|
json_t* root = json_object();
|
|
|
|
// Get Joystick data as JSON array
|
|
json_t* array = getJsonArray();
|
|
|
|
// Attach array to root
|
|
json_object_set_new(root, "joysticks", array);
|
|
|
|
// Dump to STDOUT
|
|
json_dumpf(root, stdout, JSON_INDENT(4));
|
|
|
|
shutdown();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compatibility for the rest of the engine follows
|
|
|
|
int joystick_read_raw_axis(short cid, int num_axes, int *axis)
|
|
{
|
|
int i;
|
|
|
|
auto current = io::joystick::getPlayerJoystick(cid);
|
|
if (!initialized || current == nullptr)
|
|
{
|
|
// fake a return value so that controlconfig doesn't get freaky with no joystick
|
|
for (i = 0; i < num_axes; i++)
|
|
{
|
|
axis[i] = 32768;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Assert(num_axes <= JOY_NUM_AXES);
|
|
|
|
for (i = 0; i < num_axes; i++)
|
|
{
|
|
if (i < current->numAxes())
|
|
{
|
|
axis[i] = current->getAxis(i) + 32768;
|
|
} else
|
|
{
|
|
axis[i] = 32768;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
float joy_down_time(const CC_bind &bind)
|
|
{
|
|
int btn = bind.get_btn();
|
|
if (btn < 0) return 0.f;
|
|
|
|
auto current = io::joystick::getPlayerJoystick(bind.get_cid());
|
|
|
|
if (current == nullptr)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
if (btn >= JOY_TOTAL_BUTTONS || (btn >= current->numButtons() && btn < JOY_NUM_BUTTONS))
|
|
{
|
|
// Not a valid button
|
|
return 0.f;
|
|
|
|
}
|
|
else if (btn >= JOY_NUM_BUTTONS)
|
|
{
|
|
// Is hat
|
|
if (current->numHats() > 0) {
|
|
return current->getHatDownTime(0, hatBtnToEnum(btn), false);
|
|
} else {
|
|
// Don't have any hats to query
|
|
return 0.0f;
|
|
}
|
|
|
|
} // Else, Is a button
|
|
|
|
return current->getButtonDownTime(btn);
|
|
}
|
|
|
|
int joy_down_count(const CC_bind &bind, int reset_count)
|
|
{
|
|
int btn = bind.get_btn();
|
|
if (btn < 0) return 0;
|
|
|
|
auto current = io::joystick::getPlayerJoystick(bind.get_cid());
|
|
|
|
if (current == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (btn >= JOY_TOTAL_BUTTONS || (btn >= current->numButtons() && btn < JOY_NUM_BUTTONS))
|
|
{
|
|
// Not a valid button
|
|
return 0;
|
|
|
|
}
|
|
else if (btn >= JOY_NUM_BUTTONS)
|
|
{
|
|
// Is hat
|
|
if (current->numHats() > 0) {
|
|
return current->getHatDownCount(0, hatBtnToEnum(btn), false, reset_count != 0);
|
|
} else {
|
|
// Don't have any hats to query
|
|
return 0;
|
|
}
|
|
|
|
} // Else, is a button
|
|
|
|
return current->getButtonDownCount(btn, reset_count != 0);
|
|
}
|
|
|
|
int joy_down(const CC_bind &bind)
|
|
{
|
|
return joy_down_time(bind) > 0.0f;
|
|
}
|
|
|
|
bool joy_present(short cid) {
|
|
if ((cid < CID_JOY0) || (cid >= CID_JOY_MAX)) {
|
|
return false;
|
|
}
|
|
|
|
return pJoystick[cid] != nullptr;
|
|
}
|
|
|
|
// Check for special trigger buttons and return axis for button
|
|
// This is primarily for easier UI/UX in controlconfig (like conflict detection)
|
|
// returns axis or -1 if not an axis button
|
|
short joy_get_button_axis(short cid, short btn)
|
|
{
|
|
auto current = io::joystick::getPlayerJoystick(cid);
|
|
|
|
// gamepads only (for now?)
|
|
if ( !current || !current->isGamepad() ) {
|
|
return -1;
|
|
}
|
|
|
|
// the special trigger buttons are extended on normal buttons so we need to
|
|
// remove those and sort out whether this is the left (0) or right (1) trigger
|
|
auto axis_button = btn - SDL_GAMEPAD_BUTTON_COUNT;
|
|
|
|
if (axis_button < 0 || axis_button > 1) {
|
|
return -1;
|
|
}
|
|
|
|
return static_cast<short>(SDL_GAMEPAD_AXIS_LEFT_TRIGGER + axis_button);
|
|
}
|