diff options
author | BogDan Vatra <bogdan@kdab.com> | 2015-10-26 12:38:33 +0200 |
---|---|---|
committer | Andy Nichols <andy.nichols@theqtcompany.com> | 2015-10-26 12:40:37 +0000 |
commit | c32e995bc79035e600893e91fdcec984f8293c75 (patch) | |
tree | 6de9a857b695fedfb3a52c5e4e34334759a09acc | |
parent | 00871e531f7fb44feb3dac7abfe4eff0af5dfada (diff) |
Enable the usage of unknown gamepads.
Allowing the user to configure every button & axis of (s)his gamepad,
will enable the usage of any gamepad.
Currently only evdev backed has this feature.
Change-Id: I6e0692980e77b448d20d5ba818b787a7b89e7257
Reviewed-by: Andy Nichols <andy.nichols@theqtcompany.com>
-rw-r--r-- | examples/configureButtons/configureButtons.pro | 19 | ||||
-rw-r--r-- | examples/configureButtons/deployment.pri | 14 | ||||
-rw-r--r-- | examples/configureButtons/main.cpp | 48 | ||||
-rw-r--r-- | examples/configureButtons/main.qml | 616 | ||||
-rw-r--r-- | examples/configureButtons/qml.qrc | 5 | ||||
-rw-r--r-- | examples/examples.pro | 3 | ||||
-rw-r--r-- | examples/keyNavigation/qml/main.qml | 7 | ||||
-rw-r--r-- | examples/mouseItem/qml/main.qml | 7 | ||||
-rw-r--r-- | examples/quickGamepad/qml/main.qml | 7 | ||||
-rw-r--r-- | src/gamepad/qgamepadbackend.cpp | 56 | ||||
-rw-r--r-- | src/gamepad/qgamepadbackend_p.h | 35 | ||||
-rw-r--r-- | src/gamepad/qgamepadmanager.cpp | 33 | ||||
-rw-r--r-- | src/gamepad/qgamepadmanager.h | 11 | ||||
-rw-r--r-- | src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp | 494 | ||||
-rw-r--r-- | src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h | 52 |
15 files changed, 1189 insertions, 218 deletions
diff --git a/examples/configureButtons/configureButtons.pro b/examples/configureButtons/configureButtons.pro new file mode 100644 index 0000000..480f8fb --- /dev/null +++ b/examples/configureButtons/configureButtons.pro @@ -0,0 +1,19 @@ +TEMPLATE = app + +QT += qml quick gamepad + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Default rules for deployment. +include(deployment.pri) + +DISTFILES += \ + android/AndroidManifest.xml + +ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + diff --git a/examples/configureButtons/deployment.pri b/examples/configureButtons/deployment.pri new file mode 100644 index 0000000..c89af25 --- /dev/null +++ b/examples/configureButtons/deployment.pri @@ -0,0 +1,14 @@ +unix:!android { + isEmpty(target.path) { + qnx { + target.path = /tmp/$${TARGET}/bin + } else { + target.path = /opt/$${TARGET}/bin + } + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) + diff --git a/examples/configureButtons/main.cpp b/examples/configureButtons/main.cpp new file mode 100644 index 0000000..4ee0220 --- /dev/null +++ b/examples/configureButtons/main.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org> +** +** 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 <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} + diff --git a/examples/configureButtons/main.qml b/examples/configureButtons/main.qml new file mode 100644 index 0000000..31d1cd7 --- /dev/null +++ b/examples/configureButtons/main.qml @@ -0,0 +1,616 @@ +/**************************************************************************** +** +** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org> +** +** 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$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.0 +import QtGamepad 1.0 + + +ApplicationWindow { + visible: true + title: qsTr("Configure gamepad") + + Component.onCompleted: { + if (Qt.platform.os === "android") + visibility = Window.Maximized + } + + property Button checkedButton: null + function pressButton(button) + { + if (checkedButton !== null && button !== checkedButton) + checkedButton.checked = false; + checkedButton = button + } + + Gamepad { + id: gamepad + deviceId: GamepadManager.connectedGamepads.length > 0 ? GamepadManager.connectedGamepads[0] : -1 + onDeviceIdChanged: GamepadManager.setCancelConfigureButton(deviceId, GamepadManager.ButtonStart); + } + + Connections { + target: GamepadManager + onButtonConfigured: pressButton(null) + onAxisConfigured: pressButton(null) + onConfigurationCanceled: pressButton(null) + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Connected gamepads") + } + ComboBox { + model: GamepadManager.connectedGamepads + onCurrentIndexChanged: gamepad.deviceId[GamepadManager.connectedGamepads[currentIndex]] + } + } + + Text { + Layout.fillWidth: true + text: qsTr("Start button cancel's current configuration") + } + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + GroupBox { + title: qsTr("Configure Gamepad Buttons") + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + anchors.fill: parent + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonA") + horizontalAlignment: Text.AlignRight + } + + Text { + text: gamepad.buttonA ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonA); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonB") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonB ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonB); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonX") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonX ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonX); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonY") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonY ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonY); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonStart") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonStart ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonStart); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("ButtonSelect") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonSelect ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonSelect); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button L1") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonL1 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonL1); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button R1") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonR1 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonR1); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button L2") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonL2 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonL2); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button R2") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonR2 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonR2); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button L3") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonL3 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonL3); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button R3") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonR3 ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonR3); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Up") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonUp ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonUp); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Down") + horizontalAlignment: Text.AlignRight + } + Text { + text: gamepad.buttonDown ? qsTr("DOWN") : qsTr("UP") + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonDown ); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Left") + horizontalAlignment: Text.AlignRight + } + Text { + Layout.fillWidth: true + text: gamepad.buttonLeft ? qsTr("DOWN") : qsTr("UP") + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonLeft); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Right") + horizontalAlignment: Text.AlignRight + } + Text { + Layout.fillWidth: true + text: gamepad.buttonRight ? qsTr("DOWN") : qsTr("UP") + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonRight); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Center") + horizontalAlignment: Text.AlignRight + } + Text { + Layout.fillWidth: true + text: gamepad.buttonCenter ? qsTr("DOWN") : qsTr("UP") + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonCenter); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("Button Guide") + horizontalAlignment: Text.AlignRight + } + Text { + Layout.fillWidth: true + text: gamepad.buttonGuide ? qsTr("DOWN") : qsTr("UP") + horizontalAlignment: Text.AlignHCenter + } + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureButton(gamepad.deviceId, GamepadManager.ButtonGuide); + } + } + } + } + } + GroupBox { + title: qsTr("Gamepad Axies") + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + anchors.fill: parent + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("AxisLeftX") + horizontalAlignment: Text.AlignRight + } + + Text { + text: gamepad.axisLeftX + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureAxis(gamepad.deviceId, GamepadManager.AxisLeftX); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("AxisLeftY") + horizontalAlignment: Text.AlignRight + } + + Text { + text: gamepad.axisLeftY + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureAxis(gamepad.deviceId, GamepadManager.AxisLeftY); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("AxisRightX") + horizontalAlignment: Text.AlignRight + } + + Text { + text: gamepad.axisRightX + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureAxis(gamepad.deviceId, GamepadManager.AxisRightX); + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("AxisRightY") + horizontalAlignment: Text.AlignRight + } + + Text { + text: gamepad.axisRightY + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Button { + text: qsTr("Configure") + checkable: true + enabled: !checked + onCheckedChanged: { + pressButton(this); + if (checked) + GamepadManager.configureAxis(gamepad.deviceId, GamepadManager.AxisRightY); + } + } + } + + Item { + Layout.fillHeight: true + } + } + } + } + } +} diff --git a/examples/configureButtons/qml.qrc b/examples/configureButtons/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/examples/configureButtons/qml.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>main.qml</file> + </qresource> +</RCC> diff --git a/examples/examples.pro b/examples/examples.pro index 4461e4d..e5acdd9 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -5,5 +5,6 @@ SUBDIRS += simple \ qtHaveModule(quick) { SUBDIRS += quickGamepad \ - keyNavigation + keyNavigation \ + configureButtons } diff --git a/examples/keyNavigation/qml/main.qml b/examples/keyNavigation/qml/main.qml index 2c4106c..d58bfc5 100644 --- a/examples/keyNavigation/qml/main.qml +++ b/examples/keyNavigation/qml/main.qml @@ -51,7 +51,12 @@ ApplicationWindow { Gamepad { id: gamepad1 - index: 0 + deviceId: GamepadManager.connectedGamepads.length > 0 ? GamepadManager.connectedGamepads[0] : -1 + } + + Connections { + target: GamepadManager + onGamepadConnected: gamepad1.deviceId = deviceId } GamepadKeyNavigation { diff --git a/examples/mouseItem/qml/main.qml b/examples/mouseItem/qml/main.qml index 4ab1f8a..4c05e90 100644 --- a/examples/mouseItem/qml/main.qml +++ b/examples/mouseItem/qml/main.qml @@ -49,9 +49,14 @@ ApplicationWindow { height: 480 title: qsTr("Hello World") + Connections { + target: GamepadManager + onGamepadConnected: gamepad1.deviceId = deviceId + } + Gamepad { id: gamepad1 - index: 0 + deviceId: GamepadManager.connectedGamepads.length > 0 ? GamepadManager.connectedGamepads[0] : -1 onButtonAChanged: { if (value == true) { diff --git a/examples/quickGamepad/qml/main.qml b/examples/quickGamepad/qml/main.qml index 10b6674..049e72b 100644 --- a/examples/quickGamepad/qml/main.qml +++ b/examples/quickGamepad/qml/main.qml @@ -226,10 +226,13 @@ ApplicationWindow { } - + Connections { + target: GamepadManager + onGamepadConnected: gamepad.deviceId = deviceId + } Gamepad { id: gamepad + deviceId: GamepadManager.connectedGamepads.length > 0 ? GamepadManager.connectedGamepads[0] : -1 } - } diff --git a/src/gamepad/qgamepadbackend.cpp b/src/gamepad/qgamepadbackend.cpp index 812a940..0f11800 100644 --- a/src/gamepad/qgamepadbackend.cpp +++ b/src/gamepad/qgamepadbackend.cpp @@ -35,6 +35,7 @@ ****************************************************************************/ #include "qgamepadbackend_p.h" +#include <QSettings> QT_BEGIN_NAMESPACE @@ -43,6 +44,61 @@ QGamepadBackend::QGamepadBackend(QObject *parent) : { } +bool QGamepadBackend::isConfigurationNeeded(int /*deviceId*/) +{ + return false; +} + +void QGamepadBackend::resetConfiguration(int /*deviceId*/) +{ +} + +bool QGamepadBackend::configureButton(int /*deviceId*/, QGamepadManager::GamepadButton /*button*/) +{ + return false; +} + +bool QGamepadBackend::configureAxis(int /*deviceId*/, QGamepadManager::GamepadAxis /*axis*/) +{ + return false; +} + +bool QGamepadBackend::setCancelConfigureButton(int /*deviceId*/, QGamepadManager::GamepadButton /*button*/) +{ + return false; +} + +void QGamepadBackend::setSettingsFile(const QString &file) +{ + m_settingsFilePath = file; +} + +void QGamepadBackend::saveSettings(int productId, const QVariant &value) +{ + QScopedPointer<QSettings> s; + if (m_settingsFilePath.isNull()) + s.reset(new QSettings()); + else + s.reset(new QSettings(m_settingsFilePath)); + s->beginGroup(QString::fromLatin1("___gamepad_saved_states")); + QString key = QString::fromLatin1("id_%1").arg(productId); + if (value.isNull()) + s->remove(key); + else + s->setValue(key, value); +} + +QVariant QGamepadBackend::readSettings(int productId) +{ + QScopedPointer<QSettings> s; + if (m_settingsFilePath.isNull()) + s.reset(new QSettings()); + else + s.reset(new QSettings(m_settingsFilePath)); + s->beginGroup(QString::fromLatin1("___gamepad_saved_states")); + return s->value(QString::fromLatin1("id_%1").arg(productId)); +} + bool QGamepadBackend::start() { return true; diff --git a/src/gamepad/qgamepadbackend_p.h b/src/gamepad/qgamepadbackend_p.h index 60f01dc..e170511 100644 --- a/src/gamepad/qgamepadbackend_p.h +++ b/src/gamepad/qgamepadbackend_p.h @@ -48,18 +48,53 @@ class Q_GAMEPAD_EXPORT QGamepadBackend : public QObject { Q_OBJECT public: + template <typename T> + struct AxisInfo { + AxisInfo(T minValue = 0, T maxValue = 1, QGamepadManager::GamepadAxis gamepadAxis = QGamepadManager::AxisInvalid) + : minValue(minValue) + , maxValue(maxValue) + , gamepadAxis(gamepadAxis) + {} + + virtual double normalized(T value) const + { + if (minValue >= 0) + return 2.0 * double(value - double(maxValue - minValue) / 2.0) / double(maxValue - minValue); + else + return 2.0 * double(value - minValue) / double(maxValue - minValue) - 1.0; + } + T minValue; + T maxValue; + QGamepadManager::GamepadAxis gamepadAxis; + }; + +public: explicit QGamepadBackend(QObject *parent = 0); + virtual bool isConfigurationNeeded(int deviceId); + virtual void resetConfiguration(int deviceId); + virtual bool configureButton(int deviceId, QGamepadManager::GamepadButton button); + virtual bool configureAxis(int deviceId, QGamepadManager::GamepadAxis axis); + virtual bool setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button); + virtual void setSettingsFile(const QString &file); + virtual void saveSettings(int productId, const QVariant &value); + virtual QVariant readSettings(int productId); public slots: virtual bool start(); virtual void stop(); signals: + void buttonConfigured(int deviceId, QGamepadManager::GamepadButton button); + void axisConfigured(int deviceId, QGamepadManager::GamepadAxis axis); + void configurationCanceled(int deviceId); void gamepadAdded(int deviceId); void gamepadRemoved(int deviceId); void gamepadAxisMoved(int deviceId, QGamepadManager::GamepadAxis axis, double value); void gamepadButtonPressed(int deviceId, QGamepadManager::GamepadButton button, double value); void gamepadButtonReleased(int deviceId, QGamepadManager::GamepadButton button); + +protected: + QString m_settingsFilePath; }; QT_END_NAMESPACE diff --git a/src/gamepad/qgamepadmanager.cpp b/src/gamepad/qgamepadmanager.cpp index 91ca70f..2c737f6 100644 --- a/src/gamepad/qgamepadmanager.cpp +++ b/src/gamepad/qgamepadmanager.cpp @@ -58,6 +58,9 @@ QGamepadManager::QGamepadManager() : connect(m_gamepadBackend, SIGNAL(gamepadAxisMoved(int,QGamepadManager::GamepadAxis,double)), this, SLOT(forwardGamepadAxisEvent(int,QGamepadManager::GamepadAxis,double))); connect(m_gamepadBackend, SIGNAL(gamepadButtonPressed(int,QGamepadManager::GamepadButton,double)), this, SLOT(forwardGamepadButtonPressEvent(int,QGamepadManager::GamepadButton,double))); connect(m_gamepadBackend, SIGNAL(gamepadButtonReleased(int,QGamepadManager::GamepadButton)), this, SLOT(forwardGamepadButtonReleaseEvent(int,QGamepadManager::GamepadButton))); + connect(m_gamepadBackend, &QGamepadBackend::buttonConfigured, this, &QGamepadManager::buttonConfigured); + connect(m_gamepadBackend, &QGamepadBackend::axisConfigured, this, &QGamepadManager::axisConfigured); + connect(m_gamepadBackend, &QGamepadBackend::configurationCanceled, this, &QGamepadManager::configurationCanceled); if (!m_gamepadBackend->start()) qCWarning(gp) << "Failed to start gamepad backend"; @@ -107,6 +110,36 @@ const QList<int> QGamepadManager::connectedGamepads() const return m_connectedGamepads.toList(); } +bool QGamepadManager::isConfigurationNeeded(int deviceId) +{ + return m_gamepadBackend->isConfigurationNeeded(deviceId); +} + +bool QGamepadManager::configureButton(int deviceId, QGamepadManager::GamepadButton button) +{ + return m_gamepadBackend->configureButton(deviceId, button); +} + +bool QGamepadManager::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) +{ + return m_gamepadBackend->configureAxis(deviceId, axis); +} + +bool QGamepadManager::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) +{ + return m_gamepadBackend->setCancelConfigureButton(deviceId, button); +} + +void QGamepadManager::resetConfiguration(int deviceId) +{ + m_gamepadBackend->resetConfiguration(deviceId); +} + +void QGamepadManager::setSettingsFile(const QString &file) +{ + m_gamepadBackend->setSettingsFile(file); +} + void QGamepadManager::forwardGamepadConnected(int deviceId) { //qDebug() << "gamepad connected: " << index; diff --git a/src/gamepad/qgamepadmanager.h b/src/gamepad/qgamepadmanager.h index 6502ff6..4d52191 100644 --- a/src/gamepad/qgamepadmanager.h +++ b/src/gamepad/qgamepadmanager.h @@ -90,6 +90,14 @@ public: bool isGamepadConnected(int deviceId); const QList<int> connectedGamepads() const; +public Q_SLOTS: + bool isConfigurationNeeded(int deviceId); + bool configureButton(int deviceId, GamepadButton button); + bool configureAxis(int deviceId, GamepadAxis axis); + bool setCancelConfigureButton(int deviceId, GamepadButton button); + void resetConfiguration(int deviceId); + void setSettingsFile(const QString &file); + Q_SIGNALS: void connectedGamepadsChanged(); void gamepadConnected(int deviceId); @@ -97,6 +105,9 @@ Q_SIGNALS: void gamepadAxisEvent(int deviceId, QGamepadManager::GamepadAxis axis, double value); void gamepadButtonPressEvent(int deviceId, QGamepadManager::GamepadButton button, double value); void gamepadButtonReleaseEvent(int deviceId, QGamepadManager::GamepadButton button); + void buttonConfigured(int deviceId, QGamepadManager::GamepadButton button); + void axisConfigured(int deviceId, QGamepadManager::GamepadAxis axis); + void configurationCanceled(int deviceId); private Q_SLOTS: void forwardGamepadConnected(int deviceId); diff --git a/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp b/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp index 6319441..17bd74e 100644 --- a/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp +++ b/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp @@ -45,8 +45,59 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcEGB, "qt.gamepad") +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 = fabs(absInfo.flat / double(maxValue - minValue)); + } +} + +void QEvdevGamepadDevice::EvdevAxisInfo::restoreSavedData(int fd, int abs, const QVariantMap &value) +{ + gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1Literal("axis")].toInt()); + gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1Literal("minButton")].toInt()); + gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1Literal("maxButton")].toInt()); + setAbsInfo(fd, abs); +} + +QVariantMap QEvdevGamepadDevice::EvdevAxisInfo::dataToSave() const +{ + QVariantMap data; + data[QLatin1Literal("axis")] = gamepadAxis; + data[QLatin1Literal("minButton")] = gamepadMinButton; + data[QLatin1Literal("maxButton")] = gamepadMaxButton; + return data; +} + QEvdevGamepadBackend::QEvdevGamepadBackend() - : m_nextIndex(0) { } @@ -83,17 +134,46 @@ QEvdevGamepadDevice *QEvdevGamepadBackend::newDevice(const QByteArray &device) return new QEvdevGamepadDevice(device, this); } -// To be called only when it is sure that there is a controller on-line. -int QEvdevGamepadBackend::idForDevice(const QByteArray &device) +QEvdevGamepadDevice *QEvdevGamepadBackend::device(int deviceId) { - int index; - if (m_devIndex.contains(device)) { - index = m_devIndex[device]; - } else { - index = m_nextIndex++; - m_devIndex[device] = index; - } - return index; + foreach (QEvdevGamepadDevice *device, 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() @@ -128,11 +208,12 @@ void QEvdevGamepadBackend::handleRemovedDevice(const QString &device) QEvdevGamepadDevice::QEvdevGamepadDevice(const QByteArray &dev, QEvdevGamepadBackend *backend) : m_dev(dev), m_backend(backend), - m_index(-1), m_fd(-1), + m_productId(0), + m_needsConfigure(true), m_notifier(0), - m_prevYHatButton(QGamepadManager::ButtonInvalid), - m_prevXHatButton(QGamepadManager::ButtonInvalid) + m_configureButton(QGamepadManager::ButtonInvalid), + m_configureAxis(QGamepadManager::AxisInvalid) { openDevice(dev); } @@ -141,8 +222,75 @@ QEvdevGamepadDevice::~QEvdevGamepadDevice() { if (m_fd != -1) QT_CLOSE(m_fd); - if (m_index >= 0) - emit m_backend->gamepadRemoved(m_index); + if (m_fd >= 0) + emit m_backend->gamepadRemoved(m_fd); +} + +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) @@ -152,41 +300,41 @@ bool QEvdevGamepadDevice::openDevice(const QByteArray &dev) if (m_fd >= 0) { m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readData())); + emit m_backend->gamepadAdded(m_fd); qCDebug(lcEGB) << "Successfully opened" << dev; } else { qErrnoWarning(errno, "Gamepad: Cannot open input device %s", qPrintable(dev)); return false; } - // Defaults. To be replaced with queried values below. - m_axisInfo.insert(ABS_X, AxisInfo(-32768, 32767, 0)); - m_axisInfo.insert(ABS_Y, AxisInfo(-32768, 32767, 0)); - m_axisInfo.insert(ABS_RX, AxisInfo(-32768, 32767, 0)); - m_axisInfo.insert(ABS_RY, AxisInfo(-32768, 32767, 0)); - m_axisInfo.insert(ABS_Z, AxisInfo(0, 255, 0)); - m_axisInfo.insert(ABS_RZ, AxisInfo(0, 255, 0)); + input_id id; + if (ioctl(m_fd, EVIOCGID, &id) >= 0) + m_productId = id.product; + + if (m_productId) { + QVariant settings = m_backend->readSettings(m_productId); + if (!settings.isNull()) { + m_needsConfigure = false; + QVariantMap data = settings.toMap()[QLatin1Literal("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()); + } - input_absinfo absInfo; - memset(&absInfo, 0, sizeof(input_absinfo)); - if (ioctl(m_fd, EVIOCGABS(ABS_X), &absInfo) >= 0) - m_axisInfo.insert(ABS_X, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - if (ioctl(m_fd, EVIOCGABS(ABS_Y), &absInfo) >= 0) - m_axisInfo.insert(ABS_Y, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - if (ioctl(m_fd, EVIOCGABS(ABS_RX), &absInfo) >= 0) - m_axisInfo.insert(ABS_RX, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - if (ioctl(m_fd, EVIOCGABS(ABS_RY), &absInfo) >= 0) - m_axisInfo.insert(ABS_RY, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - if (ioctl(m_fd, EVIOCGABS(ABS_Z), &absInfo) >= 0) - m_axisInfo.insert(ABS_Z, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - if (ioctl(m_fd, EVIOCGABS(ABS_RZ), &absInfo) >= 0) - m_axisInfo.insert(ABS_RZ, AxisInfo(absInfo.minimum, absInfo.maximum, absInfo.flat)); - - qCDebug(lcEGB) << "Axis limits:" << m_axisInfo; + data = settings.toMap()[QLatin1Literal("buttons")].toMap(); + for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) + m_buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt()); + } + } + if (m_needsConfigure) + resetConfiguration(); + + qCDebug(lcEGB) << "Axis limits:" << m_axisMap; return true; } -QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::AxisInfo &axisInfo) +QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::EvdevAxisInfo &axisInfo) { dbg.nospace() << "AxisInfo(min=" << axisInfo.minValue << ", max=" << axisInfo.maxValue << ")"; return dbg.space(); @@ -229,185 +377,135 @@ err: } } -double QEvdevGamepadDevice::AxisInfo::normalized(int value) const +void QEvdevGamepadDevice::saveData() { - double ret = 0; - if (minValue >= 0) - ret = 2 * (value - double(maxValue - minValue) / 2) / double(maxValue - minValue); - else - ret = 2 * (value - minValue) / double(maxValue - minValue) - 1.0; - if (fabs(ret) <= fabs(flat)) - ret = 0; - return ret; + 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[QLatin1Literal("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[QLatin1Literal("buttons")] = data; + + m_backend->saveSettings(m_productId, settings); } void QEvdevGamepadDevice::processInputEvent(input_event *e) { - if (m_index < 0) { - m_index = m_backend->idForDevice(m_dev); - qCDebug(lcEGB) << "Adding gamepad" << m_dev << "with index" << m_index; - emit m_backend->gamepadAdded(m_index); - } - if (e->type == EV_KEY) { - const bool pressed = e->value; QGamepadManager::GamepadButton btn = QGamepadManager::ButtonInvalid; + ButtonsMap::const_iterator it = m_buttonsMap.find(e->code); + if (it != m_buttonsMap.end()) + btn = it.value(); - switch (e->code) { - case BTN_START: - btn = QGamepadManager::ButtonStart; - break; - case BTN_SELECT: - btn = QGamepadManager::ButtonSelect; - break; - case BTN_MODE: - btn = QGamepadManager::ButtonGuide; - break; - - case BTN_X: - btn = QGamepadManager::ButtonX; - break; - case BTN_Y: - btn = QGamepadManager::ButtonY; - break; - case BTN_A: - btn = QGamepadManager::ButtonA; - break; - case BTN_B: - btn = QGamepadManager::ButtonB; - break; - - case BTN_TL: - btn = QGamepadManager::ButtonL1; - break; - case BTN_TR: - btn = QGamepadManager::ButtonR1; - break; - - case BTN_TL2: - btn = QGamepadManager::ButtonL2; - break; - case BTN_TR2: - btn = QGamepadManager::ButtonR2; - break; - + 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_fd); + return; + } - case BTN_THUMB: // wireless - case BTN_THUMBL: // wired - btn = QGamepadManager::ButtonL3; - break; - case BTN_THUMBR: - btn = QGamepadManager::ButtonR3; - break; + 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_fd, but); + } - // Directional buttons for wireless - case BTN_TRIGGER_HAPPY1: - btn = QGamepadManager::ButtonLeft; - break; - case BTN_TRIGGER_HAPPY2: - btn = QGamepadManager::ButtonRight; - break; - case BTN_TRIGGER_HAPPY3: - btn = QGamepadManager::ButtonUp; - break; - case BTN_TRIGGER_HAPPY4: - btn = QGamepadManager::ButtonDown; - break; + it = m_buttonsMap.find(e->code); + if (it != m_buttonsMap.end()) + btn = it.value(); - default: - break; - } if (btn != QGamepadManager::ButtonInvalid) { if (pressed) - emit m_backend->gamepadButtonPressed(m_index, btn, 1.0); + emit m_backend->gamepadButtonPressed(m_fd, btn, 1.0); else - emit m_backend->gamepadButtonReleased(m_index, btn); + emit m_backend->gamepadButtonReleased(m_fd, btn); } } else if (e->type == EV_ABS) { - if (e->code == ABS_HAT0X || e->code == ABS_HAT0Y) { - //Special logic for digital direction buttons as it is treated as a hat axis - //with the wired controller. - double value = m_axisInfo.value(e->code).normalized(e->value); - QGamepadManager::GamepadButton btn = QGamepadManager::ButtonInvalid; - bool pressed = false; - if (e->code == ABS_HAT0X) { - if (value == 1.0) { - btn = QGamepadManager::ButtonRight; - m_prevXHatButton = btn; - pressed = true; - } else if (value == -1.0) { - btn = QGamepadManager::ButtonLeft; - m_prevXHatButton = btn; - pressed = true; - } else { - //Release - btn = m_prevXHatButton; - m_prevXHatButton = QGamepadManager::ButtonInvalid; - pressed = false; - } - } else { - if (value == 1.0) { - btn = QGamepadManager::ButtonDown; - m_prevYHatButton = btn; - pressed = true; - } else if (value == -1.0) { - btn = QGamepadManager::ButtonUp; - m_prevYHatButton = btn; - pressed = true; - } else { - //Release - btn = m_prevYHatButton; - m_prevYHatButton = QGamepadManager::ButtonInvalid; - pressed = false; - } - } - if (btn != QGamepadManager::ButtonInvalid) { - if (pressed) - emit m_backend->gamepadButtonPressed(m_index, btn, 1.0); - else - emit m_backend->gamepadButtonReleased(m_index, btn); + if (m_configureAxis != QGamepadManager::AxisInvalid) { + 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_fd, axis); + } + + 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; } - } else { - QGamepadManager::GamepadAxis axis = QGamepadManager::AxisInvalid; - QGamepadManager::GamepadButton btn = QGamepadManager::ButtonInvalid; - - switch (e->code) { - case ABS_X: - axis = QGamepadManager::AxisLeftX; - break; - case ABS_Y: - axis = QGamepadManager::AxisLeftY; - break; - - case ABS_RX: - axis = QGamepadManager::AxisRightX; - break; - case ABS_RY: - axis = QGamepadManager::AxisRightY; - break; - - case ABS_Z: - btn = QGamepadManager::ButtonL2; - break; - case ABS_RZ: - btn = QGamepadManager::ButtonR2; - break; - - default: - break; + if (save) { + QGamepadManager::GamepadButton but = m_configureButton; + m_configureButton = QGamepadManager::ButtonInvalid; + saveData(); + emit m_backend->buttonConfigured(m_fd, but); } + } + + it = m_axisMap.find(e->code); + if (it == m_axisMap.end()) + return; + + EvdevAxisInfo &info = it.value(); - AxisInfo axisInfo = m_axisInfo.value(e->code); - const double value = axisInfo.normalized(e->value); + double val = info.normalized(e->value); - if (axis != QGamepadManager::AxisInvalid) { - emit m_backend->gamepadAxisMoved(m_index, axis, value); - } else if (btn != QGamepadManager::ButtonInvalid) { - if (!qFuzzyIsNull(value)) - emit m_backend->gamepadButtonPressed(m_index, btn, value); - else - emit m_backend->gamepadButtonReleased(m_index, btn); + if (info.gamepadAxis != QGamepadManager::AxisInvalid) + emit m_backend->gamepadAxisMoved(m_fd, info.gamepadAxis, val); + + if (info.gamepadMaxButton == info.gamepadMinButton && + info.gamepadMaxButton != QGamepadManager::ButtonInvalid) { + if (val) + emit m_backend->gamepadButtonPressed(m_fd, info.gamepadMaxButton, val); + else + emit m_backend->gamepadButtonReleased(m_fd, info.gamepadMaxButton); + } else { + if (info.gamepadMaxButton != QGamepadManager::ButtonInvalid + && val == 1.0) { + info.gamepadLastButton = info.gamepadMaxButton; + emit m_backend->gamepadButtonPressed(m_fd, info.gamepadMaxButton, val); + } else if (info.gamepadMinButton != QGamepadManager::ButtonInvalid + && val == -1.0) { + info.gamepadLastButton = info.gamepadMinButton; + emit m_backend->gamepadButtonPressed(m_fd, info.gamepadMinButton, val); + } else if (!val && info.gamepadLastButton != QGamepadManager::ButtonInvalid) { + QGamepadManager::GamepadButton but = info.gamepadLastButton; + info.gamepadLastButton = QGamepadManager::ButtonInvalid; + emit m_backend->gamepadButtonReleased(m_fd, but); } } } diff --git a/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h b/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h index e9e587c..bd7d387 100644 --- a/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h +++ b/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h @@ -58,35 +58,54 @@ public: QEvdevGamepadDevice(const QByteArray &dev, QEvdevGamepadBackend *backend); ~QEvdevGamepadDevice(); QByteArray deviceName() const { return m_dev; } + int deviceId() const { return m_fd; } + void resetConfiguration(); + bool isConfigurationNeeded(); + bool configureButton(QGamepadManager::GamepadButton button); + bool configureAxis(QGamepadManager::GamepadAxis axis); + bool setCancelConfigureButton(QGamepadManager::GamepadButton button); private slots: void readData(); private: + void saveData(); void processInputEvent(input_event *e); bool openDevice(const QByteArray &dev); QByteArray m_dev; QEvdevGamepadBackend *m_backend; - int m_index; int m_fd; + int m_productId; + bool m_needsConfigure; QSocketNotifier *m_notifier; - struct AxisInfo { - AxisInfo() : minValue(0), maxValue(1), flat(0) { } - AxisInfo(int minValue, int maxValue, int flat) : minValue(minValue), maxValue(maxValue), flat(fabs((maxValue-minValue) ? flat/double(maxValue-minValue) : 0)) { } + struct EvdevAxisInfo : public QGamepadBackend::AxisInfo<int> + { + EvdevAxisInfo(); + EvdevAxisInfo(int fd, quint16 abs, int minValue = 0, int maxValue = 1, QGamepadManager::GamepadAxis gamepadAxis = QGamepadManager::AxisInvalid); double normalized(int value) const; - - int minValue; - int maxValue; + void setAbsInfo(int fd, int abs); + void restoreSavedData(int fd, int abs, const QVariantMap &value); + QVariantMap dataToSave() const; double flat; + QGamepadManager::GamepadButton gamepadMinButton; + QGamepadManager::GamepadButton gamepadMaxButton; + QGamepadManager::GamepadButton gamepadLastButton; }; - QHash<int, AxisInfo> m_axisInfo; - QGamepadManager::GamepadButton m_prevYHatButton; - QGamepadManager::GamepadButton m_prevXHatButton; - friend QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::AxisInfo &axisInfo); + typedef QHash<int, EvdevAxisInfo> AxisMap; + AxisMap m_axisMap; + + friend QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::EvdevAxisInfo &axisInfo); + + typedef QHash<int, QGamepadManager::GamepadButton> ButtonsMap; + ButtonsMap m_buttonsMap; + + QGamepadManager::GamepadButton m_configureButton; + QGamepadManager::GamepadAxis m_configureAxis; + QGamepadManager::GamepadButton m_configureCancelButton; }; -QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::AxisInfo &axisInfo); +QDebug operator<<(QDebug dbg, const QEvdevGamepadDevice::EvdevAxisInfo &axisInfo); class QEvdevGamepadBackend : public QGamepadBackend { @@ -96,7 +115,11 @@ public: QEvdevGamepadBackend(); bool start() Q_DECL_OVERRIDE; void stop() Q_DECL_OVERRIDE; - int idForDevice(const QByteArray &device); + void resetConfiguration(int deviceId) Q_DECL_OVERRIDE; + bool isConfigurationNeeded(int deviceId) Q_DECL_OVERRIDE; + bool configureButton(int deviceId, QGamepadManager::GamepadButton button) Q_DECL_OVERRIDE; + bool configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) Q_DECL_OVERRIDE; + bool setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) Q_DECL_OVERRIDE; private slots: void handleAddedDevice(const QString &device); @@ -104,11 +127,10 @@ private slots: private: QEvdevGamepadDevice *newDevice(const QByteArray &device); + QEvdevGamepadDevice *device(int deviceId); QDeviceDiscovery *m_discovery; QVector<QEvdevGamepadDevice *> m_devices; - QHash<QByteArray, int> m_devIndex; - int m_nextIndex; }; QT_END_NAMESPACE |