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.
5 #include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
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"
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
19 #ifndef DIDFT_OPTIONAL
20 #define DIDFT_OPTIONAL 0x80000000
25 using namespace WebKit;
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
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;
43 float NormalizeXInputAxis(SHORT value) {
44 return ((value + 32768.f) / 32767.5f) - 1.f;
47 const WebUChar* const GamepadSubTypeName(BYTE 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>";
63 bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad,
65 std::string* product) {
67 prop.diph.dwSize = sizeof(DIPROPDWORD);
68 prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
70 prop.diph.dwHow = DIPH_DEVICE;
72 if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph)))
74 *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData));
75 *product = base::StringPrintf("%04x", HIWORD(prop.dwData));
79 // Sets the deadzone value for all axes of a gamepad.
80 // deadzone values range from 0 (no deadzone) to 10,000 (entire range
82 bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad,
85 prop.diph.dwSize = sizeof(DIPROPDWORD);
86 prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
88 prop.diph.dwHow = DIPH_DEVICE;
89 prop.dwData = deadzone;
90 return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph));
93 struct InternalDirectInputDevice {
94 IDirectInputDevice8* gamepad;
95 GamepadStandardMappingFunction mapper;
96 wchar_t id[WebGamepad::idLengthCap];
100 struct EnumDevicesContext {
101 IDirectInput8* directinput_interface;
102 std::vector<InternalDirectInputDevice>* directinput_devices;
105 // We define our own data format structure to attempt to get as many
110 DWORD pov; // Often used for D-pads.
113 BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance,
115 EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context);
116 IDirectInputDevice8* gamepad;
118 if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance,
121 return DIENUM_CONTINUE;
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}
132 {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0}
133 DIOBJECTDATAFORMAT rgodf[] = {
168 sizeof (DIDATAFORMAT),
169 sizeof (DIOBJECTDATAFORMAT),
172 sizeof (rgodf) / sizeof (rgodf[0]),
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))) {
180 return DIENUM_CONTINUE;
183 InternalDirectInputDevice device;
184 device.guid = instance->guidInstance;
185 device.gamepad = gamepad;
188 if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) {
190 return DIENUM_CONTINUE;
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
196 SetDirectInputDeadZone(gamepad, 1000);
197 device.mapper = GetGamepadStandardMappingFunction(vendor, product);
199 base::swprintf(device.id,
200 WebGamepad::idLengthCap,
201 L"STANDARD GAMEPAD (%ls)",
202 instance->tszProductName);
203 ctxt->directinput_devices->push_back(device);
207 return DIENUM_CONTINUE;
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;
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();
229 int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const {
230 for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) {
231 if (pad_state_[i].status == DISCONNECTED)
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)
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)
256 void GamepadPlatformDataFetcherWin::EnumerateDevices(
258 TRACE_EVENT0("GAMEPAD", "EnumerateDevices");
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;
266 for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) {
267 if (HasXInputGamepad(i))
269 int pad_index = FirstAvailableGamepadId();
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;
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;
285 directinput_interface_->EnumDevices(
286 DI8DEVCLASS_GAMECTRL,
287 &DirectInputEnumDevicesCallback,
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();
295 int pad_index = FirstAvailableGamepadId();
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;
311 void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads,
312 bool devices_changed_hint) {
313 TRACE_EVENT0("GAMEPAD", "GetGamepadData");
315 if (!xinput_available_ && !directinput_available_) {
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);
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);
337 pads->length = WebGamepads::itemsLengthCap;
340 bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity(
342 WebGamepad* pad) const {
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;
351 pad->connected = true;
352 base::swprintf(pad->id,
353 WebGamepad::idLengthCap,
354 L"Xbox 360 Controller (XInput STANDARD %ls)",
355 GamepadSubTypeName(caps.SubType));
360 void GamepadPlatformDataFetcherWin::GetXInputPadData(
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.
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);
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);
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);
404 pad->connected = false;
408 void GamepadPlatformDataFetcherWin::GetDirectInputPadData(
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;
422 // Try polling again.
423 if (FAILED(gamepad->Poll())) {
424 pad->buttonsLength = 0;
430 if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) {
431 pad->connected = false;
436 raw.connected = true;
437 for (int i = 0; i < 16; i++)
438 raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0;
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;
448 raw.buttons[19] = 0.0;
450 if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment)
451 raw.buttons[17] = 1.0;
453 raw.buttons[17] = 0.0;
455 if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment)
456 raw.buttons[18] = 1.0;
458 raw.buttons[18] = 0.0;
460 if (state.pov < 3 * arc_segment ||
461 (state.pov > 13 * arc_segment && state.pov < 36000))
462 raw.buttons[16] = 1.0;
464 raw.buttons[16] = 0.0;
466 for (int i = 0; i < 10; i++)
467 raw.axes[i] = state.axes[i];
468 pad_state_[index].mapper(raw, pad);
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"));
478 xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
479 xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
480 if (!xinput_get_capabilities_)
482 xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
483 xinput_dll_.GetFunctionPointer("XInputGetState"));
484 if (!xinput_get_state_)
486 xinput_enable_(true);
490 } // namespace content