blob: 9c6027516d9db61c01687aa0ab4dbd777e25419d [file] [log] [blame]
// Copyright 2018 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 "device/gamepad/gamepad_device_linux.h"
#include <fcntl.h>
#include <limits.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include <linux/joystick.h>
#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/string_piece.h"
#include "base/strings/stringprintf.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/udev_linux/udev.h"
#if defined(OS_CHROMEOS)
#include "chromeos/dbus/permission_broker/permission_broker_client.h"
#endif // defined(OS_CHROMEOS)
namespace device {
namespace {
const char kInputSubsystem[] = "input";
const char kUsbSubsystem[] = "usb";
const char kUsbDeviceType[] = "usb_device";
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)
static inline bool test_bit(int bit, const unsigned long* data) {
return data[bit / LONG_BITS] & (1UL << (bit % LONG_BITS));
}
GamepadBusType GetEvdevBusType(const base::ScopedFD& fd) {
struct input_id input_info;
if (HANDLE_EINTR(ioctl(fd.get(), EVIOCGID, &input_info)) >= 0) {
if (input_info.bustype == BUS_USB)
return GAMEPAD_BUS_USB;
if (input_info.bustype == BUS_BLUETOOTH)
return GAMEPAD_BUS_BLUETOOTH;
}
return GAMEPAD_BUS_UNKNOWN;
}
bool HasRumbleCapability(const base::ScopedFD& fd) {
unsigned long evbit[BITS_TO_LONGS(EV_MAX)];
unsigned long ffbit[BITS_TO_LONGS(FF_MAX)];
if (HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(EV_FF, FF_MAX), ffbit)) < 0) {
return false;
}
if (!test_bit(EV_FF, evbit)) {
return false;
}
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(const base::ScopedFD& 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.get(), EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
HANDLE_EINTR(ioctl(fd.get(), 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(const base::ScopedFD& fd,
GamepadBusType* bus_type,
uint16_t* vendor_id,
uint16_t* product_id) {
struct hidraw_devinfo info;
if (HANDLE_EINTR(ioctl(fd.get(), HIDIOCGRAWINFO, &info)) < 0)
return false;
if (bus_type) {
if (info.bustype == BUS_USB)
*bus_type = GAMEPAD_BUS_USB;
else if (info.bustype == BUS_BLUETOOTH)
*bus_type = GAMEPAD_BUS_BLUETOOTH;
else
*bus_type = GAMEPAD_BUS_UNKNOWN;
}
if (vendor_id)
*vendor_id = static_cast<uint16_t>(info.vendor);
if (product_id)
*product_id = static_cast<uint16_t>(info.product);
return true;
}
int StoreRumbleEffect(const base::ScopedFD& fd,
int effect_id,
uint16_t duration,
uint16_t start_delay,
uint16_t strong_magnitude,
uint16_t weak_magnitude) {
struct ff_effect effect;
memset(&effect, 0, sizeof(effect));
effect.type = FF_RUMBLE;
effect.id = effect_id;
effect.replay.length = duration;
effect.replay.delay = start_delay;
effect.u.rumble.strong_magnitude = strong_magnitude;
effect.u.rumble.weak_magnitude = weak_magnitude;
if (HANDLE_EINTR(ioctl(fd.get(), EVIOCSFF, (const void*)&effect)) < 0)
return kInvalidEffectId;
return effect.id;
}
void DestroyEffect(const base::ScopedFD& fd, int effect_id) {
HANDLE_EINTR(ioctl(fd.get(), EVIOCRMFF, effect_id));
}
bool StartOrStopEffect(const base::ScopedFD& fd, int effect_id, bool do_start) {
struct input_event start_stop;
memset(&start_stop, 0, sizeof(start_stop));
start_stop.type = EV_FF;
start_stop.code = effect_id;
start_stop.value = do_start ? 1 : 0;
ssize_t nbytes = HANDLE_EINTR(
write(fd.get(), (const void*)&start_stop, sizeof(start_stop)));
return nbytes == sizeof(start_stop);
}
uint16_t HexStringToUInt16WithDefault(base::StringPiece input,
uint16_t default_value) {
uint32_t out = 0;
if (!base::HexStringToUInt(input, &out) ||
out > std::numeric_limits<uint16_t>::max()) {
return default_value;
}
return static_cast<uint16_t>(out);
}
} // namespace
GamepadDeviceLinux::GamepadDeviceLinux(
const std::string& syspath_prefix,
scoped_refptr<base::SequencedTaskRunner> dbus_runner)
: syspath_prefix_(syspath_prefix),
button_indices_used_(Gamepad::kButtonsLengthCap, false),
dbus_runner_(dbus_runner),
polling_runner_(base::SequencedTaskRunnerHandle::Get()) {}
GamepadDeviceLinux::~GamepadDeviceLinux() = default;
void GamepadDeviceLinux::DoShutdown() {
CloseJoydevNode();
CloseEvdevNode();
CloseHidrawNode();
}
bool GamepadDeviceLinux::IsEmpty() const {
return !joydev_fd_.is_valid() && !evdev_fd_.is_valid() &&
!hidraw_fd_.is_valid();
}
bool GamepadDeviceLinux::SupportsVibration() const {
if (dualshock4_ || hid_haptics_)
return true;
return supports_force_feedback_ && evdev_fd_.is_valid();
}
void GamepadDeviceLinux::ReadPadState(Gamepad* pad) {
DCHECK(joydev_fd_.is_valid());
// 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(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(pad);
if (!joydev_fd_.is_valid())
return false;
// Read button and axis events from the joydev device.
bool pad_updated = false;
js_event event;
while (HANDLE_EINTR(read(joydev_fd_.get(), &event, sizeof(struct js_event))) >
0) {
size_t item = event.number;
if (event.type & JS_EVENT_AXIS) {
if (item >= Gamepad::kAxesLengthCap)
continue;
pad->axes[item] = event.value / kMaxLinuxAxisValue;
if (item >= pad->axes_length)
pad->axes_length = item + 1;
pad_updated = true;
} else if (event.type & JS_EVENT_BUTTON) {
if (item >= Gamepad::kButtonsLengthCap)
continue;
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;
}
}
return pad_updated;
}
void GamepadDeviceLinux::InitializeEvdevSpecialKeys() {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (!evdev_fd_.is_valid())
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(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(pad);
if (!evdev_fd_.is_valid())
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_.get(), &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 {
return GetGamepadStandardMappingFunction(vendor_id_, product_id_,
version_number_, bus_type_);
}
bool GamepadDeviceLinux::IsSameDevice(const UdevGamepadLinux& pad_info) {
return pad_info.syspath_prefix == syspath_prefix_;
}
bool GamepadDeviceLinux::OpenJoydevNode(const UdevGamepadLinux& pad_info,
udev_device* device) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(pad_info.type == UdevGamepadLinux::Type::JOYDEV);
DCHECK(pad_info.syspath_prefix == syspath_prefix_);
CloseJoydevNode();
joydev_fd_ =
base::ScopedFD(open(pad_info.path.c_str(), O_RDONLY | O_NONBLOCK));
if (!joydev_fd_.is_valid())
return false;
udev_device* parent_device =
device::udev_device_get_parent_with_subsystem_devtype(
device, kInputSubsystem, nullptr);
const base::StringPiece vendor_id =
udev_device_get_sysattr_value(parent_device, "id/vendor");
const base::StringPiece product_id =
udev_device_get_sysattr_value(parent_device, "id/product");
const base::StringPiece version_number =
udev_device_get_sysattr_value(parent_device, "id/version");
const base::StringPiece name =
udev_device_get_sysattr_value(parent_device, "name");
uint16_t vendor_id_int = HexStringToUInt16WithDefault(vendor_id, 0);
uint16_t product_id_int = HexStringToUInt16WithDefault(product_id, 0);
uint16_t version_number_int = HexStringToUInt16WithDefault(version_number, 0);
// In many cases the information the input subsystem contains isn't
// as good as the information that the device bus has, walk up further
// to the subsystem/device type "usb"/"usb_device" and if this device
// has the same vendor/product id, prefer the description from that.
struct udev_device* usb_device =
udev_device_get_parent_with_subsystem_devtype(
parent_device, kUsbSubsystem, kUsbDeviceType);
std::string name_string(name);
if (usb_device) {
const base::StringPiece usb_vendor_id =
udev_device_get_sysattr_value(usb_device, "idVendor");
const base::StringPiece usb_product_id =
udev_device_get_sysattr_value(usb_device, "idProduct");
if (vendor_id == usb_vendor_id && product_id == usb_product_id) {
const char* manufacturer =
udev_device_get_sysattr_value(usb_device, "manufacturer");
const char* product =
udev_device_get_sysattr_value(usb_device, "product");
if (manufacturer && product) {
// Replace the previous name string with one containing the better
// information.
name_string = base::StringPrintf("%s %s", manufacturer, product);
}
}
}
joydev_index_ = pad_info.index;
vendor_id_ = vendor_id_int;
product_id_ = product_id_int;
version_number_ = version_number_int;
name_ = name_string;
return true;
}
void GamepadDeviceLinux::CloseJoydevNode() {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
joydev_fd_.reset();
joydev_index_ = -1;
vendor_id_ = 0;
product_id_ = 0;
version_number_ = 0;
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) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(pad_info.type == UdevGamepadLinux::Type::EVDEV);
DCHECK(pad_info.syspath_prefix == syspath_prefix_);
CloseEvdevNode();
evdev_fd_ = base::ScopedFD(open(pad_info.path.c_str(), O_RDWR | O_NONBLOCK));
if (!evdev_fd_.is_valid())
return false;
supports_force_feedback_ = HasRumbleCapability(evdev_fd_);
bus_type_ = GetEvdevBusType(evdev_fd_);
return true;
}
void GamepadDeviceLinux::CloseEvdevNode() {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (evdev_fd_.is_valid()) {
if (effect_id_ != kInvalidEffectId) {
DestroyEffect(evdev_fd_, effect_id_);
effect_id_ = kInvalidEffectId;
}
}
evdev_fd_.reset();
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;
}
void GamepadDeviceLinux::OpenHidrawNode(const UdevGamepadLinux& pad_info,
OpenDeviceNodeCallback callback) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(pad_info.type == UdevGamepadLinux::Type::HIDRAW);
DCHECK(pad_info.syspath_prefix == syspath_prefix_);
CloseHidrawNode();
auto fd = base::ScopedFD(open(pad_info.path.c_str(), O_RDWR | O_NONBLOCK));
#if defined(OS_CHROMEOS)
// If we failed to open the device it may be due to insufficient permissions.
// Try again using the PermissionBrokerClient.
if (!fd.is_valid()) {
DCHECK(dbus_runner_);
DCHECK(polling_runner_);
auto open_path_callback =
base::BindOnce(&GamepadDeviceLinux::OnOpenHidrawNodeComplete,
weak_factory_.GetWeakPtr(), std::move(callback));
dbus_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GamepadDeviceLinux::OpenPathWithPermissionBroker,
weak_factory_.GetWeakPtr(), pad_info.path,
std::move(open_path_callback)));
return;
}
#endif // defined(OS_CHROMEOS)
OnOpenHidrawNodeComplete(std::move(callback), std::move(fd));
}
void GamepadDeviceLinux::OnOpenHidrawNodeComplete(
OpenDeviceNodeCallback callback,
base::ScopedFD fd) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (fd.is_valid())
InitializeHidraw(std::move(fd));
std::move(callback).Run(this);
}
void GamepadDeviceLinux::InitializeHidraw(base::ScopedFD fd) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
DCHECK(fd.is_valid());
hidraw_fd_ = std::move(fd);
uint16_t vendor_id;
uint16_t product_id;
bool is_dualshock4 = false;
bool is_hid_haptic = false;
if (GetHidrawDevinfo(hidraw_fd_, &bus_type_, &vendor_id, &product_id)) {
is_dualshock4 =
Dualshock4ControllerLinux::IsDualshock4(vendor_id, product_id);
is_hid_haptic = HidHapticGamepadLinux::IsHidHaptic(vendor_id, product_id);
DCHECK_LE(is_dualshock4 + is_hid_haptic, 1);
}
if (is_dualshock4 && !dualshock4_)
dualshock4_ = std::make_unique<Dualshock4ControllerLinux>(hidraw_fd_);
if (is_hid_haptic && !hid_haptics_) {
hid_haptics_ =
HidHapticGamepadLinux::Create(vendor_id, product_id, hidraw_fd_);
}
}
void GamepadDeviceLinux::CloseHidrawNode() {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (dualshock4_)
dualshock4_->Shutdown();
dualshock4_.reset();
if (hid_haptics_)
hid_haptics_->Shutdown();
hid_haptics_.reset();
hidraw_fd_.reset();
}
#if defined(OS_CHROMEOS)
void GamepadDeviceLinux::OpenPathWithPermissionBroker(
const std::string& path,
OpenPathCallback callback) {
DCHECK(dbus_runner_->RunsTasksInCurrentSequence());
auto* client = chromeos::PermissionBrokerClient::Get();
DCHECK(client) << "Could not get permission broker client.";
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
auto success_callback =
base::BindOnce(&GamepadDeviceLinux::OnOpenPathSuccess,
weak_factory_.GetWeakPtr(), copyable_callback);
auto error_callback =
base::BindOnce(&GamepadDeviceLinux::OnOpenPathError,
weak_factory_.GetWeakPtr(), copyable_callback);
client->OpenPath(path, std::move(success_callback),
std::move(error_callback));
}
void GamepadDeviceLinux::OnOpenPathSuccess(OpenPathCallback callback,
base::ScopedFD fd) {
DCHECK(dbus_runner_->RunsTasksInCurrentSequence());
polling_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), std::move(fd)));
}
void GamepadDeviceLinux::OnOpenPathError(OpenPathCallback callback,
const std::string& error_name,
const std::string& error_message) {
DCHECK(dbus_runner_->RunsTasksInCurrentSequence());
polling_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), base::ScopedFD()));
}
#endif
void GamepadDeviceLinux::SetVibration(double strong_magnitude,
double weak_magnitude) {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (dualshock4_) {
dualshock4_->SetVibration(strong_magnitude, weak_magnitude);
return;
}
if (hid_haptics_) {
hid_haptics_->SetVibration(strong_magnitude, weak_magnitude);
return;
}
uint16_t strong_magnitude_scaled =
static_cast<uint16_t>(strong_magnitude * kRumbleMagnitudeMax);
uint16_t weak_magnitude_scaled =
static_cast<uint16_t>(weak_magnitude * kRumbleMagnitudeMax);
// AbstractHapticGamepad will call SetZeroVibration when the effect is
// complete, so we don't need to set the duration here except to make sure it
// is at least as long as the maximum duration.
uint16_t duration_millis =
static_cast<uint16_t>(GamepadHapticActuator::kMaxEffectDurationMillis);
// Upload the effect and get the new effect ID. If we already created an
// effect on this device, reuse its ID.
effect_id_ =
StoreRumbleEffect(evdev_fd_, effect_id_, duration_millis, 0,
strong_magnitude_scaled, weak_magnitude_scaled);
if (effect_id_ != kInvalidEffectId)
StartOrStopEffect(evdev_fd_, effect_id_, true);
}
void GamepadDeviceLinux::SetZeroVibration() {
DCHECK(polling_runner_->RunsTasksInCurrentSequence());
if (dualshock4_) {
dualshock4_->SetZeroVibration();
return;
}
if (hid_haptics_) {
hid_haptics_->SetZeroVibration();
return;
}
if (effect_id_ != kInvalidEffectId)
StartOrStopEffect(evdev_fd_, effect_id_, false);
}
} // namespace device