dinput joystick axis mapping patch (revised)

Robert Reif reif at earthlink.net
Tue Sep 7 21:55:37 CDT 2004


This patch replaces my previous mapping patchs.  This one
has been tested to produce the exact same results as windows
using the joystick.exe program from the DX9 SDK.

This patch allows you map a linux joystick axis to a dinput axis or POV.

I had to change the initialization sequence a bit to get the needed 
data at the right time.

Add a HKLM/Software/Wine/dinput key to your registry and add a string
value with the following information:

value name: your device name
value data: X,Y,Z,Rx,Ry,Rz,Slider1,Slider2,POV1,POV2,POV3,POV4

where "your device name" is what you get from /bin/jstest and
the axes list are the axes supported by your joystick.

For example, here is my registry string value:

value name: Logitech Logitech Extreme 3D Pro
value data: X,Y,Rz,Slider1,POV1

where my joystick has 6 axes. POVs use 2 axes so POV1 counts as 2 axes.
You must map all axes.  An incomplete specification will produce an error
message.

Change Log:
- Add linux to dinput axis mapping using HKLM/Software/Wine/dinput registry settings.
- Fix some format mapping bugs.
- Adds GetDeviceInfo.
- Adds  SetProperty DIPH_DEVICE support for setting all properties.
- Use windows compatible object naming.

-------------- next part --------------
Index: dlls/dinput/joystick_linux.c
===================================================================
RCS file: /home/wine/wine/dlls/dinput/joystick_linux.c,v
retrieving revision 1.11
diff -u -r1.11 joystick_linux.c
--- dlls/dinput/joystick_linux.c	8 Sep 2004 01:37:25 -0000	1.11
+++ dlls/dinput/joystick_linux.c	8 Sep 2004 02:43:05 -0000
@@ -19,6 +19,14 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+/*
+ * To Do:
+ *	support more than one device
+ *	dead zone
+ *	buffered mode
+ *	force feadback
+ */
+
 #include "config.h"
 #include "wine/port.h"
 
@@ -56,6 +64,7 @@
 #include "windef.h"
 #include "winbase.h"
 #include "winerror.h"
+#include "winreg.h"
 #include "dinput.h"
 
 #include "dinput_private.h"
@@ -63,10 +72,6 @@
 
 WINE_DEFAULT_DEBUG_CHANNEL(dinput);
 
-/* Wine joystick driver object instances */
-#define WINE_JOYSTICK_AXIS_BASE   0
-#define WINE_JOYSTICK_BUTTON_BASE 8
-
 typedef struct {
     LONG lMin;
     LONG lMax;
@@ -74,6 +79,11 @@
     LONG lSaturation;
 } ObjProps;
 
+typedef struct {
+    LONG lX;
+    LONG lY;
+} POV;
+
 typedef struct JoystickImpl JoystickImpl;
 static IDirectInputDevice8AVtbl JoystickAvt;
 static IDirectInputDevice8WVtbl JoystickWvt;
@@ -98,6 +108,12 @@
         int				queue_head, queue_tail, queue_len;
 	BOOL				acquired;
 	char				*name;
+	DIDEVCAPS			devcaps;
+	LONG				deadzone;
+	int				*axis_map;
+	int				axes;
+	int				buttons;
+	POV				povs[4];
 };
 
 static GUID DInput_Wine_Joystick_GUID = { /* 9e573ed9-7734-11d2-8d4a-23903fb6bdf7 */
@@ -107,6 +123,29 @@
   {0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf7}
 };
 
+static void _dump_DIDEVCAPS(LPDIDEVCAPS lpDIDevCaps)
+{
+    TRACE("dwSize: %ld\n", lpDIDevCaps->dwSize);
+    TRACE("dwFlags: %08lx\n",lpDIDevCaps->dwFlags);
+    TRACE("dwDevType: %08lx %s\n", lpDIDevCaps->dwDevType,
+          lpDIDevCaps->dwDevType == DIDEVTYPE_DEVICE ? "DIDEVTYPE_DEVICE" :
+          lpDIDevCaps->dwDevType == DIDEVTYPE_DEVICE ? "DIDEVTYPE_DEVICE" :
+          lpDIDevCaps->dwDevType == DIDEVTYPE_MOUSE ? "DIDEVTYPE_MOUSE" :
+          lpDIDevCaps->dwDevType == DIDEVTYPE_KEYBOARD ? "DIDEVTYPE_KEYBOARD" :
+          lpDIDevCaps->dwDevType == DIDEVTYPE_JOYSTICK ? "DIDEVTYPE_JOYSTICK" :
+          lpDIDevCaps->dwDevType == DIDEVTYPE_HID ? "DIDEVTYPE_HID" : "UNKNOWN");
+    TRACE("dwAxes: %ld\n",lpDIDevCaps->dwAxes);
+    TRACE("dwButtons: %ld\n",lpDIDevCaps->dwButtons);
+    TRACE("dwPOVs: %ld\n",lpDIDevCaps->dwPOVs);
+    if (lpDIDevCaps->dwSize > sizeof(DIDEVCAPS_DX3)) {
+        TRACE("dwFFSamplePeriod: %ld\n",lpDIDevCaps->dwFFSamplePeriod);
+        TRACE("dwFFMinTimeResolution: %ld\n",lpDIDevCaps->dwFFMinTimeResolution);
+        TRACE("dwFirmwareRevision: %ld\n",lpDIDevCaps->dwFirmwareRevision);
+        TRACE("dwHardwareRevision: %ld\n",lpDIDevCaps->dwHardwareRevision);
+        TRACE("dwFFDriverVersion: %ld\n",lpDIDevCaps->dwFFDriverVersion);
+    }
+}
+
 static BOOL joydev_enum_deviceA(DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEA lpddi, int version)
 {
     int fd = -1;
@@ -133,8 +172,8 @@
             lpddi->dwDevType = DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8);
         strcpy(lpddi->tszInstanceName, "Joystick");
 #if defined(JSIOCGNAME)
