From 221eb2881521c67dd4d32c38b24383767c9f1697 Mon Sep 17 00:00:00 2001 From: VasylBaran Date: Sat, 20 Jul 2024 14:16:22 +0300 Subject: [PATCH] Add UI to configure keyboard-to-controller mapping --- .github/workflows/linux-qt.yml | 8 +- .reuse/dep5 | 1 + CMakeLists.txt | 6 + src/common/config.cpp | 46 ++ src/common/config.h | 5 + src/emulator.h | 3 +- src/images/PS4_controller_scheme_final.svg | 100 ++++ src/images/keyboard_icon.png | Bin 0 -> 4297 bytes src/input/keys_constants.h | 30 ++ src/input/keysmappingprovider.cpp | 16 + src/input/keysmappingprovider.h | 21 + src/qt_gui/keyboardcontrolswindow.cpp | 524 +++++++++++++++++++++ src/qt_gui/keyboardcontrolswindow.h | 40 ++ src/qt_gui/keyboardcontrolswindow.ui | 427 +++++++++++++++++ src/qt_gui/main_window.cpp | 14 + src/qt_gui/main_window.h | 5 + src/qt_gui/main_window_ui.h | 5 + src/sdl_window.cpp | 468 +++++++++--------- src/sdl_window.h | 28 +- src/shadps4.qrc | 2 + 20 files changed, 1530 insertions(+), 219 deletions(-) create mode 100644 src/images/PS4_controller_scheme_final.svg create mode 100644 src/images/keyboard_icon.png create mode 100644 src/input/keys_constants.h create mode 100644 src/input/keysmappingprovider.cpp create mode 100644 src/input/keysmappingprovider.h create mode 100644 src/qt_gui/keyboardcontrolswindow.cpp create mode 100644 src/qt_gui/keyboardcontrolswindow.h create mode 100644 src/qt_gui/keyboardcontrolswindow.ui diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml index 5611ae50..7b895424 100644 --- a/.github/workflows/linux-qt.yml +++ b/.github/workflows/linux-qt.yml @@ -23,7 +23,13 @@ jobs: - name: Install misc packages run: > - sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev + sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential + + - name: Setup Qt + uses: jurplel/install-qt-action@v4 + with: + arch: linux_gcc_64 + version: 6.7.1 - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_QT_GUI=ON diff --git a/.reuse/dep5 b/.reuse/dep5 index 0140c0c0..a46ed270 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -17,6 +17,7 @@ Files: CMakeSettings.json scripts/ps4_names.txt src/images/about_icon.png src/images/controller_icon.png + src/images/keyboard_icon.png src/images/exit_icon.png src/images/file_icon.png src/images/flag_china.png diff --git a/CMakeLists.txt b/CMakeLists.txt index edd67015..eba133d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -542,6 +542,9 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp set(INPUT src/input/controller.cpp src/input/controller.h + src/input/keysmappingprovider.h + src/input/keysmappingprovider.cpp + src/input/keys_constants.h ) set(EMULATOR src/emulator.cpp @@ -582,6 +585,9 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/settings_dialog.cpp src/qt_gui/settings_dialog.h src/qt_gui/settings_dialog.ui + src/qt_gui/keyboardcontrolswindow.h + src/qt_gui/keyboardcontrolswindow.cpp + src/qt_gui/keyboardcontrolswindow.ui src/qt_gui/main.cpp ${EMULATOR} ${RESOURCE_FILES} diff --git a/src/common/config.cpp b/src/common/config.cpp index 24db6b03..e12c7aea 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -48,6 +48,7 @@ std::vector m_elf_viewer; std::vector m_recent_files; // Settings u32 m_language = 1; // english +std::map m_keyboard_binding_map; bool isLleLibc() { return isLibc; @@ -247,6 +248,14 @@ void setRecentFiles(std::vector recentFiles) { m_recent_files = recentFiles; } +void setKeyboardBindingMap(std::map map) { + m_keyboard_binding_map = map; +} + +std::map getKeyboardBindingMap() { + return m_keyboard_binding_map; +} + u32 getMainWindowGeometryX() { return main_window_geometry_x; } @@ -386,6 +395,34 @@ void load(const std::filesystem::path& path) { m_language = toml::find_or(settings, "consoleLanguage", 1); } + + if (data.contains("Controls")) { + auto controls = toml::find(data, "Controls"); + + toml::table keyboardBindings{}; + auto it = controls.find("keyboardBindings"); + if (it != controls.end() && it->second.is_table()) { + keyboardBindings = it->second.as_table(); + } + + // Convert TOML table to std::map + for (const auto& [key, value] : keyboardBindings) { + try { + Uint32 int_key = static_cast(std::stoll(key)); + if (value.is_integer()) { + // Convert the TOML integer value to KeysMapping (int) + int int_value = value.as_integer(); + + // Add to the map + m_keyboard_binding_map[int_key] = static_cast(int_value); + } else { + fmt::print("Unexpected type for value: expected integer, got other type\n"); + } + } catch (const std::exception& e) { + fmt::print("Error processing key-value pair: {}\n", e.what()); + } + } + } } void save(const std::filesystem::path& path) { toml::value data; @@ -443,6 +480,15 @@ void save(const std::filesystem::path& path) { data["GUI"]["elfDirs"] = m_elf_viewer; data["GUI"]["recentFiles"] = m_recent_files; + // Create a TOML table with keyboard bindings + toml::table keyboardBindingsTable; + // Serialize the map to the TOML table + for (const auto& [key, value] : m_keyboard_binding_map) { + keyboardBindingsTable[std::to_string(key)] = static_cast(value); + } + + data["Controls"]["keyboardBindings"] = keyboardBindingsTable; + data["Settings"]["consoleLanguage"] = m_language; std::ofstream file(path, std::ios::out); diff --git a/src/common/config.h b/src/common/config.h index 3006f2e2..3dc75667 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -4,7 +4,10 @@ #pragma once #include +#include #include +#include "SDL3/SDL_stdinc.h" +#include "input/keys_constants.h" #include "types.h" namespace Config { @@ -70,6 +73,8 @@ void setMainWindowHeight(u32 height); void setPkgViewer(std::vector pkgList); void setElfViewer(std::vector elfList); void setRecentFiles(std::vector recentFiles); +void setKeyboardBindingMap(std::map map); +std::map getKeyboardBindingMap(); u32 getMainWindowGeometryX(); u32 getMainWindowGeometryY(); diff --git a/src/emulator.h b/src/emulator.h index 01bce7e7..307f2a49 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -9,6 +9,7 @@ #include "common/singleton.h" #include "core/linker.h" #include "input/controller.h" +#include "input/keysmappingprovider.h" #include "sdl_window.h" namespace Core { @@ -34,6 +35,6 @@ private: Input::GameController* controller; Core::Linker* linker; std::unique_ptr window; + std::unique_ptr m_keysMappingProvider; }; - } // namespace Core diff --git a/src/images/PS4_controller_scheme_final.svg b/src/images/PS4_controller_scheme_final.svg new file mode 100644 index 00000000..475dc22c --- /dev/null +++ b/src/images/PS4_controller_scheme_final.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/keyboard_icon.png b/src/images/keyboard_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..facf8ef8f82a8433bae5f370171ac90c52b06d62 GIT binary patch literal 4297 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 bindingsMap) + : m_bindingsMap{bindingsMap} {} + +std::optional KeysMappingProvider::mapKey(SDL_Keycode sdkKey) { + auto foundIt = m_bindingsMap.find(sdkKey); + if (foundIt != m_bindingsMap.end()) { + return foundIt->second; + } else { + return {}; + } +} diff --git a/src/input/keysmappingprovider.h b/src/input/keysmappingprovider.h new file mode 100644 index 00000000..de1a8c65 --- /dev/null +++ b/src/input/keysmappingprovider.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include +#include + +#include "input/keys_constants.h" + +class KeysMappingProvider { +public: + KeysMappingProvider(std::map bindingsMap); + + std::optional mapKey(SDL_Keycode sdkKey); + +private: + std::map m_bindingsMap; +}; \ No newline at end of file diff --git a/src/qt_gui/keyboardcontrolswindow.cpp b/src/qt_gui/keyboardcontrolswindow.cpp new file mode 100644 index 00000000..f539cb9b --- /dev/null +++ b/src/qt_gui/keyboardcontrolswindow.cpp @@ -0,0 +1,524 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "./ui_keyboardcontrolswindow.h" +#include "common/config.h" +#include "keyboardcontrolswindow.h" + +#include +#include + +#include + +namespace { +static constexpr auto keyBindingsSettingsKey = "ShadPS4_Keyboard_Settings_KEY"; +static constexpr auto inputErrorTimerTimeout = 2000; +} // namespace + +void showError(const QString& message) { + QMessageBox::critical(nullptr, "Error", message, QMessageBox::Ok); +} + +void showWarning(const QString& message) { + QMessageBox::warning(nullptr, "Warning", message, QMessageBox::Ok); +} + +void showInfo(const QString& message) { + QMessageBox::information(nullptr, "Info", message, QMessageBox::Ok); +} + +KeyboardControlsWindow::KeyboardControlsWindow(QWidget* parent) + : QDialog(parent), ui(new Ui::KeyboardControlsWindow) { + ui->setupUi(this); + + m_keysMap = Config::getKeyboardBindingMap(); + + for (auto& pair : m_keysMap) { + m_reverseKeysMap.emplace(pair.second, pair.first); + } + + m_listOfKeySequenceEdits = {ui->StartKeySequenceEdit, ui->SelectKeySequenceEdit, + ui->LAnalogDownkeySequenceEdit, ui->LAnalogLeftkeySequenceEdit, + ui->LAnalogUpkeySequenceEdit, ui->LAnalogRightkeySequenceEdit, + ui->PSkeySequenceEdit, ui->RAnalogDownkeySequenceEdit, + ui->RAnalogLeftkeySequenceEdit, ui->RAnalogUpkeySequenceEdit, + ui->RAnalogRightkeySequenceEdit, ui->DPadLeftkeySequenceEdit, + ui->DPadRightkeySequenceEdit, ui->DPadUpkeySequenceEdit, + ui->DPadDownkeySequenceEdit, ui->L2keySequenceEdit, + ui->L1keySequenceEdit, ui->CrossKeySequenceEdit, + ui->R2KeySequenceEdit, ui->CircleKeySequenceEdit, + ui->R1KeySequenceEdit, ui->SquareKeySequenceEdit, + ui->TriangleKeySequenceEdit}; + + for (auto edit : m_listOfKeySequenceEdits) { + edit->setStyleSheet("QLineEdit { qproperty-alignment: AlignCenter; }"); + QObject::connect(edit, &QKeySequenceEdit::editingFinished, this, + &KeyboardControlsWindow::onEditingFinished); + } + + ui->StartKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Start_Key])); + ui->SelectKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Select_Key])); + ui->LAnalogDownkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::LAnalogDown_Key])); + ui->LAnalogLeftkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::LAnalogLeft_Key])); + ui->LAnalogUpkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::LAnalogUp_Key])); + ui->LAnalogRightkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::LAnalogRight_Key])); + ui->PSkeySequenceEdit->setKeySequence(convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::PS_Key])); + ui->RAnalogDownkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::RAnalogDown_Key])); + ui->RAnalogLeftkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::RAnalogLeft_Key])); + ui->RAnalogUpkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::RAnalogUp_Key])); + ui->RAnalogRightkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::RAnalogRight_Key])); + ui->DPadLeftkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::DPadLeft_Key])); + ui->DPadRightkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::DPadRight_Key])); + ui->DPadUpkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::DPadUp_Key])); + ui->DPadDownkeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::DPadDown_Key])); + ui->L2keySequenceEdit->setKeySequence(convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::L2_Key])); + ui->L1keySequenceEdit->setKeySequence(convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::L1_Key])); + ui->CrossKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Cross_Key])); + ui->R2KeySequenceEdit->setKeySequence(convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::R2_Key])); + ui->CircleKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Circle_Key])); + ui->R1KeySequenceEdit->setKeySequence(convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::R1_Key])); + ui->SquareKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Square_Key])); + ui->TriangleKeySequenceEdit->setKeySequence( + convertSDLKeyToQt(m_reverseKeysMap[KeysMapping::Triangle_Key])); + + QObject::connect(ui->applyButton, &QPushButton::clicked, + [this]() { validateAndSaveKeyBindings(); }); + + QObject::connect(ui->cancelButton, &QPushButton::clicked, [this]() { this->close(); }); +} + +KeyboardControlsWindow::~KeyboardControlsWindow() { + delete ui; +} + +const std::map& KeyboardControlsWindow::getKeysMapping() const { + return m_keysMap; +} + +void KeyboardControlsWindow::validateAndSaveKeyBindings() { + int nOfUnconfiguredButtons = 0; + for (auto& keyEdit : m_listOfKeySequenceEdits) { + auto keySequence = keyEdit->keySequence(); + // If key sequence is empty (i.e. there is no key assigned to it) we highlight it in red + if (keySequence.isEmpty()) { + keyEdit->setStyleSheet("background-color: red; qproperty-alignment: AlignCenter;"); + QTimer::singleShot(inputErrorTimerTimeout, keyEdit, [keyEdit]() { + keyEdit->setStyleSheet("qproperty-alignment: AlignCenter;"); // Reset to default + }); + + ++nOfUnconfiguredButtons; + } + } + + if (nOfUnconfiguredButtons > 0) { + showError("Some of the buttons were not configured"); + return; + } + + m_keysMap.clear(); + m_reverseKeysMap.clear(); + + m_keysMap.emplace(convertQtKeyToSDL(ui->LAnalogDownkeySequenceEdit->keySequence()[0].key()), + KeysMapping::LAnalogDown_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->LAnalogLeftkeySequenceEdit->keySequence()[0].key()), + KeysMapping::LAnalogLeft_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->LAnalogUpkeySequenceEdit->keySequence()[0].key()), + KeysMapping::LAnalogUp_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->LAnalogRightkeySequenceEdit->keySequence()[0].key()), + KeysMapping::LAnalogRight_Key); + + m_keysMap.emplace(convertQtKeyToSDL(ui->PSkeySequenceEdit->keySequence()[0].key()), + KeysMapping::PS_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->StartKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Start_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->SelectKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Select_Key); + + m_keysMap.emplace(convertQtKeyToSDL(ui->RAnalogDownkeySequenceEdit->keySequence()[0].key()), + KeysMapping::RAnalogDown_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->RAnalogLeftkeySequenceEdit->keySequence()[0].key()), + KeysMapping::RAnalogLeft_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->RAnalogUpkeySequenceEdit->keySequence()[0].key()), + KeysMapping::RAnalogUp_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->RAnalogRightkeySequenceEdit->keySequence()[0].key()), + KeysMapping::RAnalogRight_Key); + + m_keysMap.emplace(convertQtKeyToSDL(ui->DPadLeftkeySequenceEdit->keySequence()[0].key()), + KeysMapping::DPadLeft_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->DPadRightkeySequenceEdit->keySequence()[0].key()), + KeysMapping::DPadRight_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->DPadUpkeySequenceEdit->keySequence()[0].key()), + KeysMapping::DPadUp_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->DPadDownkeySequenceEdit->keySequence()[0].key()), + KeysMapping::DPadDown_Key); + + m_keysMap.emplace(convertQtKeyToSDL(ui->L1keySequenceEdit->keySequence()[0].key()), + KeysMapping::L1_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->L2keySequenceEdit->keySequence()[0].key()), + KeysMapping::L2_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->R1KeySequenceEdit->keySequence()[0].key()), + KeysMapping::R1_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->R2KeySequenceEdit->keySequence()[0].key()), + KeysMapping::R2_Key); + + m_keysMap.emplace(convertQtKeyToSDL(ui->CrossKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Cross_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->CircleKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Circle_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->SquareKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Square_Key); + m_keysMap.emplace(convertQtKeyToSDL(ui->TriangleKeySequenceEdit->keySequence()[0].key()), + KeysMapping::Triangle_Key); + + for (auto& pair : m_keysMap) { + m_reverseKeysMap.emplace(pair.second, pair.first); + } + + // Saving into settings (for permanent storage) + Config::setKeyboardBindingMap(m_keysMap); + + this->close(); +} + +Qt::Key KeyboardControlsWindow::convertSDLKeyToQt(SDL_Keycode sdlKey) { + switch (sdlKey) { + case SDLK_A: + return Qt::Key_A; + case SDLK_B: + return Qt::Key_B; + case SDLK_C: + return Qt::Key_C; + case SDLK_D: + return Qt::Key_D; + case SDLK_E: + return Qt::Key_E; + case SDLK_F: + return Qt::Key_F; + case SDLK_G: + return Qt::Key_G; + case SDLK_H: + return Qt::Key_H; + case SDLK_I: + return Qt::Key_I; + case SDLK_J: + return Qt::Key_J; + case SDLK_K: + return Qt::Key_K; + case SDLK_L: + return Qt::Key_L; + case SDLK_M: + return Qt::Key_M; + case SDLK_N: + return Qt::Key_N; + case SDLK_O: + return Qt::Key_O; + case SDLK_P: + return Qt::Key_P; + case SDLK_Q: + return Qt::Key_Q; + case SDLK_R: + return Qt::Key_R; + case SDLK_S: + return Qt::Key_S; + case SDLK_T: + return Qt::Key_T; + case SDLK_U: + return Qt::Key_U; + case SDLK_V: + return Qt::Key_V; + case SDLK_W: + return Qt::Key_W; + case SDLK_X: + return Qt::Key_X; + case SDLK_Y: + return Qt::Key_Y; + case SDLK_Z: + return Qt::Key_Z; + case SDLK_0: + return Qt::Key_0; + case SDLK_1: + return Qt::Key_1; + case SDLK_2: + return Qt::Key_2; + case SDLK_3: + return Qt::Key_3; + case SDLK_4: + return Qt::Key_4; + case SDLK_5: + return Qt::Key_5; + case SDLK_6: + return Qt::Key_6; + case SDLK_7: + return Qt::Key_7; + case SDLK_8: + return Qt::Key_8; + case SDLK_9: + return Qt::Key_9; + case SDLK_SPACE: + return Qt::Key_Space; + case SDLK_RETURN: + return Qt::Key_Return; + case SDLK_ESCAPE: + return Qt::Key_Escape; + case SDLK_TAB: + return Qt::Key_Tab; + case SDLK_BACKSPACE: + return Qt::Key_Backspace; + case SDLK_DELETE: + return Qt::Key_Delete; + case SDLK_INSERT: + return Qt::Key_Insert; + case SDLK_HOME: + return Qt::Key_Home; + case SDLK_END: + return Qt::Key_End; + case SDLK_PAGEUP: + return Qt::Key_PageUp; + case SDLK_PAGEDOWN: + return Qt::Key_PageDown; + case SDLK_LEFT: + return Qt::Key_Left; + case SDLK_RIGHT: + return Qt::Key_Right; + case SDLK_UP: + return Qt::Key_Up; + case SDLK_DOWN: + return Qt::Key_Down; + case SDLK_CAPSLOCK: + return Qt::Key_CapsLock; + case SDLK_NUMLOCKCLEAR: + return Qt::Key_NumLock; + case SDLK_SCROLLLOCK: + return Qt::Key_ScrollLock; + case SDLK_F1: + return Qt::Key_F1; + case SDLK_F2: + return Qt::Key_F2; + case SDLK_F3: + return Qt::Key_F3; + case SDLK_F4: + return Qt::Key_F4; + case SDLK_F5: + return Qt::Key_F5; + case SDLK_F6: + return Qt::Key_F6; + case SDLK_F7: + return Qt::Key_F7; + case SDLK_F8: + return Qt::Key_F8; + case SDLK_F9: + return Qt::Key_F9; + case SDLK_F10: + return Qt::Key_F10; + case SDLK_F11: + return Qt::Key_F11; + case SDLK_F12: + return Qt::Key_F12; + case SDLK_LSHIFT: + return Qt::Key_Shift; + case SDLK_LCTRL: + return Qt::Key_Control; + case SDLK_LALT: + return Qt::Key_Alt; + case SDLK_LGUI: + return Qt::Key_Meta; + default: + return Qt::Key_unknown; + } +} + +SDL_Keycode KeyboardControlsWindow::convertQtKeyToSDL(Qt::Key qtKey) { + switch (qtKey) { + case Qt::Key_A: + return SDLK_A; + case Qt::Key_B: + return SDLK_B; + case Qt::Key_C: + return SDLK_C; + case Qt::Key_D: + return SDLK_D; + case Qt::Key_E: + return SDLK_E; + case Qt::Key_F: + return SDLK_F; + case Qt::Key_G: + return SDLK_G; + case Qt::Key_H: + return SDLK_H; + case Qt::Key_I: + return SDLK_I; + case Qt::Key_J: + return SDLK_J; + case Qt::Key_K: + return SDLK_K; + case Qt::Key_L: + return SDLK_L; + case Qt::Key_M: + return SDLK_M; + case Qt::Key_N: + return SDLK_N; + case Qt::Key_O: + return SDLK_O; + case Qt::Key_P: + return SDLK_P; + case Qt::Key_Q: + return SDLK_Q; + case Qt::Key_R: + return SDLK_R; + case Qt::Key_S: + return SDLK_S; + case Qt::Key_T: + return SDLK_T; + case Qt::Key_U: + return SDLK_U; + case Qt::Key_V: + return SDLK_V; + case Qt::Key_W: + return SDLK_W; + case Qt::Key_X: + return SDLK_X; + case Qt::Key_Y: + return SDLK_Y; + case Qt::Key_Z: + return SDLK_Z; + case Qt::Key_0: + return SDLK_0; + case Qt::Key_1: + return SDLK_1; + case Qt::Key_2: + return SDLK_2; + case Qt::Key_3: + return SDLK_3; + case Qt::Key_4: + return SDLK_4; + case Qt::Key_5: + return SDLK_5; + case Qt::Key_6: + return SDLK_6; + case Qt::Key_7: + return SDLK_7; + case Qt::Key_8: + return SDLK_8; + case Qt::Key_9: + return SDLK_9; + case Qt::Key_Space: + return SDLK_SPACE; + case Qt::Key_Enter: + return SDLK_RETURN; + case Qt::Key_Return: + return SDLK_RETURN; + case Qt::Key_Escape: + return SDLK_ESCAPE; + case Qt::Key_Tab: + return SDLK_TAB; + case Qt::Key_Backspace: + return SDLK_BACKSPACE; + case Qt::Key_Delete: + return SDLK_DELETE; + case Qt::Key_Insert: + return SDLK_INSERT; + case Qt::Key_Home: + return SDLK_HOME; + case Qt::Key_End: + return SDLK_END; + case Qt::Key_PageUp: + return SDLK_PAGEUP; + case Qt::Key_PageDown: + return SDLK_PAGEDOWN; + case Qt::Key_Left: + return SDLK_LEFT; + case Qt::Key_Right: + return SDLK_RIGHT; + case Qt::Key_Up: + return SDLK_UP; + case Qt::Key_Down: + return SDLK_DOWN; + case Qt::Key_CapsLock: + return SDLK_CAPSLOCK; + case Qt::Key_NumLock: + return SDLK_NUMLOCKCLEAR; + case Qt::Key_ScrollLock: + return SDLK_SCROLLLOCK; + case Qt::Key_F1: + return SDLK_F1; + case Qt::Key_F2: + return SDLK_F2; + case Qt::Key_F3: + return SDLK_F3; + case Qt::Key_F4: + return SDLK_F4; + case Qt::Key_F5: + return SDLK_F5; + case Qt::Key_F6: + return SDLK_F6; + case Qt::Key_F7: + return SDLK_F7; + case Qt::Key_F8: + return SDLK_F8; + case Qt::Key_F9: + return SDLK_F9; + case Qt::Key_F10: + return SDLK_F10; + case Qt::Key_F11: + return SDLK_F11; + case Qt::Key_F12: + return SDLK_F12; + case Qt::Key_Shift: + return SDLK_LSHIFT; + case Qt::Key_Control: + return SDLK_LCTRL; + case Qt::Key_Alt: + return SDLK_LALT; + case Qt::Key_Meta: + return SDLK_LGUI; + default: + return SDLK_UNKNOWN; + } +} + +void KeyboardControlsWindow::onEditingFinished() { + auto sender = qobject_cast(QObject::sender()); + auto new_keySequence = sender->keySequence(); + + // If new key sequence is empty (i.e. there is no key assigned to it) - skip 'duplicate' checks + // Two checks are needed for the sake of robustness (when we click on a widget but don't type + // anything it might no longer be "empty") + if (new_keySequence.isEmpty() || new_keySequence.toString().isEmpty()) { + return; + } + + // Check if sequance is not already used (i.e. making sure there are not duplicates) + for (auto& keyEdit : m_listOfKeySequenceEdits) { + if (keyEdit != sender && new_keySequence == keyEdit->keySequence()) { + sender->clear(); + sender->setStyleSheet("background-color: red; qproperty-alignment: AlignCenter;"); + QTimer::singleShot(inputErrorTimerTimeout, sender, [sender]() { + sender->setStyleSheet( + "QLineEdit { qproperty-alignment: AlignCenter; }"); // Reset to default + }); + + keyEdit->setStyleSheet("background-color: red; qproperty-alignment: AlignCenter;"); + QTimer::singleShot(inputErrorTimerTimeout, keyEdit, [keyEdit]() { + keyEdit->setStyleSheet( + "QLineEdit { qproperty-alignment: AlignCenter; }"); // Reset to default + }); + } + } +} diff --git a/src/qt_gui/keyboardcontrolswindow.h b/src/qt_gui/keyboardcontrolswindow.h new file mode 100644 index 00000000..fadd61b3 --- /dev/null +++ b/src/qt_gui/keyboardcontrolswindow.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include +#include "input/keys_constants.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class KeyboardControlsWindow; +} +QT_END_NAMESPACE + +class KeyboardControlsWindow : public QDialog { + Q_OBJECT + +public: + KeyboardControlsWindow(QWidget* parent = nullptr); + ~KeyboardControlsWindow(); + + const std::map& getKeysMapping() const; + +private slots: + void onEditingFinished(); + +private: + void validateAndSaveKeyBindings(); + SDL_Keycode convertQtKeyToSDL(Qt::Key qtKey); + Qt::Key convertSDLKeyToQt(SDL_Keycode qtKey); + + Ui::KeyboardControlsWindow* ui; + QSet m_listOfKeySequenceEdits; + std::map m_keysMap; + std::map m_reverseKeysMap; +}; diff --git a/src/qt_gui/keyboardcontrolswindow.ui b/src/qt_gui/keyboardcontrolswindow.ui new file mode 100644 index 00000000..da0ecd1b --- /dev/null +++ b/src/qt_gui/keyboardcontrolswindow.ui @@ -0,0 +1,427 @@ + + + + KeyboardControlsWindow + + + + 0 + 0 + 1148 + 731 + + + + + 0 + 0 + + + + + 1148 + 731 + + + + + 1148 + 731 + + + + Configure Keyboard Bindings + + + true + + + + + 0 + 0 + 1151 + 722 + + + + + + 170 + 50 + 870 + 522 + + + + false + + + QWidget { + background-image: url(:/images/PS4_controller_scheme_final.svg); + background-repeat: no-repeat; + background-position: center; +} + + + + + 550 + 0 + 71 + 40 + + + + 1 + + + + + + 260 + 0 + 71 + 40 + + + + 1 + + + + + + 280 + 480 + 71 + 40 + + + + 1 + + + + + + 240 + 440 + 71 + 40 + + + + 1 + + + + + + 280 + 400 + 71 + 40 + + + + 1 + + + + + + 320 + 440 + 71 + 40 + + + + 1 + + + + + + 400 + 400 + 71 + 40 + + + + 1 + + + + + + 520 + 480 + 71 + 40 + + + + 1 + + + + + + 480 + 440 + 71 + 40 + + + + 1 + + + + + + 520 + 400 + 71 + 40 + + + + 1 + + + + + + 560 + 440 + 71 + 40 + + + + 1 + + + + + + + 10 + 230 + 71 + 40 + + + + 1 + + + + + + 90 + 230 + 71 + 40 + + + + 1 + + + + + + 50 + 190 + 71 + 40 + + + + 1 + + + + + + 50 + 270 + 71 + 40 + + + + 1 + + + + + + 90 + 40 + 71 + 40 + + + + 1 + + + + + + 90 + 110 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 380 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 30 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 240 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 100 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 310 + 71 + 40 + + + + 1 + + + + + true + + + + 1050 + 170 + 71 + 40 + + + + 1 + + + + + + 890 + 670 + 100 + 32 + + + + Cancel + + + + + + 1000 + 670 + 100 + 32 + + + + Apply + + + + + + + 0 + 0 + 20 + 22 + + + + + + + diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 34ef0d86..16d915a0 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -52,6 +52,7 @@ bool MainWindow::Init() { auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); statusBar.reset(new QStatusBar); + m_keyboardControlsDialog.reset(new KeyboardControlsWindow()); this->setStatusBar(statusBar.data()); // Update status bar int numGames = m_game_info->m_games.size(); @@ -92,6 +93,10 @@ void MainWindow::AddUiWidgets() { ui->toolBar->addWidget(ui->stopButton); ui->toolBar->addWidget(ui->settingsButton); ui->toolBar->addWidget(ui->controllerButton); + auto connection = QObject::connect(ui->keyboardButton, &QPushButton::clicked, this, + &MainWindow::KeyboardConfigurationButtonPressed); + + ui->toolBar->addWidget(ui->keyboardButton); QFrame* line = new QFrame(this); line->setFrameShape(QFrame::StyledPanel); line->setFrameShadow(QFrame::Sunken); @@ -100,6 +105,10 @@ void MainWindow::AddUiWidgets() { ui->toolBar->addWidget(ui->mw_searchbar); } +void MainWindow::KeyboardConfigurationButtonPressed() { + m_keyboardControlsDialog->show(); +} + void MainWindow::CreateDockWindows() { // place holder widget is needed for good health they say :) QWidget* phCentralWidget = new QWidget(this); @@ -650,6 +659,10 @@ void MainWindow::InstallDirectory() { RefreshGameTable(); } +std::map MainWindow::getKeysMapping() { + return m_keyboardControlsDialog->getKeysMapping(); +} + void MainWindow::SetLastUsedTheme() { Theme lastTheme = static_cast(Config::getMainWindowTheme()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); @@ -725,6 +738,7 @@ void MainWindow::SetUiIcons(bool isWhite) { ui->stopButton->setIcon(RecolorIcon(ui->stopButton->icon(), isWhite)); ui->settingsButton->setIcon(RecolorIcon(ui->settingsButton->icon(), isWhite)); ui->controllerButton->setIcon(RecolorIcon(ui->controllerButton->icon(), isWhite)); + ui->keyboardButton->setIcon(RecolorIcon(ui->keyboardButton->icon(), isWhite)); ui->refreshGameListAct->setIcon(RecolorIcon(ui->refreshGameListAct->icon(), isWhite)); ui->menuGame_List_Mode->setIcon(RecolorIcon(ui->menuGame_List_Mode->icon(), isWhite)); ui->pkgViewerAct->setIcon(RecolorIcon(ui->pkgViewerAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 24de15b9..74b66205 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -21,6 +21,7 @@ #include "game_info.h" #include "game_list_frame.h" #include "game_list_utils.h" +#include "keyboardcontrolswindow.h" #include "main_window_themes.h" #include "main_window_ui.h" #include "pkg_viewer.h" @@ -41,6 +42,8 @@ public: void InstallDirectory(); void StartGame(); + std::map getKeysMapping(); + private Q_SLOTS: void ConfigureGuiFromSettings(); void SaveWindowState() const; @@ -48,6 +51,7 @@ private Q_SLOTS: void ShowGameList(); void RefreshGameTable(); void HandleResize(QResizeEvent* event); + void KeyboardConfigurationButtonPressed(); private: Ui_MainWindow* ui; @@ -82,6 +86,7 @@ private: QScopedPointer m_elf_viewer; // Status Bar. QScopedPointer statusBar; + QScopedPointer m_keyboardControlsDialog; // Available GPU devices std::vector m_physical_devices; diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 7d0c58dd..c50524c3 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -59,6 +59,7 @@ public: QPushButton* stopButton; QPushButton* settingsButton; QPushButton* controllerButton; + QPushButton* keyboardButton; QWidget* sizeSliderContainer; QHBoxLayout* sizeSliderContainer_layout; @@ -202,6 +203,10 @@ public: controllerButton->setFlat(true); controllerButton->setIcon(QIcon(":images/controller_icon.png")); controllerButton->setIconSize(QSize(40, 40)); + keyboardButton = new QPushButton(centralWidget); + keyboardButton->setFlat(true); + keyboardButton->setIcon(QIcon(":images/keyboard_icon.png")); + keyboardButton->setIconSize(QSize(40, 40)); sizeSliderContainer = new QWidget(centralWidget); sizeSliderContainer->setObjectName("sizeSliderContainer"); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 9fd59669..8fa41f75 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -108,174 +108,165 @@ void WindowSDL::waitEvent() { } } +void WindowSDL::setKeysMappingProvider(KeysMappingProvider* provider) { + keysMappingProvider = provider; +} + void WindowSDL::onResize() { SDL_GetWindowSizeInPixels(window, &width, &height); } +using Libraries::Pad::OrbisPadButtonDataOffset; + void WindowSDL::onKeyPress(const SDL_Event* event) { - using Libraries::Pad::OrbisPadButtonDataOffset; - -#ifdef __APPLE__ - // Use keys that are more friendly for keyboards without a keypad. - // Once there are key binding options this won't be necessary. - constexpr SDL_Keycode CrossKey = SDLK_N; - constexpr SDL_Keycode CircleKey = SDLK_B; - constexpr SDL_Keycode SquareKey = SDLK_V; - constexpr SDL_Keycode TriangleKey = SDLK_C; -#else - constexpr SDL_Keycode CrossKey = SDLK_KP_2; - constexpr SDL_Keycode CircleKey = SDLK_KP_6; - constexpr SDL_Keycode SquareKey = SDLK_KP_4; - constexpr SDL_Keycode TriangleKey = SDLK_KP_8; -#endif - u32 button = 0; Input::Axis axis = Input::Axis::AxisMax; int axisvalue = 0; int ax = 0; - switch (event->key.key) { - case SDLK_UP: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; - break; - case SDLK_DOWN: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN; - break; - case SDLK_LEFT: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT; - break; - case SDLK_RIGHT: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT; - break; - case TriangleKey: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; - break; - case CircleKey: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; - break; - case CrossKey: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; - break; - case SquareKey: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; - break; - case SDLK_RETURN: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS; - break; - case SDLK_A: - axis = Input::Axis::LeftX; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += -127; - } else { - axisvalue = 0; + + bool keyHandlingPending = true; + if (keysMappingProvider != nullptr) { + auto ps4KeyOpt = keysMappingProvider->mapKey(event->key.key); + + // No support for modifiers (yet) + if (ps4KeyOpt.has_value() && (event->key.mod == SDL_KMOD_NONE)) { + keyHandlingPending = false; + + auto ps4Key = ps4KeyOpt.value(); + if (ps4Key == KeysMapping::Start_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS; + if (ps4Key == KeysMapping::Triangle_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; + if (ps4Key == KeysMapping::Circle_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; + if (ps4Key == KeysMapping::Cross_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; + if (ps4Key == KeysMapping::Square_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; + if (ps4Key == KeysMapping::R1_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1; + if (ps4Key == KeysMapping::R2_Key) + handleR2Key(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::DPadLeft_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT; + if (ps4Key == KeysMapping::DPadRight_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT; + if (ps4Key == KeysMapping::DPadDown_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN; + if (ps4Key == KeysMapping::DPadUp_Key) + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; + if (ps4Key == KeysMapping::LAnalogLeft_Key) + handleLAnalogLeftKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::LAnalogUp_Key) + handleLAnalogUpKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::LAnalogDown_Key) + handleLAnalogDownKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::RAnalogLeft_Key) + handleRAnalogLeftKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::RAnalogRight_Key) + handleRAnalogRightKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::RAnalogUp_Key) + handleRAnalogUpKey(event, button, axis, axisvalue, ax); + if (ps4Key == KeysMapping::RAnalogDown_Key) + handleRAnalogDownKey(event, button, axis, axisvalue, ax); } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_D: - axis = Input::Axis::LeftX; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_W: - axis = Input::Axis::LeftY; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += -127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_S: - if (event->key.mod == SDL_KMOD_LCTRL) { - // Trigger rdoc capture - VideoCore::TriggerCapture(); + } + + if (keyHandlingPending) { + switch (event->key.key) { + case SDLK_UP: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; + break; + case SDLK_DOWN: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN; + break; + case SDLK_LEFT: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT; + break; + case SDLK_RIGHT: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT; + break; + case Triangle_Key: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; + break; + case Circle_Key: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; + break; + case Cross_Key: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; + break; + case Square_Key: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; + break; + case SDLK_KP_8: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; + break; + case SDLK_KP_6: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; + break; + case SDLK_KP_2: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; + break; + case SDLK_KP_4: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; + break; + case SDLK_RETURN: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS; + break; + case SDLK_A: + handleLAnalogLeftKey(event, button, axis, axisvalue, ax); + break; + case SDLK_D: + handleLAnalogRightKey(event, button, axis, axisvalue, ax); + break; + case SDLK_W: + handleLAnalogUpKey(event, button, axis, axisvalue, ax); + break; + case SDLK_S: + handleLAnalogDownKey(event, button, axis, axisvalue, ax); + if (event->key.mod == SDL_KMOD_LCTRL) { + // Trigger rdoc capture + VideoCore::TriggerCapture(); + } + break; + case SDLK_J: + handleRAnalogLeftKey(event, button, axis, axisvalue, ax); + break; + case SDLK_L: + handleRAnalogRightKey(event, button, axis, axisvalue, ax); + break; + case SDLK_I: + handleRAnalogUpKey(event, button, axis, axisvalue, ax); + break; + case SDLK_K: + handleRAnalogDownKey(event, button, axis, axisvalue, ax); + break; + case SDLK_X: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3; + break; + case SDLK_M: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3; + break; + case SDLK_Q: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1; + break; + case SDLK_U: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1; + break; + case SDLK_E: + handleL2Key(event, button, axis, axisvalue, ax); + break; + case SDLK_O: + handleR2Key(event, button, axis, axisvalue, ax); + break; + case SDLK_SPACE: + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; + break; + default: break; } - axis = Input::Axis::LeftY; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_J: - axis = Input::Axis::RightX; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += -127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_L: - axis = Input::Axis::RightX; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_I: - axis = Input::Axis::RightY; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += -127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_K: - axis = Input::Axis::RightY; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 127; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(-0x80, 0x80, axisvalue); - break; - case SDLK_X: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3; - break; - case SDLK_M: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3; - break; - case SDLK_Q: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1; - break; - case SDLK_U: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1; - break; - case SDLK_E: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2; - axis = Input::Axis::TriggerLeft; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 255; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(0, 0x80, axisvalue); - break; - case SDLK_O: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2; - axis = Input::Axis::TriggerRight; - if (event->type == SDL_EVENT_KEY_DOWN) { - axisvalue += 255; - } else { - axisvalue = 0; - } - ax = Input::GetAxis(0, 0x80, axisvalue); - break; - case SDLK_SPACE: - button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; - break; - default: - break; } + if (button != 0) { controller->CheckButton(0, button, event->type == SDL_EVENT_KEY_DOWN); } @@ -284,71 +275,116 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { } } -void WindowSDL::onGamepadEvent(const SDL_Event* event) { - using Libraries::Pad::OrbisPadButtonDataOffset; - - u32 button = 0; - Input::Axis axis = Input::Axis::AxisMax; - switch (event->type) { - case SDL_EVENT_GAMEPAD_BUTTON_DOWN: - case SDL_EVENT_GAMEPAD_BUTTON_UP: - button = sdlGamepadToOrbisButton(event->gbutton.button); - if (button != 0) { - controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); - } - break; - case SDL_EVENT_GAMEPAD_AXIS_MOTION: - axis = event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX ? Input::Axis::LeftX - : event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFTY ? Input::Axis::LeftY - : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTX ? Input::Axis::RightX - : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHTY ? Input::Axis::RightY - : event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ? Input::Axis::TriggerLeft - : event->gaxis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER ? Input::Axis::TriggerRight - : Input::Axis::AxisMax; - if (axis != Input::Axis::AxisMax) { - controller->Axis(0, axis, Input::GetAxis(-0x8000, 0x8000, event->gaxis.value)); - } - break; +void WindowSDL::handleR2Key(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax) { + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R2; + axis = Input::Axis::TriggerRight; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 255; + } else { + axisvalue = 0; } + ax = Input::GetAxis(0, 0x80, axisvalue); } -int WindowSDL::sdlGamepadToOrbisButton(u8 button) { - using Libraries::Pad::OrbisPadButtonDataOffset; - - switch (button) { - case SDL_GAMEPAD_BUTTON_DPAD_DOWN: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_DOWN; - case SDL_GAMEPAD_BUTTON_DPAD_UP: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; - case SDL_GAMEPAD_BUTTON_DPAD_LEFT: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_LEFT; - case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_RIGHT; - case SDL_GAMEPAD_BUTTON_SOUTH: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CROSS; - case SDL_GAMEPAD_BUTTON_NORTH: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TRIANGLE; - case SDL_GAMEPAD_BUTTON_WEST: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_SQUARE; - case SDL_GAMEPAD_BUTTON_EAST: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_CIRCLE; - case SDL_GAMEPAD_BUTTON_START: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_OPTIONS; - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; - case SDL_GAMEPAD_BUTTON_BACK: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; - case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L1; - case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R1; - case SDL_GAMEPAD_BUTTON_LEFT_STICK: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L3; - case SDL_GAMEPAD_BUTTON_RIGHT_STICK: - return OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_R3; - default: - return 0; +void WindowSDL::handleL2Key(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax) { + button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_L2; + axis = Input::Axis::TriggerLeft; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 255; + } else { + axisvalue = 0; } + ax = Input::GetAxis(0, 0x80, axisvalue); +} + +void WindowSDL::handleLAnalogRightKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::LeftX; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleLAnalogLeftKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::LeftX; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += -127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleLAnalogUpKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::LeftY; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += -127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleLAnalogDownKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::LeftY; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleRAnalogRightKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::RightX; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleRAnalogLeftKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::RightX; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += -127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleRAnalogUpKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::RightY; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += -127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); +} + +void WindowSDL::handleRAnalogDownKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax) { + axis = Input::Axis::RightY; + if (event->type == SDL_EVENT_KEY_DOWN) { + axisvalue += 127; + } else { + axisvalue = 0; + } + ax = Input::GetAxis(-0x80, 0x80, axisvalue); } } // namespace Frontend diff --git a/src/sdl_window.h b/src/sdl_window.h index cf6c3711..823f59c1 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -5,6 +5,7 @@ #include #include "common/types.h" +#include "input/keysmappingprovider.h" struct SDL_Window; struct SDL_Gamepad; @@ -12,7 +13,8 @@ union SDL_Event; namespace Input { class GameController; -} +enum class Axis; +} // namespace Input namespace Frontend { @@ -64,6 +66,8 @@ public: void waitEvent(); + void setKeysMappingProvider(KeysMappingProvider* provider); + private: void onResize(); void onKeyPress(const SDL_Event* event); @@ -71,12 +75,34 @@ private: int sdlGamepadToOrbisButton(u8 button); + void handleR2Key(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax); + void handleL2Key(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax); + void handleLAnalogRightKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + void handleLAnalogLeftKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + void handleLAnalogUpKey(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax); + void handleLAnalogDownKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + void handleRAnalogRightKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + void handleRAnalogLeftKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + void handleRAnalogUpKey(const SDL_Event* event, u32& button, Input::Axis& axis, int& axisvalue, + int& ax); + void handleRAnalogDownKey(const SDL_Event* event, u32& button, Input::Axis& axis, + int& axisvalue, int& ax); + private: s32 width; s32 height; Input::GameController* controller; WindowSystemInfo window_info{}; SDL_Window* window{}; + KeysMappingProvider* keysMappingProvider = nullptr; bool is_shown{}; bool is_open{true}; }; diff --git a/src/shadps4.qrc b/src/shadps4.qrc index c22b837b..39705a02 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -14,6 +14,8 @@ images/exit_icon.png images/settings_icon.png images/controller_icon.png + images/keyboard_icon.png + images/PS4_controller_scheme_final.svg images/refresh_icon.png images/list_mode_icon.png images/flag_jp.png