pH/sommelier/sommelier-gaming.cc

313 lines
12 KiB
C++
Raw Normal View History

// 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-tracing.h" // NOLINT(build/include_directory)
#include <assert.h>
#include <errno.h>
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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
};
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 BT vendor ID for SteelSeries is due to a chipset bug
// and is not an actual claimed Vendor ID.
const uint32_t kSteelSeriesVendorBt = 0x111;
const uint32_t kSteelSeriesProductStratusDuoBt = 0x1431;
const uint32_t kSteelSeriesProductStratusPlusBt = 0x1434;
const uint32_t kStadiaVendor = 0x18d1;
const uint32_t kStadiaProduct = 0x9400;
const uint32_t kStadiaVersion = 0x111;
// 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 != NULL)
libevdev_uinput_destroy(host_gamepad->uinput_dev);
if (host_gamepad->ev_dev != NULL)
libevdev_free(host_gamepad->ev_dev);
zcr_gamepad_v2_destroy(gamepad);
wl_list_remove(&host_gamepad->link);
delete host_gamepad;
}
static uint32_t remap_axis(struct sl_host_gamepad* host_gamepad,
uint32_t axis) {
if (host_gamepad->axes_quirk) {
if (axis == ABS_Z)
axis = ABS_RX;
else if (axis == ABS_RZ)
axis = ABS_RY;
else if (axis == ABS_BRAKE)
axis = ABS_Z;
else if (axis == ABS_GAS)
axis = ABS_RZ;
}
return axis;
}
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;
axis = remap_axis(host_gamepad, axis);
// Note: incoming time is ignored, it will be regenerated from current time.
libevdev_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;
// Note: Exo wayland server always sends analog==0, only pay attention
// to state.
int value = (state == ZCR_GAMEPAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0;
// Note: incoming time is ignored, it will be regenerated from current time.
libevdev_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_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) {
fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__,
host_gamepad->state);
host_gamepad->state = kStateError;
return;
}
index = remap_axis(host_gamepad, index);
libevdev_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) {
fprintf(stderr, "error: %s invoked in unexpected state %d\n", __func__,
host_gamepad->state);
host_gamepad->state = kStateError;
return;
}
int err = libevdev_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 {
fprintf(stderr,
"error: libevdev_uinput_create_from_device failed with error %d\n",
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_new();
host_gamepad->uinput_dev = NULL;
host_gamepad->axes_quirk = false;
if (host_gamepad->ev_dev == NULL) {
fprintf(stderr, "error: libevdev_new failed\n");
host_gamepad->state = kStateError;
return;
}
// We provide limited remapping at this time. Only moderately XBox360
// HID compatible controllers are likely to work well.
if (product_id == kStadiaProduct && vendor_id == kStadiaVendor &&
version == kStadiaVersion) {
host_gamepad->axes_quirk = true;
} else if (bus == ZCR_GAMING_SEAT_V2_BUS_TYPE_BLUETOOTH &&
vendor_id == kSteelSeriesVendorBt &&
(product_id == kSteelSeriesProductStratusDuoBt ||
product_id == kSteelSeriesProductStratusPlusBt)) {
host_gamepad->axes_quirk = true;
}
// Describe a common controller
libevdev_set_name(host_gamepad->ev_dev, kXboxName);
libevdev_set_id_bustype(host_gamepad->ev_dev, kUsbBus);
libevdev_set_id_vendor(host_gamepad->ev_dev, kXboxVendor);
libevdev_set_id_product(host_gamepad->ev_dev, kXboxProduct);
libevdev_set_id_version(host_gamepad->ev_dev, kXboxVersion);
// Enable common set of buttons
// 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.
int buttons[] = {BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
BTN_TL, BTN_TR, BTN_THUMBL, BTN_THUMBR,
BTN_SELECT, BTN_START, BTN_MODE};
for (unsigned int i = 0; i < ARRAY_SIZE(buttons); i++)
libevdev_enable_event_code(host_gamepad->ev_dev, EV_KEY, buttons[i], NULL);
} // NOLINT(whitespace/indent), lint bug b/173143790
// 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");
fprintf(stderr,
"error: sl_internal_gaming_seat_gamepad_added unimplemented\n");
}
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");
// TODO(kenalba): does gaming_seat need to persist in ctx?
struct zcr_gaming_seat_v2* gaming_seat =
zcr_gaming_input_v2_get_gaming_seat(ctx->gaming_input_manager->internal,
ctx->default_seat->proxy);
zcr_gaming_seat_v2_add_listener(gaming_seat,
&sl_internal_gaming_seat_listener, ctx);
}
}