started implementing controller remapping functionality

Currently axes don't work, will need to find a good solution for this
This commit is contained in:
MattKC 2024-11-14 18:49:18 -08:00
parent a5ef5fca7c
commit cb0a2afaa1
8 changed files with 549 additions and 119 deletions

View file

@ -1,12 +1,15 @@
#include "gamepadhandler.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <QXmlStreamWriter>
#include <vanilla.h>
#include "keymap.h"
static int g_buttonMap[SDL_CONTROLLER_BUTTON_MAX] = {-1};
static int g_axisMap[SDL_CONTROLLER_AXIS_MAX] = {-1};
static bool g_defaultMapsInitialized = false;
static int g_defaultButtonMap[SDL_CONTROLLER_BUTTON_MAX] = {-1};
static int g_defaultAxisMap[SDL_CONTROLLER_AXIS_MAX] = {-1};
GamepadHandler::GamepadHandler(QObject *parent) : QObject(parent)
{
@ -14,29 +17,44 @@ GamepadHandler::GamepadHandler(QObject *parent) : QObject(parent)
m_controller = nullptr;
m_nextGamepad = -2;
m_vibrate = false;
m_keyMap = nullptr;
m_signalNext = false;
g_buttonMap[SDL_CONTROLLER_BUTTON_A] = VANILLA_BTN_A;
g_buttonMap[SDL_CONTROLLER_BUTTON_B] = VANILLA_BTN_B;
g_buttonMap[SDL_CONTROLLER_BUTTON_X] = VANILLA_BTN_X;
g_buttonMap[SDL_CONTROLLER_BUTTON_Y] = VANILLA_BTN_Y;
g_buttonMap[SDL_CONTROLLER_BUTTON_BACK] = VANILLA_BTN_MINUS;
g_buttonMap[SDL_CONTROLLER_BUTTON_GUIDE] = VANILLA_BTN_HOME;
g_buttonMap[SDL_CONTROLLER_BUTTON_MISC1] = VANILLA_BTN_TV;
g_buttonMap[SDL_CONTROLLER_BUTTON_START] = VANILLA_BTN_PLUS;
g_buttonMap[SDL_CONTROLLER_BUTTON_LEFTSTICK] = VANILLA_BTN_L3;
g_buttonMap[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = VANILLA_BTN_R3;
g_buttonMap[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = VANILLA_BTN_L;
g_buttonMap[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = VANILLA_BTN_R;
g_buttonMap[SDL_CONTROLLER_BUTTON_DPAD_UP] = VANILLA_BTN_UP;
g_buttonMap[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = VANILLA_BTN_DOWN;
g_buttonMap[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = VANILLA_BTN_LEFT;
g_buttonMap[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = VANILLA_BTN_RIGHT;
g_axisMap[SDL_CONTROLLER_AXIS_LEFTX] = VANILLA_AXIS_L_X;
g_axisMap[SDL_CONTROLLER_AXIS_LEFTY] = VANILLA_AXIS_L_Y;
g_axisMap[SDL_CONTROLLER_AXIS_RIGHTX] = VANILLA_AXIS_R_X;
g_axisMap[SDL_CONTROLLER_AXIS_RIGHTY] = VANILLA_AXIS_R_Y;
g_axisMap[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = VANILLA_BTN_ZL;
g_axisMap[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = VANILLA_BTN_ZR;
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;
}
void GamepadHandler::close()
@ -53,7 +71,98 @@ void GamepadHandler::setController(int index)
m_mutex.unlock();
}
void EnableSensorIfAvailable(SDL_GameController *controller, SDL_SensorType sensor)
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)
{
if (SDL_GameControllerHasSensor(controller, sensor)) {
SDL_GameControllerSetSensorEnabled(controller, sensor, SDL_TRUE);
@ -78,8 +187,28 @@ void GamepadHandler::run()
}
if (m_nextGamepad != -1) {
m_controller = SDL_GameControllerOpen(m_nextGamepad);
EnableSensorIfAvailable(m_controller, SDL_SENSOR_ACCEL);
EnableSensorIfAvailable(m_controller, SDL_SENSOR_GYRO);
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];
}
}
} else {
m_controller = nullptr;
}
@ -91,8 +220,6 @@ void GamepadHandler::run()
SDL_GameControllerRumble(m_controller, amount, amount, 0);
}
m_mutex.unlock();
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
@ -112,18 +239,32 @@ void GamepadHandler::run()
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if (m_controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(m_controller))) {
int vanilla_btn = g_buttonMap[event.cbutton.button];
int vanilla_btn = m_buttonMap[event.cbutton.button];
if (vanilla_btn != -1) {
emit buttonStateChanged(vanilla_btn, event.type == SDL_CONTROLLERBUTTONDOWN ? INT16_MAX : 0);
}
if (m_signalNext && event.type == SDL_CONTROLLERBUTTONDOWN) {
emit buttonPressed(static_cast<SDL_GameControllerButton>(event.cbutton.button));
}
}
break;
case SDL_CONTROLLERAXISMOTION:
if (m_controller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(m_controller))) {
int vanilla_axis = g_axisMap[event.caxis.axis];
Sint16 axis_value = event.caxis.value;
if (vanilla_axis != -1) {
emit buttonStateChanged(vanilla_axis, axis_value);
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;
}
}
break;
@ -141,6 +282,8 @@ void GamepadHandler::run()
}
}
m_mutex.unlock();
// Don't spam CPU cycles, still allow for up to 200Hz polling (for reference, Wii U gamepad is 180Hz)
SDL_Delay(5);
@ -159,9 +302,9 @@ void GamepadHandler::vibrate(bool on)
void GamepadHandler::keyPressed(Qt::Key key)
{
if (!m_controller) {
auto it = KeyMap::instance.find(key);
if (it != KeyMap::instance.end()) {
if (!m_controller && m_keyMap) {
auto it = m_keyMap->find(key);
if (it != m_keyMap->end()) {
emit buttonStateChanged(it->second, INT16_MAX);
}
}
@ -169,10 +312,28 @@ void GamepadHandler::keyPressed(Qt::Key key)
void GamepadHandler::keyReleased(Qt::Key key)
{
if (!m_controller) {
auto it = KeyMap::instance.find(key);
if (it != KeyMap::instance.end()) {
if (!m_controller && m_keyMap) {
auto it = m_keyMap->find(key);
if (it != m_keyMap->end()) {
emit buttonStateChanged(it->second, 0);
}
}
}
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;
}

View file

@ -1,10 +1,13 @@
#ifndef GAMEPAD_HANDLER_H
#define GAMEPAD_HANDLER_H
#include <QMap>
#include <QMutex>
#include <QObject>
#include <SDL2/SDL.h>
#include "keymap.h"
class GamepadHandler : public QObject
{
Q_OBJECT
@ -15,11 +18,26 @@ public:
void setController(int index);
void setButton(SDL_GameControllerButton sdl_btn, int vanilla_btn);
void setAxis(SDL_GameControllerAxis sdl_axis, int vanilla_axis);
void clear();
void save();
int button(SDL_GameControllerButton sdl_btn);
int axis(SDL_GameControllerAxis sdl_axis);
void setKeyMap(KeyMap *keyMap);
void signalNextButtonOrAxis(bool e);
signals:
void gamepadsChanged();
void buttonStateChanged(int button, int32_t value);
void buttonPressed(SDL_GameControllerButton button);
void axisMoved(SDL_GameControllerAxis axis);
public slots:
void run();
@ -29,14 +47,24 @@ public slots:
void keyReleased(Qt::Key key);
private:
void saveCustomProfile();
static QString getConfigFilename(SDL_JoystickGUID guid);
QMutex m_mutex;
bool m_closed;
int m_nextGamepad;
bool m_vibrate;
bool m_signalNext;
SDL_GameController *m_controller;
QMutex m_mapMutex;
int m_buttonMap[SDL_CONTROLLER_BUTTON_MAX];
int m_axisMap[SDL_CONTROLLER_AXIS_MAX];
KeyMap *m_keyMap;
};
#endif // GAMEPAD_HANDLER_H

View file

@ -7,45 +7,107 @@
#include <vanilla.h>
#include "keymap.h"
InputConfigDialog::InputConfigDialog(QWidget *parent) : QDialog(parent)
{
}
InputConfigDialog::InputConfigDialog(KeyMap *keyMap, QWidget *parent) : InputConfigDialog(parent)
{
m_keyMap = keyMap;
m_gamepadHandler = nullptr;
createLayout<InputConfigKeyButton>();
for (auto it = m_keyMap->cbegin(); it != m_keyMap->cend(); it++) {
InputConfigKeyButton *btn = static_cast<InputConfigKeyButton *>(m_buttons[it->second]);
if (btn) {
btn->setKey(it->first);
}
}
connectSoloSignals();
}
InputConfigDialog::InputConfigDialog(GamepadHandler *gamepadHandler, QWidget *parent) : InputConfigDialog(parent)
{
m_keyMap = nullptr;
m_gamepadHandler = gamepadHandler;
m_gamepadHandler->signalNextButtonOrAxis(true);
connect(m_gamepadHandler, &GamepadHandler::axisMoved, this, &InputConfigDialog::setControllerAxis);
connect(m_gamepadHandler, &GamepadHandler::buttonPressed, this, &InputConfigDialog::setControllerButton);
createLayout<InputConfigControllerButton>();
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
SDL_GameControllerButton b = static_cast<SDL_GameControllerButton>(i);
int id = m_gamepadHandler->button(b);
if (id != -1) {
if (InputConfigControllerButton *btn = static_cast<InputConfigControllerButton *>(m_buttons[id])) {
btn->setButton(b);
}
}
}
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) {
SDL_GameControllerAxis a = static_cast<SDL_GameControllerAxis>(i);
int id = m_gamepadHandler->axis(a);
if (id != -1) {
if (InputConfigControllerButton *btn = static_cast<InputConfigControllerButton *>(m_buttons[id])) {
btn->setAxis(a);
}
}
}
connectSoloSignals();
}
InputConfigDialog::~InputConfigDialog()
{
if (m_gamepadHandler) {
m_gamepadHandler->signalNextButtonOrAxis(false);
}
}
template<typename T>
void InputConfigDialog::createLayout()
{
QGridLayout *layout = new QGridLayout(this);
layout->addWidget(createButton(tr("L"), VANILLA_BTN_L), 2, 1);
layout->addWidget(createButton(tr("ZL"), VANILLA_BTN_ZL), 3, 1);
layout->addWidget(createButton<T>(tr("L"), VANILLA_BTN_L), 2, 1);
layout->addWidget(createButton<T>(tr("ZL"), VANILLA_BTN_ZL), 3, 1);
layout->addWidget(createButton(tr("R"), VANILLA_BTN_R), 2, 3);
layout->addWidget(createButton(tr("ZR"), VANILLA_BTN_ZR), 3, 3);
layout->addWidget(createButton<T>(tr("R"), VANILLA_BTN_R), 2, 3);
layout->addWidget(createButton<T>(tr("ZR"), VANILLA_BTN_ZR), 3, 3);
layout->addWidget(createButton(tr("Left Stick Up"), VANILLA_AXIS_L_UP), 4, 0);
layout->addWidget(createButton(tr("Left Stick Down"), VANILLA_AXIS_L_DOWN), 5, 0);
layout->addWidget(createButton(tr("Left Stick Left"), VANILLA_AXIS_L_LEFT), 6, 0);
layout->addWidget(createButton(tr("Left Stick Right"), VANILLA_AXIS_L_RIGHT), 7, 0);
layout->addWidget(createButton(tr("Left Stick Click"), VANILLA_BTN_L3), 8, 0);
layout->addWidget(createButton<T>(tr("Left Stick Up"), VANILLA_AXIS_L_UP), 4, 0);
layout->addWidget(createButton<T>(tr("Left Stick Down"), VANILLA_AXIS_L_DOWN), 5, 0);
layout->addWidget(createButton<T>(tr("Left Stick Left"), VANILLA_AXIS_L_LEFT), 6, 0);
layout->addWidget(createButton<T>(tr("Left Stick Right"), VANILLA_AXIS_L_RIGHT), 7, 0);
layout->addWidget(createButton<T>(tr("Left Stick Click"), VANILLA_BTN_L3), 8, 0);
layout->addWidget(createButton(tr("D-Pad Up"), VANILLA_BTN_UP), 10, 0);
layout->addWidget(createButton(tr("D-Pad Down"), VANILLA_BTN_DOWN), 11, 0);
layout->addWidget(createButton(tr("D-Pad Left"), VANILLA_BTN_LEFT), 12, 0);
layout->addWidget(createButton(tr("D-Pad Right"), VANILLA_BTN_RIGHT), 13, 0);
layout->addWidget(createButton<T>(tr("D-Pad Up"), VANILLA_BTN_UP), 10, 0);
layout->addWidget(createButton<T>(tr("D-Pad Down"), VANILLA_BTN_DOWN), 11, 0);
layout->addWidget(createButton<T>(tr("D-Pad Left"), VANILLA_BTN_LEFT), 12, 0);
layout->addWidget(createButton<T>(tr("D-Pad Right"), VANILLA_BTN_RIGHT), 13, 0);
layout->addWidget(createButton(tr("TV"), VANILLA_BTN_TV), 14, 1, 1, 1);
layout->addWidget(createButton(tr("Home"), VANILLA_BTN_HOME), 14, 2);
layout->addWidget(createButton<T>(tr("TV"), VANILLA_BTN_TV), 14, 1, 1, 1);
layout->addWidget(createButton<T>(tr("Home"), VANILLA_BTN_HOME), 14, 2);
layout->addWidget(createButton(tr("Right Stick Up"), VANILLA_AXIS_R_UP), 2, 4);
layout->addWidget(createButton(tr("Right Stick Down"), VANILLA_AXIS_R_DOWN), 3, 4);
layout->addWidget(createButton(tr("Right Stick Left"), VANILLA_AXIS_R_LEFT), 4, 4);
layout->addWidget(createButton(tr("Right Stick Right"), VANILLA_AXIS_R_RIGHT), 5, 4);
layout->addWidget(createButton(tr("Right Stick Click"), VANILLA_BTN_R3), 6, 4);
layout->addWidget(createButton<T>(tr("Right Stick Up"), VANILLA_AXIS_R_UP), 2, 4);
layout->addWidget(createButton<T>(tr("Right Stick Down"), VANILLA_AXIS_R_DOWN), 3, 4);
layout->addWidget(createButton<T>(tr("Right Stick Left"), VANILLA_AXIS_R_LEFT), 4, 4);
layout->addWidget(createButton<T>(tr("Right Stick Right"), VANILLA_AXIS_R_RIGHT), 5, 4);
layout->addWidget(createButton<T>(tr("Right Stick Click"), VANILLA_BTN_R3), 6, 4);
layout->addWidget(createButton(tr("A"), VANILLA_BTN_A), 8, 4);
layout->addWidget(createButton(tr("B"), VANILLA_BTN_B), 9, 4);
layout->addWidget(createButton(tr("X"), VANILLA_BTN_X), 10, 4);
layout->addWidget(createButton(tr("Y"), VANILLA_BTN_Y), 11, 4);
layout->addWidget(createButton<T>(tr("A"), VANILLA_BTN_A), 8, 4);
layout->addWidget(createButton<T>(tr("B"), VANILLA_BTN_B), 9, 4);
layout->addWidget(createButton<T>(tr("X"), VANILLA_BTN_X), 10, 4);
layout->addWidget(createButton<T>(tr("Y"), VANILLA_BTN_Y), 11, 4);
layout->addWidget(createButton(tr("Start/Plus"), VANILLA_BTN_PLUS), 13, 4);
layout->addWidget(createButton(tr("Select/Minus"), VANILLA_BTN_MINUS), 14, 4);
layout->addWidget(createButton<T>(tr("Start/Plus"), VANILLA_BTN_PLUS), 13, 4);
layout->addWidget(createButton<T>(tr("Select/Minus"), VANILLA_BTN_MINUS), 14, 4);
QLabel *imageLbl = new QLabel(this);
QPixmap imgPix;
@ -58,42 +120,49 @@ InputConfigDialog::InputConfigDialog(QWidget *parent) : QDialog(parent)
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons, 16, 0, 1, 5);
for (auto it = KeyMap::instance.cbegin(); it != KeyMap::instance.cend(); it++) {
InputConfigButton *btn = m_buttons[it->second];
if (btn) {
btn->setKey(it->first);
}
}
setWindowTitle(tr("Keyboard Configuration"));
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
}
InputConfigButton *InputConfigDialog::createButton(const QString &name, int vanillaBtn)
template<typename T>
InputConfigButtonBase *InputConfigDialog::createButton(const QString &name, int vanillaBtn)
{
InputConfigButton *i = new InputConfigButton(name, this);
InputConfigButtonBase *i = new T(name, this);
m_buttons[vanillaBtn] = i;
return i;
}
void InputConfigDialog::accept()
{
KeyMap::instance.clear();
if (m_keyMap) {
m_keyMap->clear();
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
Qt::Key key = it->second->key();
if (key != Qt::Key_unknown) {
KeyMap::instance[key] = it->first;
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
InputConfigKeyButton *keyBtn = static_cast<InputConfigKeyButton *>(it->second);
Qt::Key key = keyBtn->key();
if (key != Qt::Key_unknown) {
(*m_keyMap)[key] = it->first;
}
}
}
KeyMap::instance.save(KeyMap::getConfigFilename());
m_keyMap->save(KeyMap::getConfigFilename());
} else if (m_gamepadHandler) {
m_gamepadHandler->clear();
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
InputConfigControllerButton *cBtn = static_cast<InputConfigControllerButton *>(it->second);
if (cBtn->button() != SDL_CONTROLLER_BUTTON_INVALID) {
m_gamepadHandler->setButton(cBtn->button(), it->first);
} else if (cBtn->axis() != SDL_CONTROLLER_AXIS_INVALID) {
m_gamepadHandler->setAxis(cBtn->axis(), it->first);
}
}
m_gamepadHandler->save();
}
QDialog::accept();
}
InputConfigButton::InputConfigButton(const QString &name, QWidget *parent) : QWidget(parent)
InputConfigButtonBase::InputConfigButtonBase(const QString &name, QWidget *parent) : QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout(this);
@ -104,27 +173,32 @@ InputConfigButton::InputConfigButton(const QString &name, QWidget *parent) : QWi
m_button = new QPushButton(this);
m_button->setCheckable(true);
connect(m_button, &QPushButton::clicked, this, &InputConfigButton::buttonClicked);
m_button->installEventFilter(this);
connect(m_button, &QPushButton::clicked, this, &InputConfigButtonBase::buttonClicked);
connect(m_button, &QPushButton::toggled, this, &InputConfigButtonBase::toggled);
connect(m_button, &QPushButton::clicked, this, &InputConfigButtonBase::checked);
layout->addWidget(m_button);
layout->addStretch();
m_timer = new QTimer(this);
m_timer->setInterval(1000);
connect(m_timer, &QTimer::timeout, this, &InputConfigButton::timerTimeout);
connect(m_timer, &QTimer::timeout, this, &InputConfigButtonBase::timerTimeout);
}
InputConfigKeyButton::InputConfigKeyButton(const QString &name, QWidget *parent) : InputConfigButtonBase(name, parent)
{
button()->installEventFilter(this);
m_key = Qt::Key_unknown;
buttonClicked(false);
}
void InputConfigButton::setKey(Qt::Key key)
void InputConfigKeyButton::setKey(Qt::Key key)
{
m_key = key;
buttonClicked(false);
}
void InputConfigButton::buttonClicked(bool e)
void InputConfigButtonBase::buttonClicked(bool e)
{
if (e) {
m_timerState = 6;
@ -132,14 +206,25 @@ void InputConfigButton::buttonClicked(bool e)
timerTimeout();
} else {
m_timer->stop();
m_button->setText(m_key == Qt::Key_unknown ? tr("<none>") : QKeySequence(m_key).toString());
m_button->setText(text());
m_button->setChecked(false);
}
}
bool InputConfigButton::eventFilter(QObject *object, QEvent *event)
void InputConfigButtonBase::setChecked(bool e)
{
if (object == m_button && m_button->isChecked() && event->type() == QEvent::KeyPress) {
m_button->setChecked(e);
buttonClicked(e);
}
QString InputConfigKeyButton::text() const
{
return m_key == Qt::Key_unknown ? tr("<none>") : QKeySequence(m_key).toString();
}
bool InputConfigKeyButton::eventFilter(QObject *object, QEvent *event)
{
if (object == button() && button()->isChecked() && event->type() == QEvent::KeyPress) {
QKeyEvent *key = dynamic_cast<QKeyEvent *>(event);
if (key->key() == Qt::Key_Escape) {
// Don't set
@ -155,7 +240,7 @@ bool InputConfigButton::eventFilter(QObject *object, QEvent *event)
return false;
}
void InputConfigButton::timerTimeout()
void InputConfigButtonBase::timerTimeout()
{
m_timerState--;
if (m_timerState == 0) {
@ -164,3 +249,87 @@ void InputConfigButton::timerTimeout()
m_button->setText(tr("Waiting for input (%1)\n<Backspace> to clear").arg(m_timerState));
}
}
InputConfigControllerButton::InputConfigControllerButton(const QString &name, QWidget *parent) : InputConfigButtonBase(name, parent)
{
m_button = SDL_CONTROLLER_BUTTON_INVALID;
m_axis = SDL_CONTROLLER_AXIS_INVALID;
buttonClicked(false);
}
QString InputConfigControllerButton::text() const
{
if (m_button != SDL_CONTROLLER_BUTTON_INVALID) {
return SDL_GameControllerGetStringForButton(m_button);
} else if (m_axis != SDL_CONTROLLER_AXIS_INVALID) {
return SDL_GameControllerGetStringForAxis(m_axis);
}
return tr("<none>");
}
void InputConfigControllerButton::setButton(SDL_GameControllerButton button)
{
m_button = button;
m_axis = SDL_CONTROLLER_AXIS_INVALID;
buttonClicked(false);
}
void InputConfigControllerButton::setAxis(SDL_GameControllerAxis axis)
{
m_button = SDL_CONTROLLER_BUTTON_INVALID;
m_axis = axis;
buttonClicked(false);
}
InputConfigButtonBase *InputConfigDialog::findCheckedButton()
{
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
InputConfigButtonBase *test = it->second;
if (test && test->isChecked()) {
return test;
}
}
return nullptr;
}
void InputConfigDialog::setControllerAxis(SDL_GameControllerAxis axis)
{
if (InputConfigButtonBase *b = findCheckedButton()) {
InputConfigControllerButton *c = static_cast<InputConfigControllerButton *>(b);
c->setAxis(axis);
}
}
void InputConfigDialog::setControllerButton(SDL_GameControllerButton button)
{
if (InputConfigButtonBase *b = findCheckedButton()) {
InputConfigControllerButton *c = static_cast<InputConfigControllerButton *>(b);
c->setButton(button);
}
}
void InputConfigDialog::connectSoloSignals()
{
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
InputConfigButtonBase *btn = it->second;
if (btn)
connect(btn, &InputConfigButtonBase::checked, this, &InputConfigDialog::buttonChecked);
}
}
void InputConfigDialog::uncheckAllExcept(InputConfigButtonBase *except)
{
for (auto it = m_buttons.cbegin(); it != m_buttons.cend(); it++) {
InputConfigButtonBase *b = it->second;
if (b && b != except) {
b->setChecked(false);
}
}
}
void InputConfigDialog::buttonChecked(bool e)
{
if (e) {
uncheckAllExcept(static_cast<InputConfigButtonBase *>(sender()));
}
}

View file

@ -1,18 +1,54 @@
#ifndef INPUT_CONFIG_DIALOG_H
#define INPUT_CONFIG_DIALOG_H
#include <SDL2/SDL.h>
#include <QDialog>
#include <QPushButton>
#include <QTimer>
class InputConfigButton : public QWidget
#include "gamepadhandler.h"
#include "keymap.h"
class InputConfigButtonBase : public QWidget
{
Q_OBJECT
public:
InputConfigButton(const QString &name, QWidget *parent = nullptr);
InputConfigButtonBase(const QString &name, QWidget *parent = nullptr);
bool isChecked() const { return m_button->isChecked(); }
void setChecked(bool e);
private:
QPushButton *m_button;
QTimer *m_timer;
int m_timerState;
protected:
QPushButton *button() { return m_button; }
virtual QString text() const = 0;
signals:
void checked(bool e);
void toggled(bool e);
protected slots:
void buttonClicked(bool e);
private slots:
void timerTimeout();
};
class InputConfigKeyButton : public InputConfigButtonBase
{
Q_OBJECT
public:
InputConfigKeyButton(const QString &name, QWidget *parent = nullptr);
Qt::Key key() const { return m_key; }
virtual QString text() const override;
public slots:
void setKey(Qt::Key key);
@ -20,15 +56,28 @@ protected:
virtual bool eventFilter(QObject *object, QEvent *event) override;
private:
QPushButton *m_button;
Qt::Key m_key;
QTimer *m_timer;
int m_timerState;
private slots:
void buttonClicked(bool e);
};
void timerTimeout();
class InputConfigControllerButton : public InputConfigButtonBase
{
Q_OBJECT
public:
InputConfigControllerButton(const QString &name, QWidget *parent = nullptr);
SDL_GameControllerButton button() const { return m_button; }
SDL_GameControllerAxis axis() const { return m_axis; }
virtual QString text() const override;
public slots:
void setButton(SDL_GameControllerButton btn);
void setAxis(SDL_GameControllerAxis axis);
private:
SDL_GameControllerButton m_button;
SDL_GameControllerAxis m_axis;
};
@ -36,15 +85,36 @@ class InputConfigDialog : public QDialog
{
Q_OBJECT
public:
InputConfigDialog(QWidget *parent = nullptr);
InputConfigDialog(KeyMap *keyMap, QWidget *parent = nullptr);
InputConfigDialog(GamepadHandler *gamepadHandler, QWidget *parent = nullptr);
virtual ~InputConfigDialog() override;
public slots:
virtual void accept() override;
private:
InputConfigButton *createButton(const QString &name, int vanillaBtn);
InputConfigDialog(QWidget *parent = nullptr);
std::map<int, InputConfigButton *> m_buttons;
template<typename T>
void createLayout();
template<typename T>
InputConfigButtonBase *createButton(const QString &name, int vanillaBtn);
InputConfigButtonBase *findCheckedButton();
void connectSoloSignals();
void uncheckAllExcept(InputConfigButtonBase *except);
std::map<int, InputConfigButtonBase *> m_buttons;
KeyMap *m_keyMap;
GamepadHandler *m_gamepadHandler;
private slots:
void setControllerAxis(SDL_GameControllerAxis axis);
void setControllerButton(SDL_GameControllerButton button);
void buttonChecked(bool e);
};
#endif // INPUT_CONFIG_DIALOG_H

View file

@ -6,8 +6,6 @@
#include <QXmlStreamWriter>
#include <vanilla.h>
KeyMap KeyMap::instance;
KeyMap::KeyMap()
{
KeyMap &ref = *this;
@ -39,7 +37,7 @@ KeyMap::KeyMap()
ref[Qt::Key_J] = VANILLA_BTN_ZR;
}
int g_serializationVersion = 1;
static int g_serializationVersion = 1;
bool KeyMap::load(const QString &filename)
{

View file

@ -14,8 +14,6 @@ public:
static QString getConfigFilename();
static KeyMap instance;
};
#endif

View file

@ -27,7 +27,6 @@
#include "backendinitdialog.h"
#include "inputconfigdialog.h"
#include "keymap.h"
#include "syncdialog.h"
#include "udpaddressdialog.h"
@ -218,6 +217,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
startObjectOnThread(m_videoDecoder);
m_gamepadHandler = new GamepadHandler();
m_gamepadHandler->setKeyMap(&m_keyMap);
startObjectOnThread(m_gamepadHandler);
QMetaObject::invokeMethod(m_gamepadHandler, &GamepadHandler::run, Qt::QueuedConnection);
@ -236,7 +236,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
populateMicrophones();
populateControllers();
KeyMap::instance.load(KeyMap::getConfigFilename());
m_keyMap.load(KeyMap::getConfigFilename());
setWindowTitle(tr("Vanilla"));
}
@ -422,7 +422,6 @@ void MainWindow::setConnectedState(bool on)
void MainWindow::setJoystick(int index)
{
m_controllerMappingButton->setEnabled(index == 0); // Currently we only allow configuring keyboard controls
m_gamepadHandler->setController(index - 1);
}
@ -453,7 +452,12 @@ void MainWindow::volumeChanged(int v)
void MainWindow::showInputConfigDialog()
{
InputConfigDialog *d = new InputConfigDialog(this);
InputConfigDialog *d;
if (m_controllerComboBox->currentData().toInt() == -1) {
d = new InputConfigDialog(&m_keyMap, this);
} else {
d = new InputConfigDialog(m_gamepadHandler, this);
}
d->open();
}

View file

@ -11,6 +11,7 @@
#include "audiohandler.h"
#include "backend.h"
#include "gamepadhandler.h"
#include "keymap.h"
#include "videodecoder.h"
#include "viewer.h"
@ -58,6 +59,7 @@ private:
VideoDecoder *m_videoDecoder;
GamepadHandler *m_gamepadHandler;
AudioHandler *m_audioHandler;
KeyMap m_keyMap;
QMap<QObject *, QThread *> m_threadMap;