From 94eeb587c1969bcfea79303273895619e9edd3c2 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 15 May 2022 20:01:12 -0700 Subject: [PATCH] First pass at extending virtual controller functionality Added the ability to specify a name and the product VID/PID for a virtual controller Also added a test case to testgamecontroller, if you pass --virtual as a parameter --- include/SDL_joystick.h | 38 +++ src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/joystick/SDL_gamecontroller.c | 102 +------ src/joystick/SDL_joystick.c | 16 +- src/joystick/SDL_joystick_c.h | 5 + src/joystick/virtual/SDL_virtualjoystick.c | 304 ++++++++++++++----- src/joystick/virtual/SDL_virtualjoystick_c.h | 15 +- test/testgamecontroller.c | 230 ++++++++++++-- 9 files changed, 509 insertions(+), 203 deletions(-) diff --git a/include/SDL_joystick.h b/include/SDL_joystick.h index 1aa130f1a..5231d023a 100644 --- a/include/SDL_joystick.h +++ b/include/SDL_joystick.h @@ -348,6 +348,44 @@ extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtual(SDL_JoystickType type, int nbuttons, int nhats); +/** + * The structure that defines an extended virtual joystick description + * + * The caller must zero the structure and then initialize the version with `SDL_VIRTUAL_JOYSTICK_DESC_VERSION` before passing it to SDL_JoystickAttachVirtualEx() + * + * \sa SDL_JoystickAttachVirtualEx + */ +typedef struct SDL_VirtualJoystickDesc +{ + Uint16 version; /**< `SDL_VIRTUAL_JOYSTICK_DESC_VERSION` */ + 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 vendor_id; /**< the USB vendor ID of this joystick */ + Uint16 product_id; /**< the USB product ID of this joystick */ + Uint16 padding; /**< unused */ + const char *name; /**< the name of the joystick */ + + void *userdata; /**< User data pointer passed to callbacks */ + void (*Update)(void *userdata); /**< Called when the joystick state should be updated */ + +} SDL_VirtualJoystickDesc; + +/** + * \brief The current version of the SDL_VirtualJoystickDesc structure + */ +#define SDL_VIRTUAL_JOYSTICK_DESC_VERSION 1 + +/** + * Attach a new virtual joystick with extended properties. + * + * \returns the joystick's device index, or -1 if an error occurred. + * + * \since This function is available since SDL 2.24.0. + */ +extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtualEx(const SDL_VirtualJoystickDesc *desc); + /** * Detach a virtual joystick. * diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 90b2254ed..15577b51e 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -870,3 +870,4 @@ #define SDL_GameControllerPath SDL_GameControllerPath_REAL #define SDL_JoystickPathForIndex SDL_JoystickPathForIndex_REAL #define SDL_JoystickPath SDL_JoystickPath_REAL +#define SDL_JoystickAttachVirtualEx SDL_JoystickAttachVirtualEx_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index f78c0e423..45ee57547 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -941,3 +941,4 @@ SDL_DYNAPI_PROC(const char*,SDL_GameControllerPathForIndex,(int a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GameControllerPath,(SDL_GameController *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_JoystickPathForIndex,(int a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_JoystickPath,(SDL_Joystick *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_JoystickAttachVirtualEx,(const SDL_VirtualJoystickDesc *a),(a),return) diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 50eb190a5..f0f932e51 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -464,100 +464,6 @@ static int SDLCALL SDL_GameControllerEventWatcher(void *userdata, SDL_Event * ev return 1; } -/* - * Helper function to guess at a mapping for virtual controllers - */ -static ControllerMapping_t *SDL_CreateMappingForVirtualController(SDL_JoystickGUID guid) -{ - const int face_button_mask = ((1 << SDL_CONTROLLER_BUTTON_A) | - (1 << SDL_CONTROLLER_BUTTON_B) | - (1 << SDL_CONTROLLER_BUTTON_X) | - (1 << SDL_CONTROLLER_BUTTON_Y)); - SDL_bool existing; - char mapping_string[1024]; - int button_mask; - int axis_mask; - - button_mask = SDL_SwapLE16(*(Uint16*)(&guid.data[sizeof(guid.data)-4])); - axis_mask = SDL_SwapLE16(*(Uint16*)(&guid.data[sizeof(guid.data)-2])); - if (!button_mask && !axis_mask) { - return NULL; - } - if (!(button_mask & face_button_mask)) { - /* We don't know what buttons or axes are supported, don't make up a mapping */ - return NULL; - } - - SDL_strlcpy(mapping_string, "none,*,", sizeof(mapping_string)); - - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_A)) { - SDL_strlcat(mapping_string, "a:b0,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_B)) { - SDL_strlcat(mapping_string, "b:b1,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_X)) { - SDL_strlcat(mapping_string, "x:b2,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_Y)) { - SDL_strlcat(mapping_string, "y:b3,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) { - SDL_strlcat(mapping_string, "back:b4,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_GUIDE)) { - SDL_strlcat(mapping_string, "guide:b5,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { - SDL_strlcat(mapping_string, "start:b6,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) { - SDL_strlcat(mapping_string, "leftstick:b7,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) { - SDL_strlcat(mapping_string, "rightstick:b8,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) { - SDL_strlcat(mapping_string, "leftshoulder:b9,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) { - SDL_strlcat(mapping_string, "rightshoulder:b10,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_UP)) { - SDL_strlcat(mapping_string, "dpup:b11,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_DOWN)) { - SDL_strlcat(mapping_string, "dpdown:b12,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_LEFT)) { - SDL_strlcat(mapping_string, "dpleft:b13,", sizeof(mapping_string)); - } - if (button_mask & (1 << SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) { - SDL_strlcat(mapping_string, "dpright:b14,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_LEFTX)) { - SDL_strlcat(mapping_string, "leftx:a0,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_LEFTY)) { - SDL_strlcat(mapping_string, "lefty:a1,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_RIGHTX)) { - SDL_strlcat(mapping_string, "rightx:a2,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_RIGHTY)) { - SDL_strlcat(mapping_string, "righty:a3,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT)) { - SDL_strlcat(mapping_string, "lefttrigger:a4,", sizeof(mapping_string)); - } - if (axis_mask & (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) { - SDL_strlcat(mapping_string, "righttrigger:a5,", sizeof(mapping_string)); - } - - return SDL_PrivateAddMappingForGUID(guid, mapping_string, - &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT); -} - #ifdef __ANDROID__ /* * Helper function to guess at a mapping based on the elements reported for this controller @@ -790,9 +696,6 @@ static ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickG return s_pXInputMapping; } #endif - if (!mapping && SDL_IsJoystickVirtual(guid)) { - mapping = SDL_CreateMappingForVirtualController(guid); - } #ifdef __ANDROID__ if (!mapping && !SDL_IsJoystickHIDAPI(guid)) { mapping = SDL_CreateMappingForAndroidController(guid); @@ -1355,6 +1258,11 @@ static ControllerMapping_t *SDL_PrivateGenerateAutomaticControllerMapping(const SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpdown", &raw_map->dpdown); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpleft", &raw_map->dpleft); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "dpright", &raw_map->dpright); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "misc1", &raw_map->misc1); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle1", &raw_map->paddle1); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle2", &raw_map->paddle2); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle3", &raw_map->paddle3); + SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "paddle4", &raw_map->paddle4); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "leftx", &raw_map->leftx); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "lefty", &raw_map->lefty); SDL_PrivateAppendToMappingString(mapping, sizeof(mapping), "rightx", &raw_map->rightx); diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index e5ec5b7f8..914f29e2d 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -522,9 +522,23 @@ SDL_JoystickOpen(int device_index) int SDL_JoystickAttachVirtual(SDL_JoystickType type, int naxes, int nbuttons, int nhats) +{ + SDL_VirtualJoystickDesc desc; + + SDL_zero(desc); + desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; + desc.type = (Uint16)type; + desc.naxes = (Uint16)naxes; + desc.nbuttons = (Uint16)nbuttons; + desc.nhats = (Uint16)nhats; + return SDL_JoystickAttachVirtualEx(&desc); +} + +int +SDL_JoystickAttachVirtualEx(const SDL_VirtualJoystickDesc *desc) { #if SDL_JOYSTICK_VIRTUAL - return SDL_JoystickAttachVirtualInner(type, naxes, nbuttons, nhats); + return SDL_JoystickAttachVirtualInner(desc); #else return SDL_SetError("SDL not built with virtual-joystick support"); #endif diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 312e11337..a7aa6b714 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -168,6 +168,11 @@ typedef struct _SDL_GamepadMapping SDL_InputMapping dpdown; SDL_InputMapping dpleft; SDL_InputMapping dpright; + SDL_InputMapping misc1; + SDL_InputMapping paddle1; + SDL_InputMapping paddle2; + SDL_InputMapping paddle3; + SDL_InputMapping paddle4; SDL_InputMapping leftx; SDL_InputMapping lefty; SDL_InputMapping rightx; diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c index 9be879b30..a2c290b42 100644 --- a/src/joystick/virtual/SDL_virtualjoystick.c +++ b/src/joystick/virtual/SDL_virtualjoystick.c @@ -56,18 +56,6 @@ VIRTUAL_FreeHWData(joystick_hwdata *hwdata) if (!hwdata) { return; } - if (hwdata->axes) { - SDL_free((void *)hwdata->axes); - hwdata->axes = 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 */ while (cur) { @@ -83,81 +71,103 @@ VIRTUAL_FreeHWData(joystick_hwdata *hwdata) cur = cur->next; } + if (hwdata->name) { + SDL_free(hwdata->name); + hwdata->name = NULL; + } + if (hwdata->axes) { + SDL_free((void *)hwdata->axes); + hwdata->axes = NULL; + } + if (hwdata->buttons) { + SDL_free((void *)hwdata->buttons); + hwdata->buttons = NULL; + } + if (hwdata->hats) { + SDL_free(hwdata->hats); + hwdata->hats = NULL; + } SDL_free(hwdata); } int -SDL_JoystickAttachVirtualInner(SDL_JoystickType type, - int naxes, - int nbuttons, - int nhats) +SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc) { joystick_hwdata *hwdata = NULL; int device_index = -1; - const Uint16 vendor_id = 0; - const Uint16 product_id = 0; + const char *name = NULL; Uint16 button_mask = 0; Uint16 axis_mask = 0; Uint16 *guid16; + if (!desc) { + return SDL_InvalidParamError("desc"); + } + if (desc->version != SDL_VIRTUAL_JOYSTICK_DESC_VERSION) { + /* Is this an old version that we can support? */ + return SDL_SetError("Unsupported virtual joystick description version %d", desc->version); + } + hwdata = SDL_calloc(1, sizeof(joystick_hwdata)); if (!hwdata) { VIRTUAL_FreeHWData(hwdata); return SDL_OutOfMemory(); } + SDL_memcpy(&hwdata->desc, desc, sizeof(*desc)); - hwdata->naxes = naxes; - hwdata->nbuttons = nbuttons; - hwdata->nhats = nhats; - - switch (type) { - case SDL_JOYSTICK_TYPE_GAMECONTROLLER: - hwdata->name = "Virtual Controller"; - break; - case SDL_JOYSTICK_TYPE_WHEEL: - hwdata->name = "Virtual Wheel"; - break; - case SDL_JOYSTICK_TYPE_ARCADE_STICK: - hwdata->name = "Virtual Arcade Stick"; - break; - case SDL_JOYSTICK_TYPE_FLIGHT_STICK: - hwdata->name = "Virtual Flight Stick"; - break; - case SDL_JOYSTICK_TYPE_DANCE_PAD: - hwdata->name = "Virtual Dance Pad"; - break; - case SDL_JOYSTICK_TYPE_GUITAR: - hwdata->name = "Virtual Guitar"; - break; - case SDL_JOYSTICK_TYPE_DRUM_KIT: - hwdata->name = "Virtual Drum Kit"; - break; - case SDL_JOYSTICK_TYPE_ARCADE_PAD: - hwdata->name = "Virtual Arcade Pad"; - break; - case SDL_JOYSTICK_TYPE_THROTTLE: - hwdata->name = "Virtual Throttle"; - break; - default: - hwdata->name = "Virtual Joystick"; - break; + if (desc->name) { + name = desc->name; + } else { + switch (desc->type) { + case SDL_JOYSTICK_TYPE_GAMECONTROLLER: + name = "Virtual Controller"; + break; + case SDL_JOYSTICK_TYPE_WHEEL: + name = "Virtual Wheel"; + break; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: + name = "Virtual Arcade Stick"; + break; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: + name = "Virtual Flight Stick"; + break; + case SDL_JOYSTICK_TYPE_DANCE_PAD: + name = "Virtual Dance Pad"; + break; + case SDL_JOYSTICK_TYPE_GUITAR: + name = "Virtual Guitar"; + break; + case SDL_JOYSTICK_TYPE_DRUM_KIT: + name = "Virtual Drum Kit"; + break; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: + name = "Virtual Arcade Pad"; + break; + case SDL_JOYSTICK_TYPE_THROTTLE: + name = "Virtual Throttle"; + break; + default: + name = "Virtual Joystick"; + break; + } } + hwdata->name = SDL_strdup(name); - if (type == SDL_JOYSTICK_TYPE_GAMECONTROLLER) { + if (desc->type == SDL_JOYSTICK_TYPE_GAMECONTROLLER) { int i; - if (naxes >= 2) { + if (desc->naxes >= 2) { axis_mask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY)); } - if (naxes >= 4) { + if (desc->naxes >= 4) { axis_mask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY)); } - if (naxes >= 6) { + if (desc->naxes >= 6) { axis_mask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT)); } - for (i = 0; i < nbuttons && i < sizeof(Uint16)*8; ++i) { + for (i = 0; i < desc->nbuttons && i < sizeof(Uint16)*8; ++i) { button_mask |= (1 << i); } } @@ -167,34 +177,41 @@ SDL_JoystickAttachVirtualInner(SDL_JoystickType type, guid16 = (Uint16 *)hwdata->guid.data; *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_VIRTUAL); *guid16++ = 0; - *guid16++ = SDL_SwapLE16(vendor_id); + *guid16++ = SDL_SwapLE16(desc->vendor_id); *guid16++ = 0; - *guid16++ = SDL_SwapLE16(product_id); + *guid16++ = SDL_SwapLE16(desc->product_id); *guid16++ = 0; *guid16++ = SDL_SwapLE16(button_mask); *guid16++ = SDL_SwapLE16(axis_mask); /* Note that this is a Virtual device and what subtype it is */ hwdata->guid.data[14] = 'v'; - hwdata->guid.data[15] = (Uint8)type; + hwdata->guid.data[15] = (Uint8)desc->type; /* Allocate fields for different control-types */ - if (naxes > 0) { - hwdata->axes = SDL_calloc(naxes, sizeof(Sint16)); + if (desc->naxes > 0) { + hwdata->axes = SDL_calloc(desc->naxes, sizeof(Sint16)); if (!hwdata->axes) { VIRTUAL_FreeHWData(hwdata); return SDL_OutOfMemory(); } + + /* Trigger axes are at minimum value at rest */ + if (desc->type == SDL_JOYSTICK_TYPE_GAMECONTROLLER && + desc->naxes > SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + hwdata->axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = SDL_JOYSTICK_AXIS_MIN; + hwdata->axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = SDL_JOYSTICK_AXIS_MIN; + } } - if (nbuttons > 0) { - hwdata->buttons = SDL_calloc(nbuttons, sizeof(Uint8)); + if (desc->nbuttons > 0) { + hwdata->buttons = SDL_calloc(desc->nbuttons, sizeof(Uint8)); if (!hwdata->buttons) { VIRTUAL_FreeHWData(hwdata); return SDL_OutOfMemory(); } } - if (nhats > 0) { - hwdata->hats = SDL_calloc(nhats, sizeof(Uint8)); + if (desc->nhats > 0) { + hwdata->hats = SDL_calloc(desc->nhats, sizeof(Uint8)); if (!hwdata->hats) { VIRTUAL_FreeHWData(hwdata); return SDL_OutOfMemory(); @@ -243,7 +260,7 @@ SDL_JoystickSetVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value) } hwdata = (joystick_hwdata *)joystick->hwdata; - if (axis < 0 || axis >= hwdata->naxes) { + if (axis < 0 || axis >= hwdata->desc.naxes) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid axis index"); } @@ -268,7 +285,7 @@ SDL_JoystickSetVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 valu } hwdata = (joystick_hwdata *)joystick->hwdata; - if (button < 0 || button >= hwdata->nbuttons) { + if (button < 0 || button >= hwdata->desc.nbuttons) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid button index"); } @@ -293,7 +310,7 @@ SDL_JoystickSetVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value) } hwdata = (joystick_hwdata *)joystick->hwdata; - if (hat < 0 || hat >= hwdata->nhats) { + if (hat < 0 || hat >= hwdata->desc.nhats) { SDL_UnlockJoysticks(); return SDL_SetError("Invalid hat index"); } @@ -338,7 +355,7 @@ VIRTUAL_JoystickGetDeviceName(int device_index) if (!hwdata) { return NULL; } - return hwdata->name ? hwdata->name : ""; + return hwdata->name; } @@ -399,9 +416,9 @@ VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index) } joystick->instance_id = hwdata->instance_id; joystick->hwdata = hwdata; - joystick->naxes = hwdata->naxes; - joystick->nbuttons = hwdata->nbuttons; - joystick->nhats = hwdata->nhats; + joystick->naxes = hwdata->desc.naxes; + joystick->nbuttons = hwdata->desc.nbuttons; + joystick->nhats = hwdata->desc.nhats; hwdata->opened = SDL_TRUE; return 0; } @@ -461,13 +478,17 @@ VIRTUAL_JoystickUpdate(SDL_Joystick *joystick) hwdata = (joystick_hwdata *)joystick->hwdata; - for (i = 0; i < hwdata->naxes; ++i) { + if (hwdata->desc.Update) { + hwdata->desc.Update(hwdata->desc.userdata); + } + + for (i = 0; i < hwdata->desc.naxes; ++i) { SDL_PrivateJoystickAxis(joystick, i, hwdata->axes[i]); } - for (i = 0; i < hwdata->nbuttons; ++i) { + for (i = 0; i < hwdata->desc.nbuttons; ++i) { SDL_PrivateJoystickButton(joystick, i, hwdata->buttons[i]); } - for (i = 0; i < hwdata->nhats; ++i) { + for (i = 0; i < hwdata->desc.nhats; ++i) { SDL_PrivateJoystickHat(joystick, i, hwdata->hats[i]); } } @@ -501,7 +522,134 @@ VIRTUAL_JoystickQuit(void) static SDL_bool VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) { - return SDL_FALSE; + joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index); + + if (hwdata->desc.type != SDL_JOYSTICK_TYPE_GAMECONTROLLER) { + return SDL_FALSE; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_A) { + out->a.kind = EMappingKind_Button; + out->a.target = SDL_CONTROLLER_BUTTON_A; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_B) { + out->b.kind = EMappingKind_Button; + out->b.target = SDL_CONTROLLER_BUTTON_B; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_X) { + out->x.kind = EMappingKind_Button; + out->x.target = SDL_CONTROLLER_BUTTON_X; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_Y) { + out->y.kind = EMappingKind_Button; + out->y.target = SDL_CONTROLLER_BUTTON_Y; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_BACK) { + out->back.kind = EMappingKind_Button; + out->back.target = SDL_CONTROLLER_BUTTON_BACK; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_GUIDE) { + out->guide.kind = EMappingKind_Button; + out->guide.target = SDL_CONTROLLER_BUTTON_GUIDE; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_START) { + out->start.kind = EMappingKind_Button; + out->start.target = SDL_CONTROLLER_BUTTON_START; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_LEFTSTICK) { + out->leftstick.kind = EMappingKind_Button; + out->leftstick.target = SDL_CONTROLLER_BUTTON_LEFTSTICK; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_RIGHTSTICK) { + out->rightstick.kind = EMappingKind_Button; + out->rightstick.target = SDL_CONTROLLER_BUTTON_RIGHTSTICK; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_LEFTSHOULDER) { + out->leftshoulder.kind = EMappingKind_Button; + out->leftshoulder.target = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) { + out->rightshoulder.kind = EMappingKind_Button; + out->rightshoulder.target = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_DPAD_UP) { + out->dpup.kind = EMappingKind_Button; + out->dpup.target = SDL_CONTROLLER_BUTTON_DPAD_UP; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_DPAD_DOWN) { + out->dpdown.kind = EMappingKind_Button; + out->dpdown.target = SDL_CONTROLLER_BUTTON_DPAD_DOWN; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + out->dpleft.kind = EMappingKind_Button; + out->dpleft.target = SDL_CONTROLLER_BUTTON_DPAD_LEFT; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { + out->dpright.kind = EMappingKind_Button; + out->dpright.target = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_MISC1) { + out->misc1.kind = EMappingKind_Button; + out->misc1.target = SDL_CONTROLLER_BUTTON_MISC1; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_PADDLE1) { + out->paddle1.kind = EMappingKind_Button; + out->paddle1.target = SDL_CONTROLLER_BUTTON_PADDLE1; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_PADDLE2) { + out->paddle2.kind = EMappingKind_Button; + out->paddle2.target = SDL_CONTROLLER_BUTTON_PADDLE2; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_PADDLE3) { + out->paddle3.kind = EMappingKind_Button; + out->paddle3.target = SDL_CONTROLLER_BUTTON_PADDLE3; + } + + if (hwdata->desc.nbuttons > SDL_CONTROLLER_BUTTON_PADDLE4) { + out->paddle4.kind = EMappingKind_Button; + out->paddle4.target = SDL_CONTROLLER_BUTTON_PADDLE4; + } + + if (hwdata->desc.naxes > SDL_CONTROLLER_AXIS_LEFTY) { + out->leftx.kind = EMappingKind_Axis; + out->lefty.kind = EMappingKind_Axis; + out->leftx.target = SDL_CONTROLLER_AXIS_LEFTX; + out->lefty.target = SDL_CONTROLLER_AXIS_LEFTY; + } + + if (hwdata->desc.naxes > SDL_CONTROLLER_AXIS_RIGHTY) { + out->rightx.kind = EMappingKind_Axis; + out->righty.kind = EMappingKind_Axis; + out->rightx.target = SDL_CONTROLLER_AXIS_RIGHTX; + out->righty.target = SDL_CONTROLLER_AXIS_RIGHTY; + } + + if (hwdata->desc.naxes > SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + out->lefttrigger.kind = EMappingKind_Axis; + out->righttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = SDL_CONTROLLER_AXIS_TRIGGERLEFT; + out->righttrigger.target = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; + } + + return SDL_TRUE; } SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = diff --git a/src/joystick/virtual/SDL_virtualjoystick_c.h b/src/joystick/virtual/SDL_virtualjoystick_c.h index b251c2d14..a12d6c84a 100644 --- a/src/joystick/virtual/SDL_virtualjoystick_c.h +++ b/src/joystick/virtual/SDL_virtualjoystick_c.h @@ -34,24 +34,18 @@ typedef struct joystick_hwdata { SDL_JoystickType type; SDL_bool attached; - const char *name; + char *name; SDL_JoystickGUID guid; - int naxes; + SDL_VirtualJoystickDesc desc; Sint16 *axes; - 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 nbuttons, - int nhats); - +int SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *desc); int SDL_JoystickDetachVirtualInner(int device_index); int SDL_JoystickSetVirtualAxisInner(SDL_Joystick * joystick, int axis, Sint16 value); @@ -59,4 +53,7 @@ int SDL_JoystickSetVirtualButtonInner(SDL_Joystick * joystick, int button, Uint8 int SDL_JoystickSetVirtualHatInner(SDL_Joystick * joystick, int hat, Uint8 value); #endif /* SDL_JOYSTICK_VIRTUAL */ + #endif /* SDL_VIRTUALJOYSTICK_C_H */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c index 82b6b6c98..ef0838cb2 100644 --- a/test/testgamecontroller.c +++ b/test/testgamecontroller.c @@ -28,6 +28,10 @@ #define SCREEN_WIDTH 512 #define SCREEN_HEIGHT 320 +#define BUTTON_SIZE 50 +#define AXIS_SIZE 50 + + /* This is indexed by SDL_GameControllerButton. */ static const struct { int x; int y; } button_positions[] = { {387, 167}, /* SDL_CONTROLLER_BUTTON_A */ @@ -50,7 +54,9 @@ static const struct { int x; int y; } button_positions[] = { {330, 135}, /* SDL_CONTROLLER_BUTTON_PADDLE2 */ {132, 175}, /* SDL_CONTROLLER_BUTTON_PADDLE3 */ {330, 175}, /* SDL_CONTROLLER_BUTTON_PADDLE4 */ + {0, 0}, /* SDL_CONTROLLER_BUTTON_TOUCHPAD */ }; +SDL_COMPILE_TIME_ASSERT(button_positions, SDL_arraysize(button_positions) == SDL_CONTROLLER_BUTTON_MAX); /* This is indexed by SDL_GameControllerAxis. */ static const struct { int x; int y; double angle; } axis_positions[] = { @@ -61,6 +67,7 @@ static const struct { int x; int y; double angle; } axis_positions[] = { {91, -20, 0.0}, /* TRIGGERLEFT */ {375, -20, 0.0}, /* TRIGGERRIGHT */ }; +SDL_COMPILE_TIME_ASSERT(axis_positions, SDL_arraysize(axis_positions) == SDL_CONTROLLER_AXIS_MAX); static SDL_Window *window = NULL; static SDL_Renderer *screen = NULL; @@ -72,6 +79,11 @@ static SDL_Texture *background_front, *background_back, *button, *axis; static SDL_GameController *gamecontroller; static SDL_GameController **gamecontrollers; static int num_controllers = 0; +static SDL_Joystick *virtual_joystick = NULL; +static SDL_GameControllerAxis virtual_axis_active = SDL_CONTROLLER_AXIS_INVALID; +static int virtual_axis_start_x; +static int virtual_axis_start_y; +static SDL_GameControllerButton virtual_button_active = SDL_CONTROLLER_BUTTON_INVALID; static void UpdateWindowTitle() { @@ -280,12 +292,170 @@ static void CyclePS5TriggerEffect() SDL_GameControllerSendEffect(gamecontroller, &state, sizeof(state)); } +static SDL_bool ShowingFront() +{ + SDL_bool showing_front = SDL_TRUE; + int i; + + if (gamecontroller) { + /* Show the back of the controller if the paddles are being held */ + for (i = SDL_CONTROLLER_BUTTON_PADDLE1; i <= SDL_CONTROLLER_BUTTON_PADDLE4; ++i) { + if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) { + showing_front = SDL_FALSE; + break; + } + } + } + if ((SDL_GetModState() & KMOD_SHIFT) != 0) { + showing_front = SDL_FALSE; + } + return showing_front; +} + +static int OpenVirtualController() +{ + SDL_VirtualJoystickDesc desc; + + SDL_zero(desc); + desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; + desc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER; + desc.naxes = SDL_CONTROLLER_AXIS_MAX; + desc.nbuttons = SDL_CONTROLLER_BUTTON_MAX; + return SDL_JoystickAttachVirtualEx(&desc); +} + +static SDL_GameControllerButton FindButtonAtPosition(int x, int y) +{ + SDL_Point point; + int i; + SDL_bool showing_front = ShowingFront(); + + point.x = x; + point.y = y; + for (i = 0; i < SDL_CONTROLLER_BUTTON_TOUCHPAD; ++i) { + SDL_bool on_front = (i < SDL_CONTROLLER_BUTTON_PADDLE1 || i > SDL_CONTROLLER_BUTTON_PADDLE4); + if (on_front == showing_front) { + SDL_Rect rect; + rect.x = button_positions[i].x; + rect.y = button_positions[i].y; + rect.w = BUTTON_SIZE; + rect.h = BUTTON_SIZE; + if (SDL_PointInRect(&point, &rect)) { + return (SDL_GameControllerButton)i; + } + } + } + return SDL_CONTROLLER_BUTTON_INVALID; +} + +static SDL_GameControllerAxis FindAxisAtPosition(int x, int y) +{ + SDL_Point point; + int i; + SDL_bool showing_front = ShowingFront(); + + point.x = x; + point.y = y; + for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) { + if (showing_front) { + SDL_Rect rect; + rect.x = axis_positions[i].x; + rect.y = axis_positions[i].y; + rect.w = AXIS_SIZE; + rect.h = AXIS_SIZE; + if (SDL_PointInRect(&point, &rect)) { + return (SDL_GameControllerAxis)i; + } + } + } + return SDL_CONTROLLER_AXIS_INVALID; +} + +static void VirtualControllerMouseMotion(int x, int y) +{ + if (virtual_button_active != SDL_CONTROLLER_BUTTON_INVALID) { + if (virtual_axis_active != SDL_CONTROLLER_AXIS_INVALID) { + const int MOVING_DISTANCE = 2; + if (SDL_abs(x - virtual_axis_start_x) >= MOVING_DISTANCE || + SDL_abs(y - virtual_axis_start_y) >= MOVING_DISTANCE) { + SDL_JoystickSetVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED); + virtual_button_active = SDL_CONTROLLER_BUTTON_INVALID; + } + } + } + + if (virtual_axis_active != SDL_CONTROLLER_AXIS_INVALID) { + if (virtual_axis_active == SDL_CONTROLLER_AXIS_TRIGGERLEFT || + virtual_axis_active == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); + float distance = SDL_clamp(((float)y - virtual_axis_start_y) / AXIS_SIZE, 0.0f, 1.0f); + Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range)); + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active, value); + } else { + float distanceX = SDL_clamp(((float)x - virtual_axis_start_x) / AXIS_SIZE, -1.0f, 1.0f); + float distanceY = SDL_clamp(((float)y - virtual_axis_start_y) / AXIS_SIZE, -1.0f, 1.0f); + Sint16 valueX, valueY; + + if (distanceX >= 0) { + valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX); + } else { + valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN); + } + if (distanceY >= 0) { + valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX); + } else { + valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN); + } + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active, valueX); + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active+1, valueY); + } + } +} + +static void VirtualControllerMouseDown(int x, int y) +{ + SDL_GameControllerButton button; + SDL_GameControllerAxis axis; + + button = FindButtonAtPosition(x, y); + if (button != SDL_CONTROLLER_BUTTON_INVALID) { + virtual_button_active = button; + SDL_JoystickSetVirtualButton(virtual_joystick, virtual_button_active, SDL_PRESSED); + } + + axis = FindAxisAtPosition(x, y); + if (axis != SDL_CONTROLLER_AXIS_INVALID) { + virtual_axis_active = axis; + virtual_axis_start_x = x; + virtual_axis_start_y = y; + } +} + +static void VirtualControllerMouseUp(int x, int y) +{ + if (virtual_button_active != SDL_CONTROLLER_BUTTON_INVALID) { + SDL_JoystickSetVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED); + virtual_button_active = SDL_CONTROLLER_BUTTON_INVALID; + } + + if (virtual_axis_active != SDL_CONTROLLER_AXIS_INVALID) { + if (virtual_axis_active == SDL_CONTROLLER_AXIS_TRIGGERLEFT || + virtual_axis_active == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) { + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN); + } else { + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active, 0); + SDL_JoystickSetVirtualAxis(virtual_joystick, virtual_axis_active+1, 0); + } + virtual_axis_active = SDL_CONTROLLER_AXIS_INVALID; + } +} + void loop(void *arg) { SDL_Event event; int i; - SDL_bool showing_front = SDL_TRUE; + SDL_bool showing_front; /* Update to get the current event state */ SDL_PumpEvents(); @@ -356,6 +526,24 @@ loop(void *arg) } break; + case SDL_MOUSEBUTTONDOWN: + if (virtual_joystick) { + VirtualControllerMouseDown(event.button.x, event.button.y); + } + break; + + case SDL_MOUSEBUTTONUP: + if (virtual_joystick) { + VirtualControllerMouseUp(event.button.x, event.button.y); + } + break; + + case SDL_MOUSEMOTION: + if (virtual_joystick) { + VirtualControllerMouseMotion(event.motion.x, event.motion.y); + } + break; + case SDL_KEYDOWN: if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { if (gamecontroller) { @@ -377,15 +565,7 @@ loop(void *arg) } } - if (gamecontroller) { - /* Show the back of the controller if the paddles are being held */ - for (i = SDL_CONTROLLER_BUTTON_PADDLE1; i <= SDL_CONTROLLER_BUTTON_PADDLE4; ++i) { - if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) { - showing_front = SDL_FALSE; - break; - } - } - } + showing_front = ShowingFront(); /* blank screen, set up for drawing this frame. */ SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); @@ -401,8 +581,8 @@ loop(void *arg) SDL_Rect dst; dst.x = button_positions[i].x; dst.y = button_positions[i].y; - dst.w = 50; - dst.h = 50; + dst.w = BUTTON_SIZE; + dst.h = BUTTON_SIZE; SDL_RenderCopyEx(screen, button, NULL, &dst, 0, NULL, SDL_FLIP_NONE); } } @@ -417,16 +597,16 @@ loop(void *arg) SDL_Rect dst; dst.x = axis_positions[i].x; dst.y = axis_positions[i].y; - dst.w = 50; - dst.h = 50; + dst.w = AXIS_SIZE; + dst.h = AXIS_SIZE; SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE); } else if (value > deadzone) { const double angle = axis_positions[i].angle + 180.0; SDL_Rect dst; dst.x = axis_positions[i].x; dst.y = axis_positions[i].y; - dst.w = 50; - dst.h = 50; + dst.w = AXIS_SIZE; + dst.h = AXIS_SIZE; SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE); } } @@ -627,8 +807,22 @@ main(int argc, char *argv[]) /* !!! FIXME: */ /*SDL_RenderSetLogicalSize(screen, background->w, background->h);*/ - if (argv[1] && *argv[1] != '-') { - controller_index = SDL_atoi(argv[1]); + for (i = 1; i < argc; ++i) { + if (SDL_strcmp(argv[i], "--virtual") == 0) { + int virtual_index = OpenVirtualController(); + if (virtual_index < 0) { + SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError()); + } else { + virtual_joystick = SDL_JoystickOpen(virtual_index); + if (!virtual_joystick) { + SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError()); + } + } + } + if (argv[i] && *argv[i] != '-') { + controller_index = SDL_atoi(argv[1]); + break; + } } if (controller_index < num_controllers) { gamecontroller = gamecontrollers[controller_index];