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