From 2be75c6a61d5612f7773c543f3f6426d6afe82d0 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 13 Mar 2020 19:08:45 -0700 Subject: [PATCH] Fixed bug 5028 - Virtual Joysticks (new joystick backend) David Ludwig I have created a new driver for SDL's Joystick and Game-Controller subsystem: a Virtual driver. This driver allows one to create a software-based joystick, which to SDL applications will look and react like a real joystick, but whose state can be set programmatically. A primary use case for this is to help enable developers to add touch-screen joysticks to their apps. The driver comes with a set of new, public APIs, with functions to attach and detach joysticks, set virtual-joystick state, and to determine if a joystick is a virtual-one. Use of virtual joysticks goes as such: 1. Attach one or more virtual joysticks by calling SDL_JoystickAttachVirtual. If successful, this returns the virtual-device's joystick-index. 2. Open the virtual joysticks (using indicies returned by SDL_JoystickAttachVirtual). 3. Call any of the SDL_JoystickSetVirtual* functions when joystick-state changes. Please note that virtual-joystick state will only get applied on the next call to SDL_JoystickUpdate, or when pumping or polling for SDL events (via SDL_PumpEvents or SDL_PollEvent). Here is a listing of the new, public APIs, at present and subject to change: ------------------------------------------------------------ /** * Attaches a new virtual joystick. * Returns the joystick's device index, or -1 if an error occurred. */ extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtual(SDL_JoystickType type, int naxes, int nballs, int nbuttons, int nhats); /** * Detaches a virtual joystick * Returns 0 on success, or -1 if an error occurred. */ extern DECLSPEC int SDLCALL SDL_JoystickDetachVirtual(int device_index); /** * Indicates whether or not a virtual-joystick is at a given device index. */ extern DECLSPEC SDL_bool SDLCALL SDL_JoystickIsVirtual(int device_index); /** * Set values on an opened, virtual-joystick's controls. * Returns 0 on success, -1 on error. */ extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualAxis(SDL_Joystick * joystick, int axis, Sint16 value); extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualBall(SDL_Joystick * joystick, int ball, Sint16 xrel, Sint16 yrel); extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualButton(SDL_Joystick * joystick, int button, Uint8 value); extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualHat(SDL_Joystick * joystick, int hat, Uint8 value); ------------------------------------------------------------ Miscellaneous notes on the initial patch, which are also subject to change: 1. no test code is present in SDL, yet. This should, perhaps, change. Initial development was done with an ImGui-based app, which potentially is too thick for use in SDL-official. If tests are to be added, what kind of tests? Automated? Graphical? 2. virtual game controllers can be created by calling SDL_JoystickAttachVirtual with a joystick-type of SDL_JOYSTICK_TYPE_GAME_CONTROLLER, with naxes (num axes) set to SDL_CONTROLLER_AXIS_MAX, and with nbuttons (num buttons) set to SDL_CONTROLLER_BUTTON_MAX. When updating their state, values of type SDL_GameControllerAxis or SDL_GameControllerButton can be casted to an int and used for the control-index (in calls to SDL_JoystickSetVirtual* functions). 3. virtual joysticks' guids are mostly all-zeros with the exception of the last two bytes, the first of which is a 'v', to indicate that the guid is a virtual one, and the second of which is a SDL_JoystickType that has been converted into a Uint8. 4. virtual joysticks are ONLY turned into virtual game-controllers if and when their joystick-type is set to SDL_JOYSTICK_TYPE_GAMECONTROLLER. This is controlled by having SDL's default list of game-controllers have a single entry for a virtual game controller (of guid, "00000000000000000000000000007601", which is subject to the guid-encoding described above). 5. regarding having to call SDL_JoystickUpdate, either directly or indirectly via SDL_PumpEvents or SDL_PollEvents, before new virtual-joystick state becomes active (as specified via SDL_JoystickSetVirtual* function-calls), this was done to match behavior found in SDL's other joystick drivers, almost all of which will only update SDL-state during SDL_JoystickUpdate. 6. the initial patch is based off of SDL 2.0.12 7. the virtual joystick subsystem is disabled by default. It should be possible to enable it by building with SDL_JOYSTICK_VIRTUAL=1 Questions, comments, suggestions, or bug reports very welcome! --- CMakeLists.txt | 9 + include/SDL_config.h.cmake | 1 + include/SDL_gamecontroller.h | 3 +- include/SDL_joystick.h | 31 ++ src/dynapi/SDL_dynapi_overrides.h | 7 + src/dynapi/SDL_dynapi_procs.h | 7 + src/joystick/SDL_gamecontrollerdb.h | 3 + src/joystick/SDL_joystick.c | 128 +++++++ src/joystick/SDL_joystick_c.h | 3 + src/joystick/SDL_sysjoystick.h | 1 + src/joystick/virtual/SDL_sysjoystick.c | 455 +++++++++++++++++++++++ src/joystick/virtual/SDL_sysjoystick_c.h | 69 ++++ test/testgamecontroller.c | 3 + 13 files changed, 719 insertions(+), 1 deletion(-) create mode 100644 src/joystick/virtual/SDL_sysjoystick.c create mode 100644 src/joystick/virtual/SDL_sysjoystick_c.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 58ab7f893dc98..35f331171f4c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,6 +387,7 @@ set_option(VIDEO_OFFSCREEN "Use offscreen video driver" OFF) option_string(BACKGROUNDING_SIGNAL "number to use for magic backgrounding signal or 'OFF'" "OFF") option_string(FOREGROUNDING_SIGNAL "number to use for magic foregrounding signal or 'OFF'" "OFF") set_option(HIDAPI "Use HIDAPI for low level joystick drivers" ${OPT_DEF_HIDAPI}) +set_option(JOYSTICK_VIRTUAL "Enable the virtual-joystick driver" ON) set(SDL_SHARED ${SDL_SHARED_ENABLED_BY_DEFAULT} CACHE BOOL "Build a shared version of the library") set(SDL_STATIC ${SDL_STATIC_ENABLED_BY_DEFAULT} CACHE BOOL "Build a static version of the library") @@ -905,6 +906,14 @@ if(SDL_DLOPEN) endif() endif() +if(SDL_JOYSTICK) + if(JOYSTICK_VIRTUAL) + set(SDL_JOYSTICK_VIRTUAL 1) + file(GLOB JOYSTICK_VIRTUAL_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/virtual/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${JOYSTICK_VIRTUAL_SOURCES}) + endif() +endif() + if(SDL_VIDEO) if(VIDEO_DUMMY) set(SDL_VIDEO_DRIVER_DUMMY 1) diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake index d6ea31eacdd25..cc6d064765b7f 100644 --- a/include/SDL_config.h.cmake +++ b/include/SDL_config.h.cmake @@ -293,6 +293,7 @@ #cmakedefine SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H @SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H@ #cmakedefine SDL_JOYSTICK_HIDAPI @SDL_JOYSTICK_HIDAPI@ #cmakedefine SDL_JOYSTICK_EMSCRIPTEN @SDL_JOYSTICK_EMSCRIPTEN@ +#cmakedefine SDL_JOYSTICK_VIRTUAL @SDL_JOYSTICK_VIRTUAL@ #cmakedefine SDL_HAPTIC_DUMMY @SDL_HAPTIC_DUMMY@ #cmakedefine SDL_HAPTIC_LINUX @SDL_HAPTIC_LINUX@ #cmakedefine SDL_HAPTIC_IOKIT @SDL_HAPTIC_IOKIT@ diff --git a/include/SDL_gamecontroller.h b/include/SDL_gamecontroller.h index 21cc1e43775cc..70008f74176da 100644 --- a/include/SDL_gamecontroller.h +++ b/include/SDL_gamecontroller.h @@ -64,7 +64,8 @@ typedef enum SDL_CONTROLLER_TYPE_XBOXONE, SDL_CONTROLLER_TYPE_PS3, SDL_CONTROLLER_TYPE_PS4, - SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO, + SDL_CONTROLLER_TYPE_VIRTUAL } SDL_GameControllerType; typedef enum diff --git a/include/SDL_joystick.h b/include/SDL_joystick.h index a0dd7205daa50..b31f6786e115f 100644 --- a/include/SDL_joystick.h +++ b/include/SDL_joystick.h @@ -105,6 +105,7 @@ typedef enum SDL_JOYSTICK_POWER_MAX } SDL_JoystickPowerLevel; + /* Function prototypes */ /** @@ -199,6 +200,36 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromInstanceID(SDL_JoystickID */ extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickFromPlayerIndex(int player_index); +/** + * Attaches a new virtual joystick. + * Returns the joystick's device index, or -1 if an error occurred. + */ +extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtual(SDL_JoystickType type, + int naxes, + int nballs, + int nbuttons, + int nhats); + +/** + * Detaches a virtual joystick + * Returns 0 on success, or -1 if an error occurred. + */ +extern DECLSPEC int SDLCALL SDL_JoystickDetachVirtual(int device_index); + +/** + * Indicates whether or not a virtual-joystick is at a given device index. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickIsVirtual(int device_index); + +/** + * Set values on an opened, virtual-joystick's controls. + * Returns 0 on success, -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualAxis(SDL_Joystick * joystick, int axis, Sint16 value); +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualBall(SDL_Joystick * joystick, int ball, Sint16 xrel, Sint16 yrel); +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualButton(SDL_Joystick * joystick, int button, Uint8 value); +extern DECLSPEC int SDLCALL SDL_JoystickSetVirtualHat(SDL_Joystick * joystick, int hat, Uint8 value); + /** * Return the name for this currently opened joystick. * If no name can be found, this function returns NULL. diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index a5b4d6a4298ad..d6476a2eec255 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -749,3 +749,10 @@ #define SDL_GetAndroidSDKVersion SDL_GetAndroidSDKVersion_REAL #define SDL_isupper SDL_isupper_REAL #define SDL_islower SDL_islower_REAL +#define SDL_JoystickAttachVirtual SDL_JoystickAttachVirtual_REAL +#define SDL_JoystickDetachVirtual SDL_JoystickDetachVirtual_REAL +#define SDL_JoystickIsVirtual SDL_JoystickIsVirtual_REAL +#define SDL_JoystickSetVirtualAxis SDL_JoystickSetVirtualAxis_REAL +#define SDL_JoystickSetVirtualBall SDL_JoystickSetVirtualBall_REAL +#define SDL_JoystickSetVirtualButton SDL_JoystickSetVirtualButton_REAL +#define SDL_JoystickSetVirtualHat SDL_JoystickSetVirtualHat_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 404a5e706f968..2d8a291d64384 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -809,3 +809,10 @@ SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return) #endif SDL_DYNAPI_PROC(int,SDL_isupper,(int a),(a),return) SDL_DYNAPI_PROC(int,SDL_islower,(int a),(a),return) +SDL_DYNAPI_PROC(int,SDL_JoystickAttachVirtual,(SDL_JoystickType a, int b, int c, int d, int e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_JoystickDetachVirtual,(int a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickIsVirtual,(int a),(a),return) +SDL_DYNAPI_PROC(int,SDL_JoystickSetVirtualAxis,(SDL_Joystick *a, int b, Sint16 c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_JoystickSetVirtualBall,(SDL_Joystick *a, int b, Sint16 c, Sint16 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_JoystickSetVirtualButton,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_JoystickSetVirtualHat,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return) diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h index 94883cff1f12f..fb74cbb51c183 100644 --- a/src/joystick/SDL_gamecontrollerdb.h +++ b/src/joystick/SDL_gamecontrollerdb.h @@ -707,6 +707,9 @@ static const char *s_ControllerMappings [] = "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,", "050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,", #endif +#if defined(SDL_JOYSTICK_VIRTUAL) + "00000000000000000000000000007601,Virtual Joystick,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5", +#endif #if defined(SDL_JOYSTICK_EMSCRIPTEN) "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,", #endif diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 858e1792a4c62..d3e2254498b0f 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -46,6 +46,10 @@ #include #endif +#if SDL_JOYSTICK_VIRTUAL +#include "./virtual/SDL_sysjoystick_c.h" +#endif + static SDL_JoystickDriver *SDL_joystick_drivers[] = { #if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT) &SDL_WINDOWS_JoystickDriver, @@ -74,6 +78,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI &SDL_HIDAPI_JoystickDriver, #endif +#ifdef SDL_JOYSTICK_VIRTUAL + &SDL_VIRTUAL_JoystickDriver, +#endif #if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED) &SDL_DUMMY_JoystickDriver #endif @@ -456,6 +463,115 @@ SDL_JoystickOpen(int device_index) } +int +SDL_JoystickAttachVirtual(SDL_JoystickType type, + int naxes, + int nballs, + int nbuttons, + int nhats) +{ +#if SDL_JOYSTICK_VIRTUAL + return SDL_JoystickAttachVirtualInner(type, + naxes, + nballs, + nbuttons, + nhats); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + +int +SDL_JoystickDetachVirtual(int device_index) +{ +#if SDL_JOYSTICK_VIRTUAL + SDL_JoystickDriver *driver; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) { + if (driver == &SDL_VIRTUAL_JoystickDriver) { + const int result = SDL_JoystickDetachVirtualInner(device_index); + SDL_UnlockJoysticks(); + return result; + } + } + SDL_UnlockJoysticks(); + + return SDL_SetError("Virtual joystick not found at provided index"); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + +SDL_bool +SDL_JoystickIsVirtual(int device_index) +{ +#if SDL_JOYSTICK_VIRTUAL + SDL_JoystickDriver *driver; + int driver_device_index; + SDL_bool is_virtual = SDL_FALSE; + + SDL_LockJoysticks(); + if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &driver_device_index)) { + if (driver == &SDL_VIRTUAL_JoystickDriver) { + is_virtual = SDL_TRUE; + } + } + SDL_UnlockJoysticks(); + + return is_virtual; +#else + return SDL_FALSE; +#endif +} + + +int +SDL_JoystickSetVirtualAxis(SDL_Joystick * joystick, int axis, Sint16 value) +{ +#if SDL_JOYSTICK_VIRTUAL + return SDL_JoystickSetVirtualAxisInner(joystick, axis, value); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + +int +SDL_JoystickSetVirtualBall(SDL_Joystick * joystick, int axis, Sint16 xrel, Sint16 yrel) +{ +#if SDL_JOYSTICK_VIRTUAL + return SDL_JoystickSetVirtualBallInner(joystick, axis, xrel, yrel); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + +int +SDL_JoystickSetVirtualButton(SDL_Joystick * joystick, int button, Uint8 value) +{ +#if SDL_JOYSTICK_VIRTUAL + return SDL_JoystickSetVirtualButtonInner(joystick, button, value); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + +int +SDL_JoystickSetVirtualHat(SDL_Joystick * joystick, int hat, Uint8 value) +{ +#if SDL_JOYSTICK_VIRTUAL + return SDL_JoystickSetVirtualHatInner(joystick, hat, value); +#else + return SDL_SetError("SDL not built with virtual-joystick support"); +#endif +} + + /* * Checks to make sure the joystick is valid. */ @@ -1563,6 +1679,8 @@ SDL_GetJoystickGameControllerType(const char *name, Uint16 vendor, Uint16 produc SDL_strcmp(name, "Wireless Gamepad") == 0) { /* HORI or PowerA Switch Pro Controller clone */ type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO; + } else if (SDL_strcmp(name, "Virtual Joystick") == 0) { + type = SDL_CONTROLLER_TYPE_VIRTUAL; } else { type = SDL_CONTROLLER_TYPE_UNKNOWN; } @@ -1624,6 +1742,12 @@ SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid) return (guid.data[14] == 'h') ? SDL_TRUE : SDL_FALSE; } +SDL_bool +SDL_IsJoystickVirtual(SDL_JoystickGUID guid) +{ + return (guid.data[14] == 'v') ? SDL_TRUE : SDL_FALSE; +} + static SDL_bool SDL_IsJoystickProductWheel(Uint32 vidpid) { static Uint32 wheel_joysticks[] = { @@ -1715,6 +1839,10 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_JoystickGUID guid) } } + if (SDL_IsJoystickVirtual(guid)) { + return (SDL_JoystickType)guid.data[15]; + } + SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL); vidpid = MAKE_VIDPID(vendor, product); diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index e03bdd26bb364..e1329738d71b7 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -73,6 +73,9 @@ extern SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid); /* Function to return whether a joystick guid comes from the HIDAPI driver */ extern SDL_bool SDL_IsJoystickHIDAPI(SDL_JoystickGUID guid); +/* Function to return whether a joystick guid comes from the Virtual driver */ +extern SDL_bool SDL_IsJoystickVirtual(SDL_JoystickGUID guid); + /* Function to return whether a joystick should be ignored */ extern SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index 6eb8bf47ec724..a6b2858d98de9 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -152,6 +152,7 @@ extern SDL_JoystickDriver SDL_HAIKU_JoystickDriver; extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver; extern SDL_JoystickDriver SDL_IOS_JoystickDriver; extern SDL_JoystickDriver SDL_LINUX_JoystickDriver; +extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver; #endif /* SDL_sysjoystick_h_ */ diff --git a/src/joystick/virtual/SDL_sysjoystick.c b/src/joystick/virtual/SDL_sysjoystick.c new file mode 100644 index 0000000000000..d3d9735c7adf5 --- /dev/null +++ b/src/joystick/virtual/SDL_sysjoystick.c @@ -0,0 +1,455 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#if defined(SDL_JOYSTICK_VIRTUAL) + +/* This is the virtual implementation of the SDL joystick API */ + +#include "SDL_sysjoystick_c.h" +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; + +static joystick_hwdata * g_VJoys = NULL; + + +static joystick_hwdata * +VIRTUAL_HWDataForIndex(int device_index) +{ + joystick_hwdata *vjoy = g_VJoys; + while (vjoy) { + if (device_index == 0) + break; + --device_index; + vjoy = vjoy->next; + } + return vjoy; +} + + +static void +VIRTUAL_FreeHWData(joystick_hwdata *hwdata) +{ + if (!hwdata) { + return; + } + if (hwdata->axes) { + SDL_free((void *)hwdata->axes); + hwdata->axes = NULL; + } + if (hwdata->balls) { + SDL_free((void *)hwdata->balls); + hwdata->balls = NULL; + } + if (hwdata->buttons) { + SDL_free((void *)hwdata->buttons); + hwdata->buttons = NULL; + } + if (hwdata->hats) { + SDL_free(hwdata->hats); + hwdata->hats = NULL; + } + + /* Remove hwdata from SDL-global list */ + joystick_hwdata * cur = g_VJoys; + joystick_hwdata * prev = NULL; + while (cur) { + if (hwdata == cur) { + if (prev) { + prev->next = cur->next; + } else { + g_VJoys = cur->next; + } + break; + } + prev = cur; + cur = cur->next; + } + + SDL_free(hwdata); +} + + +int +SDL_JoystickAttachVirtualInner(SDL_JoystickType type, + int naxes, + int nballs, + int nbuttons, + int nhats) +{ + joystick_hwdata *hwdata = NULL; + int device_index = -1; + + hwdata = SDL_calloc(1, sizeof(joystick_hwdata)); + if (!hwdata) { + VIRTUAL_FreeHWData(hwdata); + return SDL_OutOfMemory(); + } + + hwdata->naxes = naxes; + hwdata->nballs = nballs; + hwdata->nbuttons = nbuttons; + hwdata->nhats = nhats; + hwdata->name = "Virtual Joystick"; + + /* Note that this is a Virtual device and what subtype it is */ + hwdata->guid.data[14] = 'v'; + hwdata->guid.data[15] = (Uint8)type; + + /* Allocate fields for different control-types */ + if (naxes > 0) { + hwdata->axes = SDL_calloc(naxes, sizeof(Sint16)); + if (!hwdata->axes) { + VIRTUAL_FreeHWData(hwdata); + return SDL_OutOfMemory(); + } + } + if (nballs > 0) { + hwdata->balls = SDL_calloc(nballs, sizeof(hwdata->balls[0])); + if (!hwdata->balls) { + VIRTUAL_FreeHWData(hwdata); + return SDL_OutOfMemory(); + } + } + if (nbuttons > 0) { + hwdata->buttons = SDL_calloc(nbuttons, sizeof(Uint8)); + if (!hwdata->buttons) { + VIRTUAL_FreeHWData(hwdata); + return SDL_OutOfMemory(); + } + } + if (nhats > 0) { + hwdata->hats = SDL_calloc(nhats, sizeof(Uint8)); + if (!hwdata->hats) { + VIRTUAL_FreeHWData(hwdata); + return SDL_OutOfMemory(); + } + } + + /* Allocate an instance ID for this device */ + hwdata->instance_id = SDL_GetNextJoystickInstanceID(); + + /* Add virtual joystick to SDL-global lists */ + hwdata->next = g_VJoys; + g_VJoys = hwdata; + SDL_PrivateJoystickAdded(hwdata->instance_id); + + /* Return the new virtual-device's index */ + device_index = SDL_JoystickGetDeviceIndexFromInstanceID(hwdata->instance_id); + return device_index; +} + + +int +SDL_JoystickDetachVirtualInner(int device_index) +{ + SDL_JoystickID instance_id; + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return SDL_SetError("Virtual joystick data not found"); + } + instance_id = hwdata->instance_id; + VIRTUAL_FreeHWData(hwdata); + SDL_PrivateJoystickRemoved(instance_id); + return 0; +} + + +int +SDL_JoystickSetVirtualAxisInner(SDL_Joystick * joystick, int axis, Sint16 value) +{ + joystick_hwdata *hwdata; + + SDL_LockJoysticks(); + + if (!joystick || !joystick->hwdata) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (axis < 0 || axis >= hwdata->nbuttons) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid axis index"); + } + + hwdata->axes[axis] = value; + + SDL_UnlockJoysticks(); + return 0; +} + + +int +SDL_JoystickSetVirtualBallInner(SDL_Joystick * joystick, int ball, Sint16 xrel, Sint16 yrel) +{ + joystick_hwdata *hwdata; + + SDL_LockJoysticks(); + + if (!joystick || !joystick->hwdata) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (ball < 0 || ball >= hwdata->nbuttons) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid ball index"); + } + + hwdata->balls[ball].xrel = xrel; + hwdata->balls[ball].yrel = yrel; + + SDL_UnlockJoysticks(); + return 0; +} + + +int +SDL_JoystickSetVirtualButtonInner(SDL_Joystick * joystick, int button, Uint8 value) +{ + joystick_hwdata *hwdata; + + SDL_LockJoysticks(); + + if (!joystick || !joystick->hwdata) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (button < 0 || button >= hwdata->nbuttons) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid button index"); + } + + hwdata->buttons[button] = value; + + SDL_UnlockJoysticks(); + return 0; +} + + +int +SDL_JoystickSetVirtualHatInner(SDL_Joystick * joystick, int hat, Uint8 value) +{ + joystick_hwdata *hwdata; + + SDL_LockJoysticks(); + + if (!joystick || !joystick->hwdata) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid joystick"); + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + if (hat < 0 || hat >= hwdata->nbuttons) { + SDL_UnlockJoysticks(); + return SDL_SetError("Invalid hat index"); + } + + hwdata->hats[hat] = value; + + SDL_UnlockJoysticks(); + return 0; +} + + +static int +VIRTUAL_JoystickInit(void) +{ + return 0; +} + + +static int +VIRTUAL_JoystickGetCount(void) +{ + int count = 0; + joystick_hwdata *cur = g_VJoys; + while (cur) { + ++count; + cur = cur->next; + } + return count; +} + + +static void +VIRTUAL_JoystickDetect(void) +{ +} + + +static const char * +VIRTUAL_JoystickGetDeviceName(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return NULL; + } + return hwdata->name ? hwdata->name : ""; +} + + +static int +VIRTUAL_JoystickGetDevicePlayerIndex(int device_index) +{ + return -1; +} + + +static void +VIRTUAL_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ +} + + +static SDL_JoystickGUID +VIRTUAL_JoystickGetDeviceGUID(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + SDL_JoystickGUID guid; + SDL_zero(guid); + return guid; + } + return hwdata->guid; +} + + +static SDL_JoystickID +VIRTUAL_JoystickGetDeviceInstanceID(int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return -1; + } + return hwdata->instance_id; +} + + +static int +VIRTUAL_JoystickOpen(SDL_Joystick * joystick, int device_index) +{ + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + if (!hwdata) { + return SDL_SetError("No such device"); + } + if (hwdata->opened) { + return SDL_SetError("Joystick already opened"); + } + joystick->instance_id = hwdata->instance_id; + joystick->hwdata = hwdata; + joystick->naxes = hwdata->naxes; + joystick->nballs = hwdata->nballs; + joystick->nbuttons = hwdata->nbuttons; + joystick->nhats = hwdata->nhats; + hwdata->opened = SDL_TRUE; + return 0; +} + + +static int +VIRTUAL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + return SDL_Unsupported(); +} + + +static void +VIRTUAL_JoystickUpdate(SDL_Joystick * joystick) +{ + joystick_hwdata *hwdata; + + if (!joystick) { + return; + } + if (!joystick->hwdata) { + return; + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + + for (int i = 0; i < hwdata->naxes; ++i) { + SDL_PrivateJoystickAxis(joystick, i, hwdata->axes[i]); + } + for (int i = 0; i < hwdata->nballs; ++i) { + SDL_PrivateJoystickBall(joystick, i, hwdata->balls[i].xrel, hwdata->balls[i].yrel); + } + for (int i = 0; i < hwdata->nbuttons; ++i) { + SDL_PrivateJoystickButton(joystick, i, hwdata->buttons[i]); + } + for (int i = 0; i < hwdata->nhats; ++i) { + SDL_PrivateJoystickHat(joystick, i, hwdata->hats[i]); + } +} + + +static void +VIRTUAL_JoystickClose(SDL_Joystick * joystick) +{ + joystick_hwdata *hwdata; + + if (!joystick) { + return; + } + if (!joystick->hwdata) { + return; + } + + hwdata = (joystick_hwdata *)joystick->hwdata; + hwdata->opened = SDL_FALSE; +} + + +static void +VIRTUAL_JoystickQuit(void) +{ + while (g_VJoys) { + VIRTUAL_FreeHWData(g_VJoys); + } +} + + +SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = +{ + VIRTUAL_JoystickInit, + VIRTUAL_JoystickGetCount, + VIRTUAL_JoystickDetect, + VIRTUAL_JoystickGetDeviceName, + VIRTUAL_JoystickGetDevicePlayerIndex, + VIRTUAL_JoystickSetDevicePlayerIndex, + VIRTUAL_JoystickGetDeviceGUID, + VIRTUAL_JoystickGetDeviceInstanceID, + VIRTUAL_JoystickOpen, + VIRTUAL_JoystickRumble, + VIRTUAL_JoystickUpdate, + VIRTUAL_JoystickClose, + VIRTUAL_JoystickQuit, +}; + +#endif /* SDL_JOYSTICK_VIRTUAL || SDL_JOYSTICK_DISABLED */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/virtual/SDL_sysjoystick_c.h b/src/joystick/virtual/SDL_sysjoystick_c.h new file mode 100644 index 0000000000000..3b782f1b6c40f --- /dev/null +++ b/src/joystick/virtual/SDL_sysjoystick_c.h @@ -0,0 +1,69 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "../../SDL_internal.h" + +#ifndef SDL_JOYSTICK_VIRTUAL_H +#define SDL_JOYSTICK_VIRTUAL_H + +#if SDL_JOYSTICK_VIRTUAL + +#include "SDL_joystick.h" + +/** + * Data for a virtual, software-only joystick. + */ +typedef struct joystick_hwdata +{ + SDL_JoystickType type; + SDL_bool attached; + const char *name; + SDL_JoystickGUID guid; + int naxes; + Sint16 *axes; + int nballs; + struct { + Sint16 xrel; + Sint16 yrel; + } *balls; + int nbuttons; + Uint8 *buttons; + int nhats; + Uint8 *hats; + SDL_JoystickID instance_id; + SDL_bool opened; + struct joystick_hwdata *next; +} joystick_hwdata; + +int SDL_JoystickAttachVirtualInner(SDL_JoystickType type, + int naxes, + int nballs, + int nbuttons, + int nhats); + +int SDL_JoystickDetachVirtualInner(int device_index); + +int SDL_JoystickSetVirtualAxisInner(SDL_Joystick * joystick, int axis, Sint16 value); +int SDL_JoystickSetVirtualBallInner(SDL_Joystick * joystick, int ball, Sint16 xrel, Sint16 yrel); +int SDL_JoystickSetVirtualButtonInner(SDL_Joystick * joystick, int button, Uint8 value); +int SDL_JoystickSetVirtualHatInner(SDL_Joystick * joystick, int hat, Uint8 value); + +#endif /* SDL_JOYSTICK_VIRTUAL */ +#endif /* SDL_JOYSTICK_VIRTUAL_H */ diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c index 6ed31051af4ad..9fd5eb5042ed1 100644 --- a/test/testgamecontroller.c +++ b/test/testgamecontroller.c @@ -310,6 +310,9 @@ main(int argc, char *argv[]) case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: description = "Nintendo Switch Pro Controller"; break; + case SDL_CONTROLLER_TYPE_VIRTUAL: + description = "Virtual Game Controller"; + break; default: description = "Game Controller"; break;