// 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 #include #include #include #include #include #include #include #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); } }