vm_tools: sommelier: allow it to pass gamepads into VM

Implement a basic wayland zcr_gaming_seat_v2 protocol <-> uinput bridge
to present gamepads (game controllers) to VM.

Note that this logic does not perform extensive controller remapping;
it is not expected to work successfully with the more exotic (further
from simple HID standard) controllers.

Wayland gaming profile copied from Chrome source at
third_party/wayland-protocols/unstable/gaming-input/gaming-input-unstable-v2.xml

BUG=b:171985777
TEST=Manual w/ borealis, on hatch-kled, w/ variety of gamepads,
     and games to test button and trigger assignments are accurate.
     The controller option screen in Dead Cells is particularly
     useful as it allows almost all buttons to be mapped and tested.
     borealis board target only tested for successful build.

Change-Id: I2ffd053e5f79f9155874027994987d8072c4edf8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2528629
Tested-by: Kenneth Albanowski <kenalba@google.com>
Reviewed-by: Jun Mukai <mukai@chromium.org>
Reviewed-by: Chloe Pelling <cpelling@google.com>
Reviewed-by: Tetsui Ohkubo <tetsui@chromium.org>
Reviewed-by: David Riley <davidriley@chromium.org>
Reviewed-by: Harry Cutts <hcutts@chromium.org>
Commit-Queue: Kenneth Albanowski <kenalba@google.com>
diff --git a/vm_tools/sommelier/meson.build b/vm_tools/sommelier/meson.build
index 51e2413..3f3bff6 100644
--- a/vm_tools/sommelier/meson.build
+++ b/vm_tools/sommelier/meson.build
@@ -50,6 +50,7 @@
 wl_protocols = [
     'protocol/aura-shell.xml',
     'protocol/drm.xml',
+    'protocol/gaming-input-unstable-v2.xml',
     'protocol/gtk-shell.xml',
     'protocol/keyboard-extension-unstable-v1.xml',
     'protocol/linux-dmabuf-unstable-v1.xml',
@@ -86,6 +87,23 @@
   includes += include_directories('perfetto/sdk')
 endif
 
+#==========#
+# Gamepad support #
+#==========#
+
+gamepad_sources = []
+gamepad_dependencies = []
+
+if get_option('gamepad')
+  gamepad_sources = [
+    'sommelier-gaming.cc',
+  ]
+  gamepad_dependencies = [
+    dependency('libevdev'),
+  ]
+  cpp_args += '-DGAMEPAD_SUPPORT'
+endif
+
 #===========#
 # Sommelier #
 #===========#
@@ -110,7 +128,7 @@
     'sommelier-viewporter.cc',
     'sommelier-xdg-shell.cc',
     'sommelier.cc',
-  ] + wl_outs + tracing_sources,
+  ] + wl_outs + tracing_sources + gamepad_sources,
   dependencies: [
     meson.get_compiler('cpp').find_library('m'),
     dependency('gbm'),
@@ -122,7 +140,7 @@
     dependency('xcb-composite'),
     dependency('xcb-xfixes'),
     dependency('xkbcommon'),
-  ] + tracing_dependencies,
+  ] + tracing_dependencies + gamepad_dependencies,
   cpp_args: cpp_args + [
     '-D_GNU_SOURCE',
     '-DWL_HIDE_DEPRECATED',
diff --git a/vm_tools/sommelier/meson_options.txt b/vm_tools/sommelier/meson_options.txt
index e654a29..d9f4793 100644
--- a/vm_tools/sommelier/meson_options.txt
+++ b/vm_tools/sommelier/meson_options.txt
@@ -55,3 +55,9 @@
   value: false,
   description: 'enable tracing via perfetto'
 )
