WIP: FreeBSD 10 usbhid joystick support

Bruno Jesus 00cpxxx at gmail.com
Sun Mar 22 20:45:25 CDT 2015


For those wondering (probably a few, lol) this is the WIP patch that
is actually working for the standard HID gamepad I have. I also tried
testing in a PS2 controller attached in a USB converter but FreeBSD
does not recognize it. The configure part is still missing and it
required "-lusbhid" to compile and I didn't find the correct place to
add that so I'm currently compiling manually.

I labeled it as FreeBSD 10 only because it was the only one I tried,
probably may require changes to support other BSD systems.

This task is proving to be harder than I expected due to my lack of
knowledge in the area and any help testing is appreciated specially
with different gamepads/joysticks (for example a joystick with a HAT).

The problem with configure.ac is that both usbhid.h and
dev/usb/usbhid.h must be included and that would require a compound
"-o" switch to ensure that both must be present in order to make the
driver.

The last problem is where to put that -lusbhid is required when
compiling the file. And of course testing in the configure script if
usbhid lib is present.

Best regards,
Bruno
-------------- next part --------------

---
 dlls/winejoystick.drv/Makefile.in        |   3 +-
 dlls/winejoystick.drv/joystick_freebsd.c | 498 +++++++++++++++++++++++++++++++
 2 files changed, 500 insertions(+), 1 deletion(-)
 create mode 100644 dlls/winejoystick.drv/joystick_freebsd.c

diff --git a/dlls/winejoystick.drv/Makefile.in b/dlls/winejoystick.drv/Makefile.in
index f6d3052..afc6f5f 100644
--- a/dlls/winejoystick.drv/Makefile.in
+++ b/dlls/winejoystick.drv/Makefile.in
@@ -1,8 +1,9 @@
 MODULE    = winejoystick.drv
 IMPORTS   = winmm user32
-EXTRALIBS = $(IOKIT_LIBS)
+EXTRALIBS = $(IOKIT_LIBS) -lusbhid
 
 C_SRCS = \
 	joystick.c \
+	joystick_freebsd.c \
 	joystick_linux.c \
 	joystick_osx.c
