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  axis = Axis::none;
238  }
239  break;
240 
241  case ABS_HAT0Y:
242  if (emulate_dpad) {
243  // Emulate D-Pad.
244  _dpad_y_axis = i;
245  _dpad_up_button = (int)_buttons.size();
246  if (_device_class == DeviceClass::gamepad) {
247  add_button(GamepadButton::dpad_up());
248  add_button(GamepadButton::dpad_down());
249  } else {
250  add_button(GamepadButton::hat_up());
251  add_button(GamepadButton::hat_down());
252  }
253  axis = Axis::none;
254  }
255  break;
256 
257  case ABS_HAT2X:
258  if (_device_class == DeviceClass::gamepad) {
259  axis = InputDevice::Axis::right_trigger;
260  }
261  break;
262 
263  case ABS_HAT2Y:
264  if (_device_class == DeviceClass::gamepad) {
265  axis = InputDevice::Axis::left_trigger;
266  }
267  break;
268 
269  default:
270  if (device_cat.is_debug()) {
271  device_cat.debug() << "Unmapped /dev/input/js" << _index
272  << " axis " << (int)i << ": 0x" << std::hex << (int)axmap[i] << "\n";
273  }
274  axis = Axis::none;
275  break;
276  }
277  _axes[i].axis = axis;
278 
279  if (axis == Axis::left_trigger || axis == Axis::right_trigger) {
280  // We'd like to use 0.0 to indicate the resting position.
281  _axes[i]._scale = 1.0 / 65534.0;
282  _axes[i]._bias = 0.5;
283  have_analog_triggers = true;
284  } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) {
285  _axes[i]._scale = 1.0 / -32767.0;
286  _axes[i]._bias = 0.0;
287  } else {
288  _axes[i]._scale = 1.0 / 32767.0;
289  _axes[i]._bias = 0.0;
290  }
291  }
292  }
293 
294  if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) {
295  // Emulate analog triggers.
296  _ltrigger_axis = (int)_axes.size();
297  add_axis(Axis::left_trigger, 0, 1, false);
298  add_axis(Axis::right_trigger, 0, 1, false);
299  } else {
300  _ltrigger_button = -1;
301  _rtrigger_button = -1;
302  }
303 
304  // Get additional information from sysfs.
305  sprintf(path, "/sys/class/input/js%zd/device/id/vendor", _index);
306  FILE *f = fopen(path, "r");
307  if (f) {
308  if (fscanf(f, "%hx", &_vendor_id) < 1) {
309  _vendor_id = 0;
310  }
311  fclose(f);
312  }
313  sprintf(path, "/sys/class/input/js%zd/device/id/product", _index);
314  f = fopen(path, "r");
315  if (f) {
316  if (fscanf(f, "%hx", &_product_id) < 1) {
317  _product_id = 0;
318  }
319  fclose(f);
320  }
321  char buffer[256];
322  sprintf(path, "/sys/class/input/js%zd/device/device/../product", _index);
323  f = fopen(path, "r");
324  if (f) {
325  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
326  buffer[strcspn(buffer, "\r\n")] = 0;
327  if (buffer[0] != 0) {
328  _name.assign(buffer);
329  }
330  }
331  fclose(f);
332  }
333  sprintf(path, "/sys/class/input/js%zd/device/device/../manufacturer", _index);
334  f = fopen(path, "r");
335  if (f) {
336  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
337  buffer[strcspn(buffer, "\r\n")] = 0;
338  _manufacturer.assign(buffer);
339  }
340  fclose(f);
341  }
342  sprintf(path, "/sys/class/input/js%zd/device/device/../serial", _index);
343  f = fopen(path, "r");
344  if (f) {
345  if (fgets(buffer, sizeof(buffer), f) != nullptr) {
346  buffer[strcspn(buffer, "\r\n")] = 0;
347  _serial_number.assign(buffer);
348  }
349  fclose(f);
350  }
351 
352  // Read the init events.
353  while (process_events()) {};
354 
355  // Special case handling for the wireless Xbox receiver - the Linux
356  // joystick API doesn't seem to have a way to report whether the device
357  // is actually turned on. The best we can do is check whether the axes
358  // are all 0, which indicates that the driver hasn't received any data for
359  // this gamepad yet (which means it hasn't been plugged in for this session)
360  if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) {
361  for (const auto &control : _axes) {
362  if (control.value != 0.0) {
363  _is_connected = true;
364  return true;
365  }
366  }
367  _is_connected = false;
368  } else {
369  _is_connected = true;
370  }
371 
372  return true;
373 }
374 
375 /**
376  * Reads a number of events from the device. Returns true if events were
377  * read, meaning this function should keep being called until it returns
378  * false.
379  */
380 bool LinuxJoystickDevice::
381 process_events() {
382  // Read 8 events at a time.
383  struct js_event events[8];
384 
385  int n_read = read(_fd, events, sizeof(events));
386  if (n_read < 0) {
387  if (errno == EAGAIN || errno == EWOULDBLOCK) {
388  // No data available for now.
389 
390  } else if (errno == ENODEV) {
391  // The device ceased to exist, so we better close it. No need
392  // to worry about removing it from the InputDeviceManager, as it
393  // will get an inotify event sooner or later about this.
394  close(_fd);
395  _fd = -1;
396  //_is_connected = false;
397  errno = 0;
398 
399  } else {
400  device_cat.error() << "read: " << strerror(errno) << "\n";
401  }
402  return false;
403  }
404 
405  if (n_read == 0) {
406  return false;
407  }
408 
409  n_read /= sizeof(struct js_event);
410 
411  for (int i = 0; i < n_read; ++i) {
412  int index = events[i].number;
413 
414  if (events[i].type & JS_EVENT_BUTTON) {
415  if (index == _ltrigger_button) {
416  axis_changed(_ltrigger_axis, events[i].value);
417  } else if (index == _rtrigger_button) {
418  axis_changed(_ltrigger_axis + 1, events[i].value);
419  }
420  button_changed(index, (events[i].value != 0));
421 
422  } else if (events[i].type & JS_EVENT_AXIS) {
423  if (index == _dpad_x_axis) {
424  button_changed(_dpad_left_button, events[i].value < -1000);
425  button_changed(_dpad_left_button+1, events[i].value > 1000);
426  } else if (index == _dpad_y_axis) {
427  button_changed(_dpad_up_button, events[i].value < -1000);
428  button_changed(_dpad_up_button+1, events[i].value > 1000);
429  }
430 
431  axis_changed(index, events[i].value);
432  }
433  }
434 
435  return true;
436 }
437 
438 #endif
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.
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