mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30: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> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue