1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Gamepad module
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include <QtCore/qglobal.h>
38QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // GCC warnings don't make sense, so disable
39
40#include "qevdevgamepadbackend_p.h"
41#include <QtCore/QSocketNotifier>
42#include <QtCore/QLoggingCategory>
43#include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h>
44#include <QtCore/private/qcore_unix_p.h>
45#include <linux/input.h>
46
47#include <cmath>
48
49QT_BEGIN_NAMESPACE
50
51Q_LOGGING_CATEGORY(lcEGB, "qt.gamepad")
52
53#ifndef BTN_TRIGGER_HAPPY1
54# define BTN_TRIGGER_HAPPY1 0x2c0
55#endif
56#ifndef BTN_TRIGGER_HAPPY2
57# define BTN_TRIGGER_HAPPY2 0x2c1
58#endif
59#ifndef BTN_TRIGGER_HAPPY3
60# define BTN_TRIGGER_HAPPY3 0x2c2
61#endif
62#ifndef BTN_TRIGGER_HAPPY4
63# define BTN_TRIGGER_HAPPY4 0x2c3
64#endif
65
66QEvdevGamepadDevice::EvdevAxisInfo::EvdevAxisInfo()
67 : QGamepadBackend::AxisInfo<int>(0, 1, QGamepadManager::AxisInvalid)
68{
69}
70
71QEvdevGamepadDevice::EvdevAxisInfo::EvdevAxisInfo(int fd, quint16 abs, int min, int max, QGamepadManager::GamepadAxis gamepadAxis)
72 : QGamepadBackend::AxisInfo<int>(min, max, gamepadAxis)
73 , flat(0)
74 , gamepadMinButton(QGamepadManager::ButtonInvalid)
75 , gamepadMaxButton(QGamepadManager::ButtonInvalid)
76 , gamepadLastButton(QGamepadManager::ButtonInvalid)
77{
78 setAbsInfo(fd, abs);
79}
80
81double QEvdevGamepadDevice::EvdevAxisInfo::normalized(int value) const
82{
83 double val = AxisInfo::normalized(value);
84 if (qAbs(t: val) <= flat)
85 val = 0;
86 return val;
87}
88
89void QEvdevGamepadDevice::EvdevAxisInfo::setAbsInfo(int fd, int abs)
90{
91 input_absinfo absInfo;
92 memset(s: &absInfo, c: 0, n: sizeof(input_absinfo));
93 if (ioctl(fd: fd, EVIOCGABS(abs), &absInfo) >= 0) {
94 minValue = absInfo.minimum;
95 maxValue = absInfo.maximum;
96 if (maxValue - minValue)
97 flat = std::abs(x: absInfo.flat / double(maxValue - minValue));
98 }
99}
100
101void QEvdevGamepadDevice::EvdevAxisInfo::restoreSavedData(int fd, int abs, const QVariantMap &value)
102{
103 gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1String("axis")].toInt());
104 gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1String("minButton")].toInt());
105 gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1String("maxButton")].toInt());
106 setAbsInfo(fd, abs);
107}
108
109QVariantMap QEvdevGamepadDevice::EvdevAxisInfo::dataToSave() const
110{
111 QVariantMap data;
112 data[QLatin1String("axis")] = gamepadAxis;
113 data[QLatin1String("minButton")] = gamepadMinButton;
114 data[QLatin1String("maxButton")] = gamepadMaxButton;
115 return data;
116}
117
118QEvdevGamepadBackend::QEvdevGamepadBackend()
119{
120}
121
122bool QEvdevGamepadBackend::start()
123{
124 qCDebug(lcEGB) << "start";
125 QByteArray device = qgetenv(varName: "QT_GAMEPAD_DEVICE");
126 if (device.isEmpty()) {
127 qCDebug(lcEGB) << "Using device discovery";
128 m_discovery = QDeviceDiscovery::create(type: QDeviceDiscovery::Device_Joystick, parent: this);
129 if (m_discovery) {
130 const QStringList devices = m_discovery->scanConnectedDevices();
131 for (const QString &devStr : devices) {
132 device = devStr.toUtf8();
133 m_devices.append(t: newDevice(device));
134 }
135 connect(sender: m_discovery, SIGNAL(deviceDetected(QString)), receiver: this, SLOT(handleAddedDevice(QString)));
136 connect(sender: m_discovery, SIGNAL(deviceRemoved(QString)), receiver: this, SLOT(handleRemovedDevice(QString)));
137 } else {
138 qWarning(msg: "No device specified, set QT_GAMEPAD_DEVICE");
139 return false;
140 }
141 } else {
142 qCDebug(lcEGB) << "Using device" << device;
143 m_devices.append(t: newDevice(device));
144 }
145
146 return true;
147}
148
149QEvdevGamepadDevice *QEvdevGamepadBackend::newDevice(const QByteArray &device)
150{
151 qCDebug(lcEGB) << "Opening device" << device;
152 return new QEvdevGamepadDevice(device, this);
153}
154
155QEvdevGamepadDevice *QEvdevGamepadBackend::device(int deviceId)
156{
157 for (QEvdevGamepadDevice *device : qAsConst(t&: m_devices))
158 if (device->deviceId() == deviceId)
159 return device;
160 return nullptr;
161}
162
163void QEvdevGamepadBackend::resetConfiguration(int deviceId)
164{
165 if (QEvdevGamepadDevice *dev = device(deviceId))
166 return dev->resetConfiguration();
167}
168
169bool QEvdevGamepadBackend::isConfigurationNeeded(int deviceId)
170{
171 if (QEvdevGamepadDevice *dev = device(deviceId))
172 return dev->isConfigurationNeeded();
173 return false;
174}
175
176bool QEvdevGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button)
177{
178 if (QEvdevGamepadDevice *dev = device(deviceId))
179 return dev->configureButton(button);
180 return false;
181}
182
183bool QEvdevGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis)
184{
185 if (QEvdevGamepadDevice *dev = device(deviceId))
186 return dev->configureAxis(axis);
187 return false;
188}
189
190bool QEvdevGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button)
191{
192 if (QEvdevGamepadDevice *dev = device(deviceId))
193 return dev->setCancelConfigureButton(button);
194 return false;
195}
196
197void QEvdevGamepadBackend::stop()
198{
199 qCDebug(lcEGB) << "stop";
200 qDeleteAll(c: m_devices);
201 m_devices.clear();
202}
203
204void QEvdevGamepadBackend::handleAddedDevice(const QString &device)
205{
206 // This does not imply that an actual controller is connected.
207 // When connecting the wireless receiver 4 devices will show up right away.
208 // Therefore gamepadAdded() will be emitted only later, when something is read.
209 qCDebug(lcEGB) << "Connected device" << device;
210 m_devices.append(t: newDevice(device: device.toUtf8()));
211}
212
213void QEvdevGamepadBackend::handleRemovedDevice(const QString &device)
214{
215 qCDebug(lcEGB) << "Disconnected device" << device;
216 QByteArray dev = device.toUtf8();
217 for (int i = 0; i < m_devices.count(); ++i) {
218 if (m_devices[i]->deviceName() == dev) {
219 delete m_devices[i];
220 m_devices.removeAt(i);
221 break;
222 }
223 }
224}
225
226QEvdevGamepadDevice::QEvdevGamepadDevice(const QByteArray &dev, QEvdevGamepadBackend *backend)
227 : m_dev(dev),
228 m_backend(backend),
229 m_fd(-1),
230 m_productId(0),
231 m_needsConfigure(true),
232 m_notifier(0),
233 m_configureButton(QGamepadManager::ButtonInvalid),
234 m_configureAxis(QGamepadManager::AxisInvalid)
235{
236 openDevice(dev);
237}
238
239QEvdevGamepadDevice::~QEvdevGamepadDevice()
240{
241 if (m_fd != -1)
242 QT_CLOSE(fd: m_fd);
243
244 if (m_productId)
245 emit m_backend->gamepadRemoved(deviceId: m_productId);
246}
247
248void QEvdevGamepadDevice::resetConfiguration()
249{
250 m_axisMap.insert(ABS_X, avalue: EvdevAxisInfo(m_fd, ABS_X, -32768, 32767, QGamepadManager::AxisLeftX));
251 m_axisMap.insert(ABS_Y, avalue: EvdevAxisInfo(m_fd, ABS_Y, -32768, 32767, QGamepadManager::AxisLeftY));
252 m_axisMap.insert(ABS_RX, avalue: EvdevAxisInfo(m_fd, ABS_RX, -32768, 32767, QGamepadManager::AxisRightX));
253 m_axisMap.insert(ABS_RY, avalue: EvdevAxisInfo(m_fd, ABS_RY, -32768, 32767, QGamepadManager::AxisRightY));
254 m_axisMap.insert(ABS_Z, avalue: EvdevAxisInfo(m_fd, ABS_Z, 0, 255));
255 m_axisMap[ABS_Z].gamepadMinButton = QGamepadManager::ButtonL2;
256 m_axisMap[ABS_Z].gamepadMaxButton = QGamepadManager::ButtonL2;
257
258 m_axisMap.insert(ABS_RZ, avalue: EvdevAxisInfo(m_fd, ABS_RZ, 0, 255));
259 m_axisMap[ABS_RZ].gamepadMinButton = QGamepadManager::ButtonR2;
260 m_axisMap[ABS_RZ].gamepadMaxButton = QGamepadManager::ButtonR2;
261
262 m_axisMap.insert(ABS_HAT0X, avalue: EvdevAxisInfo(m_fd, ABS_HAT0X, -1, 1));
263 m_axisMap[ABS_HAT0X].gamepadMinButton = QGamepadManager::ButtonLeft;
264 m_axisMap[ABS_HAT0X].gamepadMaxButton = QGamepadManager::ButtonRight;
265
266 m_axisMap.insert(ABS_HAT0Y, avalue: EvdevAxisInfo(m_fd, ABS_HAT0Y, -1, 1));
267 m_axisMap[ABS_HAT0Y].gamepadMinButton = QGamepadManager::ButtonUp;
268 m_axisMap[ABS_HAT0Y].gamepadMaxButton = QGamepadManager::ButtonDown;
269
270 m_buttonsMap[BTN_START] = QGamepadManager::ButtonStart;
271 m_buttonsMap[BTN_SELECT] = QGamepadManager::ButtonSelect;
272 m_buttonsMap[BTN_MODE] = QGamepadManager::ButtonGuide;
273 m_buttonsMap[BTN_X] = QGamepadManager::ButtonX;
274 m_buttonsMap[BTN_Y] = QGamepadManager::ButtonY;
275 m_buttonsMap[BTN_A] = QGamepadManager::ButtonA;
276 m_buttonsMap[BTN_B] = QGamepadManager::ButtonB;
277 m_buttonsMap[BTN_TL] = QGamepadManager::ButtonL1;
278 m_buttonsMap[BTN_TR] = QGamepadManager::ButtonR1;
279 m_buttonsMap[BTN_TL2] = QGamepadManager::ButtonL2;
280 m_buttonsMap[BTN_TR2] = QGamepadManager::ButtonR2;
281 m_buttonsMap[BTN_THUMB] = m_buttonsMap[BTN_THUMBL] = QGamepadManager::ButtonL3;
282 m_buttonsMap[BTN_THUMBR] = QGamepadManager::ButtonR3;
283 m_buttonsMap[BTN_TRIGGER_HAPPY1] = QGamepadManager::ButtonLeft;
284 m_buttonsMap[BTN_TRIGGER_HAPPY2] = QGamepadManager::ButtonRight;
285 m_buttonsMap[BTN_TRIGGER_HAPPY3] = QGamepadManager::ButtonUp;
286 m_buttonsMap[BTN_TRIGGER_HAPPY4] = QGamepadManager::ButtonDown;
287
288 if (m_productId)
289 m_backend->saveSettings(productId: m_productId, value: QVariant());
290}
291
292bool QEvdevGamepadDevice::isConfigurationNeeded()
293{
294 return m_needsConfigure;
295}
296
297bool QEvdevGamepadDevice::configureButton(QGamepadManager::GamepadButton button)
298{
299 m_configureButton = button;
300 return true;
301}
302
303bool QEvdevGamepadDevice::configureAxis(QGamepadManager::GamepadAxis axis)
304{
305 m_configureAxis = axis;
306 return true;
307}
308
309bool QEvdevGamepadDevice::setCancelConfigureButton(QGamepadManager::GamepadButton button)
310{
311 m_configureCancelButton = button;
312 return true;
313}
314
315bool QEvdevGamepadDevice::openDevice(const QByteArray &dev)
316{
317 m_fd = QT_OPEN(pathname: dev.constData(), O_RDONLY | O_NDELAY, mode: 0);
318
319 if (m_fd >= 0) {
320 m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
321 connect(sender: m_notifier, SIGNAL(activated(int)), receiver: this, SLOT(readData()));
322 qCDebug(lcEGB) << "Successfully opened" << dev;
323 } else {
324 qErrnoWarning(errno, msg: "Gamepad: Cannot open input device %s", qPrintable(dev));
325 return false;
326 }
327
328 input_id id;
329 if (ioctl(fd: m_fd, EVIOCGID, &id) >= 0) {
330 m_productId = id.product;
331
332 QVariant settings = m_backend->readSettings(productId: m_productId);
333 if (!settings.isNull()) {
334 m_needsConfigure = false;
335 QVariantMap data = settings.toMap()[QLatin1String("axes")].toMap();
336 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) {
337 const int key = it.key().toInt();
338 m_axisMap[key].restoreSavedData(fd: m_fd, abs: key, value: it.value().toMap());
339 }
340
341 data = settings.toMap()[QLatin1String("buttons")].toMap();
342 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
343 m_buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt());
344 }
345
346 emit m_backend->gamepadAdded(deviceId: m_productId);
347
348 // same as libevdev::libevdev_set_fd() in libevdev.c
349 char buffer[256];
350 memset(s: buffer, c: 0, n: sizeof(buffer));
351 if (ioctl(fd: m_fd, EVIOCGNAME(sizeof(buffer) - 1), buffer) >= 0)
352 emit m_backend->gamepadNamed(deviceId: m_productId, name: QString::fromUtf8(str: buffer));
353
354 } else {
355 QT_CLOSE(fd: m_fd);
356 m_fd = -1;
357 return false;
358 }
359
360 if (m_needsConfigure)
361 resetConfiguration();
362
363 qCDebug(lcEGB) << "Axis limits:" << m_axisMap;
364
365 return true;
366}
367
368QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::EvdevAxisInfo &axisInfo)
369{
370 dbg.nospace() << "AxisInfo(min=" << axisInfo.minValue << ", max=" << axisInfo.maxValue << ")";
371 return dbg.space();
372}
373
374void QEvdevGamepadDevice::readData()
375{
376 input_event buffer[32];
377 int events = 0, n = 0;
378 for (; ;) {
379 events = QT_READ(fd: m_fd, data: reinterpret_cast<char*>(buffer) + n, maxlen: sizeof(buffer) - n);
380 if (events <= 0)
381 goto err;
382 n += events;
383 if (n % sizeof(::input_event) == 0)
384 break;
385 }
386
387 n /= sizeof(::input_event);
388
389 for (int i = 0; i < n; ++i)
390 processInputEvent(e: &buffer[i]);
391
392 return;
393
394err:
395 if (!events) {
396 qWarning(msg: "Gamepad: Got EOF from input device");
397 return;
398 } else if (events < 0) {
399 if (errno != EINTR && errno != EAGAIN) {
400 qErrnoWarning(errno, msg: "Gamepad: Could not read from input device");
401 if (errno == ENODEV) { // device got disconnected -> stop reading
402 delete m_notifier;
403 m_notifier = 0;
404 QT_CLOSE(fd: m_fd);
405 m_fd = -1;
406 }
407 }
408 }
409}
410
411void QEvdevGamepadDevice::saveData()
412{
413 if (!m_productId)
414 return ;
415
416 QVariantMap settings, data;
417 for (AxisMap::const_iterator it = m_axisMap.begin(); it != m_axisMap.end(); ++it)
418 data[QString::number(it.key())] = it.value().dataToSave();
419 settings[QLatin1String("axes")] = data;
420
421 data.clear();
422 for (ButtonsMap::const_iterator it = m_buttonsMap.begin(); it != m_buttonsMap.end(); ++it)
423 data[QString::number(it.key())] = it.value();
424
425 settings[QLatin1String("buttons")] = data;
426
427 m_backend->saveSettings(productId: m_productId, value: settings);
428}
429
430void QEvdevGamepadDevice::processInputEvent(input_event *e)
431{
432 if (e->type == EV_KEY) {
433 QGamepadManager::GamepadButton btn = QGamepadManager::ButtonInvalid;
434 ButtonsMap::const_iterator it = m_buttonsMap.find(akey: e->code);
435 if (it != m_buttonsMap.end())
436 btn = it.value();
437
438 const bool pressed = e->value;
439 if (m_configureCancelButton != QGamepadManager::ButtonInvalid &&
440 m_configureCancelButton != m_configureButton &&
441 !pressed && btn == m_configureCancelButton &&
442 (m_configureButton != QGamepadManager::ButtonInvalid ||
443 m_configureAxis != QGamepadManager::AxisInvalid)) {
444 m_configureButton = QGamepadManager::ButtonInvalid;
445 m_configureAxis = QGamepadManager::AxisInvalid;
446 emit m_backend->configurationCanceled(deviceId: m_productId);
447 return;
448 }
449
450 if (!pressed && m_configureButton != QGamepadManager::ButtonInvalid) {
451 m_buttonsMap[e->code] = m_configureButton;
452 QGamepadManager::GamepadButton but = m_configureButton;
453 m_configureButton = QGamepadManager::ButtonInvalid;
454 saveData();
455 emit m_backend->buttonConfigured(deviceId: m_productId, button: but);
456 }
457
458 it = m_buttonsMap.find(akey: e->code);
459 if (it != m_buttonsMap.end())
460 btn = it.value();
461
462 if (btn != QGamepadManager::ButtonInvalid) {
463 if (pressed)
464 emit m_backend->gamepadButtonPressed(deviceId: m_productId, button: btn, value: 1.0);
465 else
466 emit m_backend->gamepadButtonReleased(deviceId: m_productId, button: btn);
467 }
468 } else if (e->type == EV_ABS) {
469 if (m_configureAxis != QGamepadManager::AxisInvalid) {
470 EvdevAxisInfo inf(m_fd, e->code, -32768, 32767, m_configureAxis);
471 if (std::abs(x: inf.normalized(value: e->value)) == 1) {
472 m_axisMap.insert(akey: e->code, avalue: EvdevAxisInfo(m_fd, e->code, -32768, 32767, m_configureAxis));
473
474 QGamepadManager::GamepadAxis axis = m_configureAxis;
475 m_configureAxis = QGamepadManager::AxisInvalid;
476
477 saveData();
478 emit m_backend->axisConfigured(deviceId: m_productId, axis);
479 } else {
480 return;
481 }
482 }
483
484 AxisMap::iterator it = m_axisMap.find(akey: e->code);
485 if (m_configureButton != QGamepadManager::ButtonInvalid) {
486 EvdevAxisInfo axisInfo;
487 if (it != m_axisMap.end())
488 axisInfo = it.value();
489 else
490 axisInfo = EvdevAxisInfo(m_fd, e->code);
491
492 axisInfo.gamepadAxis = QGamepadManager::AxisInvalid;
493
494 bool save = false;
495 if (e->value == axisInfo.minValue) {
496 axisInfo.gamepadMinButton = m_configureButton;
497 if (axisInfo.gamepadMaxButton != QGamepadManager::ButtonInvalid)
498 axisInfo.gamepadMaxButton = m_configureButton;
499 save = true;
500 } else if (e->value == axisInfo.maxValue) {
501 axisInfo.gamepadMaxButton = m_configureButton;
502 if (axisInfo.gamepadMinButton != QGamepadManager::ButtonInvalid)
503 axisInfo.gamepadMinButton = m_configureButton;
504 save = true;
505 }
506
507 if (save) {
508 QGamepadManager::GamepadButton but = m_configureButton;
509 m_configureButton = QGamepadManager::ButtonInvalid;
510 if (but == QGamepadManager::ButtonL2 || but == QGamepadManager::ButtonR2)
511 m_axisMap.insert(akey: e->code, avalue: axisInfo);
512 saveData();
513 emit m_backend->buttonConfigured(deviceId: m_productId, button: but);
514 }
515 }
516
517 it = m_axisMap.find(akey: e->code);
518 if (it == m_axisMap.end())
519 return;
520
521 EvdevAxisInfo &info = it.value();
522
523 double val = info.normalized(value: e->value);
524
525 if (info.gamepadAxis != QGamepadManager::AxisInvalid)
526 emit m_backend->gamepadAxisMoved(deviceId: m_productId, axis: info.gamepadAxis, value: val);
527
528 if (info.gamepadMaxButton == info.gamepadMinButton &&
529 info.gamepadMaxButton != QGamepadManager::ButtonInvalid) {
530 if (val)
531 emit m_backend->gamepadButtonPressed(deviceId: m_productId, button: info.gamepadMaxButton, value: std::abs(x: val));
532 else
533 emit m_backend->gamepadButtonReleased(deviceId: m_productId, button: info.gamepadMaxButton);
534 } else {
535 if (info.gamepadMaxButton != QGamepadManager::ButtonInvalid
536 && val == 1.0) {
537 info.gamepadLastButton = info.gamepadMaxButton;
538 emit m_backend->gamepadButtonPressed(deviceId: m_productId, button: info.gamepadMaxButton, value: val);
539 } else if (info.gamepadMinButton != QGamepadManager::ButtonInvalid
540 && val == -1.0) {
541 info.gamepadLastButton = info.gamepadMinButton;
542 emit m_backend->gamepadButtonPressed(deviceId: m_productId, button: info.gamepadMinButton, value: -val);
543 } else if (!val && info.gamepadLastButton != QGamepadManager::ButtonInvalid) {
544 QGamepadManager::GamepadButton but = info.gamepadLastButton;
545 info.gamepadLastButton = QGamepadManager::ButtonInvalid;
546 emit m_backend->gamepadButtonReleased(deviceId: m_productId, button: but);
547 }
548 }
549 }
550}
551
552QT_END_NAMESPACE
553

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtgamepad/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp