diff options
author | teravest@chromium.org <teravest@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-21 01:09:26 +0000 |
---|---|---|
committer | teravest@chromium.org <teravest@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-21 01:09:26 +0000 |
commit | 8cf3e8e05c35bbd056b7848eb059f773f329f703 (patch) | |
tree | a2501b9640aaf16f6c49045ce1275cb707ab0d92 /content/browser/gamepad/gamepad_platform_data_fetcher_win.cc | |
parent | 19ae39fbc1cd896d32c293fea402fd8b46b6c2e7 (diff) | |
download | chromium_src-8cf3e8e05c35bbd056b7848eb059f773f329f703.zip chromium_src-8cf3e8e05c35bbd056b7848eb059f773f329f703.tar.gz chromium_src-8cf3e8e05c35bbd056b7848eb059f773f329f703.tar.bz2 |
Support DirectInput gamepads on Windows.
This change adds generic support for gamepads which support DirectInput but do
not support XInput. It only adds support for recognized controllers to
prevent gamepads from only partially working.
This change only adds explicit support for the "DragonRise Generic USB"
controller but it should be easy to add follow-on patches for different
controllers (as is done for Linux and Mac).
Tested:
I tested this change with a DirectInput controller, with and without an
XInput controller also connected, and ensured that the XInput
controller's desired ID took precedence. All buttons and axes appear to
work fine.
BUG=crbug.com/110013
Review URL: https://chromiumcodereview.appspot.com/12260011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@183689 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/gamepad/gamepad_platform_data_fetcher_win.cc')
-rw-r--r-- | content/browser/gamepad/gamepad_platform_data_fetcher_win.cc | 462 |
1 files changed, 393 insertions, 69 deletions
diff --git a/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc b/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc index 937b9a2..37af979 100644 --- a/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc +++ b/content/browser/gamepad/gamepad_platform_data_fetcher_win.cc @@ -4,10 +4,21 @@ #include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h" +#include <dinput.h> +#include <dinputd.h> + #include "base/debug/trace_event.h" +#include "base/stringprintf.h" #include "content/common/gamepad_messages.h" #include "content/common/gamepad_hardware_buffer.h" +// This was removed from the Windows 8 SDK for some reason. +// We need it so we can get state for axes without worrying if they +// exist. +#ifndef DIDFT_OPTIONAL +#define DIDFT_OPTIONAL 0x80000000 +#endif + namespace content { using namespace WebKit; @@ -28,7 +39,7 @@ static const BYTE kDeviceSubTypeDrumKit = 8; static const BYTE kDeviceSubTypeGuitarBass = 11; static const BYTE kDeviceSubTypeArcadePad = 19; -float NormalizeAxis(SHORT value) { +float NormalizeXInputAxis(SHORT value) { return ((value + 32768.f) / 32767.5f) - 1.f; } @@ -48,105 +59,418 @@ const WebUChar* const GamepadSubTypeName(BYTE sub_type) { } } +bool GetDirectInputVendorProduct(IDirectInputDevice8* gamepad, + std::string* vendor, + std::string* product) { + DIPROPDWORD prop; + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + + if (FAILED(gamepad->GetProperty(DIPROP_VIDPID, &prop.diph))) + return false; + *vendor = base::StringPrintf("%04x", LOWORD(prop.dwData)); + *product = base::StringPrintf("%04x", HIWORD(prop.dwData)); + return true; +} + +// Sets the deadzone value for all axes of a gamepad. +// deadzone values range from 0 (no deadzone) to 10,000 (entire range +// is dead). +bool SetDirectInputDeadZone(IDirectInputDevice8* gamepad, + int deadzone) { + DIPROPDWORD prop; + prop.diph.dwSize = sizeof(DIPROPDWORD); + prop.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + prop.dwData = deadzone; + return SUCCEEDED(gamepad->SetProperty(DIPROP_DEADZONE, &prop.diph)); +} + +struct InternalDirectInputDevice { + IDirectInputDevice8* gamepad; + GamepadStandardMappingFunction mapper; + wchar_t id[WebGamepad::idLengthCap]; + GUID guid; +}; + +struct EnumDevicesContext { + IDirectInput8* directinput_interface; + std::vector<InternalDirectInputDevice>* directinput_devices; +}; + +// We define our own data format structure to attempt to get as many +// axes as possible. +struct JoyData { + long axes[10]; + char buttons[24]; + DWORD pov; // Often used for D-pads. +}; + +BOOL CALLBACK DirectInputEnumDevicesCallback(const DIDEVICEINSTANCE* instance, + void* context) { + EnumDevicesContext* ctxt = reinterpret_cast<EnumDevicesContext*>(context); + IDirectInputDevice8* gamepad; + + if (FAILED(ctxt->directinput_interface->CreateDevice(instance->guidInstance, + &gamepad, + NULL))) + return DIENUM_CONTINUE; + + gamepad->Acquire(); + +#define MAKE_AXIS(i) \ + {0, FIELD_OFFSET(JoyData, axes) + 4 * i, \ + DIDFT_AXIS | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} +#define MAKE_BUTTON(i) \ + {&GUID_Button, FIELD_OFFSET(JoyData, buttons) + i, \ + DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i) | DIDFT_OPTIONAL, 0} +#define MAKE_POV() \ + {&GUID_POV, FIELD_OFFSET(JoyData, pov), DIDFT_POV | DIDFT_OPTIONAL, 0} + DIOBJECTDATAFORMAT rgodf[] = { + MAKE_AXIS(0), + MAKE_AXIS(1), + MAKE_AXIS(2), + MAKE_AXIS(3), + MAKE_AXIS(4), + MAKE_AXIS(5), + MAKE_AXIS(6), + MAKE_AXIS(7), + MAKE_AXIS(8), + MAKE_AXIS(9), + MAKE_BUTTON(0), + MAKE_BUTTON(1), + MAKE_BUTTON(2), + MAKE_BUTTON(3), + MAKE_BUTTON(4), + MAKE_BUTTON(5), + MAKE_BUTTON(6), + MAKE_BUTTON(7), + MAKE_BUTTON(8), + MAKE_BUTTON(9), + MAKE_BUTTON(10), + MAKE_BUTTON(11), + MAKE_BUTTON(12), + MAKE_BUTTON(13), + MAKE_BUTTON(14), + MAKE_BUTTON(15), + MAKE_BUTTON(16), + MAKE_POV(), + }; +#undef MAKE_AXIS +#undef MAKE_BUTTON +#undef MAKE_POV + + DIDATAFORMAT df = { + sizeof (DIDATAFORMAT), + sizeof (DIOBJECTDATAFORMAT), + DIDF_ABSAXIS, + sizeof (JoyData), + sizeof (rgodf) / sizeof (rgodf[0]), + rgodf + }; + + // If we can't set the data format on the device, don't add it to our + // list, since we won't know how to read data from it. + if (FAILED(gamepad->SetDataFormat(&df))) { + gamepad->Release(); + return DIENUM_CONTINUE; + } + + InternalDirectInputDevice device; + device.guid = instance->guidInstance; + device.gamepad = gamepad; + std::string vendor; + std::string product; + if (!GetDirectInputVendorProduct(gamepad, &vendor, &product)) { + gamepad->Release(); + return DIENUM_CONTINUE; + } + + // Set the dead zone to 10% of the axis length for all axes. This + // gives us a larger space for what's "neutral" so the controls don't + // slowly drift. + SetDirectInputDeadZone(gamepad, 1000); + device.mapper = GetGamepadStandardMappingFunction(vendor, product); + if (device.mapper) { + base::swprintf(device.id, + WebGamepad::idLengthCap, + L"STANDARD GAMEPAD (%ls)", + instance->tszProductName); + ctxt->directinput_devices->push_back(device); + } else { + gamepad->Release(); + } + return DIENUM_CONTINUE; +} + } // namespace GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin() : xinput_dll_(base::FilePath(FILE_PATH_LITERAL("xinput1_3.dll"))), - xinput_available_(GetXinputDllFunctions()) { + xinput_available_(GetXInputDllFunctions()) { + directinput_available_ = SUCCEEDED(DirectInput8Create( + GetModuleHandle(NULL), + DIRECTINPUT_VERSION, + IID_IDirectInput8, + reinterpret_cast<void**>(&directinput_interface_), + NULL)); + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) + pad_state_[i].status = DISCONNECTED; } GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DIRECTINPUT_CONNECTED) + pad_state_[i].directinput_gamepad->Release(); + } } +int GamepadPlatformDataFetcherWin::FirstAvailableGamepadId() const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DISCONNECTED) + return i; + } + return -1; +} + +bool GamepadPlatformDataFetcherWin::HasXInputGamepad(int index) const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == XINPUT_CONNECTED && + pad_state_[i].xinput_index == index) + return true; + } + return false; +} + +bool GamepadPlatformDataFetcherWin::HasDirectInputGamepad( + const GUID& guid) const { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (pad_state_[i].status == DIRECTINPUT_CONNECTED && + pad_state_[i].guid == guid) + return true; + } + return false; +} + +void GamepadPlatformDataFetcherWin::EnumerateDevices( + WebGamepads* pads) { + TRACE_EVENT0("GAMEPAD", "EnumerateDevices"); + + // Mark all disconnected pads DISCONNECTED. + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { + if (!pads->items[i].connected) + pad_state_[i].status = DISCONNECTED; + } + + for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) { + if (HasXInputGamepad(i)) + continue; + int pad_index = FirstAvailableGamepadId(); + if (pad_index == -1) + return; // We can't add any more gamepads. + WebGamepad& pad = pads->items[pad_index]; + if (xinput_available_ && GetXInputPadConnectivity(i, &pad)) { + pad_state_[pad_index].status = XINPUT_CONNECTED; + pad_state_[pad_index].xinput_index = i; + } + } + + if (directinput_available_) { + struct EnumDevicesContext context; + std::vector<InternalDirectInputDevice> directinput_gamepads; + context.directinput_interface = directinput_interface_; + context.directinput_devices = &directinput_gamepads; + + directinput_interface_->EnumDevices( + DI8DEVCLASS_GAMECTRL, + &DirectInputEnumDevicesCallback, + &context, + DIEDFL_ATTACHEDONLY); + for (size_t i = 0; i < directinput_gamepads.size(); ++i) { + if (HasDirectInputGamepad(directinput_gamepads[i].guid)) { + directinput_gamepads[i].gamepad->Release(); + continue; + } + int pad_index = FirstAvailableGamepadId(); + if (pad_index == -1) + return; + WebGamepad& pad = pads->items[pad_index]; + pad.connected = true; + wcscpy_s(pad.id, WebGamepad::idLengthCap, directinput_gamepads[i].id); + PadState& state = pad_state_[pad_index]; + state.status = DIRECTINPUT_CONNECTED; + state.guid = directinput_gamepads[i].guid; + state.directinput_gamepad = directinput_gamepads[i].gamepad; + state.mapper = directinput_gamepads[i].mapper; + } + } +} + + void GamepadPlatformDataFetcherWin::GetGamepadData(WebGamepads* pads, - bool devices_changed_hint) { + bool devices_changed_hint) { TRACE_EVENT0("GAMEPAD", "GetGamepadData"); - // If there's no XInput DLL on the system, early out so that we don't - // call any other XInput functions. - if (!xinput_available_) { + if (!xinput_available_ && !directinput_available_) { pads->length = 0; return; } - pads->length = WebGamepads::itemsLengthCap; - + // A note on XInput devices: // If we got notification that system devices have been updated, then // run GetCapabilities to update the connected status and the device // identifier. It can be slow to do to both GetCapabilities and // GetState on unconnected devices, so we want to avoid a 2-5ms pause // here by only doing this when the devices are updated (despite // documentation claiming it's OK to call it any time). - if (devices_changed_hint) { - for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { - WebGamepad& pad = pads->items[i]; - TRACE_EVENT1("GAMEPAD", "GetCapabilities", "id", i); - XINPUT_CAPABILITIES caps; - DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); - if (res == ERROR_DEVICE_NOT_CONNECTED) { - pad.connected = false; - } else { - pad.connected = true; - base::swprintf(pad.id, - WebGamepad::idLengthCap, - L"Xbox 360 Controller (XInput STANDARD %ls)", - GamepadSubTypeName(caps.SubType)); - } - } - } + if (devices_changed_hint) + EnumerateDevices(pads); - // We've updated the connection state if necessary, now update the actual - // data for the devices that are connected. - for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) { + for (size_t i = 0; i < WebGamepads::itemsLengthCap; ++i) { WebGamepad& pad = pads->items[i]; + if (pad_state_[i].status == XINPUT_CONNECTED) + GetXInputPadData(i, &pad); + else if (pad_state_[i].status == DIRECTINPUT_CONNECTED) + GetDirectInputPadData(i, &pad); + } + pads->length = WebGamepads::itemsLengthCap; +} - // We rely on device_changed and GetCapabilities to tell us that - // something's been connected, but we will mark as disconnected if - // GetState returns that we've lost the pad. - if (!pad.connected) - continue; +bool GamepadPlatformDataFetcherWin::GetXInputPadConnectivity( + int i, + WebGamepad* pad) const { + DCHECK(pad); + TRACE_EVENT1("GAMEPAD", "GetXInputPadConnectivity", "id", i); + XINPUT_CAPABILITIES caps; + DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); + if (res == ERROR_DEVICE_NOT_CONNECTED) { + pad->connected = false; + return false; + } else { + pad->connected = true; + base::swprintf(pad->id, + WebGamepad::idLengthCap, + L"Xbox 360 Controller (XInput STANDARD %ls)", + GamepadSubTypeName(caps.SubType)); + return true; + } +} + +void GamepadPlatformDataFetcherWin::GetXInputPadData( + int i, + WebGamepad* pad) { + // We rely on device_changed and GetCapabilities to tell us that + // something's been connected, but we will mark as disconnected if + // GetState returns that we've lost the pad. + if (!pad->connected) + return; + + XINPUT_STATE state; + memset(&state, 0, sizeof(XINPUT_STATE)); + TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); + DWORD dwResult = xinput_get_state_(pad_state_[i].xinput_index, &state); + TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); - XINPUT_STATE state; - memset(&state, 0, sizeof(XINPUT_STATE)); - TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i); - DWORD dwResult = xinput_get_state_(i, &state); - TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i); - - if (dwResult == ERROR_SUCCESS) { - pad.timestamp = state.dwPacketNumber; - pad.buttonsLength = 0; -#define ADD(b) pad.buttons[pad.buttonsLength++] = \ - ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0); - ADD(XINPUT_GAMEPAD_A); - ADD(XINPUT_GAMEPAD_B); - ADD(XINPUT_GAMEPAD_X); - ADD(XINPUT_GAMEPAD_Y); - ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); - ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); - pad.buttons[pad.buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0; - pad.buttons[pad.buttonsLength++] = state.Gamepad.bRightTrigger / 255.0; - ADD(XINPUT_GAMEPAD_BACK); - ADD(XINPUT_GAMEPAD_START); - ADD(XINPUT_GAMEPAD_LEFT_THUMB); - ADD(XINPUT_GAMEPAD_RIGHT_THUMB); - ADD(XINPUT_GAMEPAD_DPAD_UP); - ADD(XINPUT_GAMEPAD_DPAD_DOWN); - ADD(XINPUT_GAMEPAD_DPAD_LEFT); - ADD(XINPUT_GAMEPAD_DPAD_RIGHT); + if (dwResult == ERROR_SUCCESS) { + pad->timestamp = state.dwPacketNumber; + pad->buttonsLength = 0; +#define ADD(b) pad->buttons[pad->buttonsLength++] = \ + ((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0); + ADD(XINPUT_GAMEPAD_A); + ADD(XINPUT_GAMEPAD_B); + ADD(XINPUT_GAMEPAD_X); + ADD(XINPUT_GAMEPAD_Y); + ADD(XINPUT_GAMEPAD_LEFT_SHOULDER); + ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER); + pad->buttons[pad->buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0; + pad->buttons[pad->buttonsLength++] = state.Gamepad.bRightTrigger / 255.0; + ADD(XINPUT_GAMEPAD_BACK); + ADD(XINPUT_GAMEPAD_START); + ADD(XINPUT_GAMEPAD_LEFT_THUMB); + ADD(XINPUT_GAMEPAD_RIGHT_THUMB); + ADD(XINPUT_GAMEPAD_DPAD_UP); + ADD(XINPUT_GAMEPAD_DPAD_DOWN); + ADD(XINPUT_GAMEPAD_DPAD_LEFT); + ADD(XINPUT_GAMEPAD_DPAD_RIGHT); #undef ADD - pad.axesLength = 0; - // XInput are +up/+right, -down/-left, we want -up/-left. - pad.axes[pad.axesLength++] = NormalizeAxis(state.Gamepad.sThumbLX); - pad.axes[pad.axesLength++] = -NormalizeAxis(state.Gamepad.sThumbLY); - pad.axes[pad.axesLength++] = NormalizeAxis(state.Gamepad.sThumbRX); - pad.axes[pad.axesLength++] = -NormalizeAxis(state.Gamepad.sThumbRY); - } else { - pad.connected = false; + pad->axesLength = 0; + // XInput are +up/+right, -down/-left, we want -up/-left. + pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbLX); + pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbLY); + pad->axes[pad->axesLength++] = NormalizeXInputAxis(state.Gamepad.sThumbRX); + pad->axes[pad->axesLength++] = -NormalizeXInputAxis(state.Gamepad.sThumbRY); + } else { + pad->connected = false; + } +} + +void GamepadPlatformDataFetcherWin::GetDirectInputPadData( + int index, + WebGamepad* pad) { + if (!pad->connected) + return; + + IDirectInputDevice8* gamepad = pad_state_[index].directinput_gamepad; + if (FAILED(gamepad->Poll())) { + // Polling didn't work, try acquiring the gamepad. + if (FAILED(gamepad->Acquire())) { + pad->buttonsLength = 0; + pad->axesLength = 0; + return; } + // Try polling again. + if (FAILED(gamepad->Poll())) { + pad->buttonsLength = 0; + pad->axesLength = 0; + return; + } + } + JoyData state; + if (FAILED(gamepad->GetDeviceState(sizeof(JoyData), &state))) { + pad->connected = false; + return; } + + WebGamepad raw; + raw.connected = true; + for (int i = 0; i < 16; i++) + raw.buttons[i] = (state.buttons[i] & 0x80) ? 1.0 : 0.0; + + // We map the POV (often a D-pad) into the buttons 16-19. + // DirectInput gives pov measurements in hundredths of degrees, + // clockwise from "North". + // We use 22.5 degree slices so we can handle diagonal D-raw presses. + static const int arc_segment = 2250; // 22.5 degrees = 1/16 circle + if (state.pov > arc_segment && state.pov < 7 * arc_segment) + raw.buttons[19] = 1.0; + else + raw.buttons[19] = 0.0; + + if (state.pov > 5 * arc_segment && state.pov < 11 * arc_segment) + raw.buttons[17] = 1.0; + else + raw.buttons[17] = 0.0; + + if (state.pov > 9 * arc_segment && state.pov < 15 * arc_segment) + raw.buttons[18] = 1.0; + else + raw.buttons[18] = 0.0; + + if (state.pov < 3 * arc_segment || + (state.pov > 13 * arc_segment && state.pov < 36000)) + raw.buttons[16] = 1.0; + else + raw.buttons[16] = 0.0; + + for (int i = 0; i < 10; i++) + raw.axes[i] = state.axes[i]; + pad_state_[index].mapper(raw, pad); } -bool GamepadPlatformDataFetcherWin::GetXinputDllFunctions() { +bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { xinput_get_capabilities_ = NULL; xinput_get_state_ = NULL; xinput_enable_ = static_cast<XInputEnableFunc>( |