hal/linux/LinuxGamepad.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /*
     6  * LinuxGamepadService: A Linux backend for the GamepadService.
     7  * Derived from the kernel documentation at
     8  * http://www.kernel.org/doc/Documentation/input/joystick-api.txt
     9  */
    10 #include <algorithm>
    11 #include <cstddef>
    13 #include <glib.h>
    14 #include <linux/joystick.h>
    15 #include <stdio.h>
    16 #include <stdint.h>
    17 #include <sys/ioctl.h>
    18 #include <unistd.h>
    20 #include "nscore.h"
    21 #include "mozilla/dom/GamepadService.h"
    22 #include "udev.h"
    24 namespace {
    26 using mozilla::dom::GamepadService;
    27 using mozilla::udev_lib;
    28 using mozilla::udev_device;
    29 using mozilla::udev_list_entry;
    30 using mozilla::udev_enumerate;
    31 using mozilla::udev_monitor;
    33 static const float kMaxAxisValue = 32767.0;
    34 static const char kJoystickPath[] = "/dev/input/js";
    36 //TODO: should find a USB identifier for each device so we can
    37 // provide something that persists across connect/disconnect cycles.
    38 typedef struct {
    39   int index;
    40   guint source_id;
    41   int numAxes;
    42   int numButtons;
    43   char idstring[128];
    44   char devpath[PATH_MAX];
    45 } Gamepad;
    47 class LinuxGamepadService {
    48 public:
    49   LinuxGamepadService() : mMonitor(nullptr),
    50                           mMonitorSourceID(0) {
    51   }
    53   void Startup();
    54   void Shutdown();
    56 private:
    57   void AddDevice(struct udev_device* dev);
    58   void RemoveDevice(struct udev_device* dev);
    59   void ScanForDevices();
    60   void AddMonitor();
    61   void RemoveMonitor();
    62   bool is_gamepad(struct udev_device* dev);
    63   void ReadUdevChange();
    65   // handler for data from /dev/input/jsN
    66   static gboolean OnGamepadData(GIOChannel *source,
    67                                 GIOCondition condition,
    68                                 gpointer data);
    70   // handler for data from udev monitor
    71   static gboolean OnUdevMonitor(GIOChannel *source,
    72                                 GIOCondition condition,
    73                                 gpointer data);
    75   udev_lib mUdev;
    76   struct udev_monitor* mMonitor;
    77   guint mMonitorSourceID;
    78   // Information about currently connected gamepads.
    79   nsAutoTArray<Gamepad,4> mGamepads;
    80 };
    82 // singleton instance
    83 LinuxGamepadService* gService = nullptr;
    85 void
    86 LinuxGamepadService::AddDevice(struct udev_device* dev)
    87 {
    88   const char* devpath = mUdev.udev_device_get_devnode(dev);
    89   if (!devpath) {
    90     return;
    91   }
    93   // Ensure that this device hasn't already been added.
    94   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    95     if (strcmp(mGamepads[i].devpath, devpath) == 0) {
    96       return;
    97     }
    98   }
   100   Gamepad gamepad;
   101   snprintf(gamepad.devpath, sizeof(gamepad.devpath), "%s", devpath);
   102   GIOChannel* channel = g_io_channel_new_file(devpath, "r", nullptr);
   103   if (!channel) {
   104     return;
   105   }
   107   g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr);
   108   g_io_channel_set_encoding(channel, nullptr, nullptr);
   109   g_io_channel_set_buffered(channel, FALSE);
   110   int fd = g_io_channel_unix_get_fd(channel);
   111   char name[128];
   112   if (ioctl(fd, JSIOCGNAME(sizeof(name)), &name) == -1) {
   113     strcpy(name, "unknown");
   114   }
   115   const char* vendor_id =
   116     mUdev.udev_device_get_property_value(dev, "ID_VENDOR_ID");
   117   const char* model_id =
   118     mUdev.udev_device_get_property_value(dev, "ID_MODEL_ID");
   119   if (!vendor_id || !model_id) {
   120     struct udev_device* parent =
   121       mUdev.udev_device_get_parent_with_subsystem_devtype(dev,
   122                                                           "input",
   123                                                           nullptr);
   124     if (parent) {
   125       vendor_id = mUdev.udev_device_get_sysattr_value(parent, "id/vendor");
   126       model_id = mUdev.udev_device_get_sysattr_value(parent, "id/product");
   127     }
   128   }
   129   snprintf(gamepad.idstring, sizeof(gamepad.idstring),
   130            "%s-%s-%s",
   131            vendor_id ? vendor_id : "unknown",
   132            model_id ? model_id : "unknown",
   133            name);
   135   char numAxes = 0, numButtons = 0;
   136   ioctl(fd, JSIOCGAXES, &numAxes);
   137   gamepad.numAxes = numAxes;
   138   ioctl(fd, JSIOCGBUTTONS, &numButtons);
   139   gamepad.numButtons = numButtons;
   141   nsRefPtr<GamepadService> service(GamepadService::GetService());
   142   gamepad.index = service->AddGamepad(gamepad.idstring,
   143                                       mozilla::dom::NoMapping,
   144                                       gamepad.numButtons,
   145                                       gamepad.numAxes);
   147   gamepad.source_id =
   148     g_io_add_watch(channel,
   149                    GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
   150                    OnGamepadData,
   151                    GINT_TO_POINTER(gamepad.index));
   152   g_io_channel_unref(channel);
   154   mGamepads.AppendElement(gamepad);
   155 }
   157 void
   158 LinuxGamepadService::RemoveDevice(struct udev_device* dev)
   159 {
   160   const char* devpath = mUdev.udev_device_get_devnode(dev);
   161   if (!devpath) {
   162     return;
   163   }
   165   nsRefPtr<GamepadService> service(GamepadService::GetService());
   166   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
   167     if (strcmp(mGamepads[i].devpath, devpath) == 0) {
   168       g_source_remove(mGamepads[i].source_id);
   169       service->RemoveGamepad(mGamepads[i].index);
   170       mGamepads.RemoveElementAt(i);
   171       break;
   172     }
   173   }
   174 }
   176 void
   177 LinuxGamepadService::ScanForDevices()
   178 {
   179   struct udev_enumerate* en = mUdev.udev_enumerate_new(mUdev.udev);
   180   mUdev.udev_enumerate_add_match_subsystem(en, "input");
   181   mUdev.udev_enumerate_scan_devices(en);
   183   struct udev_list_entry* dev_list_entry;
   184   for (dev_list_entry = mUdev.udev_enumerate_get_list_entry(en);
   185        dev_list_entry != nullptr;
   186        dev_list_entry = mUdev.udev_list_entry_get_next(dev_list_entry)) {
   187     const char* path = mUdev.udev_list_entry_get_name(dev_list_entry);
   188     struct udev_device* dev = mUdev.udev_device_new_from_syspath(mUdev.udev,
   189                                                                  path);
   190     if (is_gamepad(dev)) {
   191       AddDevice(dev);
   192     }
   194     mUdev.udev_device_unref(dev);
   195   }
   197   mUdev.udev_enumerate_unref(en);
   198 }
   200 void
   201 LinuxGamepadService::AddMonitor()
   202 {
   203   // Add a monitor to watch for device changes
   204   mMonitor =
   205     mUdev.udev_monitor_new_from_netlink(mUdev.udev, "udev");
   206   if (!mMonitor) {
   207     // Not much we can do here.
   208     return;
   209   }
   210   mUdev.udev_monitor_filter_add_match_subsystem_devtype(mMonitor,
   211                                                         "input",
   212 							nullptr);
   214   int monitor_fd = mUdev.udev_monitor_get_fd(mMonitor);
   215   GIOChannel* monitor_channel = g_io_channel_unix_new(monitor_fd);
   216   mMonitorSourceID =
   217     g_io_add_watch(monitor_channel,
   218                    GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
   219                    OnUdevMonitor,
   220                    nullptr);
   221   g_io_channel_unref(monitor_channel);
   223   mUdev.udev_monitor_enable_receiving(mMonitor);
   224 }
   226 void
   227 LinuxGamepadService::RemoveMonitor()
   228 {
   229   if (mMonitorSourceID) {
   230     g_source_remove(mMonitorSourceID);
   231     mMonitorSourceID = 0;
   232   }
   233   if (mMonitor) {
   234     mUdev.udev_monitor_unref(mMonitor);
   235     mMonitor = nullptr;
   236   }
   237 }
   239 void
   240 LinuxGamepadService::Startup()
   241 {
   242   // Don't bother starting up if libudev couldn't be loaded or initialized.
   243   if (!mUdev)
   244     return;
   246   AddMonitor();
   247   ScanForDevices();
   248 }
   250 void
   251 LinuxGamepadService::Shutdown()
   252 {
   253   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
   254     g_source_remove(mGamepads[i].source_id);
   255   }
   256   mGamepads.Clear();
   257   RemoveMonitor();
   258 }
   260 bool
   261 LinuxGamepadService::is_gamepad(struct udev_device* dev)
   262 {
   263   if (!mUdev.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"))
   264     return false;
   266   const char* devpath = mUdev.udev_device_get_devnode(dev);
   267   if (!devpath) {
   268     return false;
   269   }
   270   if (strncmp(kJoystickPath, devpath, sizeof(kJoystickPath) - 1) != 0) {
   271     return false;
   272   }
   274   return true;
   275 }
   277 void
   278 LinuxGamepadService::ReadUdevChange()
   279 {
   280   struct udev_device* dev =
   281     mUdev.udev_monitor_receive_device(mMonitor);
   282   const char* action = mUdev.udev_device_get_action(dev);
   283   if (is_gamepad(dev)) {
   284     if (strcmp(action, "add") == 0) {
   285       AddDevice(dev);
   286     } else if (strcmp(action, "remove") == 0) {
   287       RemoveDevice(dev);
   288     }
   289   }
   290   mUdev.udev_device_unref(dev);
   291 }
   293 // static
   294 gboolean
   295 LinuxGamepadService::OnGamepadData(GIOChannel* source,
   296                                    GIOCondition condition,
   297                                    gpointer data)
   298 {
   299   int index = GPOINTER_TO_INT(data);
   300   //TODO: remove gamepad?
   301   if (condition & G_IO_ERR || condition & G_IO_HUP)
   302     return FALSE;
   304   while (true) {
   305     struct js_event event;
   306     gsize count;
   307     GError* err = nullptr;
   308     if (g_io_channel_read_chars(source,
   309 				(gchar*)&event,
   310 				sizeof(event),
   311 				&count,
   312 				&err) != G_IO_STATUS_NORMAL ||
   313 	count == 0) {
   314       break;
   315     }
   317     //TODO: store device state?
   318     if (event.type & JS_EVENT_INIT) {
   319       continue;
   320     }
   322     nsRefPtr<GamepadService> service(GamepadService::GetService());
   323     switch (event.type) {
   324     case JS_EVENT_BUTTON:
   325       service->NewButtonEvent(index, event.number, !!event.value);
   326       break;
   327     case JS_EVENT_AXIS:
   328       service->NewAxisMoveEvent(index, event.number,
   329                                 ((float)event.value) / kMaxAxisValue);
   330       break;
   331     }
   332   }
   334   return TRUE;
   335 }
   337 // static
   338 gboolean
   339 LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
   340                                    GIOCondition condition,
   341                                    gpointer data)
   342 {
   343   if (condition & G_IO_ERR || condition & G_IO_HUP)
   344     return FALSE;
   346   gService->ReadUdevChange();
   347   return TRUE;
   348 }
   350 } // namespace
   352 namespace mozilla {
   353 namespace hal_impl {
   355 void StartMonitoringGamepadStatus()
   356 {
   357   if (!gService) {
   358     gService = new LinuxGamepadService();
   359     gService->Startup();
   360   }
   361 }
   363 void StopMonitoringGamepadStatus()
   364 {
   365   if (gService) {
   366     gService->Shutdown();
   367     delete gService;
   368     gService = nullptr;
   369   }
   370 }
   372 } // namespace hal_impl
   373 } // namespace mozilla

mercurial