Robustify Linux gamepad support
This commit is contained in:
parent
70370999c9
commit
ab66108661
|
|
@ -19,6 +19,7 @@
|
|||
#include "gamepadButton.h"
|
||||
#include "keyboardButton.h"
|
||||
#include "mouseButton.h"
|
||||
#include "inputDeviceManager.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
|
|
@ -32,19 +33,6 @@ static InputDevice::ControlAxis axis_map[] = {
|
|||
|
||||
TypeHandle EvdevInputDevice::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: EvdevInputDevice::Constructor
|
||||
// Access: Published
|
||||
// Description: Creates a new device using the Linux joystick
|
||||
// device using the given file descriptor. It will
|
||||
// be closed when this object destructs.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
EvdevInputDevice::
|
||||
EvdevInputDevice(int fd) :
|
||||
_fd(fd) {
|
||||
init_device();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: EvdevInputDevice::Constructor
|
||||
// Access: Published
|
||||
|
|
@ -52,15 +40,18 @@ EvdevInputDevice(int fd) :
|
|||
// device using the given device filename.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
EvdevInputDevice::
|
||||
EvdevInputDevice(const string &fn) {
|
||||
_fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK);
|
||||
EvdevInputDevice(int index) : _index(index) {
|
||||
char path[64];
|
||||
sprintf(path, "/dev/input/event%d", index);
|
||||
|
||||
_fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
|
||||
if (_fd >= 0) {
|
||||
init_device();
|
||||
} else {
|
||||
_is_connected = false;
|
||||
device_cat.error()
|
||||
<< "Opening raw input device: " << strerror(errno) << " " << fn << "\n";
|
||||
<< "Opening raw input device: " << strerror(errno) << " " << path << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +68,18 @@ EvdevInputDevice::
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: EvdevInputDevice::check_events
|
||||
// Access: Public
|
||||
// Description: Returns true if there are pending events.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool EvdevInputDevice::
|
||||
check_events() const {
|
||||
unsigned int avail = 0;
|
||||
ioctl(_fd, FIONREAD, &avail);
|
||||
return (avail != 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: EvdevInputDevice::do_poll
|
||||
// Access: Private, Virtual
|
||||
|
|
@ -88,8 +91,15 @@ EvdevInputDevice::
|
|||
////////////////////////////////////////////////////////////////////
|
||||
void EvdevInputDevice::
|
||||
do_poll() {
|
||||
if (_fd != -1) {
|
||||
if (_fd != -1 && process_events()) {
|
||||
while (process_events()) {}
|
||||
|
||||
// If we got events, we are obviously connected. Mark us so.
|
||||
if (!_is_connected) {
|
||||
_is_connected = true;
|
||||
InputDeviceManager *mgr = InputDeviceManager::get_global_ptr();
|
||||
mgr->add_device(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,10 +114,8 @@ init_device() {
|
|||
|
||||
uint8_t evtypes[(EV_CNT + 7) >> 3];
|
||||
memset(evtypes, 0, sizeof(evtypes));
|
||||
char name[256];
|
||||
char uniq[256];
|
||||
char name[128];
|
||||
if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 ||
|
||||
ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 ||
|
||||
ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) {
|
||||
close(_fd);
|
||||
_fd = -1;
|
||||
|
|
@ -116,29 +124,59 @@ init_device() {
|
|||
return false;
|
||||
}
|
||||
|
||||
for (char *p=name; *p; p++) {
|
||||
if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) {
|
||||
*p = '_';
|
||||
}
|
||||
}
|
||||
for (char *p=uniq; *p; p++) {
|
||||
if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) {
|
||||
*p = '_';
|
||||
}
|
||||
_name.assign(name);
|
||||
|
||||
struct input_id id;
|
||||
if (ioctl(_fd, EVIOCGID, &id) >= 0) {
|
||||
_vendor_id = id.vendor;
|
||||
_product_id = id.product;
|
||||
}
|
||||
|
||||
_name = ((string)name) + "." + uniq;
|
||||
|
||||
bool all_values_zero = true;
|
||||
if (test_bit(EV_ABS, evtypes)) {
|
||||
// Check which axes are on the device.
|
||||
uint8_t axes[(ABS_CNT + 7) >> 3];
|
||||
memset(axes, 0, sizeof(axes));
|
||||
|
||||
AxisRange range;
|
||||
range._scale = 1.0;
|
||||
range._bias = 0.0;
|
||||
_axis_ranges.resize(ABS_CNT, range);
|
||||
|
||||
int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
|
||||
for (int i = 0; i < num_bits; ++i) {
|
||||
if (test_bit(i, axes)) {
|
||||
if (i >= 0 && i < 6) {
|
||||
set_control_map(i, axis_map[i]);
|
||||
|
||||
// Check the initial value and ranges.
|
||||
struct input_absinfo absinfo;
|
||||
if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) {
|
||||
double factor, bias;
|
||||
if (absinfo.minimum < 0) {
|
||||
// Centered, eg. for sticks.
|
||||
factor = 2.0 / (absinfo.maximum - absinfo.minimum);
|
||||
bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum);
|
||||
} else {
|
||||
// 0-based, eg. for triggers.
|
||||
factor = 1.0 / absinfo.maximum;
|
||||
bias = 0.0;
|
||||
}
|
||||
|
||||
// Flip Y axis to match Windows implementation.
|
||||
if (i == ABS_Y || i == ABS_RY) {
|
||||
factor = -factor;
|
||||
bias = -bias;
|
||||
}
|
||||
|
||||
_axis_ranges[i]._scale = factor;
|
||||
_axis_ranges[i]._bias = bias;
|
||||
_controls[i]._state = fma(absinfo.value, factor, bias);
|
||||
|
||||
if (absinfo.value != 0) {
|
||||
all_values_zero = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,14 +186,40 @@ init_device() {
|
|||
// Check which buttons are on the device.
|
||||
uint8_t keys[(KEY_CNT + 7) >> 3];
|
||||
memset(keys, 0, sizeof(keys));
|
||||
ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys);
|
||||
|
||||
// Also check whether the buttons are currently pressed.
|
||||
uint8_t states[(KEY_CNT + 7) >> 3];
|
||||
memset(states, 0, sizeof(states));
|
||||
ioctl(_fd, EVIOCGKEY(sizeof(states)), states);
|
||||
|
||||
int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3;
|
||||
int bi = 0;
|
||||
for (int i = 0; i < num_bits; ++i) {
|
||||
for (int i = 0; i < KEY_CNT; ++i) {
|
||||
if (test_bit(i, keys)) {
|
||||
set_button_map(bi++, map_button(i));
|
||||
set_button_map(bi, map_button(i));
|
||||
if (test_bit(i, states)) {
|
||||
_buttons[bi]._state = S_down;
|
||||
all_values_zero = false;
|
||||
} else {
|
||||
_buttons[bi]._state = S_up;
|
||||
}
|
||||
++bi;
|
||||
}
|
||||
}
|
||||
|
||||
// Check device type.
|
||||
if (test_bit(BTN_GAMEPAD, keys)) {
|
||||
_device_class = DC_gamepad;
|
||||
|
||||
} else if (test_bit(BTN_JOYSTICK, keys)) {
|
||||
_device_class = DC_flight_stick;
|
||||
|
||||
} else if (test_bit(BTN_MOUSE, keys)) {
|
||||
_device_class = DC_mouse;
|
||||
|
||||
} else if (test_bit(BTN_WHEEL, keys)) {
|
||||
_device_class = DC_steering_wheel;
|
||||
}
|
||||
}
|
||||
|
||||
if (test_bit(EV_REL, evtypes)) {
|
||||
|
|
@ -166,14 +230,52 @@ init_device() {
|
|||
_flags |= IDF_has_vibration;
|
||||
}
|
||||
|
||||
_is_connected = true;
|
||||
char path[64];
|
||||
char buffer[256];
|
||||
sprintf(path, "/sys/class/input/event%d/device/device/../product", _index);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
if (buffer[0] != 0) {
|
||||
_name.assign(buffer);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
_manufacturer.assign(buffer);
|
||||
fclose(f);
|
||||
}
|
||||
sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
_serial_number.assign(buffer);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel
|
||||
// driver always reports 4 connected gamepads, regardless of the number
|
||||
// of gamepads actually present. This hack partially remedies this.
|
||||
if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) {
|
||||
_is_connected = false;
|
||||
} else {
|
||||
_is_connected = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: EvdevInputDevice::process_events
|
||||
// Access: Private
|
||||
// Description: Reads a number of events from the device.
|
||||
// Description: Reads a number of events from the device. Returns
|
||||
// true if events were read, meaning this function
|
||||
// should keep being called until it returns false.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool EvdevInputDevice::
|
||||
process_events() {
|
||||
|
|
@ -186,10 +288,12 @@ process_events() {
|
|||
// No data available for now.
|
||||
|
||||
} else if (errno == ENODEV || errno == EINVAL) {
|
||||
// The device ceased to exist, so we better close it.
|
||||
// The device ceased to exist, so we better close it. No need
|
||||
// to worry about removing it from the InputDeviceManager, as it
|
||||
// will get an inotify event sooner or later about this.
|
||||
close(_fd);
|
||||
_fd = -1;
|
||||
_is_connected = false;
|
||||
//_is_connected = false;
|
||||
errno = 0;
|
||||
|
||||
} else {
|
||||
|
|
@ -210,23 +314,31 @@ process_events() {
|
|||
double time = ClockObject::get_global_clock()->get_frame_time();
|
||||
ButtonHandle button;
|
||||
|
||||
// It seems that some devices send a single EV_SYN event when being
|
||||
// unplugged. Boo. Ignore it.
|
||||
if (n_read == 1 && events[0].code == EV_SYN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_read; ++i) {
|
||||
int code = events[i].code;
|
||||
|
||||
switch (events[i].type) {
|
||||
case EV_SYN:
|
||||
break;
|
||||
|
||||
case EV_REL:
|
||||
if (events[i].code == REL_X) x += events[i].value;
|
||||
if (events[i].code == REL_Y) y += events[i].value;
|
||||
if (code == REL_X) x += events[i].value;
|
||||
if (code == REL_Y) y += events[i].value;
|
||||
have_pointer = true;
|
||||
break;
|
||||
|
||||
case EV_ABS:
|
||||
set_control_state(events[i].code, events[i].value / 32767.0);
|
||||
set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias));
|
||||
break;
|
||||
|
||||
case EV_KEY:
|
||||
button = map_button(events[i].code);
|
||||
button = map_button(code);
|
||||
_button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time));
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,12 @@
|
|||
// /dev/input/event# API to read data from a raw mouse.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice {
|
||||
PUBLISHED:
|
||||
EvdevInputDevice(int fd);
|
||||
EvdevInputDevice(const string &device);
|
||||
public:
|
||||
EvdevInputDevice(int index);
|
||||
virtual ~EvdevInputDevice();
|
||||
|
||||
bool check_events() const;
|
||||
|
||||
private:
|
||||
virtual void do_poll();
|
||||
|
||||
|
|
@ -37,8 +38,15 @@ private:
|
|||
bool process_events();
|
||||
|
||||
private:
|
||||
int _index;
|
||||
int _fd;
|
||||
|
||||
struct AxisRange {
|
||||
double _scale;
|
||||
double _bias;
|
||||
};
|
||||
pvector<AxisRange> _axis_ranges;
|
||||
|
||||
public:
|
||||
static ButtonHandle map_button(int code);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ InputDevice() :
|
|||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDevice::get_name
|
||||
// Access: Public
|
||||
// Description:
|
||||
// Description: Returns a human-readable name for the device. May
|
||||
// not necessarily be unique.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE string InputDevice::
|
||||
get_name() const {
|
||||
|
|
@ -43,6 +44,42 @@ get_name() const {
|
|||
return _name;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDevice::get_manufacturer
|
||||
// Access: Public
|
||||
// Description: Returns a string containing the manufacturer of
|
||||
// the device, if this information is known.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE string InputDevice::
|
||||
get_manufacturer() const {
|
||||
LightMutexHolder holder(_lock);
|
||||
return _manufacturer;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDevice::get_vendor_id
|
||||
// Access: Public
|
||||
// Description: Returns a string containing the USB vendor ID of
|
||||
// the device, if this information is known.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE unsigned short InputDevice::
|
||||
get_vendor_id() const {
|
||||
LightMutexHolder holder(_lock);
|
||||
return _vendor_id;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDevice::get_product_id
|
||||
// Access: Public
|
||||
// Description: Returns a string containing the USB product ID of
|
||||
// the device, if this information is known.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE unsigned short InputDevice::
|
||||
get_product_id() const {
|
||||
LightMutexHolder holder(_lock);
|
||||
return _product_id;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDevice::is_connected
|
||||
// Access: Public
|
||||
|
|
|
|||
|
|
@ -111,12 +111,24 @@ PUBLISHED:
|
|||
};
|
||||
|
||||
INLINE string get_name() const;
|
||||
INLINE string get_manufacturer() const;
|
||||
INLINE unsigned short get_vendor_id() const;
|
||||
INLINE unsigned short get_product_id() const;
|
||||
INLINE bool is_connected() const;
|
||||
INLINE DeviceClass get_device_class() const;
|
||||
|
||||
// The human-readable name of this input device.
|
||||
MAKE_PROPERTY(name, get_name);
|
||||
|
||||
// The device's manufacturer, or the empty string if not known.
|
||||
MAKE_PROPERTY(manufacturer, get_manufacturer);
|
||||
|
||||
// USB vendor ID of the device, or 0 if not known.
|
||||
MAKE_PROPERTY(vendor_id, get_vendor_id);
|
||||
|
||||
// USB product ID of the device, or 0 if not known.
|
||||
MAKE_PROPERTY(product_id, get_product_id);
|
||||
|
||||
// This is false if we know that the device is not currently connected.
|
||||
// May report false positives if we can't know this with certainty.
|
||||
MAKE_PROPERTY(connected, is_connected);
|
||||
|
|
@ -212,8 +224,8 @@ protected:
|
|||
DeviceClass _device_class;
|
||||
int _flags;
|
||||
int _event_sequence;
|
||||
short _vendor_id;
|
||||
short _product_id;
|
||||
unsigned short _vendor_id;
|
||||
unsigned short _product_id;
|
||||
bool _is_connected;
|
||||
bool _enable_pointer_events;
|
||||
PointerData _pointer_data;
|
||||
|
|
|
|||
|
|
@ -39,33 +39,6 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL;
|
|||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
InputDeviceManager::
|
||||
InputDeviceManager() : _inotify_fd(-1) {
|
||||
|
||||
// Scan /dev/input for a list of input devices.
|
||||
DIR *dir = opendir("/dev/input");
|
||||
if (dir) {
|
||||
dirent *entry = readdir(dir);
|
||||
while (entry != NULL) {
|
||||
if (entry->d_type == DT_CHR) {
|
||||
string name(entry->d_name);
|
||||
if (!consider_add_linux_device(name)) {
|
||||
// We can't access it. That's pretty normal for most devices.
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Ignoring input device /dev/input/" << name << ": "
|
||||
<< strerror(errno) << "\n";
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
}
|
||||
entry = readdir(dir);
|
||||
}
|
||||
closedir(dir);
|
||||
} else {
|
||||
device_cat.error()
|
||||
<< "Error opening directory /dev/input: " << strerror(errno) << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Use inotify to watch /dev/input for hotplugging of devices.
|
||||
_inotify_fd = inotify_init1(O_NONBLOCK);
|
||||
|
||||
|
|
@ -77,6 +50,35 @@ InputDeviceManager() : _inotify_fd(-1) {
|
|||
device_cat.error()
|
||||
<< "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n";
|
||||
}
|
||||
|
||||
// Scan /dev/input for a list of input devices.
|
||||
DIR *dir = opendir("/dev/input");
|
||||
if (dir) {
|
||||
vector_int indices;
|
||||
dirent *entry = readdir(dir);
|
||||
while (entry != NULL) {
|
||||
int index = -1;
|
||||
if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) {
|
||||
indices.push_back(index);
|
||||
}
|
||||
entry = readdir(dir);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
// We'll want to sort the devices by index, since the order may be
|
||||
// meaningful (eg. for the Xbox wireless receiver).
|
||||
std::sort(indices.begin(), indices.end());
|
||||
_evdev_devices.resize(indices.back() + 1, NULL);
|
||||
|
||||
vector_int::const_iterator it;
|
||||
for (it = indices.begin(); it != indices.end(); ++it) {
|
||||
consider_add_evdev_device(*it);
|
||||
}
|
||||
} else {
|
||||
device_cat.error()
|
||||
<< "Error opening directory /dev/input: " << strerror(errno) << "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
InputDeviceManager::
|
||||
|
|
@ -129,41 +131,124 @@ InputDeviceManager::
|
|||
|
||||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDeviceManager::consider_add_linux_device
|
||||
// Function: InputDeviceManager::consider_add_evdev_device
|
||||
// Access: Private
|
||||
// Description: Checks whether the given device is accessible, and
|
||||
// if so, adds it. Returns false on error.
|
||||
// Description: Checks whether the event device with the given index
|
||||
// is accessible, and if so, adds it. Returns the
|
||||
// device if it was newly connected.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceManager::
|
||||
consider_add_linux_device(const string &name) {
|
||||
// Get the full path name first.
|
||||
string path = "/dev/input/";
|
||||
path += name;
|
||||
|
||||
if (access(path.c_str(), R_OK) < 0) {
|
||||
return false;
|
||||
InputDevice *InputDeviceManager::
|
||||
consider_add_evdev_device(int ev_index) {
|
||||
if (ev_index < _evdev_devices.size()) {
|
||||
if (_evdev_devices[ev_index] != NULL) {
|
||||
// We already have this device. FIXME: probe it and add it to the
|
||||
// list of connected devices?
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
// Make room to store this index.
|
||||
_evdev_devices.resize(ev_index + 1, NULL);
|
||||
}
|
||||
|
||||
if (_devices_by_path.count(name)) {
|
||||
// We already have this device.
|
||||
return true;
|
||||
}
|
||||
// Check if we can directly read the event device.
|
||||
char path[64];
|
||||
sprintf(path, "/dev/input/event%d", ev_index);
|
||||
|
||||
// Check if it's a joystick or game controller device.
|
||||
if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) {
|
||||
PT(InputDevice) device = new LinuxJoystickDevice(path);
|
||||
if (device_cat.is_info()) {
|
||||
device_cat.info()
|
||||
<< "Discovered input device " << *device << "\n";
|
||||
if (access(path, R_OK) == 0) {
|
||||
PT(InputDevice) device = new EvdevInputDevice(ev_index);
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Discovered evdev input device " << *device << "\n";
|
||||
}
|
||||
|
||||
_devices_by_path[name] = device;
|
||||
_connected_devices.add_device(MOVE(device));
|
||||
return true;
|
||||
_evdev_devices[ev_index] = device;
|
||||
|
||||
if (device->is_connected()) {
|
||||
_connected_devices.add_device(MOVE(device));
|
||||
} else {
|
||||
// Wait for activity on the device before it is considered connected.
|
||||
_inactive_devices.add_device(MOVE(device));
|
||||
}
|
||||
return _evdev_devices[ev_index];
|
||||
}
|
||||
|
||||
return true;
|
||||
// Nope. The permissions haven't been configured to allow it.
|
||||
// Check if this corresponds to a /dev/input/jsX interface, which has
|
||||
// a better chance of having read permissions set, but doesn't export
|
||||
// all of the features (notably, force feedback).
|
||||
|
||||
// We do this by checking for a js# directory inside the sysfs
|
||||
// device directory.
|
||||
sprintf(path, "/sys/class/input/event%d/device", ev_index);
|
||||
|
||||
DIR *dir = opendir(path);
|
||||
if (dir == NULL) {
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Error opening directory " << path << ": " << strerror(errno) << "\n";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dirent *entry = readdir(dir);
|
||||
while (entry != NULL) {
|
||||
int js_index = -1;
|
||||
if (sscanf(entry->d_name, "js%d", &js_index) == 1) {
|
||||
// Yes, we fonud a corresponding js device. Try adding it.
|
||||
closedir(dir);
|
||||
|
||||
InputDevice *device = consider_add_js_device(js_index);
|
||||
if (device != NULL && device_cat.is_warning()) {
|
||||
// This seemed to work. Display a warning to the user indicating
|
||||
// that they might want to configure udev properly.
|
||||
device_cat.warning()
|
||||
<< "Some features of " << device->get_device_class()
|
||||
<< " device " << device->get_name() << " are not available due"
|
||||
" to lack of read permissions on /dev/input/event" << ev_index
|
||||
<< ".\n";
|
||||
}
|
||||
_evdev_devices[ev_index] = device;
|
||||
return device;
|
||||
}
|
||||
entry = readdir(dir);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: InputDeviceManager::consider_add_evdev_device
|
||||
// Access: Private
|
||||
// Description: Checks whether the joystick device with the given
|
||||
// index is accessible, and if so, adds it. Returns
|
||||
// the device if it was newly connected.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
InputDevice *InputDeviceManager::
|
||||
consider_add_js_device(int js_index) {
|
||||
char path[64];
|
||||
sprintf(path, "/dev/input/js%d", js_index);
|
||||
|
||||
if (access(path, R_OK) == 0) {
|
||||
PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index);
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Discovered joydev input device " << *device << "\n";
|
||||
}
|
||||
InputDevice *device_p = device.p();
|
||||
|
||||
if (device->is_connected()) {
|
||||
_connected_devices.add_device(MOVE(device));
|
||||
} else {
|
||||
// Wait for activity on the device before it is considered connected.
|
||||
_inactive_devices.add_device(MOVE(device));
|
||||
}
|
||||
return device_p;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -199,6 +284,12 @@ add_device(InputDevice *device) {
|
|||
{
|
||||
LightMutexHolder holder(_lock);
|
||||
_connected_devices.add_device(device);
|
||||
|
||||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
// If we had added it pending activity on the device, remove it
|
||||
// from the list of inactive devices.
|
||||
_inactive_devices.remove_device(device);
|
||||
#endif
|
||||
}
|
||||
throw_event("connect-device", device);
|
||||
}
|
||||
|
|
@ -229,9 +320,23 @@ remove_device(InputDevice *device) {
|
|||
void InputDeviceManager::
|
||||
update() {
|
||||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
// Check for any devices that may be disconnected and need to be probed
|
||||
// in order to see whether they have been reconnected.
|
||||
InputDeviceSet inactive_devices;
|
||||
{
|
||||
LightMutexHolder holder(_lock);
|
||||
inactive_devices = _inactive_devices;
|
||||
}
|
||||
for (size_t i = 0; i < inactive_devices.size(); ++i) {
|
||||
InputDevice *device = inactive_devices[i];
|
||||
if (device != NULL && !device->is_connected()) {
|
||||
device->poll();
|
||||
}
|
||||
}
|
||||
|
||||
// We use inotify to tell us whether a device was added, removed,
|
||||
// or has changed permissions to allow us to access it.
|
||||
unsigned int avail;
|
||||
unsigned int avail = 0;
|
||||
ioctl(_inotify_fd, FIONREAD, &avail);
|
||||
if (avail == 0) {
|
||||
return;
|
||||
|
|
@ -261,34 +366,39 @@ update() {
|
|||
|
||||
if (event->mask & IN_DELETE) {
|
||||
// The device was deleted. If we have it, remove it.
|
||||
DevicesByPath::iterator it = _devices_by_path.find(name);
|
||||
if (it != _devices_by_path.end()) {
|
||||
PT(InputDevice) device = it->second;
|
||||
device->set_connected(false);
|
||||
|
||||
_devices_by_path.erase(it);
|
||||
_connected_devices.remove_device(device);
|
||||
int index = -1;
|
||||
if (sscanf(event->name, "event%d", &index) == 1) {
|
||||
// Check if we have this evdev device. If so, disconnect it.
|
||||
if (index < _evdev_devices.size()) {
|
||||
PT(InputDevice) device = _evdev_devices[index];
|
||||
if (device != NULL) {
|
||||
_evdev_devices[index] = NULL;
|
||||
_inactive_devices.remove_device(device);
|
||||
if (_connected_devices.remove_device(device)) {
|
||||
throw_event("disconnect-device", device.p());
|
||||
}
|
||||
|
||||
if (device_cat.is_info()) {
|
||||
device_cat.info()
|
||||
<< "Removed input device " << *device << "\n";
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Removed input device " << *device << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
throw_event("disconnect-device", device.p());
|
||||
}
|
||||
|
||||
} else if (event->mask & (IN_CREATE | IN_ATTRIB)) {
|
||||
// The device was created, or it was chmodded to be accessible.
|
||||
DevicesByPath::iterator it = _devices_by_path.find(name);
|
||||
if (it == _devices_by_path.end()) {
|
||||
// We don't know about this device yet.
|
||||
if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) {
|
||||
if (device_cat.is_debug()) {
|
||||
device_cat.debug()
|
||||
<< "Ignoring input device /dev/input/" << name << ": "
|
||||
<< strerror(errno) << "\n";
|
||||
}
|
||||
// The device was created, or it was chmodded to be accessible. We
|
||||
// need to check for the latter since it seems that the device may
|
||||
// get the IN_CREATE event before the driver gets the permissions
|
||||
// set properly.
|
||||
|
||||
int index = -1;
|
||||
if (sscanf(event->name, "event%d", &index) == 1) {
|
||||
InputDevice *device = consider_add_evdev_device(index);
|
||||
if (device != NULL && device->is_connected()) {
|
||||
throw_event("connect-device", device);
|
||||
}
|
||||
errno = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,6 +411,8 @@ update() {
|
|||
// check if it's connected every so often. Perhaps we can switch to
|
||||
// using RegisterDeviceNotification in the future.
|
||||
double time_now = ClockObject::get_global_clock()->get_real_time();
|
||||
LightMutexHolder holder(_update_lock);
|
||||
|
||||
if (time_now - _last_detection > xinput_detection_delay.get_value()) {
|
||||
// I've heard this can be quite slow if no device is detected. We
|
||||
// should probably move it to a thread.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ private:
|
|||
~InputDeviceManager();
|
||||
|
||||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
bool consider_add_linux_device(const string &name);
|
||||
InputDevice *consider_add_evdev_device(int index);
|
||||
InputDevice *consider_add_js_device(int index);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
|
@ -59,12 +60,13 @@ private:
|
|||
#ifdef PHAVE_LINUX_INPUT_H
|
||||
int _inotify_fd;
|
||||
|
||||
typedef pmap<string, InputDevice*> DevicesByPath;
|
||||
DevicesByPath _devices_by_path;
|
||||
pvector<InputDevice *> _evdev_devices;
|
||||
InputDeviceSet _inactive_devices;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// There are always exactly four of these in existence.
|
||||
LightMutex _update_lock;
|
||||
XInputDevice _xinput_device0;
|
||||
XInputDevice _xinput_device1;
|
||||
XInputDevice _xinput_device2;
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ TypeHandle LinuxJoystickDevice::_type_handle;
|
|||
// device using the given device filename.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
LinuxJoystickDevice::
|
||||
LinuxJoystickDevice(const string &device) :
|
||||
LinuxJoystickDevice(int index) :
|
||||
_fd(-1),
|
||||
_device(device)
|
||||
_index(index)
|
||||
{
|
||||
LightMutexHolder holder(_lock);
|
||||
if (!open_device()) {
|
||||
device_cat.error()
|
||||
<< "Could not open joystick device " << _device
|
||||
<< "Could not open joystick device /dev/input/js" << index
|
||||
<< ": " << strerror(errno) << "\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,18 @@ LinuxJoystickDevice::
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: LinuxJoystickDevice::check_events
|
||||
// Access: Public
|
||||
// Description: Returns true if there are pending events.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool LinuxJoystickDevice::
|
||||
check_events() const {
|
||||
unsigned int avail = 0;
|
||||
ioctl(_fd, FIONREAD, &avail);
|
||||
return (avail != 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: LinuxJoystickDevice::do_poll
|
||||
// Access: Private, Virtual
|
||||
|
|
@ -66,10 +78,15 @@ LinuxJoystickDevice::
|
|||
////////////////////////////////////////////////////////////////////
|
||||
void LinuxJoystickDevice::
|
||||
do_poll() {
|
||||
if (_fd != -1) {
|
||||
if (_fd != -1 && process_events()) {
|
||||
while (process_events()) {}
|
||||
} else {
|
||||
open_device();
|
||||
|
||||
// If we got events, we are obviously connected. Mark us so.
|
||||
if (!_is_connected) {
|
||||
_is_connected = true;
|
||||
InputDeviceManager *mgr = InputDeviceManager::get_global_ptr();
|
||||
mgr->add_device(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,14 +101,18 @@ bool LinuxJoystickDevice::
|
|||
open_device() {
|
||||
nassertr(_lock.debug_is_locked(), false);
|
||||
|
||||
_fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK);
|
||||
char path[64];
|
||||
sprintf(path, "/dev/input/js%d", _index);
|
||||
|
||||
_fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
|
||||
if (_fd == -1) {
|
||||
_is_connected = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the name from the device.
|
||||
// Read the name from the device. We'll later try to use sysfs to read
|
||||
// the proper product name from the device, but this is a good fallback.
|
||||
char name[128];
|
||||
name[0] = 0;
|
||||
ioctl(_fd, JSIOCGNAME(sizeof(name)), name);
|
||||
|
|
@ -249,16 +270,76 @@ open_device() {
|
|||
}
|
||||
}
|
||||
|
||||
// Get additional information from sysfs.
|
||||
sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (f) {
|
||||
fscanf(f, "%hx", &_vendor_id);
|
||||
fclose(f);
|
||||
}
|
||||
sprintf(path, "/sys/class/input/js%d/device/id/product", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fscanf(f, "%hx", &_product_id);
|
||||
fclose(f);
|
||||
}
|
||||
char buffer[256];
|
||||
sprintf(path, "/sys/class/input/js%d/device/device/../product", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
if (buffer[0] != 0) {
|
||||
_name.assign(buffer);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
_manufacturer.assign(buffer);
|
||||
fclose(f);
|
||||
}
|
||||
sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index);
|
||||
f = fopen(path, "r");
|
||||
if (f) {
|
||||
fgets(buffer, sizeof(buffer), f);
|
||||
buffer[strcspn(buffer, "\r\n")] = 0;
|
||||
_serial_number.assign(buffer);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// Read the init events.
|
||||
_is_connected = true;
|
||||
while (process_events()) {};
|
||||
|
||||
// Special case handling for the wireless Xbox receiver - the Linux
|
||||
// joystick API doesn't seem to have a way to report whether the device
|
||||
// is actually turned on. The best we can do is check whether the axes
|
||||
// are all 0, which indicates that the driver hasn't received any data for
|
||||
// this gamepad yet (which means it hasn't been plugged in for this session)
|
||||
if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
|
||||
for (int i = 0; i < _controls.size(); ++i) {
|
||||
if (_controls[i]._state != 0.0) {
|
||||
_is_connected = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_is_connected = false;
|
||||
} else {
|
||||
_is_connected = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: LinuxJoystickDevice::process_events
|
||||
// Access: Private
|
||||
// Description: Reads a number of events from the joystick.
|
||||
// Description: Reads a number of events from the device. Returns
|
||||
// true if events were read, meaning this function
|
||||
// should keep being called until it returns false.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool LinuxJoystickDevice::
|
||||
process_events() {
|
||||
|
|
@ -271,10 +352,12 @@ process_events() {
|
|||
// No data available for now.
|
||||
|
||||
} else if (errno == ENODEV) {
|
||||
// The device ceased to exist, so we better close it.
|
||||
// The device ceased to exist, so we better close it. No need
|
||||
// to worry about removing it from the InputDeviceManager, as it
|
||||
// will get an inotify event sooner or later about this.
|
||||
close(_fd);
|
||||
_fd = -1;
|
||||
_is_connected = false;
|
||||
//_is_connected = false;
|
||||
errno = 0;
|
||||
|
||||
} else {
|
||||
|
|
@ -301,6 +384,8 @@ process_events() {
|
|||
if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) {
|
||||
// We'd like to use 0.0 to indicate the resting position.
|
||||
set_control_state(index, (events[i].value + 32767) / 65534.0);
|
||||
} else if (axis == C_left_y || axis == C_right_y || axis == C_y) {
|
||||
set_control_state(index, events[i].value / -32767.0);
|
||||
} else {
|
||||
set_control_state(index, events[i].value / 32767.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@
|
|||
// /dev/input/js# API to read data from a game controller.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice {
|
||||
PUBLISHED:
|
||||
LinuxJoystickDevice(const string &device);
|
||||
public:
|
||||
LinuxJoystickDevice(int index);
|
||||
virtual ~LinuxJoystickDevice();
|
||||
|
||||
bool check_events() const;
|
||||
|
||||
private:
|
||||
virtual void do_poll();
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ private:
|
|||
|
||||
private:
|
||||
int _fd;
|
||||
string _device;
|
||||
int _index;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue