LCOV - code coverage report
Current view: top level - dom/gamepad/linux - LinuxGamepad.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 1 165 0.6 %
Date: 2017-07-14 16:53:18 Functions: 2 17 11.8 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13