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
This commit is contained in:
Sam Lantinga 2022-05-15 20:01:12 -07:00
parent 7ad15c5b8f
commit 94eeb587c1
9 changed files with 509 additions and 203 deletions

View File

@ -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.
*

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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) {
if (desc->name) {
name = desc->name;
} else {
switch (desc->type) {
case SDL_JOYSTICK_TYPE_GAMECONTROLLER:
hwdata->name = "Virtual Controller";
name = "Virtual Controller";
break;
case SDL_JOYSTICK_TYPE_WHEEL:
hwdata->name = "Virtual Wheel";
name = "Virtual Wheel";
break;
case SDL_JOYSTICK_TYPE_ARCADE_STICK:
hwdata->name = "Virtual Arcade Stick";
name = "Virtual Arcade Stick";
break;
case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
hwdata->name = "Virtual Flight Stick";
name = "Virtual Flight Stick";
break;
case SDL_JOYSTICK_TYPE_DANCE_PAD:
hwdata->name = "Virtual Dance Pad";
name = "Virtual Dance Pad";
break;
case SDL_JOYSTICK_TYPE_GUITAR:
hwdata->name = "Virtual Guitar";
name = "Virtual Guitar";
break;
case SDL_JOYSTICK_TYPE_DRUM_KIT:
hwdata->name = "Virtual Drum Kit";
name = "Virtual Drum Kit";
break;
case SDL_JOYSTICK_TYPE_ARCADE_PAD:
hwdata->name = "Virtual Arcade Pad";
name = "Virtual Arcade Pad";
break;
case SDL_JOYSTICK_TYPE_THROTTLE:
hwdata->name = "Virtual Throttle";
name = "Virtual Throttle";
break;
default:
hwdata->name = "Virtual Joystick";
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,9 +522,136 @@ VIRTUAL_JoystickQuit(void)
static SDL_bool
VIRTUAL_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
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 =
{
VIRTUAL_JoystickInit,

View File

@ -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: */

View File

@ -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] != '-') {
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];