17#ifdef PHAVE_LINUX_INPUT_H
23#include <linux/joystick.h>
31LinuxJoystickDevice(LinuxInputDeviceManager *manager,
size_t index) :
37 _dpad_left_button(-1),
45 <<
"Could not open joystick device /dev/input/js" << index
46 <<
": " << strerror(errno) <<
"\n";
54~LinuxJoystickDevice() {
64bool LinuxJoystickDevice::
66 unsigned int avail = 0;
67 ioctl(_fd, FIONREAD, &avail);
76void LinuxJoystickDevice::
78 if (_fd != -1 && process_events()) {
79 while (process_events()) {}
84 if (_manager !=
nullptr) {
85 _manager->add_device(
this);
95bool LinuxJoystickDevice::
97 nassertr(_lock.debug_is_locked(),
false);
100 sprintf(path,
"/dev/input/js%zd", _index);
102 _fd = open(path, O_RDONLY | O_NONBLOCK);
105 _is_connected =
false;
113 ioctl(_fd, JSIOCGNAME(
sizeof(name)), name);
116 bool emulate_dpad =
true;
117 bool have_analog_triggers =
false;
120 uint8_t num_axes = 0, num_buttons = 0;
121 ioctl(_fd, JSIOCGAXES, &num_axes);
122 ioctl(_fd, JSIOCGBUTTONS, &num_buttons);
124 _buttons.resize(num_buttons);
125 _axes.resize(num_axes);
127 if (num_buttons > 0) {
128 uint16_t btnmap[512];
129 ioctl(_fd, JSIOCGBTNMAP, btnmap);
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";
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;
149 _buttons[i].handle = handle;
155 ioctl(_fd, JSIOCGAXMAP, axmap);
157 for (uint8_t i = 0; i < num_axes; ++i) {
158 Axis axis = Axis::none;
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;
167 axis = InputDevice::Axis::x;
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;
177 axis = InputDevice::Axis::y;
182 if (_device_class == DeviceClass::gamepad) {
183 axis = Axis::left_trigger;
190 axis = Axis::right_x;
194 axis = Axis::right_y;
198 if (_device_class == DeviceClass::gamepad) {
199 axis = InputDevice::Axis::right_trigger;
201 axis = InputDevice::Axis::yaw;
206 axis = InputDevice::Axis::throttle;
210 axis = InputDevice::Axis::rudder;
214 axis = InputDevice::Axis::wheel;
218 axis = InputDevice::Axis::accelerator;
222 axis = InputDevice::Axis::brake;
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());
234 add_button(GamepadButton::hat_left());
235 add_button(GamepadButton::hat_right());
237 _buttons[_dpad_left_button]._state = S_up;
238 _buttons[_dpad_left_button+1]._state = S_up;
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());
252 add_button(GamepadButton::hat_up());
253 add_button(GamepadButton::hat_down());
255 _buttons[_dpad_up_button]._state = S_up;
256 _buttons[_dpad_up_button+1]._state = S_up;
262 if (_device_class == DeviceClass::gamepad) {
263 axis = InputDevice::Axis::right_trigger;
268 if (_device_class == DeviceClass::gamepad) {
269 axis = InputDevice::Axis::left_trigger;
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";
281 _axes[i].axis = axis;
283 if (axis == Axis::left_trigger || axis == Axis::right_trigger) {
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;
292 _axes[i]._scale = 1.0 / 32767.0;
293 _axes[i]._bias = 0.0;
298 if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_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);
304 _ltrigger_button = -1;
305 _rtrigger_button = -1;
309 sprintf(path,
"/sys/class/input/js%zd/device/id/vendor", _index);
310 FILE *f = fopen(path,
"r");
312 if (fscanf(f,
"%hx", &_vendor_id) < 1) {
317 sprintf(path,
"/sys/class/input/js%zd/device/id/product", _index);
318 f = fopen(path,
"r");
320 if (fscanf(f,
"%hx", &_product_id) < 1) {
326 sprintf(path,
"/sys/class/input/js%zd/device/device/../product", _index);
327 f = fopen(path,
"r");
329 if (fgets(buffer,
sizeof(buffer), f) !=
nullptr) {
330 buffer[strcspn(buffer,
"\r\n")] = 0;
331 if (buffer[0] != 0) {
332 _name.assign(buffer);
337 sprintf(path,
"/sys/class/input/js%zd/device/device/../manufacturer", _index);
338 f = fopen(path,
"r");
340 if (fgets(buffer,
sizeof(buffer), f) !=
nullptr) {
341 buffer[strcspn(buffer,
"\r\n")] = 0;
342 _manufacturer.assign(buffer);
346 sprintf(path,
"/sys/class/input/js%zd/device/device/../serial", _index);
347 f = fopen(path,
"r");
349 if (fgets(buffer,
sizeof(buffer), f) !=
nullptr) {
350 buffer[strcspn(buffer,
"\r\n")] = 0;
351 _serial_number.assign(buffer);
357 while (process_events()) {};
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;
371 _is_connected =
false;
373 _is_connected =
true;
384bool LinuxJoystickDevice::
387 struct js_event events[8];
389 int n_read = read(_fd, events,
sizeof(events));
391 if (errno == EAGAIN || errno == EWOULDBLOCK) {
394 }
else if (errno == ENODEV) {
404 device_cat.error() <<
"read: " << strerror(errno) <<
"\n";
413 n_read /=
sizeof(
struct js_event);
415 for (
int i = 0; i < n_read; ++i) {
416 int index = events[i].number;
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);
424 button_changed(index, (events[i].value != 0));
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);
435 axis_changed(index, events[i].value);
Similar to MutexHolder, but for a light mutex.
TypeHandle is the identifier used to differentiate C++ class types.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.