forked from mirrors/gecko-dev
		
	 761ca4cf49
			
		
	
	
		761ca4cf49
		
	
	
	
	
		
			
			Based on work by: Val Packett <val@packett.cool> and coolreader18 <coolreader18@gmail.com>. Differential Revision: https://phabricator.services.mozilla.com/D197865
		
			
				
	
	
		
			529 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			529 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /*
 | |
|  * LinuxGamepadService: An evdev backend for the GamepadService.
 | |
|  *
 | |
|  * Ref: https://www.kernel.org/doc/html/latest/input/gamepad.html
 | |
|  */
 | |
| #include <algorithm>
 | |
| #include <unordered_map>
 | |
| #include <cstddef>
 | |
| 
 | |
| #include <glib.h>
 | |
| #include <linux/input.h>
 | |
| #include <stdio.h>
 | |
| #include <stdint.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <unistd.h>
 | |
| #include "nscore.h"
 | |
| #include "mozilla/dom/GamepadHandle.h"
 | |
| #include "mozilla/dom/GamepadPlatformService.h"
 | |
| #include "mozilla/dom/GamepadRemapping.h"
 | |
| #include "mozilla/Tainting.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "mozilla/Sprintf.h"
 | |
| #include "udev.h"
 | |
| 
 | |
| #define BITS_PER_LONG ((sizeof(unsigned long)) * 8)
 | |
| #define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| using mozilla::MakeUnique;
 | |
| using mozilla::udev_device;
 | |
| using mozilla::udev_enumerate;
 | |
| using mozilla::udev_lib;
 | |
| using mozilla::udev_list_entry;
 | |
| using mozilla::udev_monitor;
 | |
| using mozilla::UniquePtr;
 | |
| 
 | |
| static const char kEvdevPath[] = "/dev/input/event";
 | |
| 
 | |
| static inline bool TestBit(const unsigned long* arr, size_t bit) {
 | |
|   return !!(arr[bit / BITS_PER_LONG] & (1LL << (bit % BITS_PER_LONG)));
 | |
| }
 | |
| 
 | |
| static inline double ScaleAxis(const input_absinfo& info, int value) {
 | |
|   return 2.0 * (value - info.minimum) / (double)(info.maximum - info.minimum) -
 | |
|          1.0;
 | |
| }
 | |
| 
 | |
| // TODO: should find a USB identifier for each device so we can
 | |
| // provide something that persists across connect/disconnect cycles.
 | |
| struct Gamepad {
 | |
|   GamepadHandle handle;
 | |
|   bool isStandardGamepad = false;
 | |
|   RefPtr<GamepadRemapper> remapper = nullptr;
 | |
|   guint source_id = UINT_MAX;
 | |
|   char idstring[256] = {0};
 | |
|   char devpath[PATH_MAX] = {0};
 | |
|   uint8_t key_map[KEY_MAX] = {0};
 | |
|   uint8_t abs_map[ABS_MAX] = {0};
 | |
|   std::unordered_map<uint16_t, input_absinfo> abs_info;
 | |
| };
 | |
| 
 | |