-        if (ioctl(fd,JSIOCGNAME(sizeof(lpddi->tszProductName)),lpddi->tszProductName) < 0) { 
-            WARN("ioctl(%s,JSIOCGNAME) failed: %s\n", JOYDEV, strerror(errno)); 
+        if (ioctl(fd,JSIOCGNAME(sizeof(lpddi->tszProductName)),lpddi->tszProductName) < 0) {
+            WARN("ioctl(%s,JSIOCGNAME) failed: %s\n", JOYDEV, strerror(errno));
             strcpy(lpddi->tszProductName, "Wine Joystick");
         }
 #else
@@ -162,7 +201,7 @@
 
     if ((dwDevType==0) || (GET_DIDEVICE_TYPE(dwDevType)==DIDEVTYPE_JOYSTICK)) {
         /* check whether we have a joystick */
-        if ((fd = open(JOYDEV,O_RDONLY) < 0)) {
+        if ((fd = open(JOYDEV,O_RDONLY)) < 0) {
             WARN("open(%s,O_RDONLY) failed: %s\n", JOYDEV, strerror(errno));
             return FALSE;
         }
@@ -176,8 +215,8 @@
 
         MultiByteToWideChar(CP_ACP, 0, "Joystick", -1, lpddi->tszInstanceName, MAX_PATH);
 #if defined(JSIOCGNAME)
-        if (ioctl(fd,JSIOCGNAME(sizeof(name)),name) < 0) { 
-            WARN("ioctl(%s,JSIOCGNAME) failed: %s\n", JOYDEV, strerror(errno)); 
+        if (ioctl(fd,JSIOCGNAME(sizeof(name)),name) < 0) {
+            WARN("ioctl(%s,JSIOCGNAME) failed: %s\n", JOYDEV, strerror(errno));
             strcpy(name, "Wine Joystick");
         }
 #else
@@ -193,23 +232,270 @@
     return FALSE;
 }
 
-static JoystickImpl *alloc_device(REFGUID rguid, LPVOID jvt, IDirectInputImpl *dinput)
+/*
+ * Get a config key from either the app-specific or the default config
+ */
+
+inline static DWORD get_config_key( HKEY defkey, HKEY appkey, const char *name,
+                                    char *buffer, DWORD size )
+{
+    if (appkey && !RegQueryValueExA( appkey, name, 0, NULL, buffer, &size ))
+        return 0;
+
+    return RegQueryValueExA( defkey, name, 0, NULL, buffer, &size );
+}
+
+/*
+ * Setup the dinput options.
+ */
+
+static HRESULT setup_dinput_options(JoystickImpl * device)
+{
+    char buffer[MAX_PATH+1];
+    HKEY hkey, appkey = 0;
+    DWORD len;
+
+    buffer[MAX_PATH]='\0';
+
+    if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\\Wine\\dinput", 0, NULL,
+                         REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL )) {
+        ERR("Cannot create config registry key\n" );
+        ExitProcess(1);
+    }
+
+    len = GetModuleFileNameA( 0, buffer, MAX_PATH );
+    if (len && len < MAX_PATH) {
+        HKEY tmpkey;
+
+        if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\AppDefaults", &tmpkey )) {
+           char appname[MAX_PATH+16];
+           char *p = strrchr( buffer, '\\' );
+           if (p!=NULL) {
+                   appname[MAX_PATH]='\0';
+                   strncpy(appname,p+1,MAX_PATH);
+                   strcat(appname,"\\dinput");
+                   TRACE("appname = [%s] \n",appname);
+                   if (RegOpenKeyA( tmpkey, appname, &appkey )) appkey = 0;
+                       RegCloseKey( tmpkey );
+           }
+        }
+    }
+
+    /* get options */
+
+    if (!get_config_key( hkey, appkey, "DefaultDeadZone", buffer, MAX_PATH )) {
+        device->deadzone = atoi(buffer);
+        TRACE("setting default deadzone to: \"%s\" %ld\n", buffer, device->deadzone);
+    }
+
+    if (!get_config_key( hkey, appkey, device->name, buffer, MAX_PATH )) {
+        int tokens = 0;
+        int axis = 0;
+        int pov = 0;
+        char *delim = ",";
+        char * ptr;
+        TRACE("\"%s\" = \"%s\"\n", device->name, buffer);
+
+        device->axis_map = HeapAlloc(GetProcessHeap(), 0, device->axes * sizeof(int));
+        if (device->axis_map == 0)
+            return DIERR_OUTOFMEMORY;
+
+        if ((ptr = strtok(buffer, delim)) != NULL) {
+            do {
+                if (strcmp(ptr, "X") == 0) {
+                    device->axis_map[tokens] = 0;
+                    axis++;
+                } else if (strcmp(ptr, "Y") == 0) {
+                    device->axis_map[tokens] = 1;
+                    axis++;
+                } else if (strcmp(ptr, "Z") == 0) {
+                    device->axis_map[tokens] = 2;
+                    axis++;
+                } else if (strcmp(ptr, "Rx") == 0) {
+                    device->axis_map[tokens] = 3;
+                    axis++;
+                } else if (strcmp(ptr, "Ry") == 0) {
+                    device->axis_map[tokens] = 4;
+                    axis++;
+                } else if (strcmp(ptr, "Rz") == 0) {
+                    device->axis_map[tokens] = 5;
+                    axis++;
+                } else if (strcmp(ptr, "Slider1") == 0) {
+                    device->axis_map[tokens] = 6;
+                    axis++;
+                } else if (strcmp(ptr, "Slider2") == 0) {
+                    device->axis_map[tokens] = 7;
+                    axis++;
+                } else if (strcmp(ptr, "POV1") == 0) {
+                    device->axis_map[tokens++] = 8;
+                    device->axis_map[tokens] = 8;
+                    pov++;
+                } else if (strcmp(ptr, "POV2") == 0) {
+                    device->axis_map[tokens++] = 9;
+                    device->axis_map[tokens] = 9;
+                    pov++;
+                } else if (strcmp(ptr, "POV3") == 0) {
+                    device->axis_map[tokens++] = 10;
+                    device->axis_map[tokens] = 10;
+                    pov++;
+                } else if (strcmp(ptr, "POV4") == 0) {
+                    device->axis_map[tokens++] = 11;
+                    device->axis_map[tokens] = 11;
+                    pov++;
+                } else {
+                    ERR("invalid joystick axis type: %s\n", ptr);
+                    device->axis_map[tokens] = tokens;
+                    axis++;
+                }
+
+                tokens++;
+            } while ((ptr = strtok(NULL, delim)) != NULL);
+
+            if (tokens != device->devcaps.dwAxes) {
+                ERR("not all joystick axes mapped: %d axes(%d,%d), %d arguments\n", device->axes, axis, pov,tokens);
+                while (tokens < device->axes) {
+                    device->axis_map[tokens] = tokens;
+                    tokens++;
+                }
+            }
+        }
+
+        device->devcaps.dwAxes = axis;
+        device->devcaps.dwPOVs = pov;
+    }
+
+    if (appkey)
+        RegCloseKey( appkey );
+
+    RegCloseKey( hkey );
+
+    return DI_OK;
+}
+
+void calculate_ids(JoystickImpl* device)
+{
+    int i;
+    int axis = 0;
+    int button = 0;
+    int pov = 0;
+    int axis_base;
+    int pov_base;
+    int button_base;
+
+    /* Make two passes over the format. The first counts the number
+     * for each type and the second sets the id */
+    for (i = 0; i < device->user_df->dwNumObjs; i++) {
+        if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_AXIS)
+            axis++;
+        else if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_POV)
+            pov++;
+        else if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_BUTTON)
+            button++;
+    }
+
+    axis_base = 0;
+    pov_base = axis;
+    button_base = axis + pov;
+
+    axis = 0;
+    button = 0;
+    pov = 0;
+
+    for (i = 0; i < device->user_df->dwNumObjs; i++) {
+        DWORD type = 0;
+        if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_AXIS) {
+            axis++;
+            type = DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) |
+                DIDFT_MAKEINSTANCE(axis + axis_base);
+            TRACE("axis type = 0x%08lx\n", type);
+        } else if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_POV) {
+            pov++;
+            type = DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) |
+                DIDFT_MAKEINSTANCE(pov + pov_base);
+            TRACE("POV type = 0x%08lx\n", type);
+        } else if (DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) & DIDFT_BUTTON) {
+            button++;
+            type = DIDFT_GETTYPE(device->user_df->rgodf[i].dwType) |
+                DIDFT_MAKEINSTANCE(button + button_base);
+            TRACE("button type = 0x%08lx\n", type);
+        }
+        device->user_df->rgodf[i].dwType = type;
+    }
+}
+
+static HRESULT alloc_device(REFGUID rguid, LPVOID jvt, IDirectInputImpl *dinput, LPDIRECTINPUTDEVICEA* pdev)
 {
     DWORD i;
     JoystickImpl* newDevice;
+    char name[MAX_PATH];
+    HRESULT hr;
 
     newDevice = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(JoystickImpl));
