mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Allow input configuration with SDL joysticks (#3116)
* Add infrastructure to poll joystick input and get ParamPackages * Generalize the callbacks in configure_input.cpp and add buttons for analog sticks * Use the polling classes in the input dialog * Fix includes * Formatting fix * Include real header instead of forward declaring, to fix compiler error * Split up pair and add deadzone for joystick configuration * Pass ParamPackages by reference to callback * fix formatting * getPollers -> GetPollers * Add forward declarations and simplify code a bit * Update joysticks before opening them * Fix mixup between joystick IDs and device indices
This commit is contained in:
		
							parent
							
								
									e165b5bb94
								
							
						
					
					
						commit
						e784434a25
					
				
					 7 changed files with 385 additions and 52 deletions
				
			
		|  | @ -5,11 +5,11 @@ | |||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <utility> | ||||
| #include <QMessageBox> | ||||
| #include <QTimer> | ||||
| #include "citra_qt/configuration/config.h" | ||||
| #include "citra_qt/configuration/configure_input.h" | ||||
| #include "common/param_package.h" | ||||
| #include "input_common/main.h" | ||||
| 
 | ||||
| const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> | ||||
|     ConfigureInput::analog_sub_buttons{{ | ||||
|  | @ -31,23 +31,19 @@ static QString getKeyName(int key_code) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static void SetButtonKey(int key, Common::ParamPackage& button_param) { | ||||
|     button_param = Common::ParamPackage{InputCommon::GenerateKeyboardParam(key)}; | ||||
| } | ||||
| 
 | ||||
| static void SetAnalogKey(int key, Common::ParamPackage& analog_param, | ||||
|                          const std::string& button_name) { | ||||
| static void SetAnalogButton(const Common::ParamPackage& input_param, | ||||
|                             Common::ParamPackage& analog_param, const std::string& button_name) { | ||||
|     if (analog_param.Get("engine", "") != "analog_from_button") { | ||||
|         analog_param = { | ||||
|             {"engine", "analog_from_button"}, {"modifier_scale", "0.5"}, | ||||
|         }; | ||||
|     } | ||||
|     analog_param.Set(button_name, InputCommon::GenerateKeyboardParam(key)); | ||||
|     analog_param.Set(button_name, input_param.Serialize()); | ||||
| } | ||||
| 
 | ||||
| ConfigureInput::ConfigureInput(QWidget* parent) | ||||
|     : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), | ||||
|       timer(std::make_unique<QTimer>()) { | ||||
|       timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
|     setFocusPolicy(Qt::ClickFocus); | ||||
|  | @ -58,7 +54,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) | |||
|         ui->buttonStart,    ui->buttonSelect,   ui->buttonZL,        ui->buttonZR, ui->buttonHome, | ||||
|     }; | ||||
| 
 | ||||
|     analog_map = {{ | ||||
|     analog_map_buttons = {{ | ||||
|         { | ||||
|             ui->buttonCircleUp, ui->buttonCircleDown, ui->buttonCircleLeft, ui->buttonCircleRight, | ||||
|             ui->buttonCircleMod, | ||||
|  | @ -69,35 +65,57 @@ ConfigureInput::ConfigureInput(QWidget* parent) | |||
|         }, | ||||
|     }}; | ||||
| 
 | ||||
|     analog_map_stick = {ui->buttonCircleAnalog, ui->buttonCStickAnalog}; | ||||
| 
 | ||||
|     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { | ||||
|         if (button_map[button_id]) | ||||
|             connect(button_map[button_id], &QPushButton::released, [=]() { | ||||
|                 handleClick(button_map[button_id], | ||||
|                             [=](int key) { SetButtonKey(key, buttons_param[button_id]); }); | ||||
|                 handleClick( | ||||
|                     button_map[button_id], | ||||
|                     [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, | ||||
|                     InputCommon::Polling::DeviceType::Button); | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { | ||||
|         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { | ||||
|             if (analog_map[analog_id][sub_button_id] != nullptr) { | ||||
|                 connect(analog_map[analog_id][sub_button_id], &QPushButton::released, [=]() { | ||||
|                     handleClick(analog_map[analog_id][sub_button_id], [=](int key) { | ||||
|                         SetAnalogKey(key, analogs_param[analog_id], | ||||
|                                      analog_sub_buttons[sub_button_id]); | ||||
|                     }); | ||||
|                 }); | ||||
|             if (analog_map_buttons[analog_id][sub_button_id] != nullptr) { | ||||
|                 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, | ||||
|                         [=]() { | ||||
|                             handleClick(analog_map_buttons[analog_id][sub_button_id], | ||||
|                                         [=](const Common::ParamPackage& params) { | ||||
|                                             SetAnalogButton(params, analogs_param[analog_id], | ||||
|                                                             analog_sub_buttons[sub_button_id]); | ||||
|                                         }, | ||||
|                                         InputCommon::Polling::DeviceType::Button); | ||||
|                         }); | ||||
|             } | ||||
|         } | ||||
|         connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { | ||||
|             QMessageBox::information( | ||||
|                 this, "Information", | ||||
|                 "After pressing OK, first move your joystick horizontally, and then vertically."); | ||||
|             handleClick( | ||||
|                 analog_map_stick[analog_id], | ||||
|                 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, | ||||
|                 InputCommon::Polling::DeviceType::Analog); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); | ||||
| 
 | ||||
|     timer->setSingleShot(true); | ||||
|     connect(timer.get(), &QTimer::timeout, [this]() { | ||||
|         releaseKeyboard(); | ||||
|         releaseMouse(); | ||||
|         key_setter = boost::none; | ||||
|         updateButtonLabels(); | ||||
|     timeout_timer->setSingleShot(true); | ||||
|     connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); | ||||
| 
 | ||||
|     connect(poll_timer.get(), &QTimer::timeout, [this]() { | ||||
|         Common::ParamPackage params; | ||||
|         for (auto& poller : device_pollers) { | ||||
|             params = poller->GetNextInput(); | ||||
|             if (params.Has("engine")) { | ||||
|                 setPollingResult(params, false); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     this->loadConfiguration(); | ||||
|  | @ -127,13 +145,15 @@ void ConfigureInput::loadConfiguration() { | |||
| 
 | ||||
| void ConfigureInput::restoreDefaults() { | ||||
|     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { | ||||
|         SetButtonKey(Config::default_buttons[button_id], buttons_param[button_id]); | ||||
|         buttons_param[button_id] = Common::ParamPackage{ | ||||
|             InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; | ||||
|     } | ||||
| 
 | ||||
|     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { | ||||
|         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { | ||||
|             SetAnalogKey(Config::default_analogs[analog_id][sub_button_id], | ||||
|                          analogs_param[analog_id], analog_sub_buttons[sub_button_id]); | ||||
|             Common::ParamPackage params{InputCommon::GenerateKeyboardParam( | ||||
|                 Config::default_analogs[analog_id][sub_button_id])}; | ||||
|             SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); | ||||
|         } | ||||
|     } | ||||
|     updateButtonLabels(); | ||||
|  | @ -144,7 +164,9 @@ void ConfigureInput::updateButtonLabels() { | |||
|     QString non_keyboard(tr("[non-keyboard]")); | ||||
| 
 | ||||
|     auto KeyToText = [&non_keyboard](const Common::ParamPackage& param) { | ||||
|         if (param.Get("engine", "") != "keyboard") { | ||||
|         if (!param.Has("engine")) { | ||||
|             return QString("[not set]"); | ||||
|         } else if (param.Get("engine", "") != "keyboard") { | ||||
|             return non_keyboard; | ||||
|         } else { | ||||
|             return getKeyName(param.Get("code", 0)); | ||||
|  | @ -157,7 +179,7 @@ void ConfigureInput::updateButtonLabels() { | |||
| 
 | ||||
|     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { | ||||
|         if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") { | ||||
|             for (QPushButton* button : analog_map[analog_id]) { | ||||
|             for (QPushButton* button : analog_map_buttons[analog_id]) { | ||||
|                 if (button) | ||||
|                     button->setText(non_keyboard); | ||||
|             } | ||||
|  | @ -165,35 +187,66 @@ void ConfigureInput::updateButtonLabels() { | |||
|             for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { | ||||
|                 Common::ParamPackage param( | ||||
|                     analogs_param[analog_id].Get(analog_sub_buttons[sub_button_id], "")); | ||||
|                 if (analog_map[analog_id][sub_button_id]) | ||||
|                     analog_map[analog_id][sub_button_id]->setText(KeyToText(param)); | ||||
|                 if (analog_map_buttons[analog_id][sub_button_id]) | ||||
|                     analog_map_buttons[analog_id][sub_button_id]->setText(KeyToText(param)); | ||||
|             } | ||||
|         } | ||||
|         analog_map_stick[analog_id]->setText("Set Analog Stick"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ConfigureInput::handleClick(QPushButton* button, std::function<void(int)> new_key_setter) { | ||||
| void ConfigureInput::handleClick(QPushButton* button, | ||||
|                                  std::function<void(const Common::ParamPackage&)> new_input_setter, | ||||
|                                  InputCommon::Polling::DeviceType type) { | ||||
|     button->setText(tr("[press key]")); | ||||
|     button->setFocus(); | ||||
| 
 | ||||
|     key_setter = new_key_setter; | ||||
|     input_setter = new_input_setter; | ||||
| 
 | ||||
|     device_pollers = InputCommon::Polling::GetPollers(type); | ||||
| 
 | ||||
|     // Keyboard keys can only be used as button devices
 | ||||
|     want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; | ||||
| 
 | ||||
|     for (auto& poller : device_pollers) { | ||||
|         poller->Start(); | ||||
|     } | ||||
| 
 | ||||
|     grabKeyboard(); | ||||
|     grabMouse(); | ||||
|     timer->start(5000); // Cancel after 5 seconds
 | ||||
|     timeout_timer->start(5000); // Cancel after 5 seconds
 | ||||
|     poll_timer->start(200);     // Check for new inputs every 200ms
 | ||||
| } | ||||
| 
 | ||||
| void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool abort) { | ||||
|     releaseKeyboard(); | ||||
|     releaseMouse(); | ||||
|     timeout_timer->stop(); | ||||
|     poll_timer->stop(); | ||||
|     for (auto& poller : device_pollers) { | ||||
|         poller->Stop(); | ||||
|     } | ||||
| 
 | ||||
|     if (!abort) { | ||||
|         (*input_setter)(params); | ||||
|     } | ||||
| 
 | ||||
|     updateButtonLabels(); | ||||
|     input_setter = boost::none; | ||||
| } | ||||
| 
 | ||||
| void ConfigureInput::keyPressEvent(QKeyEvent* event) { | ||||
|     releaseKeyboard(); | ||||
|     releaseMouse(); | ||||
| 
 | ||||
|     if (!key_setter || !event) | ||||
|     if (!input_setter || !event) | ||||
|         return; | ||||
| 
 | ||||
|     if (event->key() != Qt::Key_Escape) | ||||
|         (*key_setter)(event->key()); | ||||
| 
 | ||||
|     updateButtonLabels(); | ||||
|     key_setter = boost::none; | ||||
|     timer->stop(); | ||||
|     if (event->key() != Qt::Key_Escape) { | ||||
|         if (want_keyboard_keys) { | ||||
|             setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, | ||||
|                              false); | ||||
|         } else { | ||||
|             // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
 | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     setPollingResult({}, true); | ||||
| } | ||||
|  |  | |||
|  | @ -8,11 +8,13 @@ | |||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <QKeyEvent> | ||||
| #include <QWidget> | ||||
| #include <boost/optional.hpp> | ||||
| #include "common/param_package.h" | ||||
| #include "core/settings.h" | ||||
| #include "input_common/main.h" | ||||
| #include "ui_configure_input.h" | ||||
| 
 | ||||
| class QPushButton; | ||||
|  | @ -35,10 +37,11 @@ public: | |||
| private: | ||||
|     std::unique_ptr<Ui::ConfigureInput> ui; | ||||
| 
 | ||||
|     std::unique_ptr<QTimer> timer; | ||||
|     std::unique_ptr<QTimer> timeout_timer; | ||||
|     std::unique_ptr<QTimer> poll_timer; | ||||
| 
 | ||||
|     /// This will be the the setting function when an input is awaiting configuration.
 | ||||
|     boost::optional<std::function<void(int)>> key_setter; | ||||
|     boost::optional<std::function<void(const Common::ParamPackage&)>> input_setter; | ||||
| 
 | ||||
|     std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; | ||||
|     std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; | ||||
|  | @ -48,13 +51,23 @@ private: | |||
|     /// Each button input is represented by a QPushButton.
 | ||||
|     std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; | ||||
| 
 | ||||
|     /// Each analog input is represented by five QPushButtons which represents up, down, left, right
 | ||||
|     /// and modifier
 | ||||
|     /// A group of five QPushButtons represent one analog input. The buttons each represent up,
 | ||||
|     /// down, left, right, and modifier, respectively.
 | ||||
|     std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> | ||||
|         analog_map; | ||||
|         analog_map_buttons; | ||||
| 
 | ||||
|     /// Analog inputs are also represented each with a single button, used to configure with an
 | ||||
|     /// actual analog stick
 | ||||
|     std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; | ||||
| 
 | ||||
|     static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; | ||||
| 
 | ||||
|     std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; | ||||
| 
 | ||||
|     /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
 | ||||
|     /// keyboard events are ignored.
 | ||||
|     bool want_keyboard_keys = false; | ||||
| 
 | ||||
|     /// Load configuration settings.
 | ||||
|     void loadConfiguration(); | ||||
|     /// Restore all buttons to their default values.
 | ||||
|  | @ -63,7 +76,13 @@ private: | |||
|     void updateButtonLabels(); | ||||
| 
 | ||||
|     /// Called when the button was pressed.
 | ||||
|     void handleClick(QPushButton* button, std::function<void(int)> new_key_setter); | ||||
|     void handleClick(QPushButton* button, | ||||
|                      std::function<void(const Common::ParamPackage&)> new_input_setter, | ||||
|                      InputCommon::Polling::DeviceType type); | ||||
| 
 | ||||
|     /// Finish polling and configure input using the input_setter
 | ||||
|     void setPollingResult(const Common::ParamPackage& params, bool abort); | ||||
| 
 | ||||
|     /// Handle key press events.
 | ||||
|     void keyPressEvent(QKeyEvent* event) override; | ||||
| }; | ||||
|  |  | |||
|  | @ -289,6 +289,13 @@ | |||
|         <bool>false</bool> | ||||
|        </property> | ||||
|        <layout class="QGridLayout" name="gridLayout_4"> | ||||
|         <item row="2" column="0" colspan="2"> | ||||
|          <widget class="QPushButton" name="buttonCircleAnalog"> | ||||
|           <property name="text"> | ||||
|            <string>Set Analog Stick</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="0" column="0"> | ||||
|          <layout class="QVBoxLayout" name="verticalLayout_17"> | ||||
|           <item> | ||||
|  | @ -448,6 +455,13 @@ | |||
|           </item> | ||||
|          </layout> | ||||
|         </item> | ||||
|         <item row="2" column="0" colspan="2"> | ||||
|          <widget class="QPushButton" name="buttonCStickAnalog"> | ||||
|           <property name="text"> | ||||
|            <string>Set Analog Stick</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|  |  | |||
|  | @ -71,4 +71,15 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, | |||
|     return circle_pad_param.Serialize(); | ||||
| } | ||||
| 
 | ||||
| namespace Polling { | ||||
| 
 | ||||
| std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { | ||||
| #ifdef HAVE_SDL2 | ||||
|     return SDL::Polling::GetPollers(type); | ||||
| #else | ||||
|     return {}; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| } // namespace Polling
 | ||||
| } // namespace InputCommon
 | ||||
|  |  | |||
|  | @ -4,7 +4,13 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace Common { | ||||
| class ParamPackage; | ||||
| } | ||||
| 
 | ||||
| namespace InputCommon { | ||||
| 
 | ||||
|  | @ -31,4 +37,30 @@ std::string GenerateKeyboardParam(int key_code); | |||
| std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, | ||||
|                                         int key_modifier, float modifier_scale); | ||||
| 
 | ||||
| namespace Polling { | ||||
| 
 | ||||
| enum class DeviceType { Button, Analog }; | ||||
| 
 | ||||
| /**
 | ||||
|  * A class that can be used to get inputs from an input device like controllers without having to | ||||
|  * poll the device's status yourself | ||||
|  */ | ||||
| class DevicePoller { | ||||
| public: | ||||
|     virtual ~DevicePoller() = default; | ||||
|     /// Setup and start polling for inputs, should be called before GetNextInput
 | ||||
|     virtual void Start() = 0; | ||||
|     /// Stop polling
 | ||||
|     virtual void Stop() = 0; | ||||
|     /**
 | ||||
|      * Every call to this function returns the next input recorded since calling Start | ||||
|      * @return A ParamPackage of the recorded input, which can be used to create an InputDevice. | ||||
|      *         If there has been no input, the package is empty | ||||
|      */ | ||||
|     virtual Common::ParamPackage GetNextInput() = 0; | ||||
| }; | ||||
| 
 | ||||
| // Get all DevicePoller from all backends for a specific device type
 | ||||
| std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type); | ||||
| } // namespace Polling
 | ||||
| } // namespace InputCommon
 | ||||
|  |  | |||
|  | @ -3,13 +3,15 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <tuple> | ||||
| #include <unordered_map> | ||||
| #include <utility> | ||||
| #include <SDL.h> | ||||
| #include "common/logging/log.h" | ||||
| #include "common/math_util.h" | ||||
| #include "common/param_package.h" | ||||
| #include "input_common/main.h" | ||||
| #include "input_common/sdl/sdl.h" | ||||
| 
 | ||||
| namespace InputCommon { | ||||
|  | @ -69,6 +71,10 @@ public: | |||
|         return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0; | ||||
|     } | ||||
| 
 | ||||
|     SDL_JoystickID GetJoystickID() const { | ||||
|         return SDL_JoystickInstanceID(joystick.get()); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick; | ||||
| }; | ||||
|  | @ -247,5 +253,180 @@ void Shutdown() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * This function converts a joystick ID used in SDL events to the device index. This is necessary | ||||
|  * because Citra opens joysticks using their indices, not their IDs. | ||||
|  */ | ||||
| static int JoystickIDToDeviceIndex(SDL_JoystickID id) { | ||||
|     int num_joysticks = SDL_NumJoysticks(); | ||||
|     for (int i = 0; i < num_joysticks; i++) { | ||||
|         auto joystick = GetJoystick(i); | ||||
|         if (joystick->GetJoystickID() == id) { | ||||
|             return i; | ||||
|         } | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { | ||||
|     Common::ParamPackage params({{"engine", "sdl"}}); | ||||
|     switch (event.type) { | ||||
|     case SDL_JOYAXISMOTION: | ||||
|         params.Set("joystick", JoystickIDToDeviceIndex(event.jaxis.which)); | ||||
|         params.Set("axis", event.jaxis.axis); | ||||
|         if (event.jaxis.value > 0) { | ||||
|             params.Set("direction", "+"); | ||||
|             params.Set("threshold", "0.5"); | ||||
|         } else { | ||||
|             params.Set("direction", "-"); | ||||
|             params.Set("threshold", "-0.5"); | ||||
|         } | ||||
|         break; | ||||
|     case SDL_JOYBUTTONUP: | ||||
|         params.Set("joystick", JoystickIDToDeviceIndex(event.jbutton.which)); | ||||
|         params.Set("button", event.jbutton.button); | ||||
|         break; | ||||
|     case SDL_JOYHATMOTION: | ||||
|         params.Set("joystick", JoystickIDToDeviceIndex(event.jhat.which)); | ||||
|         params.Set("hat", event.jhat.hat); | ||||
|         switch (event.jhat.value) { | ||||
|         case SDL_HAT_UP: | ||||
|             params.Set("direction", "up"); | ||||
|             break; | ||||
|         case SDL_HAT_DOWN: | ||||
|             params.Set("direction", "down"); | ||||
|             break; | ||||
|         case SDL_HAT_LEFT: | ||||
|             params.Set("direction", "left"); | ||||
|             break; | ||||
|         case SDL_HAT_RIGHT: | ||||
|             params.Set("direction", "right"); | ||||
|             break; | ||||
|         default: | ||||
|             return {}; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
|     return params; | ||||
| } | ||||
| 
 | ||||
| namespace Polling { | ||||
| 
 | ||||
| class SDLPoller : public InputCommon::Polling::DevicePoller { | ||||
| public: | ||||
|     SDLPoller() = default; | ||||
| 
 | ||||
|     ~SDLPoller() = default; | ||||
| 
 | ||||
|     void Start() override { | ||||
|         // SDL joysticks must be opened, otherwise they don't generate events
 | ||||
|         SDL_JoystickUpdate(); | ||||
|         int num_joysticks = SDL_NumJoysticks(); | ||||
|         for (int i = 0; i < num_joysticks; i++) { | ||||
|             joysticks_opened.emplace_back(GetJoystick(i)); | ||||
|         } | ||||
|         // Empty event queue to get rid of old events. citra-qt doesn't use the queue
 | ||||
|         SDL_Event dummy; | ||||
|         while (SDL_PollEvent(&dummy)) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void Stop() override { | ||||
|         joysticks_opened.clear(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::vector<std::shared_ptr<SDLJoystick>> joysticks_opened; | ||||
| }; | ||||
| 
 | ||||
| class SDLButtonPoller final : public SDLPoller { | ||||
| public: | ||||
|     SDLButtonPoller() = default; | ||||
| 
 | ||||
|     ~SDLButtonPoller() = default; | ||||
| 
 | ||||
|     Common::ParamPackage GetNextInput() override { | ||||
|         SDL_Event event; | ||||
|         while (SDL_PollEvent(&event)) { | ||||
|             switch (event.type) { | ||||
|             case SDL_JOYAXISMOTION: | ||||
|                 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { | ||||
|                     break; | ||||
|                 } | ||||
|             case SDL_JOYBUTTONUP: | ||||
|             case SDL_JOYHATMOTION: | ||||
|                 return SDLEventToButtonParamPackage(event); | ||||
|             } | ||||
|         } | ||||
|         return {}; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| class SDLAnalogPoller final : public SDLPoller { | ||||
| public: | ||||
|     SDLAnalogPoller() = default; | ||||
| 
 | ||||
|     ~SDLAnalogPoller() = default; | ||||
| 
 | ||||
|     void Start() override { | ||||
|         SDLPoller::Start(); | ||||
| 
 | ||||
|         // Reset stored axes
 | ||||
|         analog_xaxis = -1; | ||||
|         analog_yaxis = -1; | ||||
|         analog_axes_joystick = -1; | ||||
|     } | ||||
| 
 | ||||
|     Common::ParamPackage GetNextInput() override { | ||||
|         SDL_Event event; | ||||
|         while (SDL_PollEvent(&event)) { | ||||
|             if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { | ||||
|                 continue; | ||||
|             } | ||||
|             // An analog device needs two axes, so we need to store the axis for later and wait for
 | ||||
|             // a second SDL event. The axes also must be from the same joystick.
 | ||||
|             int axis = event.jaxis.axis; | ||||
|             if (analog_xaxis == -1) { | ||||
|                 analog_xaxis = axis; | ||||
|                 analog_axes_joystick = event.jaxis.which; | ||||
|             } else if (analog_yaxis == -1 && analog_xaxis != axis && | ||||
|                        analog_axes_joystick == event.jaxis.which) { | ||||
|                 analog_yaxis = axis; | ||||
|             } | ||||
|         } | ||||
|         Common::ParamPackage params; | ||||
|         if (analog_xaxis != -1 && analog_yaxis != -1) { | ||||
|             params.Set("engine", "sdl"); | ||||
|             params.Set("joystick", JoystickIDToDeviceIndex(analog_axes_joystick)); | ||||
|             params.Set("axis_x", analog_xaxis); | ||||
|             params.Set("axis_y", analog_yaxis); | ||||
|             analog_xaxis = -1; | ||||
|             analog_yaxis = -1; | ||||
|             analog_axes_joystick = -1; | ||||
|             return params; | ||||
|         } | ||||
|         return params; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     int analog_xaxis = -1; | ||||
|     int analog_yaxis = -1; | ||||
|     SDL_JoystickID analog_axes_joystick = -1; | ||||
| }; | ||||
| 
 | ||||
| std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers( | ||||
|     InputCommon::Polling::DeviceType type) { | ||||
|     std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers; | ||||
|     switch (type) { | ||||
|     case InputCommon::Polling::DeviceType::Analog: | ||||
|         pollers.push_back(std::make_unique<SDLAnalogPoller>()); | ||||
|         break; | ||||
|     case InputCommon::Polling::DeviceType::Button: | ||||
|         pollers.push_back(std::make_unique<SDLButtonPoller>()); | ||||
|         break; | ||||
|     } | ||||
|     return std::move(pollers); | ||||
| } | ||||
| } // namespace Polling
 | ||||
| } // namespace SDL
 | ||||
| } // namespace InputCommon
 | ||||
|  |  | |||
|  | @ -4,8 +4,21 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include "core/frontend/input.h" | ||||
| 
 | ||||
| union SDL_Event; | ||||
| namespace Common { | ||||
| class ParamPackage; | ||||
| } | ||||
| namespace InputCommon { | ||||
| namespace Polling { | ||||
| class DevicePoller; | ||||
| enum class DeviceType; | ||||
| } // namespace Polling
 | ||||
| } // namespace InputCommon
 | ||||
| 
 | ||||
| namespace InputCommon { | ||||
| namespace SDL { | ||||
| 
 | ||||
|  | @ -15,5 +28,15 @@ void Init(); | |||
| /// Unresisters SDL device factories and shut them down.
 | ||||
| void Shutdown(); | ||||
| 
 | ||||
| /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
 | ||||
| Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); | ||||
| 
 | ||||
| namespace Polling { | ||||
| 
 | ||||
| /// Get all DevicePoller that use the SDL backend for a specific device type
 | ||||
| std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers( | ||||
|     InputCommon::Polling::DeviceType type); | ||||
| 
 | ||||
| } // namespace Polling
 | ||||
| } // namespace SDL
 | ||||
| } // namespace InputCommon
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue