Commit 8f51909b authored by Matt Reynolds's avatar Matt Reynolds Committed by Commit Bot

Support gamepad buttons with HID Consumer usages

Most gamepads report their button state in a HID output report that
associates HID usages with the state of each button. For most gamepads,
the HID usages associated with buttons come from the Button
usage page.

For some gamepads, particularly those designed to work on Android
devices, the Back and Home buttons are associated with usages outside
the normal set of gamepad usages. For instance, the Xbox One S
gamepad, when connected over Bluetooth, sends a SystemMainMenu usage
for the Home button. The same gamepad, after receiving a firmware
update to improve Android compatibility, reports the Home and Back
buttons as Home and Back usages on the Consumer usage page.

On Linux, we use the joydev input driver to enumerate connected gamepads
and fetch the current gamepad state. However, HID buttons outside the
normal set of gamepad usages are not reported through joydev and must
be read through evdev instead.

This CL allows these extra buttons to be detected and mapped by Chrome.

BUG=824871

Change-Id: Ib873948c6632edb36675f45c948190cb99ce126e
Reviewed-on: https://chromium-review.googlesource.com/1091464Reviewed-by: default avatarBrandon Jones <bajones@chromium.org>
Commit-Queue: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569439}
parent c71c2c14
......@@ -12,6 +12,7 @@
#include <sys/ioctl.h>
#include "base/posix/eintr_wrapper.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "device/gamepad/gamepad_data_fetcher.h"
......@@ -28,6 +29,19 @@ const float kMaxLinuxAxisValue = 32767.0;
const int kInvalidEffectId = -1;
const uint16_t kRumbleMagnitudeMax = 0xffff;
const size_t kSpecialKeys[] = {
// Xbox One S pre-FW update reports Xbox button as SystemMainMenu over BT.
KEY_MENU,
// Power is used for the Guide button on the Nvidia Shield 2015 gamepad.
KEY_POWER,
// Search is used for the Guide button on the Nvidia Shield 2015 gamepad.
KEY_SEARCH,
// Start, Back, and Guide buttons are often reported as Consumer Home or
// Back.
KEY_HOMEPAGE, KEY_BACK,
};
const size_t kSpecialKeysLen = base::size(kSpecialKeys);
#define LONG_BITS (CHAR_BIT * sizeof(long))
#define BITS_TO_LONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS)
......@@ -62,6 +76,37 @@ bool HasRumbleCapability(int fd) {
return test_bit(FF_RUMBLE, ffbit);
}
// Check an evdev device for key codes which sometimes appear on gamepads but
// aren't reported by joydev. If a special key is found, the corresponding entry
// of the |has_special_key| vector is set to true. Returns the number of
// special keys found.
size_t CheckSpecialKeys(int fd, std::vector<bool>* has_special_key) {
DCHECK(has_special_key);
unsigned long evbit[BITS_TO_LONGS(EV_MAX)];
unsigned long keybit[BITS_TO_LONGS(KEY_MAX)];
size_t found_special_keys = 0;
has_special_key->clear();
if (HANDLE_EINTR(ioctl(fd, EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
HANDLE_EINTR(ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), keybit)) < 0) {
return 0;
}
if (!test_bit(EV_KEY, evbit)) {
return 0;
}
has_special_key->resize(kSpecialKeysLen, false);
for (size_t special_index = 0; special_index < kSpecialKeysLen;
++special_index) {
(*has_special_key)[special_index] =
test_bit(kSpecialKeys[special_index], keybit);
++found_special_keys;
}
return found_special_keys;
}
bool GetHidrawDevinfo(int fd,
GamepadBusType* bus_type,
uint16_t* vendor_id,
......@@ -123,11 +168,7 @@ bool StartOrStopEffect(int fd, int effect_id, bool do_start) {
GamepadDeviceLinux::GamepadDeviceLinux(const std::string& syspath_prefix)
: syspath_prefix_(syspath_prefix),
joydev_fd_(-1),
joydev_index_(-1),
evdev_fd_(-1),
effect_id_(kInvalidEffectId),
hidraw_fd_(-1) {}
button_indices_used_(Gamepad::kButtonsLengthCap, false) {}
GamepadDeviceLinux::~GamepadDeviceLinux() = default;
......@@ -153,7 +194,7 @@ bool GamepadDeviceLinux::SupportsVibration() const {
return supports_force_feedback_ && evdev_fd_ >= 0;
}
void GamepadDeviceLinux::ReadPadState(Gamepad* pad) const {
void GamepadDeviceLinux::ReadPadState(Gamepad* pad) {
if (switch_pro_ && bus_type_ == GAMEPAD_BUS_USB) {
// When connected over USB, the Switch Pro controller does not correctly
// report its state over USB HID. Instead, fetch the state using the
......@@ -164,8 +205,34 @@ void GamepadDeviceLinux::ReadPadState(Gamepad* pad) const {
DCHECK_GE(joydev_fd_, 0);
js_event event;
// Read button and axis events from the joydev device.
bool pad_updated = ReadJoydevState(pad);
// Evdev special buttons must be initialized after we have read from joydev
// at least once to ensure we do not assign a button index already in use by
// joydev.
if (!evdev_special_keys_initialized_)
InitializeEvdevSpecialKeys();
// Read button events from the evdev device.
if (!special_button_map_.empty()) {
if (ReadEvdevSpecialKeys(pad))
pad_updated = true;
}
if (pad_updated)
pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
}
bool GamepadDeviceLinux::ReadJoydevState(Gamepad* pad) {
DCHECK(pad);
if (joydev_fd_ < 0)
return false;
// Read button and axis events from the joydev device.
bool pad_updated = false;
js_event event;
while (HANDLE_EINTR(read(joydev_fd_, &event, sizeof(struct js_event))) > 0) {
size_t item = event.number;
if (event.type & JS_EVENT_AXIS) {
......@@ -184,13 +251,90 @@ void GamepadDeviceLinux::ReadPadState(Gamepad* pad) const {
pad->buttons[item].pressed = event.value;
pad->buttons[item].value = event.value ? 1.0 : 0.0;
// When a joydev device is opened, synthetic events are generated for
// each joystick button and axis with the JS_EVENT_INIT flag set on the
// event type. Use this signal to mark these button indices as used.
if (event.type & JS_EVENT_INIT)
button_indices_used_[item] = true;
if (item >= pad->buttons_length)
pad->buttons_length = item + 1;
pad_updated = true;
}
}
if (pad_updated)
pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
return pad_updated;
}
void GamepadDeviceLinux::InitializeEvdevSpecialKeys() {
if (evdev_fd_ < 0)
return;
// Do some one-time initialization to decide indices for the evdev special
// buttons.
evdev_special_keys_initialized_ = true;
std::vector<bool> special_key_present;
size_t unmapped_button_count =
CheckSpecialKeys(evdev_fd_, &special_key_present);
special_button_map_.clear();
if (unmapped_button_count > 0) {
// Insert special buttons at unused button indices.
special_button_map_.resize(kSpecialKeysLen, -1);
size_t button_index = 0;
for (size_t special_index = 0; special_index < kSpecialKeysLen;
++special_index) {
if (!special_key_present[special_index])
continue;
// Advance to the next unused button index.
while (button_indices_used_[button_index] &&
button_index < Gamepad::kButtonsLengthCap) {
++button_index;
}
if (button_index >= Gamepad::kButtonsLengthCap)
break;
special_button_map_[special_index] = button_index;
button_indices_used_[button_index] = true;
++button_index;
if (--unmapped_button_count == 0)
break;
}
}
}
bool GamepadDeviceLinux::ReadEvdevSpecialKeys(Gamepad* pad) {
DCHECK(pad);
if (evdev_fd_ < 0)
return false;
// Read special button events through evdev.
bool pad_updated = false;
input_event ev;
ssize_t bytes_read;
while ((bytes_read =
HANDLE_EINTR(read(evdev_fd_, &ev, sizeof(input_event)))) > 0) {
if (size_t{bytes_read} < sizeof(input_event))
break;
if (ev.type != EV_KEY)
continue;
for (size_t special_index = 0; special_index < kSpecialKeysLen;
++special_index) {
int button_index = special_button_map_[special_index];
if (button_index < 0)
continue;
if (ev.code == kSpecialKeys[special_index]) {
pad->buttons[button_index].pressed = ev.value;
pad->buttons[button_index].value = ev.value ? 1.0 : 0.0;
pad_updated = true;
}
}
}
return pad_updated;
}
GamepadStandardMappingFunction GamepadDeviceLinux::GetMappingFunction() const {
......@@ -276,6 +420,11 @@ void GamepadDeviceLinux::CloseJoydevNode() {
product_id_.clear();
version_number_.clear();
name_.clear();
// Button indices must be recomputed once the joydev node is closed.
button_indices_used_.clear();
special_button_map_.clear();
evdev_special_keys_initialized_ = false;
}
bool GamepadDeviceLinux::OpenEvdevNode(const UdevGamepadLinux& pad_info) {
......@@ -303,6 +452,16 @@ void GamepadDeviceLinux::CloseEvdevNode() {
evdev_fd_ = -1;
}
supports_force_feedback_ = false;
// Clear any entries in |button_indices_used_| that were taken by evdev.
if (!special_button_map_.empty()) {
for (int button_index : special_button_map_) {
if (button_index >= 0)
button_indices_used_[button_index] = false;
}
}
special_button_map_.clear();
evdev_special_keys_initialized_ = false;
}
bool GamepadDeviceLinux::OpenHidrawNode(const UdevGamepadLinux& pad_info) {
......
......@@ -5,6 +5,10 @@
#ifndef DEVICE_GAMEPAD_GAMEPAD_DEVICE_LINUX_
#define DEVICE_GAMEPAD_GAMEPAD_DEVICE_LINUX_
#include <memory>
#include <string>
#include <vector>
#include "device/gamepad/abstract_haptic_gamepad.h"
#include "device/gamepad/dualshock4_controller_linux.h"
#include "device/gamepad/gamepad_standard_mappings.h"
......@@ -48,7 +52,19 @@ class GamepadDeviceLinux : public AbstractHapticGamepad {
bool SupportsVibration() const;
// Reads the current gamepad state into |pad|.
void ReadPadState(Gamepad* pad) const;
void ReadPadState(Gamepad* pad);
// Reads the state of gamepad buttons and axes using joydev. Returns true if
// |pad| was updated.
bool ReadJoydevState(Gamepad* pad);
// Discovers and assigns button indices for key codes that are outside the
// normal gamepad button range.
void InitializeEvdevSpecialKeys();
// Reads the state of keys outside the normal button range using evdev.
// Returns true if |pad| was updated.
bool ReadEvdevSpecialKeys(Gamepad* pad);
// Returns true if |pad_info| describes this device.
bool IsSameDevice(const UdevGamepadLinux& pad_info);
......@@ -90,13 +106,17 @@ class GamepadDeviceLinux : public AbstractHapticGamepad {
// The file descriptor for the device's joydev node, or -1 if no joydev node
// is associated with this device.
int joydev_fd_;
int joydev_fd_ = -1;
// The index of the device's joydev node, or -1 if unknown.
// The joydev index is the integer at the end of the joydev node path and is
// used to assign the gamepad to a slot. For example, a device with path
// /dev/input/js2 has index 2 and will be assigned to the 3rd gamepad slot.
int joydev_index_;
int joydev_index_ = -1;
// Maps from indices in the Gamepad buttons array to a boolean value
// indicating whether the button index is already mapped.
std::vector<bool> button_indices_used_;
// The vendor ID of the device.
std::string vendor_id_;
......@@ -112,17 +132,25 @@ class GamepadDeviceLinux : public AbstractHapticGamepad {
// The file descriptor for the device's evdev node, or -1 if no evdev node is
// associated with this device.
int evdev_fd_;
int evdev_fd_ = -1;
// The ID of the haptic effect stored on the device, or -1 if none is stored.
int effect_id_;
int effect_id_ = -1;
// True if the device supports rumble effects through the evdev device node.
bool supports_force_feedback_;
bool supports_force_feedback_ = false;
// Set to true once the evdev button capabilities have been checked.
bool evdev_special_keys_initialized_ = false;
// Mapping from "special" index (an index within the kSpecialKeys table) to
// button index (an index within the Gamepad buttons array), or -1 if the
// button is not mapped. Empty if no special buttons are mapped.
std::vector<int> special_button_map_;
// The file descriptor for the device's hidraw node, or -1 if no hidraw node
// is associated with this device.
int hidraw_fd_;
int hidraw_fd_ = -1;
// The type of the bus through which the device is connected, or
// GAMEPAD_BUS_UNKNOWN if the bus type could not be determined.
......
......@@ -58,6 +58,12 @@ class GamepadDeviceMac : public AbstractHapticGamepad {
// Stop vibration and release held resources.
void DoShutdown() override;
// Initialize button capabilities for |gamepad|.
bool AddButtons(Gamepad* gamepad);
// Initialize axis capabilities for |gamepad|.
bool AddAxes(Gamepad* gamepad);
// Return true if this element has a parent collection with a usage page that
// suggests it could be a gamepad.
static bool CheckCollection(IOHIDElementRef element);
......
This diff is collapsed.
......@@ -4,6 +4,7 @@
#include "raw_input_gamepad_device_win.h"
#include "base/stl_util.h"
#include "device/gamepad/gamepad_data_fetcher.h"
namespace device {
......@@ -18,14 +19,41 @@ unsigned long GetBitmask(unsigned short bits) {
return (1 << bits) - 1;
}
const uint32_t kAxisMinimumUsageNumber = 0x30;
const uint32_t kGenericDesktopUsagePage = 0x01;
const uint32_t kGameControlsUsagePage = 0x05;
const uint32_t kButtonUsagePage = 0x09;
const uint32_t kConsumerUsagePage = 0x0c;
const uint32_t kAxisMinimumUsageNumber = 0x30;
const uint32_t kSystemMainMenuUsageNumber = 0x85;
const uint32_t kPowerUsageNumber = 0x30;
const uint32_t kSearchUsageNumber = 0x0221;
const uint32_t kHomeUsageNumber = 0x0223;
const uint32_t kBackUsageNumber = 0x0224;
// Blacklisted vendor IDs.
const uint32_t kVendorOculus = 0x2833;
const uint32_t kVendorBlue = 0xb58e;
// The fetcher will collect all HID usages from the Button usage page and any
// additional usages listed below.
struct SpecialUsages {
const uint16_t usage_page;
const uint16_t usage;
} kSpecialUsages[] = {
// Xbox One S pre-FW update reports Xbox button as SystemMainMenu over BT.
{kGenericDesktopUsagePage, kSystemMainMenuUsageNumber},
// Power is used for the Guide button on the Nvidia Shield 2015 gamepad.
{kConsumerUsagePage, kPowerUsageNumber},
// Search is used for the Guide button on the Nvidia Shield 2017 gamepad.
{kConsumerUsagePage, kSearchUsageNumber},
// Start, Back, and Guide buttons are often reported as Consumer Home or
// Back.
{kConsumerUsagePage, kHomeUsageNumber},
{kConsumerUsagePage, kBackUsageNumber},
};
const size_t kSpecialUsagesLen = base::size(kSpecialUsages);
} // namespace
RawInputGamepadDeviceWin::RawInputGamepadDeviceWin(
......@@ -34,6 +62,7 @@ RawInputGamepadDeviceWin::RawInputGamepadDeviceWin(
HidDllFunctionsWin* hid_functions)
: handle_(device_handle),
source_id_(source_id),
last_update_timestamp_(GamepadDataFetcher::CurrentTimeInMicroseconds()),
hid_functions_(hid_functions) {
::ZeroMemory(buttons_, sizeof(buttons_));
::ZeroMemory(axes_, sizeof(axes_));
......@@ -87,11 +116,24 @@ void RawInputGamepadDeviceWin::UpdateGamepad(RAWINPUT* input) {
if (status == HIDP_STATUS_SUCCESS) {
// Set each reported button to true.
for (uint32_t j = 0; j < buttons_length; j++) {
int32_t button_index = usages[j].Usage - 1;
if (usages[j].UsagePage == kButtonUsagePage && button_index >= 0 &&
button_index < static_cast<int>(Gamepad::kButtonsLengthCap)) {
buttons_[button_index] = true;
for (size_t j = 0; j < buttons_length; j++) {
uint16_t usage_page = usages[j].UsagePage;
uint16_t usage = usages[j].Usage;
if (usage_page == kButtonUsagePage && usage > 0) {
size_t button_index = size_t{usage - 1};
if (button_index < Gamepad::kButtonsLengthCap)
buttons_[button_index] = true;
} else if (usage_page != kButtonUsagePage &&
!special_button_map_.empty()) {
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
int button_index = special_button_map_[special_index];
if (button_index < 0)
continue;
const auto& special = kSpecialUsages[special_index];
if (usage_page == special.usage_page && usage == special.usage)
buttons_[button_index] = true;
}
}
}
}
......@@ -128,12 +170,14 @@ void RawInputGamepadDeviceWin::UpdateGamepad(RAWINPUT* input) {
}
}
}
last_update_timestamp_ = GamepadDataFetcher::CurrentTimeInMicroseconds();
}
void RawInputGamepadDeviceWin::ReadPadState(Gamepad* pad) const {
DCHECK(pad);
pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
pad->timestamp = last_update_timestamp_;
pad->buttons_length = buttons_length_;
pad->axes_length = axes_length_;
......@@ -305,16 +349,98 @@ void RawInputGamepadDeviceWin::QueryButtonCapabilities(uint16_t button_count) {
HidP_Input, button_caps.get(), &button_count, preparsed_data_);
DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
for (size_t i = 0; i < button_count; ++i) {
if (button_caps[i].Range.UsageMin <= Gamepad::kButtonsLengthCap &&
button_caps[i].UsagePage == kButtonUsagePage) {
size_t max_index =
std::min(Gamepad::kButtonsLengthCap,
static_cast<size_t>(button_caps[i].Range.UsageMax));
buttons_length_ = std::max(buttons_length_, max_index);
// Keep track of which button indices are in use.
std::vector<bool> button_indices_used(Gamepad::kButtonsLengthCap, false);
// Collect all inputs from the Button usage page.
QueryNormalButtonCapabilities(button_caps.get(), button_count,
&button_indices_used);
// Check for common gamepad buttons that are not on the Button usage page.
QuerySpecialButtonCapabilities(button_caps.get(), button_count,
&button_indices_used);
}
}
void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities(
HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used) {
DCHECK(button_caps);
DCHECK(button_indices_used);
// Collect all inputs from the Button usage page and assign button indices
// based on the usage value.
for (size_t i = 0; i < button_count; ++i) {
uint16_t usage_page = button_caps[i].UsagePage;
uint16_t usage_min = button_caps[i].Range.UsageMin;
uint16_t usage_max = button_caps[i].Range.UsageMax;
if (usage_min == 0 || usage_max == 0)
continue;
size_t button_index_min = size_t{usage_min - 1};
size_t button_index_max = size_t{usage_max - 1};
if (usage_page == kButtonUsagePage &&
button_index_min < Gamepad::kButtonsLengthCap) {
button_index_max =
std::min(Gamepad::kButtonsLengthCap - 1, button_index_max);
buttons_length_ = std::max(buttons_length_, button_index_max + 1);
for (size_t j = button_index_min; j <= button_index_max; ++j)
(*button_indices_used)[j] = true;
}
}
}
void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities(
HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used) {
DCHECK(button_caps);
DCHECK(button_indices_used);
// Check for common gamepad buttons that are not on the Button usage page.
std::vector<bool> has_special_usage(kSpecialUsagesLen, false);
size_t unmapped_button_count = 0;
for (size_t i = 0; i < button_count; ++i) {
uint16_t usage_page = button_caps[i].UsagePage;
uint16_t usage_min = button_caps[i].Range.UsageMin;
uint16_t usage_max = button_caps[i].Range.UsageMax;
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
const auto& special = kSpecialUsages[special_index];
if (usage_page == special.usage_page && usage_min <= special.usage &&
usage_max >= special.usage) {
has_special_usage[special_index] = true;
++unmapped_button_count;
}
}
}
special_button_map_.clear();
if (unmapped_button_count > 0) {
// Insert special buttons at unused button indices.
special_button_map_.resize(kSpecialUsagesLen, -1);
size_t button_index = 0;
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
if (!has_special_usage[special_index])
continue;
// Advance to the next unused button index.
while (button_index < Gamepad::kButtonsLengthCap &&
(*button_indices_used)[button_index]) {
++button_index;
}
if (button_index >= Gamepad::kButtonsLengthCap)
break;
special_button_map_[special_index] = button_index;
(*button_indices_used)[button_index] = true;
++button_index;
if (--unmapped_button_count == 0)
break;
}
}
}
void RawInputGamepadDeviceWin::QueryAxisCapabilities(uint16_t axis_count) {
......
......@@ -13,6 +13,7 @@
#include <windows.h>
#include <memory>
#include <vector>
#include "device/gamepad/abstract_haptic_gamepad.h"
#include "device/gamepad/dualshock4_controller_win.h"
......@@ -87,6 +88,12 @@ class RawInputGamepadDeviceWin : public AbstractHapticGamepad {
// on the device.
bool QueryDeviceCapabilities();
void QueryButtonCapabilities(uint16_t button_count);
void QueryNormalButtonCapabilities(HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used);
void QuerySpecialButtonCapabilities(HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used);
void QueryAxisCapabilities(uint16_t axis_count);
// True if the device described by this object is a valid RawInput gamepad.
......@@ -98,6 +105,9 @@ class RawInputGamepadDeviceWin : public AbstractHapticGamepad {
// The index assigned to this gamepad by the data fetcher.
int source_id_ = 0;
// The last time the pad state was updated.
int64_t last_update_timestamp_;
// Functions loaded from hid.dll. Not owned.
HidDllFunctionsWin* hid_functions_ = nullptr;
......@@ -115,6 +125,11 @@ class RawInputGamepadDeviceWin : public AbstractHapticGamepad {
size_t buttons_length_ = 0;
bool buttons_[Gamepad::kButtonsLengthCap];
// Mapping from "Special" usage index (defined by the kSpecialUsages table)
// to an index within the |buttons_| array, or -1 if the special usage is not
// mapped for this device.
std::vector<int> special_button_map_;
size_t axes_length_ = 0;
RawGamepadAxis axes_[Gamepad::kAxesLengthCap];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment