From: Aric Stewart Subject: [PATCH] winejoystick.drv: add HID support Message-Id: Date: Fri, 3 Feb 2017 11:19:33 -0600 Right now, only enabled if no other joystick platform layer is defined or HID_WINMM is explicitly defined Signed-off-by: Aric Stewart --- dlls/winejoystick.drv/Makefile.in | 2 + dlls/winejoystick.drv/joystick_hid.c | 546 +++++++++++++++++++++++++++++++++ dlls/winejoystick.drv/joystick_linux.c | 2 +- dlls/winejoystick.drv/joystick_osx.c | 2 +- 4 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 dlls/winejoystick.drv/joystick_hid.c diff --git a/dlls/winejoystick.drv/Makefile.in b/dlls/winejoystick.drv/Makefile.in index f6d3052767..ab1c7050d1 100644 --- a/dlls/winejoystick.drv/Makefile.in +++ b/dlls/winejoystick.drv/Makefile.in @@ -1,8 +1,10 @@ MODULE = winejoystick.drv IMPORTS = winmm user32 +DELAYIMPORTS = hid setupapi EXTRALIBS = $(IOKIT_LIBS) C_SRCS = \ joystick.c \ + joystick_hid.c \ joystick_linux.c \ joystick_osx.c diff --git a/dlls/winejoystick.drv/joystick_hid.c b/dlls/winejoystick.drv/joystick_hid.c new file mode 100644 index 0000000000..1df757a5ae --- /dev/null +++ b/dlls/winejoystick.drv/joystick_hid.c @@ -0,0 +1,546 @@ +/* + * Joystick functions using HID + * + * Copyright 1997 Andreas Mohr + * Copyright 1998 Marcus Meissner + * Copyright 1998,1999 Lionel Ulmer + * Copyright 2000 Wolfgang Schwotzer + * Copyright 2000-2001 TransGaming Technologies Inc. + * Copyright 2002 David Hagood + * Copyright 2015 Ken Thomases for CodeWeavers Inc. + * Copyright 2016 David Lawrie + * Copyright 2017 Aric Stewart + * + * 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 + * + */ + +#include "config.h" +#include "wine/port.h" + +#include +#include +#include +#include +#include + +#include "joystick.h" + +#include "winbase.h" +#include "winreg.h" +#include "wingdi.h" +#include "winnls.h" +#include "winternl.h" +#include "wine/debug.h" + +#include "wine/unicode.h" + +#include "setupapi.h" +#include "devpkey.h" +#include "hidusage.h" +#include "ddk/hidsdi.h" +#include "initguid.h" +#include "devguid.h" + +#if defined(HID_WINMM) || (!defined(HAVE_IOKIT_HID_IOHIDLIB_H) && !defined(HAVE_LINUX_JOYSTICK_H)) + +WINE_DEFAULT_DEBUG_CHANNEL(joystick); + +#define MAXJOYSTICK (JOYSTICKID2 + 30) + +enum { DRIVER_AXIS_X = 0, + DRIVER_AXIS_Y, + DRIVER_AXIS_Z, + DRIVER_AXIS_U, + DRIVER_AXIS_V, + DRIVER_AXIS_R, + DRIVER_NUM_AXES }; + +#define DRIVER_POV (DRIVER_NUM_AXES + 1) +#define DRIVER_BUTTON_0 (DRIVER_POV+1) + +struct driver_value { + LONG min_value, max_value; + int driver_index; +}; + +typedef struct tagWINE_JSTCK { + int joyIntf; + BOOL in_use; + + ULONG data_length; + struct driver_value *values; + HIDP_DATA *data; + BOOL has_POV; + + BYTE report_id; + + HANDLE device; + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS caps; + CHAR *report; +} WINE_JSTCK; + +static WINE_JSTCK JSTCK_Data[MAXJOYSTICK]; + +static void ReadDevice(WINE_JSTCK *jstick) +{ + jstick->report[0] = jstick->report_id; + HidD_GetInputReport(jstick->device, jstick->report, jstick->caps.InputReportByteLength+1); +} + +/************************************************************************** + * JSTCK_drvGet [internal] + */ +static WINE_JSTCK *JSTCK_drvGet(DWORD_PTR dwDevID) +{ + int p; + + if ((dwDevID - (DWORD_PTR)JSTCK_Data) % sizeof(JSTCK_Data[0]) != 0) + return NULL; + p = (dwDevID - (DWORD_PTR)JSTCK_Data) / sizeof(JSTCK_Data[0]); + if (p < 0 || p >= MAXJOYSTICK || !((WINE_JSTCK*)dwDevID)->in_use) + return NULL; + + return (WINE_JSTCK*)dwDevID; +} + +/************************************************************************** + * driver_open + */ +LRESULT driver_open(LPSTR str, DWORD dwIntf) +{ + HDEVINFO device_info_set; + GUID hid_guid; + SP_DEVICE_INTERFACE_DATA interface_data; + SP_DEVICE_INTERFACE_DETAIL_DATA_W *data; + PHIDP_PREPARSED_DATA ppd; + DWORD detail_size = MAX_PATH * sizeof(WCHAR); + HANDLE device = INVALID_HANDLE_VALUE; + HIDP_CAPS Caps; + DWORD idx,didx; + + if (dwIntf >= MAXJOYSTICK || JSTCK_Data[dwIntf].in_use) + return 0; + + HidD_GetHidGuid(&hid_guid); + + device_info_set = SetupDiGetClassDevsW(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE); + + data = HeapAlloc(GetProcessHeap(), 0 , sizeof(*data) + detail_size); + data->cbSize = sizeof(*data); + + ZeroMemory(&interface_data, sizeof(interface_data)); + interface_data.cbSize = sizeof(interface_data); + + idx = didx = 0; + while (SetupDiEnumDeviceInterfaces(device_info_set, NULL, &hid_guid, idx++, + &interface_data)) + { + if (!SetupDiGetDeviceInterfaceDetailW(device_info_set, + &interface_data, data, sizeof(*data) + detail_size, NULL, NULL)) + continue; + + device = CreateFileW(data->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); + if (device == INVALID_HANDLE_VALUE) + continue; + + HidD_GetPreparsedData(device, &ppd); + HidP_GetCaps(ppd, &Caps); + if (Caps.UsagePage == HID_USAGE_PAGE_GENERIC && + (Caps.Usage == HID_USAGE_GENERIC_GAMEPAD || + Caps.Usage == HID_USAGE_GENERIC_JOYSTICK || + Caps.Usage == 0x8 /* Multi-axis Controller */)) + { + if (didx < dwIntf) + didx++; + else + break; + } + CloseHandle(device); + HidD_FreePreparsedData(ppd); + device = INVALID_HANDLE_VALUE; + } + HeapFree(GetProcessHeap(), 0, data); + SetupDiDestroyDeviceInfoList(device_info_set); + + if (device == INVALID_HANDLE_VALUE) + return 0; + + JSTCK_Data[dwIntf].joyIntf = dwIntf; + JSTCK_Data[dwIntf].in_use = TRUE; + JSTCK_Data[dwIntf].device = device; + JSTCK_Data[dwIntf].ppd = ppd; + JSTCK_Data[dwIntf].caps = Caps; + JSTCK_Data[dwIntf].report = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Caps.InputReportByteLength + 1); + + return (LRESULT)&JSTCK_Data[dwIntf]; +} + +/************************************************************************** + * driver_close + */ +LRESULT driver_close(DWORD_PTR dwDevID) +{ + WINE_JSTCK* jstick = JSTCK_drvGet(dwDevID); + + if (jstick == NULL) + return 0; + jstick->in_use = FALSE; + CloseHandle(jstick->device); + HidD_FreePreparsedData(jstick->ppd); + HeapFree(GetProcessHeap(), 0, jstick->values); + HeapFree(GetProcessHeap(), 0, jstick->data); + HeapFree(GetProcessHeap(), 0, jstick->report); + return 1; +} + +/************************************************************************** + * JoyGetDevCaps [MMSYSTEM.102] + */ +LRESULT driver_joyGetDevCaps(DWORD_PTR dwDevID, LPJOYCAPSW lpCaps, DWORD dwSize) +{ + WINE_JSTCK* jstick; + int i, idx; + + HIDP_BUTTON_CAPS *button_caps; + HIDP_VALUE_CAPS *value_caps; + USHORT button_caps_count; + USHORT value_caps_count; + int axes_count = 0; + int button_count; + int pov_count = 0; + + if ((jstick = JSTCK_drvGet(dwDevID)) == NULL) + return MMSYSERR_NODRIVER; + + jstick->data_length = HidP_MaxDataListLength(HidP_Input, jstick->ppd); + jstick->data = HeapAlloc(GetProcessHeap(), 0, jstick->data_length * sizeof(*jstick->data)); + jstick->values = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, jstick->data_length * sizeof(*jstick->values)); + + for (i = 0; i < jstick->data_length; i++) + jstick->values[i].driver_index = -1; + + button_count = 0; + button_caps_count = jstick->caps.NumberInputButtonCaps; + button_caps = HeapAlloc(GetProcessHeap(), 0, sizeof(*button_caps) * button_caps_count); + HidP_GetButtonCaps(HidP_Input, button_caps, &button_caps_count, jstick->ppd); + for (i = 0; i < button_caps_count; i++) + { + if (button_caps[i].UsagePage != HID_USAGE_PAGE_BUTTON) + continue; + if (button_caps[i].IsRange) + { + int j; + int cnt = button_caps[i].Range.UsageMax - button_caps[i].Range.UsageMin; + button_count += (cnt + 1); + + for (j = 0; j <= cnt; j++) + { + if (button_caps[i].Range.UsageMin + j <= 32) + jstick->values[button_caps[i].Range.DataIndexMin + j].driver_index = DRIVER_BUTTON_0 + button_caps[i].Range.UsageMin + j - 1; + else + { + WARN("Button usage %i exceeds 32 and cannot be reported\n", button_caps[i].Range.UsageMin + j); + button_count--; + } + } + } + else + { + if (button_caps[i].NotRange.Usage <= 32) + { + button_count++; + jstick->values[button_caps[i].NotRange.DataIndex].driver_index = DRIVER_BUTTON_0 + button_caps[i].NotRange.Usage - 1; + } + else + { + WARN("Button usage %i exceeds 32 and cannot be reported\n", button_caps[i].NotRange.Usage); + } + } + } + HeapFree(GetProcessHeap(), 0, button_caps); + + value_caps_count = jstick->caps.NumberInputValueCaps; + value_caps = HeapAlloc(GetProcessHeap(), 0, sizeof(*value_caps) * value_caps_count); + HidP_GetValueCaps(HidP_Input, value_caps, &value_caps_count, jstick->ppd); + jstick->report_id = 0; + + lpCaps->wMid = MM_MICROSOFT; + lpCaps->wPid = MM_PC_JOYSTICK; + lpCaps->wXmin = 0; + lpCaps->wYmin = 0; + lpCaps->wZmin = 0; + lpCaps->wXmax = 0; + lpCaps->wYmax = 0; + lpCaps->wZmax = 0; + lpCaps->wRmin = 0; + lpCaps->wRmax = 0; + lpCaps->wUmin = 0; + lpCaps->wUmax = 0; + lpCaps->wVmin = 0; + lpCaps->wVmax = 0; + lpCaps->wMaxAxes = 6; /* same as MS Joystick Driver */ + lpCaps->wNumAxes = 0; /* nr of axes in use */ + lpCaps->wMaxButtons = 32; /* same as MS Joystick Driver */ + lpCaps->szRegKey[0] = 0; + lpCaps->szOEMVxD[0] = 0; + lpCaps->wCaps = 0; + lpCaps->wNumButtons = button_count; + + for (i = 0; i < value_caps_count; i++) + { + if (value_caps[i].IsRange) + { + FIXME("Not handling axes as ranges\n"); + continue; + } + + if (value_caps[i].UsagePage == HID_USAGE_PAGE_GENERIC) + switch (value_caps[i].NotRange.Usage) + { + case HID_USAGE_GENERIC_HATSWITCH: + { + int data_index = value_caps[i].NotRange.DataIndex; + if (pov_count) + { + FIXME("Only 1 POV supported, additional POVs ignored\n"); + continue; + } + pov_count++; + jstick->has_POV = TRUE; + jstick->values[data_index].driver_index = DRIVER_POV; + lpCaps->wCaps |= JOYCAPS_HASPOV | JOYCAPS_POV4DIR; + continue; + } + case HID_USAGE_GENERIC_X: + idx = DRIVER_AXIS_X; + break; + case HID_USAGE_GENERIC_Y: + idx = DRIVER_AXIS_Y; + break; + case HID_USAGE_GENERIC_Z: + idx = DRIVER_AXIS_Z; + break; + case HID_USAGE_GENERIC_RX: + idx = DRIVER_AXIS_V; + break; + case HID_USAGE_GENERIC_RY: + idx = DRIVER_AXIS_U; + break; + case HID_USAGE_GENERIC_RZ: + idx = DRIVER_AXIS_R; + break; + case HID_USAGE_GENERIC_SLIDER: + case HID_USAGE_GENERIC_DIAL: + case HID_USAGE_GENERIC_WHEEL: + { + if (!lpCaps->wCaps & JOYCAPS_HASZ) idx = DRIVER_AXIS_Z; + else if (!lpCaps->wCaps & JOYCAPS_HASU) idx = DRIVER_AXIS_U; + else if (!lpCaps->wCaps & JOYCAPS_HASV) idx = DRIVER_AXIS_V; + else + { + TRACE("Slider/Dial/Wheel (%d) ignored\n", value_caps[i].NotRange.Usage); + continue; + } + break; + } + default: + idx = -1; + break; + } + else if (value_caps[i].UsagePage == HID_USAGE_PAGE_SIMULATION) + switch (value_caps[i].NotRange.Usage) + { + case 0xC8: /*Steering*/ + idx = DRIVER_AXIS_X; + break; + case 0xC4: /*Accelerator*/ + idx = DRIVER_AXIS_Y; + break; + case HID_USAGE_SIMULATION_THROTTLE: + idx = DRIVER_AXIS_Z; + break; + case HID_USAGE_SIMULATION_RUDDER: + case 0xC5: /*Brake*/ + idx = DRIVER_AXIS_R; + break; + default: + idx = -1; + break; + } + else + { + TRACE("Unhandled Usage Page (%x)\n",value_caps[i].UsagePage); + continue; + } + + if (idx >= 0) + { + int data_index = value_caps[i].NotRange.DataIndex; + lpCaps->wNumAxes++; + jstick->values[data_index].driver_index = idx; + jstick->values[data_index].min_value = value_caps[i].PhysicalMin; + jstick->values[data_index].max_value = value_caps[i].PhysicalMax; + axes_count++; + switch (idx) + { + case DRIVER_AXIS_X: + jstick->report_id = value_caps[i].ReportID; + lpCaps->wXmax = 0xFFFF; + break; + case DRIVER_AXIS_Y: + lpCaps->wYmax = 0xFFFF; + break; + case DRIVER_AXIS_Z: + lpCaps->wZmax = 0xFFFF; + lpCaps->wCaps |= JOYCAPS_HASZ; + break; + case DRIVER_AXIS_V: + lpCaps->wVmax = 0xFFFF; + lpCaps->wCaps |= JOYCAPS_HASV; + break; + case DRIVER_AXIS_U: + lpCaps->wUmax = 0xFFFF; + lpCaps->wCaps |= JOYCAPS_HASU; + break; + case DRIVER_AXIS_R: + lpCaps->wRmax = 0xFFFF; + lpCaps->wCaps |= JOYCAPS_HASR; + break; + } + } + else + { + WARN("Unknown axis %i/%i(%u). Skipped.\n", value_caps[i].UsagePage, value_caps[i].NotRange.Usage, i); + } + } + HeapFree(GetProcessHeap(), 0, value_caps); + + HidD_GetProductString(jstick->device, lpCaps->szPname, sizeof(lpCaps->szPname)); + lpCaps->szPname[MAXPNAMELEN-1] = '\0'; + + TRACE("Name: %s, #Axes: %d, #Buttons: %d #POVs: %d\n", + debugstr_w(lpCaps->szPname), axes_count, button_count, pov_count); + + return JOYERR_NOERROR; +} + +/************************************************************************** + * driver_joyGetPos + */ +LRESULT driver_joyGetPosEx(DWORD_PTR dwDevID, LPJOYINFOEX lpInfo) +{ + static const struct { + DWORD flag; + off_t offset; + } axis_map[DRIVER_NUM_AXES] = { + { JOY_RETURNX, FIELD_OFFSET(JOYINFOEX, dwXpos) }, + { JOY_RETURNY, FIELD_OFFSET(JOYINFOEX, dwYpos) }, + { JOY_RETURNZ, FIELD_OFFSET(JOYINFOEX, dwZpos) }, + { JOY_RETURNV, FIELD_OFFSET(JOYINFOEX, dwVpos) }, + { JOY_RETURNU, FIELD_OFFSET(JOYINFOEX, dwUpos) }, + { JOY_RETURNR, FIELD_OFFSET(JOYINFOEX, dwRpos) }, + }; + + WINE_JSTCK* jstick; + ULONG data_length; + int i; + + if ((jstick = JSTCK_drvGet(dwDevID)) == NULL) + return MMSYSERR_NODRIVER; + + ReadDevice(jstick); + + data_length = jstick->data_length; + HidP_GetData(HidP_Input, jstick->data, &data_length, jstick->ppd, + jstick->report, jstick->caps.InputReportByteLength+1); + + if (lpInfo->dwFlags & JOY_RETURNBUTTONS) + { + lpInfo->dwButtonNumber = 0; + lpInfo->dwButtons = 0x0; + } + + if (lpInfo->dwFlags & JOY_RETURNPOV && !jstick->has_POV) + { + lpInfo->dwPOV = JOY_POVCENTERED; + lpInfo->dwFlags &= ~JOY_RETURNPOV; + } + + for (i = 0; i < data_length; i++) + { + int data_index = jstick->data[i].DataIndex; + int driver_index = jstick->values[data_index].driver_index; + + if (data_index > jstick->data_length) + continue; + + if (driver_index >= 0 && driver_index < DRIVER_NUM_AXES && + lpInfo->dwFlags & axis_map[driver_index].flag) + { + DWORD* field = (DWORD*)((char*)lpInfo + axis_map[driver_index].offset); + int value; + + value = jstick->data[i].RawValue - jstick->values[data_index].min_value; + *field = MulDiv(value, 0xFFFF, jstick->values[data_index].max_value - jstick->values[data_index].min_value); + } + else if (lpInfo->dwFlags & JOY_RETURNBUTTONS && driver_index >= DRIVER_BUTTON_0) + { + int usage = driver_index - DRIVER_BUTTON_0; + lpInfo->dwButtons |= (1 << usage); + lpInfo->dwButtonNumber++; + } + else if (lpInfo->dwFlags & JOY_RETURNPOV && driver_index == DRIVER_POV) { + int value = jstick->data[i].RawValue; + if (value >= 8) + lpInfo->dwPOV = JOY_POVCENTERED; + else + lpInfo->dwPOV = value * 4500; + } + } + + TRACE("x: %d, y: %d, z: %d, r: %d, u: %d, v: %d, buttons: 0x%04x, flags: 0x%04x\n", + lpInfo->dwXpos, lpInfo->dwYpos, lpInfo->dwZpos, + lpInfo->dwRpos, lpInfo->dwUpos, lpInfo->dwVpos, + lpInfo->dwButtons, lpInfo->dwFlags); + + return JOYERR_NOERROR; +} + +/************************************************************************** + * driver_joyGetPos + */ +LRESULT driver_joyGetPos(DWORD_PTR dwDevID, LPJOYINFO lpInfo) +{ + 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(dwDevID, &ji); + if (ret == JOYERR_NOERROR) { + lpInfo->wXpos = ji.dwXpos; + lpInfo->wYpos = ji.dwYpos; + lpInfo->wZpos = ji.dwZpos; + lpInfo->wButtons = ji.dwButtons; + } + + return ret; +} + +#endif diff --git a/dlls/winejoystick.drv/joystick_linux.c b/dlls/winejoystick.drv/joystick_linux.c index 287f4be60d..5f9fec6c38 100644 --- a/dlls/winejoystick.drv/joystick_linux.c +++ b/dlls/winejoystick.drv/joystick_linux.c @@ -38,7 +38,7 @@ #include "config.h" #include "wine/port.h" -#ifdef HAVE_LINUX_JOYSTICK_H +#if defined(HAVE_LINUX_JOYSTICK_H) && !defined(HID_WINMM) #ifdef HAVE_UNISTD_H # include diff --git a/dlls/winejoystick.drv/joystick_osx.c b/dlls/winejoystick.drv/joystick_osx.c index bf82f67116..53766583e9 100644 --- a/dlls/winejoystick.drv/joystick_osx.c +++ b/dlls/winejoystick.drv/joystick_osx.c @@ -28,7 +28,7 @@ #include "config.h" -#if defined(HAVE_IOKIT_HID_IOHIDLIB_H) +#if defined(HAVE_IOKIT_HID_IOHIDLIB_H) && !defined(HID_WINMM) #define DWORD UInt32 #define LPDWORD UInt32*