- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / gamepad / gamepad_platform_data_fetcher_win.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
6
7 #include <dinput.h>
8 #include <dinputd.h>
9
10 #include "base/debug/trace_event.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/win/windows_version.h"
13 #include "content/common/gamepad_hardware_buffer.h"
14 #include "content/common/gamepad_messages.h"
15
16 // This was removed from the Windows 8 SDK for some reason.
17 // We need it so we can get state for axes without worrying if they
18 // exist.
19 #ifndef DIDFT_OPTIONAL
20 #define DIDFT_OPTIONAL 0x80000000
21 #endif
22
23 namespace content {
24
25 using namespace WebKit;
26
27 namespace {
28
29 // See http://goo.gl/5VSJR. These are not available in all versions of the
30 // header, but they can be returned from the driver, so we define our own
31 // versions here.
32 static const BYTE kDeviceSubTypeGamepad = 1;
33 static const BYTE kDeviceSubTypeWheel = 2;
34 static const BYTE kDeviceSubTypeArcadeStick = 3;
35 static const BYTE kDeviceSubTypeFlightStick = 4;
36 static const BYTE kDeviceSubTypeDancePad = 5;
37 static const BYTE kDeviceSubTypeGuitar = 6;
38 static const BYTE kDeviceSubTypeGuitarAlternate = 7;
39 static const BYTE kDeviceSubTypeDrumKit = 8;
40 static const BYTE kDeviceSubTypeGuitarBass = 11;
41 static const BYTE kDeviceSubTypeArcadePad = 19;
42
43 float NormalizeXInputAxis(SHORT value) {
44   return ((value + 32768.f) / 32767.5f) - 1.f;
45 }
46
47 const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
48   switch (sub_type) {
49     case kDeviceSubTypeGamepad: return L"GAMEPAD";
50     case kDeviceSubTypeWheel: return L"WHEEL";
51     case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
52     case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
53     case kDeviceSubTypeDancePad: return L"DANCE_PAD";
54     case kDeviceSubTypeGuitar: return L"GUITAR";
55     case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
56     case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
57     case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
58     case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
59     default: return L"<UNKNOWN>";
60   }
61 }
62
63 bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad,
64                                  std::string* vendor,
65                                  std::string* product) {
66   DIPROPDWORD prop;
67   prop.diph.dwSize = sizeof(DIPROPDWORD);
68   prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
69   prop.diph.dwObj = 0;
70   prop.diph.dwHow = DIPH_DEVICE;
71
72   if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph)))
73     return false;
74   *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData));
75   *product = base::StringPrintf("%04x", HIWORD(prop.dwData));
76   return true;
77 }
78
79 // Sets the deadzone value for all axes of a gamepad.
80 // deadzone values range from 0 (no deadzone) to 10,000 (entire range
81 // is dead).
82 bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad,
83                             int deadzone) {
84   DIPROPDWORD prop;
85   prop.diph.dwSize = sizeof(DIPROPDWORD);
86   prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
87   prop.diph.dwObj = 0;
88   prop.diph.dwHow = DIPH_DEVICE;
89   prop.dwData = deadzone;
90   return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph));
91 }
92
93 struct InternalDirectInputDevice {
94   IDirectInputDevice8* gamepad;
95   GamepadStandardMappingFunction mapper;
96   wchar_t id[WebGamepad::idLengthCap];
97   GUID guid;
98 };
99
100 struct EnumDevicesContext {
101   IDirectInput8* directinput_interface;
102   std::vector<InternalDirectInputDevice>* directinput_devices;
103 };
104
105 // We define our own data format structure to attempt to get as many
106 // axes as possible.
107 struct JoyData {
108   long axes[10];
109   char buttons[24];
110   DWORD pov;  // Often used for D-pads.
111 };
112
113 BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance,
114                                              void* context) {
115   EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context);
116   IDirectInputDevice8* gamepad;
117
118   if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance,
119                                                        &gamepad,
120                                                        NULL)))
121     return DIENUM_CONTINUE;
122
123   gamepad->Acquire();
124
125 #define MAKE_AXIS(i) \
126   {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \
127    DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
128 #define MAKE_BUTTON(i) \
129   {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \
130    DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0}
131 #define MAKE_POV() \
132   {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0}
133   DIOBJECTDATAFORMAT rgodf[] = {
134     MAKE_AXIS(0),
135     MAKE_AXIS(1),
136     MAKE_AXIS(2),
137     MAKE_AXIS(3),
138     MAKE_AXIS(4),
139     MAKE_AXIS(5),
140     MAKE_AXIS(6),
141     MAKE_AXIS(7),
142     MAKE_AXIS(8),
143     MAKE_AXIS(9),
144     MAKE_BUTTON(0),
145     MAKE_BUTTON(1),
146     MAKE_BUTTON(2),
147     MAKE_BUTTON(3),
148     MAKE_BUTTON(4),
149     MAKE_BUTTON(5),
150     MAKE_BUTTON(6),
151     MAKE_BUTTON(7),
152     MAKE_BUTTON(8),
153     MAKE_BUTTON(9),
154     MAKE_BUTTON(10),
155     MAKE_BUTTON(11),
156     MAKE_BUTTON(12),
157     MAKE_BUTTON(13),
158     MAKE_BUTTON(14),
159     MAKE_BUTTON(15),
160     MAKE_BUTTON(16),
161     MAKE_POV(),
162   };
163 #undef MAKE_AXIS
164 #undef MAKE_BUTTON
165 #undef MAKE_POV
166
167   DIDATAFORMAT df = {
168     sizeof (DIDATAFORMAT),
169     sizeof (DIOBJECTDATAFORMAT),
170     DIDF_ABSAXIS,
171     sizeof (JoyData),
172     sizeof (rgodf) / sizeof (rgodf[0]),
173     rgodf
174   };
175
176   // If we can't set the data format on the device, don't add it to our
177   // list, since we won't know how to read data from it.
178   if (FAILED(gamepad->SetDataFormat(&df))) {
179     gamepad->Release();
180     return DIENUM_CONTINUE;
181   }
182
183   InternalDirectInputDevice device;
184   device.guid = instance->guidInstance;
185   device.gamepad = gamepad;
186   std::string vendor;
187   std::string product;
188   if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) {
189     gamepad->Release();
190     return DIENUM_CONTINUE;
191   }
192
193   // Set the dead zone to 10% of the axis length for all axes. This
194   // gives us a larger space for what's "neutral" so the controls don't
195   // slowly drift.
196   SetDirectInputDeadZone(gamepad, 1000);
197   device.mapper = GetGamepadStandardMappingFunction(vendor, product);
198   if (device.mapper) {
199     base::swprintf(device.id,
200                    WebGamepad::idLengthCap,
201                    L"STANDARD GAMEPAD (%ls)",
202                    instance->tszProductName);
203     ctxt->directinput_devices->push_back(device);
204   } else {
205     gamepad->Release();
206   }
207   return DIENUM_CONTINUE;
208 }
209
210 }  // namespace
211
212 GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin()
213     : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))),
214       xinput_available_(GetXInputDllFunctions()) {
215   // TODO(teravest): http://crbug.com/260187 for Windows XP.
216   // TODO(teravest): http://crbug.com/305267 for later versions of windows.
217   directinput_available_ = false;
218   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
219     pad_state_[i].status = DISCONNECTED;
220 }
221
222 GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() {
223   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
224     if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
225       pad_state_[i].directinput_gamepad->Release();
226   }
227 }
228
229 int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
230   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
231     if (pad_state_[i].status == DISCONNECTED)
232       return i;
233   }
234   return -1;
235 }
236
237 bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const {
238   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
239     if (pad_state_[i].status == XINPUT_CONNECTED &&
240         pad_state_[i].xinput_index == index)
241       return true;
242   }
243   return false;
244 }
245
246 bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad(
247     const GUID& guid) const {
248   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
249     if (pad_state_[i].status == DIRECTINPUT_CONNECTED &&
250         pad_state_[i].guid == guid)
251       return true;
252   }
253   return false;
254 }
255
256 void GamepadPlatformDataFetcherWin::EnumerateDevices(
257     WebGamepads* pads) {
258   TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
259
260   // Mark all disconnected pads DISCONNECTED.
261   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
262     if (!pads->items[i].connected)
263       pad_state_[i].status = DISCONNECTED;
264   }
265
266   for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
267     if (HasXInputGamepad(i))
268       continue;
269     int pad_index = FirstAvailableGamepadId();
270     if (pad_index == -1)
271       return;  // We can't add any more gamepads.
272     WebGamepad& pad = pads->items[pad_index];
273     if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) {
274       pad_state_[pad_index].status = XINPUT_CONNECTED;
275       pad_state_[pad_index].xinput_index = i;
276     }
277   }
278
279   if (directinput_available_) {
280     struct EnumDevicesContext context;
281     std::vector<InternalDirectInputDevice> directinput_gamepads;
282     context.directinput_interface = directinput_interface_;
283     context.directinput_devices = &directinput_gamepads;
284
285     directinput_interface_->EnumDevices(
286         DI8DEVCLASS_GAMECTRL,
287         &DirectInputEnumDevicesCallback,
288         &context,
289         DIEDFL_ATTACHEDONLY);
290     for (size_t i = 0; i < directinput_gamepads.size(); ++i) {
291       if (HasDirectInputGamepad(directinput_gamepads[i].guid)) {
292         directinput_gamepads[i].gamepad->Release();
293         continue;
294       }
295       int pad_index = FirstAvailableGamepadId();
296       if (pad_index == -1)
297         return;
298       WebGamepad& pad = pads->items[pad_index];
299       pad.connected = true;
300       wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id);
301       PadState& state = pad_state_[pad_index];
302       state.status = DIRECTINPUT_CONNECTED;
303       state.guid = directinput_gamepads[i].guid;
304       state.directinput_gamepad = directinput_gamepads[i].gamepad;
305       state.mapper = directinput_gamepads[i].mapper;
306     }
307   }
308 }
309
310
311 void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
312                                                    bool devices_changed_hint) {
313   TRACE_EVENT0("GAMEPAD", "GetGamepadData");
314
315   if (!xinput_available_ && !directinput_available_) {
316     pads->length = 0;
317     return;
318   }
319
320   // A note on XInput devices:
321   // If we got notification that system devices have been updated, then
322   // run GetCapabilities to update the connected status and the device
323   // identifier. It can be slow to do to both GetCapabilities and
324   // GetState on unconnected devices, so we want to avoid a 2-5ms pause
325   // here by only doing this when the devices are updated (despite
326   // documentation claiming it's OK to call it any time).
327   if (devices_changed_hint)
328     EnumerateDevices(pads);
329
330   for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
331     WebGamepad& pad = pads->items[i];
332     if (pad_state_[i].status == XINPUT_CONNECTED)
333       GetXInputPadData(i, &pad);
334     else if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
335       GetDirectInputPadData(i, &pad);
336   }
337   pads->length = WebGamepads::itemsLengthCap;
338 }
339
340 bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
341     int i,
342     WebGamepad* pad) const {
343   DCHECK(pad);
344   TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i);
345   XINPUT_CAPABILITIES caps;
346   DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps);
347   if (res == ERROR_DEVICE_NOT_CONNECTED) {
348     pad->connected = false;
349     return false;
350   } else {
351     pad->connected = true;
352     base::swprintf(pad->id,
353                    WebGamepad::idLengthCap,
354                    L"Xbox 360 Controller (XInput STANDARD %ls)",
355                    GamepadSubTypeName(caps.SubType));
356     return true;
357   }
358 }
359
360 void GamepadPlatformDataFetcherWin::GetXInputPadData(
361     int i,
362     WebGamepad* pad) {
363   // We rely on device_changed and GetCapabilities to tell us that
364   // something's been connected, but we will mark as disconnected if
365   // GetState returns that we've lost the pad.
366   if (!pad->connected)
367     return;
368
369   XINPUT_STATE state;
370   memset(&state, 0, sizeof(XINPUT_STATE));
371   TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
372   DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state);
373   TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
374
375   if (dwResult == ERROR_SUCCESS) {
376     pad->timestamp = state.dwPacketNumber;
377     pad->buttonsLength = 0;
378 #define ADD(b) pad->buttons[pad->buttonsLength++] = \
379   ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0);
380     ADD(XINPUT_GAMEPAD_A);
381     ADD(XINPUT_GAMEPAD_B);
382     ADD(XINPUT_GAMEPAD_X);
383     ADD(XINPUT_GAMEPAD_Y);
384     ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
385     ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
386     pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0;
387     pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0;
388     ADD(XINPUT_GAMEPAD_BACK);
389     ADD(XINPUT_GAMEPAD_START);
390     ADD(XINPUT_GAMEPAD_LEFT_THUMB);
391     ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
392     ADD(XINPUT_GAMEPAD_DPAD_UP);
393     ADD(XINPUT_GAMEPAD_DPAD_DOWN);
394     ADD(XINPUT_GAMEPAD_DPAD_LEFT);
395     ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
396 #undef ADD
397     pad->axesLength = 0;
398     // XInput are +up/+right, -down/-left, we want -up/-left.
399     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX);
400     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY);
401     pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX);
402     pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY);
403   } else {
404     pad->connected = false;
405   }
406 }
407
408 void GamepadPlatformDataFetcherWin::GetDirectInputPadData(
409     int index,
410     WebGamepad* pad) {
411   if (!pad->connected)
412     return;
413
414   IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad;
415   if (FAILED(gamepad->Poll())) {
416     // Polling didn't work, try acquiring the gamepad.
417     if (FAILED(gamepad->Acquire())) {
418       pad->buttonsLength = 0;
419       pad->axesLength = 0;
420       return;
421     }
422     // Try polling again.
423     if (FAILED(gamepad->Poll())) {
424       pad->buttonsLength = 0;
425       pad->axesLength = 0;
426       return;
427     }
428   }
429   JoyData state;
430   if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) {
431     pad->connected = false;
432     return;
433   }
434
435   WebGamepad raw;
436   raw.connected = true;
437   for (int i = 0; i < 16; i++)
438     raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0;
439
440   // We map the POV (often a D-pad) into the buttons 16-19.
441   // DirectInput gives pov measurements in hundredths of degrees,
442   // clockwise from "North".
443   // We use 22.5 degree slices so we can handle diagonal D-raw presses.
444   static const int arc_segment = 2250;  // 22.5 degrees = 1/16 circle
445   if (state.pov > arc_segment && state.pov < 7 * arc_segment)
446     raw.buttons[19] = 1.0;
447   else
448     raw.buttons[19] = 0.0;
449
450   if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment)
451     raw.buttons[17] = 1.0;
452   else
453     raw.buttons[17] = 0.0;
454
455   if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment)
456     raw.buttons[18] = 1.0;
457   else
458     raw.buttons[18] = 0.0;
459
460   if (state.pov < 3 * arc_segment ||
461       (state.pov > 13 * arc_segment && state.pov < 36000))
462     raw.buttons[16] = 1.0;
463   else
464     raw.buttons[16] = 0.0;
465
466   for (int i = 0; i < 10; i++)
467     raw.axes[i] = state.axes[i];
468   pad_state_[index].mapper(raw, pad);
469 }
470
471 bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() {
472   xinput_get_capabilities_ = NULL;
473   xinput_get_state_ = NULL;
474   xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
475       xinput_dll_.GetFunctionPointer("XInputEnable"));
476   if (!xinput_enable_)
477     return false;
478   xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
479       xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
480   if (!xinput_get_capabilities_)
481     return false;
482   xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
483       xinput_dll_.GetFunctionPointer("XInputGetState"));
484   if (!xinput_get_state_)
485     return false;
486   xinput_enable_(true);
487   return true;
488 }
489
490 }  // namespace content