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
23namespace content {
24
25using namespace WebKit;
26
27namespace {
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.
32static const BYTE kDeviceSubTypeGamepad = 1;
33static const BYTE kDeviceSubTypeWheel = 2;
34static const BYTE kDeviceSubTypeArcadeStick = 3;
35static const BYTE kDeviceSubTypeFlightStick = 4;
36static const BYTE kDeviceSubTypeDancePad = 5;
37static const BYTE kDeviceSubTypeGuitar = 6;
38static const BYTE kDeviceSubTypeGuitarAlternate = 7;
39static const BYTE kDeviceSubTypeDrumKit = 8;
40static const BYTE kDeviceSubTypeGuitarBass = 11;
41static const BYTE kDeviceSubTypeArcadePad = 19;
42
43float NormalizeXInputAxis(SHORT value) {
44  return ((value + 32768.f) / 32767.5f) - 1.f;
45}
46
47const 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
63bool 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).
82bool 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
93struct InternalDirectInputDevice {
94  IDirectInputDevice8* gamepad;
95  GamepadStandardMappingFunction mapper;
96  wchar_t id[WebGamepad::idLengthCap];
97  GUID guid;
98};
99
100struct 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.
107struct JoyData {
108  long axes[10];
109  char buttons[24];
110  DWORD pov;  // Often used for D-pads.
111};
112
113BOOL 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
212GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin()
213    : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))),
214      xinput_available_(GetXInputDllFunctions()) {
215  // TODO(teravest): http://crbug.com/260187
216  if (base::win::GetVersion() > base::win::VERSION_XP) {
217    directinput_available_ = SUCCEEDED(DirectInput8Create(
218        GetModuleHandle(NULL),
219        DIRECTINPUT_VERSION,
220        IID_IDirectInput8,
221        reinterpret_cast<void**>(&directinput_interface_),
222        NULL));
223  } else {
224    directinput_available_ = false;
225  }
226  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i)
227    pad_state_[i].status = DISCONNECTED;
228}
229
230GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() {
231  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
232    if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
233      pad_state_[i].directinput_gamepad->Release();
234  }
235}
236
237int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
238  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
239    if (pad_state_[i].status == DISCONNECTED)
240      return i;
241  }
242  return -1;
243}
244
245bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const {
246  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
247    if (pad_state_[i].status == XINPUT_CONNECTED &&
248        pad_state_[i].xinput_index == index)
249      return true;
250  }
251  return false;
252}
253
254bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad(
255    const GUID& guid) const {
256  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
257    if (pad_state_[i].status == DIRECTINPUT_CONNECTED &&
258        pad_state_[i].guid == guid)
259      return true;
260  }
261  return false;
262}
263
264void GamepadPlatformDataFetcherWin::EnumerateDevices(
265    WebGamepads* pads) {
266  TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
267
268  // Mark all disconnected pads DISCONNECTED.
269  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
270    if (!pads->items[i].connected)
271      pad_state_[i].status = DISCONNECTED;
272  }
273
274  for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
275    if (HasXInputGamepad(i))
276      continue;
277    int pad_index = FirstAvailableGamepadId();
278    if (pad_index == -1)
279      return;  // We can't add any more gamepads.
280    WebGamepad& pad = pads->items[pad_index];
281    if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) {
282      pad_state_[pad_index].status = XINPUT_CONNECTED;
283      pad_state_[pad_index].xinput_index = i;
284    }
285  }
286
287  if (directinput_available_) {
288    struct EnumDevicesContext context;
289    std::vector<InternalDirectInputDevice> directinput_gamepads;
290    context.directinput_interface = directinput_interface_;
291    context.directinput_devices = &directinput_gamepads;
292
293    directinput_interface_->EnumDevices(
294        DI8DEVCLASS_GAMECTRL,
295        &DirectInputEnumDevicesCallback,
296        &context,
297        DIEDFL_ATTACHEDONLY);
298    for (size_t i = 0; i < directinput_gamepads.size(); ++i) {
299      if (HasDirectInputGamepad(directinput_gamepads[i].guid)) {
300        directinput_gamepads[i].gamepad->Release();
301        continue;
302      }
303      int pad_index = FirstAvailableGamepadId();
304      if (pad_index == -1)
305        return;
306      WebGamepad& pad = pads->items[pad_index];
307      pad.connected = true;
308      wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id);
309      PadState& state = pad_state_[pad_index];
310      state.status = DIRECTINPUT_CONNECTED;
311      state.guid = directinput_gamepads[i].guid;
312      state.directinput_gamepad = directinput_gamepads[i].gamepad;
313      state.mapper = directinput_gamepads[i].mapper;
314    }
315  }
316}
317
318
319void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
320                                                   bool devices_changed_hint) {
321  TRACE_EVENT0("GAMEPAD", "GetGamepadData");
322
323  if (!xinput_available_ && !directinput_available_) {
324    pads->length = 0;
325    return;
326  }
327
328  // A note on XInput devices:
329  // If we got notification that system devices have been updated, then
330  // run GetCapabilities to update the connected status and the device
331  // identifier. It can be slow to do to both GetCapabilities and
332  // GetState on unconnected devices, so we want to avoid a 2-5ms pause
333  // here by only doing this when the devices are updated (despite
334  // documentation claiming it's OK to call it any time).
335  if (devices_changed_hint)
336    EnumerateDevices(pads);
337
338  for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
339    WebGamepad& pad = pads->items[i];
340    if (pad_state_[i].status == XINPUT_CONNECTED)
341      GetXInputPadData(i, &pad);
342    else if (pad_state_[i].status == DIRECTINPUT_CONNECTED)
343      GetDirectInputPadData(i, &pad);
344  }
345  pads->length = WebGamepads::itemsLengthCap;
346}
347
348bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
349    int i,
350    WebGamepad* pad) const {
351  DCHECK(pad);
352  TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i);
353  XINPUT_CAPABILITIES caps;
354  DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps);
355  if (res == ERROR_DEVICE_NOT_CONNECTED) {
356    pad->connected = false;
357    return false;
358  } else {
359    pad->connected = true;
360    base::swprintf(pad->id,
361                   WebGamepad::idLengthCap,
362                   L"Xbox 360 Controller (XInput STANDARD %ls)",
363                   GamepadSubTypeName(caps.SubType));
364    return true;
365  }
366}
367
368void GamepadPlatformDataFetcherWin::GetXInputPadData(
369    int i,
370    WebGamepad* pad) {
371  // We rely on device_changed and GetCapabilities to tell us that
372  // something's been connected, but we will mark as disconnected if
373  // GetState returns that we've lost the pad.
374  if (!pad->connected)
375    return;
376
377  XINPUT_STATE state;
378  memset(&state, 0, sizeof(XINPUT_STATE));
379  TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
380  DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state);
381  TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
382
383  if (dwResult == ERROR_SUCCESS) {
384    pad->timestamp = state.dwPacketNumber;
385    pad->buttonsLength = 0;
386#define ADD(b) pad->buttons[pad->buttonsLength++] = \
387  ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0);
388    ADD(XINPUT_GAMEPAD_A);
389    ADD(XINPUT_GAMEPAD_B);
390    ADD(XINPUT_GAMEPAD_X);
391    ADD(XINPUT_GAMEPAD_Y);
392    ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
393    ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
394    pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0;
395    pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0;
396    ADD(XINPUT_GAMEPAD_BACK);
397    ADD(XINPUT_GAMEPAD_START);
398    ADD(XINPUT_GAMEPAD_LEFT_THUMB);
399    ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
400    ADD(XINPUT_GAMEPAD_DPAD_UP);
401    ADD(XINPUT_GAMEPAD_DPAD_DOWN);
402    ADD(XINPUT_GAMEPAD_DPAD_LEFT);
403    ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
404#undef ADD
405    pad->axesLength = 0;
406    // XInput are +up/+right, -down/-left, we want -up/-left.
407    pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX);
408    pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY);
409    pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX);
410    pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY);
411  } else {
412    pad->connected = false;
413  }
414}
415
416void GamepadPlatformDataFetcherWin::GetDirectInputPadData(
417    int index,
418    WebGamepad* pad) {
419  if (!pad->connected)
420    return;
421
422  IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad;
423  if (FAILED(gamepad->Poll())) {
424    // Polling didn't work, try acquiring the gamepad.
425    if (FAILED(gamepad->Acquire())) {
426      pad->buttonsLength = 0;
427      pad->axesLength = 0;
428      return;
429    }
430    // Try polling again.
431    if (FAILED(gamepad->Poll())) {
432      pad->buttonsLength = 0;
433      pad->axesLength = 0;
434      return;
435    }
436  }
437  JoyData state;
438  if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) {
439    pad->connected = false;
440    return;
441  }
442
443  WebGamepad raw;
444  raw.connected = true;
445  for (int i = 0; i < 16; i++)
446    raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0;
447
448  // We map the POV (often a D-pad) into the buttons 16-19.
449  // DirectInput gives pov measurements in hundredths of degrees,
450  // clockwise from "North".
451  // We use 22.5 degree slices so we can handle diagonal D-raw presses.
452  static const int arc_segment = 2250;  // 22.5 degrees = 1/16 circle
453  if (state.pov > arc_segment && state.pov < 7 * arc_segment)
454    raw.buttons[19] = 1.0;
455  else
456    raw.buttons[19] = 0.0;
457
458  if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment)
459    raw.buttons[17] = 1.0;
460  else
461    raw.buttons[17] = 0.0;
462
463  if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment)
464    raw.buttons[18] = 1.0;
465  else
466    raw.buttons[18] = 0.0;
467
468  if (state.pov < 3 * arc_segment ||
469      (state.pov > 13 * arc_segment && state.pov < 36000))
470    raw.buttons[16] = 1.0;
471  else
472    raw.buttons[16] = 0.0;
473
474  for (int i = 0; i < 10; i++)
475    raw.axes[i] = state.axes[i];
476  pad_state_[index].mapper(raw, pad);
477}
478
479bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() {
480  xinput_get_capabilities_ = NULL;
481  xinput_get_state_ = NULL;
482  xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
483      xinput_dll_.GetFunctionPointer("XInputEnable"));
484  if (!xinput_enable_)
485    return false;
486  xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
487      xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
488  if (!xinput_get_capabilities_)
489    return false;
490  xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
491      xinput_dll_.GetFunctionPointer("XInputGetState"));
492  if (!xinput_get_state_)
493    return false;
494  xinput_enable_(true);
495  return true;
496}
497
498}  // namespace content
499