summaryrefslogtreecommitdiffstats
path: root/dom/gamepad/linux
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/gamepad/linux
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/gamepad/linux')
-rw-r--r--dom/gamepad/linux/LinuxGamepad.cpp529
-rw-r--r--dom/gamepad/linux/udev.h150
2 files changed, 679 insertions, 0 deletions
diff --git a/dom/gamepad/linux/LinuxGamepad.cpp b/dom/gamepad/linux/LinuxGamepad.cpp
new file mode 100644
index 0000000000..e7a7071b86
--- /dev/null
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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: An evdev backend for the GamepadService.
+ *
+ * Ref: https://www.kernel.org/doc/html/latest/input/gamepad.html
+ */
+#include <algorithm>
+#include <unordered_map>
+#include <cstddef>
+
+#include <glib.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "nscore.h"
+#include "mozilla/dom/GamepadHandle.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/dom/GamepadRemapping.h"
+#include "mozilla/Tainting.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Sprintf.h"
+#include "udev.h"
+
+#define BITS_PER_LONG ((sizeof(unsigned long)) * 8)
+#define BITS_TO_LONGS(x) (((x) + BITS_PER_LONG - 1) / BITS_PER_LONG)
+
+namespace {
+
+using namespace mozilla::dom;
+using mozilla::MakeUnique;
+using mozilla::udev_device;
+using mozilla::udev_enumerate;
+using mozilla::udev_lib;
+using mozilla::udev_list_entry;
+using mozilla::udev_monitor;
+using mozilla::UniquePtr;
+
+static const char kEvdevPath[] = "/dev/input/event";
+
+static inline bool TestBit(const unsigned long* arr, size_t bit) {
+ return !!(arr[bit / BITS_PER_LONG] & (1LL << (bit % BITS_PER_LONG)));
+}
+
+static inline double ScaleAxis(const input_absinfo& info, int value) {
+ return 2.0 * (value - info.minimum) / (double)(info.maximum - info.minimum) -
+ 1.0;
+}
+
+// TODO: should find a USB identifier for each device so we can
+// provide something that persists across connect/disconnect cycles.
+struct Gamepad {
+ GamepadHandle handle;
+ bool isStandardGamepad = false;
+ RefPtr<GamepadRemapper> remapper = nullptr;
+ guint source_id = UINT_MAX;
+ char idstring[256] = {0};
+ char devpath[PATH_MAX] = {0};
+ uint8_t key_map[KEY_MAX] = {0};
+ uint8_t abs_map[ABS_MAX] = {0};
+ std::unordered_map<uint16_t, input_absinfo> abs_info;
+};
+
+static inline bool LoadAbsInfo(int fd, Gamepad* gamepad, uint16_t code) {
+ input_absinfo info{0};
+ if (ioctl(fd, EVIOCGABS(code), &info) < 0) {
+ return false;
+ }
+ if (info.minimum == info.maximum) {
+ return false;
+ }
+ gamepad->abs_info.emplace(code, std::move(info));
+ return true;
+}
+
+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 IsDeviceGamepad(struct udev_device* dev);
+ void ReadUdevChange();
+
+ // handler for data from /dev/input/eventN
+ 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.
+ AutoTArray<UniquePtr<Gamepad>, 4> mGamepads;
+};
+
+// singleton instance
+LinuxGamepadService* gService = nullptr;
+
+void LinuxGamepadService::AddDevice(struct udev_device* dev) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ 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;
+ }
+ }
+
+ auto gamepad = MakeUnique<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);
+
+ input_id id{0};
+ if (ioctl(fd, EVIOCGID, &id) < 0) {
+ return;
+ }
+
+ char name[128]{0};
+ if (ioctl(fd, EVIOCGNAME(sizeof(name)), &name) < 0) {
+ strcpy(name, "Unknown Device");
+ }
+
+ SprintfLiteral(gamepad->idstring, "%04" PRIx16 "-%04" PRIx16 "-%s", id.vendor,
+ id.product, name);
+
+ unsigned long keyBits[BITS_TO_LONGS(KEY_CNT)] = {0};
+ unsigned long absBits[BITS_TO_LONGS(ABS_CNT)] = {0};
+ if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 ||
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0) {
+ return;
+ }
+
+ /* Here, we try to support even strange cases where proper semantic
+ * BTN_GAMEPAD button are combined with arbitrary extra buttons. */
+
+ /* These are mappings where the index is a CanonicalButtonIndex and the value
+ * is an evdev code */
+ const std::array<uint16_t, BUTTON_INDEX_COUNT> kStandardButtons = {
+ /* BUTTON_INDEX_PRIMARY = */ BTN_SOUTH,
+ /* BUTTON_INDEX_SECONDARY = */ BTN_EAST,
+ /* BUTTON_INDEX_TERTIARY = */ BTN_WEST,
+ /* BUTTON_INDEX_QUATERNARY = */ BTN_NORTH,
+ /* BUTTON_INDEX_LEFT_SHOULDER = */ BTN_TL,
+ /* BUTTON_INDEX_RIGHT_SHOULDER = */ BTN_TR,
+ /* BUTTON_INDEX_LEFT_TRIGGER = */ BTN_TL2,
+ /* BUTTON_INDEX_RIGHT_TRIGGER = */ BTN_TR2,
+ /* BUTTON_INDEX_BACK_SELECT = */ BTN_SELECT,
+ /* BUTTON_INDEX_START = */ BTN_START,
+ /* BUTTON_INDEX_LEFT_THUMBSTICK = */ BTN_THUMBL,
+ /* BUTTON_INDEX_RIGHT_THUMBSTICK = */ BTN_THUMBR,
+ /* BUTTON_INDEX_DPAD_UP = */ BTN_DPAD_UP,
+ /* BUTTON_INDEX_DPAD_DOWN = */ BTN_DPAD_DOWN,
+ /* BUTTON_INDEX_DPAD_LEFT = */ BTN_DPAD_LEFT,
+ /* BUTTON_INDEX_DPAD_RIGHT = */ BTN_DPAD_RIGHT,
+ /* BUTTON_INDEX_META = */ BTN_MODE,
+ };
+ const std::array<uint16_t, AXIS_INDEX_COUNT> kStandardAxes = {
+ /* AXIS_INDEX_LEFT_STICK_X = */ ABS_X,
+ /* AXIS_INDEX_LEFT_STICK_Y = */ ABS_Y,
+ /* AXIS_INDEX_RIGHT_STICK_X = */ ABS_RX,
+ /* AXIS_INDEX_RIGHT_STICK_Y = */ ABS_RY,
+ };
+
+ /*
+ * According to https://www.kernel.org/doc/html/latest/input/gamepad.html,
+ * "All gamepads that follow the protocol described here map BTN_GAMEPAD",
+ * so we can use it as a proxy for semantic buttons in general. If it's
+ * enabled, we're probably going to be acting like a standard gamepad
+ */
+ uint32_t numButtons = 0;
+ if (TestBit(keyBits, BTN_GAMEPAD)) {
+ gamepad->isStandardGamepad = true;
+ for (uint8_t button = 0; button < BUTTON_INDEX_COUNT; button++) {
+ gamepad->key_map[kStandardButtons[button]] = button;
+ }
+ numButtons = BUTTON_INDEX_COUNT;
+ }
+
+ // Now, go through the non-semantic buttons and handle them as extras
+ for (uint16_t key = 0; key < KEY_MAX; key++) {
+ // Skip standard buttons
+ if (gamepad->isStandardGamepad &&
+ std::find(kStandardButtons.begin(), kStandardButtons.end(), key) !=
+ kStandardButtons.end()) {
+ continue;
+ }
+
+ if (TestBit(keyBits, key)) {
+ gamepad->key_map[key] = numButtons++;
+ }
+ }
+
+ uint32_t numAxes = 0;
+ if (gamepad->isStandardGamepad) {
+ for (uint8_t i = 0; i < AXIS_INDEX_COUNT; i++) {
+ gamepad->abs_map[kStandardAxes[i]] = i;
+ LoadAbsInfo(fd, gamepad.get(), kStandardAxes[i]);
+ }
+ numAxes = AXIS_INDEX_COUNT;
+
+ // These are not real axis and get remapped to buttons.
+ LoadAbsInfo(fd, gamepad.get(), ABS_HAT0X);
+ LoadAbsInfo(fd, gamepad.get(), ABS_HAT0Y);
+ }
+
+ for (uint16_t i = 0; i < ABS_MAX; ++i) {
+ if (gamepad->isStandardGamepad &&
+ (std::find(kStandardAxes.begin(), kStandardAxes.end(), i) !=
+ kStandardAxes.end() ||
+ i == ABS_HAT0X || i == ABS_HAT0Y)) {
+ continue;
+ }
+
+ if (TestBit(absBits, i)) {
+ if (LoadAbsInfo(fd, gamepad.get(), i)) {
+ gamepad->abs_map[i] = numAxes++;
+ }
+ }
+ }
+
+ if (numAxes == 0) {
+ NS_WARNING("Gamepad with zero axes detected?");
+ }
+ if (numButtons == 0) {
+ NS_WARNING("Gamepad with zero buttons detected?");
+ }
+
+ // NOTE: This almost always true, so we basically never use the remapping
+ // code.
+ if (gamepad->isStandardGamepad) {
+ gamepad->handle =
+ service->AddGamepad(gamepad->idstring, GamepadMappingType::Standard,
+ GamepadHand::_empty, numButtons, numAxes, 0, 0, 0);
+ } else {
+ bool defaultRemapper = false;
+ RefPtr<GamepadRemapper> remapper =
+ GetGamepadRemapper(id.vendor, id.product, defaultRemapper);
+ MOZ_ASSERT(remapper);
+ remapper->SetAxisCount(numAxes);
+ remapper->SetButtonCount(numButtons);
+
+ gamepad->handle = service->AddGamepad(
+ gamepad->idstring, remapper->GetMappingType(), GamepadHand::_empty,
+ remapper->GetButtonCount(), remapper->GetAxisCount(), 0,
+ remapper->GetLightIndicatorCount(), remapper->GetTouchEventCount());
+ gamepad->remapper = remapper.forget();
+ }
+ // TODO: Bug 680289, implement gamepad haptics for Linux.
+ // TODO: Bug 1523355, implement gamepad lighindicator and touch for Linux.
+
+ gamepad->source_id =
+ g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
+ OnGamepadData, gamepad.get());
+ g_io_channel_unref(channel);
+
+ mGamepads.AppendElement(std::move(gamepad));
+}
+
+void LinuxGamepadService::RemoveDevice(struct udev_device* dev) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(dev);
+ if (!devpath) {
+ return;
+ }
+
+ for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+ if (strcmp(mGamepads[i]->devpath, devpath) == 0) {
+ auto gamepad = std::move(mGamepads[i]);
+ mGamepads.RemoveElementAt(i);
+
+ g_source_remove(gamepad->source_id);
+ service->RemoveGamepad(gamepad->handle);
+
+ 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 (IsDeviceGamepad(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::IsDeviceGamepad(struct udev_device* aDev) {
+ if (!mUdev.udev_device_get_property_value(aDev, "ID_INPUT_JOYSTICK")) {
+ return false;
+ }
+
+ const char* devpath = mUdev.udev_device_get_devnode(aDev);
+ if (!devpath) {
+ return false;
+ }
+
+ return strncmp(devpath, kEvdevPath, strlen(kEvdevPath)) == 0;
+}
+
+void LinuxGamepadService::ReadUdevChange() {
+ struct udev_device* dev = mUdev.udev_monitor_receive_device(mMonitor);
+ if (IsDeviceGamepad(dev)) {
+ const char* action = mUdev.udev_device_get_action(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) {
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return TRUE;
+ }
+ auto* gamepad = static_cast<Gamepad*>(data);
+
+ // TODO: remove gamepad?
+ if (condition & (G_IO_ERR | G_IO_HUP)) {
+ return FALSE;
+ }
+
+ while (true) {
+ struct input_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;
+ }
+
+ switch (event.type) {
+ case EV_KEY:
+ if (gamepad->isStandardGamepad) {
+ service->NewButtonEvent(gamepad->handle, gamepad->key_map[event.code],
+ !!event.value);
+ } else {
+ gamepad->remapper->RemapButtonEvent(
+ gamepad->handle, gamepad->key_map[event.code], !!event.value);
+ }
+ break;
+ case EV_ABS: {
+ if (!gamepad->abs_info.count(event.code)) {
+ continue;
+ }
+
+ double scaledValue =
+ ScaleAxis(gamepad->abs_info[event.code], event.value);
+ if (gamepad->isStandardGamepad) {
+ switch (event.code) {
+ case ABS_HAT0X:
+ service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_LEFT,
+ AxisNegativeAsButton(scaledValue));
+ service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_RIGHT,
+ AxisPositiveAsButton(scaledValue));
+ break;
+ case ABS_HAT0Y:
+ service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_UP,
+ AxisNegativeAsButton(scaledValue));
+ service->NewButtonEvent(gamepad->handle, BUTTON_INDEX_DPAD_DOWN,
+ AxisPositiveAsButton(scaledValue));
+ break;
+ default:
+ service->NewAxisMoveEvent(
+ gamepad->handle, gamepad->abs_map[event.code], scaledValue);
+ break;
+ }
+ } else {
+ gamepad->remapper->RemapAxisMoveEvent(
+ gamepad->handle, gamepad->abs_map[event.code], scaledValue);
+ }
+ } break;
+ }
+ }
+
+ return TRUE;
+}
+
+// static
+gboolean LinuxGamepadService::OnUdevMonitor(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ if (condition & (G_IO_ERR | G_IO_HUP)) {
+ return FALSE;
+ }
+
+ gService->ReadUdevChange();
+ return TRUE;
+}
+
+} // namespace
+
+namespace mozilla::dom {
+
+void StartGamepadMonitoring() {
+ if (gService) {
+ return;
+ }
+ gService = new LinuxGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring() {
+ if (!gService) {
+ return;
+ }
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+}
+
+void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
+ const Tainted<uint32_t>& aLightColorIndex,
+ const uint8_t& aRed, const uint8_t& aGreen,
+ const uint8_t& aBlue) {
+ // TODO: Bug 1523355.
+ NS_WARNING("Linux doesn't support gamepad light indicator.");
+}
+
+} // namespace mozilla::dom
diff --git a/dom/gamepad/linux/udev.h b/dom/gamepad/linux/udev.h
new file mode 100644
index 0000000000..4e3f0b7865
--- /dev/null
+++ b/dom/gamepad/linux/udev.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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>
+
+#include "mozilla/ArrayUtils.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(nullptr), udev(nullptr) {
+ // Be careful about ABI compat! 0 -> 1 didn't change any
+ // symbols this code relies on, per:
+ // https://lists.fedoraproject.org/pipermail/devel/2012-June/168227.html
+ const char* lib_names[] = {"libudev.so.0", "libudev.so.1"};
+ // Check whether a library is already loaded so we don't load two
+ // conflicting libs.
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ // If nothing loads the first time through, it means no version of libudev
+ // was already loaded.
+ if (!lib) {
+ for (unsigned i = 0; i < ArrayLength(lib_names); i++) {
+ lib = dlopen(lib_names[i], RTLD_LAZY | RTLD_GLOBAL);
+ if (lib) {
+ break;
+ }
+ }
+ }
+ if (lib && LoadSymbols()) {
+ udev = udev_new();
+ }
+ }
+
+ ~udev_lib() {
+ if (udev) {
+ udev_unref(udev);
+ }
+
+ if (lib) {
+ dlclose(lib);
+ }
+ }
+
+ explicit operator bool() { return udev; }
+
+ private:
+#define DLSYM(s) \
+ do { \
+ (s) = (decltype(s))dlsym(lib, #s); \
+ if (!(s)) return false; \
+ } while (0)
+
+ bool LoadSymbols() {
+ 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_parent_with_subsystem_devtype);
+ DLSYM(udev_device_get_property_value);
+ DLSYM(udev_device_get_action);
+ DLSYM(udev_device_get_sysattr_value);
+ 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);
+
+ return true;
+ }
+
+#undef DLSYM
+
+ 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*);
+ struct udev_device* (*udev_device_get_parent_with_subsystem_devtype)(
+ struct udev_device*, const char*, const char*);
+ const char* (*udev_device_get_property_value)(struct udev_device*,
+ const char*);
+ const char* (*udev_device_get_action)(struct udev_device*);
+ const char* (*udev_device_get_sysattr_value)(struct udev_device*,
+ const char*);
+
+ 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_