From c1ba31118b9033f738c99e95147bb8168f0fe075 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 9 May 2024 17:36:15 -0700 Subject: [PATCH] Added ball, touchpad, and sensor support for virtual joysticks Fixes https://github.com/libsdl-org/SDL/issues/9542 --- include/SDL3/SDL_events.h | 2 +- include/SDL3/SDL_gamepad.h | 4 +- include/SDL3/SDL_joystick.h | 126 +++++++++- src/dynapi/SDL_dynapi.sym | 3 + src/dynapi/SDL_dynapi_overrides.h | 3 + src/dynapi/SDL_dynapi_procs.h | 3 + src/joystick/SDL_joystick.c | 57 +++++ src/joystick/virtual/SDL_virtualjoystick.c | 243 +++++++++++++++++-- src/joystick/virtual/SDL_virtualjoystick_c.h | 42 +++- test/gamepadutils.c | 14 ++ test/gamepadutils.h | 1 + test/testcontroller.c | 37 +++ 12 files changed, 494 insertions(+), 41 deletions(-) diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 1ef15bb3ca..6757f036e7 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -48,7 +48,7 @@ extern "C" { /* General keyboard/mouse state definitions */ #define SDL_RELEASED 0 -#define SDL_PRESSED 1 +#define SDL_PRESSED 1 /** * The types of events that can be delivered. diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h index f3010e8d87..36904e4787 100644 --- a/include/SDL3/SDL_gamepad.h +++ b/include/SDL3/SDL_gamepad.h @@ -1240,8 +1240,8 @@ extern DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepa * \param touchpad a touchpad * \param finger a finger * \param state filled with state - * \param x filled with x position - * \param y filled with y position + * \param x filled with x position, normalized 0 to 1, with the origin in the upper left + * \param y filled with y position, normalized 0 to 1, with the origin in the upper left * \param pressure filled with pressure value * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h index 9c759f063b..2a95438535 100644 --- a/include/SDL3/SDL_joystick.h +++ b/include/SDL3/SDL_joystick.h @@ -44,6 +44,7 @@ #include #include #include +#include #include /* Set up for C function definitions, even when using C++ */ @@ -391,28 +392,62 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_GetJoystickFromInstanceID(SDL_Joystick extern DECLSPEC SDL_Joystick *SDLCALL SDL_GetJoystickFromPlayerIndex(int player_index); /** - * The structure that defines an extended virtual joystick description + * The structure that describes a virtual joystick touchpad. + * + * \since This struct is available since SDL 3.0.0. + * + * \sa SDL_VirtualJoystickDesc + */ +typedef struct SDL_VirtualJoystickTouchpadDesc +{ + Uint16 nfingers; /**< the number of simultaneous fingers on this touchpad */ + Uint16 padding[3]; +} SDL_VirtualJoystickTouchpadDesc; + +/** + * The structure that describes a virtual joystick sensor. + * + * \since This struct is available since SDL 3.0.0. + * + * \sa SDL_VirtualJoystickDesc + */ +typedef struct SDL_VirtualJoystickSensorDesc +{ + SDL_SensorType type; /**< the type of this sensor */ + float rate; /**< the update frequency of this sensor, may be 0.0f */ +} SDL_VirtualJoystickSensorDesc; + +/** + * The structure that describes a virtual joystick. * * All elements of this structure are optional and can be left 0. * * \since This struct is available since SDL 3.0.0. * * \sa SDL_AttachVirtualJoystick + * \sa SDL_VirtualJoystickSensorDesc + * \sa SDL_VirtualJoystickTouchpadDesc */ typedef struct SDL_VirtualJoystickDesc { Uint16 type; /**< `SDL_JoystickType` */ - Uint16 naxes; /**< the number of axes on this joystick */ - Uint16 nbuttons; /**< the number of buttons on this joystick */ - Uint16 nhats; /**< the number of hats on this joystick */ + Uint16 padding; /**< unused */ Uint16 vendor_id; /**< the USB vendor ID of this joystick */ Uint16 product_id; /**< the USB product ID of this joystick */ - Uint16 padding; /**< unused */ + Uint16 naxes; /**< the number of axes on this joystick */ + Uint16 nbuttons; /**< the number of buttons on this joystick */ + Uint16 nballs; /**< the number of balls on this joystick */ + Uint16 nhats; /**< the number of hats on this joystick */ + Uint16 ntouchpads; /**< the number of touchpads on this joystick, requires `touchpads` to point at valid descriptions */ + Uint16 nsensors; /**< the number of sensors on this joystick, requires `sensors` to point at valid descriptions */ + Uint16 padding2[2]; /**< unused */ Uint32 button_mask; /**< A mask of which buttons are valid for this controller - e.g. (1u << SDL_GAMEPAD_BUTTON_SOUTH) */ + e.g. (1 << SDL_GAMEPAD_BUTTON_SOUTH) */ Uint32 axis_mask; /**< A mask of which axes are valid for this controller - e.g. (1u << SDL_GAMEPAD_AXIS_LEFTX) */ + e.g. (1 << SDL_GAMEPAD_AXIS_LEFTX) */ const char *name; /**< the name of the joystick */ + const SDL_VirtualJoystickTouchpadDesc *touchpads; /**< A pointer to an array of touchpad descriptions, required if `ntouchpads` is > 0 */ + const SDL_VirtualJoystickSensorDesc *sensors; /**< A pointer to an array of sensor descriptions, required if `nsensors` is > 0 */ void *userdata; /**< User data pointer passed to callbacks */ void (SDLCALL *Update)(void *userdata); /**< Called when the joystick state should be updated */ @@ -421,6 +456,7 @@ typedef struct SDL_VirtualJoystickDesc int (SDLCALL *RumbleTriggers)(void *userdata, Uint16 left_rumble, Uint16 right_rumble); /**< Implements SDL_RumbleJoystickTriggers() */ int (SDLCALL *SetLED)(void *userdata, Uint8 red, Uint8 green, Uint8 blue); /**< Implements SDL_SetJoystickLED() */ int (SDLCALL *SendEffect)(void *userdata, const void *data, int size); /**< Implements SDL_SendJoystickEffect() */ + int (SDLCALL *SetSensorsEnabled)(void *userdata, SDL_bool enabled); /**< Implements SDL_SetGamepadSensorEnabled() */ } SDL_VirtualJoystickDesc; /** @@ -461,7 +497,7 @@ extern DECLSPEC int SDLCALL SDL_DetachVirtualJoystick(SDL_JoystickID instance_id extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_id); /** - * Set values on an opened, virtual-joystick's axis. + * Set the state of an axis on an opened virtual joystick. * * Please note that values set here will not be applied until the next call to * SDL_UpdateJoysticks, which can either be called directly, or can be called @@ -474,7 +510,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i * `SDL_JOYSTICK_AXIS_MIN`. * * \param joystick the virtual joystick on which to set state. - * \param axis the specific axis on the virtual joystick to set. + * \param axis the index of the axis on the virtual joystick to update. * \param value the new value for the specified axis. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. @@ -484,7 +520,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value); /** - * Set values on an opened, virtual-joystick's button. + * Generate ball motion on an opened virtual joystick. * * Please note that values set here will not be applied until the next call to * SDL_UpdateJoysticks, which can either be called directly, or can be called @@ -493,7 +529,27 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, i * SDL_WaitEvent. * * \param joystick the virtual joystick on which to set state. - * \param button the specific button on the virtual joystick to set. + * \param ball the index of the ball on the virtual joystick to update. + * \param xrel the relative motion on the X axis. + * \param yrel the relative motion on the Y axis. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel); + +/** + * Set the state of a button on an opened virtual joystick. + * + * Please note that values set here will not be applied until the next call to + * SDL_UpdateJoysticks, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * \param joystick the virtual joystick on which to set state. + * \param button the index of the button on the virtual joystick to update. * \param value the new value for the specified button. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. @@ -503,7 +559,7 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, i extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, Uint8 value); /** - * Set values on an opened, virtual-joystick's hat. + * Set the state of a hat on an opened virtual joystick. * * Please note that values set here will not be applied until the next call to * SDL_UpdateJoysticks, which can either be called directly, or can be called @@ -512,7 +568,7 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, * SDL_WaitEvent. * * \param joystick the virtual joystick on which to set state. - * \param hat the specific hat on the virtual joystick to set. + * \param hat the index of the hat on the virtual joystick to update. * \param value the new value for the specified hat. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. @@ -521,6 +577,50 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, */ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value); +/** + * Set touchpad finger state on an opened virtual joystick. + * + * Please note that values set here will not be applied until the next call to + * SDL_UpdateJoysticks, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * \param joystick the virtual joystick on which to set state. + * \param touchpad the index of the touchpad on the virtual joystick to update. + * \param finger the index of the finger on the touchpad to set. + * \param state `SDL_PRESSED` if the finger is pressed, `SDL_RELEASED` if the finger is released + * \param x the x coordinate of the finger on the touchpad, normalized 0 to 1, with the origin in the upper left + * \param y the y coordinate of the finger on the touchpad, normalized 0 to 1, with the origin in the upper left + * \param pressure the pressure of the finger + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure); + +/** + * Send a sensor update for an opened virtual joystick. + * + * Please note that values set here will not be applied until the next call to + * SDL_UpdateJoysticks, which can either be called directly, or can be called + * indirectly through various other SDL APIs, including, but not limited to + * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout, + * SDL_WaitEvent. + * + * \param joystick the virtual joystick on which to set state. + * \param type the type of the sensor on the virtual joystick to update. + * \param sensor_timestamp a 64-bit timestamp in nanoseconds associated with the sensor reading + * \param data the data associated with the sensor reading + * \param num_values the number of values pointed to by `data` + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); + /** * Get the properties associated with a joystick. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 0eec501b1e..919998f84f 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -680,6 +680,7 @@ SDL3_0.0.0 { SDL_SeekIO; SDL_SendGamepadEffect; SDL_SendJoystickEffect; + SDL_SendJoystickVirtualSensorData; SDL_SetAssertionHandler; SDL_SetAudioPostmixCallback; SDL_SetAudioStreamFormat; @@ -708,8 +709,10 @@ SDL3_0.0.0 { SDL_SetJoystickLED; SDL_SetJoystickPlayerIndex; SDL_SetJoystickVirtualAxis; + SDL_SetJoystickVirtualBall; SDL_SetJoystickVirtualButton; SDL_SetJoystickVirtualHat; + SDL_SetJoystickVirtualTouchpad; SDL_SetLogOutputFunction; SDL_SetMainReady; SDL_SetMemoryFunctions; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 7acba40750..335134bfbb 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -705,6 +705,7 @@ #define SDL_SeekIO SDL_SeekIO_REAL #define SDL_SendGamepadEffect SDL_SendGamepadEffect_REAL #define SDL_SendJoystickEffect SDL_SendJoystickEffect_REAL +#define SDL_SendJoystickVirtualSensorData SDL_SendJoystickVirtualSensorData_REAL #define SDL_SetAssertionHandler SDL_SetAssertionHandler_REAL #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL @@ -732,8 +733,10 @@ #define SDL_SetJoystickLED SDL_SetJoystickLED_REAL #define SDL_SetJoystickPlayerIndex SDL_SetJoystickPlayerIndex_REAL #define SDL_SetJoystickVirtualAxis SDL_SetJoystickVirtualAxis_REAL +#define SDL_SetJoystickVirtualBall SDL_SetJoystickVirtualBall_REAL #define SDL_SetJoystickVirtualButton SDL_SetJoystickVirtualButton_REAL #define SDL_SetJoystickVirtualHat SDL_SetJoystickVirtualHat_REAL +#define SDL_SetJoystickVirtualTouchpad SDL_SetJoystickVirtualTouchpad_REAL #define SDL_SetLogOutputFunction SDL_SetLogOutputFunction_REAL #define SDL_SetMainReady SDL_SetMainReady_REAL #define SDL_SetMemoryFunctions SDL_SetMemoryFunctions_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ba3e936bb5..4a9047bc26 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -726,6 +726,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ScreenSaverEnabled,(void),(),return) SDL_DYNAPI_PROC(Sint64,SDL_SeekIO,(SDL_IOStream *a, Sint64 b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SendGamepadEffect,(SDL_Gamepad *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SendJoystickEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SendJoystickVirtualSensorData,(SDL_Joystick *a, SDL_SensorType b, Uint64 c, const float *d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(void,SDL_SetAssertionHandler,(SDL_AssertionHandler a, void *b),(a,b),) SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, const SDL_AudioSpec *b, const SDL_AudioSpec *c),(a,b,c),return) @@ -752,8 +753,10 @@ SDL_DYNAPI_PROC(void,SDL_SetJoystickEventsEnabled,(SDL_bool a),(a),) SDL_DYNAPI_PROC(int,SDL_SetJoystickLED,(SDL_Joystick *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_SetJoystickPlayerIndex,(SDL_Joystick *a, int b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualAxis,(SDL_Joystick *a, int b, Sint16 c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualBall,(SDL_Joystick *a, int b, Sint16 c, Sint16 d),(a,b,c,d),return) SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualButton,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualHat,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualTouchpad,(SDL_Joystick *a, int b, int c, Uint8 d, float e, float f, float g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(void,SDL_SetLogOutputFunction,(SDL_LogOutputFunction a, void *b),(a,b),) SDL_DYNAPI_PROC(void,SDL_SetMainReady,(void),(),) SDL_DYNAPI_PROC(int,SDL_SetMemoryFunctions,(SDL_malloc_func a, SDL_calloc_func b, SDL_realloc_func c, SDL_free_func d),(a,b,c,d),return) diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index fbd67bbd7c..1b7115fa05 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -1235,6 +1235,25 @@ int SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value) return retval; } +int SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel) +{ + int retval; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + +#ifdef SDL_JOYSTICK_VIRTUAL + retval = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel); +#else + retval = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return retval; +} + int SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, Uint8 value) { int retval; @@ -1273,6 +1292,44 @@ int SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value) return retval; } +int SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure) +{ + int retval; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + +#ifdef SDL_JOYSTICK_VIRTUAL + retval = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, state, x, y, pressure); +#else + retval = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return retval; +} + +int SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values) +{ + int retval; + + SDL_LockJoysticks(); + { + CHECK_JOYSTICK_MAGIC(joystick, -1); + +#ifdef SDL_JOYSTICK_VIRTUAL + retval = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values); +#else + retval = SDL_SetError("SDL not built with virtual-joystick support"); +#endif + } + SDL_UnlockJoysticks(); + + return retval; +} + /* * Checks to make sure the joystick is valid. */ diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c index f6e1930819..6165badde4 100644 --- a/src/joystick/virtual/SDL_virtualjoystick.c +++ b/src/joystick/virtual/SDL_virtualjoystick.c @@ -102,6 +102,26 @@ static void VIRTUAL_FreeHWData(joystick_hwdata *hwdata) SDL_free(hwdata->hats); hwdata->hats = NULL; } + if (hwdata->balls) { + SDL_free(hwdata->balls); + hwdata->balls = NULL; + } + if (hwdata->touchpads) { + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + SDL_free(hwdata->touchpads[i].fingers); + hwdata->touchpads[i].fingers = NULL; + } + SDL_free(hwdata->touchpads); + hwdata->touchpads = NULL; + } + if (hwdata->sensors) { + SDL_free(hwdata->sensors); + hwdata->sensors = NULL; + } + if (hwdata->sensor_events) { + SDL_free(hwdata->sensor_events); + hwdata->sensor_events = NULL; + } SDL_free(hwdata); } @@ -123,7 +143,9 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des VIRTUAL_FreeHWData(hwdata); return 0; } - SDL_memcpy(&hwdata->desc, desc, sizeof(*desc)); + SDL_copyp(&hwdata->desc, desc); + hwdata->desc.touchpads = NULL; + hwdata->desc.sensors = NULL; if (hwdata->desc.name) { name = hwdata->desc.name; @@ -203,7 +225,7 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des /* Allocate fields for different control-types */ if (hwdata->desc.naxes > 0) { - hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(Sint16)); + hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(*hwdata->axes)); if (!hwdata->axes) { VIRTUAL_FreeHWData(hwdata); return 0; @@ -218,19 +240,64 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des } } if (hwdata->desc.nbuttons > 0) { - hwdata->buttons = (Uint8 *)SDL_calloc(hwdata->desc.nbuttons, sizeof(Uint8)); + hwdata->buttons = (Uint8 *)SDL_calloc(hwdata->desc.nbuttons, sizeof(*hwdata->buttons)); if (!hwdata->buttons) { VIRTUAL_FreeHWData(hwdata); return 0; } } if (hwdata->desc.nhats > 0) { - hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(Uint8)); + hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(*hwdata->hats)); if (!hwdata->hats) { VIRTUAL_FreeHWData(hwdata); return 0; } } + if (hwdata->desc.nballs > 0) { + hwdata->balls = (SDL_JoystickBallData *)SDL_calloc(hwdata->desc.nballs, sizeof(*hwdata->balls)); + if (!hwdata->balls) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + if (hwdata->desc.ntouchpads > 0) { + if (!desc->touchpads) { + VIRTUAL_FreeHWData(hwdata); + SDL_SetError("desc missing touchpad descriptions"); + return 0; + } + hwdata->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(hwdata->desc.ntouchpads, sizeof(*hwdata->touchpads)); + if (!hwdata->touchpads) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_VirtualJoystickTouchpadDesc *touchpad_desc = &desc->touchpads[i]; + hwdata->touchpads[i].nfingers = touchpad_desc->nfingers; + hwdata->touchpads[i].fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(touchpad_desc->nfingers, sizeof(*hwdata->touchpads[i].fingers)); + if (!hwdata->touchpads[i].fingers) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + } + } + if (hwdata->desc.nsensors > 0) { + if (!desc->sensors) { + VIRTUAL_FreeHWData(hwdata); + SDL_SetError("desc missing sensor descriptions"); + return 0; + } + hwdata->sensors = (SDL_JoystickSensorInfo *)SDL_calloc(hwdata->desc.nsensors, sizeof(*hwdata->sensors)); + if (!hwdata->sensors) { + VIRTUAL_FreeHWData(hwdata); + return 0; + } + for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) { + const SDL_VirtualJoystickSensorDesc *sensor_desc = &desc->sensors[i]; + hwdata->sensors[i].type = sensor_desc->type; + hwdata->sensors[i].rate = sensor_desc->rate; + } + } /* Allocate an instance ID for this device */ hwdata->instance_id = SDL_GetNextObjectID(); @@ -268,17 +335,40 @@ int SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 val SDL_AssertJoysticksLocked(); if (!joystick || !joystick->hwdata) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } hwdata = (joystick_hwdata *)joystick->hwdata; if (axis < 0 || axis >= hwdata->desc.naxes) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid axis index"); } hwdata->axes[axis] = value; + hwdata->changes |= AXES_CHANGED; + + return 0; +} + +int SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (ball < 0 || ball >= hwdata->desc.nballs) { + return SDL_SetError("Invalid ball index"); + } + + hwdata->balls[ball].dx += xrel; + hwdata->balls[ball].dx = SDL_clamp(hwdata->balls[ball].dx, SDL_MIN_SINT16, SDL_MAX_SINT16); + hwdata->balls[ball].dy += yrel; + hwdata->balls[ball].dy = SDL_clamp(hwdata->balls[ball].dy, SDL_MIN_SINT16, SDL_MAX_SINT16); + hwdata->changes |= BALLS_CHANGED; return 0; } @@ -290,17 +380,16 @@ int SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 SDL_AssertJoysticksLocked(); if (!joystick || !joystick->hwdata) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } hwdata = (joystick_hwdata *)joystick->hwdata; if (button < 0 || button >= hwdata->desc.nbuttons) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid button index"); } hwdata->buttons[button] = value; + hwdata->changes |= BUTTONS_CHANGED; return 0; } @@ -312,17 +401,74 @@ int SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value) SDL_AssertJoysticksLocked(); if (!joystick || !joystick->hwdata) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid joystick"); } hwdata = (joystick_hwdata *)joystick->hwdata; if (hat < 0 || hat >= hwdata->desc.nhats) { - SDL_UnlockJoysticks(); return SDL_SetError("Invalid hat index"); } hwdata->hats[hat] = value; + hwdata->changes |= HATS_CHANGED; + + return 0; +} + +int SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (touchpad < 0 || touchpad >= hwdata->desc.ntouchpads) { + return SDL_SetError("Invalid touchpad index"); + } + if (finger < 0 || finger >= hwdata->touchpads[touchpad].nfingers) { + return SDL_SetError("Invalid finger index"); + } + + SDL_JoystickTouchpadFingerInfo *info = &hwdata->touchpads[touchpad].fingers[finger]; + info->state = state; + info->x = x; + info->y = y; + info->pressure = pressure; + hwdata->changes |= TOUCHPADS_CHANGED; + + return 0; +} + +int SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values) +{ + joystick_hwdata *hwdata; + + SDL_AssertJoysticksLocked(); + + if (!joystick || !joystick->hwdata) { + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (hwdata->num_sensor_events == hwdata->max_sensor_events) { + int new_max_sensor_events = (hwdata->max_sensor_events + 1); + VirtualSensorEvent *sensor_events = (VirtualSensorEvent *)SDL_realloc(hwdata->sensor_events, new_max_sensor_events * sizeof(*sensor_events)); + if (!sensor_events) { + return -1; + } + hwdata->sensor_events = sensor_events; + hwdata->max_sensor_events = hwdata->max_sensor_events; + } + + VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++]; + event->type = type; + event->sensor_timestamp = sensor_timestamp; + event->num_values = SDL_min(num_values, SDL_arraysize(event->data)); + SDL_memcpy(event->data, data, (event->num_values * sizeof(*event->data))); return 0; } @@ -424,6 +570,15 @@ static int VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->nhats = hwdata->desc.nhats; hwdata->joystick = joystick; + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i]; + SDL_PrivateJoystickAddTouchpad(joystick, touchpad->nfingers); + } + for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) { + const SDL_JoystickSensorInfo *sensor = &hwdata->sensors[i]; + SDL_PrivateJoystickAddSensor(joystick, sensor->type, sensor->rate); + } + if (hwdata->desc.SetLED) { SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, SDL_TRUE); } @@ -518,13 +673,30 @@ static int VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, static int VIRTUAL_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled) { - return SDL_Unsupported(); + int result; + + SDL_AssertJoysticksLocked(); + + if (joystick->hwdata) { + joystick_hwdata *hwdata = joystick->hwdata; + if (hwdata->desc.SetSensorsEnabled) { + result = hwdata->desc.SetSensorsEnabled(hwdata->desc.userdata, enabled); + } else { + result = 0; + } + if (result == 0) { + hwdata->sensors_enabled = enabled; + } + } else { + result = SDL_SetError("SetSensorsEnabled failed, device disconnected"); + } + + return result; } static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick) { joystick_hwdata *hwdata; - Uint8 i; Uint64 timestamp = SDL_GetTicksNS(); SDL_AssertJoysticksLocked(); @@ -542,15 +714,50 @@ static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick) hwdata->desc.Update(hwdata->desc.userdata); } - for (i = 0; i < hwdata->desc.naxes; ++i) { - SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]); + if (hwdata->changes & AXES_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.naxes; ++i) { + SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]); + } } - for (i = 0; i < hwdata->desc.nbuttons; ++i) { - SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]); + if (hwdata->changes & BALLS_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.nballs; ++i) { + SDL_JoystickBallData *ball = &hwdata->balls[i]; + if (ball->dx || ball->dy) { + SDL_SendJoystickBall(timestamp, joystick, i, ball->dx, ball->dy); + ball->dx = 0; + ball->dy = 0; + } + } } - for (i = 0; i < hwdata->desc.nhats; ++i) { - SDL_SendJoystickHat(timestamp, joystick, i, hwdata->hats[i]); + if (hwdata->changes & BUTTONS_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.nbuttons; ++i) { + SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]); + } } + if (hwdata->changes & HATS_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.nhats; ++i) { + SDL_SendJoystickHat(timestamp, joystick, i, hwdata->hats[i]); + } + } + if (hwdata->changes & TOUCHPADS_CHANGED) { + for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) { + const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i]; + for (int j = 0; j < touchpad->nfingers; ++j) { + const SDL_JoystickTouchpadFingerInfo *finger = &touchpad->fingers[j]; + SDL_SendJoystickTouchpad(timestamp, joystick, i, j, finger->state, finger->x, finger->y, finger->pressure); + } + } + } + if (hwdata->num_sensor_events > 0) { + if (hwdata->sensors_enabled) { + for (int i = 0; i < hwdata->num_sensor_events; ++i) { + const VirtualSensorEvent *event = &hwdata->sensor_events[i]; + SDL_SendJoystickSensor(timestamp, joystick, event->type, event->sensor_timestamp, event->data, event->num_values); + } + } + hwdata->num_sensor_events = 0; + } + hwdata->changes = 0; } static void VIRTUAL_JoystickClose(SDL_Joystick *joystick) diff --git a/src/joystick/virtual/SDL_virtualjoystick_c.h b/src/joystick/virtual/SDL_virtualjoystick_c.h index 2f16cc6c1a..5e47943392 100644 --- a/src/joystick/virtual/SDL_virtualjoystick_c.h +++ b/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -25,31 +25,59 @@ #ifdef SDL_JOYSTICK_VIRTUAL +#include "../SDL_sysjoystick.h" + +#define AXES_CHANGED 0x00000001 +#define BALLS_CHANGED 0x00000002 +#define BUTTONS_CHANGED 0x00000004 +#define HATS_CHANGED 0x00000008 +#define TOUCHPADS_CHANGED 0x00000010 + /** * Data for a virtual, software-only joystick. */ +typedef struct VirtualSensorEvent +{ + SDL_SensorType type; + Uint64 sensor_timestamp; + float data[3]; + int num_values; +} VirtualSensorEvent; + typedef struct joystick_hwdata { - SDL_JoystickType type; + SDL_JoystickID instance_id; SDL_bool attached; char *name; + SDL_JoystickType type; SDL_JoystickGUID guid; SDL_VirtualJoystickDesc desc; + Uint32 changes; Sint16 *axes; Uint8 *buttons; Uint8 *hats; - SDL_JoystickID instance_id; + SDL_JoystickBallData *balls; + SDL_JoystickTouchpadInfo *touchpads; + SDL_JoystickSensorInfo *sensors; + SDL_bool sensors_enabled; + int num_sensor_events; + int max_sensor_events; + VirtualSensorEvent *sensor_events; + SDL_Joystick *joystick; struct joystick_hwdata *next; } joystick_hwdata; -SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc); -int SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id); +extern SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc); +extern int SDL_JoystickDetachVirtualInner(SDL_JoystickID instance_id); -int SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value); -int SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 value); -int SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value); +extern int SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value); +extern int SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel); +extern int SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 value); +extern int SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value); +extern int SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure); +extern int SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values); #endif /* SDL_JOYSTICK_VIRTUAL */ diff --git a/test/gamepadutils.c b/test/gamepadutils.c index fe93cd58ce..d14d603e7b 100644 --- a/test/gamepadutils.c +++ b/test/gamepadutils.c @@ -191,6 +191,7 @@ void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area) { if (!ctx) { SDL_zerop(area); + return; } area->x = ctx->x; @@ -202,6 +203,19 @@ void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area) } } +void GetGamepadTouchpadArea(GamepadImage *ctx, SDL_Rect *area) +{ + if (!ctx) { + SDL_zerop(area); + return; + } + + area->x = (float)ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2 + touchpad_area.x; + area->y = (float)ctx->y + ctx->gamepad_height + touchpad_area.y; + area->w = (float)touchpad_area.w; + area->h = (float)touchpad_area.h; +} + void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front) { if (!ctx) { diff --git a/test/gamepadutils.h b/test/gamepadutils.h index 31813529d4..e1be51b558 100644 --- a/test/gamepadutils.h +++ b/test/gamepadutils.h @@ -54,6 +54,7 @@ enum extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer); extern void SetGamepadImagePosition(GamepadImage *ctx, int x, int y); extern void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area); +extern void GetGamepadTouchpadArea(GamepadImage *ctx, SDL_Rect *area); extern void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front); extern void SetGamepadImageType(GamepadImage *ctx, SDL_GamepadType type); extern SDL_GamepadType GetGamepadImageType(GamepadImage *ctx); diff --git a/test/testcontroller.c b/test/testcontroller.c index 038d765cdf..a7a89f915a 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -100,6 +100,9 @@ static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; static float virtual_axis_start_x; static float virtual_axis_start_y; static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; +static SDL_bool virtual_touchpad_active = SDL_FALSE; +static float virtual_touchpad_x; +static float virtual_touchpad_y; static int s_arrBindingOrder[] = { /* Standard sequence */ @@ -1109,6 +1112,8 @@ static int SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, static void OpenVirtualGamepad(void) { + SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } }; + SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f }; SDL_VirtualJoystickDesc desc; SDL_JoystickID virtual_id; @@ -1120,6 +1125,10 @@ static void OpenVirtualGamepad(void) desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; desc.naxes = SDL_GAMEPAD_AXIS_MAX; desc.nbuttons = SDL_GAMEPAD_BUTTON_MAX; + desc.ntouchpads = 1; + desc.touchpads = &virtual_touchpad; + desc.nsensors = 1; + desc.sensors = &virtual_sensor; desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex; desc.Rumble = VirtualGamepadRumble; desc.RumbleTriggers = VirtualGamepadRumbleTriggers; @@ -1195,6 +1204,14 @@ static void VirtualGamepadMouseMotion(float x, float y) SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY); } } + + if (virtual_touchpad_active) { + SDL_Rect touchpad; + GetGamepadTouchpadArea(image, &touchpad); + virtual_touchpad_x = (x - touchpad.x) / touchpad.w; + virtual_touchpad_y = (y - touchpad.y) / touchpad.h; + SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f); + } } static void VirtualGamepadMouseDown(float x, float y) @@ -1202,6 +1219,15 @@ static void VirtualGamepadMouseDown(float x, float y) int element = GetGamepadImageElementAt(image, x, y); if (element == SDL_GAMEPAD_ELEMENT_INVALID) { + SDL_Point point = { (int)x, (int)y }; + SDL_Rect touchpad; + GetGamepadTouchpadArea(image, &touchpad); + if (SDL_PointInRect(&point, &touchpad)) { + virtual_touchpad_active = SDL_TRUE; + virtual_touchpad_x = (x - touchpad.x) / touchpad.w; + virtual_touchpad_y = (y - touchpad.y) / touchpad.h; + SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f); + } return; } @@ -1251,6 +1277,11 @@ static void VirtualGamepadMouseUp(float x, float y) } virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; } + + if (virtual_touchpad_active) { + SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_RELEASED, virtual_touchpad_x, virtual_touchpad_y, 0.0f); + virtual_touchpad_active = SDL_FALSE; + } } static void DrawGamepadWaiting(SDL_Renderer *renderer) @@ -1500,6 +1531,12 @@ static void loop(void *arg) { SDL_Event event; + /* If we have a virtual controller, send a virtual accelerometer sensor reading */ + if (virtual_joystick) { + float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f }; + SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data)); + } + /* Update to get the current event state */ SDL_PumpEvents();