+
+option('gamepad',
+  type: 'boolean',
+  value: false,
+  description: 'enable gamepad support'
+)
diff --git a/vm_tools/sommelier/protocol/gaming-input-unstable-v2.xml b/vm_tools/sommelier/protocol/gaming-input-unstable-v2.xml
new file mode 100644
index 0000000..3f9a74c
--- /dev/null
+++ b/vm_tools/sommelier/protocol/gaming-input-unstable-v2.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gaming_input_unstable_v2">
+
+  <copyright>
+    Copyright 2016 The Chromium Authors.
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <interface name="zcr_gaming_input_v2" version="2">
+    <description summary="extends wl_seat with gaming input devices">
+      A global interface to provide gaming input devices for a given seat.
+
+      Currently only gamepad devices are supported.
+
+      Warning! The protocol described in this file is experimental and
+      backward incompatible changes may be made. Backward compatible changes
+      may be added together with the corresponding uinterface version bump.
+      Backward incompatible changes are done by bumping the version number in
+      the protocol and uinterface names and resetting the interface version.
+      Once the protocol is to be declared stable, the 'z' prefix and the
+      version number in the protocol and interface names are removed and the
+      interface version number is reset.
+    </description>
+
+    <request name="get_gaming_seat">
+      <description summary="get a gaming seat">
+        Get a gaming seat object for a given seat. Gaming seat provides access
+        to gaming devices
+      </description>
+      <arg name="gaming_seat" type="new_id" interface="zcr_gaming_seat_v2"/>
+      <arg name="seat" type="object" interface="wl_seat"/>
+    </request>
+
+    <request name="destroy" type="destructor">
+      <description summary="release the memory for the gaming input object">
+        Destroy gaming_input object. Objects created from this object are
+        unaffected and should be destroyed separately.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="zcr_gaming_seat_v2" version="1">
+    <description summary="controller object for all gaming devices of a seat">
+      An object that provides access to all the gaming devices of a seat.
+      When a gamepad is connected, the compositor will send gamepad_added event.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="release the memory for the gaming seat object">
+        Destroy gaming_seat object. Objects created from this object are
+        unaffected and should be destroyed separately.
+      </description>
+    </request>
+
+    <event name="gamepad_added">
+      <description summary="gamepad added event">
+        Notification that there is gamepad connected at this seat.
+      </description>
+      <arg name="gamepad" type="new_id" interface="zcr_gamepad_v2" summary="new connected gamepad"/>
+    </event>
+
+    <enum name="bus_type">
+      <description summary="gamepad device bus type">
+        Device connection type e.g. Bluetooth
+      </description>
+      <entry name="usb" value="0" summary="Universal Serial Bus" />
+      <entry name="bluetooth" value="1" summary="Bluetooth" />
+    </enum>
+
+    <event name="gamepad_added_with_device_info">
+      <description summary="gamepad added event">
+        Notification that there is gamepad connected at this seat.
+      </description>
+      <arg name="gamepad" type="new_id" interface="zcr_gamepad_v2" summary="new connected gamepad"/>
+      <arg name="name" type="string" summary="name of the gamepad device" />
+      <arg name="bus" type="uint" enum="bus_type" summary="type of the device connection e.g. Bluetooth" />
+      <arg name="vendor_id" type="uint" summary="vendor ID of the gamepad device" />
+      <arg name="product_id" type="uint" summary="product ID of the gamepad device" />
+      <arg name="version" type="uint" summary="product version of the gamepad device" />
+    </event>
+  </interface>
+
+  <interface name="zcr_gamepad_v2" version="2">
+    <description summary="gamepad input device">
+      The zcr_gamepad_v2 interface represents one or more gamepad input devices,
+      which are reported as a normalized 'Standard Gamepad' as it is specified
+      by the W3C Gamepad API at: https://w3c.github.io/gamepad/#remapping
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy gamepad">
+        Destroy gamepad. Instances created from this gamepad are unaffected
+        and should be destroyed separately.
+      </description>
+    </request>
+
+    <event name="removed">
+      <description summary="gamepad removed">
+        Removed event is send when the gamepad is disconnected. The client should
+        expect no more event and call destroy.
+
+        This event cannot be used as destructor as requests (e.g. vibration) might
+        be added to this interface.
+      </description>
+    </event>
+
+    <event name="axis">
+      <description summary="axis change event">
+        Notification of axis change.
+
+        The axis id specifies which axis has changed as defined by the W3C
+        'Standard Gamepad'.
+
+        The value is calibrated and normalized to the -1 to 1 range.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="axis" type="uint" summary="axis that produced this event"/>
+      <arg name="value" type="fixed" summary="new value of axis"/>
+    </event>
+
+    <enum name="button_state">
+      <description summary="physical button state">
+        Describes the physical state of a button that produced the button
+        event.
+      </description>
+      <entry name="released" value="0" summary="the button is not pressed"/>
+      <entry name="pressed" value="1" summary="the button is pressed"/>
+    </enum>
+
+    <event name="button">
+      <description summary="Gamepad button changed">
+        Notification of button change.
+
+        The button id specifies which button has changed as defined by the W3C
+        'Standard Gamepad'.
+
+        A button can have a digital and an analog value. The analog value is
+        normalized to a 0 to 1 range.
+        If a button does not provide an analog value, it will be derived from
+        the digital state.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+      <arg name="button" type="uint" summary="id of button"/>
+      <arg name="state" type="uint" enum="button_state" summary="digital state of the button"/>
+      <arg name="analog" type="fixed" summary="analog value of the button"/>
+    </event>
+
+    <event name="frame">
+      <description summary="Notifies end of a series of gamepad changes.">
+        Indicates the end of a set of events that logically belong together.
+        A client is expected to accumulate the data in all events within the
+        frame before proceeding.
+      </description>
+      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+    </event>
+
+    <event name="axis_added">
+      <description summary="an axis is added">
+        Adds an axis to the gamepad. Only called when the gamepad was created by
+        gamepad_added_with_device_info. The values are compatible with
+        input_absinfo.
+      </description>
+      <arg name="index" type="uint" summary="An index of the axis" />
+      <arg name="min_value" type="int" summary="minimum value of the axis" />
+      <arg name="max_value" type="int" summary="maximum value of the axis" />
+      <arg name="flat" type="int" summary="input within this value are ignored" />
+      <arg name="fuzz" type="int" summary="used to filter noise" />
+      <arg name="resolution" type="int" summary="resolution of input in units per millimeter, or units per radian for rotational axes." />
+    </event>
+
+    <event name="activated">
+      <description summary="Gamepad activated">
+        Activates the gamepad i.e. the gamepad will be visible to applications
+        after this event is fired. All axis_added events should be sent before
+        this event. Only called when the gamepad was created by
+        gamepad_added_with_device_info.
+      </description>
+    </event>
+
+    <!-- added since v2 -->
+    <event name="vibrator_added" since="2">
+      <description summary="a vibrator is added">
+        Adds a vibrator to the gamepad. Only called if server has verified
+        that gamepad has a vibrator. The vibrator(s) for a gamepad are expected
+        to be added before the "activated" event is called.
+      </description>
+      <arg name="vibrator" type="new_id" interface="zcr_gamepad_vibrator_v2" summary="the gamepad vibrator"/>
+    </event>
+  </interface>
+
+  <interface name="zcr_gamepad_vibrator_v2" version="2">
+    <description summary="vibrator interface for a gamepad">
+      An interface that provides access to the vibrator of a gamepad. Requests can be
+      sent to make the gamepad vibrate and to stop an ongoing vibration.
+    </description>
+
+    <request name="vibrate" since="2">
+      <description summary="triggers the vibration event">
+        Triggers the vibration event on the gamepad vibrator. The gamepad is only allowed to
+        vibrate while the window is in focus. The values in the timings array are 64-bit integers
+        and the values in the amplitudes array are unsigned 8-bit integers.
+        The timings array and the amplitudes array are of the same length.
+        For each timing/amplitude pair, the amplitude determines the strength of
+        the vibration and the timing determines the length of the vibration in milliseconds.
+        Amplitude values must be between 0 and 255. An amplitude of 0 implies no vibration
+        and any timing/amplitude pair with a timing value of 0 is ignored.
+        The repeat argument determines the index at which the vibration pattern to repeat begins.
+        A repeat value of -1 disables repetition. If repetition is enabled, the vibration
+        pattern will repeat indefinitely until stopped, or when focus is lost.
+      </description>
+      <arg name="timings" type="array" summary="array of timing values" />
+      <arg name="amplitudes" type="array" summary="array of amplitude values" />
+      <arg name="repeat" type="int" summary="index into the timings array at which to repeat" />
+    </request>
+
+    <request name="cancel_vibration" since="2">
+      <description summary="cancels the existing vibration event">
+        Cancels the currently ongoing vibration event on the gamepad vibrator.
+      </description>
+    </request>
+
+    <request name="destroy" type="destructor" since="2">
+      <description summary="destroy gamepad vibrator"/>
+    </request>
+  </interface>
+</protocol>
diff --git a/vm_tools/sommelier/sommelier-gaming.cc b/vm_tools/sommelier/sommelier-gaming.cc
new file mode 100644
index 0000000..920d841
--- /dev/null
+++ b/vm_tools/sommelier/sommelier-gaming.cc
@@ -0,0 +1,292 @@
+// Copyright 2020 The Chromium OS 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 "sommelier.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;
+
+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) {
+  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);
+  free(host_gamepad);
+}
+
+static uint32_t remap_axis(struct sl_host_gamepad* host_gamepad,
+                           uint32_t axis) {
+  if (host_gamepad->stadia) {
+    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) {
+  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) {
+  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) {
+  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) {
+  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) {
+  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) {
+  // 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) {
+  struct sl_context* ctx = (struct sl_context*)data;
+  struct sl_host_gamepad* host_gamepad =
+      static_cast<sl_host_gamepad*>(malloc(sizeof(struct sl_host_gamepad)));
+  assert(host_gamepad);
+  wl_list_insert(&ctx->gamepads, &host_gamepad->link);
+  zcr_gamepad_v2_set_user_data(gamepad, host_gamepad);
+  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->stadia = 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->stadia = 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) {
+  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) {
+    // 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);
+  }
+}
diff --git a/vm_tools/sommelier/sommelier.cc b/vm_tools/sommelier/sommelier.cc
index bf524e2..471c3ef 100644
--- a/vm_tools/sommelier/sommelier.cc
+++ b/vm_tools/sommelier/sommelier.cc
@@ -30,6 +30,9 @@
 
 #include "aura-shell-client-protocol.h"  // NOLINT(build/include_directory)
 #include "drm-server-protocol.h"         // NOLINT(build/include_directory)
