SDL: Added game controller support for virtual joysticks

From 7ad15c5b8f937bb759ae1f7d85f1a144a65194c6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 15 May 2022 09:35:52 -0700
Subject: [PATCH] Added game controller support for virtual joysticks

Fixes https://github.com/libsdl-org/SDL/issues/5662
---
 src/joystick/SDL_gamecontroller.c          | 97 ++++++++++++++++++++++
 src/joystick/SDL_sysjoystick.h             |  1 +
 src/joystick/virtual/SDL_virtualjoystick.c | 71 +++++++++++++++-
 3 files changed, 168 insertions(+), 1 deletion(-)

diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index fad7d9a0931..50eb190a53c 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -464,6 +464,100 @@ 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
@@ -696,6 +790,9 @@ 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);
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 37025ddde42..04b6b7a0c91 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -119,6 +119,7 @@ struct _SDL_Joystick
 };
 
 /* Device bus definitions */
+#define SDL_HARDWARE_BUS_VIRTUAL    0x00
 #define SDL_HARDWARE_BUS_USB        0x03
 #define SDL_HARDWARE_BUS_BLUETOOTH  0x05
 
diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c
index 0e6525d5fb3..9be879b30aa 100644
--- a/src/joystick/virtual/SDL_virtualjoystick.c
+++ b/src/joystick/virtual/SDL_virtualjoystick.c
@@ -24,6 +24,7 @@
 
 /* This is the virtual implementation of the SDL joystick API */
 
+#include "SDL_endian.h"
 #include "SDL_virtualjoystick_c.h"
 #include "../SDL_sysjoystick.h"
 #include "../SDL_joystick_c.h"
@@ -94,6 +95,11 @@ SDL_JoystickAttachVirtualInner(SDL_JoystickType type,
 {
     joystick_hwdata *hwdata = NULL;
     int device_index = -1;
+    const Uint16 vendor_id = 0;
+    const Uint16 product_id = 0;
+    Uint16 button_mask = 0;
+    Uint16 axis_mask = 0;
+    Uint16 *guid16;
 
     hwdata = SDL_calloc(1, sizeof(joystick_hwdata));
     if (!hwdata) {
@@ -104,7 +110,69 @@ SDL_JoystickAttachVirtualInner(SDL_JoystickType type,
     hwdata->naxes = naxes;
     hwdata->nbuttons = nbuttons;
     hwdata->nhats = nhats;
-    hwdata->name = "Virtual Joystick";
+
+    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 (type == SDL_JOYSTICK_TYPE_GAMECONTROLLER) {
+        int i;
+
+        if (naxes >= 2) {
+            axis_mask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY));
+        }
+        if (naxes >= 4) {
+            axis_mask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY));
+        }
+        if (naxes >= 6) {
+            axis_mask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
+        }
+
+        for (i = 0; i < nbuttons && i < sizeof(Uint16)*8; ++i) {
+            button_mask |= (1 << i);
+        }
+    }
+
+    /* We only need 16 bits for each of these; space them out to fill 128. */
+    /* Byteswap so devices get same GUID on little/big endian platforms. */
+    guid16 = (Uint16 *)hwdata->guid.data;
+    *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_VIRTUAL);
+    *guid16++ = 0;
+    *guid16++ = SDL_SwapLE16(vendor_id);
+    *guid16++ = 0;
+    *guid16++ = SDL_SwapLE16(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';
@@ -326,6 +394,7 @@ VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
         return SDL_SetError("No such device");
     }
     if (hwdata->opened) {
+        /* This should never happen, it's handled by the higher joystick code */
         return SDL_SetError("Joystick already opened");
     }
     joystick->instance_id = hwdata->instance_id;