2024-05-15 15:44:45 -04:00
|
|
|
#include "gamepadhandler.h"
|
|
|
|
|
2024-05-18 15:55:00 -04:00
|
|
|
#include <QDateTime>
|
2024-11-14 21:49:18 -05:00
|
|
|
#include <QDir>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QStandardPaths>
|
|
|
|
#include <QXmlStreamWriter>
|
2024-05-15 15:44:45 -04:00
|
|
|
#include <vanilla.h>
|
|
|
|
|
2024-11-14 21:49:18 -05:00
|
|
|
static bool g_defaultMapsInitialized = false;
|
|
|
|
static int g_defaultButtonMap[SDL_CONTROLLER_BUTTON_MAX] = {-1};
|
|
|
|
static int g_defaultAxisMap[SDL_CONTROLLER_AXIS_MAX] = {-1};
|
2024-05-15 15:44:45 -04:00
|
|
|
|
|
|
|
GamepadHandler::GamepadHandler(QObject *parent) : QObject(parent)
|
|
|
|
{
|
|
|
|
m_closed = false;
|
|
|
|
m_controller = nullptr;
|
2024-05-20 23:25:55 -04:00
|
|
|
m_nextGamepad = -2;
|
2024-05-20 19:16:40 -04:00
|
|
|
m_vibrate = false;
|
2024-11-14 21:49:18 -05:00
|
|
|
m_keyMap = nullptr;
|
|
|
|
m_signalNext = false;
|
2024-05-15 15:44:45 -04:00
|
|
|
|
2024-11-14 21:49:18 -05:00
|
|
|
if (!g_defaultMapsInitialized) {
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT;
|
|
|
|
g_defaultButtonMap[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL;
|
|
|
|
g_defaultAxisMap[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR;
|
|
|
|
g_defaultMapsInitialized = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::setKeyMap(KeyMap *keyMap)
|
|
|
|
{
|
|
|
|
m_keyMap = keyMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::signalNextButtonOrAxis(bool e)
|
|
|
|
{
|
|
|
|
m_signalNext = e;
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::close()
|
|
|
|
{
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.lock();
|
2024-05-15 15:44:45 -04:00
|
|
|
m_closed = true;
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.unlock();
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::setController(int index)
|
|
|
|
{
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.lock();
|
2024-05-15 15:44:45 -04:00
|
|
|
m_nextGamepad = index;
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.unlock();
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
|
2024-11-14 21:49:18 -05:00
|
|
|
void GamepadHandler::clear()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
|
|
|
|
m_buttonMap[i] = -1;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) {
|
|
|
|
m_axisMap[i] = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::setButton(SDL_GameControllerButton sdl_btn, int vanilla_btn)
|
|
|
|
{
|
|
|
|
m_mapMutex.lock();
|
|
|
|
m_buttonMap[sdl_btn] = vanilla_btn;
|
|
|
|
m_mapMutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::setAxis(SDL_GameControllerAxis sdl_axis, int vanilla_axis)
|
|
|
|
{
|
|
|
|
m_mapMutex.lock();
|
|
|
|
m_axisMap[sdl_axis] = vanilla_axis;
|
|
|
|
m_mapMutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::save()
|
|
|
|
{
|
|
|
|
saveCustomProfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString GamepadHandler::getConfigFilename(SDL_JoystickGUID guid)
|
|
|
|
{
|
|
|
|
QDir dir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
|
|
|
|
dir = dir.filePath("vanilla");
|
|
|
|
dir.mkpath(".");
|
|
|
|
|
|
|
|
char buf[33];
|
|
|
|
SDL_JoystickGetGUIDString(guid, buf, sizeof(buf));
|
|
|
|
|
|
|
|
return dir.filePath(QStringLiteral("controller-%1.xml").arg(buf));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int g_serializationVersion = 1;
|
|
|
|
|
|
|
|
void GamepadHandler::saveCustomProfile()
|
|
|
|
{
|
|
|
|
m_mutex.lock();
|
|
|
|
if (m_controller) {
|
|
|
|
SDL_JoystickGUID guid = SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(m_controller));
|
|
|
|
QFile file(getConfigFilename(guid));
|
|
|
|
if (file.open(QFile::WriteOnly)) {
|
|
|
|
QXmlStreamWriter writer(&file);
|
|
|
|
writer.setAutoFormatting(true);
|
|
|
|
|
|
|
|
writer.writeStartDocument();
|
|
|
|
|
|
|
|
writer.writeStartElement(QStringLiteral("VanillaControllerMap"));
|
|
|
|
|
|
|
|
writer.writeAttribute(QStringLiteral("Version"), QString::number(g_serializationVersion));
|
|
|
|
|
|
|
|
writer.writeStartElement(QStringLiteral("Buttons"));
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
|
|
|
|
if (m_buttonMap[i] != g_defaultButtonMap[i]) {
|
|
|
|
writer.writeStartElement(QStringLiteral("Button"));
|
|
|
|
writer.writeAttribute(QStringLiteral("Id"), QString::number(i));
|
|
|
|
writer.writeCharacters(QString::number(m_buttonMap[i]));
|
|
|
|
writer.writeEndElement(); // Button
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writer.writeEndElement(); // Buttons
|
|
|
|
|
|
|
|
writer.writeStartElement(QStringLiteral("Axes"));
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) {
|
|
|
|
if (m_axisMap[i] != g_defaultAxisMap[i]) {
|
|
|
|
writer.writeStartElement(QStringLiteral("Axis"));
|
|
|
|
writer.writeAttribute(QStringLiteral("Id"), QString::number(i));
|
|
|
|
writer.writeCharacters(QString::number(m_axisMap[i]));
|
|
|
|
writer.writeEndElement(); // Axis
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writer.writeEndElement(); // Axes
|
|
|
|
|
|
|
|
writer.writeEndElement(); // VanillaControllerMap
|
|
|
|
|
|
|
|
writer.writeEndDocument();
|
|
|
|
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void enableSensorIfAvailable(SDL_GameController *controller, SDL_SensorType sensor)
|
2024-06-13 21:38:24 -04:00
|
|
|
{
|
|
|
|
if (SDL_GameControllerHasSensor(controller, sensor)) {
|
|
|
|
SDL_GameControllerSetSensorEnabled(controller, sensor, SDL_TRUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t packFloat(float f)
|
|
|
|
{
|
|
|
|
int32_t x;
|
|
|
|
memcpy(&x, &f, sizeof(int32_t));
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2024-05-15 15:44:45 -04:00
|
|
|
void GamepadHandler::run()
|
|
|
|
{
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.lock();
|
|
|
|
|
|
|
|
while (!m_closed) {// See status of rumble
|
2024-05-20 23:25:55 -04:00
|
|
|
if (m_nextGamepad != -2) {
|
2024-05-15 15:44:45 -04:00
|
|
|
if (m_controller) {
|
|
|
|
SDL_GameControllerClose(m_controller);
|
|
|
|
}
|
2024-05-20 23:25:55 -04:00
|
|
|
if (m_nextGamepad != -1) {
|
|
|
|
m_controller = SDL_GameControllerOpen(m_nextGamepad);
|
2024-11-14 21:49:18 -05:00
|
|
|
enableSensorIfAvailable(m_controller, SDL_SENSOR_ACCEL);
|
|
|
|
enableSensorIfAvailable(m_controller, SDL_SENSOR_GYRO);
|
|
|
|
|
|
|
|
SDL_JoystickGUID guid = SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(m_controller));
|
|
|
|
QFile f(getConfigFilename(guid));
|
|
|
|
clear();
|
|
|
|
bool loaded = false;
|
|
|
|
if (f.open(QFile::ReadOnly)) {
|
|
|
|
// TODO: Load custom profile if available
|
|
|
|
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!loaded) {
|
|
|
|
// Set defaults
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
|
|
|
|
m_buttonMap[i] = g_defaultButtonMap[i];
|
|
|
|
}
|
|
|
|
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) {
|
|
|
|
m_axisMap[i] = g_defaultAxisMap[i];
|
|
|
|
}
|
|
|
|
}
|
2024-05-20 23:25:55 -04:00
|
|
|
} else {
|
|
|
|
m_controller = nullptr;
|
|
|
|
}
|
|
|
|
m_nextGamepad = -2;
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
2024-05-18 15:55:00 -04:00
|
|
|
|
2024-05-20 19:16:40 -04:00
|
|
|
if (m_controller) {
|
|
|
|
uint16_t amount = m_vibrate ? 0xFFFF : 0;
|
|
|
|
SDL_GameControllerRumble(m_controller, amount, amount, 0);
|
2024-05-18 15:55:00 -04:00
|
|
|
}
|
|
|
|
|
2024-05-15 15:44:45 -04:00
|
|
|
SDL_Event event;
|
|
|
|
while (SDL_PollEvent(&event)) {
|
|
|
|
switch (event.type) {
|
|
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
|
|
emit gamepadsChanged();
|
|
|
|
break;
|
|
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
|
|
// Our connected controller was disconnected
|
|
|
|
if (m_controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(m_controller))) {
|
|
|
|
SDL_GameControllerClose(m_controller);
|
|
|
|
m_controller = nullptr;
|
|
|
|
}
|
|
|
|
emit gamepadsChanged();
|
|
|
|
break;
|
|
|
|
case SDL_CONTROLLERDEVICEREMAPPED:
|
|
|
|
break;
|
|
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
|
|
if (m_controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(m_controller))) {
|
2024-11-14 21:49:18 -05:00
|
|
|
int vanilla_btn = m_buttonMap[event.cbutton.button];
|
2024-05-20 23:25:55 -04:00
|
|
|
if (vanilla_btn != -1) {
|
2024-05-23 21:47:04 -04:00
|
|
|
emit buttonStateChanged(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0);
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
2024-11-14 21:49:18 -05:00
|
|
|
if (m_signalNext && event.type == SDL_CONTROLLERBUTTONDOWN) {
|
|
|
|
emit buttonPressed(static_cast<SDL_GameControllerButton>(event.cbutton.button));
|
|
|
|
}
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SDL_CONTROLLERAXISMOTION:
|
|
|
|
if (m_controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(m_controller))) {
|
2024-11-14 21:49:18 -05:00
|
|
|
static Sint16 cached_axes[SDL_CONTROLLER_AXIS_MAX] = {0};
|
|
|
|
|
|
|
|
if (event.caxis.value != cached_axes[event.caxis.axis]) {
|
|
|
|
int vanilla_axis = m_axisMap[event.caxis.axis];
|
|
|
|
Sint16 axis_value = event.caxis.value;
|
|
|
|
if (vanilla_axis != -1) {
|
|
|
|
emit buttonStateChanged(vanilla_axis, axis_value);
|
|
|
|
}
|
|
|
|
if (m_signalNext) {
|
|
|
|
static const int threshold = 1024;
|
|
|
|
if (abs(event.caxis.value - cached_axes[event.caxis.axis]) > threshold) {
|
|
|
|
emit axisMoved(static_cast<SDL_GameControllerAxis>(event.caxis.axis));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cached_axes[event.caxis.axis] = event.caxis.value;
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2024-06-13 21:38:24 -04:00
|
|
|
case SDL_CONTROLLERSENSORUPDATE:
|
|
|
|
if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_ACCEL_X, packFloat(event.csensor.data[0]));
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_ACCEL_Y, packFloat(event.csensor.data[1]));
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_ACCEL_Z, packFloat(event.csensor.data[2]));
|
|
|
|
} else if (event.csensor.sensor == SDL_SENSOR_GYRO) {
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_GYRO_PITCH, packFloat(event.csensor.data[0]));
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_GYRO_YAW, packFloat(event.csensor.data[1]));
|
|
|
|
emit buttonStateChanged(VANILLA_SENSOR_GYRO_ROLL, packFloat(event.csensor.data[2]));
|
|
|
|
}
|
|
|
|
break;
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
|
|
|
}
|
2024-05-18 15:55:00 -04:00
|
|
|
|
2024-11-14 21:49:18 -05:00
|
|
|
m_mutex.unlock();
|
|
|
|
|
2024-06-20 05:15:05 -04:00
|
|
|
// Don't spam CPU cycles, still allow for up to 200Hz polling (for reference, Wii U gamepad is 180Hz)
|
|
|
|
SDL_Delay(5);
|
|
|
|
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.lock();
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|
2024-05-18 15:55:00 -04:00
|
|
|
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
2024-05-20 19:16:40 -04:00
|
|
|
void GamepadHandler::vibrate(bool on)
|
2024-05-18 15:55:00 -04:00
|
|
|
{
|
|
|
|
m_mutex.lock();
|
2024-05-20 19:16:40 -04:00
|
|
|
m_vibrate = on;
|
2024-05-18 15:55:00 -04:00
|
|
|
m_mutex.unlock();
|
2024-05-20 23:25:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::keyPressed(Qt::Key key)
|
|
|
|
{
|
2024-11-14 21:49:18 -05:00
|
|
|
if (!m_controller && m_keyMap) {
|
|
|
|
auto it = m_keyMap->find(key);
|
|
|
|
if (it != m_keyMap->end()) {
|
2024-05-23 21:47:04 -04:00
|
|
|
emit buttonStateChanged(it->second, INT16_MAX);
|
2024-05-20 23:25:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GamepadHandler::keyReleased(Qt::Key key)
|
|
|
|
{
|
2024-11-14 21:49:18 -05:00
|
|
|
if (!m_controller && m_keyMap) {
|
|
|
|
auto it = m_keyMap->find(key);
|
|
|
|
if (it != m_keyMap->end()) {
|
2024-05-23 21:47:04 -04:00
|
|
|
emit buttonStateChanged(it->second, 0);
|
2024-05-20 23:25:55 -04:00
|
|
|
}
|
|
|
|
}
|
2024-11-14 21:49:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int GamepadHandler::button(SDL_GameControllerButton sdl_btn)
|
|
|
|
{
|
|
|
|
int b;
|
|
|
|
m_mapMutex.lock();
|
|
|
|
b = m_buttonMap[sdl_btn];
|
|
|
|
m_mapMutex.unlock();
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GamepadHandler::axis(SDL_GameControllerAxis sdl_axis)
|
|
|
|
{
|
|
|
|
int b;
|
|
|
|
m_mapMutex.lock();
|
|
|
|
b = m_axisMap[sdl_axis];
|
|
|
|
m_mapMutex.unlock();
|
|
|
|
return b;
|
2024-05-15 15:44:45 -04:00
|
|
|
}
|