-    if (newDevice == 0)
-        return 0;
+    if (newDevice == 0) {
+        WARN("out of memory\n");
+        *pdev = 0;
+        return DIERR_OUTOFMEMORY;
+    }
+
+    if ((newDevice->joyfd = open(JOYDEV,O_RDONLY)) < 0) {
+        WARN("open(%s,O_RDONLY) failed: %s\n", JOYDEV, strerror(errno));
+        HeapFree(GetProcessHeap(), 0, newDevice);
+        return DIERR_DEVICENOTREG;
+    }
+
+    /* get the device name */
+#if defined(JSIOCGNAME)
+    if (ioctl(newDevice->joyfd,JSIOCGNAME(MAX_PATH),name) < 0) {
+        WARN("ioctl(%s,JSIOCGNAME) failed: %s\n", JOYDEV, strerror(errno));
+        strcpy(name, "Wine Joystick");
+    }
+#else
+    strcpy(name, "Wine Joystick");
+#endif
+
+    /* copy the device name */
+    newDevice->name = HeapAlloc(GetProcessHeap(),0,strlen(name) + 1);
+    strcpy(newDevice->name, name);
+
+#ifdef JSIOCGAXES
+    if (ioctl(newDevice->joyfd,JSIOCGAXES,&newDevice->axes) < 0) {
+        WARN("ioctl(%s,JSIOCGAXES) failed: %s, defauting to 2\n", JOYDEV, strerror(errno));
+        newDevice->axes = 2;
+    }
+#endif
+#ifdef JSIOCGBUTTONS
+    if (ioctl(newDevice->joyfd,JSIOCGBUTTONS,&newDevice->buttons) < 0) {
+        WARN("ioctl(%s,JSIOCGBUTTONS) failed: %s, defauting to 2\n", JOYDEV, strerror(errno));
+        newDevice->buttons = 2;
+    }
+#endif
 
     newDevice->lpVtbl = jvt;
     newDevice->ref = 1;
-    newDevice->joyfd = -1;
     newDevice->dinput = dinput;
     newDevice->acquired = FALSE;
-    newDevice->name = NULL;
     CopyMemory(&(newDevice->guid),rguid,sizeof(*rguid));
 
+    /* setup_dinput_options may change these */
+    newDevice->deadzone = 5000;
+    newDevice->devcaps.dwButtons = newDevice->buttons;
+    newDevice->devcaps.dwAxes = newDevice->axes;
+    newDevice->devcaps.dwPOVs = 0;
+
+    /* do any user specified configuration */
+    hr = setup_dinput_options(newDevice);
+    if (hr != DI_OK)
+        goto FAILED1;
+
+    if (newDevice->axis_map == 0) {
+        newDevice->axis_map = HeapAlloc(GetProcessHeap(), 0, newDevice->axes * sizeof(int));
+        if (newDevice->axis_map == 0)
+            goto FAILED;
+
+        for (i = 0; i < newDevice->axes; i++)
+            newDevice->axis_map[i] = i;
+    }
+
     /* wine uses DIJOYSTATE2 as it's internal format so copy
      * the already defined format c_dfDIJoystick2 */
     newDevice->user_df = HeapAlloc(GetProcessHeap(),0,c_dfDIJoystick2.dwSize);
