blob: 2155252b33afc2f73f4ef822b4bc011e4f03b832 [file] [log] [blame] [edit]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h" // NOLINT(build/include_directory)
#include "sommelier-logging.h" // NOLINT(build/include_directory)
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <errno.h>
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include "libevdev/libevdev-shim.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unordered_map>
#include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory)
// Overview of state management via gaming events, in order:
// 1) Acquire gaming seats (in sommelier.cc)
// 2) Add listeners to gaming seats
// 3) Listen for zcr_gaming_seat_v2.gamepad_added to construct a 'default'
// game controller (not currently implemented)
// Calls libevdev_new, libevdev_enable_event_type,
// libevdev_uinput_create_from_device
// 4) Listen for zcr_gaming_seat_v2.gamepad_added_with_device_info to construct
// a custom game controller
// Calls libevdev_new
// 5) Listen for zcr_gamepad_v2.axis_added to fill in a custom game controller
// Calls libevdev_enable_event_type
// 6) Listen for zcr_gamepad_v2.activated to finalize a custom game controller
// Calls libevdev_uinput_create_from_device
// 7) Listen for zcr_gamepad_v2.axis to set frame state for game controller
// Calls libevdev_uinput_write_event
// 8) Listen for zcr_gamepad_v2.button to set frame state for game controller
// Calls libevdev_uinput_write_event
// 9) Listen for zcr_gamepad_v2.frame to emit collected frame
// Calls libevdev_uinput_write_event(EV_SYN)
// 10) Listen for zcr_gamepad_v2.removed to destroy gamepad
// Must handle gamepads in all states of construction or error
enum GamepadActivationState {
kStateUnknown = 0, // Should not happen
kStatePending = 1, // Constructed, pending axis definition
kStateActivated = 2, // Fully activated
kStateError = 3 // Error occurred during construction; ignore gracefully
};
struct DeviceID {
const uint32_t vendor;
const uint32_t product;
const uint32_t version;
bool operator==(const DeviceID& other) const {
return vendor == other.vendor && product == other.product &&
version == other.version;
}
};
// Simple Hash/equal operators for DeviceID struct. Doesn't need to be efficient
// as the size of our map will be small.
template <>
struct std::hash<DeviceID> {
std::size_t operator()(const DeviceID& device) const {
return device.vendor ^ device.product ^ device.version;
}
};
// Buttons being emulated by libevdev uinput.
// Note: Do not enable BTN_TL2 or BTN_TR2, as they will significantly
// change the Linux joydev interpretation of the triggers on ABS_Z/ABS_RZ.
const int kButtons[] = {BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
BTN_TL, BTN_TR, BTN_THUMBL, BTN_THUMBR,
BTN_SELECT, BTN_START, BTN_MODE};
// ID constants for identifying gamepads. Note that some gamepads
// may share the same vendor if they're from the same brand. Bluetooth (BT)
// and USB variants may also share the same product id - though this is
// not guaranteed.
// IDs for emulated controllers.
const char kXboxName[] = "Microsoft X-Box One S pad";
const uint32_t kUsbBus = 0x03;
const uint32_t kXboxVendor = 0x45e;
const uint32_t kXboxProduct = 0x2ea;
const uint32_t kXboxVersion = 0x301;
// Note: the Bluetooth (BT) vendor ID for SteelSeries is due to a chipset bug
// and is not an actual claimed Vendor ID.
const uint32_t kSteelSeriesBTVendor = 0x111;
const uint32_t kStadiaVendor = 0x18d1;
const uint32_t kStadiaProduct = 0x9400;
const uint32_t kSonyVendor = 0x54C;
const uint32_t kDualSenseProduct = 0xCE6;
const uint32_t kDualSenseEdgeProduct = 0xDF2;
const uint32_t kDualShock4v1Product = 0x5C4;
const uint32_t kDualShock4v2Product = 0x9CC;
const DeviceID kStadiaUSB = {
.vendor = kStadiaVendor, .product = kStadiaProduct, .version = 0x111};
const DeviceID kStadiaBT = {
.vendor = kStadiaVendor, .product = kStadiaProduct, .version = 0x100};
const DeviceID kStratusDuoBT = {
.vendor = kSteelSeriesBTVendor, .product = 0x1431, .version = 0x11B};
const DeviceID kStratusPlusBT = {
.vendor = kSteelSeriesBTVendor, .product = 0x1434, .version = 0x216};
// DualSense versions are the HID specification versions (bcdHID). We care about
// these versions as hid-playstation and hid-sony use bcdHID to signal that the
// broken hid-generic mapping is not used.
const DeviceID kDualSenseUSB = {
.vendor = kSonyVendor, .product = kDualSenseProduct, .version = 0x111};
const DeviceID kDualSenseBT = {
.vendor = kSonyVendor, .product = kDualSenseProduct, .version = 0x100};
// There's currently a quirk that causes Dualsense gamepads to have 0x000 as
// their version when connected over BT after a ChromeOS reboot.
const DeviceID kDualSenseBuggedBT = {
.vendor = kSonyVendor, .product = kDualSenseProduct, .version = 0x000};
const DeviceID kDualSenseEdgeUSB = {
.vendor = kSonyVendor, .product = kDualSenseEdgeProduct, .version = 0x111};
const DeviceID kDualSenseEdgeBT = {
.vendor = kSonyVendor, .product = kDualSenseEdgeProduct, .version = 0x100};
const DeviceID kDualShock4v1USB = {
.vendor = kSonyVendor, .product = kDualShock4v1Product, .version = 0x8111};
const DeviceID kDualShock4v1BT = {
.vendor = kSonyVendor, .product = kDualShock4v1Product, .version = 0x8100};
const DeviceID kDualShock4v2USB = {
.vendor = kSonyVendor, .product = kDualShock4v2Product, .version = 0x8111};
const DeviceID kDualShock4v2BT = {
.vendor = kSonyVendor, .product = kDualShock4v2Product, .version = 0x8100};
const DeviceID kDualShock3USB = {
.vendor = kSonyVendor, .product = 0x268, .version = 0x8111};
const DeviceID kXboxSeriesXBT = {
.vendor = kXboxVendor, .product = 0xB13, .version = 0x513};
const DeviceID kXboxOneSOldBT = {
.vendor = kXboxVendor, .product = 0x2E0, .version = 0x903};
const DeviceID kXboxOneS2016BT = {
.vendor = kXboxVendor, .product = 0x2FD, .version = 0x903};
const DeviceID kXboxOneSUpdatedBT = {
.vendor = kXboxVendor, .product = 0xB20, .version = 0x517};
const DeviceID kXboxAdaptiveBT = {
.vendor = kXboxVendor, .product = 0xB21, .version = 0x511};
const DeviceID kXboxElite2BT = {
.vendor = kXboxVendor, .product = 0xB22, .version = 0x511};
// Mappings from the input event of a given gamepad (key) to the
// appropriate output event (value). These mappings are intended to maintain
// the locality of a gamepad; i.e the left face button should map to a
// left face button event. Input events not represented in a map will be
// discarded.
// DualSense (PS5).
const InputMapping kDualSenseMapping = {
.id = "DualSenseMappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_Z, ABS_RX},
{ABS_RZ, ABS_RY},
// Joystick press
{BTN_SELECT, BTN_THUMBL},
{BTN_START, BTN_THUMBR},
// DPad
{ABS_HAT0X, ABS_HAT0X},
{ABS_HAT0Y, ABS_HAT0Y},
// Face Buttons
{BTN_B, BTN_A},
{BTN_C, BTN_B},
{BTN_A, BTN_X},
{BTN_X, BTN_Y},
// Left bumper and trigger
{BTN_Y, BTN_TL},
{ABS_RX, ABS_Z},
// Right bumper and trigger
{BTN_Z, BTN_TR},
{ABS_RY, ABS_RZ},
// Menu buttons
{BTN_TL2, BTN_SELECT},
{BTN_TR2, BTN_START},
{BTN_MODE, BTN_MODE},
// Unused buttons: Touchpad_click: BTN_THUMBL, Microphone_button:
// BTN_THUMBR
}};
// DualShock4 (PS4).
const InputMapping kDualShock4Mapping = {.id = "DualShock4MappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_RX, ABS_RX},
{ABS_RY, ABS_RY},
// Joystick press
{BTN_THUMBL, BTN_THUMBL},
{BTN_THUMBR, BTN_THUMBR},
// DPad
{ABS_HAT0X, ABS_HAT0X},
{ABS_HAT0Y, ABS_HAT0Y},
// Right-hand Buttons
{BTN_A, BTN_A},
{BTN_B, BTN_B},
{BTN_X, BTN_Y},
{BTN_Y, BTN_X},
// Left bumper and trigger
{BTN_TL, BTN_TL},
{ABS_Z, ABS_Z},
// Right bumper and trigger
{BTN_TR, BTN_TR},
{ABS_RZ, ABS_RZ},
// Menu buttons
{BTN_SELECT, BTN_SELECT},
{BTN_START, BTN_START},
{BTN_MODE, BTN_MODE},
}};
// DualShock3 (PS3).
const InputMapping kDualShock3Mapping = {.id = "DualShock3MappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_RX, ABS_RX},
{ABS_RY, ABS_RY},
// Joystick press
{BTN_THUMBL, BTN_THUMBL},
{BTN_THUMBR, BTN_THUMBR},
// DPad
{BTN_DPAD_LEFT, ABS_HAT0X},
{BTN_DPAD_RIGHT, ABS_HAT0X},
{BTN_DPAD_UP, ABS_HAT0Y},
{BTN_DPAD_DOWN, ABS_HAT0Y},
// Face Buttons
{BTN_A, BTN_A},
{BTN_B, BTN_B},
{BTN_Y, BTN_X},
{BTN_X, BTN_Y},
// Left bumper and trigger
{BTN_TL, BTN_TL},
{ABS_Z, ABS_Z},
// Right bumper and trigger
{BTN_TR, BTN_TR},
{ABS_RZ, ABS_RZ},
// Menu buttons
{BTN_SELECT, BTN_SELECT},
{BTN_START, BTN_START},
{BTN_MODE, BTN_MODE},
}};
// Represents how the input events of a certain controllers (key) should be
// interpreted (value). So far this pattern has been observed in:
// - Stadia
// - Stratus Duo (BT)
// - Stratus + (BT)
// - Xbox Series X (BT)
// - Xbox One S (updated firmware) (BT)
// - Xbox Adaptive (BT)
// - Xbox Elite 2 (BT)
const InputMapping kAxisQuirkMapping = {.id = "AxisQuirkMappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_Z, ABS_RX},
{ABS_RZ, ABS_RY},
// Joystick press
{BTN_THUMBL, BTN_THUMBL},
{BTN_THUMBR, BTN_THUMBR},
// DPad
{ABS_HAT0X, ABS_HAT0X},
{ABS_HAT0Y, ABS_HAT0Y},
// Face Buttons
{BTN_A, BTN_A},
{BTN_B, BTN_B},
{BTN_X, BTN_X},
{BTN_Y, BTN_Y},
// Left bumper and trigger
{BTN_TL, BTN_TL},
{ABS_BRAKE, ABS_Z},
// Right bumper and trigger
{BTN_TR, BTN_TR},
{ABS_GAS, ABS_RZ},
// Menu buttons
{BTN_SELECT, BTN_SELECT},
{BTN_START, BTN_START},
{BTN_MODE, BTN_MODE},
}};
// Xbox One S (BT) - Old firmware.
// Note: this mapping is based off of a mapping from another feature
// and has not been explicitly tested. See b/277829347.
const InputMapping kXboxOneSOldMapping = {.id = "XboxOneSOldMappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_RX, ABS_RX},
{ABS_RY, ABS_RY},
// Joystick press
{BTN_TL2, BTN_THUMBL},
{BTN_TR2, BTN_THUMBR},
// DPad
{ABS_HAT0X, ABS_HAT0X},
{ABS_HAT0Y, ABS_HAT0Y},
// Face Buttons
{BTN_A, BTN_A},
{BTN_B, BTN_B},
{BTN_C, BTN_X},
{BTN_X, BTN_Y},
// Left bumper and trigger
{BTN_Y, BTN_TL},
{ABS_Z, ABS_Z},
// Right bumper and trigger
{BTN_Z, BTN_TR},
{ABS_RZ, ABS_RZ},
// Menu buttons
{BTN_TL, BTN_SELECT},
{BTN_TR, BTN_START},
{0x8b, BTN_MODE},
}};
// Xbox One S (BT) - 2016 firmware.
const InputMapping kXboxOneS2016Mapping = {.id = "XboxOneS2016MappingV1",
.mapping = {
// Left Joystick
{ABS_X, ABS_X},
{ABS_Y, ABS_Y},
// Right Joystick
{ABS_Z, ABS_RX},
{ABS_RZ, ABS_RY},
// Joystick press
{BTN_THUMBL, BTN_THUMBL},
{BTN_THUMBR, BTN_THUMBR},
// DPad
{ABS_HAT0X, ABS_HAT0X},
{ABS_HAT0Y, ABS_HAT0Y},
// Face Buttons
{BTN_A, BTN_A},
{BTN_B, BTN_B},
{BTN_X, BTN_X},
{BTN_Y, BTN_Y},
// Left bumper and trigger
{BTN_TL, BTN_TL},
{ABS_BRAKE, ABS_Z},
// Right bumper and trigger
{BTN_TR, BTN_TR},
{ABS_GAS, ABS_RZ},
// Menu buttons
{KEY_BACK, BTN_SELECT},
{BTN_START, BTN_START},
{KEY_HOMEPAGE, BTN_MODE},
}};
// Map of devices to their respctive input remappings.
const std::unordered_map<DeviceID, const InputMapping*> kDeviceMappings = {
{kStadiaUSB, &kAxisQuirkMapping},
{kStadiaBT, &kAxisQuirkMapping},
// Note that the BTN_MODE is not mapped correctly for the
// Stratus Duo, due to it being interpreted on the host
// as a key event causing a browser HOME action.
{kStratusDuoBT, &kAxisQuirkMapping},
{kStratusPlusBT, &kAxisQuirkMapping},
{kDualSenseUSB, &kDualSenseMapping},
{kDualSenseBT, &kDualSenseMapping},
{kDualSenseBuggedBT, &kDualSenseMapping},
{kDualShock4v2USB, &kDualShock4Mapping},
{kDualShock4v2BT, &kDualShock4Mapping},
{kDualShock3USB, &kDualShock3Mapping},
{kXboxSeriesXBT, &kAxisQuirkMapping},
{kXboxOneSOldBT, &kXboxOneSOldMapping},
{kXboxOneS2016BT, &kXboxOneS2016Mapping},
{kXboxOneSUpdatedBT, &kAxisQuirkMapping},
// These mappings are inferred to be the same based on the gamepad api
// mappings.
// See:
// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:device/gamepad/gamepad_standard_mappings_linux.cc;l=968
{kXboxAdaptiveBT, &kAxisQuirkMapping},
{kXboxElite2BT, &kAxisQuirkMapping},
{kDualShock4v1USB, &kDualShock4Mapping},
{kDualShock4v1BT, &kDualShock4Mapping},
{kDualSenseEdgeUSB, &kDualSenseMapping},
{kDualSenseEdgeBT, &kDualSenseMapping},
};
// Note: the majority of protocol errors are treated as non-fatal, and
// are intended to be handled gracefully, as is removal at any
// state of construction or operation. We should expect that
// 'sudden removal' can happen at any time, due to hotplugging
// or unexpected state changes from the wayland server.
static void sl_internal_gamepad_removed(void* data,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gamepad_removed");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
assert(host_gamepad->state == kStatePending ||
host_gamepad->state == kStateActivated ||
host_gamepad->state == kStateError);
if (host_gamepad->uinput_dev != nullptr) {
Libevdev::Get()->uinput_destroy(host_gamepad->uinput_dev);
}
if (host_gamepad->ev_dev != nullptr) {
Libevdev::Get()->free(host_gamepad->ev_dev);
}
zcr_gamepad_v2_destroy(gamepad);
wl_list_remove(&host_gamepad->link);
LOG(INFO) << "Gamepad removed: " << host_gamepad;
delete host_gamepad;
}
// Helper function to remap the input events from a host_gamepad into the
// correct output event to be emulated by the generated uinput device.
static bool remap_input(struct sl_host_gamepad* host_gamepad, uint32_t& input) {
if (host_gamepad->input_mapping == nullptr) {
return true;
}
auto it = host_gamepad->input_mapping->mapping.find(input);
if (it != host_gamepad->input_mapping->mapping.end()) {
input = it->second;
return true;
}
// If an input_mapping exists, and we get an input we don't expect
// or don't handle, we shouldn't emulate it. An example of this
// is that the DualSense controller's triggers activate an axis
// and a button at the same time, which would result in unexpected
// behaviour if we forwarded both inputs.
return false;
}
static void sl_internal_gamepad_axis(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time,
uint32_t axis,
wl_fixed_t value) {
TRACE_EVENT("gaming", "sl_internal_gamepad_axis");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated) {
return;
}
if (!remap_input(host_gamepad, axis)) {
return;
}
// Note: incoming time is ignored, it will be regenerated from current time.
Libevdev::Get()->uinput_write_event(host_gamepad->uinput_dev, EV_ABS, axis,
wl_fixed_to_double(value));
}
static void sl_internal_gamepad_button(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time,
uint32_t button,
uint32_t state,
wl_fixed_t analog) {
TRACE_EVENT("gaming", "sl_internal_gamepad_button");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated) {
return;
}
uint32_t original_button = button;
if (!remap_input(host_gamepad, button)) {
return;
}
// Note: Exo wayland server always sends analog==0, only pay attention
// to state.
int value = (state == ZCR_GAMEPAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0;
if (host_gamepad->input_mapping == &kDualShock3Mapping) {
// Convert button to axis if necessary.
if (original_button != button &&
(original_button == BTN_DPAD_UP || original_button == BTN_DPAD_LEFT ||
original_button == BTN_DPAD_DOWN ||
original_button == BTN_DPAD_RIGHT)) {
// BTN_DPAD_UP and BTN_DPAD_LEFT are the equivalent of negative DPAD axis
// events and we need to correct their pressed (1) values.
if (value == 1 && (original_button == BTN_DPAD_UP ||
original_button == BTN_DPAD_LEFT)) {
value = -1;
}
Libevdev::Get()->uinput_write_event(host_gamepad->uinput_dev, EV_ABS,
button, value);
return;
}
}
// Note: incoming time is ignored, it will be regenerated from current time.
Libevdev::Get()->uinput_write_event(host_gamepad->uinput_dev, EV_KEY, button,
value);
}
static void sl_internal_gamepad_frame(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t time) {
TRACE_EVENT("gaming", "sl_internal_gamepad_frame");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStateActivated) {
return;
}
// Note: incoming time is ignored, it will be regenerated from current time.
Libevdev::Get()->uinput_write_event(host_gamepad->uinput_dev, EV_SYN,
SYN_REPORT, 0);
}
static void sl_internal_gamepad_axis_added(void* data,
struct zcr_gamepad_v2* gamepad,
uint32_t index,
int32_t min_value,
int32_t max_value,
int32_t flat,
int32_t fuzz,
int32_t resolution) {
TRACE_EVENT("gaming", "sl_internal_gamepad_axis_added");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
struct input_absinfo info = {.value = 0, // Does this matter?
.minimum = min_value,
.maximum = max_value,
.fuzz = fuzz,
.flat = flat,
.resolution = resolution};
if (host_gamepad->state != kStatePending) {
LOG(ERROR) << "invoked in unexpected state " << host_gamepad->state;
host_gamepad->state = kStateError;
return;
}
if (!remap_input(host_gamepad, index)) {
return;
}
Libevdev::Get()->enable_event_code(host_gamepad->ev_dev, EV_ABS, index,
&info);
}
static void sl_internal_gamepad_activated(void* data,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gamepad_activated");
struct sl_host_gamepad* host_gamepad = (struct sl_host_gamepad*)data;
if (host_gamepad->state != kStatePending) {
LOG(ERROR) << "invoked in unexpected state " << host_gamepad->state;
host_gamepad->state = kStateError;
return;
}
int err = Libevdev::Get()->uinput_create_from_device(
host_gamepad->ev_dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
&host_gamepad->uinput_dev);
if (err == 0) {
// TODO(kenalba): can we destroy and clean up the ev_dev now?
host_gamepad->state = kStateActivated;
} else {
LOG(ERROR) << "libevdev_uinput_create_from_device failed with error "
<< err;
host_gamepad->state = kStateError;
}
}
static void sl_internal_gamepad_vibrator_added(
void* data,
struct zcr_gamepad_v2* gamepad,
struct zcr_gamepad_vibrator_v2* vibrator) {
TRACE_EVENT("gaming", "sl_internal_gamepad_vibrator_added");
// TODO(kenalba): add vibration logic
}
static const struct zcr_gamepad_v2_listener sl_internal_gamepad_listener = {
sl_internal_gamepad_removed, sl_internal_gamepad_axis,
sl_internal_gamepad_button, sl_internal_gamepad_frame,
sl_internal_gamepad_axis_added, sl_internal_gamepad_activated,
sl_internal_gamepad_vibrator_added};
static void sl_internal_gaming_seat_gamepad_added_with_device_info(
void* data,
struct zcr_gaming_seat_v2* gaming_seat,
struct zcr_gamepad_v2* gamepad,
const char* name,
uint32_t bus,
uint32_t vendor_id,
uint32_t product_id,
uint32_t version) {
TRACE_EVENT("gaming",
"sl_internal_gaming_seat_gamepad_added_with_device_info");
struct sl_context* ctx = (struct sl_context*)data;
struct sl_host_gamepad* host_gamepad = new sl_host_gamepad();
wl_list_insert(&ctx->gamepads, &host_gamepad->link);
zcr_gamepad_v2_add_listener(gamepad, &sl_internal_gamepad_listener,
host_gamepad);
host_gamepad->ctx = ctx;
host_gamepad->state = kStatePending;
host_gamepad->ev_dev = Libevdev::Get()->new_evdev();
host_gamepad->uinput_dev = nullptr;
host_gamepad->name = name;
host_gamepad->bus = bus;
host_gamepad->vendor_id = vendor_id;
host_gamepad->product_id = product_id;
host_gamepad->version = version;
host_gamepad->input_mapping = nullptr;
if (host_gamepad->ev_dev == nullptr) {
LOG(ERROR) << "libevdev_new failed";
host_gamepad->state = kStateError;
return;
}
// We provide limited remapping at this time. Only moderately XBox360
// HID compatible controllers are likely to work well.
auto it = kDeviceMappings.find(DeviceID{host_gamepad->vendor_id,
host_gamepad->product_id,
host_gamepad->version});
if (it != kDeviceMappings.end()) {
host_gamepad->input_mapping = it->second;
if (it->first == kDualShock3USB) {
// The DualShock3 gamepad doesn't have any axes for its DPAD, we need to
// add them manually.
sl_internal_gamepad_axis_added(
host_gamepad, gamepad, BTN_DPAD_DOWN, /*min_value=*/-1,
/*max_value=*/1, /*flat=*/0, /*fuzz=*/0, /*resolution=*/0);
sl_internal_gamepad_axis_added(
host_gamepad, gamepad, BTN_DPAD_LEFT, /*min_value=*/-1,
/*max_value=*/1, /*flat=*/0, /*fuzz=*/0, /*resolution=*/0);
}
}
// Describe a common controller
Libevdev::Get()->set_name(host_gamepad->ev_dev, kXboxName);
Libevdev::Get()->set_id_bustype(host_gamepad->ev_dev, kUsbBus);
Libevdev::Get()->set_id_vendor(host_gamepad->ev_dev, kXboxVendor);
Libevdev::Get()->set_id_product(host_gamepad->ev_dev, kXboxProduct);
Libevdev::Get()->set_id_version(host_gamepad->ev_dev, kXboxVersion);
// Enable common set of buttons
for (unsigned int i = 0; i < ARRAY_SIZE(kButtons); i++) {
Libevdev::Get()->enable_event_code(host_gamepad->ev_dev, EV_KEY,
kButtons[i], nullptr);
}
LOG(INFO) << "Gamepad added: " << host_gamepad;
}
// Note: not currently implemented by Exo.
static void sl_internal_gaming_seat_gamepad_added(
void* data,
struct zcr_gaming_seat_v2* gaming_seat,
struct zcr_gamepad_v2* gamepad) {
TRACE_EVENT("gaming", "sl_internal_gaming_seat_gamepad_added");
LOG(ERROR) << "sl_internal_gaming_seat_gamepad_added unimplemented";
}
static const struct zcr_gaming_seat_v2_listener
sl_internal_gaming_seat_listener = {
sl_internal_gaming_seat_gamepad_added,
sl_internal_gaming_seat_gamepad_added_with_device_info};
void sl_gaming_seat_add_listener(struct sl_context* ctx) {
if (ctx->gaming_input_manager && ctx->gaming_input_manager->internal) {
TRACE_EVENT("gaming", "sl_gaming_seat_add_listener");
ctx->gaming_seat = zcr_gaming_input_v2_get_gaming_seat(
ctx->gaming_input_manager->internal, ctx->default_seat->proxy);
zcr_gaming_seat_v2_add_listener(ctx->gaming_seat,
&sl_internal_gaming_seat_listener, ctx);
}
}