+#ifdef GAMEPAD_SUPPORT
+#include "gaming-input-unstable-v2-client-protocol.h"  // NOLINT(build/include_directory)
+#endif
 #include "keyboard-extension-unstable-v1-client-protocol.h"  // NOLINT(build/include_directory)
 #include "linux-dmabuf-unstable-v1-client-protocol.h"  // NOLINT(build/include_directory)
 #include "pointer-constraints-unstable-v1-client-protocol.h"  // NOLINT(build/include_directory)
@@ -1044,6 +1047,10 @@
     wl_data_device_add_listener(ctx->selection_data_device,
                                 &sl_internal_data_device_listener, ctx);
   }
+
+#ifdef GAMEPAD_SUPPORT
+  sl_gaming_seat_add_listener(ctx);
+#endif
 }
 
 void sl_host_seat_removed(struct sl_host_seat* host) {
@@ -1289,6 +1296,19 @@
     text_input_manager->host_global = sl_text_input_manager_global_create(ctx);
     assert(!ctx->text_input_manager);
     ctx->text_input_manager = text_input_manager;
+#ifdef GAMEPAD_SUPPORT
+  } else if (strcmp(interface, "zcr_gaming_input_v2") == 0) {
+    struct sl_gaming_input_manager* gaming_input_manager =
+        static_cast<sl_gaming_input_manager*>(
+            malloc(sizeof(struct sl_gaming_input_manager)));
+    assert(gaming_input_manager);
+    gaming_input_manager->ctx = ctx;
+    gaming_input_manager->id = id;
+    gaming_input_manager->internal = static_cast<zcr_gaming_input_v2*>(
+        wl_registry_bind(registry, id, &zcr_gaming_input_v2_interface, 2));
+    assert(!ctx->gaming_input_manager);
+    ctx->gaming_input_manager = gaming_input_manager;
+#endif
   }
 }
 