@@ -234,7 +520,7 @@
     for (i = 0; i < c_dfDIJoystick2.dwNumObjs; i++) {
         newDevice->props[i].lMin = 0;
         newDevice->props[i].lMax = 0xffff;
-        newDevice->props[i].lDeadZone = 1000;
+        newDevice->props[i].lDeadZone = newDevice->deadzone;	/* % * 1000 */
         newDevice->props[i].lSaturation = 0;
     }
 
@@ -246,14 +532,37 @@
     /* create the default transform filter */
     newDevice->transform = create_DataFormat(&c_dfDIJoystick2, newDevice->user_df, newDevice->offsets);
 
+    calculate_ids(newDevice);
+
     IDirectInputDevice_AddRef((LPDIRECTINPUTDEVICE8A)newDevice->dinput);
 
-    if (TRACE_ON(dinput))
+    newDevice->devcaps.dwSize = sizeof(newDevice->devcaps);
+    newDevice->devcaps.dwFlags = DIDC_ATTACHED;
+    newDevice->devcaps.dwDevType = DIDEVTYPE_JOYSTICK;
+    newDevice->devcaps.dwFFSamplePeriod = 0;
+    newDevice->devcaps.dwFFMinTimeResolution = 0;
+    newDevice->devcaps.dwFirmwareRevision = 0;
+    newDevice->devcaps.dwHardwareRevision = 0;
+    newDevice->devcaps.dwFFDriverVersion = 0;
+
+    if (TRACE_ON(dinput)) {
         _dump_DIDATAFORMAT(newDevice->user_df);
+       for (i = 0; i < (newDevice->axes); i++)
+           TRACE("axis_map[%ld] = %d\n", i, newDevice->axis_map[i]);
+        _dump_DIDEVCAPS(&newDevice->devcaps);
+    }
+
+    *pdev = (LPDIRECTINPUTDEVICEA)newDevice;
 
-    return newDevice;
+    return DI_OK;
 
 FAILED:
+    hr = DIERR_OUTOFMEMORY;
+FAILED1:
+    if (newDevice->axis_map)
+        HeapFree(GetProcessHeap(),0,newDevice->axis_map);
+    if (newDevice->name)
+        HeapFree(GetProcessHeap(),0,newDevice->name);
     if (newDevice->props)
         HeapFree(GetProcessHeap(),0,newDevice->props);
     if (newDevice->user_df->rgodf)
@@ -262,7 +571,9 @@
         HeapFree(GetProcessHeap(),0,newDevice->user_df);
     if (newDevice)
         HeapFree(GetProcessHeap(),0,newDevice);
-    return 0;
+    *pdev = 0;
+
+    return hr;
 }
 
 static HRESULT joydev_create_deviceA(IDirectInputImpl *dinput, REFGUID rguid, REFIID riid, LPDIRECTINPUTDEVICEA* pdev)
@@ -274,14 +585,7 @@
 	IsEqualGUID(&IID_IDirectInputDevice2A,riid) ||
 	IsEqualGUID(&IID_IDirectInputDevice7A,riid) ||
 	IsEqualGUID(&IID_IDirectInputDevice8A,riid)) {
-      *pdev=(IDirectInputDeviceA*) alloc_device(rguid, &JoystickAvt, dinput);
-      if (*pdev == 0) {
-        WARN("out of memory\n");
-        return DIERR_OUTOFMEMORY;
-      }
-
-      TRACE("Creating a Joystick device (%p)\n", *pdev);
-      return DI_OK;
+      return alloc_device(rguid, &JoystickAvt, dinput, pdev);
     } else {
       WARN("no interface\n");
       *pdev = 0;
@@ -303,14 +607,7 @@
 	IsEqualGUID(&IID_IDirectInputDevice2W,riid) ||
 	IsEqualGUID(&IID_IDirectInputDevice7W,riid) ||
 	IsEqualGUID(&IID_IDirectInputDevice8W,riid)) {
-      *pdev = (IDirectInputDeviceW*) alloc_device(rguid, &JoystickWvt, dinput);
-      if (*pdev == 0) {
-        WARN("out of memory\n");
-        return DIERR_OUTOFMEMORY;
-      }
-
-      TRACE("Creating a Joystick device (%p)\n", *pdev);
-      return DI_OK;
+      return alloc_device(rguid, &JoystickWvt, dinput, (LPDIRECTINPUTDEVICEA *)pdev);
     } else {
       WARN("no interface\n");
       *pdev = 0;
@@ -349,6 +646,10 @@
     if (This->name)
         HeapFree(GetProcessHeap(),0,This->name);
 
+    /* Free the axis map */
+    if (This->axis_map)
+        HeapFree(GetProcessHeap(),0,This->axis_map);
+
     /* Free the data queue */
     if (This->data_queue != NULL)
         HeapFree(GetProcessHeap(),0,This->data_queue);
@@ -434,6 +735,8 @@
     This->offsets = new_offsets;
     This->transform = create_DataFormat(&c_dfDIJoystick2, This->user_df, This->offsets);
 
+    calculate_ids(This);
+
     return DI_OK;
 
 FAILED:
@@ -523,7 +826,7 @@
     return fret;
 }
 
-/* convert user format offset to user format object index */
+/* convert wine format offset to user format object index */
 int offset_to_object(JoystickImpl *This, int offset)
 {
     int i;
@@ -536,6 +839,32 @@
     return -1;
 }
 
