1 /// Translated from C to D
2 module glfw3.win32_joystick;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 //========================================================================
6 // GLFW 3.3 Win32 - www.glfw.org
7 //------------------------------------------------------------------------
8 // Copyright (c) 2002-2006 Marcus Geelnard
9 // Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org>
10 //
11 // This software is provided 'as-is', without any express or implied
12 // warranty. In no event will the authors be held liable for any damages
13 // arising from the use of this software.
14 //
15 // Permission is granted to anyone to use this software for any purpose,
16 // including commercial applications, and to alter it and redistribute it
17 // freely, subject to the following restrictions:
18 //
19 // 1. The origin of this software must not be misrepresented; you must not
20 //    claim that you wrote the original software. If you use this software
21 //    in a product, an acknowledgment in the product documentation would
22 //    be appreciated but is not required.
23 //
24 // 2. Altered source versions must be plainly marked as such, and must not
25 //    be misrepresented as being the original software.
26 //
27 // 3. This notice may not be removed or altered from any source
28 //    distribution.
29 //
30 //========================================================================
31 // Please use C89 style variable declarations in this file because VS 2010
32 //========================================================================
33 
34 import glfw3.internal;
35 import core.stdc.stdlib; // free
36 import core.stdc.string; // memset
37 
38 package:
39 
40 // header
41 mixin template _GLFW_PLATFORM_JOYSTICK_STATE() {_GLFWjoystickWin32 win32;}
42 mixin template _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE() {int dummyLibraryJoystick;}
43 
44 enum _GLFW_PLATFORM_MAPPING_NAME = "Windows";
45 
46 // Joystick element (axis, button or slider)
47 //
48 struct _GLFWjoyobjectWin32 {
49     int offset;
50     int type;
51 }
52 
53 // Win32-specific per-joystick data
54 //
55 struct _GLFWjoystickWin32 {
56     _GLFWjoyobjectWin32* objects;
57     int objectCount;
58     IDirectInputDevice8 device;
59     DWORD index;
60     GUID guid;
61 }
62 
63 import core.stdc.stdio;
64 import core.stdc.math;
65 
66 enum _GLFW_TYPE_AXIS =     0;
67 enum _GLFW_TYPE_SLIDER =   1;
68 enum _GLFW_TYPE_BUTTON =   2;
69 enum _GLFW_TYPE_POV =      3;
70 
71 // Data produced with DirectInput device object enumeration
72 //
73 struct _GLFWobjenumWin32 {
74     IDirectInputDevice8 device;
75     _GLFWjoyobjectWin32* objects;
76     int objectCount;
77     int axisCount;
78     int sliderCount;
79     int buttonCount;
80     int povCount;
81 }
82 
83 // Define local copies of the necessary GUIDs
84 //
85 private immutable GUID _glfw_IID_IDirectInput8W = GUID(0xbf798031,0x483a,0x4da2,[0xaa,0x99,0x5d,0x64,0xed,0x36,0x97,0x00]);
86 private immutable GUID _glfw_GUID_XAxis = GUID(0xa36d02e0,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
87 private immutable GUID _glfw_GUID_YAxis = GUID(0xa36d02e1,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
88 private immutable GUID _glfw_GUID_ZAxis = GUID(0xa36d02e2,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
89 private immutable GUID _glfw_GUID_RxAxis = GUID(0xa36d02f4,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
90 private immutable GUID _glfw_GUID_RyAxis = GUID(0xa36d02f5,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
91 private immutable GUID _glfw_GUID_RzAxis = GUID(0xa36d02e3,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
92 private immutable GUID _glfw_GUID_Slider = GUID(0xa36d02e4,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
93 private immutable GUID _glfw_GUID_POV = GUID(0xa36d02f2,0xc9f3,0x11cf,[0xbf,0xc7,0x44,0x45,0x53,0x54,0x00,0x00]);
94 
95 alias IID_IDirectInput8W = _glfw_IID_IDirectInput8W;
96 alias GUID_XAxis = _glfw_GUID_XAxis;
97 alias GUID_YAxis = _glfw_GUID_YAxis;
98 alias GUID_ZAxis = _glfw_GUID_ZAxis;
99 alias GUID_RxAxis = _glfw_GUID_RxAxis;
100 alias GUID_RyAxis = _glfw_GUID_RyAxis;
101 alias GUID_RzAxis = _glfw_GUID_RzAxis;
102 alias GUID_Slider = _glfw_GUID_Slider;
103 alias GUID_POV = _glfw_GUID_POV;
104 
105 // Object data array for our clone of c_dfDIJoystick
106 // Generated with https://github.com/elmindreda/c_dfDIJoystick2
107 //
108 private DIOBJECTDATAFORMAT[44] _glfwObjectDataFormats = [
109     DIOBJECTDATAFORMAT(&GUID_XAxis,DIJOFS_X,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
110     DIOBJECTDATAFORMAT(&GUID_YAxis,DIJOFS_Y,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
111     DIOBJECTDATAFORMAT(&GUID_ZAxis,DIJOFS_Z,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
112     DIOBJECTDATAFORMAT(&GUID_RxAxis,DIJOFS_RX,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
113     DIOBJECTDATAFORMAT(&GUID_RyAxis,DIJOFS_RY,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
114     DIOBJECTDATAFORMAT(&GUID_RzAxis,DIJOFS_RZ,DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
115     DIOBJECTDATAFORMAT(&GUID_Slider,DIJOFS_SLIDER(0),DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
116     DIOBJECTDATAFORMAT(&GUID_Slider,DIJOFS_SLIDER(1),DIDFT_AXIS|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,DIDOI_ASPECTPOSITION),
117     DIOBJECTDATAFORMAT(&GUID_POV,DIJOFS_POV(0),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
118     DIOBJECTDATAFORMAT(&GUID_POV,DIJOFS_POV(1),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
119     DIOBJECTDATAFORMAT(&GUID_POV,DIJOFS_POV(2),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
120     DIOBJECTDATAFORMAT(&GUID_POV,DIJOFS_POV(3),DIDFT_POV|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
121     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(0),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
122     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(1),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
123     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(2),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
124     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(3),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
125     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(4),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
126     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(5),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
127     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(6),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
128     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(7),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
129     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(8),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
130     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(9),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
131     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(10),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
132     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(11),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
133     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(12),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
134     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(13),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
135     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(14),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
136     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(15),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
137     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(16),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
138     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(17),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
139     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(18),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
140     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(19),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
141     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(20),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
142     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(21),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
143     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(22),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
144     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(23),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
145     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(24),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
146     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(25),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
147     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(26),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
148     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(27),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
149     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(28),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
150     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(29),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
151     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(30),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
152     DIOBJECTDATAFORMAT(null,DIJOFS_BUTTON(31),DIDFT_BUTTON|DIDFT_OPTIONAL|DIDFT_ANYINSTANCE,0),
153 ];
154 
155 // Our clone of c_dfDIJoystick
156 //
157 private const DIDATAFORMAT _glfwDataFormat = DIDATAFORMAT(
158     DIDATAFORMAT.sizeof,
159     DIOBJECTDATAFORMAT.sizeof,
160     DIDFT_ABSAXIS,
161     DIJOYSTATE.sizeof,
162     _glfwObjectDataFormats.length, //_glfwObjectDataFormats.sizeof / DIOBJECTDATAFORMAT.sizeof,
163     _glfwObjectDataFormats.ptr
164 );
165 
166 // Returns a description fitting the specified XInput capabilities
167 //
168 private const(char)* getDeviceDescription(const(XINPUT_CAPABILITIES)* xic) {
169     switch (xic.SubType)
170     {
171         case XINPUT_DEVSUBTYPE_WHEEL:
172             return "XInput Wheel";
173         case XINPUT_DEVSUBTYPE_ARCADE_STICK:
174             return "XInput Arcade Stick";
175         case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
176             return "XInput Flight Stick";
177         case XINPUT_DEVSUBTYPE_DANCE_PAD:
178             return "XInput Dance Pad";
179         case XINPUT_DEVSUBTYPE_GUITAR:
180             return "XInput Guitar";
181         case XINPUT_DEVSUBTYPE_DRUM_KIT:
182             return "XInput Drum Kit";
183         case XINPUT_DEVSUBTYPE_GAMEPAD:
184         {
185             if (xic.Flags & XINPUT_CAPS_WIRELESS)
186                 return "Wireless Xbox Controller";
187             else
188                 return "Xbox Controller";
189         }
190         default: break;
191     }
192 
193     return "Unknown XInput Device";
194 }
195 
196 // Lexically compare device objects
197 //
198 private int compareJoystickObjects(const(void)* first, const(void)* second) {
199     auto fo = cast(const(_GLFWjoyobjectWin32)*) first;
200     auto so = cast(const(_GLFWjoyobjectWin32)*) second;
201 
202     if (fo.type != so.type)
203         return fo.type - so.type;
204 
205     return fo.offset - so.offset;
206 }
207 
208 // Checks whether the specified device supports XInput
209 // Technique from FDInputJoystickManager::IsXInputDeviceFast in ZDoom
210 //
211 private GLFWbool supportsXInput(const(GUID)* guid) {
212     UINT i;UINT count = 0;
213     RAWINPUTDEVICELIST* ridl;
214     GLFWbool result = GLFW_FALSE;
215 
216     if (GetRawInputDeviceList(null, &count, RAWINPUTDEVICELIST.sizeof) != 0)
217         return GLFW_FALSE;
218 
219     ridl = cast(RAWINPUTDEVICELIST*) calloc(count, RAWINPUTDEVICELIST.sizeof);
220 
221     if (GetRawInputDeviceList(ridl, &count, RAWINPUTDEVICELIST.sizeof) == cast(UINT) -1)
222     {
223         free(ridl);
224         return GLFW_FALSE;
225     }
226 
227     for (i = 0;  i < count;  i++)
228     {
229         RID_DEVICE_INFO rdi;
230         char[256] name;
231         UINT size;
232 
233         if (ridl[i].dwType != RIM_TYPEHID)
234             continue;
235 
236         memset(&rdi, 0, typeof(rdi).sizeof); //ZeroMemory(&rdi, typeof(rdi).sizeof);
237         rdi.cbSize = typeof(rdi).sizeof;
238         size = typeof(rdi).sizeof;
239 
240         if (cast(INT) GetRawInputDeviceInfoA(ridl[i].hDevice,
241                                          RIDI_DEVICEINFO,
242                                          &rdi, &size) == -1)
243         {
244             continue;
245         }
246 
247         if (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) != cast(LONG) guid.Data1)
248             continue;
249 
250         memset(name.ptr, 0, typeof(name).sizeof);
251         size = typeof(name).sizeof;
252 
253         if (cast(INT) GetRawInputDeviceInfoA(ridl[i].hDevice,
254                                          RIDI_DEVICENAME,
255                                          name.ptr, &size) == -1)
256         {
257             break;
258         }
259 
260         name[name.length - 1] = '\0';
261         if (strstr(name.ptr, "IG_"))
262         {
263             result = GLFW_TRUE;
264             break;
265         }
266     }
267 
268     free(ridl);
269     return result;
270 }
271 
272 // Frees all resources associated with the specified joystick
273 //
274 private void closeJoystick(_GLFWjoystick* js) {
275     if (js.win32.device)
276     {
277         js.win32.device.Unacquire();
278         js.win32.device.Release();
279     }
280 
281     free(js.win32.objects);
282 
283     _glfwFreeJoystick(js);
284     _glfwInputJoystick(js, GLFW_DISCONNECTED);
285 }
286 
287 // DirectInput device object enumeration callback
288 // Insights gleaned from SDL
289 //
290 extern(Windows) BOOL deviceObjectCallback(const(DIDEVICEOBJECTINSTANCEW)* doi, void* user) {
291     auto data = cast(_GLFWobjenumWin32*) user;
292     _GLFWjoyobjectWin32* object = data.objects + data.objectCount;
293 
294     if (DIDFT_GETTYPE(doi.dwType) & DIDFT_AXIS)
295     {
296         DIPROPRANGE dipr;
297 
298         if (memcmp(&doi.guidType, &GUID_Slider, GUID.sizeof) == 0)
299             object.offset = DIJOFS_SLIDER(data.sliderCount);
300         else if (memcmp(&doi.guidType, &GUID_XAxis, GUID.sizeof) == 0)
301             object.offset = DIJOFS_X;
302         else if (memcmp(&doi.guidType, &GUID_YAxis, GUID.sizeof) == 0)
303             object.offset = DIJOFS_Y;
304         else if (memcmp(&doi.guidType, &GUID_ZAxis, GUID.sizeof) == 0)
305             object.offset = DIJOFS_Z;
306         else if (memcmp(&doi.guidType, &GUID_RxAxis, GUID.sizeof) == 0)
307             object.offset = DIJOFS_RX;
308         else if (memcmp(&doi.guidType, &GUID_RyAxis, GUID.sizeof) == 0)
309             object.offset = DIJOFS_RY;
310         else if (memcmp(&doi.guidType, &GUID_RzAxis, GUID.sizeof) == 0)
311             object.offset = DIJOFS_RZ;
312         else
313             return DIENUM_CONTINUE;
314 
315         memset(&dipr, 0, typeof(dipr).sizeof);
316         dipr.diph.dwSize = typeof(dipr).sizeof;
317         dipr.diph.dwHeaderSize = typeof(dipr.diph).sizeof;
318         dipr.diph.dwObj = doi.dwType;
319         dipr.diph.dwHow = DIPH_BYID;
320         dipr.lMin = -32768;
321         dipr.lMax =  32767;
322 
323         if (FAILED(data.device.SetProperty(DIPROP_RANGE, &dipr.diph)))
324         {
325             return DIENUM_CONTINUE;
326         }
327 
328         if (memcmp(&doi.guidType, &GUID_Slider, GUID.sizeof) == 0)
329         {
330             object.type = _GLFW_TYPE_SLIDER;
331             data.sliderCount++;
332         }
333         else
334         {
335             object.type = _GLFW_TYPE_AXIS;
336             data.axisCount++;
337         }
338     }
339     else if (DIDFT_GETTYPE(doi.dwType) & DIDFT_BUTTON)
340     {
341         object.offset = DIJOFS_BUTTON(data.buttonCount);
342         object.type = _GLFW_TYPE_BUTTON;
343         data.buttonCount++;
344     }
345     else if (DIDFT_GETTYPE(doi.dwType) & DIDFT_POV)
346     {
347         object.offset = DIJOFS_POV(data.povCount);
348         object.type = _GLFW_TYPE_POV;
349         data.povCount++;
350     }
351 
352     data.objectCount++;
353     return DIENUM_CONTINUE;
354 }
355 
356 // DirectInput device enumeration callback
357 //
358 private extern(Windows) BOOL deviceCallback(const(DIDEVICEINSTANCE)* di, void* user) {
359     int jid = 0;
360     DIDEVCAPS dc;
361     DIPROPDWORD dipd;
362     IDirectInputDevice8 device;
363     _GLFWobjenumWin32 data;
364     //_GLFWjoystick* js; moved down to avoid shadowing
365     char[33] guid;
366     char[256] name;
367 
368     for (jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
369     {
370         _GLFWjoystick* js = _glfw.joysticks.ptr + jid;
371         if (js.present)
372         {
373             if (memcmp(&js.win32.guid, &di.guidInstance, GUID.sizeof) == 0)
374                 return DIENUM_CONTINUE;
375         }
376     }
377 
378     if (supportsXInput(&di.guidProduct))
379         return DIENUM_CONTINUE;
380 
381     if (FAILED(_glfw.win32.dinput8.api.CreateDevice(&di.guidInstance,
382                                                     &device,
383                                                     null)))
384     {
385         _glfwInputError(GLFW_PLATFORM_ERROR, "Win32: Failed to create device");
386         return DIENUM_CONTINUE;
387     }
388 
389     if (FAILED(device.SetDataFormat(&_glfwDataFormat)))
390     {
391         _glfwInputError(GLFW_PLATFORM_ERROR,
392                         "Win32: Failed to set device data format");
393 
394         device.Release();
395         return DIENUM_CONTINUE;
396     }
397 
398     memset(&dc, 0, typeof(dc).sizeof);
399     dc.dwSize = typeof(dc).sizeof;
400 
401     if (FAILED(device.GetCapabilities(&dc)))
402     {
403         _glfwInputError(GLFW_PLATFORM_ERROR,
404                         "Win32: Failed to query device capabilities");
405 
406         device.Release();
407         return DIENUM_CONTINUE;
408     }
409 
410     memset(&dipd, 0, typeof(dipd).sizeof);
411     dipd.diph.dwSize = typeof(dipd).sizeof;
412     dipd.diph.dwHeaderSize = typeof(dipd.diph).sizeof;
413     dipd.diph.dwHow = DIPH_DEVICE;
414     dipd.dwData = DIPROPAXISMODE_ABS;
415 
416     if (FAILED(device.SetProperty(DIPROP_AXISMODE, &dipd.diph)))
417     {
418         _glfwInputError(GLFW_PLATFORM_ERROR,
419                         "Win32: Failed to set device axis mode");
420 
421         device.Release();
422         return DIENUM_CONTINUE;
423     }
424 
425     memset(&data, 0, typeof(data).sizeof);
426     data.device = device;
427     data.objects = cast(_GLFWjoyobjectWin32*) calloc(dc.dwAxes + cast(size_t) dc.dwButtons + dc.dwPOVs,
428                           _GLFWjoyobjectWin32.sizeof);
429 
430     if (FAILED(device.EnumObjects(&deviceObjectCallback,
431                                   &data,
432                                   DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV)))
433     {
434         _glfwInputError(GLFW_PLATFORM_ERROR,
435                         "Win32: Failed to enumerate device objects");
436 
437         device.Release();
438         free(data.objects);
439         return DIENUM_CONTINUE;
440     }
441 
442     qsort(data.objects, data.objectCount,
443           _GLFWjoyobjectWin32.sizeof,
444           &compareJoystickObjects);
445 
446     if (!WideCharToMultiByte(CP_UTF8, 0,
447                              di.tszInstanceName.ptr, -1,
448                              name.ptr, typeof(name).sizeof,
449                              null, null))
450     {
451         _glfwInputError(GLFW_PLATFORM_ERROR,
452                         "Win32: Failed to convert joystick name to UTF-8");
453 
454         device.Release();
455         free(data.objects);
456         return DIENUM_STOP;
457     }
458 
459     // Generate a joystick GUID that matches the SDL 2.0.5+ one
460     if (memcmp(&di.guidProduct.Data4[2], "PIDVID".ptr, 6) == 0)
461     {
462         sprintf(guid.ptr, "03000000%02x%02x0000%02x%02x000000000000",
463                 cast(ubyte) di.guidProduct.Data1,
464                 cast(ubyte) (di.guidProduct.Data1 >> 8),
465                 cast(ubyte) (di.guidProduct.Data1 >> 16),
466                 cast(ubyte) (di.guidProduct.Data1 >> 24));
467     }
468     else
469     {
470         sprintf(guid.ptr, "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00",
471                 name[0], name[1], name[2], name[3],
472                 name[4], name[5], name[6], name[7],
473                 name[8], name[9], name[10]);
474     }
475 
476     _GLFWjoystick* js = _glfwAllocJoystick(name.ptr, guid.ptr,
477                             data.axisCount + data.sliderCount,
478                             data.buttonCount,
479                             data.povCount);
480     if (!js)
481     {
482         device.Release();
483         free(data.objects);
484         return DIENUM_STOP;
485     }
486 
487     js.win32.device = device;
488     js.win32.guid = di.guidInstance;
489     js.win32.objects = data.objects;
490     js.win32.objectCount = data.objectCount;
491 
492     _glfwInputJoystick(js, GLFW_CONNECTED);
493     return DIENUM_CONTINUE;
494 }
495 
496 
497 //////////////////////////////////////////////////////////////////////////
498 //////                       GLFW internal API                      //////
499 //////////////////////////////////////////////////////////////////////////
500 
501 // Initialize joystick interface
502 //
503 void _glfwInitJoysticksWin32() {
504     if (_glfw.win32.dinput8.instance)
505     {
506         if (FAILED(mixin(DirectInput8Create)(GetModuleHandle(null),
507                                       DIRECTINPUT_VERSION,
508                                       &IID_IDirectInput8W,
509                                       cast(void**) &_glfw.win32.dinput8.api,
510                                       null)))
511         {
512             _glfwInputError(GLFW_PLATFORM_ERROR,
513                             "Win32: Failed to create interface");
514         }
515     }
516     _glfwDetectJoystickConnectionWin32();
517 }
518 
519 // Close all opened joystick handles
520 //
521 void _glfwTerminateJoysticksWin32() {
522     int jid;
523 
524     for (jid = GLFW_JOYSTICK_1;  jid <= GLFW_JOYSTICK_LAST;  jid++)
525         closeJoystick(_glfw.joysticks.ptr + jid);
526 
527     if (_glfw.win32.dinput8.api)
528         _glfw.win32.dinput8.api.Release();
529 }
530 
531 // Checks for new joysticks after DBT_DEVICEARRIVAL
532 //
533 void _glfwDetectJoystickConnectionWin32() {
534     if (_glfw.win32.xinput.instance)
535     {
536         DWORD index;
537 
538         for (index = 0;  index < XUSER_MAX_COUNT;  index++)
539         {
540             int jid;
541             char[33] guid;
542             XINPUT_CAPABILITIES xic;
543             _GLFWjoystick* js;
544 
545             for (jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
546             {
547                 if (_glfw.joysticks[jid].present &&
548                     _glfw.joysticks[jid].win32.device is null &&
549                     _glfw.joysticks[jid].win32.index == index)
550                 {
551                     break;
552                 }
553             }
554 
555             if (jid <= GLFW_JOYSTICK_LAST)
556                 continue;
557 
558             if (mixin(XInputGetCapabilities)(index, 0, &xic) != ERROR_SUCCESS)
559                 continue;
560 
561             // Generate a joystick GUID that matches the SDL 2.0.5+ one
562             sprintf(guid.ptr, "78696e707574%02x000000000000000000",
563                     xic.SubType & 0xff);
564 
565             js = _glfwAllocJoystick(getDeviceDescription(&xic), guid.ptr, 6, 10, 1);
566             if (!js)
567                 continue;
568 
569             js.win32.index = index;
570 
571             _glfwInputJoystick(js, GLFW_CONNECTED);
572         }
573     }
574     if (_glfw.win32.dinput8.api)
575     {
576         if (FAILED(_glfw.win32.dinput8.api.EnumDevices(DI8DEVCLASS_GAMECTRL,
577                                                        &deviceCallback,
578                                                        null,
579                                                        DIEDFL_ALLDEVICES)))
580         {
581             _glfwInputError(GLFW_PLATFORM_ERROR,
582                             "Failed to enumerate DirectInput8 devices");
583             return;
584         }
585     }
586 }
587 
588 // Checks for joystick disconnection after DBT_DEVICEREMOVECOMPLETE
589 //
590 void _glfwDetectJoystickDisconnectionWin32() {
591     int jid;
592 
593     for (jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
594     {
595         _GLFWjoystick* js = _glfw.joysticks.ptr + jid;
596         if (js.present)
597             _glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE);
598     }
599 }
600 
601 
602 //////////////////////////////////////////////////////////////////////////
603 //////                       GLFW platform API                      //////
604 //////////////////////////////////////////////////////////////////////////
605 
606 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) {
607     if (js.win32.device)
608     {
609         int i;int ai = 0;int bi = 0;int pi = 0;
610         HRESULT result;
611         DIJOYSTATE state;
612 
613         js.win32.device.Poll();
614         result = js.win32.device.GetDeviceState(typeof(state).sizeof,
615                                                 &state);
616         if (result == DIERR_NOTACQUIRED || result == DIERR_INPUTLOST)
617         {
618             js.win32.device.Acquire();
619             js.win32.device.Poll();
620             result = js.win32.device.GetDeviceState(typeof(state).sizeof,
621                                                     &state);
622         }
623 
624         if (FAILED(result))
625         {
626             closeJoystick(js);
627             return GLFW_FALSE;
628         }
629 
630         if (mode == _GLFW_POLL_PRESENCE)
631             return GLFW_TRUE;
632 
633         for (i = 0;  i < js.win32.objectCount;  i++)
634         {
635             const(void)* data = cast(char*) &state + js.win32.objects[i].offset;
636 
637             switch (js.win32.objects[i].type)
638             {
639                 case _GLFW_TYPE_AXIS:
640                 case _GLFW_TYPE_SLIDER:
641                 {
642                     const(float) value = (*(cast(LONG*) data) + 0.5f) / 32767.5f;
643                     _glfwInputJoystickAxis(js, ai, value);
644                     ai++;
645                     break;
646                 }
647 
648                 case _GLFW_TYPE_BUTTON:
649                 {
650                     const(char) value = (*(cast(BYTE*) data) & 0x80) != 0;
651                     _glfwInputJoystickButton(js, bi, value);
652                     bi++;
653                     break;
654                 }
655 
656                 case _GLFW_TYPE_POV:
657                 {
658                     const(int)[9] states = [
659                         GLFW_HAT_UP,
660                         GLFW_HAT_RIGHT_UP,
661                         GLFW_HAT_RIGHT,
662                         GLFW_HAT_RIGHT_DOWN,
663                         GLFW_HAT_DOWN,
664                         GLFW_HAT_LEFT_DOWN,
665                         GLFW_HAT_LEFT,
666                         GLFW_HAT_LEFT_UP,
667                         GLFW_HAT_CENTERED
668                     ];
669 
670                     // Screams of horror are appropriate at this point
671                     int state1 = LOWORD(*cast(DWORD*) data) / (45 * DI_DEGREES);
672                     if (state1 < 0 || state1 > 8)
673                         state1 = 8;
674 
675                     _glfwInputJoystickHat(js, pi, cast(char) states[state1]);
676                     pi++;
677                     break;
678                 }
679                 default: break;
680             }
681         }
682     }
683     else
684     {
685         int i;int dpad = 0;
686         DWORD result;
687         XINPUT_STATE xis;
688         const(WORD)[10] buttons = [
689             XINPUT_GAMEPAD_A,
690             XINPUT_GAMEPAD_B,
691             XINPUT_GAMEPAD_X,
692             XINPUT_GAMEPAD_Y,
693             XINPUT_GAMEPAD_LEFT_SHOULDER,
694             XINPUT_GAMEPAD_RIGHT_SHOULDER,
695             XINPUT_GAMEPAD_BACK,
696             XINPUT_GAMEPAD_START,
697             XINPUT_GAMEPAD_LEFT_THUMB,
698             XINPUT_GAMEPAD_RIGHT_THUMB
699         ];
700 
701         result = mixin(XInputGetState)(js.win32.index, &xis);
702         if (result != ERROR_SUCCESS)
703         {
704             if (result == ERROR_DEVICE_NOT_CONNECTED)
705                 closeJoystick(js);
706 
707             return GLFW_FALSE;
708         }
709 
710         if (mode == _GLFW_POLL_PRESENCE)
711             return GLFW_TRUE;
712 
713         _glfwInputJoystickAxis(js, 0, (xis.Gamepad.sThumbLX + 0.5f) / 32767.5f);
714         _glfwInputJoystickAxis(js, 1, -(xis.Gamepad.sThumbLY + 0.5f) / 32767.5f);
715         _glfwInputJoystickAxis(js, 2, (xis.Gamepad.sThumbRX + 0.5f) / 32767.5f);
716         _glfwInputJoystickAxis(js, 3, -(xis.Gamepad.sThumbRY + 0.5f) / 32767.5f);
717         _glfwInputJoystickAxis(js, 4, xis.Gamepad.bLeftTrigger / 127.5f - 1.0f);
718         _glfwInputJoystickAxis(js, 5, xis.Gamepad.bRightTrigger / 127.5f - 1.0f);
719 
720         for (i = 0;  i < 10;  i++)
721         {
722             const(char) value = (xis.Gamepad.wButtons & buttons[i]) ? 1 : 0;
723             _glfwInputJoystickButton(js, i, value);
724         }
725 
726         if (xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)
727             dpad |= GLFW_HAT_UP;
728         if (xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
729             dpad |= GLFW_HAT_RIGHT;
730         if (xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
731             dpad |= GLFW_HAT_DOWN;
732         if (xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
733             dpad |= GLFW_HAT_LEFT;
734 
735         _glfwInputJoystickHat(js, 0, cast(char) dpad);
736     }
737 
738     return GLFW_TRUE;
739 }
740 
741 void _glfwPlatformUpdateGamepadGUID(char* guid) {
742     if (strcmp(guid + 20, "504944564944") == 0)
743     {
744         char[33] original;
745         strncpy(original.ptr, guid, original.length - 1);
746         sprintf(guid, "03000000%.4s0000%.4s000000000000",
747                 original.ptr, original.ptr + 4);
748     }
749 }
750 
751 int _glfwPlatformSetJoystickRumble(_GLFWjoystick* js, float slowMotorIntensity, float fastMotorIntensity)
752 {
753     XINPUT_VIBRATION effect = _XINPUT_VIBRATION.init;
754 
755     if (js.win32.device)
756         return GLFW_FALSE;
757 
758     effect.wLeftMotorSpeed  = cast(WORD)(65_535.0f * slowMotorIntensity);
759     effect.wRightMotorSpeed = cast(WORD)(65_535.0f * fastMotorIntensity);
760 
761     return cast(int) (mixin(XInputSetState)(js.win32.index, &effect) == ERROR_SUCCESS);
762 }