| static inline bool LoadAbsInfo(int fd, Gamepad* gamepad, uint16_t code) {
 | |
|   input_absinfo info{0};
 | |
|   if (ioctl(fd, EVIOCGABS(code), &info) < 0) {
 | |
|     return false;
 | |
|   }
 | |
|   if (info.minimum == info.maximum) {
 | |
|     return false;
 | |
|   }
 | |
|   gamepad->abs_info.emplace(code, std::move(info));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| class LinuxGamepadService {
 | |
|  public:
 | |
|   LinuxGamepadService() : mMonitor(nullptr), mMonitorSourceID(0) {}
 | |
| 
 | |
|   void Startup();
 | |
|   void Shutdown();
 | |
| 
 | |
|  private:
 | |
|   void AddDevice(struct udev_device* dev);
 | |
|   void RemoveDevice(struct udev_device* dev);
 | |
|   void ScanForDevices();
 | |
|   void AddMonitor();
 | |
|   void RemoveMonitor();
 | |
|   bool IsDeviceGamepad(struct udev_device* dev);
 | |
|   void ReadUdevChange();
 | |
| 
 | |
|   // handler for data from /dev/input/eventN
 | |
|   static gboolean OnGamepadData(GIOChannel* source, GIOCondition condition,
 | |
|                                 gpointer data);
 | |
| 
 | |
|   // handler for data from udev monitor
 | |
|   static gboolean OnUdevMonitor(GIOChannel* source, GIOCondition condition,
 | |
|                                 gpointer data);
 | |
| 
 | |
|   udev_lib mUdev;
 | |
|   struct udev_monitor* mMonitor;
 | |
|   guint mMonitorSourceID;
 | |
|   // Information about currently connected gamepads.
 | |
|   AutoTArray<UniquePtr<Gamepad>, 4> mGamepads;
 | |
| };
 | |
| 
 | |
| // singleton instance
 | |
| LinuxGamepadService* gService = nullptr;
 | |
| 
 | |
| void LinuxGamepadService::AddDevice(struct udev_device* dev) {
 | |
|   RefPtr<GamepadPlatformService> service =
 | |
|       GamepadPlatformService::GetParentService();
 | |
|   if (!service) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const char* devpath = mUdev.udev_device_get_devnode(dev);
 | |
|   if (!devpath) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Ensure that this device hasn't already been added.
 | |
|   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
 | |
|     if (strcmp(mGamepads[i]->devpath, devpath) == 0) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   auto gamepad = MakeUnique<Gamepad>();
 | |
|   snprintf(gamepad->devpath, sizeof(gamepad->devpath), "%s", devpath);
 | |
|   GIOChannel* channel = g_io_channel_new_file(devpath, "r", nullptr);
 | |
|   if (!channel) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr);
 | |
|   g_io_channel_set_encoding(channel, nullptr, nullptr);
 | |
|   g_io_channel_set_buffered(channel, FALSE);
 | |
|   int fd = g_io_channel_unix_get_fd(channel);
 | |
| 
 | |
|   input_id id{0};
 | |
|   if (ioctl(fd, EVIOCGID, &id) < 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   char name[128]{0};
 | |
|   if (ioctl(fd, EVIOCGNAME(sizeof(name)), &name) < 0) {
 | |
|     strcpy(name, "Unknown Device");
 | |
|   }
 | |
| 
 | |
|   SprintfLiteral(gamepad->idstring, "%04" PRIx16 "-%04" PRIx16 "-%s", id.vendor,
 | |
|                  id.product, name);
 | |
| 
 | |
|   unsigned long keyBits[BITS_TO_LONGS(KEY_CNT)] = {0};
 | |
|   unsigned long absBits[BITS_TO_LONGS(ABS_CNT)] = {0};
 | |
|   if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 ||
 | |
|       ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   /* Here, we try to support even strange cases where proper semantic
 | |
|    * BTN_GAMEPAD button are combined with arbitrary extra buttons. */
 | |
| 
 | |
|   /* These are mappings where the index is a CanonicalButtonIndex and the value
 | |
|    * is an evdev code */
 | |
|   const std::array<uint16_t, BUTTON_INDEX_COUNT> kStandardButtons = {
 | |
|       /* BUTTON_INDEX_PRIMARY = */ BTN_SOUTH,
 | |
|       /* BUTTON_INDEX_SECONDARY = */ BTN_EAST,
 | |
|       /* BUTTON_INDEX_TERTIARY = */ BTN_WEST,
 | |
|       /* BUTTON_INDEX_QUATERNARY = */ BTN_NORTH,
 | |
|       /* BUTTON_INDEX_LEFT_SHOULDER = */ BTN_TL,
 | |
|       /* BUTTON_INDEX_RIGHT_SHOULDER = */ BTN_TR,
 | |
|       /* BUTTON_INDEX_LEFT_TRIGGER = */ BTN_TL2,
 | |
|       /* BUTTON_INDEX_RIGHT_TRIGGER = */ BTN_TR2,
 | |
|       /* BUTTON_INDEX_BACK_SELECT = */ BTN_SELECT,
 | |
|       /* BUTTON_INDEX_START = */ BTN_START,
 | |
|       /* BUTTON_INDEX_LEFT_THUMBSTICK = */ BTN_THUMBL,
 | |
|       /* BUTTON_INDEX_RIGHT_THUMBSTICK = */ BTN_THUMBR,
 | |
|       /* BUTTON_INDEX_DPAD_UP = */ BTN_DPAD_UP,
 | |
|       /* BUTTON_INDEX_DPAD_DOWN = */ BTN_DPAD_DOWN,
 | |
|       /* BUTTON_INDEX_DPAD_LEFT = */ BTN_DPAD_LEFT,
 | |
|       /* BUTTON_INDEX_DPAD_RIGHT = */ BTN_DPAD_RIGHT,
 | |
|       /* BUTTON_INDEX_META = */ BTN_MODE,
 | |
|   };
 | |
|   const std::array<uint16_t, AXIS_INDEX_COUNT> kStandardAxes = {
 | |
|       /* AXIS_INDEX_LEFT_STICK_X = */ ABS_X,
 | |
|       /* AXIS_INDEX_LEFT_STICK_Y = */ ABS_Y,
 | |
|       /* AXIS_INDEX_RIGHT_STICK_X = */ ABS_RX,
 | |
|       /* AXIS_INDEX_RIGHT_STICK_Y = */ ABS_RY,
 | |
|   };
 | |
| 
 | |
|   /*
 | |
|    * According to https://www.kernel.org/doc/html/latest/input/gamepad.html,
 | |
|    * "All gamepads that follow the protocol described here map BTN_GAMEPAD",
 | |
|    * so we can use it as a proxy for semantic buttons in general. If it's
 | |
|    * enabled, we're probably going to be acting like a standard gamepad
 | |
|    */
 | |
|   uint32_t numButtons = 0;
 | |
|   if (TestBit(keyBits, BTN_GAMEPAD)) {
 | |
|     gamepad->isStandardGamepad = true;
 | |
|     for (uint8_t button = 0; button < BUTTON_INDEX_COUNT; button++) {
 | |
|       gamepad->key_map[kStandardButtons[button]] = button;
 | |
|     }
 | |
|     numButtons = BUTTON_INDEX_COUNT;
 | |
|   }
 | |
| 
 | |
|   // Now, go through the non-semantic buttons and handle them as extras
 | |
|   for (uint16_t key = 0; key < KEY_MAX; key++) {
 | |
|     // Skip standard buttons
 | |
|     if (gamepad->isStandardGamepad &&
 | |
|         std::find(kStandardButtons.begin(), kStandardButtons.end(), key) !=
 | |
|             kStandardButtons.end()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (TestBit(keyBits, key)) {
 | |
|       gamepad->key_map[key] = numButtons++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   uint32_t numAxes = 0;
 | |
|   if (gamepad->isStandardGamepad) {
 | |
|     for (uint8_t i = 0; i < AXIS_INDEX_COUNT; i++) {
 | |
|       gamepad->abs_map[kStandardAxes[i]] = i;
 | |
|       LoadAbsInfo(fd, gamepad.get(), kStandardAxes[i]);
 | |
|     }
 | |
|     numAxes = AXIS_INDEX_COUNT;
 | |
| 
 | |
|     // These are not real axis and get remapped to buttons.
 | |
|     LoadAbsInfo(fd, gamepad.get(), ABS_HAT0X);
 | |
|     LoadAbsInfo(fd, gamepad.get(), ABS_HAT0Y);
 | |
|   }
 | |
| 
 | |
|   for (uint16_t i = 0; i < ABS_MAX; ++i) {
 | |
|     if (gamepad->isStandardGamepad &&
 | |
|         (std::find(kStandardAxes.begin(), kStandardAxes.end(), i) !=
 | |
|              kStandardAxes.end() ||
 | |
|          i == ABS_HAT0X || i == ABS_HAT0Y)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (TestBit(absBits, i)) {
 | |
|       if (LoadAbsInfo(fd, gamepad.get(), i)) {
 | |
|         gamepad->abs_map[i] = numAxes++;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (numAxes == 0) {
 | |
|     NS_WARNING("Gamepad with zero axes detected?");
 | |
|   }
 | |
|   if (numButtons == 0) {
 | |
|     NS_WARNING("Gamepad with zero buttons detected?");
 | |
|   }
 | |
| 
 | |
|   // NOTE: This almost always true, so we basically never use the remapping
 | |
|   // code.
 | |
|   if (gamepad->isStandardGamepad) {
 | |
|     gamepad->handle =
 | |
|         service->AddGamepad(gamepad->idstring, GamepadMappingType::Standard,
 | |
|                             GamepadHand::_empty, numButtons, numAxes, 0, 0, 0);
 | |
|   } else {
 | |
|     bool defaultRemapper = false;
 | |
|     RefPtr<GamepadRemapper> remapper =
 | |
|         GetGamepadRemapper(id.vendor, id.product, defaultRemapper);
 | |
|     MOZ_ASSERT(remapper);
 | |
|     remapper->SetAxisCount(numAxes);
 | |
|     remapper->SetButtonCount(numButtons);
 | |
| 
 | |
|     gamepad->handle = service->AddGamepad(
 | |
|         gamepad->idstring, remapper->GetMappingType(), GamepadHand::_empty,
 | |
|         remapper->GetButtonCount(), remapper->GetAxisCount(), 0,
 | |
|         remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());
 | |
|     gamepad->remapper = remapper.forget();
 | |
|   }
 | |
|   // TODO: Bug 680289, implement gamepad haptics for Linux.
 | |
|   // TODO: Bug 1523355, implement gamepad lighindicator and touch for Linux.
 | |
| 
 | |
|   gamepad->source_id =
 | |
|       g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
 | |
|                      OnGamepadData, gamepad.get());
 | |
|   g_io_channel_unref(channel);
 | |
| 
 | |
|   mGamepads.AppendElement(std::move(gamepad));
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::RemoveDevice(struct udev_device* dev) {
 | |
|   RefPtr<GamepadPlatformService> service =
 | |
|       GamepadPlatformService::GetParentService();
 | |
|   if (!service) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const char* devpath = mUdev.udev_device_get_devnode(dev);
 | |
|   if (!devpath) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
 | |
|     if (strcmp(mGamepads[i]->devpath, devpath) == 0) {
 | |
|       auto gamepad = std::move(mGamepads[i]);
 | |
|       mGamepads.RemoveElementAt(i);
 | |
| 
 | |
|       g_source_remove(gamepad->source_id);
 | |
|       service->RemoveGamepad(gamepad->handle);
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::ScanForDevices() {
 | |
|   struct udev_enumerate* en = mUdev.udev_enumerate_new(mUdev.udev);
 | |
|   mUdev.udev_enumerate_add_match_subsystem(en, "input");
 | |
|   mUdev.udev_enumerate_scan_devices(en);
 | |
| 
 | |
|   struct udev_list_entry* dev_list_entry;
 | |
|   for (dev_list_entry = mUdev.udev_enumerate_get_list_entry(en);
 | |
|        dev_list_entry != nullptr;
 | |
|        dev_list_entry = mUdev.udev_list_entry_get_next(dev_list_entry)) {
 | |
|     const char* path = mUdev.udev_list_entry_get_name(dev_list_entry);
 | |
|     struct udev_device* dev =
 | |
|         mUdev.udev_device_new_from_syspath(mUdev.udev, path);
 | |
|     if (IsDeviceGamepad(dev)) {
 | |
|       AddDevice(dev);
 | |
|     }
 | |
| 
 | |
|     mUdev.udev_device_unref(dev);
 | |
|   }
 | |
| 
 | |
|   mUdev.udev_enumerate_unref(en);
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::AddMonitor() {
 | |
|   // Add a monitor to watch for device changes
 | |
|   mMonitor = mUdev.udev_monitor_new_from_netlink(mUdev.udev, "udev");
 | |
|   if (!mMonitor) {
 | |
|     // Not much we can do here.
 | |
|     return;
 | |
|   }
 | |
|   mUdev.udev_monitor_filter_add_match_subsystem_devtype(mMonitor, "input",
 | |
|                                                         nullptr);
 | |
| 
 | |
|   int monitor_fd = mUdev.udev_monitor_get_fd(mMonitor);
 | |
|   GIOChannel* monitor_channel = g_io_channel_unix_new(monitor_fd);
 | |
|   mMonitorSourceID = g_io_add_watch(monitor_channel,
 | |
|                                     GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
 | |
|                                     OnUdevMonitor, nullptr);
 | |
|   g_io_channel_unref(monitor_channel);
 | |
| 
 | |
|   mUdev.udev_monitor_enable_receiving(mMonitor);
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::RemoveMonitor() {
 | |
|   if (mMonitorSourceID) {
 | |
|     g_source_remove(mMonitorSourceID);
 | |
|     mMonitorSourceID = 0;
 | |
|   }
 | |
|   if (mMonitor) {
 | |
|     mUdev.udev_monitor_unref(mMonitor);
 | |
|     mMonitor = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::Startup() {
 | |
|   // Don't bother starting up if libudev couldn't be loaded or initialized.
 | |
|   if (!mUdev) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AddMonitor();
 | |
|   ScanForDevices();
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::Shutdown() {
 | |
|   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
 | |
|     g_source_remove(mGamepads[i]->source_id);
 | |
|   }
 | |
|   mGamepads.Clear();
 | |
|   RemoveMonitor();
 | |
| }
 | |
| 
 | |
| bool LinuxGamepadService::IsDeviceGamepad(struct udev_device* aDev) {
 | |
|   if (!mUdev.udev_device_get_property_value(aDev, "ID_INPUT_JOYSTICK")) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const char* devpath = mUdev.udev_device_get_devnode(aDev);
 | |
|   if (!devpath) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return strncmp(devpath, kEvdevPath, strlen(kEvdevPath)) == 0;
 | |
| }
 | |
| 
 | |
| void LinuxGamepadService::ReadUdevChange() {
 | |
|   struct udev_device* dev = mUdev.udev_monitor_receive_device(mMonitor);
 | |
|   if (IsDeviceGamepad(dev)) {
 | |
|     const char* action = mUdev.udev_device_get_action(dev);
 | |
|     if (strcmp(action, "add") == 0) {
 | |
|       AddDevice(dev);
 | |
|     } else if (strcmp(action, "remove") == 0) {
 | |
|       RemoveDevice(dev);
 | |
|     }
 | |
|   }
 | |
|   mUdev.udev_device_unref(dev);
 | |
| }
 | |
| 
 | |
| // static
 | |
| gboolean LinuxGamepadService::OnGamepadData(GIOChannel* source,
 | |
|                                             GIOCondition condition,
 | |
|                                             gpointer data) {
 | |
|   RefPtr<GamepadPlatformService> service =
 | |
|       GamepadPlatformService::GetParentService();
 | |
|   if (!service) {
 | |
|     return TRUE;
 | |
|   }
 | |
|   auto* gamepad = static_cast<Gamepad*>(data);
 | |
| 
 | |
|   // TODO: remove gamepad?
 | |
|   if (condition & (G_IO_ERR | G_IO_HUP)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   while (true) {
 | |
|     struct input_event event {};
 | |
|     gsize count;
 | |
|     GError* err = nullptr;
 | |
|     if (g_io_channel_read_chars(source, (gchar*)&event, sizeof(event), &count,
 | |
|                                 &err) != G_IO_STATUS_NORMAL ||
 | |
|         count == 0) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     switch (event.type) {
 | |
|       case EV_KEY:
 | |
|         if (gamepad->isStandardGamepad) {
 | |
|           service->NewButtonEvent(gamepad->handle, gamepad->key_map[event.code],
 | |
|                                   !!event.value);
 | |
|         } else {
 | |
|           gamepad->remapper->RemapButtonEvent(
 | |
|               gamepad->handle, gamepad->key_map[event.code], !!event.value);
 | |
|         }
 | |
|         break;
 | |
|       case EV_ABS: {
 | |
|         if (!gamepad->abs_info.count(event.code)) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         double scaledValue =
 | |
|             ScaleAxis(gamepad->abs_info[event.code], event.value);
 | |
|         if (gamepad->isStandardGamepad) {
 | |
|           switch (event.code) {
 | |
|             case ABS_HAT0X:
 | |
|               service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_LEFT,
 | |
|                                       AxisNegativeAsButton(scaledValue));
 | |
|               service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_RIGHT,
 | |
|                                       AxisPositiveAsButton(scaledValue));
 | |
|               break;
 | |
|             case ABS_HAT0Y:
 | |
|               service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_UP,
 | |
|                                       AxisNegativeAsButton(scaledValue));
 | |
|               service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_DOWN,
 | |
|                                       AxisPositiveAsButton(scaledValue));
 | |
|               break;
 | |
|             default:
 | |
|               service->NewAxisMoveEvent(
 | |
|                   gamepad->handle, gamepad->abs_map[event.code], scaledValue);
 | |
|               break;
 | |
|           }
 | |
|         } else {
 | |
|           gamepad->remapper->RemapAxisMoveEvent(
 | |
|               gamepad->handle, gamepad->abs_map[event.code], scaledValue);
 | |
|         }
 | |
|       } break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| // static
 | |
| gboolean LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
 | |
|                                             GIOCondition condition,
 | |
|                                             gpointer data) {
 | |
|   if (condition & (G_IO_ERR | G_IO_HUP)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   gService->ReadUdevChange();
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| void StartGamepadMonitoring() {
 | |
|   if (gService) {
 | |
|     return;
 | |
|   }
 | |
|   gService = new LinuxGamepadService();
 | |
|   gService->Startup();
 | |
| }
 | |
| 
 | |
| void StopGamepadMonitoring() {
 | |
|   if (!gService) {
 | |
|     return;
 | |
|   }
 | |
|   gService->Shutdown();
 | |
|   delete gService;
 | |
|   gService = nullptr;
 | |
| }
 | |
| 
 | |
| void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
 | |
|                                    const Tainted<uint32_t>& aLightColorIndex,
 | |
|                                    const uint8_t& aRed, const uint8_t& aGreen,
 | |
|                                    const uint8_t& aBlue) {
 | |
|   // TODO: Bug 1523355.
 | |
|   NS_WARNING("Linux doesn't support gamepad light indicator.");
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 |