mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #5851 from flibitijibibo/master
Add support for SDL controller accelerometer/gyro events
This commit is contained in:
		
						commit
						28039d39a1
					
				
					 6 changed files with 187 additions and 4 deletions
				
			
		|  | @ -7,6 +7,7 @@ | |||
| #include <QLabel> | ||||
| #include <QMessageBox> | ||||
| #include <QPushButton> | ||||
| #include <QTimer> | ||||
| #include <QVBoxLayout> | ||||
| #include "citra_qt/configuration/configure_motion_touch.h" | ||||
| #include "citra_qt/configuration/configure_touch_from_button.h" | ||||
|  | @ -70,16 +71,18 @@ void CalibrationConfigurationDialog::UpdateButtonText(QString text) { | |||
|     cancel_button->setText(text); | ||||
| } | ||||
| 
 | ||||
| const std::array<std::pair<const char*, const char*>, 2> MotionProviders = { | ||||
| const std::array<std::pair<const char*, const char*>, 3> MotionProviders = { | ||||
|     {{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")}, | ||||
|      {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}}; | ||||
|      {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, | ||||
|      {"sdl", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "SDL")}}}; | ||||
| 
 | ||||
| const std::array<std::pair<const char*, const char*>, 2> TouchProviders = { | ||||
|     {{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")}, | ||||
|      {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}}; | ||||
| 
 | ||||
| ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()) { | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()), | ||||
|       timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { | ||||
|     ui->setupUi(this); | ||||
|     for (auto [provider, name] : MotionProviders) { | ||||
|         ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider)); | ||||
|  | @ -95,6 +98,22 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent) | |||
|            "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " | ||||
|            "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); | ||||
| 
 | ||||
|     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(); | ||||
|             // We want all the input systems to be in a "polling" state, but we only care about the
 | ||||
|             // input from SDL.
 | ||||
|             if (params.Has("engine") && params.Get("engine", "") == "sdl") { | ||||
|                 SetPollingResult(params, false); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     SetConfiguration(); | ||||
|     UpdateUiDisplay(); | ||||
|     ConnectEvents(); | ||||
|  | @ -122,6 +141,9 @@ void ConfigureMotionTouch::SetConfiguration() { | |||
|         Settings::values.current_input_profile.touch_from_button_map_index); | ||||
|     ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); | ||||
| 
 | ||||
|     guid = motion_param.Get("guid", "0"); | ||||
|     port = motion_param.Get("port", 0); | ||||
| 
 | ||||
|     min_x = touch_param.Get("min_x", 100); | ||||
|     min_y = touch_param.Get("min_y", 50); | ||||
|     max_x = touch_param.Get("max_x", 1800); | ||||
|  | @ -145,6 +167,14 @@ void ConfigureMotionTouch::UpdateUiDisplay() { | |||
|         ui->motion_sensitivity->setVisible(false); | ||||
|     } | ||||
| 
 | ||||
|     if (motion_engine == "sdl") { | ||||
|         ui->motion_controller_label->setVisible(true); | ||||
|         ui->motion_controller_button->setVisible(true); | ||||
|     } else { | ||||
|         ui->motion_controller_label->setVisible(false); | ||||
|         ui->motion_controller_button->setVisible(false); | ||||
|     } | ||||
| 
 | ||||
|     if (touch_engine == "cemuhookudp") { | ||||
|         ui->touch_calibration->setVisible(true); | ||||
|         ui->touch_calibration_config->setVisible(true); | ||||
|  | @ -172,6 +202,30 @@ void ConfigureMotionTouch::ConnectEvents() { | |||
|     connect(ui->touch_provider, | ||||
|             static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, | ||||
|             [this]([[maybe_unused]] int index) { UpdateUiDisplay(); }); | ||||
|     connect(ui->motion_controller_button, &QPushButton::clicked, [=]() { | ||||
|         if (QMessageBox::information(this, tr("Information"), | ||||
|                                      tr("After pressing OK, press a button on the controller whose " | ||||
|                                         "motion you want to track."), | ||||
|                                      QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { | ||||
|             ui->motion_controller_button->setText(tr("[press button]")); | ||||
|             ui->motion_controller_button->setFocus(); | ||||
| 
 | ||||
|             input_setter = [=](const Common::ParamPackage& params) { | ||||
|                 guid = params.Get("guid", "0"); | ||||
|                 port = params.Get("port", 0); | ||||
|             }; | ||||
| 
 | ||||
|             device_pollers = | ||||
|                 InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button); | ||||
| 
 | ||||
|             for (auto& poller : device_pollers) { | ||||
|                 poller->Start(); | ||||
|             } | ||||
| 
 | ||||
|             timeout_timer->start(5000); // Cancel after 5 seconds
 | ||||
|             poll_timer->start(200);     // Check for new inputs every 200ms
 | ||||
|         } | ||||
|     }); | ||||
|     connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); | ||||
|     connect(ui->touch_calibration_config, &QPushButton::clicked, this, | ||||
|             &ConfigureMotionTouch::OnConfigureTouchCalibration); | ||||
|  | @ -183,6 +237,21 @@ void ConfigureMotionTouch::ConnectEvents() { | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| void ConfigureMotionTouch::SetPollingResult(const Common::ParamPackage& params, bool abort) { | ||||
|     timeout_timer->stop(); | ||||
|     poll_timer->stop(); | ||||
|     for (auto& poller : device_pollers) { | ||||
|         poller->Stop(); | ||||
|     } | ||||
| 
 | ||||
|     if (!abort && input_setter) { | ||||
|         (*input_setter)(params); | ||||
|     } | ||||
| 
 | ||||
|     ui->motion_controller_button->setText(tr("Configure")); | ||||
|     input_setter.reset(); | ||||
| } | ||||
| 
 | ||||
| void ConfigureMotionTouch::OnCemuhookUDPTest() { | ||||
|     ui->udp_test->setEnabled(false); | ||||
|     ui->udp_test->setText(tr("Testing")); | ||||
|  | @ -285,6 +354,9 @@ void ConfigureMotionTouch::ApplyConfiguration() { | |||
| 
 | ||||
|     if (motion_engine == "motion_emu") { | ||||
|         motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value())); | ||||
|     } else if (motion_engine == "sdl") { | ||||
|         motion_param.Set("guid", guid); | ||||
|         motion_param.Set("port", port); | ||||
|     } | ||||
| 
 | ||||
|     if (touch_engine == "cemuhookudp") { | ||||
|  |  | |||
|  | @ -8,11 +8,13 @@ | |||
| #include <QDialog> | ||||
| #include "common/param_package.h" | ||||
| #include "core/settings.h" | ||||
| #include "input_common/main.h" | ||||
| #include "input_common/udp/udp.h" | ||||
| 
 | ||||
| class QVBoxLayout; | ||||
| class QLabel; | ||||
| class QPushButton; | ||||
| class QTimer; | ||||
| 
 | ||||
| namespace Ui { | ||||
| class ConfigureMotionTouch; | ||||
|  | @ -63,10 +65,21 @@ private: | |||
|     void SetConfiguration(); | ||||
|     void UpdateUiDisplay(); | ||||
|     void ConnectEvents(); | ||||
|     void SetPollingResult(const Common::ParamPackage& params, bool abort); | ||||
|     bool CanCloseDialog(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::ConfigureMotionTouch> ui; | ||||
| 
 | ||||
|     // Used for SDL input polling
 | ||||
|     std::string guid; | ||||
|     int port; | ||||
|     std::unique_ptr<QTimer> timeout_timer; | ||||
|     std::unique_ptr<QTimer> poll_timer; | ||||
|     std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; | ||||
| 
 | ||||
|     /// This will be the the setting function when an input is awaiting configuration.
 | ||||
|     std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; | ||||
| 
 | ||||
|     // Coordinate system of the CemuhookUDP touch provider
 | ||||
|     int min_x, min_y, max_x, max_y; | ||||
| 
 | ||||
|  |  | |||
|  | @ -67,6 +67,24 @@ | |||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout"> | ||||
|         <item> | ||||
|          <widget class="QLabel" name="motion_controller_label"> | ||||
|           <property name="text"> | ||||
|            <string>Controller:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QPushButton" name="motion_controller_button"> | ||||
|           <property name="text"> | ||||
|            <string>Configure</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|  |  | |||
|  | @ -173,6 +173,24 @@ public: | |||
|         std::lock_guard lock{mutex}; | ||||
|         return (state.hats.at(hat) & direction) != 0; | ||||
|     } | ||||
| 
 | ||||
|     void SetAccel(const float x, const float y, const float z) { | ||||
|         std::lock_guard lock{mutex}; | ||||
|         state.accel.x = x; | ||||
|         state.accel.y = y; | ||||
|         state.accel.z = z; | ||||
|     } | ||||
|     void SetGyro(const float pitch, const float yaw, const float roll) { | ||||
|         std::lock_guard lock{mutex}; | ||||
|         state.gyro.x = pitch; | ||||
|         state.gyro.y = yaw; | ||||
|         state.gyro.z = roll; | ||||
|     } | ||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetMotion() const { | ||||
|         std::lock_guard lock{mutex}; | ||||
|         return std::make_tuple(state.accel, state.gyro); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * The guid of the joystick | ||||
|      */ | ||||
|  | @ -204,6 +222,8 @@ private: | |||
|         std::unordered_map<int, bool> buttons; | ||||
|         std::unordered_map<int, Sint16> axes; | ||||
|         std::unordered_map<int, Uint8> hats; | ||||
|         Common::Vec3<float> accel; | ||||
|         Common::Vec3<float> gyro; | ||||
|     } state; | ||||
|     std::string guid; | ||||
|     int port; | ||||
|  | @ -473,6 +493,14 @@ void SDLState::InitGameController(int controller_index) { | |||
|         LOG_WARNING(Input, "failed to open joystick {} as controller", controller_index); | ||||
|         return; | ||||
|     } | ||||
| #if SDL_VERSION_ATLEAST(2, 0, 14) | ||||
|     if (SDL_GameControllerHasSensor(sdl_controller, SDL_SENSOR_ACCEL)) { | ||||
|         SDL_GameControllerSetSensorEnabled(sdl_controller, SDL_SENSOR_ACCEL, SDL_TRUE); | ||||
|     } | ||||
|     if (SDL_GameControllerHasSensor(sdl_controller, SDL_SENSOR_GYRO)) { | ||||
|         SDL_GameControllerSetSensorEnabled(sdl_controller, SDL_SENSOR_GYRO, SDL_TRUE); | ||||
|     } | ||||
| #endif | ||||
|     const std::string guid = GetGUID(SDL_GameControllerGetJoystick(sdl_controller)); | ||||
| 
 | ||||
|     LOG_INFO(Input, "opened joystick {} as controller", controller_index); | ||||
|  | @ -557,6 +585,25 @@ void SDLState::HandleGameControllerEvent(const SDL_Event& event) { | |||
|         } | ||||
|         break; | ||||
|     } | ||||
| #if SDL_VERSION_ATLEAST(2, 0, 14) | ||||
|     case SDL_CONTROLLERSENSORUPDATE: { | ||||
|         if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { | ||||
|             switch (event.csensor.sensor) { | ||||
|             case SDL_SENSOR_ACCEL: | ||||
|                 joystick->SetAccel(event.csensor.data[0] / SDL_STANDARD_GRAVITY, | ||||
|                                    -event.csensor.data[1] / SDL_STANDARD_GRAVITY, | ||||
|                                    event.csensor.data[2] / SDL_STANDARD_GRAVITY); | ||||
|                 break; | ||||
|             case SDL_SENSOR_GYRO: | ||||
|                 joystick->SetGyro(-event.csensor.data[0] * (180.0f / Common::PI), | ||||
|                                   event.csensor.data[1] * (180.0f / Common::PI), | ||||
|                                   -event.csensor.data[2] * (180.0f / Common::PI)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| #endif | ||||
|     case SDL_JOYDEVICEREMOVED: | ||||
|         LOG_DEBUG(Input, "Joystick removed with Instance_ID {}", event.jdevice.which); | ||||
|         CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); | ||||
|  | @ -658,6 +705,18 @@ private: | |||
|     const float deadzone; | ||||
| }; | ||||
| 
 | ||||
| class SDLMotion final : public Input::MotionDevice { | ||||
| public: | ||||
|     explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {} | ||||
| 
 | ||||
|     std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { | ||||
|         return joystick->GetMotion(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<SDLJoystick> joystick; | ||||
| }; | ||||
| 
 | ||||
| /// A button device factory that creates button devices from SDL joystick
 | ||||
| class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { | ||||
| public: | ||||
|  | @ -764,10 +823,28 @@ private: | |||
|     SDLState& state; | ||||
| }; | ||||
| 
 | ||||
| class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||||
| public: | ||||
|     explicit SDLMotionFactory(SDLState& state_) : state(state_) {} | ||||
| 
 | ||||
|     std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { | ||||
|         const std::string guid = params.Get("guid", "0"); | ||||
|         const int port = params.Get("port", 0); | ||||
| 
 | ||||
|         auto joystick = state.GetSDLJoystickByGUID(guid, port); | ||||
| 
 | ||||
|         return std::make_unique<SDLMotion>(joystick); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     SDLState& state; | ||||
| }; | ||||
| 
 | ||||
| SDLState::SDLState() { | ||||
|     using namespace Input; | ||||
|     RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); | ||||
|     RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); | ||||
|     RegisterFactory<MotionDevice>("sdl", std::make_shared<SDLMotionFactory>(*this)); | ||||
| 
 | ||||
|     // If the frontend is going to manage the event loop, then we dont start one here
 | ||||
|     start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER); | ||||
|  | @ -812,6 +889,7 @@ SDLState::~SDLState() { | |||
|     using namespace Input; | ||||
|     UnregisterFactory<ButtonDevice>("sdl"); | ||||
|     UnregisterFactory<AnalogDevice>("sdl"); | ||||
|     UnregisterFactory<MotionDevice>("sdl"); | ||||
| 
 | ||||
|     CloseJoysticks(); | ||||
|     CloseGameControllers(); | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ class SDLJoystick; | |||
| class SDLGameController; | ||||
| class SDLButtonFactory; | ||||
| class SDLAnalogFactory; | ||||
| class SDLMotionFactory; | ||||
| 
 | ||||
| class SDLState : public State { | ||||
| public: | ||||
|  | @ -73,6 +74,7 @@ private: | |||
| 
 | ||||
|     std::shared_ptr<SDLButtonFactory> button_factory; | ||||
|     std::shared_ptr<SDLAnalogFactory> analog_factory; | ||||
|     std::shared_ptr<SDLMotionFactory> motion_factory; | ||||
| 
 | ||||
|     bool start_thread = false; | ||||
|     std::atomic<bool> initialized = false; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue