| /* | |
| * joystick functions | |
| * | |
| * Copyright 1997 Andreas Mohr | |
| * Copyright 2000 Wolfgang Schwotzer | |
| * Copyright 2002 David Hagood | |
| * | |
| * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| * NOTES: | |
| * | |
| * - nearly all joystick functions can be regarded as obsolete, | |
| * as Linux (2.1.x) now supports extended joysticks with a completely | |
| * new joystick driver interface | |
| * New driver's docu says: | |
| * "For backward compatibility the old interface is still included, | |
| * but will be dropped in the future." | |
| * Thus we should implement the new interface and at most keep the old | |
| * routines for backward compatibility. | |
| * - better support of enhanced joysticks (Linux 2.2 interface is available) | |
| * - support more joystick drivers (like the XInput extension) | |
| * - should load joystick DLL as any other driver (instead of hardcoding) | |
| * the driver's name, and load it as any low lever driver. | |
| */ | |
| #include "config.h" | |
| #ifdef HAVE_UNISTD_H | |
| # include <unistd.h> | |
| #endif | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #ifdef HAVE_SYS_IOCTL_H | |
| #include <sys/ioctl.h> | |
| #endif | |
| #ifdef HAVE_LINUX_IOCTL_H | |
| #include <linux/ioctl.h> | |
| #endif | |
| #ifdef HAVE_LINUX_JOYSTICK_H | |
| #include <linux/joystick.h> | |
| #define JOYDEV "/dev/js%d" | |
| #endif | |
| #ifdef HAVE_SYS_ERRNO_H | |
| #include <sys/errno.h> | |
| #endif | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "wingdi.h" | |
| #include "winuser.h" | |
| #include "winnls.h" | |
| #include "mmddk.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(joystick); | |
| #ifdef HAVE_LINUX_JOYSTICK_H | |
| #define MAXJOYSTICK (JOYSTICKID2 + 1) | |
| typedef struct tagWINE_JSTCK { | |
| int joyIntf; | |
| int in_use; | |
| /* Some extra info we need to make this acutaly work under the | |
| Linux 2.2 event api. | |
| First of all, we cannot keep closing and reopening the device file - | |
| that blows away the state of the stick device, and we lose events. So, we | |
| need to open the low-level device once, and close it when we are done. | |
| Secondly, the event API only gives us what's changed. However, Windows apps | |
| want the whole state every time, so we have to cache the data. | |
| */ | |
| int dev; /* Linux level device file descriptor */ | |
| int x; | |
| int y; | |
| int z; | |
| int r; | |
| int u; | |
| int v; | |
| int buttons; | |
| } WINE_JSTCK; | |
| static WINE_JSTCK JSTCK_Data[MAXJOYSTICK]; | |
| /************************************************************************** | |
| * JSTCK_drvGet [internal] | |
| */ | |
| static WINE_JSTCK* JSTCK_drvGet(DWORD dwDevID) | |
| { | |
| int p; | |
| if ((dwDevID - (DWORD)JSTCK_Data) % sizeof(JSTCK_Data[0]) != 0) | |
| return NULL; | |
| p = (dwDevID - (DWORD)JSTCK_Data) / sizeof(JSTCK_Data[0]); | |
| if (p < 0 || p >= MAXJOYSTICK || !((WINE_JSTCK*)dwDevID)->in_use) | |
| return NULL; | |
| return (WINE_JSTCK*)dwDevID; | |
| } | |
| /************************************************************************** | |
| * JSTCK_drvOpen [internal] | |
| */ | |
| static DWORD JSTCK_drvOpen(LPSTR str, DWORD dwIntf) | |
| { | |
| if (dwIntf >= MAXJOYSTICK || JSTCK_Data[dwIntf].in_use) | |
| return 0; | |
| JSTCK_Data[dwIntf].joyIntf = dwIntf; | |
| JSTCK_Data[dwIntf].in_use = 1; | |
| return (DWORD)&JSTCK_Data[dwIntf]; | |
| } | |
| /************************************************************************** | |
| * JSTCK_drvClose [internal] | |
| */ | |
| static DWORD JSTCK_drvClose(DWORD dwDevID) | |
| { | |
| WINE_JSTCK* jstck = JSTCK_drvGet(dwDevID); | |
| if (jstck == NULL) | |
| return 0; | |
| jstck->in_use = 0; | |
| if (jstck->dev > 0) | |
| { | |
| close(jstck->dev); | |
| jstck->dev = 0; | |
| } | |
| return 1; | |
| } | |
| struct js_status | |
| { | |
| int buttons; | |
| int x; | |
| int y; | |
| }; | |
| /************************************************************************** | |
| * JSTCK_OpenDevice [internal] | |
| */ | |
| static int JSTCK_OpenDevice(WINE_JSTCK* jstick) | |
| { | |
| char buf[20]; | |
| int flags; | |
| if (jstick->dev > 0) | |
| return jstick->dev; | |
| sprintf(buf, JOYDEV, jstick->joyIntf); | |
| #ifdef HAVE_LINUX_22_JOYSTICK_API | |
| flags = O_RDONLY | O_NONBLOCK; | |
| #else | |
| flags = O_RDONLY; | |
| #endif | |
| return (jstick->dev = open(buf, flags)); | |
| } | |
| /************************************************************************** | |
| * JoyGetDevCaps [MMSYSTEM.102] | |
| */ | |
| static LONG JSTCK_GetDevCaps(DWORD dwDevID, LPJOYCAPSW lpCaps, DWORD dwSize) | |
| { | |
| WINE_JSTCK* jstck; | |
| #ifdef HAVE_LINUX_22_JOYSTICK_API | |
| int dev; | |
| char nrOfAxes; | |
| char nrOfButtons; | |
| char identString[MAXPNAMELEN]; | |
| int driverVersion; | |
| #else | |
| static const WCHAR ini[] = {'W','i','n','e',' ','J','o','y','s','t','i','c','k',' ','D','r','i','v','e','r',0}; | |
| #endif | |
| if ((jstck = JSTCK_drvGet(dwDevID)) == NULL) | |
| return MMSYSERR_NODRIVER; | |
| #ifdef HAVE_LINUX_22_JOYSTICK_API | |
| if ((dev = JSTCK_OpenDevice(jstck)) < 0) return JOYERR_PARMS; | |
| ioctl(dev, JSIOCGAXES, &nrOfAxes); | |
| ioctl(dev, JSIOCGBUTTONS, &nrOfButtons); | |
| ioctl(dev, JSIOCGVERSION, &driverVersion); | |
| ioctl(dev, JSIOCGNAME(sizeof(identString)), &identString); | |
| TRACE("Driver: 0x%06x, Name: %s, #Axes: %d, #Buttons: %d\n", | |
| driverVersion, identString, nrOfAxes, nrOfButtons); | |
| lpCaps->wMid = MM_MICROSOFT; | |
| lpCaps->wPid = MM_PC_JOYSTICK; | |
| MultiByteToWideChar(CP_ACP, 0, identString, -1, lpCaps->szPname, MAXPNAMELEN); | |
| lpCaps->szPname[MAXPNAMELEN-1] = '\0'; | |
| lpCaps->wXmin = 0; | |
| lpCaps->wXmax = 0xFFFF; | |
| lpCaps->wYmin = 0; | |
| lpCaps->wYmax = 0xFFFF; | |
| lpCaps->wZmin = 0; | |
| lpCaps->wZmax = (nrOfAxes >= 3) ? 0xFFFF : 0; | |
| #ifdef BODGE_THE_HAT | |
| /* HalfLife won't allow you to map an axis event to things like | |
| "next weapon" and "use". Linux reports the hat on my stick as | |
| axis U and V. So, IFF BODGE_THE_HAT is defined, lie through our | |
| teeth and say we have 32 buttons, and we will map the axises to | |
| the high buttons. Really, perhaps this should be a registry entry, | |
| or even a parameter to the Linux joystick driver (which would completely | |
| remove the need for this.) | |
| */ | |
| lpCaps->wNumButtons = 32; | |
| #else | |
| lpCaps->wNumButtons = nrOfButtons; | |
| #endif | |
| if (dwSize == sizeof(JOYCAPSW)) { | |
| /* since we suppose ntOfAxes <= 6 in the following code, do it explicitly */ | |
| if (nrOfAxes > 6) nrOfAxes = 6; | |
| /* complete 95 structure */ | |
| lpCaps->wRmin = 0; | |
| lpCaps->wRmax = nrOfAxes >= 4 ? 0xFFFF : 0; | |
| lpCaps->wUmin = 0; | |
| lpCaps->wUmax = nrOfAxes >= 5 ? 0xFFFF : 0; | |
| lpCaps->wVmin = 0; | |
| lpCaps->wVmax = nrOfAxes >= 6 ? 0xFFFF : 0; | |
| lpCaps->wMaxAxes = 6; /* same as MS Joystick Driver */ | |
| lpCaps->wNumAxes = nrOfAxes; /* nr of axes in use */ | |
| lpCaps->wMaxButtons = 32; /* same as MS Joystick Driver */ | |
| lpCaps->szRegKey[0] = 0; | |
| lpCaps->szOEMVxD[0] = 0; | |
| lpCaps->wCaps = 0; | |
| switch(nrOfAxes) { | |
| case 6: lpCaps->wCaps |= JOYCAPS_HASV; | |
| case 5: lpCaps->wCaps |= JOYCAPS_HASU; | |
| case 4: lpCaps->wCaps |= JOYCAPS_HASR; | |
| case 3: lpCaps->wCaps |= JOYCAPS_HASZ; | |
| /* FIXME: don't know how to detect for | |
| JOYCAPS_HASPOV, JOYCAPS_POV4DIR, JOYCAPS_POVCTS */ | |
| } | |
| } | |
| #else | |
| lpCaps->wMid = MM_MICROSOFT; | |
| lpCaps->wPid = MM_PC_JOYSTICK; | |
| strcpyW(lpCaps->szPname, ini); /* joystick product name */ | |
| lpCaps->wXmin = 0; | |
| lpCaps->wXmax = 0xFFFF; | |
| lpCaps->wYmin = 0; | |
| lpCaps->wYmax = 0xFFFF; | |
| lpCaps->wZmin = 0; | |
| lpCaps->wZmax = 0; | |
| lpCaps->wNumButtons = 2; | |
| if (dwSize == sizeof(JOYCAPSW)) { | |
| /* complete 95 structure */ | |
| lpCaps->wRmin = 0; | |
| lpCaps->wRmax = 0; | |
| lpCaps->wUmin = 0; | |
| lpCaps->wUmax = 0; | |
| lpCaps->wVmin = 0; | |
| lpCaps->wVmax = 0; | |
| lpCaps->wCaps = 0; | |
| lpCaps->wMaxAxes = 2; | |
| lpCaps->wNumAxes = 2; | |
| lpCaps->wMaxButtons = 4; | |
| lpCaps->szRegKey[0] = 0; | |
| lpCaps->szOEMVxD[0] = 0; | |
| } | |
| #endif | |
| return JOYERR_NOERROR; | |
| } | |
| /************************************************************************** | |
| * JSTCK_GetPos [internal] | |
| */ | |
| static LONG JSTCK_GetPosEx(DWORD dwDevID, LPJOYINFOEX lpInfo) | |
| { | |
| WINE_JSTCK* jstck; | |
| int dev; | |
| #ifdef HAVE_LINUX_22_JOYSTICK_API | |
| struct js_event ev; | |
| #else | |
| struct js_status js; | |
| int dev_stat; | |
| #endif | |
| if ((jstck = JSTCK_drvGet(dwDevID)) == NULL) | |
| return MMSYSERR_NODRIVER; | |
| if ((dev = JSTCK_OpenDevice(jstck)) < 0) return JOYERR_PARMS; | |
| #ifdef HAVE_LINUX_22_JOYSTICK_API | |
| while ((read(dev, &ev, sizeof(struct js_event))) > 0) { | |
| if (ev.type == (JS_EVENT_AXIS)) { | |
| switch (ev.number) { | |
| case 0: | |
| jstck->x = ev.value; | |
| break; | |
| case 1: | |
| jstck->y = ev.value; | |
| break; | |
| case 2: | |
| jstck->z = ev.value; | |
| break; | |
| case 3: | |
| jstck->r = ev.value; | |
| break; | |
| case 4: | |
| jstck->u = ev.value; | |
| break; | |
| case 5: | |
| jstck->v = ev.value; | |
| break; | |
| default: | |
| FIXME("Unknown joystick event '%d'\n", ev.number); | |
| } | |
| } else if (ev.type == (JS_EVENT_BUTTON)) { | |
| if (ev.value) { | |
| jstck->buttons |= (1 << ev.number); | |
| /* FIXME: what to do for this field when | |
| * multiple buttons are depressed ? | |
| */ | |
| if (lpInfo->dwFlags & JOY_RETURNBUTTONS) | |
| lpInfo->dwButtonNumber = ev.number + 1; | |
| } | |
| else | |
| jstck->buttons &= ~(1 << ev.number); | |
| } | |
| } | |
| /* EAGAIN is returned when the queue is empty */ | |
| if (errno != EAGAIN) { | |
| /* FIXME: error should not be ignored */ | |
| ERR("Error while reading joystick state (%s)\n", strerror(errno)); | |
| } | |
| /* Now, copy the cached values into Window's structure... */ | |
| if (lpInfo->dwFlags & JOY_RETURNBUTTONS) | |
| lpInfo->dwButtons = jstck->buttons; | |
| if (lpInfo->dwFlags & JOY_RETURNX) | |
| lpInfo->dwXpos = jstck->x + 32767; | |
| if (lpInfo->dwFlags & JOY_RETURNY) | |
| lpInfo->dwYpos = jstck->y + 32767; | |
| if (lpInfo->dwFlags & JOY_RETURNZ) | |
| lpInfo->dwZpos = jstck->z + 32767; | |
| if (lpInfo->dwFlags & JOY_RETURNR) | |
| lpInfo->dwRpos = jstck->r + 32767; | |
| # ifdef BODGE_THE_HAT | |
| else if (lpInfo->dwFlags & JOY_RETURNBUTTONS) | |
| { | |
| if (jstck->r > 0) | |
| lpInfo->dwButtons |= 1<<7; | |
| else if (jstck->r < 0) | |
| lpInfo->dwButtons |= 1<<8; | |
| } | |
| # endif | |
| if (lpInfo->dwFlags & JOY_RETURNU) | |
| lpInfo->dwUpos = jstck->u + 32767; | |
| # ifdef BODGE_THE_HAT | |
| else if (lpInfo->dwFlags & JOY_RETURNBUTTONS) | |
| { | |
| if (jstck->u > 0) | |
| lpInfo->dwButtons |= 1<<9; | |
| else if (jstck->u < 0) | |
| lpInfo->dwButtons |= 1<<10; | |
| } | |
| # endif | |
| if (lpInfo->dwFlags & JOY_RETURNV) | |
| lpInfo->dwVpos = jstck->v + 32767; | |
| #else | |
| dev_stat = read(dev, &js, sizeof(js)); | |
| if (dev_stat != sizeof(js)) { | |
| return JOYERR_UNPLUGGED; /* FIXME: perhaps wrong, but what should I return else ? */ | |
| } | |
| js.x = js.x<<8; | |
| js.y = js.y<<8; | |
| if (lpInfo->dwFlags & JOY_RETURNX) | |
| lpInfo->dwXpos = js.x; /* FIXME: perhaps multiply it somehow ? */ | |
| if (lpInfo->dwFlags & JOY_RETURNY) | |
| lpInfo->dwYpos = js.y; | |
| if (lpInfo->dwFlags & JOY_RETURNBUTTONS) | |
| lpInfo->dwButtons = js.buttons; | |
| #endif | |
| TRACE("x: %ld, y: %ld, z: %ld, r: %ld, u: %ld, v: %ld, buttons: 0x%04x, flags: 0x%04x (fd %d)\n", | |
| lpInfo->dwXpos, lpInfo->dwYpos, lpInfo->dwZpos, | |
| lpInfo->dwRpos, lpInfo->dwUpos, lpInfo->dwVpos, | |
| (unsigned int)lpInfo->dwButtons, | |
| (unsigned int)lpInfo->dwFlags, | |
| dev | |
| ); | |
| return JOYERR_NOERROR; | |
| } | |
| /************************************************************************** | |
| * JSTCK_GetPos [internal] | |
| */ | |
| static LONG JSTCK_GetPos(DWORD 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 = JSTCK_GetPosEx(dwDevID, &ji); | |
| if (ret == JOYERR_NOERROR) { | |
| lpInfo->wXpos = ji.dwXpos; | |
| lpInfo->wYpos = ji.dwYpos; | |
| lpInfo->wZpos = ji.dwZpos; | |
| lpInfo->wButtons = ji.dwButtons; | |
| } | |
| return ret; | |
| } | |
| /************************************************************************** | |
| * DriverProc (JOYSTICK.@) | |
| */ | |
| LONG CALLBACK JSTCK_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg, | |
| DWORD dwParam1, DWORD dwParam2) | |
| { | |
| /* EPP TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n", */ | |
| /* EPP dwDevID, hDriv, wMsg, dwParam1, dwParam2); */ | |
| switch(wMsg) { | |
| case DRV_LOAD: return 1; | |
| case DRV_FREE: return 1; | |
| case DRV_OPEN: return JSTCK_drvOpen((LPSTR)dwParam1, dwParam2); | |
| case DRV_CLOSE: return JSTCK_drvClose(dwDevID); | |
| case DRV_ENABLE: return 1; | |
| case DRV_DISABLE: return 1; | |
| case DRV_QUERYCONFIGURE: return 1; | |
| case DRV_CONFIGURE: MessageBoxA(0, "JoyStick MultiMedia Driver !", "JoyStick Driver", MB_OK); return 1; | |
| case DRV_INSTALL: return DRVCNF_RESTART; | |
| case DRV_REMOVE: return DRVCNF_RESTART; | |
| case JDD_GETNUMDEVS: return 1; | |
| case JDD_GETDEVCAPS: return JSTCK_GetDevCaps(dwDevID, (LPJOYCAPSW)dwParam1, dwParam2); | |
| case JDD_GETPOS: return JSTCK_GetPos(dwDevID, (LPJOYINFO)dwParam1); | |
| case JDD_SETCALIBRATION: | |
| case JDD_CONFIGCHANGED: return JOYERR_NOCANDO; | |
| case JDD_GETPOSEX: return JSTCK_GetPosEx(dwDevID, (LPJOYINFOEX)dwParam1); | |
| default: | |
| return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); | |
| } | |
| } | |
| #else | |
| /************************************************************************** | |
| * DriverProc (JOYSTICK.@) | |
| */ | |
| LONG CALLBACK JSTCK_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg, | |
| DWORD dwParam1, DWORD dwParam2) | |
| { | |
| /* EPP TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n", */ | |
| /* EPP dwDevID, hDriv, wMsg, dwParam1, dwParam2); */ | |
| switch(wMsg) { | |
| case DRV_LOAD: | |
| case DRV_FREE: | |
| case DRV_OPEN: | |
| case DRV_CLOSE: | |
| case DRV_ENABLE: | |
| case DRV_DISABLE: | |
| case DRV_QUERYCONFIGURE: return 0; | |
| case DRV_CONFIGURE: MessageBoxA(0, "JoyStick MultiMedia Driver !", "JoyStick Driver", MB_OK); return 1; | |
| case DRV_INSTALL: return DRVCNF_RESTART; | |
| case DRV_REMOVE: return DRVCNF_RESTART; | |
| default: | |
| return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); | |
| } | |
| } | |
| #endif |