diff --git a/app/gamepadhandler.cpp b/app/gamepadhandler.cpp index 14b4c43..b766c18 100644 --- a/app/gamepadhandler.cpp +++ b/app/gamepadhandler.cpp @@ -1,12 +1,15 @@ #include "gamepadhandler.h" #include +#include +#include +#include +#include #include -#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(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(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; } \ No newline at end of file diff --git a/app/gamepadhandler.h b/app/gamepadhandler.h index 1540f50..84499db 100644 --- a/app/gamepadhandler.h +++ b/app/gamepadhandler.h @@ -1,10 +1,13 @@ #ifndef GAMEPAD_HANDLER_H #define GAMEPAD_HANDLER_H +#include #include #include #include +#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 \ No newline at end of file diff --git a/app/inputconfigdialog.cpp b/app/inputconfigdialog.cpp index 567564f..6ebc10b 100644 --- a/app/inputconfigdialog.cpp +++ b/app/inputconfigdialog.cpp @@ -7,45 +7,107 @@ #include -#include "keymap.h" - InputConfigDialog::InputConfigDialog(QWidget *parent) : QDialog(parent) +{ +} + +InputConfigDialog::InputConfigDialog(KeyMap *keyMap, QWidget *parent) : InputConfigDialog(parent) +{ + m_keyMap = keyMap; + m_gamepadHandler = nullptr; + + createLayout(); + + for (auto it = m_keyMap->cbegin(); it != m_keyMap->cend(); it++) { + InputConfigKeyButton *btn = static_cast(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(); + + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) { + SDL_GameControllerButton b = static_cast(i); + int id = m_gamepadHandler->button(b); + if (id != -1) { + if (InputConfigControllerButton *btn = static_cast(m_buttons[id])) { + btn->setButton(b); + } + } + } + + for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) { + SDL_GameControllerAxis a = static_cast(i); + int id = m_gamepadHandler->axis(a); + if (id != -1) { + if (InputConfigControllerButton *btn = static_cast(m_buttons[id])) { + btn->setAxis(a); + } + } + } + + connectSoloSignals(); +} + +InputConfigDialog::~InputConfigDialog() +{ + if (m_gamepadHandler) { + m_gamepadHandler->signalNextButtonOrAxis(false); + } +} + +template +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(tr("L"), VANILLA_BTN_L), 2, 1); + layout->addWidget(createButton(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(tr("R"), VANILLA_BTN_R), 2, 3); + layout->addWidget(createButton(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(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(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(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(tr("TV"), VANILLA_BTN_TV), 14, 1, 1, 1); - layout->addWidget(createButton(tr("Home"), VANILLA_BTN_HOME), 14, 2); + 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(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(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(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(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(tr("Start/Plus"), VANILLA_BTN_PLUS), 13, 4); - layout->addWidget(createButton(tr("Select/Minus"), VANILLA_BTN_MINUS), 14, 4); + layout->addWidget(createButton(tr("Start/Plus"), VANILLA_BTN_PLUS), 13, 4); + layout->addWidget(createButton(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 +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(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(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("") : 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("") : QKeySequence(m_key).toString(); +} + +bool InputConfigKeyButton::eventFilter(QObject *object, QEvent *event) +{ + if (object == button() && button()->isChecked() && event->type() == QEvent::KeyPress) { QKeyEvent *key = dynamic_cast(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) { @@ -163,4 +248,88 @@ void InputConfigButton::timerTimeout() } else { m_button->setText(tr("Waiting for input (%1)\n 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(""); +} + +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(b); + c->setAxis(axis); + } +} + +void InputConfigDialog::setControllerButton(SDL_GameControllerButton button) +{ + if (InputConfigButtonBase *b = findCheckedButton()) { + InputConfigControllerButton *c = static_cast(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(sender())); + } } \ No newline at end of file diff --git a/app/inputconfigdialog.h b/app/inputconfigdialog.h index 35fa2ab..fc3b464 100644 --- a/app/inputconfigdialog.h +++ b/app/inputconfigdialog.h @@ -1,18 +1,54 @@ #ifndef INPUT_CONFIG_DIALOG_H #define INPUT_CONFIG_DIALOG_H +#include #include #include #include -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 m_buttons; + template + void createLayout(); + + template + InputConfigButtonBase *createButton(const QString &name, int vanillaBtn); + + InputConfigButtonBase *findCheckedButton(); + + void connectSoloSignals(); + void uncheckAllExcept(InputConfigButtonBase *except); + + std::map 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 \ No newline at end of file diff --git a/app/keymap.cpp b/app/keymap.cpp index 4224740..900036a 100644 --- a/app/keymap.cpp +++ b/app/keymap.cpp @@ -6,8 +6,6 @@ #include #include -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) { diff --git a/app/keymap.h b/app/keymap.h index 45049af..a1fa44a 100644 --- a/app/keymap.h +++ b/app/keymap.h @@ -14,8 +14,6 @@ public: static QString getConfigFilename(); - static KeyMap instance; - }; #endif \ No newline at end of file diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 458fbf8..7c5c9f8 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -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(); } diff --git a/app/mainwindow.h b/app/mainwindow.h index d813578..a16fc2b 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -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 m_threadMap;