| /**************************************************************************** |
| ** |
| ** Copyright (C) 2015 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Gamepad module |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtCore/qglobal.h> |
| QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") // GCC warnings don't make sense, so disable |
| |
| #include "qevdevgamepadbackend_p.h" |
| #include <QtCore/QSocketNotifier> |
| #include <QtCore/QLoggingCategory> |
| #include <QtDeviceDiscoverySupport/private/qdevicediscovery_p.h> |
| #include <QtCore/private/qcore_unix_p.h> |
| #include <linux/input.h> |
| |
| #include <cmath> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_LOGGING_CATEGORY(lcEGB, "qt.gamepad") |
| |
| #ifndef BTN_TRIGGER_HAPPY1 |
| # define BTN_TRIGGER_HAPPY1 0x2c0 |
| #endif |
| #ifndef BTN_TRIGGER_HAPPY2 |
| # define BTN_TRIGGER_HAPPY2 0x2c1 |
| #endif |
| #ifndef BTN_TRIGGER_HAPPY3 |
| # define BTN_TRIGGER_HAPPY3 0x2c2 |
| #endif |
| #ifndef BTN_TRIGGER_HAPPY4 |
| # define BTN_TRIGGER_HAPPY4 0x2c3 |
| #endif |
| |
| QEvdevGamepadDevice::EvdevAxisInfo::EvdevAxisInfo() |
| : QGamepadBackend::AxisInfo<int>(0, 1, QGamepadManager::AxisInvalid) |
| { |
| } |
| |
| QEvdevGamepadDevice::EvdevAxisInfo::EvdevAxisInfo(int fd, quint16 abs, int min, int max, QGamepadManager::GamepadAxis gamepadAxis) |
| : QGamepadBackend::AxisInfo<int>(min, max, gamepadAxis) |
| , flat(0) |
| , gamepadMinButton(QGamepadManager::ButtonInvalid) |
| , gamepadMaxButton(QGamepadManager::ButtonInvalid) |
| , gamepadLastButton(QGamepadManager::ButtonInvalid) |
| { |
| setAbsInfo(fd, abs); |
| } |
| |
| double QEvdevGamepadDevice::EvdevAxisInfo::normalized(int value) const |
| { |
| double val = AxisInfo::normalized(value); |
| if (qAbs(val) <= flat) |
| val = 0; |
| return val; |
| } |
| |
| void QEvdevGamepadDevice::EvdevAxisInfo::setAbsInfo(int fd, int abs) |
| { |
| input_absinfo absInfo; |
| memset(&absInfo, 0, sizeof(input_absinfo)); |
| if (ioctl(fd, EVIOCGABS(abs), &absInfo) >= 0) { |
| minValue = absInfo.minimum; |
| maxValue = absInfo.maximum; |
| if (maxValue - minValue) |
| flat = std::abs(absInfo.flat / double(maxValue - minValue)); |
| } |
| } |
| |
| void QEvdevGamepadDevice::EvdevAxisInfo::restoreSavedData(int fd, int abs, const QVariantMap &value) |
| { |
| gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1String("axis")].toInt()); |
| gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1String("minButton")].toInt()); |
| gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1String("maxButton")].toInt()); |
| setAbsInfo(fd, abs); |
| } |
| |
| QVariantMap QEvdevGamepadDevice::EvdevAxisInfo::dataToSave() const |
| { |
| QVariantMap data; |
| data[QLatin1String("axis")] = gamepadAxis; |
| data[QLatin1String("minButton")] = gamepadMinButton; |
| data[QLatin1String("maxButton")] = gamepadMaxButton; |
| return data; |
| } |
| |
| QEvdevGamepadBackend::QEvdevGamepadBackend() |
| { |
| } |
| |
| bool QEvdevGamepadBackend::start() |
| { |
| qCDebug(lcEGB) << "start"; |
| QByteArray device = qgetenv("QT_GAMEPAD_DEVICE"); |
| if (device.isEmpty()) { |
| qCDebug(lcEGB) << "Using device discovery"; |
| m_discovery = QDeviceDiscovery::create(QDeviceDiscovery::Device_Joystick, this); |
| if (m_discovery) { |
| const QStringList devices = m_discovery->scanConnectedDevices(); |
| for (const QString &devStr : devices) { |
| device = devStr.toUtf8(); |
| m_devices.append(newDevice(device)); |
| } |
| connect(m_discovery, SIGNAL(deviceDetected(QString)), this, SLOT(handleAddedDevice(QString))); |
| connect(m_discovery, SIGNAL(deviceRemoved(QString)), this, SLOT(handleRemovedDevice(QString))); |
| } else { |
| qWarning("No device specified, set QT_GAMEPAD_DEVICE"); |
| return false; |
| } |
| } else { |
| qCDebug(lcEGB) << "Using device" << device; |
| m_devices.append(newDevice(device)); |
| } |
| |
| return true; |
| } |
| |
| QEvdevGamepadDevice *QEvdevGamepadBackend::newDevice(const QByteArray &device) |
| { |
| qCDebug(lcEGB) << "Opening device" << device; |
| return new QEvdevGamepadDevice(device, this); |
| } |
| |
| QEvdevGamepadDevice *QEvdevGamepadBackend::device(int deviceId) |
| { |
| for (QEvdevGamepadDevice *device : qAsConst(m_devices)) |
| if (device->deviceId() == deviceId) |
| return device; |
| return nullptr; |
| } |
| |
| void QEvdevGamepadBackend::resetConfiguration(int deviceId) |
| { |
| if (QEvdevGamepadDevice *dev = device(deviceId)) |
| return dev->resetConfiguration(); |
| } |
| |
| bool QEvdevGamepadBackend::isConfigurationNeeded(int deviceId) |
| { |
| if (QEvdevGamepadDevice *dev = device(deviceId)) |
| return dev->isConfigurationNeeded(); |
| return false; |
| } |
| |
| bool QEvdevGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button) |
| { |
| if (QEvdevGamepadDevice *dev = device(deviceId)) |
| return dev->configureButton(button); |
| return false; |
| } |
| |
| bool QEvdevGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) |
| { |
| if (QEvdevGamepadDevice *dev = device(deviceId)) |
| return dev->configureAxis(axis); |
| return false; |
| } |
| |
| bool QEvdevGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) |
| { |
| if (QEvdevGamepadDevice *dev = device(deviceId)) |
| return dev->setCancelConfigureButton(button); |
| return false; |
| } |
| |
| void QEvdevGamepadBackend::stop() |
| { |
| qCDebug(lcEGB) << "stop"; |
| qDeleteAll(m_devices); |
| m_devices.clear(); |
| } |
| |
| void QEvdevGamepadBackend::handleAddedDevice(const QString &device) |
| { |
| // This does not imply that an actual controller is connected. |
| // When connecting the wireless receiver 4 devices will show up right away. |
| // Therefore gamepadAdded() will be emitted only later, when something is read. |
| qCDebug(lcEGB) << "Connected device" << device; |
| m_devices.append(newDevice(device.toUtf8())); |
| } |
| |
| void QEvdevGamepadBackend::handleRemovedDevice(const QString &device) |
| { |
| qCDebug(lcEGB) << "Disconnected device" << device; |
| QByteArray dev = device.toUtf8(); |
| for (int i = 0; i < m_devices.count(); ++i) { |
| if (m_devices[i]->deviceName() == dev) { |
| delete m_devices[i]; |
| m_devices.removeAt(i); |
| break; |
| } |
| } |
| } |
| |
| QEvdevGamepadDevice::QEvdevGamepadDevice(const QByteArray &dev, QEvdevGamepadBackend *backend) |
| : m_dev(dev), |
| m_backend(backend), |
| m_fd(-1), |
| m_productId(0), |
| m_needsConfigure(true), |
| m_notifier(0), |
| m_configureButton(QGamepadManager::ButtonInvalid), |
| m_configureAxis(QGamepadManager::AxisInvalid) |
| { |
| openDevice(dev); |
| } |
| |
| QEvdevGamepadDevice::~QEvdevGamepadDevice() |
| { |
| if (m_fd != -1) |
| QT_CLOSE(m_fd); |
| |
| if (m_productId) |
| emit m_backend->gamepadRemoved(m_productId); |
| } |
| |
| void QEvdevGamepadDevice::resetConfiguration() |
| { |
| m_axisMap.insert(ABS_X, EvdevAxisInfo(m_fd, ABS_X, -32768, 32767, QGamepadManager::AxisLeftX)); |
| m_axisMap.insert(ABS_Y, EvdevAxisInfo(m_fd, ABS_Y, -32768, 32767, QGamepadManager::AxisLeftY)); |
| m_axisMap.insert(ABS_RX, EvdevAxisInfo(m_fd, ABS_RX, -32768, 32767, QGamepadManager::AxisRightX)); |
| m_axisMap.insert(ABS_RY, EvdevAxisInfo(m_fd, ABS_RY, -32768, 32767, QGamepadManager::AxisRightY)); |
| m_axisMap.insert(ABS_Z, EvdevAxisInfo(m_fd, ABS_Z, 0, 255)); |
| m_axisMap[ABS_Z].gamepadMinButton = QGamepadManager::ButtonL2; |
| m_axisMap[ABS_Z].gamepadMaxButton = QGamepadManager::ButtonL2; |
| |
| m_axisMap.insert(ABS_RZ, EvdevAxisInfo(m_fd, ABS_RZ, 0, 255)); |
| m_axisMap[ABS_RZ].gamepadMinButton = QGamepadManager::ButtonR2; |
| m_axisMap[ABS_RZ].gamepadMaxButton = QGamepadManager::ButtonR2; |
| |
| m_axisMap.insert(ABS_HAT0X, EvdevAxisInfo(m_fd, ABS_HAT0X, -1, 1)); |
| m_axisMap[ABS_HAT0X].gamepadMinButton = QGamepadManager::ButtonLeft; |
| m_axisMap[ABS_HAT0X].gamepadMaxButton = QGamepadManager::ButtonRight; |
| |
| m_axisMap.insert(ABS_HAT0Y, EvdevAxisInfo(m_fd, ABS_HAT0Y, -1, 1)); |
| m_axisMap[ABS_HAT0Y].gamepadMinButton = QGamepadManager::ButtonUp; |
| m_axisMap[ABS_HAT0Y].gamepadMaxButton = QGamepadManager::ButtonDown; |
| |
| m_buttonsMap[BTN_START] = QGamepadManager::ButtonStart; |
| m_buttonsMap[BTN_SELECT] = QGamepadManager::ButtonSelect; |
| m_buttonsMap[BTN_MODE] = QGamepadManager::ButtonGuide; |
| m_buttonsMap[BTN_X] = QGamepadManager::ButtonX; |
| m_buttonsMap[BTN_Y] = QGamepadManager::ButtonY; |
| m_buttonsMap[BTN_A] = QGamepadManager::ButtonA; |
| m_buttonsMap[BTN_B] = QGamepadManager::ButtonB; |
| m_buttonsMap[BTN_TL] = QGamepadManager::ButtonL1; |
| m_buttonsMap[BTN_TR] = QGamepadManager::ButtonR1; |
| m_buttonsMap[BTN_TL2] = QGamepadManager::ButtonL2; |
| m_buttonsMap[BTN_TR2] = QGamepadManager::ButtonR2; |
| m_buttonsMap[BTN_THUMB] = m_buttonsMap[BTN_THUMBL] = QGamepadManager::ButtonL3; |
| m_buttonsMap[BTN_THUMBR] = QGamepadManager::ButtonR3; |
| m_buttonsMap[BTN_TRIGGER_HAPPY1] = QGamepadManager::ButtonLeft; |
| m_buttonsMap[BTN_TRIGGER_HAPPY2] = QGamepadManager::ButtonRight; |
| m_buttonsMap[BTN_TRIGGER_HAPPY3] = QGamepadManager::ButtonUp; |
| m_buttonsMap[BTN_TRIGGER_HAPPY4] = QGamepadManager::ButtonDown; |
| |
| if (m_productId) |
| m_backend->saveSettings(m_productId, QVariant()); |
| } |
| |
| bool QEvdevGamepadDevice::isConfigurationNeeded() |
| { |
| return m_needsConfigure; |
| } |
| |
| bool QEvdevGamepadDevice::configureButton(QGamepadManager::GamepadButton button) |
| { |
| m_configureButton = button; |
| return true; |
| } |
| |
| bool QEvdevGamepadDevice::configureAxis(QGamepadManager::GamepadAxis axis) |
| { |
| m_configureAxis = axis; |
| return true; |
| } |
| |
| bool QEvdevGamepadDevice::setCancelConfigureButton(QGamepadManager::GamepadButton button) |
| { |
| m_configureCancelButton = button; |
| return true; |
| } |
| |
| bool QEvdevGamepadDevice::openDevice(const QByteArray &dev) |
| { |
| m_fd = QT_OPEN(dev.constData(), O_RDONLY | O_NDELAY, 0); |
| |
| if (m_fd >= 0) { |
| m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); |
| connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readData())); |
| qCDebug(lcEGB) << "Successfully opened" << dev; |
| } else { |
| qErrnoWarning(errno, "Gamepad: Cannot open input device %s", qPrintable(dev)); |
| return false; |
| } |
| |
| input_id id; |
| if (ioctl(m_fd, EVIOCGID, &id) >= 0) { |
| m_productId = id.product; |
| |
| QVariant settings = m_backend->readSettings(m_productId); |
| if (!settings.isNull()) { |
| m_needsConfigure = false; |
| QVariantMap data = settings.toMap()[QLatin1String("axes")].toMap(); |
| for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) { |
| const int key = it.key().toInt(); |
| m_axisMap[key].restoreSavedData(m_fd, key, it.value().toMap()); |
| } |
| |
| data = settings.toMap()[QLatin1String("buttons")].toMap(); |
| for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) |
| m_buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt()); |
| } |
| |
| emit m_backend->gamepadAdded(m_productId); |
| |
| // same as libevdev::libevdev_set_fd() in libevdev.c |
| char buffer[256]; |
| memset(buffer, 0, sizeof(buffer)); |
| if (ioctl(m_fd, EVIOCGNAME(sizeof(buffer) - 1), buffer) >= 0) |
| emit m_backend->gamepadNamed(m_productId, QString::fromUtf8(buffer)); |
| |
| } else { |
| QT_CLOSE(m_fd); |
| m_fd = -1; |
| return false; |
| } |
| |
| if (m_needsConfigure) |
| resetConfiguration(); |
| |
| qCDebug(lcEGB) << "Axis limits:" << m_axisMap; |
| |
| return true; |
| } |
| |
| QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::EvdevAxisInfo &axisInfo) |
| { |
| dbg.nospace() << "AxisInfo(min=" << axisInfo.minValue << ", max=" << axisInfo.maxValue << ")"; |
| return dbg.space(); |
| } |
| |
| void QEvdevGamepadDevice::readData() |
| { |
| input_event buffer[32]; |
| int events = 0, n = 0; |
| for (; ;) { |
| events = QT_READ(m_fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n); |
| if (events <= 0) |
| goto err; |
| n += events; |
| if (n % sizeof(::input_event) == 0) |
| break; |
| } |
| |
| n /= sizeof(::input_event); |
| |
| for (int i = 0; i < n; ++i) |
| processInputEvent(&buffer[i]); |
| |
| return; |
| |
| err: |
| if (!events) { |
| qWarning("Gamepad: Got EOF from input device"); |
| return; |
| } else if (events < 0) { |
| if (errno != EINTR && errno != EAGAIN) { |
| qErrnoWarning(errno, "Gamepad: Could not read from input device"); |
| if (errno == ENODEV) { // device got disconnected -> stop reading |
| delete m_notifier; |
| m_notifier = 0; |
| QT_CLOSE(m_fd); |
| m_fd = -1; |
| } |
| } |
| } |
| } |
| |
| void QEvdevGamepadDevice::saveData() |
| { |
| if (!m_productId) |
| return ; |
| |
| QVariantMap settings, data; |
| for (AxisMap::const_iterator it = m_axisMap.begin(); it != m_axisMap.end(); ++it) |
| data[QString::number(it.key())] = it.value().dataToSave(); |
| settings[QLatin1String("axes")] = data; |
| |
| data.clear(); |
| for (ButtonsMap::const_iterator it = m_buttonsMap.begin(); it != m_buttonsMap.end(); ++it) |
| data[QString::number(it.key())] = it.value(); |
| |
| settings[QLatin1String("buttons")] = data; |
| |
| m_backend->saveSettings(m_productId, settings); |
| } |
| |
| void QEvdevGamepadDevice::processInputEvent(input_event *e) |
| { |
| if (e->type == EV_KEY) { |
| QGamepadManager::GamepadButton btn = QGamepadManager::ButtonInvalid; |
| ButtonsMap::const_iterator it = m_buttonsMap.find(e->code); |
| if (it != m_buttonsMap.end()) |
| btn = it.value(); |
| |
| const bool pressed = e->value; |
| if (m_configureCancelButton != QGamepadManager::ButtonInvalid && |
| m_configureCancelButton != m_configureButton && |
| !pressed && btn == m_configureCancelButton && |
| (m_configureButton != QGamepadManager::ButtonInvalid || |
| m_configureAxis != QGamepadManager::AxisInvalid)) { |
| m_configureButton = QGamepadManager::ButtonInvalid; |
| m_configureAxis = QGamepadManager::AxisInvalid; |
| emit m_backend->configurationCanceled(m_productId); |
| return; |
| } |
| |
| if (!pressed && m_configureButton != QGamepadManager::ButtonInvalid) { |
| m_buttonsMap[e->code] = m_configureButton; |
| QGamepadManager::GamepadButton but = m_configureButton; |
| m_configureButton = QGamepadManager::ButtonInvalid; |
| saveData(); |
| emit m_backend->buttonConfigured(m_productId, but); |
| } |
| |
| it = m_buttonsMap.find(e->code); |
| if (it != m_buttonsMap.end()) |
| btn = it.value(); |
| |
| if (btn != QGamepadManager::ButtonInvalid) { |
| if (pressed) |
| emit m_backend->gamepadButtonPressed(m_productId, btn, 1.0); |
| else |
| emit m_backend->gamepadButtonReleased(m_productId, btn); |
| } |
| } else if (e->type == EV_ABS) { |
| if (m_configureAxis != QGamepadManager::AxisInvalid) { |
| EvdevAxisInfo inf(m_fd, e->code, -32768, 32767, m_configureAxis); |
| if (std::abs(inf.normalized(e->value)) == 1) { |
| m_axisMap.insert(e->code, EvdevAxisInfo(m_fd, e->code, -32768, 32767, m_configureAxis)); |
| |
| QGamepadManager::GamepadAxis axis = m_configureAxis; |
| m_configureAxis = QGamepadManager::AxisInvalid; |
| |
| saveData(); |
| emit m_backend->axisConfigured(m_productId, axis); |
| } else { |
| return; |
| } |
| } |
| |
| AxisMap::iterator it = m_axisMap.find(e->code); |
| if (m_configureButton != QGamepadManager::ButtonInvalid) { |
| EvdevAxisInfo axisInfo; |
| if (it != m_axisMap.end()) |
| axisInfo = it.value(); |
| else |
| axisInfo = EvdevAxisInfo(m_fd, e->code); |
| |
| axisInfo.gamepadAxis = QGamepadManager::AxisInvalid; |
| |
| bool save = false; |
| if (e->value == axisInfo.minValue) { |
| axisInfo.gamepadMinButton = m_configureButton; |
| if (axisInfo.gamepadMaxButton != QGamepadManager::ButtonInvalid) |
| axisInfo.gamepadMaxButton = m_configureButton; |
| save = true; |
| } else if (e->value == axisInfo.maxValue) { |
| axisInfo.gamepadMaxButton = m_configureButton; |
| if (axisInfo.gamepadMinButton != QGamepadManager::ButtonInvalid) |
| axisInfo.gamepadMinButton = m_configureButton; |
| save = true; |
| } |
| |
| if (save) { |
| QGamepadManager::GamepadButton but = m_configureButton; |
| m_configureButton = QGamepadManager::ButtonInvalid; |
| if (but == QGamepadManager::ButtonL2 || but == QGamepadManager::ButtonR2) |
| m_axisMap.insert(e->code, axisInfo); |
| saveData(); |
| emit m_backend->buttonConfigured(m_productId, but); |
| } |
| } |
| |
| it = m_axisMap.find(e->code); |
| if (it == m_axisMap.end()) |
| return; |
| |
| EvdevAxisInfo &info = it.value(); |
| |
| double val = info.normalized(e->value); |
| |
| if (info.gamepadAxis != QGamepadManager::AxisInvalid) |
| emit m_backend->gamepadAxisMoved(m_productId, info.gamepadAxis, val); |
| |
| if (info.gamepadMaxButton == info.gamepadMinButton && |
| info.gamepadMaxButton != QGamepadManager::ButtonInvalid) { |
| if (val) |
| emit m_backend->gamepadButtonPressed(m_productId, info.gamepadMaxButton, std::abs(val)); |
| else |
| emit m_backend->gamepadButtonReleased(m_productId, info.gamepadMaxButton); |
| } else { |
| if (info.gamepadMaxButton != QGamepadManager::ButtonInvalid |
| && val == 1.0) { |
| info.gamepadLastButton = info.gamepadMaxButton; |
| emit m_backend->gamepadButtonPressed(m_productId, info.gamepadMaxButton, val); |
| } else if (info.gamepadMinButton != QGamepadManager::ButtonInvalid |
| && val == -1.0) { |
| info.gamepadLastButton = info.gamepadMinButton; |
| emit m_backend->gamepadButtonPressed(m_productId, info.gamepadMinButton, -val); |
| } else if (!val && info.gamepadLastButton != QGamepadManager::ButtonInvalid) { |
| QGamepadManager::GamepadButton but = info.gamepadLastButton; |
| info.gamepadLastButton = QGamepadManager::ButtonInvalid; |
| emit m_backend->gamepadButtonReleased(m_productId, but); |
| } |
| } |
| } |
| } |
| |
| QT_END_NAMESPACE |