Wed, 31 Dec 2014 06:55:50 +0100
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 |