2
0
Эх сурвалжийг харах

Add initial DirectInput 8 support

Fixes #232.
Camilla Berglund 9 жил өмнө
parent
commit
7cbdae1bed

+ 1 - 0
README.md

@@ -101,6 +101,7 @@ does not find Doxygen, the documentation will not be generated.
  - Removed `_GLFW_USE_OPENGL`, `_GLFW_USE_GLESV1` and `_GLFW_USE_GLESV2`
    configuration macros
  - [Win32] Added support for Windows 8.1 per-monitor DPI
+ - [Win32] Replaced winmm with XInput and DirectInput for joystick input
  - [Win32] Bugfix: Window creation would segfault if video mode setting required
                    the system to be restarted
  - [Win32] Bugfix: MinGW import library lacked the `lib` prefix

+ 10 - 0
src/win32_init.c

@@ -86,6 +86,13 @@ static GLFWbool loadLibraries(void)
     _glfw.win32.user32.ChangeWindowMessageFilterEx = (CHANGEWINDOWMESSAGEFILTEREX_T)
         GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
 
+    _glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
+    if (_glfw.win32.dinput8.instance)
+    {
+        _glfw.win32.dinput8.DirectInput8Create = (DIRECTINPUT8CREATE_T)
+            GetProcAddress(_glfw.win32.dinput8.instance, "DirectInput8Create");
+    }
+
     {
         int i;
         const char* names[] =
@@ -139,6 +146,9 @@ static void freeLibraries(void)
     if (_glfw.win32.xinput.instance)
         FreeLibrary(_glfw.win32.xinput.instance);
 
+    if (_glfw.win32.dinput8.instance)
+        FreeLibrary(_glfw.win32.dinput8.instance);
+
     if (_glfw.win32.winmm.instance)
         FreeLibrary(_glfw.win32.winmm.instance);
 

+ 540 - 62
src/win32_joystick.c

@@ -29,9 +29,104 @@
 
 #include <math.h>
 
-#define _GLFW_UPDATE_BUTTONS 1
-#define _GLFW_UPDATE_AXES    2
+#include <initguid.h>
 
+#define _GLFW_PRESENCE_ONLY 1
+#define _GLFW_UPDATE_STATE  2
+
+#define _GLFW_TYPE_AXIS     0
+#define _GLFW_TYPE_SLIDER   1
+#define _GLFW_TYPE_BUTTON   2
+#define _GLFW_TYPE_POV      3
+
+// Data produced with DirectInput device object enumeration
+//
+typedef struct _GLFWobjenumWin32
+{
+    IDirectInputDevice8W*   device;
+    _GLFWjoyobjectWin32*    objects;
+    int                     objectCount;
+    int                     axisCount;
+    int                     sliderCount;
+    int                     buttonCount;
+    int                     povCount;
+} _GLFWobjenumWin32;
+
+// Define only the necessary GUIDs (it's bad enough that we're exporting these)
+//
+DEFINE_GUID(IID_IDirectInput8W,0xbf798031,0x483a,0x4da2,0xaa,0x99,0x5d,0x64,0xed,0x36,0x97,0x00);
+DEFINE_GUID(GUID_XAxis,0xa36d02e0,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_YAxis,0xa36d02e1,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_ZAxis,0xa36d02e2,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_RxAxis,0xa36d02f4,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_RyAxis,0xa36d02f5,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_RzAxis,0xa36d02e3,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_Slider,0xa36d02e4,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_Button,0xa36d02f0,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+DEFINE_GUID(GUID_POV,0xa36d02f2,0xc9f3,0x11cf,0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00);
+
+// Object data array for our clone of c_dfDIJoystick
+// Generated with https://github.com/elmindreda/c_dfDIJoystick2
+//
+static DIOBJECTDATAFORMAT _glfwObjectDataFormats[] =
+{
+    { &GUID_XAxis,DIJOFS_X,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_YAxis,DIJOFS_Y,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_ZAxis,DIJOFS_Z,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_RxAxis,DIJOFS_RX,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_RyAxis,DIJOFS_RY,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_RzAxis,DIJOFS_RZ,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_Slider,DIJOFS_SLIDER(0),DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_Slider,DIJOFS_SLIDER(1),DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION },
+    { &GUID_POV,DIJOFS_POV(0),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { &GUID_POV,DIJOFS_POV(1),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { &GUID_POV,DIJOFS_POV(2),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { &GUID_POV,DIJOFS_POV(3),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(0),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(1),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(2),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(3),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(4),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(5),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(6),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(7),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(8),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(9),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(10),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(11),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(12),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(13),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(14),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(15),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(16),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(17),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(18),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(19),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(20),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(21),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(22),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(23),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(24),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(25),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(26),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(27),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(28),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(29),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(30),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+    { NULL,DIJOFS_BUTTON(31),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0 },
+};
+
+// Our clone of c_dfDIJoystick
+//
+static const DIDATAFORMAT _glfwDataFormat =
+{
+    sizeof(DIDATAFORMAT),
+    sizeof(DIOBJECTDATAFORMAT),
+    DIDFT_ABSAXIS,
+    sizeof(DIJOYSTATE),
+    sizeof(_glfwObjectDataFormats) / sizeof(DIOBJECTDATAFORMAT),
+    _glfwObjectDataFormats
+};
 
 // Returns a description fitting the specified XInput capabilities
 //
@@ -63,10 +158,294 @@ static const char* getDeviceDescription(const XINPUT_CAPABILITIES* xic)
     return "Unknown XInput Device";
 }
 
+// Lexically compare device objects
+//
+static int compareJoystickObjects(const void* first, const void* second)
+{
+    const _GLFWjoyobjectWin32* fo = first;
+    const _GLFWjoyobjectWin32* so = second;
+
+    if (fo->type != so->type)
+        return fo->type - so->type;
+
+    return fo->offset - so->offset;
+}
+
+// Checks whether the specified device supports XInput
+// Technique from FDInputJoystickManager::IsXInputDeviceFast in ZDoom
+//
+static GLFWbool supportsXInput(const GUID* guid)
+{
+    UINT i, count;
+    RAWINPUTDEVICELIST* ridl;
+    GLFWbool result = GLFW_FALSE;
+
+    if (GetRawInputDeviceList(NULL, &count, sizeof(RAWINPUTDEVICELIST)) != 0)
+        return GLFW_FALSE;
+
+    ridl = calloc(count, sizeof(RAWINPUTDEVICELIST));
+
+    if (GetRawInputDeviceList(ridl, &count, sizeof(RAWINPUTDEVICELIST)) == -1)
+    {
+        free(ridl);
+        return GLFW_FALSE;
+    }
+
+    for (i = 0;  i < count;  i++)
+    {
+        RID_DEVICE_INFO rdi;
+        char name[256];
+        UINT size;
+
+        if (ridl[i].dwType != RIM_TYPEHID)
+            continue;
+
+        ZeroMemory(&rdi, sizeof(rdi));
+        rdi.cbSize = sizeof(rdi);
+        size = sizeof(rdi);
+
+        if ((INT) GetRawInputDeviceInfoA(ridl[i].hDevice,
+                                         RIDI_DEVICEINFO,
+                                         &rdi, &size) == -1)
+        {
+            continue;
+        }
+
+        if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) != guid->Data1)
+            continue;
+
+        memset(name, 0, sizeof(name));
+        size = sizeof(name);
+
+        if ((INT) GetRawInputDeviceInfoA(ridl[i].hDevice,
+                                         RIDI_DEVICENAME,
+                                         name, &size) == -1)
+        {
+            break;
+        }
+
+        name[sizeof(name) - 1] = '\0';
+        if (strstr(name, "IG_"))
+        {
+            result = GLFW_TRUE;
+            break;
+        }
+    }
+
+    free(ridl);
+    return result;
+}
+
+// Frees all resources associated with the specified joystick
+//
+static void closeJoystick(_GLFWjoystickWin32* js)
+{
+    if (js->device)
+    {
+        IDirectInputDevice8_Unacquire(js->device);
+        IDirectInputDevice8_Release(js->device);
+    }
+
+    free(js->name);
+    free(js->axes);
+    free(js->buttons);
+    free(js->objects);
+    memset(js, 0, sizeof(_GLFWjoystickWin32));
+
+    _glfwInputJoystickChange((int) (js - _glfw.win32_js), GLFW_DISCONNECTED);
+}
+
+// DirectInput device object enumeration callback
+// Insights gleaned from SDL2
+//
+static BOOL CALLBACK deviceObjectCallback(const DIDEVICEOBJECTINSTANCEW* doi,
+                                          void* user)
+{
+    _GLFWobjenumWin32* data = user;
+    _GLFWjoyobjectWin32* object = data->objects + data->objectCount;
+
+    if (DIDFT_GETTYPE(doi->dwType) & DIDFT_AXIS)
+    {
+        DIPROPRANGE dipr;
+
+        if (memcmp(&doi->guidType, &GUID_Slider, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_SLIDER(data->sliderCount);
+        else if (memcmp(&doi->guidType, &GUID_XAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_X;
+        else if (memcmp(&doi->guidType, &GUID_YAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_Y;
+        else if (memcmp(&doi->guidType, &GUID_ZAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_Z;
+        else if (memcmp(&doi->guidType, &GUID_RxAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_RX;
+        else if (memcmp(&doi->guidType, &GUID_RyAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_RY;
+        else if (memcmp(&doi->guidType, &GUID_RzAxis, sizeof(GUID)) == 0)
+            object->offset = DIJOFS_RZ;
+        else
+            return DIENUM_CONTINUE;
+
+        ZeroMemory(&dipr, sizeof(dipr));
+        dipr.diph.dwSize = sizeof(dipr);
+        dipr.diph.dwHeaderSize = sizeof(dipr.diph);
+        dipr.diph.dwObj = doi->dwType;
+        dipr.diph.dwHow = DIPH_BYID;
+        dipr.lMin = -32768;
+        dipr.lMax =  32767;
+
+        if (FAILED(IDirectInputDevice8_SetProperty(data->device,
+                                                   DIPROP_RANGE,
+                                                   &dipr.diph)))
+        {
+            return DIENUM_CONTINUE;
+        }
+
+        if (memcmp(&doi->guidType, &GUID_Slider, sizeof(GUID)) == 0)
+        {
+            object->type = _GLFW_TYPE_SLIDER;
+            data->sliderCount++;
+        }
+        else
+        {
+            object->type = _GLFW_TYPE_AXIS;
+            data->axisCount++;
+        }
+    }
+    else if (DIDFT_GETTYPE(doi->dwType) & DIDFT_BUTTON)
+    {
+        object->offset = DIJOFS_BUTTON(data->buttonCount);
+        object->type = _GLFW_TYPE_BUTTON;
+        data->buttonCount++;
+    }
+    else if (DIDFT_GETTYPE(doi->dwType) & DIDFT_POV)
+    {
+        object->offset = DIJOFS_POV(data->povCount);
+        object->type = _GLFW_TYPE_POV;
+        data->povCount++;
+    }
+
+    data->objectCount++;
+    return DIENUM_CONTINUE;
+}
+
+// DirectInput device enumeration callback
+//
+static BOOL CALLBACK deviceCallback(const DIDEVICEINSTANCE* di, void* user)
+{
+    int joy = 0;
+    DIDEVCAPS dc;
+    DIPROPDWORD dipd;
+    IDirectInputDevice8* device;
+    _GLFWobjenumWin32 data;
+    _GLFWjoystickWin32* js;
+
+    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
+    {
+        if (memcmp(&_glfw.win32_js[joy].guid, &di->guidInstance, sizeof(GUID)) == 0)
+            return DIENUM_CONTINUE;
+    }
+
+    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
+    {
+        if (!_glfw.win32_js[joy].present)
+            break;
+    }
+
+    if (joy > GLFW_JOYSTICK_LAST)
+        return DIENUM_STOP;
+
+    if (supportsXInput(&di->guidProduct))
+        return DIENUM_CONTINUE;
+
+    if (FAILED(IDirectInput8_CreateDevice(_glfw.win32.dinput8.api,
+                                          &di->guidInstance,
+                                          &device,
+                                          NULL)))
+    {
+        _glfwInputError(GLFW_PLATFORM_ERROR, "DI: Failed to create device");
+        return DIENUM_CONTINUE;
+    }
+
+    if (FAILED(IDirectInputDevice8_SetDataFormat(device, &_glfwDataFormat)))
+    {
+        _glfwInputError(GLFW_PLATFORM_ERROR,
+                        "DI: Failed to set device data format");
+
+        IDirectInputDevice8_Release(device);
+        return DIENUM_CONTINUE;
+    }
+
+    ZeroMemory(&dc, sizeof(dc));
+    dc.dwSize = sizeof(dc);
+
+    if (FAILED(IDirectInputDevice8_GetCapabilities(device, &dc)))
+    {
+        _glfwInputError(GLFW_PLATFORM_ERROR,
+                        "DI: Failed to query device capabilities");
+
+        IDirectInputDevice8_Release(device);
+        return DIENUM_CONTINUE;
+    }
+
+    ZeroMemory(&dipd, sizeof(dipd));
+    dipd.diph.dwSize = sizeof(dipd);
+    dipd.diph.dwHeaderSize = sizeof(dipd.diph);
+    dipd.diph.dwHow = DIPH_DEVICE;
+    dipd.dwData = DIPROPAXISMODE_ABS;
+
+    if (FAILED(IDirectInputDevice8_SetProperty(device,
+                                               DIPROP_AXISMODE,
+                                               &dipd.diph)))
+    {
+        _glfwInputError(GLFW_PLATFORM_ERROR,
+                        "DI: Failed to set device axis mode");
+
+        IDirectInputDevice8_Release(device);
+        return DIENUM_CONTINUE;
+    }
+
+    memset(&data, 0, sizeof(data));
+    data.device = device;
+    data.objects = calloc(dc.dwAxes + dc.dwButtons + dc.dwPOVs,
+                          sizeof(_GLFWjoyobjectWin32));
+
+    if (FAILED(IDirectInputDevice8_EnumObjects(device,
+                                               deviceObjectCallback,
+                                               &data,
+                                               DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV)))
+    {
+        _glfwInputError(GLFW_PLATFORM_ERROR,
+                        "DI: Failed to enumerate device objects");
+
+        IDirectInputDevice8_Release(device);
+        free(data.objects);
+        return DIENUM_CONTINUE;
+    }
+
+    qsort(data.objects, data.objectCount,
+          sizeof(_GLFWjoyobjectWin32),
+          compareJoystickObjects);
+
+    js = _glfw.win32_js + joy;
+    js->device = device;
+    js->guid = di->guidInstance;
+    js->axisCount = data.axisCount + data.sliderCount;
+    js->axes = calloc(js->axisCount, sizeof(float));
+    js->buttonCount += data.buttonCount + data.povCount * 4;
+    js->buttons = calloc(js->buttonCount, 1);
+    js->objects = data.objects;
+    js->objectCount = data.objectCount;
+    js->name = _glfwCreateUTF8FromWideStringWin32(di->tszInstanceName);
+    js->present = GLFW_TRUE;
+
+    _glfwInputJoystickChange(joy, GLFW_CONNECTED);
+    return DIENUM_CONTINUE;
+}
+
 // Attempt to open the specified joystick device
 // TODO: Pack state arrays for non-gamepad devices
 //
-static GLFWbool openJoystickDevice(DWORD index)
+static GLFWbool openXinputDevice(DWORD index)
 {
     int joy;
     XINPUT_CAPABILITIES xic;
@@ -74,8 +453,12 @@ static GLFWbool openJoystickDevice(DWORD index)
 
     for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
     {
-        if (_glfw.win32_js[joy].present && _glfw.win32_js[joy].index == index)
+        if (_glfw.win32_js[joy].present &&
+            _glfw.win32_js[joy].device == NULL &&
+            _glfw.win32_js[joy].index == index)
+        {
             return GLFW_FALSE;
+        }
     }
 
     for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
@@ -92,7 +475,9 @@ static GLFWbool openJoystickDevice(DWORD index)
 
     js = _glfw.win32_js + joy;
     js->axisCount = 6;
+    js->axes = calloc(js->axisCount, sizeof(float));
     js->buttonCount = 14;
+    js->buttons = calloc(js->buttonCount, 1);
     js->present = GLFW_TRUE;
     js->name = strdup(getDeviceDescription(&xic));
     js->index = index;
@@ -104,32 +489,120 @@ static GLFWbool openJoystickDevice(DWORD index)
 
 // Polls for and processes events the specified joystick
 //
-static GLFWbool pollJoystickEvents(_GLFWjoystickWin32* js, int flags)
+static GLFWbool pollJoystickState(_GLFWjoystickWin32* js, int mode)
 {
-    XINPUT_STATE xis;
-    DWORD result;
-
-    if (!_glfw.win32.xinput.instance)
-        return GLFW_FALSE;
-
     if (!js->present)
         return GLFW_FALSE;
 
-    result = _glfw_XInputGetState(js->index, &xis);
-    if (result != ERROR_SUCCESS)
+    if (js->device)
     {
-        if (result == ERROR_DEVICE_NOT_CONNECTED)
+        int i, j, ai = 0, bi = 0;
+        HRESULT result;
+        DIJOYSTATE state;
+
+        IDirectInputDevice8_Poll(js->device);
+        result = IDirectInputDevice8_GetDeviceState(js->device,
+                                                    sizeof(state),
+                                                    &state);
+        if (result == DIERR_NOTACQUIRED || result == DIERR_INPUTLOST)
         {
-            free(js->name);
-            memset(js, 0, sizeof(_GLFWjoystickWin32));
-            _glfwInputJoystickChange((int) (js - _glfw.win32_js), GLFW_DISCONNECTED);
+            IDirectInputDevice8_Acquire(js->device);
+            IDirectInputDevice8_Poll(js->device);
+            result = IDirectInputDevice8_GetDeviceState(js->device,
+                                                        sizeof(state),
+                                                        &state);
         }
 
-        return GLFW_FALSE;
-    }
+        if (FAILED(result))
+        {
+            closeJoystick(js);
+            return GLFW_FALSE;
+        }
+
+        if (mode == _GLFW_PRESENCE_ONLY)
+            return GLFW_TRUE;
 
-    if (flags & _GLFW_UPDATE_AXES)
+        for (i = 0;  i < js->objectCount;  i++)
+        {
+            const void* data = (char*) &state + js->objects[i].offset;
+
+            switch (js->objects[i].type)
+            {
+                case _GLFW_TYPE_AXIS:
+                case _GLFW_TYPE_SLIDER:
+                {
+                    js->axes[ai++] = (*((LONG*) data) + 0.5f) / 32767.5f;
+                    break;
+                }
+
+                case _GLFW_TYPE_BUTTON:
+                {
+                    if (*((BYTE*) data) & 0x80)
+                        js->buttons[bi++] = GLFW_PRESS;
+                    else
+                        js->buttons[bi++] = GLFW_RELEASE;
+
+                    break;
+                }
+
+                case _GLFW_TYPE_POV:
+                {
+                    const int directions[9] = { 1, 3, 2, 6, 4, 12, 8, 9, 0 };
+                    // Screams of horror are appropriate at this point
+                    int value = LOWORD(*(DWORD*) data) / (45 * DI_DEGREES);
+                    if (value < 0 || value > 8)
+                        value = 8;
+
+                    for (j = 0;  j < 4;  j++)
+                    {
+                        if (directions[value] & (1 << j))
+                            js->buttons[bi++] = GLFW_PRESS;
+                        else
+                            js->buttons[bi++] = GLFW_RELEASE;
+                    }
+
+                    break;
+                }
+            }
+        }
+
+        return GLFW_TRUE;
+    }
+    else
     {
+        int i;
+        DWORD result;
+        XINPUT_STATE xis;
+        const WORD buttons[14] =
+        {
+            XINPUT_GAMEPAD_A,
+            XINPUT_GAMEPAD_B,
+            XINPUT_GAMEPAD_X,
+            XINPUT_GAMEPAD_Y,
+            XINPUT_GAMEPAD_LEFT_SHOULDER,
+            XINPUT_GAMEPAD_RIGHT_SHOULDER,
+            XINPUT_GAMEPAD_BACK,
+            XINPUT_GAMEPAD_START,
+            XINPUT_GAMEPAD_LEFT_THUMB,
+            XINPUT_GAMEPAD_RIGHT_THUMB,
+            XINPUT_GAMEPAD_DPAD_UP,
+            XINPUT_GAMEPAD_DPAD_RIGHT,
+            XINPUT_GAMEPAD_DPAD_DOWN,
+            XINPUT_GAMEPAD_DPAD_LEFT
+        };
+
+        result = _glfw_XInputGetState(js->index, &xis);
+        if (result != ERROR_SUCCESS)
+        {
+            if (result == ERROR_DEVICE_NOT_CONNECTED)
+                closeJoystick(js);
+
+            return GLFW_FALSE;
+        }
+
+        if (mode == _GLFW_PRESENCE_ONLY)
+            return GLFW_TRUE;
+
         if (sqrtf((float) (xis.Gamepad.sThumbLX * xis.Gamepad.sThumbLX +
                            xis.Gamepad.sThumbLY * xis.Gamepad.sThumbLY)) >
             (float) XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
@@ -165,34 +638,12 @@ static GLFWbool pollJoystickEvents(_GLFWjoystickWin32* js, int flags)
             js->axes[5] = xis.Gamepad.bRightTrigger / 127.5f - 1.f;
         else
             js->axes[5] = -1.f;
-    }
-
-    if (flags & _GLFW_UPDATE_BUTTONS)
-    {
-        int i;
-        const WORD buttons[14] =
-        {
-            XINPUT_GAMEPAD_A,
-            XINPUT_GAMEPAD_B,
-            XINPUT_GAMEPAD_X,
-            XINPUT_GAMEPAD_Y,
-            XINPUT_GAMEPAD_LEFT_SHOULDER,
-            XINPUT_GAMEPAD_RIGHT_SHOULDER,
-            XINPUT_GAMEPAD_BACK,
-            XINPUT_GAMEPAD_START,
-            XINPUT_GAMEPAD_LEFT_THUMB,
-            XINPUT_GAMEPAD_RIGHT_THUMB,
-            XINPUT_GAMEPAD_DPAD_UP,
-            XINPUT_GAMEPAD_DPAD_RIGHT,
-            XINPUT_GAMEPAD_DPAD_DOWN,
-            XINPUT_GAMEPAD_DPAD_LEFT
-        };
 
         for (i = 0;  i < 14;  i++)
             js->buttons[i] = (xis.Gamepad.wButtons & buttons[i]) ? 1 : 0;
-    }
 
-    return GLFW_TRUE;
+        return GLFW_TRUE;
+    }
 }
 
 
@@ -204,6 +655,19 @@ static GLFWbool pollJoystickEvents(_GLFWjoystickWin32* js, int flags)
 //
 void _glfwInitJoysticksWin32(void)
 {
+    if (_glfw.win32.dinput8.instance)
+    {
+        if (FAILED(_glfw_DirectInput8Create(GetModuleHandle(NULL),
+                                            DIRECTINPUT_VERSION,
+                                            &IID_IDirectInput8W,
+                                            (void**) &_glfw.win32.dinput8.api,
+                                            NULL)))
+        {
+            _glfwInputError(GLFW_PLATFORM_ERROR,
+                            "DI: Failed to create interface");
+        }
+    }
+
     _glfwDetectJoystickConnectionWin32();
 }
 
@@ -214,33 +678,47 @@ void _glfwTerminateJoysticksWin32(void)
     int joy;
 
     for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
-        free(_glfw.win32_js[joy].name);
+        closeJoystick(_glfw.win32_js + joy);
+
+    if (_glfw.win32.dinput8.api)
+        IDirectInput8_Release(_glfw.win32.dinput8.api);
 }
 
-// Looks for new joysticks
+// Checks for new joysticks after DBT_DEVICEARRIVAL
 //
 void _glfwDetectJoystickConnectionWin32(void)
 {
-    DWORD i;
+    if (_glfw.win32.xinput.instance)
+    {
+        DWORD i;
 
-    if (!_glfw.win32.xinput.instance)
-        return;
+        for (i = 0;  i < XUSER_MAX_COUNT;  i++)
+            openXinputDevice(i);
+    }
 
-    for (i = 0;  i < XUSER_MAX_COUNT;  i++)
-        openJoystickDevice(i);
+    if (_glfw.win32.dinput8.api)
+    {
+        if (FAILED(IDirectInput8_EnumDevices(_glfw.win32.dinput8.api,
+                                             DI8DEVCLASS_GAMECTRL,
+                                             deviceCallback,
+                                             NULL,
+                                             DIEDFL_ALLDEVICES)))
+        {
+            _glfwInputError(GLFW_PLATFORM_ERROR,
+                            "Failed to enumerate DirectInput8 devices");
+            return;
+        }
+    }
 }
 
-// Checks if any current joystick has been disconnected
+// Checks for joystick disconnection after DBT_DEVICEREMOVECOMPLETE
 //
 void _glfwDetectJoystickDisconnectionWin32(void)
 {
-    DWORD i;
-
-    if (!_glfw.win32.xinput.instance)
-        return;
+    int joy;
 
-    for (i = 0;  i < XUSER_MAX_COUNT;  i++)
-        pollJoystickEvents(_glfw.win32_js + i, 0);
+    for (joy = GLFW_JOYSTICK_1;  joy <= GLFW_JOYSTICK_LAST;  joy++)
+        pollJoystickState(_glfw.win32_js + joy, _GLFW_PRESENCE_ONLY);
 }
 
 
@@ -251,13 +729,13 @@ void _glfwDetectJoystickDisconnectionWin32(void)
 int _glfwPlatformJoystickPresent(int joy)
 {
     _GLFWjoystickWin32* js = _glfw.win32_js + joy;
-    return pollJoystickEvents(js, 0);
+    return pollJoystickState(js, _GLFW_PRESENCE_ONLY);
 }
 
 const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
 {
     _GLFWjoystickWin32* js = _glfw.win32_js + joy;
-    if (!pollJoystickEvents(js, _GLFW_UPDATE_AXES))
+    if (!pollJoystickState(js, _GLFW_UPDATE_STATE))
         return NULL;
 
     *count = js->axisCount;
@@ -267,7 +745,7 @@ const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
 const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count)
 {
     _GLFWjoystickWin32* js = _glfw.win32_js + joy;
-    if (!pollJoystickEvents(js, _GLFW_UPDATE_BUTTONS))
+    if (!pollJoystickState(js, _GLFW_UPDATE_STATE))
         return NULL;
 
     *count = js->buttonCount;
@@ -277,7 +755,7 @@ const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count)
 const char* _glfwPlatformGetJoystickName(int joy)
 {
     _GLFWjoystickWin32* js = _glfw.win32_js + joy;
-    if (!pollJoystickEvents(js, 0))
+    if (!pollJoystickState(js, _GLFW_PRESENCE_ONLY))
         return NULL;
 
     return js->name;

+ 19 - 7
src/win32_joystick.h

@@ -30,17 +30,29 @@
 #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE \
     _GLFWjoystickWin32 win32_js[GLFW_JOYSTICK_LAST + 1]
 
+// Spoo
+//
+typedef struct _GLFWjoyobjectWin32
+{
+    int                     offset;
+    int                     type;
+} _GLFWjoyobjectWin32;
+
 // Win32-specific per-joystick data
 //
 typedef struct _GLFWjoystickWin32
 {
-    GLFWbool        present;
-    float           axes[6];
-    int             axisCount;
-    unsigned char   buttons[14];
-    int             buttonCount;
-    char*           name;
-    DWORD           index;
+    GLFWbool                present;
+    float*                  axes;
+    int                     axisCount;
+    unsigned char*          buttons;
+    int                     buttonCount;
+    _GLFWjoyobjectWin32*    objects;
+    int                     objectCount;
+    char*                   name;
+    IDirectInputDevice8W*   device;
+    DWORD                   index;
+    GUID                    guid;
 } _GLFWjoystickWin32;
 
 

+ 21 - 6
src/win32_platform.h

@@ -61,8 +61,12 @@
  #define _WIN32_WINNT 0x0501
 #endif
 
+// GLFW uses DirectInput8 interfaces
+#define DIRECTINPUT_VERSION 0x0800
+
 #include <windows.h>
 #include <mmsystem.h>
+#include <dinput.h>
 #include <xinput.h>
 #include <dbt.h>
 
@@ -71,7 +75,7 @@
  #define strdup _strdup
 #endif
 
-// HACK: Define macros that some older windows.h variants don't
+// HACK: Define macros that some windows.h variants don't
 #ifndef WM_MOUSEHWHEEL
  #define WM_MOUSEHWHEEL 0x020E
 #endif
@@ -121,7 +125,7 @@ typedef enum PROCESS_DPI_AWARENESS
 } PROCESS_DPI_AWARENESS;
 #endif /*DPI_ENUMS_DECLARED*/
 
-// HACK: Define macros that some older xinput.h variants don't
+// HACK: Define macros that some xinput.h variants don't
 #ifndef XINPUT_CAPS_WIRELESS
  #define XINPUT_CAPS_WIRELESS 0x0002
 #endif
@@ -147,6 +151,11 @@ typedef enum PROCESS_DPI_AWARENESS
  #define XINPUT_DEVSUBTYPE_ARCADE_PAD 0x13
 #endif
 
+// HACK: Define macros that some dinput.h variants don't
+#ifndef DIDFT_OPTIONAL
+ #define DIDFT_OPTIONAL	0x80000000
+#endif
+
 // winmm.dll function pointer typedefs
 typedef DWORD (WINAPI * TIMEGETTIME_T)(void);
 #define _glfw_timeGetTime _glfw.win32.winmm.timeGetTime
@@ -157,6 +166,10 @@ typedef DWORD (WINAPI * XINPUTGETSTATE_T)(DWORD,XINPUT_STATE*);
 #define _glfw_XInputGetCapabilities _glfw.win32.xinput.XInputGetCapabilities
 #define _glfw_XInputGetState _glfw.win32.xinput.XInputGetState
 
+// dinput8.dll function pointer typedefs
+typedef HRESULT (WINAPI * DIRECTINPUT8CREATE_T)(HINSTANCE,DWORD,REFIID,LPVOID*,LPUNKNOWN);
+#define _glfw_DirectInput8Create _glfw.win32.dinput8.DirectInput8Create
+
 // user32.dll function pointer typedefs
 typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void);
 typedef BOOL (WINAPI * CHANGEWINDOWMESSAGEFILTEREX_T)(HWND,UINT,DWORD,PCHANGEFILTERSTRUCT);
@@ -241,13 +254,17 @@ typedef struct _GLFWlibraryWin32
     short int           publicKeys[512];
     short int           nativeKeys[GLFW_KEY_LAST + 1];
 
-    // winmm.dll
     struct {
         HINSTANCE       instance;
         TIMEGETTIME_T   timeGetTime;
     } winmm;
 
-    // user32.dll
+    struct {
+        HINSTANCE            instance;
+        DIRECTINPUT8CREATE_T DirectInput8Create;
+        IDirectInput8W*      api;
+    } dinput8;
+
     struct {
         HINSTANCE               instance;
         XINPUTGETCAPABILITIES_T XInputGetCapabilities;
@@ -260,14 +277,12 @@ typedef struct _GLFWlibraryWin32
         CHANGEWINDOWMESSAGEFILTEREX_T ChangeWindowMessageFilterEx;
     } user32;
 
-    // dwmapi.dll
     struct {
         HINSTANCE       instance;
         DWMISCOMPOSITIONENABLED_T DwmIsCompositionEnabled;
         DWMFLUSH_T      DwmFlush;
     } dwmapi;
 
-    // shcore.dll
     struct {
         HINSTANCE       instance;
         SETPROCESSDPIAWARENESS_T SetProcessDpiAwareness;