diff --git a/src/frontend/EmuThread.cpp b/src/frontend/EmuThread.cpp index da9779db..1d11ffb8 100644 --- a/src/frontend/EmuThread.cpp +++ b/src/frontend/EmuThread.cpp @@ -8,12 +8,6 @@ void EmuThread::run() noexcept { core->parallel.Init(renderWidget.qtVkInstanceFactory, renderWidget.wsiPlatform, renderWidget.windowInfo, core->cpu->GetMem().GetRDRAMPtr()); - SDL_InitSubSystem(SDL_INIT_GAMEPAD); - - if (SDL_AddGamepadMappingsFromFile("resources/gamecontrollerdb.txt") < 0) { - Util::warn("[SDL] Could not load game controller DB"); - } - while (!isInterruptionRequested()) { if (!core->pause) { core->Run(settings.getVolumeL(), settings.getVolumeR()); diff --git a/src/frontend/InputSettings.cpp b/src/frontend/InputSettings.cpp index a01a1ee5..04a0828e 100644 --- a/src/frontend/InputSettings.cpp +++ b/src/frontend/InputSettings.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include InputSettings::InputSettings(nlohmann::json &settings) : QWidget(nullptr), settings(settings) { buttonLabels[0] = std::make_unique("A"); @@ -22,97 +24,112 @@ InputSettings::InputSettings(nlohmann::json &settings) : QWidget(nullptr), setti buttonLabels[17] = std::make_unique("Analog Right"); auto str = JSONGetField(settings, "input", "A"); - kb_buttons[0] = std::make_unique(str.c_str()); + kbButtons[0] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "B"); - kb_buttons[1] = std::make_unique(str.c_str()); + kbButtons[1] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Z"); - kb_buttons[2] = std::make_unique(str.c_str()); + kbButtons[2] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Start"); - kb_buttons[3] = std::make_unique(str.c_str()); + kbButtons[3] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "L"); - kb_buttons[4] = std::make_unique(str.c_str()); + kbButtons[4] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "R"); - kb_buttons[5] = std::make_unique(str.c_str()); + kbButtons[5] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Dpad Up"); - kb_buttons[6] = std::make_unique(str.c_str()); + kbButtons[6] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Dpad Down"); - kb_buttons[7] = std::make_unique(str.c_str()); + kbButtons[7] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Dpad Left"); - kb_buttons[8] = std::make_unique(str.c_str()); + kbButtons[8] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Dpad Right"); - kb_buttons[9] = std::make_unique(str.c_str()); + kbButtons[9] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "C Up"); - kb_buttons[10] = std::make_unique(str.c_str()); + kbButtons[10] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "C Down"); - kb_buttons[11] = std::make_unique(str.c_str()); + kbButtons[11] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "C Left"); - kb_buttons[12] = std::make_unique(str.c_str()); + kbButtons[12] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "C Right"); - kb_buttons[13] = std::make_unique(str.c_str()); + kbButtons[13] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Analog Up"); - kb_buttons[14] = std::make_unique(str.c_str()); + kbButtons[14] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Analog Down"); - kb_buttons[15] = std::make_unique(str.c_str()); + kbButtons[15] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Analog Left"); - kb_buttons[16] = std::make_unique(str.c_str()); + kbButtons[16] = std::make_unique(str.c_str()); str = JSONGetField(settings, "input", "Analog Right"); - kb_buttons[17] = std::make_unique(str.c_str()); + kbButtons[17] = std::make_unique(str.c_str()); for (int i = 0; i < 18; i++) { - connect(kb_buttons[i].get(), &QPushButton::pressed, this, [&, i]() { - for (auto& kb_button : kb_buttons) { - kb_button->setEnabled(false); + connect(kbButtons[i].get(), &QPushButton::pressed, this, [&, i]() { + devices->setEnabled(false); + for (const auto &kbButton : kbButtons) { + kbButton->setEnabled(false); } grabKeyboard(); grabbing = true; - which_grabbing = i; + whichGrabbing = i; }); } - AB->addWidget(n64_button_labels[0].get()); - AB->addWidget(kb_buttons[0].get()); - AB->addWidget(n64_button_labels[1].get()); - AB->addWidget(kb_buttons[1].get()); + SDL_InitSubSystem(SDL_INIT_GAMEPAD); + + if (SDL_AddGamepadMappingsFromFile("resources/gamecontrollerdb.txt") < 0) { + Util::warn("[SDL] Could not load game controller DB"); + } + + connect(&refresh, &QTimer::timeout, this, &InputSettings::QueryDevices); + refresh.start(1000); + + devices->addItem("Keyboard/Mouse"); + deviceComboBoxLayout->addWidget(devicesLabel.get()); + deviceComboBoxLayout->addWidget(devices.get()); + mainLayout->addLayout(deviceComboBoxLayout.get()); + + AB->addWidget(buttonLabels[0].get()); + AB->addWidget(kbButtons[0].get()); + AB->addWidget(buttonLabels[1].get()); + AB->addWidget(kbButtons[1].get()); mainLayout->addLayout(AB.get()); - ZStart->addWidget(n64_button_labels[2].get()); - ZStart->addWidget(kb_buttons[2].get()); - ZStart->addWidget(n64_button_labels[3].get()); - ZStart->addWidget(kb_buttons[3].get()); + ZStart->addWidget(buttonLabels[2].get()); + ZStart->addWidget(kbButtons[2].get()); + ZStart->addWidget(buttonLabels[3].get()); + ZStart->addWidget(kbButtons[3].get()); mainLayout->addLayout(ZStart.get()); - LR->addWidget(n64_button_labels[4].get()); - LR->addWidget(kb_buttons[4].get()); - LR->addWidget(n64_button_labels[5].get()); - LR->addWidget(kb_buttons[5].get()); + LR->addWidget(buttonLabels[4].get()); + LR->addWidget(kbButtons[4].get()); + LR->addWidget(buttonLabels[5].get()); + LR->addWidget(kbButtons[5].get()); mainLayout->addLayout(LR.get()); - DupDdown->addWidget(n64_button_labels[6].get()); - DupDdown->addWidget(kb_buttons[6].get()); - DupDdown->addWidget(n64_button_labels[7].get()); - DupDdown->addWidget(kb_buttons[7].get()); + DupDdown->addWidget(buttonLabels[6].get()); + DupDdown->addWidget(kbButtons[6].get()); + DupDdown->addWidget(buttonLabels[7].get()); + DupDdown->addWidget(kbButtons[7].get()); mainLayout->addLayout(DupDdown.get()); - DleftDright->addWidget(n64_button_labels[8].get()); - DleftDright->addWidget(kb_buttons[8].get()); - DleftDright->addWidget(n64_button_labels[9].get()); - DleftDright->addWidget(kb_buttons[9].get()); + DleftDright->addWidget(buttonLabels[8].get()); + DleftDright->addWidget(kbButtons[8].get()); + DleftDright->addWidget(buttonLabels[9].get()); + DleftDright->addWidget(kbButtons[9].get()); mainLayout->addLayout(DleftDright.get()); - CupCdown->addWidget(n64_button_labels[10].get()); - CupCdown->addWidget(kb_buttons[10].get()); - CupCdown->addWidget(n64_button_labels[11].get()); - CupCdown->addWidget(kb_buttons[11].get()); + CupCdown->addWidget(buttonLabels[10].get()); + CupCdown->addWidget(kbButtons[10].get()); + CupCdown->addWidget(buttonLabels[11].get()); + CupCdown->addWidget(kbButtons[11].get()); mainLayout->addLayout(CupCdown.get()); - CleftCright->addWidget(n64_button_labels[12].get()); - CleftCright->addWidget(kb_buttons[12].get()); - CleftCright->addWidget(n64_button_labels[13].get()); - CleftCright->addWidget(kb_buttons[13].get()); + CleftCright->addWidget(buttonLabels[12].get()); + CleftCright->addWidget(kbButtons[12].get()); + CleftCright->addWidget(buttonLabels[13].get()); + CleftCright->addWidget(kbButtons[13].get()); mainLayout->addLayout(CleftCright.get()); - AupAdown->addWidget(n64_button_labels[14].get()); - AupAdown->addWidget(kb_buttons[14].get()); - AupAdown->addWidget(n64_button_labels[15].get()); - AupAdown->addWidget(kb_buttons[15].get()); + AupAdown->addWidget(buttonLabels[14].get()); + AupAdown->addWidget(kbButtons[14].get()); + AupAdown->addWidget(buttonLabels[15].get()); + AupAdown->addWidget(kbButtons[15].get()); mainLayout->addLayout(AupAdown.get()); - AleftAright->addWidget(n64_button_labels[16].get()); - AleftAright->addWidget(kb_buttons[16].get()); - AleftAright->addWidget(n64_button_labels[17].get()); - AleftAright->addWidget(kb_buttons[17].get()); + AleftAright->addWidget(buttonLabels[16].get()); + AleftAright->addWidget(kbButtons[16].get()); + AleftAright->addWidget(buttonLabels[17].get()); + AleftAright->addWidget(kbButtons[17].get()); mainLayout->addLayout(AleftAright.get()); mainLayout->addStretch(); setLayout(mainLayout.get()); @@ -121,14 +138,14 @@ InputSettings::InputSettings(nlohmann::json &settings) : QWidget(nullptr), setti void InputSettings::keyPressEvent(QKeyEvent *e) { if (grabbing) { - auto k = QKeySequence(e->key()).toString(); - JSONSetField(settings, "input", n64_button_labels[which_grabbing]->text().toStdString(), - k.toStdString()); - kb_buttons[which_grabbing]->setText(k); + const auto k = QKeySequence(e->key()).toString(); + JSONSetField(settings, "input", buttonLabels[whichGrabbing]->text().toStdString(), k.toStdString()); + kbButtons[whichGrabbing]->setText(k); grabbing = false; - which_grabbing = -1; - for (auto& kb_button : kb_buttons) { - kb_button->setEnabled(true); + whichGrabbing = -1; + devices->setEnabled(true); + for (const auto &kbButton : kbButtons) { + kbButton->setEnabled(true); } releaseKeyboard(); emit modified(); @@ -139,8 +156,57 @@ std::array InputSettings::GetMappedKeys() const { std::array ret{}; for (int i = 0; i < 18; i++) { - ret[i] = QKeySequence(kb_buttons[i]->text().toUpper())[0].key(); + ret[i] = QKeySequence(kbButtons[i]->text().toUpper())[0].key(); } return ret; } + +void InputSettings::QueryDevices() noexcept { + if (!devices->isEnabled()) + return; + + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_EVENT_GAMEPAD_ADDED: + { + const auto index = e.gdevice.which; + + const auto gamepad = SDL_OpenGamepad(index); + Util::info("Found controller!"); + const auto serial = SDL_GetGamepadSerial(gamepad); + const auto name = SDL_GetGamepadName(gamepad); + const auto path = SDL_GetGamepadPath(gamepad); + + if (name) { + if (!gamepadIndexes.contains(index)) { + gamepadIndexes[index] = name; + } + devices->addItem(name); + } else if (serial) { + if (!gamepadIndexes.contains(index)) { + gamepadIndexes[index] = serial; + } + devices->addItem(serial); + } else if (path) { + if (!gamepadIndexes.contains(index)) { + gamepadIndexes[index] = path; + } + devices->addItem(path); + } + + SDL_CloseGamepad(gamepad); + } + break; + case SDL_EVENT_GAMEPAD_REMOVED: + { + const auto index = e.gdevice.which; + + if (gamepadIndexes.contains(index)) + devices->removeItem(devices->findText(gamepadIndexes[index].c_str())); + } + break; + } + } +} diff --git a/src/frontend/InputSettings.hpp b/src/frontend/InputSettings.hpp index 29b76e88..aaca1ec4 100644 --- a/src/frontend/InputSettings.hpp +++ b/src/frontend/InputSettings.hpp @@ -5,10 +5,16 @@ #include #include #include +#include +#include class InputSettings : public QWidget { bool grabbing = false; - int which_grabbing = -1; + int whichGrabbing = -1; + + void QueryDevices() noexcept; + + std::unordered_map gamepadIndexes{}; std::unique_ptr AB = std::make_unique(); std::unique_ptr ZStart = std::make_unique(); @@ -20,8 +26,12 @@ class InputSettings : public QWidget { std::unique_ptr AupAdown = std::make_unique(); std::unique_ptr AleftAright = std::make_unique(); std::unique_ptr mainLayout = std::make_unique(); - std::array, 18> kb_buttons; - std::array, 18> n64_button_labels; + std::array, 18> kbButtons; + std::array, 18> buttonLabels; + std::unique_ptr deviceComboBoxLayout = std::make_unique(); + QTimer refresh; + std::unique_ptr devicesLabel = std::make_unique("Device:"); + std::unique_ptr devices = std::make_unique(); Q_OBJECT public: InputSettings(nlohmann::json &); diff --git a/src/frontend/JSONUtils.hpp b/src/frontend/JSONUtils.hpp index 79e3c332..f4fd79db 100644 --- a/src/frontend/JSONUtils.hpp +++ b/src/frontend/JSONUtils.hpp @@ -2,60 +2,62 @@ #include #include #include +#include namespace fs = std::filesystem; -static inline nlohmann::json JSONOpenOrCreate(const std::string &path) { +static FORCE_INLINE nlohmann::json JSONOpenOrCreate(const std::string &path) { auto fileExists = fs::exists(path); if (fileExists) { auto file = std::fstream(path, std::fstream::in | std::fstream::out); auto json = nlohmann::json::parse(file); file.close(); - return json; - } else { - auto file = std::fstream(path, std::fstream::in | std::fstream::out | std::fstream::trunc); - nlohmann::json json; - json["general"]["savePath"] = ""; - json["audio"]["volumeL"] = 0.5; - json["audio"]["volumeR"] = 0.5; - json["audio"]["lock"] = true; - json["cpu"]["type"] = "interpreter"; - json["input"] = { - {"A", "X"}, - {"B", "C"}, - {"Z", "Z"}, - {"Start", "Return"}, - {"L", "A"}, - {"R", "S"}, - {"Dpad Up", ""}, - {"Dpad Down", ""}, - {"Dpad Left", ""}, - {"Dpad Right", ""}, - {"C Up", "I"}, - {"C Down", "K"}, - {"C Left", "J"}, - {"C Right", "L"}, - {"Analog Up", "Up"}, - {"Analog Down", "Down"}, - {"Analog Left", "Left"}, - {"Analog Right", "Right"}, - }; - - file << json; - file.close(); - return json; } + + auto file = std::fstream(path, std::fstream::in | std::fstream::out | std::fstream::trunc); + nlohmann::json json; + json["general"]["savePath"] = ""; + json["audio"]["volumeL"] = 0.5; + json["audio"]["volumeR"] = 0.5; + json["audio"]["lock"] = true; + json["cpu"]["type"] = "interpreter"; + json["input"] = { + {"Device", "Keyboard/Mouse"}, + {"A", "X"}, + {"B", "C"}, + {"Z", "Z"}, + {"Start", "Return"}, + {"L", "A"}, + {"R", "S"}, + {"Dpad Up", ""}, + {"Dpad Down", ""}, + {"Dpad Left", ""}, + {"Dpad Right", ""}, + {"C Up", "I"}, + {"C Down", "K"}, + {"C Left", "J"}, + {"C Right", "L"}, + {"Analog Up", "Up"}, + {"Analog Down", "Down"}, + {"Analog Left", "Left"}, + {"Analog Right", "Right"}, + }; + + file << json; + file.close(); + + return json; } template -static inline void JSONSetField(nlohmann::json &json, const std::string &field1, const std::string &field2, - const T &value) { +static FORCE_INLINE void JSONSetField(nlohmann::json &json, const std::string &field1, const std::string &field2, + const T &value) { json[field1][field2] = value; } template -static inline T JSONGetField(nlohmann::json &json, const std::string &field1, const std::string &field2) { +static FORCE_INLINE T JSONGetField(nlohmann::json &json, const std::string &field1, const std::string &field2) { return json[field1][field2].get(); } diff --git a/src/frontend/RenderWidget.cpp b/src/frontend/RenderWidget.cpp index 33f1d28e..c1debbb7 100644 --- a/src/frontend/RenderWidget.cpp +++ b/src/frontend/RenderWidget.cpp @@ -29,34 +29,6 @@ RenderWidget::RenderWidget(const std::shared_ptr &core) : QWidget(nul } void QtWSIPlatform::poll_input() { - SDL_Event e; - while (SDL_PollEvent(&e)) { - switch (e.type) { - case SDL_EVENT_GAMEPAD_ADDED: - { - const auto index = e.gdevice.which; - gamepad = SDL_OpenGamepad(index); - Util::info("Found controller!"); - const auto serial = SDL_GetGamepadSerial(gamepad); - const auto name = SDL_GetGamepadName(gamepad); - const auto path = SDL_GetGamepadPath(gamepad); - Util::info("\tName: {}", name ? name : "Not available"); - Util::info("\tSerial: {}", serial ? serial : "Not available"); - Util::info("\tPath: {}", path ? path : "Not available"); - gamepadConnected = true; - } - break; - case SDL_EVENT_GAMEPAD_REMOVED: - { - gamepadConnected = false; - SDL_CloseGamepad(gamepad); - } - break; - default: - break; - } - } - if (gamepadConnected) { n64::PIF &pif = core->cpu->GetMem().mmio.si.pif; pif.UpdateButton(0, n64::Controller::Key::A, SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH));