Hi,

The next stepping stone in my project to improve out-of-the-box
gamecontroller support.

Background: On other platforms (Linux, MacOSX), SDL2 uses a GUID for
gamecontrollers combined with a database [1] to provide the default
button/axis mappings. This hasn't worked on OpenBSD (or apparently any
*BSD) and the SDL2 code resorts to simply converting the first 16 chars
of the device name to the GUID, which doesn't help with making use of
the gamecontrollerdb [2]. Therefore, the way we have been handling this
so far is that the env var SDL_GAMECONTROLLERCONFIG was available to
hold a mapping string [3]. This poses a significant barrier, as users
have to learn the syntax of the mapping strings and find one fitting
their controller by trial and error mostly.

This diff fixes the issue. To accomplish that, a couple of tasks are
added:

- Calculate and store the GUID (Credit goes to brynet@ for finding the
  GUID calculation and writing the initial proof-of-concept code).
- Remove the workaround code in SDL_gamecontroller.c that would either
  read the mapping from SDL_GAMECONTROLLERCONFIG if present, or
  otherwise default to the (commonly used) XBox 360 controller layout.
- Enable the mappings in SDL_gamecontrollerdb.h for OpenBSD.
- Assign buttons correctly - not in the order that they appear in the
  HID report, but based on their numbering in the HID report descriptor.
- Invert Y/Ry axes for the newer XInput-type gamecontrollers. This has
  been a particularly annoying problem, as USB standards recommend the
  positive directions to be left to right and far to near, but these
  controllers just use near-to-far as the positive direction. [4]

The problem with the last bullet point is that the Y/Ry axis inversion
needs to be applied *only* to the XInput devices, and *not* to the
older DInput devices. I can't check for XBox360 and other interface
subclasses (as used by the kernel in uhidev.c), as
USB_GET_INTERFACE_DESC is not exposed in the uhid(4) driver (see
uhid_do_ioctl() in uhid.c).
The solution I came up with is admittedly a (minor) hack. It turns out
that the old DInput devices report their axis values as an unsigned
8bit integer, while the newer ones use a signed 16bit integer. With my
collection of gamecontrollers, checking if hitem.logical_maximum is
greater than 255 allows to reliably pick the correct handling of the
Y/Ry axes.

Other caveats:

- With the newly enabled GUID detection, SDL2 checks for a matching
  controller in gamecontrollerdb.txt in the application directory.
  I found that with one game (Cryptark), this contained bogus mappings
  for the XBox One controller (see the post on tech@), so some buttons
  and the Dpad didn't work until I removed that file.
- For some reason, the GUID for DInput devices matches the MACOSX
  entries in SDL_gamecontrollerdb.h, but the XInput devices match the
  LINUX entries. It's gonna take me a bit of time to get to the bottom
  of this; in the meantime just allowing both sets of GUID mappings
  works for the devices here on my testing
- The BSD_JoystickGetDeviceGUID() is not ideal at this point, as it
  takes the GUID stored as a string in char **joyguids and converts it
  into the raw numerical. I haven't figured out how to do the GUID
  calculation to avoid this additional transformation step yet. In the
  meantime, I don't notice performance issues and I don't think it's
  worth holding up the GUID mapping support over this.
 
Testing:

I tested this with sdl2-jstest (from sdl-jstest package) and few
games (Owlboy, Cryptark) with these controllers that I have:
XBox 360 controller (XInput), XBox One controller (XInput) (see separate
mail on tech@), Logitech F310 (both DInput and XInput), Logitech Dual
Action (DInput)). This maps everything correctly without additional
configuration (as long as faulty gamecontrollerdb.txt in the game
directory is removed).

Testing with a variety of controllers would be useful to see if this
really works across most of them as intended. If you do that, please
let me know the mapping/functioning before and after the diff is
applied.

[1] 
https://hg.libsdl.org/SDL/file/2370522dac4c/src/joystick/SDL_gamecontrollerdb.h
[2] 
https://hg.libsdl.org/SDL/file/2370522dac4c/src/joystick/bsd/SDL_bsdjoystick.c#l700
[3] https://marc.info/?l=openbsd-ports-cvs&m=152080802204959&w=2
[4] https://www.usb.org/sites/default/files/documents/hid1_11.pdf (page 20)

