diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 1e300f4b09..9114c2f2cb 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -616,6 +616,18 @@ extern "C" { */ #define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" +/** + * \brief Controls whether the device's built-in accelerometer and gyro should be used as sensors for gamepads. + * + * The variable can be set to the following values: + * "auto" - Sensor fusion is enabled for known wraparound controllers like the Razer Kishi and Backbone One + * "0" - Sensor fusion is disabled + * "1" - Sensor fusion is enabled for all controllers that lack sensors + * + * The default value is "auto". This hint is checked when a gamepad is opened. + */ +#define SDL_HINT_GAMECONTROLLER_SENSOR_FUSION "SDL_GAMECONTROLLER_SENSOR_FUSION" + /** * \brief A variable controlling whether grabbing input grabs the keyboard * diff --git a/src/SDL_hints.c b/src/SDL_hints.c index 42e61ccf7e..e813a6f97a 100644 --- a/src/SDL_hints.c +++ b/src/SDL_hints.c @@ -171,6 +171,23 @@ const char *SDL_GetHint(const char *name) return env; } +int SDL_GetStringInteger(const char *value, int default_value) +{ + if (value == NULL || !*value) { + return default_value; + } + if (*value == '0' || SDL_strcasecmp(value, "false") == 0) { + return 0; + } + if (*value == '1' || SDL_strcasecmp(value, "true") == 0) { + return 1; + } + if (*value == '-' || SDL_isdigit(*value)) { + return SDL_atoi(value); + } + return default_value; +} + SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value) { if (value == NULL || !*value) { diff --git a/src/SDL_hints_c.h b/src/SDL_hints_c.h index 578fbe0f94..e71314781d 100644 --- a/src/SDL_hints_c.h +++ b/src/SDL_hints_c.h @@ -26,5 +26,6 @@ #define SDL_hints_c_h_ extern SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value); +extern int SDL_GetStringInteger(const char *value, int default_value); #endif /* SDL_hints_c_h_ */ diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 2880786eac..01b1335618 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -444,6 +444,19 @@ static int SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event) SDL_PushEvent(&deviceevent); } } break; + case SDL_EVENT_SENSOR_UPDATE: + { + SDL_LockJoysticks(); + for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) { + if (gamepad->joystick->accel && gamepad->joystick->accel_sensor == event->sensor.which) { + SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_ACCEL, event->sensor.sensor_timestamp, event->sensor.data, SDL_arraysize(event->sensor.data)); + } + if (gamepad->joystick->gyro && gamepad->joystick->gyro_sensor == event->sensor.which) { + SDL_SendJoystickSensor(event->common.timestamp, gamepad->joystick, SDL_SENSOR_GYRO, event->sensor.sensor_timestamp, event->sensor.data, SDL_arraysize(event->sensor.data)); + } + } + SDL_UnlockJoysticks(); + } break; default: break; } @@ -2523,22 +2536,50 @@ int SDL_SetGamepadSensorEnabled(SDL_Gamepad *gamepad, SDL_SensorType type, SDL_b return 0; } - if (enabled) { - if (joystick->nsensors_enabled == 0) { - if (joystick->driver->SetSensorsEnabled(joystick, SDL_TRUE) < 0) { + if (type == SDL_SENSOR_ACCEL && joystick->accel_sensor) { + if (enabled) { + joystick->accel = SDL_OpenSensor(joystick->accel_sensor); + if (!joystick->accel) { SDL_UnlockJoysticks(); return -1; } + } else { + if (joystick->accel) { + SDL_CloseSensor(joystick->accel); + joystick->accel = NULL; + } + } + } else if (type == SDL_SENSOR_GYRO && joystick->gyro_sensor) { + if (enabled) { + joystick->gyro = SDL_OpenSensor(joystick->gyro_sensor); + if (!joystick->gyro) { + SDL_UnlockJoysticks(); + return -1; + } + } else { + if (joystick->gyro) { + SDL_CloseSensor(joystick->gyro); + joystick->gyro = NULL; + } } - ++joystick->nsensors_enabled; } else { - if (joystick->nsensors_enabled == 1) { - if (joystick->driver->SetSensorsEnabled(joystick, SDL_FALSE) < 0) { - SDL_UnlockJoysticks(); - return -1; + if (enabled) { + if (joystick->nsensors_enabled == 0) { + if (joystick->driver->SetSensorsEnabled(joystick, SDL_TRUE) < 0) { + SDL_UnlockJoysticks(); + return -1; + } } + ++joystick->nsensors_enabled; + } else { + if (joystick->nsensors_enabled == 1) { + if (joystick->driver->SetSensorsEnabled(joystick, SDL_FALSE) < 0) { + SDL_UnlockJoysticks(); + return -1; + } + } + --joystick->nsensors_enabled; } - --joystick->nsensors_enabled; } sensor->enabled = enabled; diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 59a9bbdda7..01acbcdd99 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -31,6 +31,7 @@ #include "../events/SDL_events_c.h" #endif #include "../video/SDL_sysvideo.h" +#include "../sensor/SDL_sensor_c.h" #include "hidapi/SDL_hidapijoystick_c.h" /* This is included in only one place because it has a large static list of controllers */ @@ -510,6 +511,166 @@ static SDL_bool SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick) #endif /* __WINRT__ */ } +static SDL_bool IsBackboneOne(SDL_Joystick *joystick) +{ + if (joystick->name && SDL_strstr(joystick->name, "Backbone One")) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +static SDL_bool IsROGAlly(SDL_Joystick *joystick) +{ + Uint16 vendor, product; + SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick); + + /* The ROG Ally controller spoofs an Xbox 360 controller */ + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + if (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) { + /* Check to see if this system has the expected sensors */ + SDL_bool has_ally_accel = SDL_FALSE; + SDL_bool has_ally_gyro = SDL_FALSE; + + if (SDL_InitSubSystem(SDL_INIT_SENSOR) == 0) { + SDL_SensorID *sensors = SDL_GetSensors(NULL); + if (sensors) { + int i; + for (i = 0; sensors[i]; ++i) { + SDL_SensorID sensor = sensors[i]; + + if (!has_ally_accel && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_ACCEL) { + const char *sensor_name = SDL_GetSensorInstanceName(sensor); + if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Acc") == 0) { + has_ally_accel = SDL_TRUE; + } + } + if (!has_ally_gyro && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_GYRO) { + const char *sensor_name = SDL_GetSensorInstanceName(sensor); + if (sensor_name && SDL_strcmp(sensor_name, "Sensor BMI320 Gyr") == 0) { + has_ally_gyro = SDL_TRUE; + } + } + } + SDL_free(sensors); + } + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + if (has_ally_accel && has_ally_gyro) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static SDL_bool ShouldAttemptSensorFusion(SDL_Joystick *joystick) +{ + static Uint32 wraparound_gamepads[] = { + MAKE_VIDPID(0x1949, 0x0402), /* Ipega PG-9083S */ + MAKE_VIDPID(0x27f8, 0x0bbc), /* Gamevice */ + MAKE_VIDPID(0x27f8, 0x0bbf), /* Razer Kishi */ + }; + SDL_JoystickGUID guid; + Uint16 vendor, product; + Uint32 vidpid; + int i; + int hint; + + /* The SDL controller sensor API is only available for gamepads (at the moment) */ + if (!joystick->is_gamepad) { + return SDL_FALSE; + } + + /* If the controller already has sensors, use those */ + if (joystick->nsensors > 0) { + return SDL_FALSE; + } + + hint = SDL_GetStringInteger(SDL_GetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION), -1); + if (hint > 0) { + return SDL_TRUE; + } + if (hint == 0) { + return SDL_FALSE; + } + + /* See if the controller is in our list of wraparound gamepads */ + guid = SDL_GetJoystickGUID(joystick); + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL); + vidpid = MAKE_VIDPID(vendor, product); + for (i = 0; i < SDL_arraysize(wraparound_gamepads); ++i) { + if (vidpid == wraparound_gamepads[i]) { + return SDL_TRUE; + } + } + + /* See if this is another known wraparound gamepad */ + if (IsBackboneOne(joystick) || IsROGAlly(joystick)) { + return SDL_TRUE; + } + + return SDL_FALSE; +} + +static void AttemptSensorFusion(SDL_Joystick *joystick) +{ + SDL_SensorID *sensors; + int i; + + if (SDL_InitSubSystem(SDL_INIT_SENSOR) < 0) { + return; + } + + sensors = SDL_GetSensors(NULL); + if (sensors) { + for (i = 0; sensors[i]; ++i) { + SDL_SensorID sensor = sensors[i]; + + if (!joystick->accel_sensor && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_ACCEL) { + /* Increment the sensor subsystem reference count */ + SDL_InitSubSystem(SDL_INIT_SENSOR); + + joystick->accel_sensor = sensor; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f); + } + if (!joystick->gyro_sensor && SDL_GetSensorInstanceType(sensor) == SDL_SENSOR_GYRO) { + /* Increment the sensor subsystem reference count */ + SDL_InitSubSystem(SDL_INIT_SENSOR); + + joystick->gyro_sensor = sensor; + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f); + } + } + SDL_free(sensors); + } + SDL_QuitSubSystem(SDL_INIT_SENSOR); +} + +static void CleanupSensorFusion(SDL_Joystick *joystick) +{ + if (joystick->accel_sensor || joystick->gyro_sensor) { + if (joystick->accel_sensor) { + if (joystick->accel) { + SDL_CloseSensor(joystick->accel); + joystick->accel = NULL; + } + joystick->accel_sensor = 0; + + /* Decrement the sensor subsystem reference count */ + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + if (joystick->gyro_sensor) { + if (joystick->gyro) { + SDL_CloseSensor(joystick->gyro); + joystick->gyro = NULL; + } + joystick->gyro_sensor = 0; + + /* Decrement the sensor subsystem reference count */ + SDL_QuitSubSystem(SDL_INIT_SENSOR); + } + } +} + /* * Open a joystick for use - the index passed as an argument refers to * the N'th joystick on the system. This index is the value which will @@ -611,6 +772,11 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id) joystick->is_gamepad = SDL_IsGamepad(instance_id); + /* Use system gyro and accelerometer if the gamepad doesn't have built-in sensors */ + if (ShouldAttemptSensorFusion(joystick)) { + AttemptSensorFusion(joystick); + } + /* Add joystick to list */ ++joystick->ref_count; /* Link the joystick in the list */ @@ -1253,6 +1419,8 @@ void SDL_CloseJoystick(SDL_Joystick *joystick) SDL_RumbleJoystickTriggers(joystick, 0, 0, 0); } + CleanupSensorFusion(joystick); + joystick->driver->Close(joystick); joystick->hwdata = NULL; joystick->magic = NULL; @@ -1273,11 +1441,10 @@ void SDL_CloseJoystick(SDL_Joystick *joystick) joysticklist = joysticklist->next; } + /* Free the data associated with this joystick */ SDL_free(joystick->name); SDL_free(joystick->path); SDL_free(joystick->serial); - - /* Free the data associated with this joystick */ SDL_free(joystick->axes); SDL_free(joystick->hats); SDL_free(joystick->buttons); @@ -1682,6 +1849,16 @@ void SDL_UpdateJoysticks(void) for (joystick = SDL_joysticks; joystick; joystick = joystick->next) { if (joystick->attached) { + if (joystick->accel || joystick->gyro) { + SDL_LockSensors(); + if (joystick->gyro) { + SDL_UpdateSensor(joystick->gyro); + } + if (joystick->accel) { + SDL_UpdateSensor(joystick->accel); + } + SDL_UnlockSensors(); + } joystick->driver->Update(joystick); if (joystick->delayed_guide_button) { diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index bab50f0dda..4b2225ccdd 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -113,6 +113,11 @@ struct SDL_Joystick SDL_bool delayed_guide_button _guarded; /* SDL_TRUE if this device has the guide button event delayed */ SDL_JoystickPowerLevel epowerlevel _guarded; /* power level of this joystick, SDL_JOYSTICK_POWER_UNKNOWN if not supported */ + SDL_SensorID accel_sensor _guarded; + SDL_Sensor *accel _guarded; + SDL_SensorID gyro_sensor _guarded; + SDL_Sensor *gyro _guarded; + struct SDL_JoystickDriver *driver _guarded; struct joystick_hwdata *hwdata _guarded; /* Driver dependent information */ diff --git a/src/sensor/SDL_sensor.c b/src/sensor/SDL_sensor.c index 659120662f..d3eb6beee9 100644 --- a/src/sensor/SDL_sensor.c +++ b/src/sensor/SDL_sensor.c @@ -504,6 +504,11 @@ int SDL_SendSensorUpdate(Uint64 timestamp, SDL_Sensor *sensor, Uint64 sensor_tim return posted; } +void SDL_UpdateSensor(SDL_Sensor *sensor) +{ + sensor->driver->Update(sensor); +} + void SDL_UpdateSensors(void) { int i; diff --git a/src/sensor/SDL_sensor_c.h b/src/sensor/SDL_sensor_c.h index 6d6e598209..3284591778 100644 --- a/src/sensor/SDL_sensor_c.h +++ b/src/sensor/SDL_sensor_c.h @@ -40,6 +40,9 @@ extern void SDL_UnlockSensors(void); /* Function to return whether there are any sensors opened by the application */ extern SDL_bool SDL_SensorsOpened(void); +/* Update an individual sensor, used by gamepad sensor fusion */ +extern void SDL_UpdateSensor(SDL_Sensor *sensor); + /* Internal event queueing functions */ extern int SDL_SendSensorUpdate(Uint64 timestamp, SDL_Sensor *sensor, Uint64 sensor_timestamp, float *data, int num_values);