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

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

mercurial