diff --git a/CHANGES b/CHANGES index f3f300eca..cfdff2aba 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Features: - Deadzone estimation for game controllers - Analog inputs can be used for shortcuts - Menu items for specific solar sensor brightness levels + - Remappable controls for tilt and gyroscope sensors Bugfixes: - GBA: Fix timers not updating timing when writing to only the reload register - All: Fix sanitize-deb script not cleaning up after itself diff --git a/src/gba/input.c b/src/gba/input.c index fbaf8c3e0..439a11d36 100644 --- a/src/gba/input.c +++ b/src/gba/input.c @@ -500,3 +500,23 @@ void GBAInputSetPreferredDevice(struct Configuration* config, uint32_t type, int snprintf(deviceId, sizeof(deviceId), "device%i", playerId); return ConfigurationSetValue(config, sectionName, deviceId, deviceName); } + +const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + if (profile) { + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + } else { + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + } + return ConfigurationGetValue(config, sectionName, key); +} + +void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile) { + char sectionName[SECTION_NAME_MAX]; + if (profile) { + snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile); + } else { + _makeSectionName(sectionName, SECTION_NAME_MAX, type); + } + ConfigurationSetValue(config, sectionName, key, value); +} diff --git a/src/gba/input.h b/src/gba/input.h index c14c0652a..91af955d0 100644 --- a/src/gba/input.h +++ b/src/gba/input.h @@ -51,4 +51,7 @@ void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Config const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId); void GBAInputSetPreferredDevice(struct Configuration*, uint32_t type, int playerId, const char* deviceName); +const char* GBAInputGetCustomValue(const struct Configuration* config, uint32_t type, const char* key, const char* profile); +void GBAInputSetCustomValue(struct Configuration* config, uint32_t type, const char* key, const char* value, const char* profile); + #endif diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 589ed8003..9dfb58daf 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -110,6 +110,11 @@ void InputController::loadProfile(uint32_t type, const QString& profile) { void InputController::saveConfiguration(uint32_t type) { GBAInputMapSave(&m_inputMap, type, m_config->input()); +#ifdef BUILD_SDL + if (m_playerAttached) { + GBASDLPlayerSaveConfig(&m_sdlPlayer, m_config->input()); + } +#endif m_config->write(); } @@ -170,6 +175,27 @@ GBARumble* InputController::rumble() { GBARotationSource* InputController::rotationSource() { return &m_sdlPlayer.rotation.d; } + +void InputController::registerTiltAxisX(int axis) { + m_sdlPlayer.rotation.axisX = axis; +} +void InputController::registerTiltAxisY(int axis) { + m_sdlPlayer.rotation.axisY = axis; +} +void InputController::registerGyroAxisX(int axis) { + m_sdlPlayer.rotation.gyroX = axis; +} +void InputController::registerGyroAxisY(int axis) { + m_sdlPlayer.rotation.gyroY = axis; +} + +float InputController::gyroSensitivity() const { + return m_sdlPlayer.rotation.gyroSensitivity; +} + +void InputController::setGyroSensitivity(float sensitivity) { + m_sdlPlayer.rotation.gyroSensitivity = sensitivity; +} #else GBARumble* InputController::rumble() { return nullptr; diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index a7c607af5..b4a73bab1 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -65,6 +65,14 @@ public: int gamepad(uint32_t type) const { return m_sdlPlayer.joystickIndex; } void setGamepad(uint32_t type, int index) { GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index); } void setPreferredGamepad(uint32_t type, const QString& device); + + void registerTiltAxisX(int axis); + void registerTiltAxisY(int axis); + void registerGyroAxisX(int axis); + void registerGyroAxisY(int axis); + + float gyroSensitivity() const; + void setGyroSensitivity(float sensitivity); #endif GBARumble* rumble(); diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index 5d637c427..e1ce46a92 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -6,6 +6,7 @@ #include "SensorView.h" #include "GameController.h" +#include "GamepadAxisEvent.h" #include "InputController.h" using namespace QGBA; @@ -50,6 +51,42 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg } else { m_timer.start(); } + + jiggerer(m_ui.tiltSetX, &InputController::registerTiltAxisX); + jiggerer(m_ui.tiltSetY, &InputController::registerTiltAxisY); + jiggerer(m_ui.gyroSetX, &InputController::registerGyroAxisX); + jiggerer(m_ui.gyroSetY, &InputController::registerGyroAxisY); + + m_ui.gyroSensitivity->setValue(m_input->gyroSensitivity() / 1e8f); + connect(m_ui.gyroSensitivity, static_cast(&QDoubleSpinBox::valueChanged), [this](double value) { + m_input->setGyroSensitivity(value * 1e8f); + }); +} + +void SensorView::jiggerer(QAbstractButton* button, void (InputController::*setter)(int)) { + connect(button, &QAbstractButton::toggled, [this, button, setter](bool checked) { + if (!checked) { + m_jiggered = nullptr; + } else { + m_jiggered = [this, button, setter](int axis) { + (m_input->*setter)(axis); + button->setChecked(false); + }; + } + }); + button->installEventFilter(this); +} + +bool SensorView::eventFilter(QObject*, QEvent* event) { + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast(event); + gae->accept(); + if (m_jiggered && gae->direction() != GamepadAxisEvent::NEUTRAL && gae->isNew()) { + m_jiggered(gae->axis()); + } + return true; + } + return false; } void SensorView::updateSensors() { diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h index 8d0e223aa..a37fb6e65 100644 --- a/src/platform/qt/SensorView.h +++ b/src/platform/qt/SensorView.h @@ -9,6 +9,8 @@ #include #include +#include + #include "ui_SensorView.h" struct GBARotationSource; @@ -17,6 +19,7 @@ namespace QGBA { class ConfigController; class GameController; +class GamepadAxisEvent; class InputController; class SensorView : public QWidget { @@ -25,6 +28,9 @@ Q_OBJECT public: SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr); +protected: + bool eventFilter(QObject*, QEvent* event) override; + private slots: void updateSensors(); void setLuminanceValue(int); @@ -33,10 +39,13 @@ private slots: private: Ui::SensorView m_ui; + std::function m_jiggered; GameController* m_controller; InputController* m_input; GBARotationSource* m_rotation; QTimer m_timer; + + void jiggerer(QAbstractButton*, void (InputController::*)(int)); }; } diff --git a/src/platform/qt/SensorView.ui b/src/platform/qt/SensorView.ui index a6f9a27e9..b01c18c94 100644 --- a/src/platform/qt/SensorView.ui +++ b/src/platform/qt/SensorView.ui @@ -6,8 +6,8 @@ 0 0 - 508 - 258 + 434 + 288 @@ -157,6 +157,9 @@ Set Y + + true + @@ -164,6 +167,9 @@ Set X + + true + @@ -240,6 +246,9 @@ Set Y + + true + @@ -247,6 +256,9 @@ Set X + + true + diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index b87b5d85b..5d16b801b 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -12,6 +12,7 @@ #include "gba/video.h" #include "gba/renderers/video-software.h" #include "util/configuration.h" +#include "util/formatting.h" #include "util/vfs.h" #if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__) @@ -233,10 +234,71 @@ void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configura if (context->joystick) { GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config); #if SDL_VERSION_ATLEAST(2, 0, 0) - GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(context->joystick)); + const char* name = SDL_JoystickName(context->joystick); #else - GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(SDL_JoystickIndex(context->joystick))); + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); #endif + GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, name); + + const char* value; + char* end; + int axis; + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisX", name); + if (value) { + axis = strtol(value, &end, 0); + if (end && !*end) { + context->rotation.axisX = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisY", name); + if (value) { + axis = strtol(value, &end, 0); + if (end && !*end) { + context->rotation.axisY = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisX", name); + if (value) { + axis = strtol(value, &end, 0); + if (end && !*end) { + context->rotation.gyroX = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisY", name); + if (value) { + axis = strtol(value, &end, 0); + if (end && !*end) { + context->rotation.gyroY = axis; + } + } + value = GBAInputGetCustomValue(config, SDL_BINDING_BUTTON, "gyroSensitivity", name); + if (value) { + float sensitivity = strtof_u(value, &end); + if (end && !*end) { + context->rotation.gyroSensitivity = sensitivity; + } + } + } +} + +void GBASDLPlayerSaveConfig(const struct GBASDLPlayer* context, struct Configuration* config) { + if (context->joystick) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + const char* name = SDL_JoystickName(context->joystick); +#else + const char* name = SDL_JoystickName(SDL_JoystickIndex(context->joystick)); +#endif + char value[12]; + snprintf(value, sizeof(value), "%i", context->rotation.axisX); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisX", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.axisY); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "tiltAxisY", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.gyroX); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisX", value, name); + snprintf(value, sizeof(value), "%i", context->rotation.gyroY); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroAxisY", value, name); + snprintf(value, sizeof(value), "%g", context->rotation.gyroSensitivity); + GBAInputSetCustomValue(config, SDL_BINDING_BUTTON, "gyroSensitivity", value, name); } } diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index c82181109..fbddda439 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -78,6 +78,7 @@ void GBASDLPlayerChangeJoystick(struct GBASDLEvents*, struct GBASDLPlayer*, size void GBASDLInitBindings(struct GBAInputMap* inputMap); void GBASDLPlayerLoadConfig(struct GBASDLPlayer*, const struct Configuration*); +void GBASDLPlayerSaveConfig(const struct GBASDLPlayer*, struct Configuration*); void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const union SDL_Event* event);