Files
qgroundcontrol/src/Joystick/JoystickSDL.cc
T
2024-12-21 04:26:03 -05:00

216 lines
6.5 KiB
C++

/****************************************************************************
*
* (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "JoystickSDL.h"
#include "QGCLoggingCategory.h"
#include <QtCore/QFile>
#include <QtCore/QIODevice>
#include <QtCore/QTextStream>
#include <SDL.h>
QGC_LOGGING_CATEGORY(JoystickSDLLog, "qgc.joystick.joysticksdl")
JoystickSDL::JoystickSDL(const QString &name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController, QObject *parent)
: Joystick(name, axisCount, buttonCount, hatCount, parent)
, _isGameController(isGameController)
, _index(index)
{
// qCDebug(JoystickSDLLog) << Q_FUNC_INFO << this;
if (_isGameController) {
_setDefaultCalibration();
}
}
JoystickSDL::~JoystickSDL()
{
// qCDebug(JoystickSDLLog) << Q_FUNC_INFO << this;
}
bool JoystickSDL::init()
{
SDL_SetMainReady();
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) < 0) {
(void) SDL_JoystickEventState(SDL_DISABLE);
qCWarning(JoystickSDLLog) << "Failed to initialize SDL:" << SDL_GetError();
return false;
}
_loadGameControllerMappings();
return true;
}
QMap<QString, Joystick*> JoystickSDL::discover()
{
static QMap<QString, Joystick*> ret;
QMap<QString, Joystick*> newRet;
qCDebug(JoystickSDLLog) << "Discovering joysticks";
for (int i = 0; i < SDL_NumJoysticks(); i++) {
QString name = SDL_JoystickNameForIndex(i);
if (ret.contains(name)) {
newRet[name] = ret[name];
JoystickSDL *const joystick = static_cast<JoystickSDL*>(newRet[name]);
if (joystick->index() != i) {
joystick->setIndex(i); // This joystick index has been remapped by SDL
}
// Anything left in ret after we exit the loop has been removed (unplugged) and needs to be cleaned up.
// We will handle that in JoystickManager in case the removed joystick was in use.
(void) ret.remove(name);
qCDebug(JoystickSDLLog) << "Skipping duplicate" << name;
continue;
}
SDL_Joystick *const sdlJoystick = SDL_JoystickOpen(i);
if (!sdlJoystick) {
qCWarning(JoystickSDLLog) << "SDL failed opening joystick" << qPrintable(name) << "error:" << SDL_GetError();
continue;
}
SDL_ClearError();
const int axisCount = SDL_JoystickNumAxes(sdlJoystick);
const int buttonCount = SDL_JoystickNumButtons(sdlJoystick);
const int hatCount = SDL_JoystickNumHats(sdlJoystick);
if ((axisCount < 0) || (buttonCount < 0) || (hatCount < 0)) {
qCWarning(JoystickSDLLog) << "SDL error parsing joystick features:" << SDL_GetError();
}
SDL_JoystickClose(sdlJoystick);
const bool isGameController = SDL_IsGameController(i);
qCDebug(JoystickSDLLog) << name << "axes:" << axisCount << "buttons:" << buttonCount << "hats:" << hatCount << "isGC:" << isGameController;
// Check for joysticks with duplicate names and differentiate the keys when necessary.
// This is required when using an Xbox 360 wireless receiver that always identifies as
// 4 individual joysticks, regardless of how many joysticks are actually connected to the
// receiver. Using GUID does not help, all of these devices present the same GUID.
const QString originalName = name;
uint8_t duplicateIdx = 1;
while (newRet[name]) {
name = QString("%1 %2").arg(originalName).arg(duplicateIdx++);
}
newRet[name] = new JoystickSDL(name, qMax(0, axisCount), qMax(0, buttonCount), qMax(0, hatCount), i, isGameController);
}
if (newRet.isEmpty()) {
qCDebug(JoystickSDLLog) << "None found";
}
ret = newRet;
return ret;
}
bool JoystickSDL::_open()
{
if (_isGameController) {
_sdlController = SDL_GameControllerOpen(_index);
_sdlJoystick = SDL_GameControllerGetJoystick(_sdlController);
} else {
_sdlJoystick = SDL_JoystickOpen(_index);
}
if (!_sdlJoystick) {
qCWarning(JoystickSDLLog) << "SDL_JoystickOpen failed:" << SDL_GetError();
return false;
}
qCDebug(JoystickSDLLog) << "Opened" << SDL_JoystickName(_sdlJoystick) << "joystick at" << _sdlJoystick;
return true;
}
void JoystickSDL::_close()
{
if (!_sdlJoystick) {
qCWarning(JoystickSDLLog) << "Attempt to close null joystick!";
return;
}
qCDebug(JoystickSDLLog) << "Closing" << SDL_JoystickName(_sdlJoystick) << "joystick at" << _sdlJoystick;
if (_isGameController) {
SDL_GameControllerClose(_sdlController);
} else {
SDL_JoystickClose(_sdlJoystick);
}
_sdlJoystick = nullptr;
_sdlController = nullptr;
}
bool JoystickSDL::_update()
{
if (_isGameController) {
SDL_GameControllerUpdate();
} else {
SDL_JoystickUpdate();
}
return true;
}
bool JoystickSDL::_getButton(int i)
{
int button = -1;
if (_isGameController) {
button = SDL_GameControllerGetButton(_sdlController, SDL_GameControllerButton(i));
} else {
button = SDL_JoystickGetButton(_sdlJoystick, i);
}
return (button == 1);
}
int JoystickSDL::_getAxis(int i)
{
int axis = -1;
if (_isGameController) {
axis = SDL_GameControllerGetAxis(_sdlController, SDL_GameControllerAxis(i));
} else {
axis = SDL_JoystickGetAxis(_sdlJoystick, i);
}
return axis;
}
bool JoystickSDL::_getHat(int hat, int i)
{
static constexpr uint8_t hatButtons[] = {SDL_HAT_UP, SDL_HAT_DOWN, SDL_HAT_LEFT, SDL_HAT_RIGHT};
if (i >= std::size(hatButtons)) {
return false;
}
return ((SDL_JoystickGetHat(_sdlJoystick, hat) & hatButtons[i]) != 0);
}
void JoystickSDL::_loadGameControllerMappings()
{
QFile file(QStringLiteral(":/db/mapping/joystick/gamecontrollerdb.txt"));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCWarning(JoystickSDLLog) << "Couldn't load GameController mapping database.";
return;
}
QTextStream stream(&file);
while (!stream.atEnd()) {
if (SDL_GameControllerAddMapping(stream.readLine().toStdString().c_str()) == -1) {
qCWarning(JoystickSDLLog) << "Couldn't add GameController mapping:" << SDL_GetError();
}
}
}