From 2d0b22761e61041de82340db44fa5fe5f76a2148 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Fri, 18 Jul 2025 03:16:28 +0300 Subject: [PATCH] More keybinding work Co-Authored-By: Paris Oplopoios --- include/input_mappings.hpp | 31 ++++++++++--------- include/panda_qt/main_window.hpp | 7 ++++- src/panda_qt/input_window.cpp | 51 ++++++++++++++++++++++++++++---- src/panda_qt/main.cpp | 1 - src/panda_qt/main_window.cpp | 29 ++++++++++++++++-- src/panda_qt/mappings.cpp | 7 +---- src/panda_sdl/mappings.cpp | 7 +---- 7 files changed, 97 insertions(+), 36 deletions(-) diff --git a/include/input_mappings.hpp b/include/input_mappings.hpp index fb78eb7a..a17cb1de 100644 --- a/include/input_mappings.hpp +++ b/include/input_mappings.hpp @@ -21,7 +21,8 @@ struct InputMappings { void setMapping(Scancode scancode, u32 key) { container[scancode] = key; } - void serialize(const std::filesystem::path& path, const std::string& frontend) const { + template + void serialize(const std::filesystem::path& path, const std::string& frontend, ScancodeToString scancodeToString) const { toml::basic_value data; std::error_code error; @@ -46,18 +47,21 @@ struct InputMappings { data["Mappings"] = toml::table{}; for (const auto& [scancode, key] : container) { - if (!data["Mappings"].contains(HID::Keys::keyToName(key))) { - data["Mappings"][HID::Keys::keyToName(key)] = toml::array{}; + const std::string& keyName = HID::Keys::keyToName(key); + if (!data["Mappings"].contains(keyName)) { + data["Mappings"][keyName] = toml::array{}; } - - data["Mappings"][HID::Keys::keyToName(key)].push_back(scancodeToName(scancode)); + data["Mappings"][keyName].push_back(scancodeToString(scancode)); } std::ofstream file(path, std::ios::out); file << data; } - static std::optional deserialize(const std::filesystem::path& path, const std::string& wantFrontend) { + template + static std::optional deserialize( + const std::filesystem::path& path, const std::string& wantFrontend, ScancodeFromString stringToScancode + ) { toml::basic_value data; std::error_code error; @@ -84,23 +88,24 @@ struct InputMappings { return std::tolower(a) == std::tolower(b); }); - if (equal) { + if (!equal) { Helpers::warn( "Mappings file %s was created for frontend %s, but we are using frontend %s\n", path.string().c_str(), haveFrontend.c_str(), wantFrontend.c_str() ); + return std::nullopt; } } catch (const std::exception& ex) { Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what()); - return std::nullopt; } const auto& mappingsTable = toml::find_or(data, "Mappings", toml::table{}); - for (const auto& [key, scancodes] : mappingsTable) { - for (const auto& scancode : scancodes.as_array()) { - mappings.setMapping(nameToScancode(scancode.as_string()), HID::Keys::nameToKey(key)); + for (const auto& [keyName, scancodes] : mappingsTable) { + for (const auto& scancodeVal : scancodes.as_array()) { + std::string scancodeStr = scancodeVal.as_string(); + mappings.setMapping(stringToScancode(scancodeStr), HID::Keys::nameToKey(keyName)); } } @@ -108,8 +113,6 @@ struct InputMappings { } static InputMappings defaultKeyboardMappings(); - static std::string scancodeToName(Scancode scancode); - static Scancode nameToScancode(const std::string& name); auto begin() { return container.begin(); } auto end() { return container.end(); } @@ -121,4 +124,4 @@ struct InputMappings { Container container; std::string name; std::string device; -}; \ No newline at end of file +}; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 80b4a7f1..7bdf6b96 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -43,7 +43,6 @@ class MainWindow : public QMainWindow { Pause, Resume, TogglePause, - DumpRomFS, PressKey, ReleaseKey, SetCirclePadX, @@ -134,6 +133,9 @@ class MainWindow : public QMainWindow { void dispatchMessage(const EmulatorMessage& message); void loadTranslation(); + void loadKeybindings(); + void saveKeybindings(); + // Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer bool usingGL = false; bool usingVk = false; @@ -145,6 +147,9 @@ class MainWindow : public QMainWindow { bool keyboardAnalogX = false; bool keyboardAnalogY = false; + // Tracks if keybindings changed, in which case we should update the keybindings file when closing the emulator + bool keybindingsChanged = false; + public: MainWindow(QApplication* app, QWidget* parent = nullptr); ~MainWindow(); diff --git a/src/panda_qt/input_window.cpp b/src/panda_qt/input_window.cpp index f0cedffc..99d5a58e 100644 --- a/src/panda_qt/input_window.cpp +++ b/src/panda_qt/input_window.cpp @@ -3,31 +3,60 @@ #include #include #include +#include #include +#include "input_mappings.hpp" #include "services/hid.hpp" InputWindow::InputWindow(QWidget* parent) : QDialog(parent) { auto mainLayout = new QVBoxLayout(this); QStringList actions = { - "A", "B", "X", "Y", "L", "R", "ZL", "ZR", "Start", "Select", "D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right", + "A", + "B", + "X", + "Y", + "L", + "R", + "ZL", + "ZR", + "Start", + "Select", + "D-Pad Up", + "D-Pad Down", + "D-Pad Left", + "D-Pad Right", + "CirclePad Up", + "CirclePad Down", + "CirclePad Left", + "CirclePad Right", }; for (const QString& action : actions) { - auto row = new QHBoxLayout; + auto row = new QHBoxLayout(); row->addWidget(new QLabel(action)); - auto button = new QPushButton("Not set"); + auto button = new QPushButton(tr("Not set")); buttonMap[action] = button; keyMappings[action] = QKeySequence(); - connect(button, &QPushButton::clicked, this, [=]() { startKeyCapture(action); }); + connect(button, &QPushButton::clicked, this, [=, this]() { startKeyCapture(action); }); row->addWidget(button); mainLayout->addLayout(row); } + auto resetButton = new QPushButton(tr("Reset Defaults")); + connect(resetButton, &QPushButton::pressed, this, [&]() { + // Restore the keymappings to the default ones for Qt + auto defaultMappings = InputMappings::defaultKeyboardMappings(); + loadFromMappings(defaultMappings); + + emit mappingsChanged(); + }); + + mainLayout->addWidget(resetButton); installEventFilter(this); } @@ -37,10 +66,20 @@ void InputWindow::startKeyCapture(const QString& action) { } bool InputWindow::eventFilter(QObject* obj, QEvent* event) { + // If we're waiting for a button to be inputted, handle the keypress if (!waitingForAction.isEmpty() && event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); QKeySequence key(keyEvent->key()); + // If this key is already bound to something else, unbind it + for (auto it = keyMappings.begin(); it != keyMappings.end(); ++it) { + if (it.key() != waitingForAction && it.value() == key) { + it.value() = QKeySequence(); + buttonMap[it.key()]->setText(tr("Not set")); + break; + } + } + keyMappings[waitingForAction] = key; buttonMap[waitingForAction]->setText(key.toString()); @@ -60,7 +99,7 @@ void InputWindow::loadFromMappings(const InputMappings& mappings) { for (const auto& [scancode, mappedKey] : mappings) { if (mappedKey == key) { - QKeySequence qkey(QString::fromStdString(InputMappings::scancodeToName(scancode))); + QKeySequence qkey(scancode); keyMappings[action] = qkey; buttonMap[action]->setText(qkey.toString()); break; @@ -77,7 +116,7 @@ void InputWindow::applyToMappings(InputMappings& mappings) const { const QKeySequence& qkey = keyMappings[action]; if (!qkey.isEmpty()) { - InputMappings::Scancode scancode = InputMappings::nameToScancode(qkey.toString().toStdString()); + InputMappings::Scancode scancode = qkey[0].key(); u32 key = HID::Keys::nameToKey(action.toStdString()); if (key != HID::Keys::Null) { diff --git a/src/panda_qt/main.cpp b/src/panda_qt/main.cpp index 4ab737b0..0574ac68 100644 --- a/src/panda_qt/main.cpp +++ b/src/panda_qt/main.cpp @@ -1,7 +1,6 @@ #include #include "panda_qt/main_window.hpp" -#include "panda_qt/screen.hpp" int main(int argc, char *argv[]) { QApplication app(argc, argv); diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index d2c782d3..57d37d59 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -14,7 +14,7 @@ #include "services/dsp.hpp" #include "version.hpp" -MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) { +MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings()) { emu = new Emulator(); loadTranslation(); @@ -115,9 +115,10 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) [&]() { return this; }, emu->getConfig(), this ); - configWindow->getInputWindow()->loadFromMappings(keyboardMappings); + loadKeybindings(); connect(configWindow->getInputWindow(), &InputWindow::mappingsChanged, this, [&]() { + keybindingsChanged = true; configWindow->getInputWindow()->applyToMappings(keyboardMappings); }); @@ -280,6 +281,10 @@ void MainWindow::closeEvent(QCloseEvent* event) { // Cleanup when the main window closes MainWindow::~MainWindow() { + if (keybindingsChanged) { + saveKeybindings(); + } + delete emu; delete menuBar; delete aboutWindow; @@ -772,3 +777,23 @@ void MainWindow::setupControllerSensors(SDL_GameController* controller) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); } } + +void MainWindow::loadKeybindings() { + auto keyMappings = InputMappings::deserialize(emu->getAppDataRoot() / "controls_qt.toml", "Qt", [](const std::string& name) { + return InputMappings::Scancode(QKeySequence(QString::fromStdString(name))[0].key()); + }); + + if (keyMappings) { + keyboardMappings = *keyMappings; + } else { + keyboardMappings = InputMappings::defaultKeyboardMappings(); + } + + configWindow->getInputWindow()->loadFromMappings(keyboardMappings); +} + +void MainWindow::saveKeybindings() { + keyboardMappings.serialize(emu->getAppDataRoot() / "controls_qt.toml", "Qt", [](InputMappings::Scancode scancode) { + return QKeySequence(scancode).toString().toStdString(); + }); +} diff --git a/src/panda_qt/mappings.cpp b/src/panda_qt/mappings.cpp index 97a3217c..b41debee 100644 --- a/src/panda_qt/mappings.cpp +++ b/src/panda_qt/mappings.cpp @@ -1,6 +1,5 @@ #include #include -#include #include "input_mappings.hpp" @@ -26,8 +25,4 @@ InputMappings InputMappings::defaultKeyboardMappings() { mappings.setMapping(Qt::Key_A, HID::Keys::CirclePadLeft); return mappings; -} - -std::string InputMappings::scancodeToName(Scancode scancode) { return QKeySequence(scancode).toString().toStdString(); } - -InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return QKeySequence(QString::fromStdString(name))[0].key(); } +} \ No newline at end of file diff --git a/src/panda_sdl/mappings.cpp b/src/panda_sdl/mappings.cpp index 5baa1a48..7e982978 100644 --- a/src/panda_sdl/mappings.cpp +++ b/src/panda_sdl/mappings.cpp @@ -1,7 +1,5 @@ #include -#include - #include "input_mappings.hpp" InputMappings InputMappings::defaultKeyboardMappings() { @@ -26,7 +24,4 @@ InputMappings InputMappings::defaultKeyboardMappings() { mappings.setMapping(SDLK_a, HID::Keys::CirclePadLeft); return mappings; -} - -std::string InputMappings::scancodeToName(Scancode scancode) { return SDL_GetKeyName(scancode); } -InputMappings::Scancode InputMappings::nameToScancode(const std::string& name) { return SDL_GetKeyFromName(name.c_str()); } +} \ No newline at end of file