Panda3D
Loading...
Searching...
No Matches
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
25TypeHandle LinuxJoystickDevice::_type_handle;
26
27/**
28 * Creates a new device using the Linux joystick device with the given index.
29 */
30LinuxJoystickDevice::
31LinuxJoystickDevice(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 */
53LinuxJoystickDevice::
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 */
64bool LinuxJoystickDevice::
65check_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 */
76void LinuxJoystickDevice::
77do_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 */
95bool LinuxJoystickDevice::
96open_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 */
384bool LinuxJoystickDevice::
385process_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...
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.