mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 604039 - Linux gamepad backend. r=karlt
--HG-- extra : rebase_source : 32d47af727b9a6209d321a8622a2f3ecb7a44d25
This commit is contained in:
parent
fa5904a1e2
commit
838ee76456
@ -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
|
||||
|
@ -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 += \
|
||||
|
364
hal/linux/LinuxGamepad.cpp
Normal file
364
hal/linux/LinuxGamepad.cpp
Normal file
@ -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
|
117
hal/linux/udev.h
Normal file
117
hal/linux/udev.h
Normal file
@ -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_
|
Loading…
Reference in New Issue
Block a user