diff --git a/dlls/winejoystick.drv/joystick_freebsd.c b/dlls/winejoystick.drv/joystick_freebsd.c
new file mode 100644
index 0000000..e1ce2d4
--- /dev/null
+++ b/dlls/winejoystick.drv/joystick_freebsd.c
@@ -0,0 +1,498 @@
+/*
+ * WinMM joystick driver implementation for FreeBSD
+ *
+ * Copyright 1997 Andreas Mohr
+ * Copyright 2000 Wolfgang Schwotzer
+ * Copyright 2002 David Hagood
+ * Copyright 2015 Bruno Jesus
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * NOTES:
+ *
+ * - supports only USB HID joysticks.
+ */
+
+//#ifdef HAVE_USBHID_H
+
+#include "config.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include <errno.h>
+#include <dev/usb/usbhid.h>
+
+#include "joystick.h"
+
+#include "wine/debug.h"
+#include "wine/unicode.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(joystick);
+
+#define MAX_JOYSTICKS    8
+#define MAX_HID_DEVICE_INDEX 32 /* limit of /dev/uhidX devices to check */
+#define HID_INPUT_BIT        (1 << hid_input)
+#define CENTERED_AXIS_VALUE  32767
+#define HID_DEV_PATH         "/dev/uhid%d"
+
+static struct JOYDATA
+{
+    DWORD driver_id; /* the number of the requested joystick */
+    int hid_id;      /* the number of the device in /dev/uhidX */
+    BOOL in_use;
+    int dev;
+    struct
+    {
+        struct report_desc *desc;
+        char *buffer;
+        int buf_sz;
+        int rid; /* report id */
+    } hid;
+    struct
+    {
+        int buttons, hats, balls, axes;
+        ULONG bits;
+    } caps;
+    struct
+    {
+        UINT buttons;
+        int button_count;
+        int axes[6], pov;
+    } stat;
+} joydata[MAX_JOYSTICKS];
+
+static int init;
+
+/* return the struct for the desired joystick index */
+static struct JOYDATA* joyget(DWORD_PTR id)
+{
+    int i;
+    for (i = 0; i < MAX_JOYSTICKS; i++)
+    {
+        if (id == (DWORD_PTR) &joydata[i] && joydata[i].in_use)
+            return &joydata[i];
+    }
+    return NULL;
+}
+
+/* check if the requested /dev/uhidX is already in use */
+static BOOL is_uhid_used(int hid_id)
+{
+    int i;
+    for (i = 0; i < MAX_JOYSTICKS; i++)
+    {
+        if (joydata[i].in_use && joydata[i].dev >= 0 && joydata[i].hid_id == hid_id)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+/* set defaults for a joystick that was not activated yet */
+static void init_stat_data(struct JOYDATA *js)
+{
+    int i;
+    js->stat.button_count = 0;
+    js->stat.buttons = 0;
+    js->stat.pov = 0;
+    js->stat.axes[0] = CENTERED_AXIS_VALUE;
+    js->stat.axes[1] = CENTERED_AXIS_VALUE;
+    for (i = 2; i < sizeof(js->stat.axes) / sizeof(js->stat.axes[0]); i++)
+        js->stat.axes[i] = 0;
+    if(js->caps.hats)
+        js->stat.pov = JOY_POVCENTERED;
+}
+
+/* set a value to an axis */
+static BOOL set_axis(struct JOYDATA *js, int axis, int value)
+{
+    switch (axis)
+    {
+        case HUG_X:
+            js->stat.axes[0] = value;
+            break;
+        case HUG_Y:
+            js->stat.axes[1] = value;
+            break;
+        case HUG_Z:
+        case HUG_SLIDER:
+            js->stat.axes[2] = value;
+            break;
+        case HUG_RX:
+            js->stat.axes[3] = value;
+            break;
+        case HUG_RY:
+            js->stat.axes[4] = value;
+            break;
+        case HUG_RZ:
+        case HUG_WHEEL:
+            js->stat.axes[5] = value;
+            break;
+        default:
+            return FALSE;
+    }
+    return TRUE;
+}
+
+/* convert the current read value from the old range to a new desired range */
+int adjust_axis_value(int v, int cur_min, int cur_max, int new_min, int new_max)
+{
+    int cur_range, new_range;
+
+    cur_range = cur_max - cur_min + 1;
+    if (!cur_range || v < 0)
+        v = 0;
+    else
+    {
+        new_range = new_max - new_min;
+        printf("OLD %d (%d-%d) - ",v,cur_min,cur_max);
+        v = (((v - cur_min) * new_range) / cur_range) + new_min;
+        printf("NEW %d (%d-%d)\n",v,new_min,new_max);
+    }
+    return v;
+}
+
+/* find out if the opened HID descriptor is really a joystick and get 
+ * the informations required from it */
+static BOOL hid_parse(struct JOYDATA *js)
+{
+    struct hid_item item;
+    struct hid_data *data;
+    BOOL is_js = FALSE;
+
+    js->hid.rid = hid_get_report_id(js->dev);
+    js->hid.buf_sz = hid_report_size(js->hid.desc, hid_input, js->hid.rid);
+    if (js->hid.buf_sz <= 0 || !(js->hid.buffer = HeapAlloc(GetProcessHeap(), 0, js->hid.buf_sz)))
+    {
+        ERR("joystick[%u] failed to alloc %d bytes.\n", js->driver_id, js->hid.buf_sz);
+        return FALSE;
+    }
+
+    data = hid_start_parse(js->hid.desc, HID_INPUT_BIT, js->hid.rid);
+    if (!data)
+    {
+        ERR("joystick[%u] failed to get HID data.\n", js->driver_id);
+        return FALSE;
+    }
+
+    memset(&js->caps, 0, sizeof(js->caps));
+
+    /* loop all items from the report and find the joystick ones */
+    while(hid_get_item(data, &item) > 0)
+    {
+        int page = HID_PAGE(item.usage), usage = HID_USAGE(item.usage);
+		if (item.kind == hid_collection)
+        {
+            /* is this really a joystick/gamepad */
+            if (page == HUP_GENERIC_DESKTOP &&
+                (usage == HUG_JOYSTICK || usage == HUG_GAME_PAD))
+                is_js = TRUE;
+        }
+        else if (item.kind == hid_input)
+        {
+            if (page == HUP_GENERIC_DESKTOP)
+            {
+                if (usage == HUG_HAT_SWITCH)
+                {
+                    js->caps.hats++;
+                    js->caps.bits |= JOYCAPS_HASPOV | JOYCAPS_POV4DIR;
+                }
+                else if (set_axis(js, usage, 0))
+                {
+                    js->caps.axes++;
+                    if (usage == HUG_Z || usage == HUG_SLIDER)
+                        js->caps.bits |= JOYCAPS_HASZ;
+                    if (usage == HUG_RZ || usage == HUG_WHEEL)
+                        js->caps.bits |= JOYCAPS_HASR;
+                    if (usage == HUG_RX)
+                        js->caps.bits |= JOYCAPS_HASU;
+                    if (usage == HUG_RY)
+                        js->caps.bits |= JOYCAPS_HASV;
+                }
+                else
+                    WARN("joystick[%u] unknown HID usage %d, skipped.\n", js->driver_id, usage);
+            }
+            else if (page == HUP_BUTTON)
+                js->caps.buttons++;
+        }
+    }
+    hid_end_parse(data);
+
+    if (is_js)
+        TRACE("joystick[%u] buttons: %d, axes %d, hats %d\n", js->driver_id,
+              js->caps.buttons, js->caps.axes, js->caps.hats);
+    return is_js;
+}
+
+/* update the cache with current joystick state */
+static BOOL update_joystick(struct JOYDATA *js)
+{
+    struct hid_item item;
+    struct hid_data *data;
+
+    /* read ALL pending messages in the queue */
+    while (read(js->dev, js->hid.buffer, js->hid.buf_sz) == js->hid.buf_sz)
+    {
+        data = hid_start_parse(js->hid.desc, HID_INPUT_BIT, js->hid.rid);
+        if (!data)
+        {
+            ERR("joystick[%u] failed to get HID data.\n", js->driver_id);
+            return FALSE;
+        }
+
+        /* find what changed and update our internal structure */
+        while(hid_get_item(data, &item) > 0)
+        {
+            int page = HID_PAGE(item.usage), usage = HID_USAGE(item.usage), v;
+            if (item.kind != hid_input) continue;
+
+            if (page == HUP_GENERIC_DESKTOP)
+            {
+                if (usage == HUG_HAT_SWITCH)
+                {
+                    v = hid_get_data(js->hid.buffer, &item);
+                    if (v >= 8)
+                        js->stat.pov = JOY_POVCENTERED;
+                    else
+                        js->stat.pov = v * 4500;
+                }
+                else
+                {
+                    v = hid_get_data(js->hid.buffer, &item);
+                    v = adjust_axis_value(v, item.logical_minimum, item.logical_maximum, 0, 65534);
+                    set_axis(js, usage, v);
+                }
+            }
+            else if (page == HUP_BUTTON)
+            {
+                /* enable or disable button pressed */
+                v = hid_get_data(js->hid.buffer, &item);
+                if (v)
+                {
+                    js->stat.buttons |= 1 << (usage - 1);
+                    js->stat.button_count++;
+                }
+                else
+                {
+                    js->stat.buttons &= ~(1 << (usage - 1));
+                    js->stat.button_count--;
+                }
+            }
+        }
+        hid_end_parse(data);
+    }
+    return errno == EAGAIN;
+}
+
+/* close descriptor and free related structures */
+static BOOL close_joystick(struct JOYDATA *js)
+{
+    int dev = js->dev;
+
+    TRACE("joystick[%u] close descriptor %d\n", js->driver_id, js->dev);
+    if (js->dev >= 0)
+    {
+        close(js->dev);
+        js->dev = 0;
+        js->hid_id = -1;
+        HeapFree(GetProcessHeap(), 0, js->hid.buffer);
+        js->hid.buffer = NULL;
+        hid_dispose_report_desc(js->hid.desc);
+    }
+    return dev >= 0;
+}
+
+/* get the handle for an opened joystick or open and parse its data now */
+static BOOL open_joystick(struct JOYDATA *js)
+{
+    if (!init)
+    {
+        hid_init(NULL);
+        init++;
+        TRACE("hid_init() called\n");
+    }
+
+    if (js->dev == -1)
+    {
+        char buf[32];
+        BOOL ok = FALSE;
+        int hid_id;
+
+        /* loop the HID devices and find the next joystick */
+        for (hid_id = 0; hid_id < MAX_HID_DEVICE_INDEX; hid_id++)
+        {
+            if (is_uhid_used(hid_id)) continue;
+
+            sprintf(buf, HID_DEV_PATH, hid_id);
+            js->dev = open(buf, O_RDONLY | O_NONBLOCK);
+            if (js->dev != -1)
+            {
+                TRACE("joystick[%u] open %s returned descriptor %d\n", js->driver_id, buf, js->dev);
+                js->hid.desc = hid_get_report_desc(js->dev);
+                if (js->hid.desc)
+                    ok = hid_parse(js);
+                else
+                    ERR("joystick[%u] failed to get HID report\n", js->driver_id);
+
+                if (ok)
+                {
+                    js->hid_id = hid_id;
+                    /* set defaults for a not activated joystick, this is required
+                     * at least in the joysticks I tested because the first attempt
+                     * to update the joystick state may not return any data as the
+                     * user may not have pressed any button yet. */
+                    init_stat_data(js);
+                    update_joystick(js);
+                    break;
+                }
+                close_joystick(js);
+            }
+        }
+    }
+    return js->dev != -1;
+}
+
+/**************************************************************************
+ * 				driver_open
+ */
+LRESULT driver_open(LPSTR str, DWORD id)
+{
+    struct JOYDATA *js;
+    if(id >= MAX_JOYSTICKS || joyget(id)) return 0;
+    js = &joydata[id];
+
+    memset(js, 0, sizeof(*js));
+    js->driver_id = id;
+    js->hid_id = -1;
+    js->dev = -1;
+    js->in_use = TRUE;
+    return (LRESULT) js;
+}
+
+/**************************************************************************
+ * 				driver_close
+ */
+LRESULT driver_close(DWORD_PTR id)
+{
+    struct JOYDATA* js = joyget(id);
+    if (!js) return 0;
+
+    close_joystick(js);
+    js->in_use = FALSE;
+    return 1;
+}
+
+/**************************************************************************
+ * 				JoyGetDevCaps		[MMSYSTEM.102]
+ */
+LRESULT driver_joyGetDevCaps(DWORD_PTR id, LPJOYCAPSW caps, DWORD size)
+{
+    static const WCHAR ini[] = {'W','i','n','e',' ','J','o','y','s','t','i','c','k',' ','D','r','i','v','e','r',0};
+    struct JOYDATA *js = joyget(id);
+
+    if (!js) return MMSYSERR_NODRIVER;
+    if (!open_joystick(js)) return JOYERR_PARMS;
+
+    caps->wMid = MM_MICROSOFT;
+    caps->wPid = MM_PC_JOYSTICK;
+    strcpyW(caps->szPname, ini); /* joystick product name */
+    caps->wXmin = 0;
+    caps->wXmax = 0xFFFF;
+    caps->wYmin = 0;
+    caps->wYmax = 0xFFFF;
+    caps->wZmin = 0;
+    caps->wZmax = (js->caps.axes >= 3) ? 0xFFFF : 0;
+    caps->wNumButtons = js->caps.buttons;
+
+    if (size == sizeof(JOYCAPSW))
+    {
+        /* complete 95 structure */
+        caps->wRmin = 0;
+        caps->wRmax = 0xFFFF;
+        caps->wUmin = 0;
+        caps->wUmax = 0xFFFF;
+        caps->wVmin = 0;
+        caps->wVmax = 0xFFFF;
+        caps->wMaxAxes = 6; /* same as MS Joystick Driver */
+        caps->wNumAxes = js->caps.axes;
+        caps->wMaxButtons = 32; /* same as MS Joystick Driver */
+        caps->szRegKey[0] = 0;
+        caps->szOEMVxD[0] = 0;
+        caps->wCaps = js->caps.bits;
+    }
+
+    return JOYERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				driver_joyGetPos
+ */
+LRESULT driver_joyGetPosEx(DWORD_PTR id, LPJOYINFOEX info)
+{
+    struct JOYDATA *js = joyget(id);
+
+    if (!js) return MMSYSERR_NODRIVER;
+    if (!open_joystick(js)) return JOYERR_PARMS;
+    if (!update_joystick(js)) return JOYERR_UNPLUGGED;
+
+    /* Now, copy the cached values into Window's structure... */
+    if (info->dwFlags & JOY_RETURNBUTTONS)
+    {
+        info->dwButtons = js->stat.buttons;
+        info->dwButtonNumber = js->stat.button_count;
+    }
+    if (info->dwFlags & JOY_RETURNX) info->dwXpos = js->stat.axes[0];
+    if (info->dwFlags & JOY_RETURNY) info->dwYpos = js->stat.axes[1];
+    if (info->dwFlags & JOY_RETURNZ) info->dwZpos = js->stat.axes[2];
+    if (info->dwFlags & JOY_RETURNR) info->dwRpos = js->stat.axes[3];
+    if (info->dwFlags & JOY_RETURNU) info->dwUpos = js->stat.axes[4];
+    if (info->dwFlags & JOY_RETURNV) info->dwVpos = js->stat.axes[5];
+    if (info->dwFlags & JOY_RETURNPOV) info->dwPOV = js->stat.pov;
+
+    TRACE("x: %d, y: %d, z: %d, r: %d, u: %d, v: %d, buttons: 0x%04x, flags: 0x%04x (fd %d)\n",
+	  info->dwXpos, info->dwYpos, info->dwZpos,
+	  info->dwRpos, info->dwUpos, info->dwVpos,
+	  info->dwButtons, info->dwFlags, js->dev
+         );
+
+    return JOYERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				driver_joyGetPos
+ */
+LRESULT driver_joyGetPos(DWORD_PTR id, LPJOYINFO info)
+{
+    JOYINFOEX ji;
+    LONG ret;
+
+    memset(&ji, 0, sizeof(ji));
+    ji.dwSize = sizeof(ji);
+    ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNBUTTONS;
+
+    ret = driver_joyGetPosEx(id, &ji);
+    if (ret == JOYERR_NOERROR)
+    {
+        info->wXpos    = ji.dwXpos;
+        info->wYpos    = ji.dwYpos;
+        info->wZpos    = ji.dwZpos;
+        info->wButtons = ji.dwButtons;
+    }
+
+    return ret;
+}
+
+//#endif /* HAVE_USBHID_H */
-- 
2.3.3



More information about the wine-devel mailing list