+static void calculate_pov(JoystickImpl *This, int index)
+{
+    if (This->povs[index].lX < 16384) {
+        if (This->povs[index].lY < 16384)
+            This->js.rgdwPOV[index] = 31500;
+        else if (This->povs[index].lY > 49150)
+            This->js.rgdwPOV[index] = 22500;
+        else
+            This->js.rgdwPOV[index] = 27000;
+    } else if (This->povs[index].lX > 49150) {
+        if (This->povs[index].lY < 16384)
+            This->js.rgdwPOV[index] = 4500;
+        else if (This->povs[index].lY > 49150)
+            This->js.rgdwPOV[index] = 13500;
+        else
+            This->js.rgdwPOV[index] = 9000;
+    } else {
+        if (This->povs[index].lY < 16384)
+            This->js.rgdwPOV[index] = 0;
+        else if (This->povs[index].lY > 49150)
+            This->js.rgdwPOV[index] = 18000;
+        else
+            This->js.rgdwPOV[index] = -1;
+    }
+}
+
 static void joy_polldev(JoystickImpl *This) {
     struct timeval tv;
     fd_set	readfds;
@@ -564,14 +893,16 @@
             This->js.rgbButtons[jse.number] = value;
             GEN_EVENT(offset,value,jse.time,(This->dinput->evsequence)++);
         } else if (jse.type & JS_EVENT_AXIS) {
-            if (jse.number < 8) {
-                int offset = This->offsets[jse.number];
+            int number = This->axis_map[jse.number];	/* wine format object index */
+            if (number < 12) {
+                int offset = This->offsets[number];
                 int index = offset_to_object(This, offset);
                 LONG value = map_axis(This, jse.value, index);
 
                 /* FIXME do deadzone and saturation here */
 
-                switch (jse.number) {
+                TRACE("changing axis %d => %d\n", jse.number, number);
+                switch (number) {
                 case 0:
                     This->js.lX = value;
                     break;
@@ -596,11 +927,40 @@
                 case 7:
                     This->js.rglSlider[1] = value;
                     break;
+                case 8:
+                    /* FIXME don't go off array */
+                    if (This->axis_map[jse.number + 1] == number)
+                        This->povs[0].lX = value;
+                    else if (This->axis_map[jse.number - 1] == number)
+                        This->povs[0].lY = value;
+                    calculate_pov(This, 0);
+                    break;
+                case 9:
+                    if (This->axis_map[jse.number + 1] == number)
+                        This->povs[1].lX = value;
+                    else if (This->axis_map[jse.number - 1] == number)
+                        This->povs[1].lY = value;
+                    calculate_pov(This, 1);
+                    break;
+                case 10:
+                    if (This->axis_map[jse.number + 1] == number)
+                        This->povs[2].lX = value;
+                    else if (This->axis_map[jse.number - 1] == number)
+                        This->povs[2].lY = value;
+                    calculate_pov(This, 2);
+                    break;
+                case 11:
+                    if (This->axis_map[jse.number + 1] == number)
+                        This->povs[3].lX = value;
+                    else if (This->axis_map[jse.number - 1] == number)
+                        This->povs[3].lY = value;
+                    calculate_pov(This, 3);
+                    break;
                 }
 
                 GEN_EVENT(offset,value,jse.time,(This->dinput->evsequence)++);
-            } else 
-                WARN("axis %d not supported\n", jse.number);
+            } else
+                WARN("axis %d not supported\n", number);
         }
     }
 }
@@ -635,24 +995,32 @@
 /******************************************************************************
   *     GetDeviceData : gets buffered input data.
   */
-static HRESULT WINAPI JoystickAImpl_GetDeviceData(LPDIRECTINPUTDEVICE8A iface,
-					      DWORD dodsize,
-					      LPDIDEVICEOBJECTDATA dod,
-					      LPDWORD entries,
-					      DWORD flags
-) {
-  ICOM_THIS(JoystickImpl,iface);
+static HRESULT WINAPI JoystickAImpl_GetDeviceData(
+    LPDIRECTINPUTDEVICE8A iface,
+    DWORD dodsize,
+    LPDIDEVICEOBJECTDATA dod,
+    LPDWORD entries,
+    DWORD flags)
+{
+    ICOM_THIS(JoystickImpl,iface);
 
-  FIXME("(%p)->(dods=%ld,entries=%ld,fl=0x%08lx),STUB!\n",This,dodsize,*entries,flags);
+    FIXME("(%p)->(dods=%ld,entries=%ld,fl=0x%08lx),STUB!\n",This,dodsize,*entries,flags);
 
-  joy_polldev(This);
-  if (flags & DIGDD_PEEK)
-    FIXME("DIGDD_PEEK\n");
+    if (!This->acquired) {
+        WARN("not acquired\n");
+        return DIERR_NOTACQUIRED;
+    }
 
-  if (dod == NULL) {
-  } else {
-  }
-  return DI_OK;
+    joy_polldev(This);
+
+    if (flags & DIGDD_PEEK)
+        FIXME("DIGDD_PEEK\n");
+    *entries = 0;
+
+    if (dod == NULL) {
+    } else {
+    }
+    return DI_OK;
 }
 
 int find_property(JoystickImpl * This, LPCDIPROPHEADER ph)
@@ -661,23 +1029,8 @@
     if (ph->dwHow == DIPH_BYOFFSET) {
         return offset_to_object(This, ph->dwObj);
     } else if (ph->dwHow == DIPH_BYID) {
-        int axis = 0;
-        int button = 0;
         for (i = 0; i < This->user_df->dwNumObjs; i++) {
-            DWORD type = 0;
-            if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_AXIS) {
-                axis++;
-                type = DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) |
-                    DIDFT_MAKEINSTANCE(axis << WINE_JOYSTICK_AXIS_BASE);
-                TRACE("type = 0x%08lx\n", type);
-            }
-            if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_BUTTON) {
-                button++;
-                type = DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) |
-                    DIDFT_MAKEINSTANCE(button << WINE_JOYSTICK_BUTTON_BASE);
-                TRACE("type = 0x%08lx\n", type);
-            }
-            if (type == ph->dwObj) {
+            if ((This->user_df->rgodf[i].dwType & 0x00ffffff) == (ph->dwObj & 0x00ffffff)) {
                 return i;
             }
         }
