diff --git a/input/drivers/sdl3_input.c b/input/drivers/sdl3_input.c index f10fbf7cdfc..57c6a49ba2e 100644 --- a/input/drivers/sdl3_input.c +++ b/input/drivers/sdl3_input.c @@ -75,6 +75,11 @@ typedef struct sdl3_input float x; float y; } touches[SDL3_MAX_TOUCH]; + + /* Sensors. Used if the SDL3 joypad driver isn't active. */ + SDL_Sensor *accel; + SDL_Sensor *gyro; + bool sensors_init; } sdl3_input_t; /* Rebuilt on SDL_EVENT_KEYMAP_CHANGED (e.g. system layout switch). */ @@ -445,20 +450,86 @@ static void sdl3_input_free(void *data) SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED); SDL_FlushEvents(SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS); + if (sdl->accel) + SDL_CloseSensor(sdl->accel); + if (sdl->gyro) + SDL_CloseSensor(sdl->gyro); + if (sdl->sensors_init) + SDL_QuitSubSystem(SDL_INIT_SENSOR); + SDL_QuitSubSystem(SDL_INIT_EVENTS); free(sdl); } +/* Opens the first sensor of the given type. */ +static SDL_Sensor *sdl3_open_sensor(SDL_SensorType type) +{ + int i; + int num_sensors = 0; + SDL_Sensor *sensor = NULL; + SDL_SensorID *sensors = SDL_GetSensors(&num_sensors); + + if (!sensors) + return NULL; + + for (i = 0; i < num_sensors; i++) + { + if (SDL_GetSensorTypeForID(sensors[i]) == type) + { + sensor = SDL_OpenSensor(sensors[i]); + break; + } + } + + SDL_free(sensors); + return sensor; +} + +/* Enables the accelerometer/gyroscope. */ static bool sdl3_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned rate) { - /* Sensors are not exposed through the SDL3 keyboard/mouse driver. - * Gamepad gyro/accel are handled by the SDL3 joypad driver. */ + sdl3_input_t *sdl = (sdl3_input_t*)data; + + /* The host device's sensors only ever map to port 0. */ + if (port != 0) + return false; + switch (action) { - case RETRO_SENSOR_ILLUMINANCE_DISABLE: - case RETRO_SENSOR_GYROSCOPE_DISABLE: + case RETRO_SENSOR_ACCELEROMETER_ENABLE: + case RETRO_SENSOR_GYROSCOPE_ENABLE: + { + bool accelerometer = action == RETRO_SENSOR_ACCELEROMETER_ENABLE; + SDL_Sensor **sensor = accelerometer ? &sdl->accel : &sdl->gyro; + + if (*sensor) + return true; + + /* Make sure the Sensor subsystem is available. */ + if (!sdl->sensors_init) + { + if (!SDL_InitSubSystem(SDL_INIT_SENSOR)) + return false; + sdl->sensors_init = true; + } + + return (*sensor = sdl3_open_sensor(accelerometer ? SDL_SENSOR_ACCEL : SDL_SENSOR_GYRO)) != NULL; + } case RETRO_SENSOR_ACCELEROMETER_DISABLE: + case RETRO_SENSOR_GYROSCOPE_DISABLE: + { + SDL_Sensor **sensor = action == RETRO_SENSOR_ACCELEROMETER_DISABLE + ? &sdl->accel : &sdl->gyro; + + if (*sensor) + { + SDL_CloseSensor(*sensor); + *sensor = NULL; + } + return true; + } + case RETRO_SENSOR_ILLUMINANCE_DISABLE: /* Disabling an unsupported sensor shouldn't fail. */ return true; default: @@ -468,6 +539,31 @@ static bool sdl3_set_sensor_state(void *data, unsigned port, return false; } +static float sdl3_get_sensor_input(void *data, unsigned port, unsigned id) +{ + sdl3_input_t *sdl = (sdl3_input_t*)data; + float v[3]; + + /* The host device's sensors only ever map to port 0. */ + if (port != 0) + return 0.0f; + + /* Acceleration is m/s^2, though libretro expects gravity. The + * gyroscope uses radians per second. */ + if (id <= RETRO_SENSOR_ACCELEROMETER_Z) + { + if (sdl->accel && SDL_GetSensorData(sdl->accel, v, 3)) + return v[id - RETRO_SENSOR_ACCELEROMETER_X] / SDL_STANDARD_GRAVITY; + } + else if (id >= RETRO_SENSOR_GYROSCOPE_X && id <= RETRO_SENSOR_GYROSCOPE_Z) + { + if (sdl->gyro && SDL_GetSensorData(sdl->gyro, v, 3)) + return v[id - RETRO_SENSOR_GYROSCOPE_X]; + } + + return 0.0f; +} + /* Gets the SDL_Window, if it exists. */ static SDL_Window *sdl3_input_window(void) { @@ -676,6 +772,10 @@ static void sdl3_input_poll(void *data) sdl3_poll_mouse(sdl); sdl3_poll_touch(sdl); + /* SDL_UpdateSensors works without a focused window. */ + if (sdl->accel || sdl->gyro) + SDL_UpdateSensors(); + sdl->mouse_wu = false; sdl->mouse_wd = false; sdl->mouse_wl = false; @@ -742,6 +842,8 @@ static void sdl3_input_poll(void *data) * keys) get dropped along with them. */ SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED); SDL_FlushEvents(SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS); + if (sdl->sensors_init) + SDL_FlushEvent(SDL_EVENT_SENSOR_UPDATE); } static void sdl3_grab_mouse(void *data, bool state) @@ -773,7 +875,7 @@ input_driver_t input_sdl3 = { sdl3_input_state, sdl3_input_free, sdl3_set_sensor_state, - NULL, /* get_sensor_input */ + sdl3_get_sensor_input, sdl3_get_capabilities, "sdl3", sdl3_grab_mouse, diff --git a/input/drivers_joypad/sdl3_joypad.c b/input/drivers_joypad/sdl3_joypad.c index cb3d6585647..c494eb98b90 100644 --- a/input/drivers_joypad/sdl3_joypad.c +++ b/input/drivers_joypad/sdl3_joypad.c @@ -41,6 +41,8 @@ typedef struct _sdl3_joypad unsigned num_hats; uint16_t rumble_gain; /* 0-100 */ uint16_t rumble[2]; /* raw magnitude per retro_rumble_effect (strong/weak) */ + bool sensor_accel; /* Whether or not the sensor has been connected. */ + bool sensor_gyro; } sdl3_joypad_t; /** @@ -252,6 +254,24 @@ static void sdl3_joypad_connect(SDL_JoystickID jid) pad->num_buttons = (num_buttons > 0) ? (unsigned)num_buttons : 0; pad->num_hats = (num_hats > 0) ? (unsigned)num_hats : 0; } + + if (gamepad) + { + bool has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); + bool has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); + if (has_accel || has_gyro) + RARCH_LOG("[SDL3] Pad #%d: found sensors (accel=%s, gyro=%s).\n", + slot, + has_accel ? "yes" : "no", + has_gyro ? "yes" : "no"); + + /* Restore any sensor state that was requested before connecting, + * e.g. when the pad is unplugged and plugged back in. */ + if (has_accel && pad->sensor_accel) + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, true); + if (has_gyro && pad->sensor_gyro) + SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, true); + } } static void sdl3_joypad_disconnect(SDL_JoystickID jid) @@ -270,7 +290,14 @@ static void sdl3_joypad_disconnect(SDL_JoystickID jid) input_autoconfigure_disconnect(i, sdl3_joypad.ident); - memset(&sdl3_joypads[i], 0, sizeof(sdl3_joypads[i])); + /* Keep the requested sensor state so it can be restored on reconnect. */ + { + bool sensor_accel = sdl3_joypads[i].sensor_accel; + bool sensor_gyro = sdl3_joypads[i].sensor_gyro; + memset(&sdl3_joypads[i], 0, sizeof(sdl3_joypads[i])); + sdl3_joypads[i].sensor_accel = sensor_accel; + sdl3_joypads[i].sensor_gyro = sensor_gyro; + } return; } } @@ -547,6 +574,9 @@ static bool sdl3_joypad_set_rumble(unsigned pad, /** * Enables or disables a sensor on the specified gamepad. * + * The requested state is remembered so it can be re-applied if the + * pad disconnects and reconnects. + * * @param pad Index of the gamepad. * @param action Sensor action to perform (enable/disable gyroscope or accelerometer). * @param rate Requested sensor update rate (unused). @@ -555,27 +585,40 @@ static bool sdl3_joypad_set_rumble(unsigned pad, static bool sdl3_joypad_set_sensor_state(unsigned pad, enum retro_sensor_action action, unsigned rate) { + SDL_Gamepad *gamepad; + if (pad >= MAX_USERS) return false; - if (!sdl3_joypads[pad].gamepad) - return false; + gamepad = sdl3_joypads[pad].gamepad; switch (action) { case RETRO_SENSOR_GYROSCOPE_ENABLE: case RETRO_SENSOR_GYROSCOPE_DISABLE: - if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO)) - return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_GYRO, - action == RETRO_SENSOR_GYROSCOPE_ENABLE); - return false; + { + bool enable = action == RETRO_SENSOR_GYROSCOPE_ENABLE; + sdl3_joypads[pad].sensor_gyro = enable; + if (gamepad && SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO)) + return SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, enable); + /* Disabling a missing sensor shouldn't fail. */ + return !enable; + } case RETRO_SENSOR_ACCELEROMETER_ENABLE: case RETRO_SENSOR_ACCELEROMETER_DISABLE: - if (SDL_GamepadHasSensor(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL)) - return SDL_SetGamepadSensorEnabled(sdl3_joypads[pad].gamepad, SDL_SENSOR_ACCEL, - action == RETRO_SENSOR_ACCELEROMETER_ENABLE); - return false; + { + bool enable = action == RETRO_SENSOR_ACCELEROMETER_ENABLE; + sdl3_joypads[pad].sensor_accel = enable; + if (gamepad && SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL)) + return SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_ACCEL, enable); + /* Disabling a missing sensor shouldn't fail. */ + return !enable; + } + + case RETRO_SENSOR_ILLUMINANCE_DISABLE: + /* Disabling an unsupported sensor shouldn't fail. */ + return true; default: return false;