blob: 196b9f5fe6ef5a83768cd1f343ec18eb584aba69 [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/input.h>
#include <linux/joystick.h>
#include <sys/ioctl.h>
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "device/udev_linux/udev_linux.h"
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;
#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));
}
bool HasRumbleCapability(int fd) {
unsigned long evbit[BITS_TO_LONGS(EV_MAX)];
unsigned long ffbit[BITS_TO_LONGS(FF_MAX)];
if (HANDLE_EINTR(ioctl(fd, EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
HANDLE_EINTR(ioctl(fd, EVIOCGBIT(EV_FF, FF_MAX), ffbit)) < 0) {
return false;
}
if (!test_bit(EV_FF, evbit)) {
return false;
}
return test_bit(FF_RUMBLE, ffbit);
}
int StoreRumbleEffect(int 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, EVIOCSFF, (const void*)&effect)) < 0)
return kInvalidEffectId;
return effect.id;
}
void DestroyEffect(int fd, int effect_id) {
HANDLE_EINTR(ioctl(fd, EVIOCRMFF, effect_id));
}
bool StartOrStopEffect(int 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, (const void*)&start_stop, sizeof(start_stop)));
return nbytes == sizeof(start_stop);
}
} // namespace
GamepadDeviceLinux::GamepadDeviceLinux(const std::string& parent_syspath)
: parent_syspath_(parent_syspath),
joydev_fd_(-1),
joydev_index_(-1),
evdev_fd_(-1),
effect_id_(kInvalidEffectId),
supports_vibration_(false) {}
GamepadDeviceLinux::~GamepadDeviceLinux() = default;
void GamepadDeviceLinux::DoShutdown() {
CloseJoydevNode();
CloseEvdevNode();
}
void GamepadDeviceLinux::ReadPadState(Gamepad* pad) const {
DCHECK_GE(joydev_fd_, 0);
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) {
if (item >= Gamepad::kAxesLengthCap)
continue;
pad->axes[item] = event.value / kMaxLinuxAxisValue;
if (item >= pad->axes_length)
pad->axes_length = item + 1;
} 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;
if (item >= pad->buttons_length)
pad->buttons_length = item + 1;
}
pad->timestamp = event.time;
}
}
bool GamepadDeviceLinux::OpenJoydevNode(const std::string& path,
udev_device* device,
int joydev_index) {
CloseJoydevNode();
joydev_fd_ = open(path.c_str(), O_RDONLY | O_NONBLOCK);
if (joydev_fd_ < 0)
return false;
udev_device* parent_device =
device::udev_device_get_parent_with_subsystem_devtype(
device, kInputSubsystem, nullptr);
const char* vendor_id =
udev_device_get_sysattr_value(parent_device, "id/vendor");
const char* product_id =
udev_device_get_sysattr_value(parent_device, "id/product");
const char* version_number =
udev_device_get_sysattr_value(parent_device, "id/version");
const char* name = udev_device_get_sysattr_value(parent_device, "name");
std::string name_string(name ? name : "");
// 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);
if (usb_device) {
const char* usb_vendor_id =
udev_device_get_sysattr_value(usb_device, "idVendor");
const char* usb_product_id =
udev_device_get_sysattr_value(usb_device, "idProduct");
if (vendor_id && product_id && strcmp(vendor_id, usb_vendor_id) == 0 &&
strcmp(product_id, usb_product_id) == 0) {
const char* manufacturer =
udev_device_get_sysattr_value(usb_device, "manufacturer");
const char* product =
udev_device_get_sysattr_value(usb_device, "product");
// Replace the previous name string with one containing the better
// information.
name_string = base::StringPrintf("%s %s", manufacturer, product);
}
}
joydev_index_ = joydev_index;
vendor_id_ = vendor_id ? vendor_id : "";
product_id_ = product_id ? product_id : "";
version_number_ = version_number ? version_number : "";
name_ = name_string;
return true;
}
void GamepadDeviceLinux::CloseJoydevNode() {
if (joydev_fd_ >= 0) {
close(joydev_fd_);
joydev_fd_ = -1;
}
joydev_index_ = -1;
vendor_id_.clear();
product_id_.clear();
version_number_.clear();
name_.clear();
}
bool GamepadDeviceLinux::OpenEvdevNode(const std::string& path) {
CloseEvdevNode();
evdev_fd_ = open(path.c_str(), O_RDWR | O_NONBLOCK);
if (evdev_fd_ < 0)
return false;
supports_vibration_ = HasRumbleCapability(evdev_fd_);
return true;
}
void GamepadDeviceLinux::CloseEvdevNode() {
if (evdev_fd_ >= 0) {
if (effect_id_ != kInvalidEffectId)
DestroyEffect(evdev_fd_, effect_id_);
close(evdev_fd_);
evdev_fd_ = -1;
}
supports_vibration_ = false;
}
void GamepadDeviceLinux::SetVibration(double strong_magnitude,
double weak_magnitude) {
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() {
if (effect_id_ != kInvalidEffectId)
StartOrStopEffect(evdev_fd_, effect_id_, false);
}
} // namespace device