/* * 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 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" WINE_DEFAULT_DEBUG_CHANNEL(joystick); #define MAXJOYSTICK (JOYSTICKID2 + 30) enum { DRIVER_AXIS_X = 0, DRIVER_AXIS_Y, DRIVER_AXIS_Z, DRIVER_AXIS_RX, DRIVER_AXIS_RY, DRIVER_AXIS_RZ, NUM_AXES}; struct axis { int min_value, max_value; int value_index; }; typedef struct tagWINE_JSTCK { int joyIntf; BOOL in_use; DWORD nrOfAxes; DWORD nrOfButtons; DWORD nrOfPOVs; struct axis axes[NUM_AXES]; HANDLE HIDDevice; PHIDP_PREPARSED_DATA HIDData; HIDP_CAPS HIDCaps; BYTE ReportID; PBYTE HIDReport; OVERLAPPED overlap; HANDLE HIDReportEvent; } WINE_JSTCK; static WINE_JSTCK JSTCK_Data[MAXJOYSTICK]; static VOID AsyncReadDevice(WINE_JSTCK *jstick) { BOOLEAN status; DWORD read; memset(&jstick->overlap, 0, sizeof(OVERLAPPED)); jstick->overlap.hEvent = jstick->HIDReportEvent; status = ReadFile(jstick->HIDDevice, jstick->HIDReport, jstick->HIDCaps.InputReportByteLength, &read, &jstick->overlap); if (status) SetEvent(jstick->HIDReportEvent); } /************************************************************************** * 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 DeviceInfoSet; GUID hidGuid; SP_DEVINFO_DATA DeviceInfoData; SP_DEVICE_INTERFACE_DATA DeviceInterfaceInfoData; PSP_DEVICE_INTERFACE_DETAIL_DATA_W Data; PHIDP_PREPARSED_DATA ppd; HANDLE device = INVALID_HANDLE_VALUE; HIDP_CAPS Caps; DWORD idx,didx; if (dwIntf >= MAXJOYSTICK || JSTCK_Data[dwIntf].in_use) return 0; HidD_GetHidGuid(&hidGuid); DeviceInfoSet = SetupDiGetClassDevsW(&hidGuid, NULL, NULL, DIGCF_DEVICEINTERFACE); ZeroMemory(&DeviceInfoData, sizeof(DeviceInfoData)); DeviceInfoData.cbSize = sizeof(DeviceInfoData); idx = didx = 0; Data = HeapAlloc(GetProcessHeap(), 0 , sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + 400); Data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); ZeroMemory(&DeviceInterfaceInfoData, sizeof(DeviceInterfaceInfoData)); DeviceInterfaceInfoData.cbSize = sizeof(DeviceInterfaceInfoData); while (SetupDiEnumDeviceInterfaces(DeviceInfoSet, NULL, &hidGuid, idx++, &DeviceInterfaceInfoData)) { if (!SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet, &DeviceInterfaceInfoData, Data, 400, 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)) { if (didx < dwIntf) didx++; else break; } CloseHandle(device); device = INVALID_HANDLE_VALUE; HidD_FreePreparsedData(ppd); } HeapFree(GetProcessHeap(), 0, Data); SetupDiDestroyDeviceInfoList(DeviceInfoSet); if (device == INVALID_HANDLE_VALUE) return 0; JSTCK_Data[dwIntf].joyIntf = dwIntf; JSTCK_Data[dwIntf].in_use = TRUE; JSTCK_Data[dwIntf].HIDDevice = device; JSTCK_Data[dwIntf].HIDData = ppd; JSTCK_Data[dwIntf].HIDCaps = Caps; JSTCK_Data[dwIntf].HIDReport = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Caps.InputReportByteLength); JSTCK_Data[dwIntf].HIDReportEvent = CreateEventW(NULL, FALSE, FALSE, NULL); /* Start a read */ AsyncReadDevice(&JSTCK_Data[dwIntf]); 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->HIDDevice); HidD_FreePreparsedData(jstick->HIDData); HeapFree(GetProcessHeap(), 0, jstick->HIDReport); CloseHandle(jstick->HIDReportEvent); return 1; } /************************************************************************** * JoyGetDevCaps [MMSYSTEM.102] */ LRESULT driver_joyGetDevCaps(DWORD_PTR dwDevID, LPJOYCAPSW lpCaps, DWORD dwSize) { WINE_JSTCK* jstick; int i; PHIDP_BUTTON_CAPS pButtonCaps; PHIDP_VALUE_CAPS pValueCaps; USHORT capsLength; if ((jstick = JSTCK_drvGet(dwDevID)) == NULL) return MMSYSERR_NODRIVER; jstick->nrOfButtons = 0; pButtonCaps = (PHIDP_BUTTON_CAPS)HeapAlloc(GetProcessHeap(), 0, sizeof(HIDP_BUTTON_CAPS) * jstick->HIDCaps.NumberInputButtonCaps); capsLength = jstick->HIDCaps.NumberInputButtonCaps; HidP_GetButtonCaps(HidP_Input, pButtonCaps, &capsLength, jstick->HIDData); for (i = 0; i < capsLength; i++) { if (pButtonCaps[i].UsagePage != HID_USAGE_PAGE_BUTTON) continue; if (pButtonCaps[i].IsRange) jstick->nrOfButtons += (pButtonCaps[i].Range.UsageMax - pButtonCaps[i].Range.UsageMin + 1); else jstick->nrOfButtons++; jstick->ReportID = pButtonCaps[i].ReportID; } HeapFree(GetProcessHeap(), 0, pButtonCaps); pValueCaps = (PHIDP_VALUE_CAPS)HeapAlloc(GetProcessHeap(), 0, sizeof(HIDP_VALUE_CAPS) * jstick->HIDCaps.NumberInputValueCaps); capsLength = jstick->HIDCaps.NumberInputValueCaps; HidP_GetValueCaps(HidP_Input, pValueCaps, &capsLength, jstick->HIDData); jstick->nrOfAxes = 0; jstick->nrOfPOVs = 0; for (i = 0; i < capsLength; i++) { if (pValueCaps[i].UsagePage != HID_USAGE_PAGE_GENERIC) continue; if (pValueCaps[i].NotRange.Usage == HID_USAGE_GENERIC_HATSWITCH) jstick->nrOfPOVs++; else jstick->nrOfAxes++; } HidD_GetProductString(jstick->HIDDevice, lpCaps->szPname, sizeof(lpCaps->szPname)); TRACE("Name: %s, #Axes: %d, #Buttons: %d #POVs: %d\n", debugstr_w(lpCaps->szPname), jstick->nrOfAxes, jstick->nrOfButtons, jstick->nrOfPOVs); lpCaps->wMid = MM_MICROSOFT; lpCaps->wPid = MM_PC_JOYSTICK; lpCaps->szPname[MAXPNAMELEN-1] = '\0'; lpCaps->wXmin = 0; lpCaps->wXmax = 0xFFFF; lpCaps->wYmin = 0; lpCaps->wYmax = 0xFFFF; lpCaps->wZmin = 0; lpCaps->wZmax = (jstick->nrOfAxes >= 3) ? 0xFFFF : 0; lpCaps->wNumButtons = jstick->nrOfButtons; if (dwSize == sizeof(JOYCAPSW)) { /* complete 95 structure */ lpCaps->wRmin = 0; lpCaps->wRmax = 0xFFFF; lpCaps->wUmin = 0; lpCaps->wUmax = 0xFFFF; lpCaps->wVmin = 0; lpCaps->wVmax = 0xFFFF; 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; /* blank out the axes */ for (i = 0; i < NUM_AXES; i++) jstick->axes[i].value_index = -1; for (i = 0; i < capsLength; i++) { if (pValueCaps[i].UsagePage != HID_USAGE_PAGE_GENERIC) continue; if (pValueCaps[i].NotRange.Usage >= HID_USAGE_GENERIC_X && pValueCaps[i].NotRange.Usage <= HID_USAGE_GENERIC_RZ) { int idx; idx = pValueCaps[i].NotRange.Usage - HID_USAGE_GENERIC_X; jstick->axes[idx].value_index = i; jstick->axes[idx].min_value = pValueCaps[i].LogicalMin; jstick->axes[idx].max_value = pValueCaps[i].LogicalMax; } else continue; switch (pValueCaps[i].NotRange.Usage) { case HID_USAGE_GENERIC_X: case HID_USAGE_GENERIC_Y: lpCaps->wNumAxes++; break; case HID_USAGE_GENERIC_Z: lpCaps->wNumAxes++; lpCaps->wCaps |= JOYCAPS_HASZ; break; case HID_USAGE_GENERIC_RZ: lpCaps->wNumAxes++; lpCaps->wCaps |= JOYCAPS_HASR; break; case HID_USAGE_GENERIC_RX: lpCaps->wNumAxes++; lpCaps->wCaps |= JOYCAPS_HASU; break; case HID_USAGE_GENERIC_RY: lpCaps->wNumAxes++; lpCaps->wCaps |= JOYCAPS_HASV; break; default: WARN("Unknown axis %i(%u). Skipped.\n", pValueCaps[i].NotRange.Usage, i); } } if (jstick->nrOfPOVs > 0) lpCaps->wCaps |= JOYCAPS_HASPOV | JOYCAPS_POV4DIR; } HeapFree(GetProcessHeap(), 0, pValueCaps); return JOYERR_NOERROR; } /************************************************************************** * driver_joyGetPos */ LRESULT driver_joyGetPosEx(DWORD_PTR dwDevID, LPJOYINFOEX lpInfo) { static const struct { DWORD flag; off_t offset; USHORT usage; } axis_map[NUM_AXES] = { { JOY_RETURNX, FIELD_OFFSET(JOYINFOEX, dwXpos), HID_USAGE_GENERIC_X}, { JOY_RETURNY, FIELD_OFFSET(JOYINFOEX, dwYpos), HID_USAGE_GENERIC_Y}, { JOY_RETURNZ, FIELD_OFFSET(JOYINFOEX, dwZpos), HID_USAGE_GENERIC_Z}, { JOY_RETURNU, FIELD_OFFSET(JOYINFOEX, dwUpos), HID_USAGE_GENERIC_RX}, { JOY_RETURNV, FIELD_OFFSET(JOYINFOEX, dwVpos), HID_USAGE_GENERIC_RY}, { JOY_RETURNR, FIELD_OFFSET(JOYINFOEX, dwRpos), HID_USAGE_GENERIC_RZ}, }; WINE_JSTCK* jstick; int i; DWORD rc; if ((jstick = JSTCK_drvGet(dwDevID)) == NULL) return MMSYSERR_NODRIVER; /* Queue another read */ rc = WaitForSingleObject(jstick->HIDReportEvent, 1); if (rc == WAIT_OBJECT_0) AsyncReadDevice(jstick); if (lpInfo->dwFlags & JOY_RETURNBUTTONS) { USAGE usage[32]; ULONG usageLength; lpInfo->dwButtonNumber = 0; lpInfo->dwButtons = 0x0; usageLength = min(jstick->nrOfButtons, 32); HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usage, &usageLength, jstick->HIDData, (PCHAR)jstick->HIDReport, jstick->HIDCaps.InputReportByteLength); for (i = 0; i < usageLength; i++) { lpInfo->dwButtons |= (1 << usage[i]); lpInfo->dwButtonNumber++; } } for (i = 0; i < NUM_AXES; i++) { if (lpInfo->dwFlags & axis_map[i].flag) { DWORD* field = (DWORD*)((char*)lpInfo + axis_map[i].offset); ULONG dev_value; int value; HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, axis_map[i].usage, &dev_value, jstick->HIDData, (PCHAR)jstick->HIDReport, jstick->HIDCaps.InputReportByteLength); value = dev_value - jstick->axes[i].min_value; *field = MulDiv(value, 0xFFFF, jstick->axes[i].max_value - jstick->axes[i].min_value); } } if (lpInfo->dwFlags & JOY_RETURNPOV && jstick->nrOfPOVs > 0) { ULONG dev_value; HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_HATSWITCH, &dev_value, jstick->HIDData, (PCHAR)jstick->HIDReport, jstick->HIDCaps.InputReportByteLength); if (dev_value >= 8) lpInfo->dwPOV = JOY_POVCENTERED; else lpInfo->dwPOV = dev_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; }