diff options
author | BogDan Vatra <bogdan@kde.org> | 2015-10-14 17:01:20 +0300 |
---|---|---|
committer | Andy Nichols <andy.nichols@theqtcompany.com> | 2015-10-26 12:42:25 +0000 |
commit | 63527552d2070be768a0bc5d07a208053a5cd8fa (patch) | |
tree | b7536e329ce95d86a858d852225b4d1e553fdffe /src/plugins | |
parent | c32e995bc79035e600893e91fdcec984f8293c75 (diff) |
Say hello to android gamepad support
Change-Id: I3712c6846c671a1aae8ca3eeae5a80eddd7a558b
Reviewed-by: Andy Nichols <andy.nichols@theqtcompany.com>
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/gamepads/android/android.pro | 2 | ||||
-rw-r--r-- | src/plugins/gamepads/android/jar/bundledjar.pro | 3 | ||||
-rw-r--r-- | src/plugins/gamepads/android/jar/distributedjar.pro | 2 | ||||
-rw-r--r-- | src/plugins/gamepads/android/jar/jar.pri | 16 | ||||
-rw-r--r-- | src/plugins/gamepads/android/jar/jar.pro | 2 | ||||
-rw-r--r-- | src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java | 109 | ||||
-rw-r--r-- | src/plugins/gamepads/android/src/android.json | 3 | ||||
-rw-r--r-- | src/plugins/gamepads/android/src/main.cpp | 61 | ||||
-rw-r--r-- | src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp | 538 | ||||
-rw-r--r-- | src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h | 129 | ||||
-rw-r--r-- | src/plugins/gamepads/android/src/src.pro | 17 | ||||
-rw-r--r-- | src/plugins/gamepads/gamepads.pro | 3 |
12 files changed, 884 insertions, 1 deletions
diff --git a/src/plugins/gamepads/android/android.pro b/src/plugins/gamepads/android/android.pro new file mode 100644 index 0000000..6ad7e3b --- /dev/null +++ b/src/plugins/gamepads/android/android.pro @@ -0,0 +1,2 @@ +TEMPLATE=subdirs +SUBDIRS += jar src diff --git a/src/plugins/gamepads/android/jar/bundledjar.pro b/src/plugins/gamepads/android/jar/bundledjar.pro new file mode 100644 index 0000000..65af1bb --- /dev/null +++ b/src/plugins/gamepads/android/jar/bundledjar.pro @@ -0,0 +1,3 @@ +TARGET = QtAndroidGamepad-bundled +CONFIG += bundled_jar_file +include(jar.pri) diff --git a/src/plugins/gamepads/android/jar/distributedjar.pro b/src/plugins/gamepads/android/jar/distributedjar.pro new file mode 100644 index 0000000..d34c90c --- /dev/null +++ b/src/plugins/gamepads/android/jar/distributedjar.pro @@ -0,0 +1,2 @@ +TARGET = QtAndroidGamepad +include(jar.pri) diff --git a/src/plugins/gamepads/android/jar/jar.pri b/src/plugins/gamepads/android/jar/jar.pri new file mode 100644 index 0000000..13d36ac --- /dev/null +++ b/src/plugins/gamepads/android/jar/jar.pri @@ -0,0 +1,16 @@ +load(qt_build_paths) +CONFIG += java + +DESTDIR = $$MODULE_BASE_OUTDIR/jar + +API_VERSION = android-16 + +JAVACLASSPATH += $$PWD/src + +JAVASOURCES += $$PWD/src/org/qtproject/qt5/android/gamepad/QtGamepad.java + +# install +target.path = $$[QT_INSTALL_PREFIX]/jar +INSTALLS += target + +OTHER_FILES += $$JAVASOURCES diff --git a/src/plugins/gamepads/android/jar/jar.pro b/src/plugins/gamepads/android/jar/jar.pro new file mode 100644 index 0000000..923e757 --- /dev/null +++ b/src/plugins/gamepads/android/jar/jar.pro @@ -0,0 +1,2 @@ +TEMPLATE=subdirs +SUBDIRS += distributedjar.pro bundledjar.pro diff --git a/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java b/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java new file mode 100644 index 0000000..513a3f0 --- /dev/null +++ b/src/plugins/gamepads/android/jar/src/org/qtproject/qt5/android/gamepad/QtGamepad.java @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +package org.qtproject.qt5.android.gamepad; + +import android.app.Activity; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.Handler; + + +public class QtGamepad { + private long m_nativePtr = 0; + private InputManager m_manager; + Activity m_activity; + private InputManager.InputDeviceListener m_listener = new InputManager.InputDeviceListener() { + @Override + public void onInputDeviceAdded(int i) { + synchronized (QtGamepad.this) { + QtGamepad.onInputDeviceAdded(m_nativePtr, i); + } + } + + @Override + public void onInputDeviceRemoved(int i) { + synchronized (QtGamepad.this) { + QtGamepad.onInputDeviceRemoved(m_nativePtr, i); + } + } + + @Override + public void onInputDeviceChanged(int i) { + synchronized (QtGamepad.this) { + QtGamepad.onInputDeviceChanged(m_nativePtr, i); + } + } + }; + + QtGamepad(Activity activity) + { + m_manager = (InputManager) activity.getSystemService(Context.INPUT_SERVICE); + m_activity = activity; + } + + public void register(long nativePtr) + { + synchronized (this) { + if (m_manager != null) { + m_nativePtr = nativePtr; + m_activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + m_manager.registerInputDeviceListener(m_listener, new Handler()); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + } + + public void unregister() + { + synchronized (this) { + if (m_manager != null) { + m_nativePtr = 0; + m_manager.unregisterInputDeviceListener(m_listener); + } + } + } + + private static native void onInputDeviceAdded(long nativePtr, int deviceId); + private static native void onInputDeviceRemoved(long nativePtr, int deviceId); + private static native void onInputDeviceChanged(long nativePtr, int deviceId); +} diff --git a/src/plugins/gamepads/android/src/android.json b/src/plugins/gamepads/android/src/android.json new file mode 100644 index 0000000..6843bd3 --- /dev/null +++ b/src/plugins/gamepads/android/src/android.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "android" ] +} diff --git a/src/plugins/gamepads/android/src/main.cpp b/src/plugins/gamepads/android/src/main.cpp new file mode 100644 index 0000000..20cc646 --- /dev/null +++ b/src/plugins/gamepads/android/src/main.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 <QtGamepad/private/qgamepadbackendfactory_p.h> +#include <QtGamepad/private/qgamepadbackendplugin_p.h> + +#include "qandroidgamepadbackend_p.h" + +QT_BEGIN_NAMESPACE + +class QAndroidGamepadBackendPlugin : public QGamepadBackendPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "android.json") +public: + QGamepadBackend *create(const QString &key, const QStringList ¶mList) Q_DECL_OVERRIDE; +}; + +QGamepadBackend *QAndroidGamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) { + Q_UNUSED(key) + Q_UNUSED(paramList) + + return new QAndroidGamepadBackend(); +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp b/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp new file mode 100644 index 0000000..e8dbc0f --- /dev/null +++ b/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp @@ -0,0 +1,538 @@ +/**************************************************************************** +** +** 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 "qandroidgamepadbackend_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QEvent> +#include <QtCore/QPair> +#include <QtCore/QThread> +#include <QtCore/QVector> + +#include <functional> +#include <vector> + +#include <QVariant> +#include <jni.h> + +QT_BEGIN_NAMESPACE +namespace { + const QLatin1String AXES_KEY("axes"); + const QLatin1String BUTTONS_KEY("buttons"); + + class FunctionEvent : public QEvent + { + typedef std::function<void()> Function; + public: + explicit FunctionEvent(const Function &func) + : QEvent(QEvent::User) + , m_function(func) + {} + inline void call() { m_function(); } + static inline void runOnQtThread(QObject *receiver, const Function &func) { + if (qApp->thread() == QThread::currentThread()) + func(); + else + qApp->postEvent(receiver, new FunctionEvent(func)); + } + + private: + Function m_function; + }; + + const char keyEventClass[] = "android/view/KeyEvent"; + inline int keyField(const char *field) + { + return QJNIObjectPrivate::getStaticField<jint>(keyEventClass, field); + } + + const char motionEventClass[] = "android/view/MotionEvent"; + inline int motionField(const char *field) + { + return QJNIObjectPrivate::getStaticField<jint>(motionEventClass, field); + } + + const char inputDeviceClass[] = "android/view/InputDevice"; + inline int inputDeviceField(const char *field) + { + return QJNIObjectPrivate::getStaticField<jint>(inputDeviceClass, field); + } + + + struct DefaultMapping : public QAndroidGamepadBackend::Mapping { + DefaultMapping() + { + buttonsMap[keyField("KEYCODE_BUTTON_A")] = QGamepadManager::ButtonA; + buttonsMap[keyField("KEYCODE_BUTTON_B")] = QGamepadManager::ButtonB; + buttonsMap[keyField("KEYCODE_BUTTON_X")] = QGamepadManager::ButtonX; + buttonsMap[keyField("KEYCODE_BUTTON_Y")] = QGamepadManager::ButtonY; + buttonsMap[keyField("KEYCODE_BUTTON_L1")] = QGamepadManager::ButtonL1; + buttonsMap[keyField("KEYCODE_BUTTON_R1")] = QGamepadManager::ButtonR1; + buttonsMap[keyField("KEYCODE_BUTTON_L2")] = QGamepadManager::ButtonL2; + buttonsMap[keyField("KEYCODE_BUTTON_R2")] = QGamepadManager::ButtonR2; + buttonsMap[keyField("KEYCODE_BUTTON_SELECT")] = QGamepadManager::ButtonSelect; + buttonsMap[keyField("KEYCODE_BUTTON_START")] = QGamepadManager::ButtonStart; + buttonsMap[keyField("KEYCODE_BUTTON_THUMBL")] = QGamepadManager::ButtonL3; + buttonsMap[keyField("KEYCODE_BUTTON_THUMBR")] = QGamepadManager::ButtonR3; + buttonsMap[keyField("KEYCODE_DPAD_UP")] = QGamepadManager::ButtonUp; + buttonsMap[keyField("KEYCODE_DPAD_DOWN")] = QGamepadManager::ButtonDown; + buttonsMap[keyField("KEYCODE_DPAD_RIGHT")] = QGamepadManager::ButtonRight; + buttonsMap[keyField("KEYCODE_DPAD_LEFT")] = QGamepadManager::ButtonLeft; + buttonsMap[keyField("KEYCODE_DPAD_CENTER")] = QGamepadManager::ButtonCenter; + buttonsMap[keyField("KEYCODE_BUTTON_MODE")] = QGamepadManager::ButtonGuide; + + if (QtAndroidPrivate::androidSdkVersion() >= 12) { + axisMap[motionField("AXIS_X")].gamepadAxis = QGamepadManager::AxisLeftX; + axisMap[motionField("AXIS_Y")].gamepadAxis = QGamepadManager::AxisLeftY; + axisMap[motionField("AXIS_HAT_X")].gamepadAxis = QGamepadManager::AxisLeftX; + axisMap[motionField("AXIS_HAT_Y")].gamepadAxis = QGamepadManager::AxisLeftY; + axisMap[motionField("AXIS_Z")].gamepadAxis = QGamepadManager::AxisRightX; + axisMap[motionField("AXIS_RZ")].gamepadAxis = QGamepadManager::AxisRightY; + + allAndroidAxes.push_back(motionField("AXIS_X")); + allAndroidAxes.push_back(motionField("AXIS_Y")); + allAndroidAxes.push_back(motionField("AXIS_Z")); + allAndroidAxes.push_back(motionField("AXIS_RZ")); + allAndroidAxes.push_back(motionField("AXIS_BRAKE")); + allAndroidAxes.push_back(motionField("AXIS_GAS")); + + for (int i = 1; i < 16; ++i) + allAndroidAxes.push_back(motionField(QByteArray("AXIS_GENERIC_").append(QByteArray::number(i)).constData())); + + allAndroidAxes.push_back(motionField("AXIS_HAT_X")); + allAndroidAxes.push_back(motionField("AXIS_HAT_Y")); + allAndroidAxes.push_back(motionField("AXIS_LTRIGGER")); + allAndroidAxes.push_back(motionField("AXIS_RTRIGGER")); + allAndroidAxes.push_back(motionField("AXIS_RUDDER")); + allAndroidAxes.push_back(motionField("AXIS_THROTTLE")); + allAndroidAxes.push_back(motionField("AXIS_WHEEL")); + } + + acceptedSources.push_back(inputDeviceField("SOURCE_DPAD")); + if (QtAndroidPrivate::androidSdkVersion() >= 12) { + acceptedSources.push_back(inputDeviceField("SOURCE_GAMEPAD")); + acceptedSources.push_back(inputDeviceField("SOURCE_CLASS_JOYSTICK")); + if (QtAndroidPrivate::androidSdkVersion() >= 21) { + acceptedSources.push_back(inputDeviceField("SOURCE_HDMI")); + } + } + + ACTION_DOWN = keyField("ACTION_DOWN"); + ACTION_UP = keyField("ACTION_UP"); + ACTION_MULTIPLE = keyField("ACTION_MULTIPLE"); + FLAG_LONG_PRESS = keyField("FLAG_LONG_PRESS"); + } + std::vector<int> acceptedSources; + std::vector<int> allAndroidAxes; + int ACTION_DOWN, ACTION_MULTIPLE, ACTION_UP, FLAG_LONG_PRESS; + }; + + void onInputDeviceAdded(JNIEnv *, jclass, jlong qtNativePtr, int deviceId) + { + if (!qtNativePtr) + return; + reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->addDevice(deviceId); + } + void onInputDeviceRemoved(JNIEnv *, jclass, jlong qtNativePtr, int deviceId) + { + if (!qtNativePtr) + return; + reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->removeDevice(deviceId); + } + void onInputDeviceChanged(JNIEnv *, jclass, jlong qtNativePtr, int deviceId) + { + if (!qtNativePtr) + return; + reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->updateDevice(deviceId); + } + + static JNINativeMethod methods[] = { + {"onInputDeviceAdded", "(JI)V", (void *)onInputDeviceAdded}, + {"onInputDeviceRemoved", "(JI)V", (void *)onInputDeviceRemoved}, + {"onInputDeviceChanged", "(JI)V", (void *)onInputDeviceChanged} + }; + + const char qtGamePadClassName[] = "org/qtproject/qt5/android/gamepad/QtGamepad"; + + inline void setAxisInfo(QJNIObjectPrivate &motionRange, QAndroidGamepadBackend::Mapping::AndroidAxisInfo &info) + { + if (motionRange.isValid()) { + info.flatArea = motionRange.callMethod<jfloat>("getFlat", "()F"); + info.minValue = motionRange.callMethod<jfloat>("getMin", "()F"); + info.maxValue = motionRange.callMethod<jfloat>("getMax", "()F"); + info.fuzz = motionRange.callMethod<jfloat>("getFuzz", "()F"); + } else { + info.flatArea = 0; + } + } + +} // namespace + +Q_GLOBAL_STATIC(DefaultMapping, g_defaultMapping) + +QAndroidGamepadBackend::QAndroidGamepadBackend(QObject *parent) + : QGamepadBackend(parent) +{ + QtAndroidPrivate::registerGenericMotionEventListener(this); + QtAndroidPrivate::registerKeyEventListener(this); +} + +QAndroidGamepadBackend::~QAndroidGamepadBackend() +{ + QtAndroidPrivate::unregisterGenericMotionEventListener(this); + QtAndroidPrivate::unregisterKeyEventListener(this); +} + +void QAndroidGamepadBackend::addDevice(int deviceId) +{ + if (deviceId == -1) + return; + + QMutexLocker lock(&m_mutex); + QJNIObjectPrivate inputDevice = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDevice", "(I)Landroid/view/InputDevice;", deviceId); + int sources = inputDevice.callMethod<jint>("getSources", "()I"); + bool acceptable = false; + for (int source : g_defaultMapping()->acceptedSources) { + if ( (source & sources) == source) { + acceptable = true; + break; + } + } + + if (acceptable) { + m_devices.insert(deviceId, *g_defaultMapping()); + int productId = inputDevice.callMethod<jint>("getProductId", "()I"); + m_devices[deviceId].productId = productId; + if (productId) { + QVariant settings = readSettings(productId); + if (!settings.isNull()) { + auto &deviceInfo = m_devices[deviceId]; + deviceInfo.needsConfigure = false; + + QVariantMap data = settings.toMap()[AXES_KEY].toMap(); + for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) + deviceInfo.axisMap[it.key().toInt()].gamepadAxis = QGamepadManager::GamepadAxis(it.value().toInt()); + + data = settings.toMap()[BUTTONS_KEY].toMap(); + for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it) + deviceInfo.buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt()); + } + } + FunctionEvent::runOnQtThread(this, [this, deviceId]{ + emit gamepadAdded(deviceId); + }); + } +} + +void QAndroidGamepadBackend::updateDevice(int deviceId) +{ + Q_UNUSED(deviceId) + //QMutexLocker lock(&m_mutex); +} + +void QAndroidGamepadBackend::removeDevice(int deviceId) +{ + QMutexLocker lock(&m_mutex); + if (m_devices.remove(deviceId)) { + FunctionEvent::runOnQtThread(this, [this, deviceId]{ + emit gamepadRemoved(deviceId); + }); + } +} + +bool QAndroidGamepadBackend::event(QEvent *ev) +{ + if (ev->type() == QEvent::User) { + static_cast<FunctionEvent*>(ev)->call(); + return true; + } + return QGamepadBackend::event(ev); +} + +bool QAndroidGamepadBackend::isConfigurationNeeded(int deviceId) +{ + QMutexLocker lock(&m_mutex); + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) + return false; + return it->needsConfigure; +} + +bool QAndroidGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button) +{ + QMutexLocker lock(&m_mutex); + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) + return false; + + it.value().calibrateButton = button; + return true; +} + +bool QAndroidGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) +{ + QMutexLocker lock(&m_mutex); + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) + return false; + + it.value().calibrateAxis = axis; + return true; +} + +bool QAndroidGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) +{ + QMutexLocker lock(&m_mutex); + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) + return false; + + it.value().cancelConfigurationButton = button; + return true; +} + +void QAndroidGamepadBackend::resetConfiguration(int deviceId) +{ + QMutexLocker lock(&m_mutex); + auto it = m_devices.find(deviceId); + if (it == m_devices.end()) + return; + + int productId = it.value().productId; + it.value() = *g_defaultMapping(); + it.value().productId = productId; +} + +bool QAndroidGamepadBackend::handleKeyEvent(jobject event) +{ + QJNIObjectPrivate ev(event); + QMutexLocker lock(&m_mutex); + const int deviceId = ev.callMethod<jint>("getDeviceId", "()I"); + const auto deviceIt = m_devices.find(deviceId); + if (deviceIt == m_devices.end()) + return false; + + const int action = ev.callMethod<jint>("getAction", "()I"); + if (action != g_defaultMapping()->ACTION_DOWN && + action != g_defaultMapping()->ACTION_UP) { + return false; + } + const int flags = ev.callMethod<jint>("getFlags", "()I"); + if ((flags & g_defaultMapping()->FLAG_LONG_PRESS) == g_defaultMapping()->FLAG_LONG_PRESS) + return false; + + const int keyCode = ev.callMethod<jint>("getKeyCode", "()I"); + auto &deviceMap = deviceIt.value(); + + if (deviceMap.cancelConfigurationButton != QGamepadManager::ButtonInvalid && + (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid || + deviceMap.calibrateAxis != QGamepadManager::AxisInvalid)) { + + const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode); + if (buttonsMapIt != deviceMap.buttonsMap.end() && + deviceMap.cancelConfigurationButton == buttonsMapIt.value()) { + deviceMap.calibrateButton = QGamepadManager::ButtonInvalid; + deviceMap.calibrateAxis = QGamepadManager::AxisInvalid; + FunctionEvent::runOnQtThread(this, [this, deviceId]{ + emit configurationCanceled(deviceId); + }); + return true; + } + } + + if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) { + deviceMap.buttonsMap[keyCode] = deviceMap.calibrateButton; + auto but = deviceMap.calibrateButton; + deviceMap.calibrateButton = QGamepadManager::ButtonInvalid; + saveData(deviceMap); + FunctionEvent::runOnQtThread(this, [this, deviceId, but]{ + emit buttonConfigured(deviceId, but); + }); + } + + const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode); + if (buttonsMapIt == deviceMap.buttonsMap.end()) + return false; + + const auto button = buttonsMapIt.value(); + if (action == g_defaultMapping()->ACTION_DOWN) { + FunctionEvent::runOnQtThread(this, [this, deviceId, button]{ + emit gamepadButtonPressed(deviceId, button, 1.0); + }); + } else { + FunctionEvent::runOnQtThread(this, [this, deviceId, button]{ + emit gamepadButtonReleased(deviceId, button); + }); + } + return true; +} + +bool QAndroidGamepadBackend::handleGenericMotionEvent(jobject event) +{ + // GenericMotionEvent was introduced in API-12 + Q_ASSERT(QtAndroidPrivate::androidSdkVersion() >= 12); + + QJNIObjectPrivate ev(event); + QMutexLocker lock(&m_mutex); + const int deviceId = ev.callMethod<jint>("getDeviceId", "()I"); + const auto deviceIt = m_devices.find(deviceId); + if (deviceIt == m_devices.end()) + return false; + + auto &deviceMap = deviceIt.value(); + if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid) { + double lastValue = 0; + int lastAxis = -1; + for (int axis : g_defaultMapping()->allAndroidAxes) { + double value = fabs(ev.callMethod<jfloat>("getAxisValue", "(I)F", axis)); + if (value > lastValue) { + lastValue = value; + lastAxis = axis; + } + } + + if (!lastValue || lastAxis == -1) + return false; + + deviceMap.axisMap[lastAxis].gamepadAxis = deviceMap.calibrateAxis; + auto axis = deviceMap.calibrateAxis; + deviceMap.calibrateAxis = QGamepadManager::AxisInvalid; + saveData(deviceMap); + FunctionEvent::runOnQtThread(this, [this, deviceId, axis]{ + emit axisConfigured(deviceId, axis); + }); + } + + typedef QPair<QGamepadManager::GamepadAxis, double> GamepadAxisValue; + QVector<GamepadAxisValue> values; + for (auto it = deviceMap.axisMap.begin(); it != deviceMap.axisMap.end(); ++it) { + auto &axisInfo = it.value(); + if (axisInfo.flatArea == -1) { + // compute the range & flat area + QJNIObjectPrivate device(ev.callObjectMethod("getDevice", "()Landroid/view/InputDevice;")); + if (device.isValid()) { + const int source = ev.callMethod<jint>("getSource", "()I"); + QJNIObjectPrivate motionRange = device.callObjectMethod("getMotionRange","(II)Landroid/view/InputDevice$MotionRange;", it.key(), source); + setAxisInfo(motionRange, axisInfo); + } + } + + const int historicalValues = ev.callMethod<jint>("getHistorySize", "()I"); + for (int i = 0; i < historicalValues; ++i) { + double value = ev.callMethod<jfloat>("getHistoricalAxisValue", "(II)F", it.key(), i); + if (axisInfo.setValue(value)) + values.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue)); + } + double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", it.key()); + if (axisInfo.setValue(value)) + values.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue)); + } + + if (!values.isEmpty()) { + FunctionEvent::runOnQtThread(this, [this, deviceId, values]{ + foreach (const auto &axisValue, values) + emit gamepadAxisMoved(deviceId, axisValue.first, axisValue.second); + }); + } + return false; +} + +bool QAndroidGamepadBackend::start() +{ + { + QMutexLocker lock(&m_mutex); + if (QtAndroidPrivate::androidSdkVersion() >= 16) { + if (!m_qtGamepad.isValid()) + m_qtGamepad = QJNIObjectPrivate(qtGamePadClassName, "(Landroid/app/Activity;)V", QtAndroidPrivate::activity()); + m_qtGamepad.callMethod<void>("register", "(J)V", jlong(this)); + } + } + + QJNIObjectPrivate ids = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDeviceIds", "()[I"); + jintArray jarr = jintArray(ids.object()); + QJNIEnvironmentPrivate env; + size_t sz = env->GetArrayLength(jarr); + jint *buff = env->GetIntArrayElements(jarr, nullptr); + for (size_t i = 0; i < sz; ++i) + addDevice(buff[i]); + env->ReleaseIntArrayElements(jarr, buff, 0); + return true; +} + +void QAndroidGamepadBackend::stop() +{ + QMutexLocker lock(&m_mutex); + if (QtAndroidPrivate::androidSdkVersion() >= 16 && m_qtGamepad.isValid()) + m_qtGamepad.callMethod<void>("unregister", "()V"); +} + +void QAndroidGamepadBackend::saveData(const QAndroidGamepadBackend::Mapping &deviceInfo) +{ + if (!deviceInfo.productId) + return ; + + QVariantMap settings, data; + for (auto it = deviceInfo.axisMap.begin(); it != deviceInfo.axisMap.end(); ++it) + data[QString::number(it.key())] = it.value().gamepadAxis; + settings[AXES_KEY] = data; + + data.clear(); + for (auto it = deviceInfo.buttonsMap.begin(); it != deviceInfo.buttonsMap.end(); ++it) + data[QString::number(it.key())] = it.value(); + settings[BUTTONS_KEY] = data; + + saveSettings(deviceInfo.productId, settings); +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) +{ + JNIEnv* env; + // get the JNIEnv pointer. + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) + return JNI_ERR; + + // search for Java class which declares the native methods + jclass javaClass = env->FindClass("org/qtproject/qt5/android/gamepad/QtGamepad"); + if (!javaClass) + return JNI_ERR; + + // register our native methods + if (env->RegisterNatives(javaClass, methods, + sizeof(methods) / sizeof(methods[0])) < 0) { + return JNI_ERR; + } + return JNI_VERSION_1_6; +} + +QT_END_NAMESPACE diff --git a/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h b/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h new file mode 100644 index 0000000..b441317 --- /dev/null +++ b/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QSDLGAMEPADCONTROLLER_H +#define QSDLGAMEPADCONTROLLER_H + +#include <QtCore/QHash> +#include <QtCore/QMutex> +#include <QtCore/QSet> + +#include <QtCore/private/qjni_p.h> +#include <QtCore/private/qjnihelpers_p.h> + +#include <QtGamepad/QGamepadManager> +#include <QtGamepad/private/qgamepadbackend_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidGamepadBackend : public QGamepadBackend, public QtAndroidPrivate::GenericMotionEventListener, public QtAndroidPrivate::KeyEventListener +{ + Q_OBJECT +public: + explicit QAndroidGamepadBackend(QObject *parent = 0); + ~QAndroidGamepadBackend(); + + void addDevice(int deviceId); + void updateDevice(int deviceId); + void removeDevice(int deviceId); + + // QObject interface + bool event(QEvent *) override; + + // QGamepadBackend interface + bool isConfigurationNeeded(int deviceId) override; + bool configureButton(int deviceId, QGamepadManager::GamepadButton button) override; + bool configureAxis(int deviceId, QGamepadManager::GamepadAxis axis) override; + bool setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button) override; + void resetConfiguration(int deviceId) override; + + + // KeyEventListener interface + bool handleKeyEvent(jobject event) override; + + // GenericMotionEventListener interface + bool handleGenericMotionEvent(jobject event) override; + +protected: + bool start(); + void stop(); + +public: + struct Mapping { + struct AndroidAxisInfo : public AxisInfo<double> { + AndroidAxisInfo() : AxisInfo(-1.0, 1.0) { } + AndroidAxisInfo(double minValue, double maxValue) : AxisInfo(minValue, maxValue) { } + + inline bool setValue(double value) + { + if (minValue != -1.0 && maxValue != 1.0) + value = AxisInfo::normalized(value); + + if (qAbs(value) <= flatArea) + value = 0; + + if (qAbs(qAbs(value) - qAbs(lastValue)) <= fuzz) + return false; + + lastValue = value; + return true; + } + + double flatArea = -1; + double fuzz = 0; + double lastValue = 0; + }; + QHash<int, AndroidAxisInfo> axisMap; + QHash<int, QGamepadManager::GamepadButton> buttonsMap; + + QGamepadManager::GamepadButton calibrateButton = QGamepadManager::ButtonInvalid; + QGamepadManager::GamepadAxis calibrateAxis = QGamepadManager::AxisInvalid; + QGamepadManager::GamepadButton cancelConfigurationButton = QGamepadManager::ButtonInvalid; + int productId = 0; + bool needsConfigure = true; + }; + +private: + void saveData(const Mapping &deviceInfo); + +private: + QMutex m_mutex; + QJNIObjectPrivate m_qtGamepad; + QHash<int, Mapping> m_devices; +}; + +QT_END_NAMESPACE + +#endif // QSDLGAMEPADCONTROLLER_H diff --git a/src/plugins/gamepads/android/src/src.pro b/src/plugins/gamepads/android/src/src.pro new file mode 100644 index 0000000..31f8a28 --- /dev/null +++ b/src/plugins/gamepads/android/src/src.pro @@ -0,0 +1,17 @@ +TARGET = androidgamepad +QT += core-private gamepad gamepad-private + +PLUGIN_TYPE = gamepads +PLUGIN_CLASS_NAME = QAndroidGamepadBackendPlugin + +load(qt_plugin) + +HEADERS += \ + qandroidgamepadbackend_p.h + +SOURCES += \ + main.cpp \ + qandroidgamepadbackend.cpp + +DISTFILES += \ + android.json diff --git a/src/plugins/gamepads/gamepads.pro b/src/plugins/gamepads/gamepads.pro index 30347bb..980f81f 100644 --- a/src/plugins/gamepads/gamepads.pro +++ b/src/plugins/gamepads/gamepads.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs config_sdl:SUBDIRS += sdl2 -contains(QT_CONFIG, evdev): SUBDIRS += evdev +!android: contains(QT_CONFIG, evdev): SUBDIRS += evdev win32: !wince*: SUBDIRS += xinput ios: SUBDIRS += ios +android: SUBDIRS += android |