Panda3D
linuxJoystickDevice.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file linuxJoystickDevice.cxx
10  * @author rdb
11  * @date 2015-08-21
12  */
13 
14 #include "linuxJoystickDevice.h"
15 #include "evdevInputDevice.h"
16 
17 #ifdef PHAVE_LINUX_INPUT_H
18 
19 #include "gamepadButton.h"
21 
22 #include <fcntl.h>
23 #include <linux/joystick.h>
24 
25 TypeHandle LinuxJoystickDevice::_type_handle;
26 
27 /**
28  * Creates a new device using the Linux joystick device with the given index.
29  */
30 LinuxJoystickDevice::
31 LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index) :
32  _manager(manager),
33  _fd(-1),
34  _index(index),
35  _dpad_x_axis(-1),
36  _dpad_y_axis(-1),
37  _dpad_left_button(-1),
38  _dpad_up_button(-1),
39  _ltrigger_button(-1),
40  _rtrigger_button(-1)
41 {
42  LightMutexHolder holder(_lock);
43  if (!open_device()) {
44  device_cat.error()
45  << "Could not open joystick device /dev/input/js" << index
46  << ": " << strerror(errno) << "\n";
47  }
48 }
49 
50 /**
51  *
52  */
53 LinuxJoystickDevice::
54 ~LinuxJoystickDevice() {
55  if (_fd != -1) {
56  close(_fd);
57  _fd = -1;
58  }
59 }
60 
61 /**
62  * Returns true if there are pending events.
63  */
64 bool LinuxJoystickDevice::
65 check_events() const {
66  unsigned int avail = 0;
67  ioctl(_fd, FIONREAD, &avail);
68  return (avail != 0);
69 }
70 
71 /**
72  * Polls the input device for new activity, to ensure it contains the latest
73  * events. This will only have any effect for some types of input devices;
74  * others may be updated automatically, and this method will be a no-op.
75  */
76 void LinuxJoystickDevice::
77 do_poll() {
78  if (_fd != -1 && process_events()) {
79  while (process_events()) {}
80 
81  // If we got events, we are obviously connected. Mark us so.
82  if (!_is_connected) {
83  _is_connected = true;
84  if (_manager != nullptr) {
85  _manager->add_device(this);
86  }
87  }
88  }
89 }
90 
91 /**
92  * Opens or reopens the joystick device, and reads out the button and axis
93  * mappings. Assumes the lock is already held.
94  */
95 bool LinuxJoystickDevice::
96 open_device() {
97  nassertr(_lock.debug_is_locked(), false);
98 
99  char path[64];
100  sprintf(path, "/dev/input/js%zd", _index);
101 
102  _fd = open(path, O_RDONLY | O_NONBLOCK);
103 
104  if (_fd == -1) {
105  _is_connected = false;
106  return false;
107  }
108 
109  // Read the name from the device. We'll later try to use sysfs to read
110  // the proper product name from the device, but this is a good fallback.
111  char name[128];
112  name[0] = 0;
113  ioctl(_fd, JSIOCGNAME(sizeof(name)), name);
114  _name = name;
115 
116  bool emulate_dpad = true;
117  bool have_analog_triggers = false;
118 
119  // Get the number of axes.
120  uint8_t num_axes = 0, num_buttons = 0;
121  ioctl(_fd, JSIOCGAXES, &num_axes);
122  ioctl(_fd, JSIOCGBUTTONS, &num_buttons);
123 
124  _buttons.resize(num_buttons);
125  _axes.resize(num_axes);
126 
127  if (num_buttons > 0) {
128  uint16_t btnmap[512];
129  ioctl(_fd, JSIOCGBTNMAP, btnmap);
130 
131  for (uint8_t i = 0; i < num_buttons; ++i) {
132  ButtonHandle handle = EvdevInputDevice::map_button(btnmap[i]);
133  if (handle == ButtonHandle::none()) {
134  if (device_cat.is_debug()) {
135  device_cat.debug() << "Unmapped /dev/input/js" << _index
136  << " button " << (int)i << ": 0x" << std::hex << btnmap[i] << "\n";
137  }
138  } else if (handle == GamepadButton::face_a()) {
139  _device_class = DeviceClass::gamepad;
140  } else if (handle == GamepadButton::trigger()) {
141  _device_class = DeviceClass::flight_stick;
142  } else if (handle == GamepadButton::dpad_left()) {
143  emulate_dpad = false;
144  } else if (handle == GamepadButton::ltrigger()) {
145  _ltrigger_button = i;
146  } else if (handle == GamepadButton::rtrigger()) {
147  _rtrigger_button = i;
148  }
149  _buttons[i].handle = handle;
150  }
151  }
152 
153  if (num_axes > 0) {
154  uint8_t axmap[512];
155  ioctl(_fd, JSIOCGAXMAP, axmap);
156 
157  for (uint8_t i = 0; i < num_axes; ++i) {
158  Axis axis = Axis::none;
159 
160  switch (axmap[i]) {
161  case ABS_X:
162  if (_device_class == DeviceClass::gamepad) {
163  axis = InputDevice::Axis::left_x;
164  } else if (_device_class == DeviceClass::flight_stick) {
165  axis = InputDevice::Axis::roll;
166  } else {
167  axis = InputDevice::Axis::x;
168  }
169  break;
170 
171  case ABS_Y:
172  if (_device_class == DeviceClass::gamepad) {
173  axis = InputDevice::Axis::left_y;
174  } else if (_device_class == DeviceClass::flight_stick) {
175  axis = InputDevice::Axis::pitch;
176  } else {
177  axis = InputDevice::Axis::y;
178  }
179  break;
180 
181  case ABS_Z:
182  if (_device_class == DeviceClass::gamepad) {
183  axis = Axis::left_trigger;
184  } else {
185  //axis = Axis::trigger;
186  }
187  break;
188 
189  case ABS_RX:
190  axis = Axis::right_x;
191  break;
192 
193  case ABS_RY:
194  axis = Axis::right_y;
195  break;
196 
197  case ABS_RZ:
198  if (_device_class == DeviceClass::gamepad) {
199  axis = InputDevice::Axis::right_trigger;
200  } else {
201  axis = InputDevice::Axis::yaw;
202  }
203  break;
204 
205  case ABS_THROTTLE:
206  axis = InputDevice::Axis::throttle;
207  break;
208 
209  case ABS_RUDDER:
210  axis = InputDevice::Axis::rudder;
211  break;
212 
213  case ABS_WHEEL:
214  axis = InputDevice::Axis::wheel;
215  break;
216 
217  case ABS_GAS:
218  axis = InputDevice::Axis::accelerator;
219  break;
220 
221  case ABS_BRAKE:
222  axis = InputDevice::Axis::brake;
223  break;
224 
225  case ABS_HAT0X:
226  if (emulate_dpad) {
227  // Emulate D-Pad or hat switch.
228  _dpad_x_axis = i;
229  _dpad_left_button = (int)_buttons.size();
230  if (_device_class == DeviceClass::gamepad) {
231  add_button(GamepadButton::dpad_left());
232  add_button(GamepadButton::dpad_right());
233  } else {
234  add_button(GamepadButton::hat_left());
235  add_button(GamepadButton::hat_right());
236  }
237  _buttons[_dpad_left_button]._state = S_up;
238  _buttons[_dpad_left_button+1]._state = S_up;
239  axis = Axis::none;
240  }
241  break;
242 
243  case ABS_HAT0Y:
244  if (emulate_dpad) {
245  // Emulate D-Pad.
246  _dpad_y_axis = i;
247  _dpad_up_button = (int)_buttons.size();
248  if (_device_class == DeviceClass::gamepad) {
249  add_button(GamepadButton::dpad_up());
250  add_button(GamepadButton::dpad_down());
251  } else {
252  add_button(GamepadButton::hat_up());
253  add_button(GamepadButton::hat_down());
254  }
255  _buttons[_dpad_up_button]._state = S_up;
256  _buttons[_dpad_up_button+1]._state = S_up;
257  axis = Axis::none;
258  }
259  break;
260 
261  case ABS_HAT2X:
262  if (_device_class == DeviceClass::gamepad) {
263  axis = InputDevice::Axis::right_trigger;
264  }
265  break;
266 
267  case ABS_HAT2Y:
268  if (_device_class == DeviceClass::gamepad) {
269  axis = InputDevice::Axis::left_trigger;
270  }
271  break;
272 
273  default:
274  if (device_cat.is_debug()) {
275  device_cat.debug() << "Unmapped /dev/input/js" << _index
276  << " axis " << (int)i << ": 0x" << std::hex << (int)axmap[i] << "\n";
277  }
278  axis = Axis::none;
279  break;
280  }
281  _axes[i].axis = axis;
282 
283  if (axis == Axis::left_trigger || axis == Axis::right_trigger) {
284  // We'd like to use 0.0 to indicate the resting position.
285  _axes[i]._scale = 1.0 / 65534.0;
286  _axes[i]._bias = 0.5;
287  have_analog_triggers = true;
288  } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) {
289  _axes[i]._scale = 1.0 / -32767.0;
290  _axes[i]._bias = 0.0;
291  } else {
292  _axes[i]._scale = 1.0 / 32767.0;
293  _axes[i]._bias = 0.0;
294  }
295  }
296  }
297 
298  if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) {
299  // Emulate analog triggers.
300  _ltrigger_axis = (int)_axes.size();
301  add_axis(Axis::left_trigger, 0, 1, false);
302  add_axis(Axis::right_trigger, 0, 1, false);
303  } else {
304  _ltrigger_button = -1;
305  _rtrigger_button = -1;
306  }
307 
308  // Get additional information from sysfs.
309  sprintf(path, "/sys/class/input/js%zd/device/id/vendor", _index);
310  FILE *f = fopen(path, "r");
311  if (f) {
312  if (fscanf(f, "%hx", &_vendor_id) < 1) {
313  _vendor_id = 0;
314  }
315  fclose(f);
316  }
317  sprintf(path, "/sys/class/input/js%zd/device/id/product", _index);
318  f = fopen(path, "r");
319  if (f) {
320  if (fscanf(f, "%hx", &_product_id) < 1) {
321  _product_id = 0;
322  }
323  fclose(f);
324  }
325  char buffer[256];
326  sprintf(path, "/sys/class/input/js%zd/device/device/../product", _index);
327  f = fopen(path, "r");
328  if (f) {
329  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
330  buffer[strcspn(buffer, "\r\n")] = 0;
331  if (buffer[0] != 0) {
332  _name.assign(buffer);
333  }
334  }
335  fclose(f);
336  }
337  sprintf(path, "/sys/class/input/js%zd/device/device/../manufacturer", _index);
338  f = fopen(path, "r");
339  if (f) {
340  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
341  buffer[strcspn(buffer, "\r\n")] = 0;
342  _manufacturer.assign(buffer);
343  }
344  fclose(f);
345  }
346  sprintf(path, "/sys/class/input/js%zd/device/device/../serial", _index);
347  f = fopen(path, "r");
348  if (f) {
349  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
350  buffer[strcspn(buffer, "\r\n")] = 0;
351  _serial_number.assign(buffer);
352  }
353  fclose(f);
354  }
355 
356  // Read the init events.
357  while (process_events()) {};
358 
359  // Special case handling for the wireless Xbox receiver - the Linux
360  // joystick API doesn't seem to have a way to report whether the device
361  // is actually turned on. The best we can do is check whether the axes
362  // are all 0, which indicates that the driver hasn't received any data for
363  // this gamepad yet (which means it hasn't been plugged in for this session)
364  if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
365  for (const auto &control : _axes) {
366  if (control.value != 0.0) {
367  _is_connected = true;
368  return true;
369  }
370  }
371  _is_connected = false;
372  } else {
373  _is_connected = true;
374  }
375 
376  return true;
377 }
378 
379 /**
380  * Reads a number of events from the device. Returns true if events were
381  * read, meaning this function should keep being called until it returns
382  * false.
383  */
384 bool LinuxJoystickDevice::
385 process_events() {
386  // Read 8 events at a time.
387  struct js_event events[8];
388 
389  int n_read = read(_fd, events, sizeof(events));
390  if (n_read < 0) {
391  if (errno == EAGAIN || errno == EWOULDBLOCK) {
392  // No data available for now.
393 
394  } else if (errno == ENODEV) {
395  // The device ceased to exist, so we better close it. No need
396  // to worry about removing it from the InputDeviceManager, as it
397  // will get an inotify event sooner or later about this.
398  close(_fd);
399  _fd = -1;
400  //_is_connected = false;
401  errno = 0;
402 
403  } else {
404  device_cat.error() << "read: " << strerror(errno) << "\n";
405  }
406  return false;
407  }
408 
409  if (n_read == 0) {
410  return false;
411  }
412 
413  n_read /= sizeof(struct js_event);
414 
415  for (int i = 0; i < n_read; ++i) {
416  int index = events[i].number;
417 
418  if (events[i].type & JS_EVENT_BUTTON) {
419  if (index == _ltrigger_button) {
420  axis_changed(_ltrigger_axis, events[i].value);
421  } else if (index == _rtrigger_button) {
422  axis_changed(_ltrigger_axis + 1, events[i].value);
423  }
424  button_changed(index, (events[i].value != 0));
425 
426  } else if (events[i].type & JS_EVENT_AXIS) {
427  if (index == _dpad_x_axis) {
428  button_changed(_dpad_left_button, events[i].value < -1000);
429  button_changed(_dpad_left_button+1, events[i].value > 1000);
430  } else if (index == _dpad_y_axis) {
431  button_changed(_dpad_up_button, events[i].value < -1000);
432  button_changed(_dpad_up_button+1, events[i].value > 1000);
433  }
434 
435  axis_changed(index, events[i].value);
436  }
437  }
438 
439  return true;
440 }
441 
442 #endif
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
Similar to MutexHolder, but for a light mutex.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.