Index: Makefile
===================================================================
RCS file: /cvs/ports/devel/sdl2/Makefile,v
retrieving revision 1.32
diff -u -p -r1.32 Makefile
--- Makefile    6 Jan 2021 22:32:08 -0000       1.32
+++ Makefile    9 Jan 2021 16:09:20 -0000
@@ -5,6 +5,7 @@ COMMENT=        cross-platform multimedia libra
 V=             2.0.14
 DISTNAME=      SDL2-${V}
 PKGNAME=       sdl2-${V}
+REVISION=      0
 CATEGORIES=    devel
 MASTER_SITES=  https://www.libsdl.org/release/
 
Index: patches/patch-src_joystick_SDL_gamecontroller_c
===================================================================
RCS file: patches/patch-src_joystick_SDL_gamecontroller_c
diff -N patches/patch-src_joystick_SDL_gamecontroller_c
--- patches/patch-src_joystick_SDL_gamecontroller_c     6 Jan 2021 22:32:08 
-0000       1.7
+++ /dev/null   1 Jan 1970 00:00:00 -0000
@@ -1,50 +0,0 @@
-$OpenBSD: patch-src_joystick_SDL_gamecontroller_c,v 1.7 2021/01/06 22:32:08 
thfr Exp $
-
-enable GameController API the Linux fallback way (by posing as Xbox360
-controller)
-also disable checking string "Xbox 360 Wireless Receiver", so for now
-everything will be Xbox360 controller (works with generic joysticks)
-map to SDL_GAMECONTROLLERCONFIG envvar if available
-Use layout for XBox360 controller to maximize compatibility because
-many controllers use this mapping
-
-Index: src/joystick/SDL_gamecontroller.c
---- src/joystick/SDL_gamecontroller.c.orig
-+++ src/joystick/SDL_gamecontroller.c
-@@ -968,7 +968,7 @@ static char *SDL_PrivateGetControllerGUIDFromMappingSt
-             SDL_memcpy(&pchGUID[8], &pchGUID[0], 4);
-             SDL_memcpy(&pchGUID[0], "03000000", 8);
-         }
--#elif __MACOSX__
-+#elif defined(__MACOSX__) || defined(__OpenBSD__)
-         if (SDL_strlen(pchGUID) == 32 &&
-             SDL_memcmp(&pchGUID[4], "000000000000", 12) == 0 &&
-             SDL_memcmp(&pchGUID[20], "000000000000", 12) == 0) {
-@@ -1131,17 +1131,21 @@ static ControllerMapping_t *SDL_PrivateGetControllerMa
-     ControllerMapping_t *mapping;
- 
-     mapping = SDL_PrivateGetControllerMappingForGUID(guid, SDL_FALSE);
--#ifdef __LINUX__
-+#if defined(__LINUX__) || defined(__OpenBSD__)
-     if (!mapping && name) {
--        if (SDL_strstr(name, "Xbox 360 Wireless Receiver")) {
--            /* The Linux driver xpad.c maps the wireless dpad to buttons */
--            SDL_bool existing;
-+        /* The Linux driver xpad.c maps the wireless dpad to buttons */
-+        SDL_bool existing;
-+        char guid_str[1024];
-+        SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
-+        if (SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG) == NULL) {
-             mapping = SDL_PrivateAddMappingForGUID(guid,
--"none,X360 Wireless 
Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3",
-+"none,XBox360 
Controller,a:b7,b:b8,back:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b2,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b3,righttrigger:a5,rightx:a3,righty:a4~,start:b0,x:b9,y:b10",
-                           &existing, SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT);
-+        } else {
-+            mapping = SDL_PrivateAddMappingForGUID(guid, 
SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG), &existing, 
SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT);
-         }
-     }
--#endif /* __LINUX__ */
-+#endif /* __LINUX__ || __OpenBSD__ */
- 
-     if (!mapping && name && !SDL_IsJoystickWGI(guid)) {
-         if (SDL_strstr(name, "Xbox") || SDL_strstr(name, "X-Box") || 
SDL_strstr(name, "XBOX")) {
Index: patches/patch-src_joystick_SDL_gamecontrollerdb_h
===================================================================
RCS file: patches/patch-src_joystick_SDL_gamecontrollerdb_h
diff -N patches/patch-src_joystick_SDL_gamecontrollerdb_h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-src_joystick_SDL_gamecontrollerdb_h   9 Jan 2021 16:09:20 
-0000
@@ -0,0 +1,26 @@
+$OpenBSD$
+
+enable controller detection by GUID on OpenBSD
+use both LINUX and MACOSX guids to match both XInput and DInput devices
+
+Index: src/joystick/SDL_gamecontrollerdb.h
+--- src/joystick/SDL_gamecontrollerdb.h.orig
++++ src/joystick/SDL_gamecontrollerdb.h
+@@ -338,7 +338,7 @@ static const char *s_ControllerMappings [] =
+     
"030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
+     "03000000101c0000171c000000000000,uRage 
Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
+ #endif
+-#if defined(__MACOSX__)
++#if defined(__MACOSX__) || defined(__OpenBSD__)
+     "03000000c82d00000090000001000000,8BitDo FC30 
Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+     "03000000c82d00000090000001000000,8BitDo FC30 
Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+     "03000000c82d00001038000000010000,8BitDo FC30 
Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+@@ -479,7 +479,7 @@ static const char *s_ControllerMappings [] =
+     "03000000830500006020000000010000,iBuffalo SNES 
Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+     "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button 
Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,",
+ #endif
+-#if defined(__LINUX__)
++#if defined(__LINUX__) || defined(__OpenBSD__)
+     "03000000c82d00000090000011010000,8BitDo FC30 
Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+     "03000000c82d00000090000011010000,8BitDo FC30 
Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+     "05000000c82d00001038000000010000,8BitDo FC30 
Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
Index: patches/patch-src_joystick_bsd_SDL_bsdjoystick_c
===================================================================
RCS file: patches/patch-src_joystick_bsd_SDL_bsdjoystick_c
diff -N patches/patch-src_joystick_bsd_SDL_bsdjoystick_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-src_joystick_bsd_SDL_bsdjoystick_c    9 Jan 2021 16:09:20 
-0000
@@ -0,0 +1,128 @@
+$OpenBSD$
+
+assign buttons correctly
+get GUID using USB_GET_DEVICEINFO
+detect newer (XInput-style) gamecontroller if hitem.logical_maximum is
+> 255; if so invert y axes
+
+Index: src/joystick/bsd/SDL_bsdjoystick.c
+--- src/joystick/bsd/SDL_bsdjoystick.c.orig
++++ src/joystick/bsd/SDL_bsdjoystick.c
+@@ -83,6 +83,9 @@
+ 
+ #ifdef __OpenBSD__
+ 
++#define DEV_USB               3       /* needed to get GUID from 
USB_GET_DEVICEINFO */
++#define GUID_LEN      32      /* GUID string has length 32 */
++
+ #define HUG_DPAD_UP         0x90
+ #define HUG_DPAD_DOWN       0x91
+ #define HUG_DPAD_RIGHT      0x92
+@@ -192,6 +195,9 @@ struct joystick_hwdata
+ 
+ static char *joynames[MAX_JOYS];
+ static char *joydevnames[MAX_JOYS];
++#ifdef __OpenBSD__
++static char joyguids[MAX_JOYS][GUID_LEN];
++#endif
+ 
+ static int report_alloc(struct report *, struct report_desc *, int);
+ static void report_free(struct report *);
+@@ -217,11 +223,17 @@ BSD_JoystickInit(void)
+ {
+     char s[16];
+     int i, fd;
++#ifdef __OpenBSD__
++    struct usb_device_info di;
++#endif
+ 
+     numjoysticks = 0;
+ 
+     SDL_memset(joynames, 0, sizeof(joynames));
+     SDL_memset(joydevnames, 0, sizeof(joydevnames));
++#ifdef __OpenBSD__
++    SDL_memset(joyguids, 0, sizeof(char) * MAX_JOYS * GUID_LEN);
++#endif
+ 
+     for (i = 0; i < MAX_UHID_JOYS; i++) {
+         SDL_Joystick nj;
+@@ -232,6 +244,22 @@ BSD_JoystickInit(void)
+ 
+         if (BSD_JoystickOpen(&nj, numjoysticks) == 0) {
+             BSD_JoystickClose(&nj);
++#ifdef __OpenBSD__
++            /* get GUID string and store it in joyguids */
++            fd = open(s, O_RDONLY);
++            if (fd != -1) {
++                if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) {
++                    SDL_snprintf(joyguids[numjoysticks],
++                                 SDL_arraysize(joyguids[numjoysticks]),
++                                 
"%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
++                                 DEV_USB & 0xFF, DEV_USB >> 8,
++                                 di.udi_vendorNo & 0xFF, di.udi_vendorNo >> 8,
++                                 di.udi_productNo & 0xFF, di.udi_productNo >> 
8,
++                                 di.udi_releaseNo & 0xFF, di.udi_releaseNo >> 
8);
++                }
++            }
++            close(fd);
++#endif
+             numjoysticks++;
+         } else {
+             SDL_free(joynames[numjoysticks]);
+@@ -544,6 +572,7 @@ BSD_JoystickUpdate(SDL_Joystick *joy)
+     Sint32 v;
+ #ifdef __OpenBSD__
+     Sint32 dpad[4] = {0, 0, 0, 0};
++    int actualbutton;
+ #endif
+ 
+ #if defined(__FREEBSD__) || SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H || 
defined(__FreeBSD_kernel__) || defined(__DragonFly_)
+@@ -618,6 +647,18 @@ BSD_JoystickUpdate(SDL_Joystick *joy)
+                             naxe = joy->hwdata->axis_map[joyaxe];
+                             /* scaleaxe */
+                             v = (Sint32) hid_get_data(REP_BUF_DATA(rep), 
&hitem);
++#ifdef __OpenBSD__
++                            /* XInput controllermapping relies on inverted Y 
axes.
++                             * These devices have a 16bit signed space, as 
opposed
++                             * to older DInput devices (8bit unsigned), so
++                             * hitem.logical_maximum can be used to 
differentiate them.
++                             */
++                            if ((joyaxe == JOYAXE_Y || joyaxe == JOYAXE_RY)
++                                && hitem.logical_maximum > 255) {
++                                if (v != 0)
++                                    v = ~v;
++                            }
++#endif
+                             v -= (hitem.logical_maximum +
+                                   hitem.logical_minimum + 1) / 2;
+                             v *= 32768 /
+@@ -652,7 +693,12 @@ BSD_JoystickUpdate(SDL_Joystick *joy)
+                     }
+                 case HUP_BUTTON:
+                     v = (Sint32) hid_get_data(REP_BUF_DATA(rep), &hitem);
++#ifdef __OpenBSD__
++                    actualbutton = HID_USAGE(hitem.usage) - 1;        /* sdl 
buttons are zero-based */
++                    SDL_PrivateJoystickButton(joy, actualbutton, v);
++#else
+                     SDL_PrivateJoystickButton(joy, nbutton, v);
++#endif
+                     nbutton++;
+                     break;
+                 default:
+@@ -697,11 +743,16 @@ static SDL_JoystickGUID
+ BSD_JoystickGetDeviceGUID( int device_index )
+ {
+     SDL_JoystickGUID guid;
++#ifdef __OpenBSD__
++    guid = SDL_JoystickGetGUIDFromString(joyguids[device_index]);
++    return guid;
++#else
+     /* the GUID is just the first 16 chars of the name for now */
+     const char *name = BSD_JoystickGetDeviceName( device_index );
+     SDL_zero( guid );
+     SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
+     return guid;
++#endif
+ }
+ 
+ static int

Reply via email to