Improved code to get the name and guid for joysticks on OpenBSD and NetBSD

Also dynamically allocate joysticks to reduce static memory usage
This commit is contained in:
Sam Lantinga 2022-08-28 18:17:50 -07:00
parent 32700294e2
commit a9d3935a84
1 changed files with 380 additions and 324 deletions

View File

@ -30,6 +30,7 @@
*/ */
#include <sys/param.h> #include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -87,9 +88,6 @@
#ifdef __OpenBSD__ #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_UP 0x90
#define HUG_DPAD_DOWN 0x91 #define HUG_DPAD_DOWN 0x91
#define HUG_DPAD_RIGHT 0x92 #define HUG_DPAD_RIGHT 0x92
@ -191,16 +189,29 @@ struct joystick_hwdata
BSDJOY_UHID, /* uhid(4) */ BSDJOY_UHID, /* uhid(4) */
BSDJOY_JOY /* joy(4) */ BSDJOY_JOY /* joy(4) */
} type; } type;
int naxes;
int nbuttons;
int nhats;
struct report_desc *repdesc; struct report_desc *repdesc;
struct report inreport; struct report inreport;
int axis_map[JOYAXE_count]; /* map present JOYAXE_* to 0,1,.. */ int axis_map[JOYAXE_count]; /* map present JOYAXE_* to 0,1,.. */
}; };
static char *joynames[MAX_JOYS]; /* A linked list of available joysticks */
static char *joydevnames[MAX_JOYS]; typedef struct SDL_joylist_item
#ifdef __OpenBSD__ {
static char joyguids[MAX_JOYS][GUID_LEN]; SDL_JoystickID device_instance;
#endif char *path; /* "/dev/uhid0" or whatever */
char *name; /* "SideWinder 3D Pro" or whatever */
SDL_JoystickGUID guid;
dev_t devnum;
struct SDL_joylist_item *next;
} SDL_joylist_item;
static SDL_joylist_item *SDL_joylist = NULL;
static SDL_joylist_item *SDL_joylist_tail = NULL;
static int numjoysticks = 0;
static int report_alloc(struct report *, struct report_desc *, int); static int report_alloc(struct report *, struct report_desc *, int);
static void report_free(struct report *); static void report_free(struct report *);
@ -216,104 +227,6 @@ static void report_free(struct report *);
#define REP_BUF_DATA(rep) ((rep)->buf->data) #define REP_BUF_DATA(rep) ((rep)->buf->data)
#endif #endif
static int numjoysticks = 0;
static int BSD_JoystickOpen(SDL_Joystick *joy, int device_index);
static void BSD_JoystickClose(SDL_Joystick *joy);
static int
BSD_JoystickInit(void)
{
char s[16];
int i, fd;
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;
#if defined(__OpenBSD__) && (OpenBSD >= 202105)
SDL_snprintf(s, SDL_arraysize(s), "/dev/ujoy/%d", i);
#else
SDL_snprintf(s, SDL_arraysize(s), "/dev/uhid%d", i);
#endif
joynames[numjoysticks] = SDL_strdup(s);
if (BSD_JoystickOpen(&nj, numjoysticks) == 0) {
BSD_JoystickClose(&nj);
numjoysticks++;
} else {
SDL_free(joynames[numjoysticks]);
joynames[numjoysticks] = NULL;
}
}
#ifdef SUPPORT_JOY_GAMEPORT
for (i = 0; i < MAX_JOY_JOYS; i++) {
SDL_snprintf(s, SDL_arraysize(s), "/dev/joy%d", i);
fd = open(s, O_RDONLY | O_CLOEXEC);
if (fd != -1) {
joynames[numjoysticks++] = SDL_strdup(s);
close(fd);
}
}
#endif /* SUPPORT_JOY_GAMEPORT */
/* Read the default USB HID usage table. */
hid_init(NULL);
return (numjoysticks);
}
static int
BSD_JoystickGetCount(void)
{
return numjoysticks;
}
static void
BSD_JoystickDetect(void)
{
}
static const char *
BSD_JoystickGetDeviceName(int device_index)
{
if (joydevnames[device_index] != NULL) {
return joydevnames[device_index];
}
return joynames[device_index];
}
static const char *
BSD_JoystickGetDevicePath(int device_index)
{
return joynames[device_index];
}
static int
BSD_JoystickGetDevicePlayerIndex(int device_index)
{
return -1;
}
static void
BSD_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
/* Function to perform the mapping from device index to the instance id for this index */
static SDL_JoystickID
BSD_JoystickGetDeviceInstanceID(int device_index)
{
return device_index;
}
static int static int
usage_to_joyaxe(unsigned usage) usage_to_joyaxe(unsigned usage)
@ -350,6 +263,345 @@ usage_to_joyaxe(unsigned usage)
return joyaxe; return joyaxe;
} }
static void
FreeJoylistItem(SDL_joylist_item *item)
{
SDL_free(item->path);
SDL_free(item->name);
SDL_free(item);
}
static void
FreeHwData(struct joystick_hwdata *hw)
{
if (hw->type == BSDJOY_UHID) {
report_free(&hw->inreport);
if (hw->repdesc) {
hid_dispose_report_desc(hw->repdesc);
}
}
close(hw->fd);
SDL_free(hw);
}
static struct joystick_hwdata *
CreateHwData(const char *path)
{
struct joystick_hwdata *hw;
struct hid_item hitem;
struct hid_data *hdata;
struct report *rep = NULL;
int fd;
int i;
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
SDL_SetError("%s: %s", path, strerror(errno));
return NULL;
}
hw = (struct joystick_hwdata *)
SDL_calloc(1, sizeof(struct joystick_hwdata));
if (hw == NULL) {
close(fd);
SDL_OutOfMemory();
return NULL;
}
hw->fd = fd;
#ifdef SUPPORT_JOY_GAMEPORT
if (SDL_strncmp(path, "/dev/joy", 8) == 0) {
hw->type = BSDJOY_JOY;
hw->naxes = 2;
hw->nbuttons = 2;
} else
#endif
{
hw->type = BSDJOY_UHID;
{
int ax;
for (ax = 0; ax < JOYAXE_count; ax++)
hw->axis_map[ax] = -1;
}
hw->repdesc = hid_get_report_desc(fd);
if (hw->repdesc == NULL) {
SDL_SetError("%s: USB_GET_REPORT_DESC: %s", path,
strerror(errno));
goto usberr;
}
rep = &hw->inreport;
#if defined(__FREEBSD__) && (__FreeBSD_kernel_version > 800063) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
rep->rid = hid_get_report_id(fd);
if (rep->rid < 0) {
#else
if (ioctl(fd, USB_GET_REPORT_ID, &rep->rid) < 0) {
#endif
rep->rid = -1; /* XXX */
}
if (report_alloc(rep, hw->repdesc, REPORT_INPUT) < 0) {
goto usberr;
}
if (rep->size <= 0) {
SDL_SetError("%s: Input report descriptor has invalid length",
path);
goto usberr;
}
#if defined(USBHID_NEW) || (defined(__FREEBSD__) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
hdata = hid_start_parse(hw->repdesc, 1 << hid_input, rep->rid);
#else
hdata = hid_start_parse(hw->repdesc, 1 << hid_input);
#endif
if (hdata == NULL) {
SDL_SetError("%s: Cannot start HID parser", path);
goto usberr;
}
for (i = 0; i < JOYAXE_count; i++)
hw->axis_map[i] = -1;
while (hid_get_item(hdata, &hitem) > 0) {
switch (hitem.kind) {
case hid_input:
switch (HID_PAGE(hitem.usage)) {
case HUP_GENERIC_DESKTOP:
{
unsigned usage = HID_USAGE(hitem.usage);
int joyaxe = usage_to_joyaxe(usage);
if (joyaxe >= 0) {
hw->axis_map[joyaxe] = 1;
} else if (usage == HUG_HAT_SWITCH
#ifdef __OpenBSD__
|| usage == HUG_DPAD_UP
#endif
) {
hw->nhats++;
}
break;
}
case HUP_BUTTON:
hw->nbuttons++;
break;
default:
break;
}
break;
default:
break;
}
}
hid_end_parse(hdata);
for (i = 0; i < JOYAXE_count; i++)
if (hw->axis_map[i] > 0)
hw->axis_map[i] = hw->naxes++;
if (hw->naxes == 0 && hw->nbuttons == 0 && hw->nhats == 0) {
SDL_SetError("%s: Not a joystick, ignoring", path);
goto usberr;
}
}
/* The poll blocks the event thread. */
fcntl(fd, F_SETFL, O_NONBLOCK);
#ifdef __NetBSD__
/* Flush pending events */
if (rep) {
while (read(fd, REP_BUF_DATA(rep), rep->size) == rep->size)
;
}
#endif
return hw;
usberr:
FreeHwData(hw);
return NULL;
}
static int
MaybeAddDevice(const char *path)
{
struct stat sb;
char *name = NULL;
SDL_JoystickGUID guid;
SDL_joylist_item *item;
struct joystick_hwdata *hw;
if (path == NULL) {
return -1;
}
if (stat(path, &sb) == -1) {
return -1;
}
/* Check to make sure it's not already in list. */
for (item = SDL_joylist; item != NULL; item = item->next) {
if (sb.st_rdev == item->devnum) {
return -1; /* already have this one */
}
}
hw = CreateHwData(path);
if (!hw) {
return -1;
}
if (hw->type == BSDJOY_JOY) {
name = SDL_strdup("Gameport joystick");
guid = SDL_CreateJoystickGUIDForName(name);
} else {
#ifdef USB_GET_DEVICEINFO
struct usb_device_info di;
if (ioctl(hw->fd, USB_GET_DEVICEINFO, &di) != -1) {
name = SDL_CreateJoystickName(di.udi_vendorNo, di.udi_productNo, di.udi_vendor, di.udi_product);
guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name, 0, 0);
#ifdef SDL_JOYSTICK_HIDAPI
if (HIDAPI_IsDevicePresent(di.udi_vendorNo, di.udi_productNo, di.udi_releaseNo, name)) {
/* The HIDAPI driver is taking care of this device */
SDL_free(name);
FreeHwData(hw);
return -1;
}
#endif
if (SDL_ShouldIgnoreJoystick(name, guid)) {
SDL_free(name);
FreeHwData(hw);
return -1;
}
}
#endif /* USB_GET_DEVICEINFO */
}
if (!name) {
name = SDL_strdup(path);
guid = SDL_CreateJoystickGUIDForName(name);
}
FreeHwData(hw);
item = (SDL_joylist_item *) SDL_calloc(1, sizeof (SDL_joylist_item));
if (item == NULL) {
SDL_free(name);
return -1;
}
item->devnum = sb.st_rdev;
item->path = SDL_strdup(path);
item->name = name;
item->guid = guid;
if ((item->path == NULL) || (item->name == NULL)) {
FreeJoylistItem(item);
return -1;
}
item->device_instance = SDL_GetNextJoystickInstanceID();
if (SDL_joylist_tail == NULL) {
SDL_joylist = SDL_joylist_tail = item;
} else {
SDL_joylist_tail->next = item;
SDL_joylist_tail = item;
}
/* Need to increment the joystick count before we post the event */
++numjoysticks;
SDL_PrivateJoystickAdded(item->device_instance);
return numjoysticks;
}
static int
BSD_JoystickInit(void)
{
char s[16];
int i;
for (i = 0; i < MAX_UHID_JOYS; i++) {
#if defined(__OpenBSD__) && (OpenBSD >= 202105)
SDL_snprintf(s, SDL_arraysize(s), "/dev/ujoy/%d", i);
#else
SDL_snprintf(s, SDL_arraysize(s), "/dev/uhid%d", i);
#endif
MaybeAddDevice(s);
}
#ifdef SUPPORT_JOY_GAMEPORT
for (i = 0; i < MAX_JOY_JOYS; i++) {
SDL_snprintf(s, SDL_arraysize(s), "/dev/joy%d", i);
MaybeAddDevice(s);
}
#endif /* SUPPORT_JOY_GAMEPORT */
/* Read the default USB HID usage table. */
hid_init(NULL);
return numjoysticks;
}
static int
BSD_JoystickGetCount(void)
{
return numjoysticks;
}
static void
BSD_JoystickDetect(void)
{
}
static SDL_joylist_item *
JoystickByDevIndex(int device_index)
{
SDL_joylist_item *item = SDL_joylist;
if ((device_index < 0) || (device_index >= numjoysticks)) {
return NULL;
}
while (device_index > 0) {
SDL_assert(item != NULL);
device_index--;
item = item->next;
}
return item;
}
static const char *
BSD_JoystickGetDeviceName(int device_index)
{
return JoystickByDevIndex(device_index)->name;
}
static const char *
BSD_JoystickGetDevicePath(int device_index)
{
return JoystickByDevIndex(device_index)->path;
}
static int
BSD_JoystickGetDevicePlayerIndex(int device_index)
{
return -1;
}
static void
BSD_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
static SDL_JoystickGUID
BSD_JoystickGetDeviceGUID(int device_index)
{
return JoystickByDevIndex(device_index)->guid;
}
/* Function to perform the mapping from device index to the instance id for this index */
static SDL_JoystickID
BSD_JoystickGetDeviceInstanceID(int device_index)
{
return JoystickByDevIndex(device_index)->device_instance;
}
static unsigned static unsigned
hatval_to_sdl(Sint32 hatval) hatval_to_sdl(Sint32 hatval)
{ {
@ -369,208 +621,25 @@ hatval_to_sdl(Sint32 hatval)
static int static int
BSD_JoystickOpen(SDL_Joystick *joy, int device_index) BSD_JoystickOpen(SDL_Joystick *joy, int device_index)
{ {
char *path = joynames[device_index]; SDL_joylist_item *item = JoystickByDevIndex(device_index);
struct joystick_hwdata *hw; struct joystick_hwdata *hw;
struct hid_item hitem;
struct hid_data *hdata;
struct report *rep = NULL;
#if defined(__NetBSD__)
usb_device_descriptor_t udd;
struct usb_string_desc usd;
#endif
int fd;
int i;
#ifdef __OpenBSD__
struct usb_device_info di;
#endif
fd = open(path, O_RDONLY | O_CLOEXEC); if (item == NULL) {
if (fd == -1) { return SDL_SetError("No such device");
return SDL_SetError("%s: %s", path, strerror(errno));
} }
joy->instance_id = device_index; hw = CreateHwData(item->path);
hw = (struct joystick_hwdata *) if (!hw) {
SDL_malloc(sizeof(struct joystick_hwdata)); return -1;
if (hw == NULL) {
close(fd);
return SDL_OutOfMemory();
} }
joy->instance_id = item->device_instance;
joy->hwdata = hw; joy->hwdata = hw;
hw->fd = fd; joy->naxes = hw->naxes;
#ifdef SUPPORT_JOY_GAMEPORT joy->nbuttons = hw->nbuttons;
if (SDL_strncmp(path, "/dev/joy", 8) == 0) { joy->nhats = hw->nhats;
hw->type = BSDJOY_JOY;
joy->naxes = 2;
joy->nbuttons = 2;
joy->nhats = 0;
joy->nballs = 0;
joydevnames[device_index] = SDL_strdup("Gameport joystick");
goto usbend;
} else
#endif
{
hw->type = BSDJOY_UHID;
}
{ return 0;
int ax;
for (ax = 0; ax < JOYAXE_count; ax++)
hw->axis_map[ax] = -1;
}
hw->repdesc = hid_get_report_desc(fd);
if (hw->repdesc == NULL) {
SDL_SetError("%s: USB_GET_REPORT_DESC: %s", path,
strerror(errno));
goto usberr;
}
rep = &hw->inreport;
#if defined(__FREEBSD__) && (__FreeBSD_kernel_version > 800063) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
rep->rid = hid_get_report_id(fd);
if (rep->rid < 0) {
#else
if (ioctl(fd, USB_GET_REPORT_ID, &rep->rid) < 0) {
#endif
rep->rid = -1; /* XXX */
}
#if defined(__NetBSD__)
if (ioctl(fd, USB_GET_DEVICE_DESC, &udd) == -1)
goto desc_failed;
/* Get default language */
usd.usd_string_index = USB_LANGUAGE_TABLE;
usd.usd_language_id = 0;
if (ioctl(fd, USB_GET_STRING_DESC, &usd) == -1 || usd.usd_desc.bLength < 4) {
usd.usd_language_id = 0;
} else {
usd.usd_language_id = UGETW(usd.usd_desc.bString[0]);
}
usd.usd_string_index = udd.iProduct;
if (ioctl(fd, USB_GET_STRING_DESC, &usd) == 0) {
char str[128];
char *new_name = NULL;
int i;
for (i = 0; i < (usd.usd_desc.bLength >> 1) - 1 && i < sizeof(str) - 1; i++) {
str[i] = UGETW(usd.usd_desc.bString[i]);
}
str[i] = '\0';
SDL_asprintf(&new_name, "%s @ %s", str, path);
if (new_name != NULL) {
SDL_free(joydevnames[numjoysticks]);
joydevnames[numjoysticks] = new_name;
}
}
desc_failed:
#endif
#if defined(__OpenBSD__)
if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) {
SDL_snprintf(joyguids[numjoysticks],
SDL_arraysize(joyguids[device_index]),
"%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);
}
#endif
if (report_alloc(rep, hw->repdesc, REPORT_INPUT) < 0) {
goto usberr;
}
if (rep->size <= 0) {
SDL_SetError("%s: Input report descriptor has invalid length",
path);
goto usberr;
}
#if defined(USBHID_NEW) || (defined(__FREEBSD__) && __FreeBSD_kernel_version >= 500111) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
hdata = hid_start_parse(hw->repdesc, 1 << hid_input, rep->rid);
#else
hdata = hid_start_parse(hw->repdesc, 1 << hid_input);
#endif
if (hdata == NULL) {
SDL_SetError("%s: Cannot start HID parser", path);
goto usberr;
}
joy->naxes = 0;
joy->nbuttons = 0;
joy->nhats = 0;
joy->nballs = 0;
for (i = 0; i < JOYAXE_count; i++)
hw->axis_map[i] = -1;
while (hid_get_item(hdata, &hitem) > 0) {
char *sp;
const char *s;
switch (hitem.kind) {
case hid_collection:
switch (HID_PAGE(hitem.usage)) {
case HUP_GENERIC_DESKTOP:
switch (HID_USAGE(hitem.usage)) {
case HUG_JOYSTICK:
case HUG_GAME_PAD:
s = hid_usage_in_page(hitem.usage);
sp = SDL_malloc(SDL_strlen(s) + 5);
SDL_snprintf(sp, SDL_strlen(s) + 5, "%s (%d)",
s, device_index);
joydevnames[device_index] = sp;
}
}
break;
case hid_input:
switch (HID_PAGE(hitem.usage)) {
case HUP_GENERIC_DESKTOP:
{
unsigned usage = HID_USAGE(hitem.usage);
int joyaxe = usage_to_joyaxe(usage);
if (joyaxe >= 0) {
hw->axis_map[joyaxe] = 1;
} else if (usage == HUG_HAT_SWITCH
#ifdef __OpenBSD__
|| usage == HUG_DPAD_UP
#endif
) {
joy->nhats++;
}
break;
}
case HUP_BUTTON:
joy->nbuttons++;
break;
default:
break;
}
break;
default:
break;
}
}
hid_end_parse(hdata);
for (i = 0; i < JOYAXE_count; i++)
if (hw->axis_map[i] > 0)
hw->axis_map[i] = joy->naxes++;
if (joy->naxes == 0 && joy->nbuttons == 0 && joy->nhats == 0 && joy->nballs == 0) {
SDL_SetError("%s: Not a joystick, ignoring", path);
goto usberr;
}
usbend:
/* The poll blocks the event thread. */
fcntl(fd, F_SETFL, O_NONBLOCK);
#ifdef __NetBSD__
/* Flush pending events */
if (rep) {
while (read(joy->hwdata->fd, REP_BUF_DATA(rep), rep->size) == rep->size)
;
}
#endif
return (0);
usberr:
close(hw->fd);
SDL_free(hw);
return (-1);
} }
static void static void
@ -728,39 +797,26 @@ BSD_JoystickUpdate(SDL_Joystick *joy)
static void static void
BSD_JoystickClose(SDL_Joystick *joy) BSD_JoystickClose(SDL_Joystick *joy)
{ {
if (joy->hwdata->type == BSDJOY_UHID) { if (joy->hwdata) {
report_free(&joy->hwdata->inreport); FreeHwData(joy->hwdata);
hid_dispose_report_desc(joy->hwdata->repdesc); joy->hwdata = NULL;
} }
close(joy->hwdata->fd);
SDL_free(joy->hwdata);
} }
static void static void
BSD_JoystickQuit(void) BSD_JoystickQuit(void)
{ {
int i; SDL_joylist_item *item = NULL;
SDL_joylist_item *next = NULL;
for (i = 0; i < MAX_JOYS; i++) { for (item = SDL_joylist; item; item = next) {
SDL_free(joynames[i]); next = item->next;
SDL_free(joydevnames[i]); FreeJoylistItem(item);
} }
return; SDL_joylist = SDL_joylist_tail = NULL;
}
static SDL_JoystickGUID numjoysticks = 0;
BSD_JoystickGetDeviceGUID(int device_index)
{
#ifdef __OpenBSD__
SDL_JoystickGUID guid;
guid = SDL_JoystickGetGUIDFromString(joyguids[device_index]);
return guid;
#else
/* the GUID is just the name for now */
const char *name = BSD_JoystickGetDeviceName(device_index);
return SDL_CreateJoystickGUIDForName(name);
#endif
} }
static int static int