Add support for SDL controller accelerometer/gyro events
This commit is contained in:
		@@ -144,7 +144,7 @@ if (ENABLE_SDL2)
 | 
			
		||||
    if (CITRA_USE_BUNDLED_SDL2)
 | 
			
		||||
        # Detect toolchain and platform
 | 
			
		||||
        if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
 | 
			
		||||
            set(SDL2_VER "SDL2-2.0.12")
 | 
			
		||||
            set(SDL2_VER "SDL2-2.0.16")
 | 
			
		||||
        else()
 | 
			
		||||
            message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable CITRA_USE_BUNDLED_SDL2 and provide your own.")
 | 
			
		||||
        endif()
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user