blob: d59ac180aa83291b3af0968e9fde1d4335c8f643 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h"
#include <dinput.h>
#include <dinputd.h>
#include "base/debug/trace_event.h"
#include "base/strings/stringprintf.h"
#include "base/win/windows_version.h"
#include "content/common/gamepad_hardware_buffer.h"
#include "content/common/gamepad_messages.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;
namespace {
// See http://goo.gl/5VSJR. These are not available in all versions of the
// header, but they can be returned from the driver, so we define our own
// versions here.
static const BYTE kDeviceSubTypeGamepad = 1;
static const BYTE kDeviceSubTypeWheel = 2;
static const BYTE kDeviceSubTypeArcadeStick = 3;
static const BYTE kDeviceSubTypeFlightStick = 4;
static const BYTE kDeviceSubTypeDancePad = 5;
static const BYTE kDeviceSubTypeGuitar = 6;
static const BYTE kDeviceSubTypeGuitarAlternate = 7;
static const BYTE kDeviceSubTypeDrumKit = 8;
static const BYTE kDeviceSubTypeGuitarBass = 11;
static const BYTE kDeviceSubTypeArcadePad = 19;
float NormalizeXInputAxis(SHORT value) {
return ((value + 32768.f) / 32767.5f) - 1.f;
}
const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
switch (sub_type) {
case kDeviceSubTypeGamepad: return L"GAMEPAD";
case kDeviceSubTypeWheel: return L"WHEEL";
case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
case kDeviceSubTypeDancePad: return L"DANCE_PAD";
case kDeviceSubTypeGuitar: return L"GUITAR";
case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
default: return L"<UNKNOWN>";
}
}
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()) {
// TODO(teravest): http://crbug.com/260187
if (base::win::GetVersion() > base::win::VERSION_XP) {
directinput_available_ = SUCCEEDED(DirectInput8Create(
GetModuleHandle(NULL),
DIRECTINPUT_VERSION,
IID_IDirectInput8,
reinterpret_cast<void**>(&directinput_interface_),
NULL));
} else {
directinput_available_ = false;
}
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) {
TRACE_EVENT0("GAMEPAD", "GetGamepadData");
if (!xinput_available_ && !directinput_available_) {
pads->length = 0;
return;
}
// 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)
EnumerateDevices(pads);
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;
}
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);
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++] = 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() {
xinput_get_capabilities_ = NULL;
xinput_get_state_ = NULL;
xinput_enable_ = reinterpret_cast<XInputEnableFunc>(
xinput_dll_.GetFunctionPointer("XInputEnable"));
if (!xinput_enable_)
return false;
xinput_get_capabilities_ = reinterpret_cast<XInputGetCapabilitiesFunc>(
xinput_dll_.GetFunctionPointer("XInputGetCapabilities"));
if (!xinput_get_capabilities_)
return false;
xinput_get_state_ = reinterpret_cast<XInputGetStateFunc>(
xinput_dll_.GetFunctionPointer("XInputGetState"));
if (!xinput_get_state_)
return false;
xinput_enable_(true);
return true;
}
} // namespace content