Add gamepad rumble support on Linux

This commit is contained in:
rdb 2015-12-19 19:32:52 +01:00
parent ab66108661
commit 687e080bdd
6 changed files with 129 additions and 17 deletions

View File

@ -40,11 +40,25 @@ TypeHandle EvdevInputDevice::_type_handle;
// device using the given device filename.
////////////////////////////////////////////////////////////////////
EvdevInputDevice::
EvdevInputDevice(int index) : _index(index) {
EvdevInputDevice(int index) :
_index(index),
_fd(-1),
_can_write(false),
_ff_id(-1),
_ff_playing(false),
_ff_strong(-1),
_ff_weak(-1) {
char path[64];
sprintf(path, "/dev/input/event%d", index);
_fd = open(path, O_RDONLY | O_NONBLOCK);
_fd = open(path, O_RDWR | O_NONBLOCK);
if (_fd >= 0) {
_can_write = true;
} else {
// On failure, open device as read-only.
_fd = open(path, O_RDONLY | O_NONBLOCK);
}
if (_fd >= 0) {
init_device();
@ -63,21 +77,74 @@ EvdevInputDevice(int index) : _index(index) {
EvdevInputDevice::
~EvdevInputDevice() {
if (_fd != -1) {
if (_ff_id != -1) {
// Remove force-feedback effect.
do_set_vibration(0, 0);
ioctl(_fd, EVIOCRMFF, _ff_id);
_ff_id = -1;
}
close(_fd);
_fd = -1;
}
}
////////////////////////////////////////////////////////////////////
// Function: EvdevInputDevice::check_events
// Access: Public
// Description: Returns true if there are pending events.
// Function: EvdevInputDevice::do_set_vibration
// Access: Private, Virtual
// Description: Sets the vibration strength. The first argument
// controls a low-frequency motor, if present, and
// the latter controls a high-frequency motor. The
// values are within the 0-1 range.
////////////////////////////////////////////////////////////////////
bool EvdevInputDevice::
check_events() const {
unsigned int avail = 0;
ioctl(_fd, FIONREAD, &avail);
return (avail != 0);
void EvdevInputDevice::
do_set_vibration(double strong, double weak) {
if (_fd == -1 || !_can_write) {
return;
}
int strong_level = strong * 0xffff;
int weak_level = weak * 0xffff;
if (strong_level == _ff_strong && weak_level == _ff_weak) {
// No change.
return;
}
// Upload the new effect parameters. Do this even if we are about
// to stop the effect, because some drivers don't respond to simply
// stopping the effect.
struct ff_effect effect;
effect.type = FF_RUMBLE;
effect.id = _ff_id;
effect.direction = 0;
effect.trigger.button = 0;
effect.trigger.interval = 0;
effect.replay.length = 0;
effect.replay.delay = 0;
effect.u.rumble.strong_magnitude = strong_level;
effect.u.rumble.weak_magnitude = weak_level;
if (ioctl(_fd, EVIOCSFF, &effect) < 0) {
return;
} else {
_ff_id = effect.id;
_ff_strong = strong_level;
_ff_weak = weak_level;
}
if (!_ff_playing) {
// Start the effect. We could pass 0 as value to stop the effect
// when a level of 0 is requested, but my driver seems to ignore it.
_ff_playing = true;
struct input_event play;
play.type = EV_FF;
play.code = _ff_id;
play.value = 1;
write(_fd, &play, sizeof(play));
}
}
////////////////////////////////////////////////////////////////////
@ -227,7 +294,20 @@ init_device() {
}
if (test_bit(EV_FF, evtypes)) {
_flags |= IDF_has_vibration;
uint8_t effects[(FF_CNT + 7) >> 3];
memset(effects, 0, sizeof(effects));
ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects);
if (test_bit(FF_RUMBLE, effects)) {
if (_can_write) {
_flags |= IDF_has_vibration;
} else {
// Let the user know what he's missing out on.
device_cat.warning()
<< "/dev/input/event" << _index << " is not writable, vibration "
<< "effects will be unavailable.\n";
}
}
}
char path[64];

View File

@ -29,9 +29,8 @@ public:
EvdevInputDevice(int index);
virtual ~EvdevInputDevice();
bool check_events() const;
private:
virtual void do_set_vibration(double strong, double weak);
virtual void do_poll();
bool init_device();
@ -40,6 +39,11 @@ private:
private:
int _index;
int _fd;
bool _can_write;
int _ff_id;
bool _ff_playing;
int _ff_strong;
int _ff_weak;
struct AxisRange {
double _scale;

View File

@ -393,6 +393,21 @@ is_control_known(int index) const {
}
}
////////////////////////////////////////////////////////////////////
// Function: InputDevice::set_vibration
// Access: Public
// Description: Sets the strength of the vibration effect, if
// supported. The values are clamped to 0-1 range.
// The first value controls the low-frequency rumble
// motor, whereas the second controls the high-frequency
// motor, if present.
////////////////////////////////////////////////////////////////////
INLINE void InputDevice::
set_vibration(double strong, double weak) {
LightMutexHolder holder(_lock);
do_set_vibration(max(min(strong, 1.0), 0.0), max(min(weak, 1.0), 0.0));
}
////////////////////////////////////////////////////////////////////
// Function: InputDevice::enable_pointer_events
// Access: Public

View File

@ -417,6 +417,18 @@ write_controls(ostream &out, int indent_level) const {
}
}
////////////////////////////////////////////////////////////////////
// Function: InputDevice::do_set_vibration
// Access: Private, Virtual
// Description: Sets the vibration strength. The first argument
// controls a low-frequency motor, if present, and
// the latter controls a high-frequency motor. The
// values are within the 0-1 range.
////////////////////////////////////////////////////////////////////
void InputDevice::
do_set_vibration(double strong, double weak) {
}
////////////////////////////////////////////////////////////////////
// Function: InputDevice::do_poll
// Access: Protected, Virtual

View File

@ -160,6 +160,8 @@ PUBLISHED:
INLINE double get_control_state(int index) const;
INLINE bool is_control_known(int index) const;
INLINE void set_vibration(double strong, double weak);
INLINE void enable_pointer_events();
INLINE void disable_pointer_events();
@ -179,6 +181,7 @@ protected:
void set_control_state(int index, double state);
void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time);
virtual void do_set_vibration(double low, double high);
virtual void do_poll();
public:

View File

@ -202,10 +202,8 @@ consider_add_evdev_device(int ev_index) {
// 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";
<< "/dev/input/event" << ev_index << " is not readable, some "
"features will be unavailable.\n";
}
_evdev_devices[ev_index] = device;
return device;