diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 948db384d..6043a292a 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -17,6 +17,10 @@ #include "video_core/debug_utils/debug_utils.h" #include "video_core/video_core.h" +// Required for SDL gamepad support +#include +#include + EmuThread::EmuThread(GRenderWindow* render_window) : exec_step(false), running(false), stop_run(false), render_window(render_window) {} @@ -105,6 +109,14 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL2 gamepad! Exiting..."); + exit(1); + } + + GamepadSetMappings(); + + keyboard_id = KeyMap::NewDeviceId(); ReloadSetKeymaps(); } @@ -141,7 +153,53 @@ void GRenderWindow::DoneCurrent() { child->doneCurrent(); } -void GRenderWindow::PollEvents() {} +void GRenderWindow::PollEvents() { + // Poll for gamepad controller button events and axis motion, maybe this should be changed into 2 calls to SDL_AddEventWatch? + // Might be more congruent with the rest of the code (i.e., only having one SDL event loop) + SDL_Event event; + + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: + gamepadButtonEvent(&event.cbutton); + break; + case SDL_CONTROLLERAXISMOTION: + gamepadAxisEvent(&event.caxis); + break; + } + } +} + +void GRenderWindow::GamepadSetMappings() { + SDL_GameControllerAddMapping("78696e70757401000000000000000000, XInput Controller, a:b0, b : b1, back : b6, dpdown : h0.4, dpleft : h0.8, dpright : h0.2, dpup : h0.1, guide : b10, leftshoulder : b4, leftstick : b8, lefttrigger : a2, leftx : a0, lefty : a1, rightshoulder : b5, rightstick : b9, righttrigger : a5, rightx : a3, righty : a4, start : b7, x : b2, y : b3, "); + + gamepad_controllers = std::vector(); + gamepad_mappings = std::vector>(); + + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_A, 65)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_B, 83)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_X, 90)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_Y, 88)); + + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_START, 77)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_BACK, 78)); + + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_DPAD_DOWN, 71)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_DPAD_LEFT, 70)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 72)); + gamepad_mappings.push_back(std::tuple(SDL_CONTROLLER_BUTTON_DPAD_UP, 84)); + + int MaxJoysticks = SDL_NumJoysticks() + 1; + int ControllerIndex = 0; + for (int JoystickIndex = 0; JoystickIndex < MaxJoysticks; ++JoystickIndex) + { + SDL_GameController* pad = SDL_GameControllerOpen(JoystickIndex); + SDL_Joystick* stick = SDL_JoystickOpen(JoystickIndex); + + gamepad_controllers.push_back(pad); + } +} // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). // @@ -230,6 +288,51 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { motion_emu->EndTilt(); } +void GRenderWindow::gamepadButtonEvent(SDL_ControllerButtonEvent* event) { + auto it = std::find_if(gamepad_mappings.begin(), gamepad_mappings.end(), [event](const std::tuple& e) {return std::get<0>(e) == event->button; }); + + if (it != gamepad_mappings.end()) { + int keycode = std::get<1>(*it); + + if (event->state == SDL_PRESSED) { + KeyMap::PressKey(*this, { keycode, keyboard_id }); + } else { + KeyMap::ReleaseKey(*this, { keycode, keyboard_id }); + } + } +} + +void GRenderWindow::gamepadAxisEvent(SDL_ControllerAxisEvent* event) { + if (event->axis == SDL_CONTROLLER_AXIS_LEFTX) { + if (event->value < -8000) { + KeyMap::PressKey(*this, { 16777234, keyboard_id }); + } else { + KeyMap::ReleaseKey(*this, { 16777234, keyboard_id }); + } + + if (event->value > 8000) { + KeyMap::PressKey(*this, { 16777236, keyboard_id }); + } else { + KeyMap::ReleaseKey(*this, { 16777236, keyboard_id }); + } + } + + if (event->axis == SDL_CONTROLLER_AXIS_LEFTY) { + if (event->value < -8000) { + KeyMap::PressKey(*this, { 16777235, keyboard_id }); + } else { + KeyMap::ReleaseKey(*this, { 16777235, keyboard_id }); + } + + if (event->value > 8000) { + KeyMap::PressKey(*this, { 16777237, keyboard_id }); + } else { + KeyMap::ReleaseKey(*this, { 16777237, keyboard_id }); + } + } +} + + void GRenderWindow::ReloadSetKeymaps() { KeyMap::ClearKeyMapping(keyboard_id); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 7dac1c480..68e05b55e 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -9,10 +9,12 @@ #include #include #include +#include #include "common/thread.h" #include "core/frontend/emu_window.h" #include "core/frontend/motion_emu.h" + class QKeyEvent; class QScreen; @@ -110,7 +112,6 @@ public: void MakeCurrent() override; void DoneCurrent() override; void PollEvents() override; - void BackupGeometry(); void RestoreGeometry(); void restoreGeometry(const QByteArray& geometry); // overridden @@ -127,8 +128,13 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; + void gamepadButtonEvent(SDL_ControllerButtonEvent* event); + void gamepadAxisEvent(SDL_ControllerAxisEvent* event); + void ReloadSetKeymaps() override; + void GamepadSetMappings(); + void OnClientAreaResized(unsigned width, unsigned height); void InitRenderTarget(); @@ -160,6 +166,12 @@ private: /// Motion sensors emulation std::unique_ptr motion_emu; + /// Gamepad ID -> Joystick mapping + std::vector gamepad_controllers; + + /// Gamepad button -> keyboard maps + std::vector> gamepad_mappings; + protected: void showEvent(QShowEvent* event) override; };