Browse files

bug 604039 - Linux gamepad backend. r=karlt

--HG--
extra : rebase_source : 32d47af727b9a6209d321a8622a2f3ecb7a44d25
  • Loading branch information...
1 parent 1983ec9 commit 74ec473eb6eab293576104c3a904a0c10b17a2ea Ted Mielczarek committed Aug 3, 2011
Showing with 491 additions and 0 deletions.
  1. +7 −0 configure.in
  2. +3 −0 hal/Makefile.in
  3. +364 −0 hal/linux/LinuxGamepad.cpp
  4. +117 −0 hal/linux/udev.h
View
7 configure.in
@@ -5968,6 +5968,13 @@ if test "$MOZ_GAMEPAD"; then
fi
MOZ_GAMEPAD_BACKEND=windows
;;
+ Linux)
+ MOZ_CHECK_HEADER([linux/joystick.h])
+ if test "$ac_cv_header_linux_joystick_h" != "yes"; then
+ AC_MSG_ERROR([Can't find header linux/joystick.h, needed for gamepad support. Please install Linux kernel headers or reconfigure with --disable-gamepad to disable gamepad support.])
+ fi
+ MOZ_GAMEPAD_BACKEND=linux
+ ;;
*)
;;
esac
View
3 hal/Makefile.in
@@ -51,6 +51,9 @@ endif
ifeq (windows,$(MOZ_GAMEPAD_BACKEND))
CPPSRCS += WindowsGamepad.cpp
endif
+ifeq (linux,$(MOZ_GAMEPAD_BACKEND))
+CPPSRCS += LinuxGamepad.cpp
+endif
ifeq (android,$(MOZ_WIDGET_TOOLKIT))
CPPSRCS += \
View
364 hal/linux/LinuxGamepad.cpp
@@ -0,0 +1,364 @@
+/* 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: A Linux backend for the GamepadService.
+ * Derived from the kernel documentation at
+ * http://www.kernel.org/doc/Documentation/input/joystick-api.txt
+ */
+#include <algorithm>
+#include <cstddef>
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "nscore.h"
+#include "mozilla/dom/GamepadService.h"
+#include "udev.h"
+
+// Include this later because it also does #define JS_VERSION
+#include <linux/joystick.h>
+
+namespace {
+
+using mozilla::dom::GamepadService;
+using mozilla::udev_lib;
+using mozilla::udev_device;
+using mozilla::udev_list_entry;
+using mozilla::udev_enumerate;
+using mozilla::udev_monitor;
+
+static const float kMaxAxisValue = 32767.0;
+static const char kJoystickPath[] = "/dev/input/js";
+
+//TODO: should find a USB identifier for each device so we can
+// provide something that persists across connect/disconnect cycles.
+typedef struct {
+ int index;
+ guint source_id;
+ int numAxes;
+ int numButtons;
+ char idstring[128];
+ char devpath[PATH_MAX];
+} Gamepad;
+
+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 is_gamepad(struct udev_device* dev);
+ void ReadUdevChange();
+
+ // handler for data from /dev/input/jsN
+ 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.
+ nsAutoTArray<Gamepad,4> mGamepads;
+};
+
+// singleton instance
+LinuxGamepadService* gService = nullptr;
+
+void
+LinuxGamepadService::AddDevice(struct udev_device* dev)
+{
+ 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;
+ }
+ }
+
+ Gamepad 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);
+ char name[128];
+ if (ioctl(fd, JSIOCGNAME(sizeof(name)), &name) == -1) {
+ strcpy(name, "unknown");
+ }
+ const char* vendor_id =
+ mUdev.udev_device_get_property_value(dev, "ID_VENDOR_ID");
+ const char* model_id =
+ mUdev.udev_device_get_property_value(dev, "ID_MODEL_ID");
+ snprintf(gamepad.idstring, sizeof(gamepad.idstring),
+ "%s-%s-%s",
+ vendor_id ? vendor_id : "unknown",
+ model_id ? model_id : "unknown",
+ name);
+
+ char numAxes = 0, numButtons = 0;
+ ioctl(fd, JSIOCGAXES, &numAxes);
+ gamepad.numAxes = numAxes;
+ ioctl(fd, JSIOCGBUTTONS, &numButtons);
+ gamepad.numButtons = numButtons;
+
+ nsRefPtr<GamepadService> service(GamepadService::GetService());
+ gamepad.index = service->AddGamepad(gamepad.idstring,
+ gamepad.numButtons,
+ gamepad.numAxes);
+
+ gamepad.source_id =
+ g_io_add_watch(channel,
+ GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnGamepadData,
+ GINT_TO_POINTER(gamepad.index));
+ g_io_channel_unref(channel);
+
+ mGamepads.AppendElement(gamepad);
+}
+
+void
+LinuxGamepadService::RemoveDevice(struct udev_device* dev)
+{
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ nsRefPtr<GamepadService> service(GamepadService::GetService());
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i].devpath, devpath) == 0) {
+ g_source_remove(mGamepads[i].source_id);
+ service->RemoveGamepad(mGamepads[i].index);
+ mGamepads.RemoveElementAt(i);
+ 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 (is_gamepad(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::is_gamepad(struct udev_device* dev)
+{
+ if (!mUdev.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"))
+ return false;
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return false;
+ }
+ if (strncmp(kJoystickPath, devpath, sizeof(kJoystickPath) - 1) != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+LinuxGamepadService::ReadUdevChange()
+{
+ struct udev_device* dev =
+ mUdev.udev_monitor_receive_device(mMonitor);
+ const char* action = mUdev.udev_device_get_action(dev);
+ if (is_gamepad(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)
+{
+ int index = GPOINTER_TO_INT(data);
+ //TODO: remove gamepad?
+ if (condition & G_IO_ERR || condition & G_IO_HUP)
+ return FALSE;
+
+ while (true) {
+ struct js_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;
+ }
+
+ //TODO: store device state?
+ if (event.type & JS_EVENT_INIT) {
+ continue;
+ }
+
+ nsRefPtr<GamepadService> service(GamepadService::GetService());
+ switch (event.type) {
+ case JS_EVENT_BUTTON:
+ service->NewButtonEvent(index, event.number, !!event.value);
+ break;
+ case JS_EVENT_AXIS:
+ service->NewAxisMoveEvent(index, event.number,
+ ((float)event.value) / kMaxAxisValue);
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+// static
+gboolean
+LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data)
+{
+ if (condition & G_IO_ERR || condition & G_IO_HUP)
+ return FALSE;
+
+ gService->ReadUdevChange();
+ return TRUE;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace hal_impl {
+
+void StartMonitoringGamepadStatus()
+{
+ if (!gService) {
+ gService = new LinuxGamepadService();
+ gService->Startup();
+ }
+}
+
+void StopMonitoringGamepadStatus()
+{
+ if (gService) {
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+ }
+}
+
+} // namespace hal_impl
+} // namespace mozilla
View
117 hal/linux/udev.h
@@ -0,0 +1,117 @@
+/* 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/. */
+
+/*
+ * This file defines a wrapper around libudev so we can avoid
+ * linking directly to it and use dlopen instead.
+ */
+
+#ifndef HAL_LINUX_UDEV_H_
+#define HAL_LINUX_UDEV_H_
+
+#include <dlfcn.h>
+
+namespace mozilla {
+
+struct udev;
+struct udev_device;
+struct udev_enumerate;
+struct udev_list_entry;
+struct udev_monitor;
+
+class udev_lib {
+ public:
+ udev_lib() : lib(dlopen("libudev.so.0", RTLD_LAZY | RTLD_GLOBAL)),
+ udev(NULL) {
+ if (lib && LoadSymbols())
+ udev = udev_new();
+ }
+
+ ~udev_lib() {
+ if (udev) {
+ udev_unref(udev);
+ }
+
+ if (lib) {
+ dlclose(lib);
+ }
+ }
+
+ operator bool() {
+ return udev;
+ }
+
+ private:
+ bool LoadSymbols() {
+#define DLSYM(s) \
+ do { \
+ s = (typeof(s))dlsym(lib, #s); \
+ if (!s) return false; \
+ } while (0)
+
+ DLSYM(udev_new);
+ DLSYM(udev_unref);
+ DLSYM(udev_device_unref);
+ DLSYM(udev_device_new_from_syspath);
+ DLSYM(udev_device_get_devnode);
+ DLSYM(udev_device_get_property_value);
+ DLSYM(udev_device_get_action);
+ DLSYM(udev_enumerate_new);
+ DLSYM(udev_enumerate_unref);
+ DLSYM(udev_enumerate_add_match_subsystem);
+ DLSYM(udev_enumerate_scan_devices);
+ DLSYM(udev_enumerate_get_list_entry);
+ DLSYM(udev_list_entry_get_next);
+ DLSYM(udev_list_entry_get_name);
+ DLSYM(udev_monitor_new_from_netlink);
+ DLSYM(udev_monitor_filter_add_match_subsystem_devtype);
+ DLSYM(udev_monitor_enable_receiving);
+ DLSYM(udev_monitor_get_fd);
+ DLSYM(udev_monitor_receive_device);
+ DLSYM(udev_monitor_unref);
+#undef DLSYM
+ return true;
+ }
+
+ void* lib;
+
+ public:
+ struct udev* udev;
+
+ // Function pointers returned from dlsym.
+ struct udev* (*udev_new)(void);
+ void (*udev_unref)(struct udev*);
+
+ void (*udev_device_unref)(struct udev_device*);
+ struct udev_device* (*udev_device_new_from_syspath)(struct udev*,
+ const char*);
+ const char* (*udev_device_get_devnode)(struct udev_device*);
+ const char* (*udev_device_get_property_value)(struct udev_device*,
+ const char*);
+ const char* (*udev_device_get_action)(struct udev_device*);
+
+ struct udev_enumerate* (*udev_enumerate_new)(struct udev*);
+ void (*udev_enumerate_unref)(struct udev_enumerate*);
+ int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate*,
+ const char*);
+ int (*udev_enumerate_scan_devices)(struct udev_enumerate*);
+ struct udev_list_entry* (*udev_enumerate_get_list_entry)
+ (struct udev_enumerate*);
+
+ struct udev_list_entry* (*udev_list_entry_get_next)(struct udev_list_entry *);
+ const char* (*udev_list_entry_get_name)(struct udev_list_entry*);
+
+ struct udev_monitor* (*udev_monitor_new_from_netlink)(struct udev*,
+ const char*);
+ int (*udev_monitor_filter_add_match_subsystem_devtype)
+ (struct udev_monitor*, const char*, const char*);
+ int (*udev_monitor_enable_receiving)(struct udev_monitor*);
+ int (*udev_monitor_get_fd)(struct udev_monitor*);
+ struct udev_device* (*udev_monitor_receive_device)(struct udev_monitor*);
+ void (*udev_monitor_unref)(struct udev_monitor*);
+};
+
+} // namespace mozilla
+
+#endif // HAL_LINUX_UDEV_H_

0 comments on commit 74ec473

Please sign in to comment.