浏览代码

Add gamepad rumble support on Linux

rdb 10 年之前
父节点
当前提交
687e080bdd

+ 91 - 11
panda/src/device/evdevInputDevice.cxx

@@ -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];

+ 6 - 2
panda/src/device/evdevInputDevice.h

@@ -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;

+ 15 - 0
panda/src/device/inputDevice.I

@@ -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

+ 12 - 0
panda/src/device/inputDevice.cxx

@@ -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

+ 3 - 0
panda/src/device/inputDevice.h

@@ -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:

+ 2 - 4
panda/src/device/inputDeviceManager.cxx

@@ -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;