@@ -695,6 +1048,7 @@
     LPCDIPROPHEADER ph)
 {
     ICOM_THIS(JoystickImpl,iface);
+    int i;
 
     TRACE("(%p,%s,%p)\n",This,debugstr_guid(rguid),ph);
 
@@ -710,32 +1064,52 @@
         }
         case (DWORD)DIPROP_RANGE: {
             LPCDIPROPRANGE	pr = (LPCDIPROPRANGE)ph;
-            int obj = find_property(This, ph);
-            TRACE("proprange(%ld,%ld) obj=%d\n",pr->lMin,pr->lMax,obj);
-            if (obj >= 0) {
-                This->props[obj].lMin = pr->lMin;
-                This->props[obj].lMax = pr->lMax;
-                return DI_OK;
+            if (ph->dwHow == DIPH_DEVICE) {
+                TRACE("proprange(%ld,%ld) all\n",pr->lMin,pr->lMax);
+                for (i = 0; i < This->user_df->dwNumObjs; i++) {
+                    This->props[i].lMin = pr->lMin;
+                    This->props[i].lMax = pr->lMax;
+                }
+            } else {
+                int obj = find_property(This, ph);
+                TRACE("proprange(%ld,%ld) obj=%d\n",pr->lMin,pr->lMax,obj);
+                if (obj >= 0) {
+                    This->props[obj].lMin = pr->lMin;
+                    This->props[obj].lMax = pr->lMax;
+                    return DI_OK;
+                }
             }
             break;
         }
         case (DWORD)DIPROP_DEADZONE: {
             LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;
-            int obj = find_property(This, ph);
-            TRACE("deadzone(%ld) obj=%d\n",pd->dwData,obj);
-            if (obj >= 0) {
-                This->props[obj].lDeadZone  = pd->dwData;
-                return DI_OK;
+            if (ph->dwHow == DIPH_DEVICE) {
+                TRACE("deadzone(%ld) all\n",pd->dwData);
+                for (i = 0; i < This->user_df->dwNumObjs; i++)
+                    This->props[i].lDeadZone  = pd->dwData;
+            } else {
+                int obj = find_property(This, ph);
+                TRACE("deadzone(%ld) obj=%d\n",pd->dwData,obj);
+                if (obj >= 0) {
+                    This->props[obj].lDeadZone  = pd->dwData;
+                    return DI_OK;
+                }
             }
             break;
         }
         case (DWORD)DIPROP_SATURATION: {
             LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;
-            int obj = find_property(This, ph);
-            TRACE("saturation(%ld) obj=%d\n",pd->dwData,obj);
-            if (obj >= 0) {
-                This->props[obj].lSaturation = pd->dwData;
-                return DI_OK;
+            if (ph->dwHow == DIPH_DEVICE) {
+                TRACE("saturation(%ld) all\n",pd->dwData);
+                for (i = 0; i < This->user_df->dwNumObjs; i++)
+                    This->props[i].lSaturation = pd->dwData;
+            } else {
+                int obj = find_property(This, ph);
+                TRACE("saturation(%ld) obj=%d\n",pd->dwData,obj);
+                if (obj >= 0) {
+                    This->props[obj].lSaturation = pd->dwData;
+                    return DI_OK;
+                }
             }
             break;
         }
@@ -761,63 +1135,24 @@
     return DI_OK;
 }
 
-static void _dump_DIDEVCAPS(LPDIDEVCAPS lpDIDevCaps)
-{
-    TRACE("dwSize: %ld\n", lpDIDevCaps->dwSize);
-    TRACE("dwFlags: %08lx\n",lpDIDevCaps->dwFlags);
-    TRACE("dwDevType: %08lx %s\n", lpDIDevCaps->dwDevType,
-          lpDIDevCaps->dwDevType == DIDEVTYPE_DEVICE ? "DIDEVTYPE_DEVICE" :
-          lpDIDevCaps->dwDevType == DIDEVTYPE_DEVICE ? "DIDEVTYPE_DEVICE" :
-          lpDIDevCaps->dwDevType == DIDEVTYPE_MOUSE ? "DIDEVTYPE_MOUSE" :
-          lpDIDevCaps->dwDevType == DIDEVTYPE_KEYBOARD ? "DIDEVTYPE_KEYBOARD" :
-          lpDIDevCaps->dwDevType == DIDEVTYPE_JOYSTICK ? "DIDEVTYPE_JOYSTICK" :
-          lpDIDevCaps->dwDevType == DIDEVTYPE_HID ? "DIDEVTYPE_HID" : "UNKNOWN");
-    TRACE("dwAxes: %ld\n",lpDIDevCaps->dwAxes);
-    TRACE("dwButtons: %ld\n",lpDIDevCaps->dwButtons);
-    TRACE("dwPOVs: %ld\n",lpDIDevCaps->dwPOVs);
-    if (lpDIDevCaps->dwSize > sizeof(DIDEVCAPS_DX3)) {
-        TRACE("dwFFSamplePeriod: %ld\n",lpDIDevCaps->dwFFSamplePeriod);
-        TRACE("dwFFMinTimeResolution: %ld\n",lpDIDevCaps->dwFFMinTimeResolution);
-        TRACE("dwFirmwareRevision: %ld\n",lpDIDevCaps->dwFirmwareRevision);
-        TRACE("dwHardwareRevision: %ld\n",lpDIDevCaps->dwHardwareRevision);
-        TRACE("dwFFDriverVersion: %ld\n",lpDIDevCaps->dwFFDriverVersion);
-    }
-}
-
 static HRESULT WINAPI JoystickAImpl_GetCapabilities(
 	LPDIRECTINPUTDEVICE8A iface,
 	LPDIDEVCAPS lpDIDevCaps)
 {
     ICOM_THIS(JoystickImpl,iface);
-    BYTE	axes,buttons;
-    int		xfd = This->joyfd;
+    int size;
 
     TRACE("%p->(%p)\n",iface,lpDIDevCaps);
 
-    if (xfd==-1)
-    	xfd = open(JOYDEV,O_RDONLY);
-    lpDIDevCaps->dwFlags	= DIDC_ATTACHED;
-    lpDIDevCaps->dwDevType	= DIDEVTYPE_JOYSTICK;
-#ifdef JSIOCGAXES
-    if (-1==ioctl(xfd,JSIOCGAXES,&axes))
-    	axes = 2;
-    lpDIDevCaps->dwAxes = axes;
-#endif
-#ifdef JSIOCGBUTTONS
-    if (-1==ioctl(xfd,JSIOCGAXES,&buttons))
-    	buttons = 2;
-    lpDIDevCaps->dwButtons = buttons;
-#endif
-    if (xfd!=This->joyfd)
-    	close(xfd);
-    if (lpDIDevCaps->dwSize > sizeof(DIDEVCAPS_DX3)) {
-        lpDIDevCaps->dwFFSamplePeriod = 0;
-        lpDIDevCaps->dwFFMinTimeResolution = 0;
-        lpDIDevCaps->dwFirmwareRevision = 0;
-        lpDIDevCaps->dwHardwareRevision = 0;
-        lpDIDevCaps->dwFFDriverVersion = 0;
+    if (lpDIDevCaps == NULL) {
+        WARN("invalid parameter: lpDIDevCaps = NULL\n");
+        return DIERR_INVALIDPARAM;
     }
 
+    size = lpDIDevCaps->dwSize;
+    CopyMemory(lpDIDevCaps, &This->devcaps, size);
+    lpDIDevCaps->dwSize = size;
+
     if (TRACE_ON(dinput))
         _dump_DIDEVCAPS(lpDIDevCaps);
 
@@ -850,7 +1185,9 @@
 {
   ICOM_THIS(JoystickImpl,iface);
   DIDEVICEOBJECTINSTANCEA ddoi;
-  int xfd = This->joyfd;
+  BYTE i;
+  int user_offset;
+  int user_object;
 
   TRACE("(this=%p,%p,%p,%08lx)\n", This, lpCallback, lpvRef, dwFlags);
   if (TRACE_ON(dinput)) {
@@ -862,97 +1199,108 @@
   /* Only the fields till dwFFMaxForce are relevant */
   ddoi.dwSize = FIELD_OFFSET(DIDEVICEOBJECTINSTANCEA, dwFFMaxForce);
 
-#if defined(JSIOCGNAME)
-  if (!This->name) {
-    char name[64];
-    if (-1==ioctl(xfd,JSIOCGNAME(sizeof(name)),name)) {
-      This->name = HeapAlloc(GetProcessHeap(),0,strlen(name) + 1);
-      strcpy(This->name, name);
-      strcpy(ddoi.tszName, This->name);
-    }
-  } else
-    strcpy(ddoi.tszName, This->name);
-#endif
-
   /* For the joystick, do as is done in the GetCapabilities function */
   if ((dwFlags == DIDFT_ALL) ||
-      (dwFlags & DIDFT_AXIS)) {
-    BYTE axes, i;
+      (dwFlags & DIDFT_AXIS) ||
+      (dwFlags & DIDFT_POV)) {
+    int	pov[4] = { 0, 0, 0, 0 };
+    int axes = 0;
+    int povs = 0;
+
+    for (i = 0; i < This->axes; i++) {
+      int wine_obj = This->axis_map[i];
+      BOOL skip = FALSE;
 
-#ifdef JSIOCGAXES
-    if (-1==ioctl(xfd,JSIOCGAXES,&axes))
-      axes = 2;
-#endif
-
-    for (i = 0; i < axes; i++) {
-      switch (i) {
+      switch (wine_obj) {
       case 0:
 	ddoi.guidType = GUID_XAxis;
-	ddoi.dwOfs = DIJOFS_X;
 	break;
       case 1:
 	ddoi.guidType = GUID_YAxis;
-	ddoi.dwOfs = DIJOFS_Y;
 	break;
       case 2:
 	ddoi.guidType = GUID_ZAxis;
-	ddoi.dwOfs = DIJOFS_Z;
 	break;
       case 3:
 	ddoi.guidType = GUID_RxAxis;
-	ddoi.dwOfs = DIJOFS_RX;
 	break;
       case 4:
 	ddoi.guidType = GUID_RyAxis;
-	ddoi.dwOfs = DIJOFS_RY;
 	break;
       case 5:
 	ddoi.guidType = GUID_RzAxis;
-	ddoi.dwOfs = DIJOFS_RZ;
 	break;
       case 6:
 	ddoi.guidType = GUID_Slider;
-	ddoi.dwOfs = DIJOFS_SLIDER(0);
 	break;
       case 7:
 	ddoi.guidType = GUID_Slider;
-	ddoi.dwOfs = DIJOFS_SLIDER(1);
+	break;
+      case 8:
+        pov[0]++;
+	ddoi.guidType = GUID_POV;
+	break;
+      case 9:
+        pov[1]++;
+	ddoi.guidType = GUID_POV;
+	break;
+      case 10:
+        pov[2]++;
+	ddoi.guidType = GUID_POV;
+	break;
+      case 11:
+        pov[3]++;
+	ddoi.guidType = GUID_POV;
 	break;
       default:
 	ddoi.guidType = GUID_Unknown;
-	ddoi.dwOfs = DIJOFS_Z + (i - 2) * sizeof(LONG);
       }
-      ddoi.dwType = DIDFT_MAKEINSTANCE((i + 1) << WINE_JOYSTICK_AXIS_BASE) | DIDFT_AXIS;
-      sprintf(ddoi.tszName, "%d-Axis", i);
-      _dump_OBJECTINSTANCEA(&ddoi);
-      if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
+      if (wine_obj < 8) {
+          user_offset = This->offsets[wine_obj];	/* get user offset from wine index */
+          user_object = offset_to_object(This, user_offset);
+
+          ddoi.dwType = This->user_df->rgodf[user_object].dwType & 0x00ffffff;
+          ddoi.dwOfs =  This->user_df->rgodf[user_object].dwOfs;
+          sprintf(ddoi.tszName, "Axis %d", axes);
+          axes++;
+      } else {
+          if (pov[wine_obj - 8] < 2) {
+              user_offset = This->offsets[wine_obj];	/* get user offset from wine index */
+              user_object = offset_to_object(This, user_offset);
+
+              ddoi.dwType = This->user_df->rgodf[user_object].dwType & 0x00ffffff;
+              ddoi.dwOfs =  This->user_df->rgodf[user_object].dwOfs;
+              sprintf(ddoi.tszName, "POV %d", povs);
+              povs++;
+          } else
+              skip = TRUE;
+      }
+      if (!skip) {
+          _dump_OBJECTINSTANCEA(&ddoi);
+          if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE)
+              return DI_OK;
+      }
     }
   }
 
   if ((dwFlags == DIDFT_ALL) ||
       (dwFlags & DIDFT_BUTTON)) {
-    BYTE buttons, i;
-
-#ifdef JSIOCGBUTTONS
-    if (-1==ioctl(xfd,JSIOCGAXES,&buttons))
-      buttons = 2;
-#endif
 
     /* The DInput SDK says that GUID_Button is only for mouse buttons but well... */
     ddoi.guidType = GUID_Button;
 
-    for (i = 0; i < buttons; i++) {
-      ddoi.dwOfs = DIJOFS_BUTTON(i);
-      ddoi.dwType = DIDFT_MAKEINSTANCE((i + 1) << WINE_JOYSTICK_BUTTON_BASE) | DIDFT_BUTTON;
-      sprintf(ddoi.tszName, "%d-Button", i);
+    for (i = 0; i < This->buttons; i++) {
+      user_offset = This->offsets[i + 12];	/* get user offset from wine index */
+      user_object = offset_to_object(This, user_offset);
+      ddoi.guidType = GUID_Button;
+      ddoi.dwType = This->user_df->rgodf[user_object].dwType & 0x00ffffff;
+      ddoi.dwOfs =  This->user_df->rgodf[user_object].dwOfs;
+      sprintf(ddoi.tszName, "Button %d", i);
       _dump_OBJECTINSTANCEA(&ddoi);
       if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
     }
   }
 
-  if (xfd!=This->joyfd)
-    close(xfd);
-
   return DI_OK;
 }
 
@@ -1077,22 +1425,41 @@
     didoiA.dwSize = pdidoi->dwSize;
 
     switch (dwHow) {
-    case DIPH_BYOFFSET:
+    case DIPH_BYOFFSET: {
+        int axis = 0;
+        int pov = 0;
+        int button = 0;
         for (i = 0; i < This->user_df->dwNumObjs; i++) {
             if (This->user_df->rgodf[i].dwOfs == dwObj) {
                 if (This->user_df->rgodf[i].pguid)
                     didoiA.guidType = *This->user_df->rgodf[i].pguid;
                 else
                     didoiA.guidType = GUID_NULL;
+
                 didoiA.dwOfs = dwObj;
                 didoiA.dwType = This->user_df->rgodf[i].dwType;
                 didoiA.dwFlags = This->user_df->rgodf[i].dwFlags;
-                strcpy(didoiA.tszName, "?????");
+
+                if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_AXIS)
+                    sprintf(didoiA.tszName, "Axis %d", axis);
+                else if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_POV)
+                    sprintf(didoiA.tszName, "POV %d", pov);
+                else if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_BUTTON)
+                    sprintf(didoiA.tszName, "Button %d", button);
+
                 CopyMemory(pdidoi, &didoiA, pdidoi->dwSize);
                 return DI_OK;
             }
+
+            if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_AXIS)
+                axis++;
+            else if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_POV)
+                pov++;
+            else if (DIDFT_GETTYPE(This->user_df->rgodf[i].dwType) & DIDFT_BUTTON)
+                button++;
         }
         break;
+    }
     case DIPH_BYID:
         FIXME("dwHow = DIPH_BYID not implemented\n");
         break;
@@ -1109,6 +1476,43 @@
     return DI_OK;
 }
 
+/******************************************************************************
+  *     GetDeviceInfo : get information about a device's identity
+  */
+HRESULT WINAPI JoystickAImpl_GetDeviceInfo(
+    LPDIRECTINPUTDEVICE8A iface,
+    LPDIDEVICEINSTANCEA pdidi)
+{
+    ICOM_THIS(JoystickImpl,iface);
+
+    TRACE("(%p,%p)\n", iface, pdidi);
+
+    if ((pdidi->dwSize != sizeof(DIDEVICEINSTANCE_DX3A)) &&
+        (pdidi->dwSize != sizeof(DIDEVICEINSTANCEA))) {
+        WARN("invalid parameter: pdidi->dwSize = %ld != %d or %d\n",
+             pdidi->dwSize, sizeof(DIDEVICEINSTANCE_DX3A),
+             sizeof(DIDEVICEINSTANCEA));
+        return DIERR_INVALIDPARAM;
+    }
+
+    /* Return joystick */
+    pdidi->guidInstance = GUID_Joystick;
+    pdidi->guidProduct = DInput_Wine_Joystick_GUID;
+    /* we only support traditional joysticks for now */
+    if (This->dinput->version >= 8)
+        pdidi->dwDevType = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_STANDARD << 8);
+    else
+        pdidi->dwDevType = DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8);
+    strcpy(pdidi->tszInstanceName, "Joystick");
+    strcpy(pdidi->tszProductName, This->name);
+    if (pdidi->dwSize > sizeof(DIDEVICEINSTANCE_DX3A)) {
+        pdidi->guidFFDriver = GUID_NULL;
+        pdidi->wUsagePage = 0;
+        pdidi->wUsage = 0;
+    }
+
+    return DI_OK;
+}
 
 static IDirectInputDevice8AVtbl JoystickAvt =
 {
@@ -1127,7 +1531,7 @@
 	JoystickAImpl_SetEventNotification,
 	IDirectInputDevice2AImpl_SetCooperativeLevel,
 	JoystickAImpl_GetObjectInfo,
-	IDirectInputDevice2AImpl_GetDeviceInfo,
+	JoystickAImpl_GetDeviceInfo,
 	IDirectInputDevice2AImpl_RunControlPanel,
 	IDirectInputDevice2AImpl_Initialize,
 	IDirectInputDevice2AImpl_CreateEffect,


More information about the wine-patches mailing list