@@ -1379,6 +1399,14 @@
     ctx->text_input_manager = NULL;
     return;
   }
+#ifdef GAMEPAD_SUPPORT
+  if (ctx->gaming_input_manager && ctx->gaming_input_manager->id == id) {
+    zcr_gaming_input_v2_destroy(ctx->gaming_input_manager->internal);
+    free(ctx->gaming_input_manager);
+    ctx->gaming_input_manager = NULL;
+    return;
+  }
+#endif
   if (ctx->relative_pointer_manager &&
       ctx->relative_pointer_manager->id == id) {
     sl_global_destroy(ctx->relative_pointer_manager->host_global);
@@ -3652,6 +3680,9 @@
   ctx.linux_dmabuf = NULL;
   ctx.keyboard_extension = NULL;
   ctx.text_input_manager = NULL;
+#ifdef GAMEPAD_SUPPORT
+  ctx.gaming_input_manager = NULL;
+#endif
   ctx.display_event_source = NULL;
   ctx.display_ready_event_source = NULL;
   ctx.sigchld_event_source = NULL;
@@ -4258,6 +4289,9 @@
   wl_list_init(&ctx.unpaired_windows);
   wl_list_init(&ctx.host_outputs);
   wl_list_init(&ctx.selection_data_source_send_pending);
+#ifdef GAMEPAD_SUPPORT
+  wl_list_init(&ctx.gamepads);
+#endif
 
   // Parse the list of accelerators that should be reserved by the
   // compositor. Format is "|MODIFIERS|KEYSYM", where MODIFIERS is a
diff --git a/vm_tools/sommelier/sommelier.h b/vm_tools/sommelier/sommelier.h
index 67e2a21..7026fba 100644
--- a/vm_tools/sommelier/sommelier.h
+++ b/vm_tools/sommelier/sommelier.h
@@ -44,6 +44,12 @@
 struct zaura_shell;
 struct zcr_keyboard_extension_v1;
 
+#ifdef GAMEPAD_SUPPORT
+struct sl_gamepad;
+struct sl_gaming_input_manager;
+struct zcr_gaming_input_v2;
+#endif
+
 enum {
   ATOM_WM_S0,
   ATOM_WM_PROTOCOLS,
@@ -105,6 +111,9 @@
   struct sl_linux_dmabuf* linux_dmabuf;
   struct sl_keyboard_extension* keyboard_extension;
   struct sl_text_input_manager* text_input_manager;
+#ifdef GAMEPAD_SUPPORT
+  struct sl_gaming_input_manager* gaming_input_manager;
+#endif
   struct sl_relative_pointer_manager* relative_pointer_manager;
   struct sl_pointer_constraints* pointer_constraints;
   struct wl_list outputs;
@@ -143,6 +152,9 @@
   struct wl_list windows, unpaired_windows;
   struct sl_window* host_focus_window;
   int needs_set_input_focus;
+#ifdef GAMEPAD_SUPPORT
+  struct wl_list gamepads;
+#endif
   double desired_scale;
   double scale;
   const char* application_id;
@@ -367,6 +379,14 @@
   struct zwp_text_input_manager_v1* internal;
 };
 
+#ifdef GAMEPAD_SUPPORT
+struct sl_gaming_input_manager {
+  struct sl_context* ctx;
+  uint32_t id;
+  struct zcr_gaming_input_v2* internal;
+};
+#endif
+
 struct sl_pointer_constraints {
   struct sl_context* ctx;
   uint32_t id;
@@ -492,6 +512,17 @@
   struct wl_list link;
 };
 
+#ifdef GAMEPAD_SUPPORT
+struct sl_host_gamepad {
+  struct sl_context* ctx;
+  int state;
+  struct libevdev* ev_dev;
+  struct libevdev_uinput* uinput_dev;
+  bool stadia;
+  struct wl_list link;
+};
+#endif
+
 struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client,
                                              uint32_t id,
                                              struct wl_buffer* proxy,
@@ -571,6 +602,10 @@
 
 void sl_window_update(struct sl_window* window);
 
+#ifdef GAMEPAD_SUPPORT
+void sl_gaming_seat_add_listener(struct sl_context* ctx);
+#endif
+
 #define sl_array_for_each(pos, array)                                   \
   for (pos = static_cast<typeof(pos)>((array)->data);                   \
        (const char*)pos < ((const char*)(array)->data + (array)->size); \