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