diff options
Diffstat (limited to 'src/plugins')
83 files changed, 3768 insertions, 3285 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt new file mode 100644 index 0000000..d4856bd --- /dev/null +++ b/src/plugins/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(joystickinputs) +add_subdirectory(mouseinputs) diff --git a/src/plugins/gamepads/android/android.pro b/src/plugins/gamepads/android/android.pro deleted file mode 100644 index 6ad7e3b..0000000 --- a/src/plugins/gamepads/android/android.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE=subdirs -SUBDIRS += jar src diff --git a/src/plugins/gamepads/android/jar/jar.pro b/src/plugins/gamepads/android/jar/jar.pro deleted file mode 100644 index 879bbd9..0000000 --- a/src/plugins/gamepads/android/jar/jar.pro +++ /dev/null @@ -1,18 +0,0 @@ -TARGET = Qt$${QT_MAJOR_VERSION}AndroidGamepad - -load(qt_build_paths) -CONFIG += java - -DESTDIR = $$MODULE_BASE_OUTDIR/jar - -API_VERSION = android-16 - -JAVACLASSPATH += $$PWD/src - -JAVASOURCES += $$PWD/src/org/qtproject/qt/android/gamepad/QtGamepad.java - -# install -target.path = $$[QT_INSTALL_PREFIX]/jar -INSTALLS += target - -OTHER_FILES += $$JAVASOURCES diff --git a/src/plugins/gamepads/android/jar/src/org/qtproject/qt/android/gamepad/QtGamepad.java b/src/plugins/gamepads/android/jar/src/org/qtproject/qt/android/gamepad/QtGamepad.java deleted file mode 100644 index 94ffa9f..0000000 --- a/src/plugins/gamepads/android/jar/src/org/qtproject/qt/android/gamepad/QtGamepad.java +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org> -** 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$ -** -****************************************************************************/ - -package org.qtproject.qt.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/main.cpp b/src/plugins/gamepads/android/src/main.cpp deleted file mode 100644 index 97af630..0000000 --- a/src/plugins/gamepads/android/src/main.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** 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) 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 deleted file mode 100644 index eae0e6a..0000000 --- a/src/plugins/gamepads/android/src/qandroidgamepadbackend.cpp +++ /dev/null @@ -1,635 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org> -** 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 "qandroidgamepadbackend_p.h" - -#include <QtCore/QCoreApplication> -#include <QtCore/QEvent> -#include <QtCore/QPair> -#include <QtCore/QThread> -#include <QtCore/QList> - -#include <functional> -#include <vector> - -#include <QVariant> -#include <jni.h> -#include <math.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; - { - auto &axis = axisMap[motionField("AXIS_LTRIGGER")]; - axis.gamepadAxis = QGamepadManager::AxisInvalid; - axis.gamepadMinButton = QGamepadManager::ButtonL2; - axis.gamepadMaxButton = QGamepadManager::ButtonL2; - } - { - auto &axis = axisMap[motionField("AXIS_RTRIGGER")]; - axis.gamepadAxis = QGamepadManager::AxisInvalid; - axis.gamepadMinButton = QGamepadManager::ButtonR2; - axis.gamepadMaxButton = QGamepadManager::ButtonR2; - } - - 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")); - } - - 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")); - } - } else { - acceptedSources.push_back(inputDeviceField("SOURCE_DPAD")); - } - - 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/qt/android/gamepad/QtGamepad"; - - inline void setAxisInfo(QJNIObjectPrivate &event, int axis, QAndroidGamepadBackend::Mapping::AndroidAxisInfo &info) - { - QJNIObjectPrivate device(event.callObjectMethod("getDevice", "()Landroid/view/InputDevice;")); - if (device.isValid()) { - const int source = event.callMethod<jint>("getSource", "()I"); - QJNIObjectPrivate motionRange = device.callObjectMethod("getMotionRange","(II)Landroid/view/InputDevice$MotionRange;", axis, source); - 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"); - return; - } - } - info.flatArea = 0; - } - -} // namespace - -Q_GLOBAL_STATIC(DefaultMapping, g_defaultMapping) - -void QAndroidGamepadBackend::Mapping::AndroidAxisInfo::restoreSavedData(const QVariantMap &value) -{ - gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1String("axis")].toInt()); - gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1String("minButton")].toInt()); - gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1String("maxButton")].toInt()); -} - -QVariantMap QAndroidGamepadBackend::Mapping::AndroidAxisInfo::dataToSave() const -{ - QVariantMap data; - data[QLatin1String("axis")] = gamepadAxis; - data[QLatin1String("minButton")] = gamepadMinButton; - data[QLatin1String("maxButton")] = gamepadMaxButton; - return data; -} - -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 = qHash(inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString()); - 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()].restoreSavedData(it.value().toMap()); - - 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; - - it.value().axisMap.clear(); - it.value().buttonsMap.clear(); - it.value().calibrateButton = QGamepadManager::ButtonInvalid; - it.value().calibrateAxis = QGamepadManager::AxisInvalid; - it.value().cancelConfigurationButton = QGamepadManager::ButtonInvalid; - it.value().needsConfigure = false; -} - -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 || - deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) { - double lastValue = 0; - int lastAxis = -1; - for (int axis : g_defaultMapping()->allAndroidAxes) { - double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", axis); - if (fabs(value) > fabs(lastValue)) { - lastValue = value; - lastAxis = axis; - } - } - - if (!lastValue || lastAxis == -1) - return false; - - if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid) { - 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); - }); - } else if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid && - deviceMap.calibrateButton != QGamepadManager::ButtonUp && - deviceMap.calibrateButton != QGamepadManager::ButtonDown && - deviceMap.calibrateButton != QGamepadManager::ButtonLeft && - deviceMap.calibrateButton != QGamepadManager::ButtonRight) { - auto &axis = deviceMap.axisMap[lastAxis]; - axis.gamepadAxis = QGamepadManager::AxisInvalid; - setAxisInfo(ev, lastAxis, axis); - bool save = false; - if (lastValue == axis.minValue) { - axis.gamepadMinButton = deviceMap.calibrateButton; - if (axis.gamepadMaxButton == QGamepadManager::ButtonInvalid) - axis.gamepadMaxButton = deviceMap.calibrateButton; - save = true; - } else if (lastValue == axis.maxValue) { - axis.gamepadMaxButton = deviceMap.calibrateButton; - if (axis.gamepadMinButton == QGamepadManager::ButtonInvalid) - axis.gamepadMinButton = deviceMap.calibrateButton; - save = true; - } - - if (save) { - auto but = deviceMap.calibrateButton; - deviceMap.calibrateButton = QGamepadManager::ButtonInvalid; - saveData(deviceMap); - FunctionEvent::runOnQtThread(this, [this, deviceId, but]{ - emit buttonConfigured(deviceId, but); - }); - } - } - } - - typedef QPair<QGamepadManager::GamepadAxis, double> GamepadAxisValue; - QList<GamepadAxisValue> axisValues; - typedef QPair<QGamepadManager::GamepadButton, double> GamepadButtonValue; - QList<GamepadButtonValue> buttonValues; - auto setValue = [&axisValues, &buttonValues](Mapping::AndroidAxisInfo &axisInfo, double value) { - if (axisInfo.setValue(value)) { - if (axisInfo.gamepadAxis != QGamepadManager::AxisInvalid) { - axisValues.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue)); - } else { - if (axisInfo.lastValue < 0) { - buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMinButton, axisInfo.lastValue)); - axisInfo.gamepadLastButton = axisInfo.gamepadMinButton; - } else if (axisInfo.lastValue > 0) { - buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMaxButton, axisInfo.lastValue)); - axisInfo.gamepadLastButton = axisInfo.gamepadMaxButton; - } else { - buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadLastButton, 0.0)); - axisInfo.gamepadLastButton = QGamepadManager::ButtonInvalid; - } - } - } - }; - for (auto it = deviceMap.axisMap.begin(); it != deviceMap.axisMap.end(); ++it) { - auto &axisInfo = it.value(); - if (axisInfo.flatArea == -1) - setAxisInfo(ev, it.key(), 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); - setValue(axisInfo, value); - } - double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", it.key()); - setValue(axisInfo, value); - } - - if (!axisValues.isEmpty()) { - FunctionEvent::runOnQtThread(this, [this, deviceId, axisValues]{ - for (const auto &axisValue : axisValues) - emit gamepadAxisMoved(deviceId, axisValue.first, axisValue.second); - }); - } - - if (!buttonValues.isEmpty()) { - FunctionEvent::runOnQtThread(this, [this, deviceId, buttonValues]{ - for (const auto &buttonValue : buttonValues) - if (buttonValue.second) - emit gamepadButtonPressed(deviceId, buttonValue.first, fabs(buttonValue.second)); - else - emit gamepadButtonReleased(deviceId, buttonValue.first); - }); - } - - 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().dataToSave(); - 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*/) -{ - static bool initialized = false; - if (initialized) - return JNI_VERSION_1_6; - initialized = true; - - 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/qt/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 deleted file mode 100644 index b54f569..0000000 --- a/src/plugins/gamepads/android/src/qandroidgamepadbackend_p.h +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org> -** 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$ -** -****************************************************************************/ - -#ifndef QANDROIDGAMEPADBACKEND_P_H -#define QANDROIDGAMEPADBACKEND_P_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 = nullptr); - ~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() override; - void stop() override; - -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; - } - void restoreSavedData(const QVariantMap &value); - QVariantMap dataToSave() const; - - double flatArea = -1; - double fuzz = 0; - double lastValue = 0; - QGamepadManager::GamepadButton gamepadMinButton = QGamepadManager::ButtonInvalid; - QGamepadManager::GamepadButton gamepadMaxButton = QGamepadManager::ButtonInvalid; - QGamepadManager::GamepadButton gamepadLastButton = QGamepadManager::ButtonInvalid; - }; - 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 = false; - }; - -private: - void saveData(const Mapping &deviceInfo); - -private: - QMutex m_mutex; - QJNIObjectPrivate m_qtGamepad; - QHash<int, Mapping> m_devices; -}; - -QT_END_NAMESPACE - -#endif // QANDROIDGAMEPADBACKEND_P_H diff --git a/src/plugins/gamepads/android/src/src.pro b/src/plugins/gamepads/android/src/src.pro deleted file mode 100644 index 31f8a28..0000000 --- a/src/plugins/gamepads/android/src/src.pro +++ /dev/null @@ -1,17 +0,0 @@ -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/darwin/darwin.json b/src/plugins/gamepads/darwin/darwin.json deleted file mode 100644 index f72350b..0000000 --- a/src/plugins/gamepads/darwin/darwin.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Keys": [ "darwin" ] -} diff --git a/src/plugins/gamepads/darwin/darwin.pro b/src/plugins/gamepads/darwin/darwin.pro deleted file mode 100644 index 9a35c75..0000000 --- a/src/plugins/gamepads/darwin/darwin.pro +++ /dev/null @@ -1,19 +0,0 @@ -TARGET = darwingamepad -QT += gamepad gamepad-private - -PLUGIN_TYPE = gamepads -PLUGIN_EXTENDS = gamepad -PLUGIN_CLASS_NAME = QDarwinGamepadBackendPlugin -load(qt_plugin) - -LIBS += -framework GameController -framework Foundation - -HEADERS += qdarwingamepadbackend_p.h -OBJECTIVE_SOURCES += \ - qdarwingamepadbackend.mm - -SOURCES += \ - main.cpp - -OTHER_FILES += \ - darwin.json diff --git a/src/plugins/gamepads/darwin/main.cpp b/src/plugins/gamepads/darwin/main.cpp deleted file mode 100644 index 87afec1..0000000 --- a/src/plugins/gamepads/darwin/main.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** 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 "qdarwingamepadbackend_p.h" - -QT_BEGIN_NAMESPACE - -class QDarwinGamepadBackendPlugin : public QGamepadBackendPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "darwin.json") -public: - QGamepadBackend *create(const QString &key, const QStringList ¶mList) override; -}; - -QGamepadBackend *QDarwinGamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) { - Q_UNUSED(key); - Q_UNUSED(paramList); - - return new QDarwinGamepadBackend(); -} - -QT_END_NAMESPACE - -#include "main.moc" diff --git a/src/plugins/gamepads/darwin/qdarwingamepadbackend.mm b/src/plugins/gamepads/darwin/qdarwingamepadbackend.mm deleted file mode 100644 index 6094a99..0000000 --- a/src/plugins/gamepads/darwin/qdarwingamepadbackend.mm +++ /dev/null @@ -1,570 +0,0 @@ -/**************************************************************************** -** -** 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 "qdarwingamepadbackend_p.h" - -#include <QtCore/QDebug> - -#import <GameController/GameController.h> - -@interface QT_MANGLE_NAMESPACE(DarwinGamepadManager) : NSObject - -@property (nonatomic, strong) id connectObserver; -@property (nonatomic, strong) id disconnectObserver; - -@end - -@implementation QT_MANGLE_NAMESPACE(DarwinGamepadManager) -{ - QDarwinGamepadBackend *backend; - NSMutableArray *connectedControllers; -} - --(instancetype)initWithBackend:(QDarwinGamepadBackend *)gamepadBackend -{ - if ((self = [self init])) { - backend = gamepadBackend; - connectedControllers = [[NSMutableArray alloc] init]; - //Setup observers for monitoring controller connections/disconnections - self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *note) { - GCController *controller = (GCController*)note.object; - [self addMonitoredController:controller]; - - }]; - self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification *note) { - GCController *controller = (GCController*)note.object; - [self removeMonitoredController:controller]; - }]; - //Set initial controller values - for (int i = 0; i < 4; ++i) - [connectedControllers addObject:[NSNull null]]; - - //Add monitoring for any alrready connected controllers - for (NSUInteger i = 0; i < [[GCController controllers] count]; ++i) { - [self addMonitoredController:[GCController controllers][i]]; - } - } - return self; -} - --(void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver]; - [[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver]; - [connectedControllers release]; - [super dealloc]; -} - --(void)addMonitoredController:(GCController *)controller -{ - int index = -1; - for (int i = 0; i < 4; ++i) { - if (connectedControllers[i] == [NSNull null]) { - [connectedControllers replaceObjectAtIndex: i withObject: controller]; - index = i; - break; - } - } - - controller.playerIndex = GCControllerPlayerIndex(index); - - QMetaObject::invokeMethod(backend, "darwinGamepadAdded", Qt::AutoConnection, Q_ARG(int, index)); - - //Pause button handler - [controller setControllerPausedHandler:^(GCController *controller) { - Q_UNUSED(controller); - QMetaObject::invokeMethod(backend, "handlePauseButton", Qt::AutoConnection, Q_ARG(int, index)); - }]; - - if (controller.extendedGamepad) { - //leftShoulder - [controller.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL1), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL1)); - } - }]; - //rightShoulder - [controller.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR1), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR1)); - } - }]; - //dpad - [controller.extendedGamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - Q_UNUSED(dpad); - if (xValue > 0) { - //right - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonRight), - Q_ARG(double, 1)); - } else if (xValue < 0) { - //left - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonLeft), - Q_ARG(double, 1)); - } else { - //released - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonRight)); - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonLeft)); - } - if (yValue > 0) { - //up - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonUp), - Q_ARG(double, 1)); - } else if (yValue < 0) { - //down - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonDown), - Q_ARG(double, 1)); - } else { - //released - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonUp)); - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonDown)); - } - }]; - //buttonA - [controller.extendedGamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA)); - } - }]; - //buttonB - [controller.extendedGamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonB), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonB)); - } - }]; - //buttonX - [controller.extendedGamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX)); - } - }]; - //buttonY - [controller.extendedGamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - //Invoke slot - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonY), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonY)); - } - }]; - - //leftThumbstick - [controller.extendedGamepad.leftThumbstick setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - Q_UNUSED(dpad); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisLeftX), - Q_ARG(double, xValue)); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisLeftY), - Q_ARG(double, -yValue)); - }]; - //rightTumbstick - [controller.extendedGamepad.rightThumbstick setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - Q_UNUSED(dpad); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisRightX), - Q_ARG(double, xValue)); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisRightY), - Q_ARG(double, -yValue)); - }]; - //leftTrigger - [controller.extendedGamepad.leftTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL2), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL2)); - } - }]; - //rightTrigger - [controller.extendedGamepad.rightTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR2), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR2)); - } - - }]; - } else if (controller.gamepad) { - //leftShoulder - [controller.gamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL1), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonL1)); - } - }]; - //rightShoulder - [controller.gamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR1), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonR1)); - } - }]; - //dpad - [controller.gamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - Q_UNUSED(dpad); - if (xValue > 0) { - //right - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonRight), - Q_ARG(double, 1)); - } else if (xValue < 0) { - //left - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonLeft), - Q_ARG(double, 1)); - } else { - //released - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonRight)); - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonLeft)); - } - if (yValue > 0) { - //up - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonUp), - Q_ARG(double, 1)); - } else if (yValue < 0) { - //down - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonDown), - Q_ARG(double, 1)); - } else { - //released - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonUp)); - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonDown)); - } - }]; - //buttonA - [controller.gamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA)); - } - }]; - //buttonB - [controller.gamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonB), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonB)); - } - }]; - //buttonX - [controller.gamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX)); - } - }]; - //buttonY - [controller.gamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonY), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonY)); - } - }]; - } -#ifdef Q_OS_TVOS - else if (controller.microGamepad) { - //leftThumbstick - [controller.microGamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue) { - Q_UNUSED(dpad); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisLeftX), - Q_ARG(double, xValue)); - QMetaObject::invokeMethod(backend, "darwinGamepadAxisMoved", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadAxis, QGamepadManager::AxisLeftY), - Q_ARG(double, -yValue)); - }]; - //buttonA - [controller.microGamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonA)); - } - }]; - //buttonX - [controller.microGamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) { - Q_UNUSED(button); - if (pressed) { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonPressed", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX), - Q_ARG(double, value)); - } else { - QMetaObject::invokeMethod(backend, "darwinGamepadButtonReleased", Qt::AutoConnection, - Q_ARG(int, index), - Q_ARG(QGamepadManager::GamepadButton, QGamepadManager::ButtonX)); - } - }]; - } -#endif -} - --(void)removeMonitoredController:(GCController *)controller -{ - int index = -1; - for (int i = 0; i < 4; ++i) { - if (connectedControllers[i] == controller) { - [connectedControllers replaceObjectAtIndex: i withObject: [NSNull null]]; - index = i; - break; - } - } - - QMetaObject::invokeMethod(backend, "darwinGamepadRemoved", Qt::AutoConnection, Q_ARG(int, index)); -} - -@end - -QT_BEGIN_NAMESPACE - -QDarwinGamepadBackend::QDarwinGamepadBackend(QObject *parent) - : QGamepadBackend(parent) - , m_darwinGamepadManager(nullptr) - , m_isMonitoringActive(false) -{ - m_darwinGamepadManager = [[QT_MANGLE_NAMESPACE(DarwinGamepadManager) alloc] initWithBackend:this]; -} - -QDarwinGamepadBackend::~QDarwinGamepadBackend() -{ - [m_darwinGamepadManager release]; -} - -bool QDarwinGamepadBackend::start() -{ - m_isMonitoringActive = true; - return true; -} - -void QDarwinGamepadBackend::stop() -{ - m_isMonitoringActive = false; -} - -void QDarwinGamepadBackend::darwinGamepadAdded(int index) -{ - if (m_isMonitoringActive) { - emit gamepadAdded(index); - m_pauseButtonMap.insert(index, false); - } -} - -void QDarwinGamepadBackend::darwinGamepadRemoved(int index) -{ - if (m_isMonitoringActive) { - emit gamepadRemoved(index); - m_pauseButtonMap.remove(index); - } -} - -void QDarwinGamepadBackend::darwinGamepadAxisMoved(int index, QGamepadManager::GamepadAxis axis, double value) -{ - if (m_isMonitoringActive) - emit gamepadAxisMoved(index, axis, value); -} - -void QDarwinGamepadBackend::darwinGamepadButtonPressed(int index, QGamepadManager::GamepadButton button, double value) -{ - if (m_isMonitoringActive) - emit gamepadButtonPressed(index, button, value); -} - -void QDarwinGamepadBackend::darwinGamepadButtonReleased(int index, QGamepadManager::GamepadButton button) -{ - if (m_isMonitoringActive) - emit gamepadButtonReleased(index, button); -} - -void QDarwinGamepadBackend::handlePauseButton(int index) -{ - //If already pressed - if (m_pauseButtonMap.value(index)) { - emit gamepadButtonReleased(index, QGamepadManager::ButtonStart); - m_pauseButtonMap[index] = false; - } else { - //If not currently pressed - emit gamepadButtonPressed(index, QGamepadManager::ButtonStart, 1.0); - m_pauseButtonMap[index] = true; - } -} - -QT_END_NAMESPACE diff --git a/src/plugins/gamepads/darwin/qdarwingamepadbackend_p.h b/src/plugins/gamepads/darwin/qdarwingamepadbackend_p.h deleted file mode 100644 index 326ccaf..0000000 --- a/src/plugins/gamepads/darwin/qdarwingamepadbackend_p.h +++ /dev/null @@ -1,77 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QDARWINGAMEPADBACKEND_P_H -#define QDARWINGAMEPADBACKEND_P_H - -#include <QtCore/QTimer> -#include <QtCore/QMap> - -#include <QtGamepad/QGamepadManager> -#include <QtGamepad/private/qgamepadbackend_p.h> - -Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(DarwinGamepadManager)); - -QT_BEGIN_NAMESPACE - -class QDarwinGamepadBackend : public QGamepadBackend -{ - Q_OBJECT -public: - explicit QDarwinGamepadBackend(QObject *parent = nullptr); - ~QDarwinGamepadBackend(); - -protected: - bool start() override; - void stop() override; - -public Q_SLOTS: - void darwinGamepadAdded(int index); - void darwinGamepadRemoved(int index); - void darwinGamepadAxisMoved(int index, QGamepadManager::GamepadAxis axis, double value); - void darwinGamepadButtonPressed(int index, QGamepadManager::GamepadButton button, double value); - void darwinGamepadButtonReleased(int index, QGamepadManager::GamepadButton button); - void handlePauseButton(int index); - -private: - QT_MANGLE_NAMESPACE(DarwinGamepadManager) *m_darwinGamepadManager; - bool m_isMonitoringActive; - QMap<int, bool> m_pauseButtonMap; -}; - -QT_END_NAMESPACE - -#endif // QDARWINGAMEPADBACKEND_P_H diff --git a/src/plugins/gamepads/evdev/evdev.json b/src/plugins/gamepads/evdev/evdev.json deleted file mode 100644 index 90c6d7d..0000000 --- a/src/plugins/gamepads/evdev/evdev.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Keys": [ "evdev" ] -} diff --git a/src/plugins/gamepads/evdev/evdev.pro b/src/plugins/gamepads/evdev/evdev.pro deleted file mode 100644 index d819de4..0000000 --- a/src/plugins/gamepads/evdev/evdev.pro +++ /dev/null @@ -1,14 +0,0 @@ -TARGET = evdevgamepad -QT += core-private devicediscovery_support-private gamepad gamepad-private - -PLUGIN_TYPE = gamepads -PLUGIN_CLASS_NAME = QEvdevGamepadBackendPlugin -load(qt_plugin) - -HEADERS += qevdevgamepadbackend_p.h -SOURCES += \ - qevdevgamepadbackend.cpp \ - main.cpp - -OTHER_FILES += \ - evdev.json diff --git a/src/plugins/gamepads/evdev/main.cpp b/src/plugins/gamepads/evdev/main.cpp deleted file mode 100644 index 1832ea5..0000000 --- a/src/plugins/gamepads/evdev/main.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/**************************************************************************** -** -** 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 "qevdevgamepadbackend_p.h" - -QT_BEGIN_NAMESPACE - -class QEvdevGamepadBackendPlugin : public QGamepadBackendPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "evdev.json") -public: - QGamepadBackend *create(const QString &key, const QStringList ¶mList) override; -}; - -QGamepadBackend *QEvdevGamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) -{ - Q_UNUSED(key); - Q_UNUSED(paramList); - - return new QEvdevGamepadBackend; -} - -QT_END_NAMESPACE - -#include "main.moc" diff --git a/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp b/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp deleted file mode 100644 index e57b2c8..0000000 --- a/src/plugins/gamepads/evdev/qevdevgamepadbackend.cpp +++ /dev/null @@ -1,553 +0,0 @@ -/**************************************************************************** -** -** 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 <QtCore/QVariant> -#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 diff --git a/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h b/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h deleted file mode 100644 index 35e2fbd..0000000 --- a/src/plugins/gamepads/evdev/qevdevgamepadbackend_p.h +++ /dev/null @@ -1,138 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QEVDEVGAMEPADBACKEND_P_H -#define QEVDEVGAMEPADBACKEND_P_H - -#include <QtGamepad/QGamepadManager> -#include <QtGamepad/private/qgamepadbackend_p.h> -#include <QtCore/QHash> -#include <QtCore/QList> - -struct input_event; - -QT_BEGIN_NAMESPACE - -class QSocketNotifier; -class QDeviceDiscovery; -class QEvdevGamepadBackend; - -class QEvdevGamepadDevice : public QObject -{ - Q_OBJECT - -public: - QEvdevGamepadDevice(const QByteArray &dev, QEvdevGamepadBackend *backend); - ~QEvdevGamepadDevice(); - QByteArray deviceName() const { return m_dev; } - int deviceId() const { return m_productId; } - void resetConfiguration(); - bool isConfigurationNeeded(); - bool configureButton(QGamepadManager::GamepadButton button); - bool configureAxis(QGamepadManager::GamepadAxis axis); - bool setCancelConfigureButton(QGamepadManager::GamepadButton button); - -private Q_SLOTS: - void readData(); - -private: - void saveData(); - void processInputEvent(input_event *e); - bool openDevice(const QByteArray &dev); - - QByteArray m_dev; - QEvdevGamepadBackend *m_backend; - int m_fd; - int m_productId; - bool m_needsConfigure; - QSocketNotifier *m_notifier; - 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 override; - 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; - }; - 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::EvdevAxisInfo &axisInfo); - -class QEvdevGamepadBackend : public QGamepadBackend -{ - Q_OBJECT - -public: - QEvdevGamepadBackend(); - bool start() override; - void stop() override; - void resetConfiguration(int deviceId) override; - 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; - -private slots: - void handleAddedDevice(const QString &device); - void handleRemovedDevice(const QString &device); - -private: - QEvdevGamepadDevice *newDevice(const QByteArray &device); - QEvdevGamepadDevice *device(int deviceId); - - QDeviceDiscovery *m_discovery; - QList<QEvdevGamepadDevice *> m_devices; -}; - -QT_END_NAMESPACE - -#endif // QEVDEVGAMEPADBACKEND_P_H diff --git a/src/plugins/gamepads/gamepads.pro b/src/plugins/gamepads/gamepads.pro deleted file mode 100644 index 2151b79..0000000 --- a/src/plugins/gamepads/gamepads.pro +++ /dev/null @@ -1,7 +0,0 @@ -TEMPLATE = subdirs -QT_FOR_CONFIG += gui-private gamepad-private -qtConfig(sdl2): SUBDIRS += sdl2 -!android: qtConfig(evdev): SUBDIRS += evdev -win32: SUBDIRS += xinput -darwin: !watchos: SUBDIRS += darwin -android: !android-embedded: SUBDIRS += android diff --git a/src/plugins/gamepads/sdl2/main.cpp b/src/plugins/gamepads/sdl2/main.cpp deleted file mode 100644 index 30b0ceb..0000000 --- a/src/plugins/gamepads/sdl2/main.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** 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 "qsdlgamepadbackend_p.h" - -QT_BEGIN_NAMESPACE - -class QSdl2GamepadBackendPlugin : public QGamepadBackendPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "sdl2.json") -public: - QGamepadBackend *create(const QString &key, const QStringList ¶mList) override; -}; - -QGamepadBackend *QSdl2GamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) { - Q_UNUSED(key); - Q_UNUSED(paramList); - - return new QSdlGamepadBackend(); -} - -QT_END_NAMESPACE - -#include "main.moc" diff --git a/src/plugins/gamepads/sdl2/qsdlgamepadbackend.cpp b/src/plugins/gamepads/sdl2/qsdlgamepadbackend.cpp deleted file mode 100644 index 6a7cee6..0000000 --- a/src/plugins/gamepads/sdl2/qsdlgamepadbackend.cpp +++ /dev/null @@ -1,210 +0,0 @@ -/**************************************************************************** -** -** 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 "qsdlgamepadbackend_p.h" - -#include <QtCore/QDebug> - -#include <SDL.h> -// Reset bool redefinition from SDL header -#undef bool - -QT_BEGIN_NAMESPACE - -QSdlGamepadBackend::QSdlGamepadBackend(QObject *parent) - : QGamepadBackend(parent) -{ - connect(&m_eventLoopTimer, SIGNAL(timeout()), this, SLOT(pumpSdlEventLoop())); -} - -QSdlGamepadBackend::~QSdlGamepadBackend() -{ -} - -void QSdlGamepadBackend::pumpSdlEventLoop() -{ - SDL_Event event; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_CONTROLLERAXISMOTION) { - SDL_ControllerAxisEvent axisEvent = event.caxis; - //qDebug() << axisEvent.timestamp << "Axis Event: " << axisEvent.which << axisEvent.axis << axisEvent.value; - double value; - if (axisEvent.value >= 0) - value = axisEvent.value / 32767.0; - else - value = axisEvent.value / 32768.0; - switch (axisEvent.axis) { - case SDL_CONTROLLER_AXIS_LEFTX: - emit gamepadAxisMoved(m_instanceIdForIndex[axisEvent.which], QGamepadManager::AxisLeftX, value); - break; - case SDL_CONTROLLER_AXIS_LEFTY: - emit gamepadAxisMoved(m_instanceIdForIndex[axisEvent.which], QGamepadManager::AxisLeftY, value); - break; - case SDL_CONTROLLER_AXIS_RIGHTX: - emit gamepadAxisMoved(m_instanceIdForIndex[axisEvent.which], QGamepadManager::AxisRightX, value); - break; - case SDL_CONTROLLER_AXIS_RIGHTY: - emit gamepadAxisMoved(m_instanceIdForIndex[axisEvent.which], QGamepadManager::AxisRightY, value); - break; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - if (value == 0) - emit gamepadButtonReleased(m_instanceIdForIndex[axisEvent.which], QGamepadManager::ButtonL2); - else - emit gamepadButtonPressed(m_instanceIdForIndex[axisEvent.which], QGamepadManager::ButtonL2, value); - break; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - if (value == 0) - emit gamepadButtonReleased(m_instanceIdForIndex[axisEvent.which], QGamepadManager::ButtonR2); - else - emit gamepadButtonPressed(m_instanceIdForIndex[axisEvent.which], QGamepadManager::ButtonR2, value); - break; - default: - break; - } - - } else if (event.type == SDL_CONTROLLERBUTTONDOWN) { - SDL_ControllerButtonEvent buttonEvent = event.cbutton; - //qDebug() << buttonEvent.timestamp << "Button Press: " << buttonEvent.which << buttonEvent.button << buttonEvent.state; - emit gamepadButtonPressed(m_instanceIdForIndex[buttonEvent.which], translateButton(buttonEvent.button), 1.0); - } else if (event.type == SDL_CONTROLLERBUTTONUP) { - SDL_ControllerButtonEvent buttonEvent = event.cbutton; - //qDebug() << buttonEvent.timestamp << "Button Release: " << buttonEvent.which << buttonEvent.button << buttonEvent.state; - emit gamepadButtonReleased(m_instanceIdForIndex[buttonEvent.which], translateButton(buttonEvent.button)); - } else if (event.type == SDL_CONTROLLERDEVICEADDED) { - SDL_ControllerDeviceEvent deviceEvent = event.cdevice; - //qDebug() << deviceEvent.timestamp << "Controller Added: " << deviceEvent.which; - addController(deviceEvent.which); - } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { - SDL_ControllerDeviceEvent deviceEvent = event.cdevice; - - int index = m_instanceIdForIndex[deviceEvent.which]; - SDL_GameControllerClose(m_indexForController[index]); - emit gamepadRemoved(index); - m_indexForController.remove(index); - m_instanceIdForIndex.remove(deviceEvent.which); - - } else if (event.type == SDL_CONTROLLERDEVICEREMAPPED) { - //SDL_ControllerDeviceEvent deviceEvent = event.cdevice; - //qDebug() << deviceEvent.timestamp << "Controller Remapped: " << deviceEvent.which; - } - } -} - -bool QSdlGamepadBackend::start() -{ - //Initialize SDL with necessary subsystems for gamepad support - if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK)) { - qDebug() << SDL_GetError(); - return false; - } - - m_eventLoopTimer.start(16); - for (int i = 0; i < SDL_NumJoysticks() ; i++) - addController(i); - - return true; -} - -void QSdlGamepadBackend::stop() -{ - m_eventLoopTimer.stop(); - SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK); -} - -QGamepadManager::GamepadButton QSdlGamepadBackend::translateButton(int button) -{ - switch (button) { - case SDL_CONTROLLER_BUTTON_A: - return QGamepadManager::ButtonA; - case SDL_CONTROLLER_BUTTON_B: - return QGamepadManager::ButtonB; - case SDL_CONTROLLER_BUTTON_X: - return QGamepadManager::ButtonX; - case SDL_CONTROLLER_BUTTON_Y: - return QGamepadManager::ButtonY; - case SDL_CONTROLLER_BUTTON_BACK: - return QGamepadManager::ButtonSelect; - case SDL_CONTROLLER_BUTTON_GUIDE: - return QGamepadManager::ButtonGuide; - case SDL_CONTROLLER_BUTTON_START: - return QGamepadManager::ButtonStart; - case SDL_CONTROLLER_BUTTON_LEFTSTICK: - return QGamepadManager::ButtonL3; - case SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return QGamepadManager::ButtonR3; - case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return QGamepadManager::ButtonL1; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return QGamepadManager::ButtonR1; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - return QGamepadManager::ButtonUp; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return QGamepadManager::ButtonDown; - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return QGamepadManager::ButtonLeft; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return QGamepadManager::ButtonRight; - default: - return QGamepadManager::ButtonInvalid; - } -} - -void QSdlGamepadBackend::addController(int index) -{ - char GUID[100]; - SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(index), GUID, 100); - if (!SDL_IsGameController(index)) - return; - - SDL_GameController *controller = SDL_GameControllerOpen(index); - if (controller) { - m_indexForController.insert(index, controller); - - SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller); - - int instanceID = SDL_JoystickInstanceID(joystick); - m_instanceIdForIndex.insert(instanceID, index); - - const char *name = SDL_JoystickName(joystick); - - //qDebug() << "Controller " << index << " added with instanceId: " << instanceID; - emit gamepadAdded(index); - - if (name) - emit gamepadNamed(index, QString::fromUtf8(name)); - } -} - -QT_END_NAMESPACE diff --git a/src/plugins/gamepads/sdl2/qsdlgamepadbackend_p.h b/src/plugins/gamepads/sdl2/qsdlgamepadbackend_p.h deleted file mode 100644 index dfd7176..0000000 --- a/src/plugins/gamepads/sdl2/qsdlgamepadbackend_p.h +++ /dev/null @@ -1,74 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QSDLGAMEPADBACKEND_P_H -#define QSDLGAMEPADBACKEND_P_H - -#include <QtCore/QTimer> -#include <QtCore/QMap> - -#include <SDL_gamecontroller.h> - -#include <QtGamepad/QGamepadManager> -#include <QtGamepad/private/qgamepadbackend_p.h> - -QT_BEGIN_NAMESPACE - -class QSdlGamepadBackend : public QGamepadBackend -{ - Q_OBJECT -public: - explicit QSdlGamepadBackend(QObject *parent = nullptr); - ~QSdlGamepadBackend(); - -private Q_SLOTS: - void pumpSdlEventLoop(); - -protected: - bool start() override; - void stop() override; - -private: - QGamepadManager::GamepadButton translateButton(int button); - void addController(int index); - QTimer m_eventLoopTimer; - QMap<int, SDL_GameController*> m_indexForController; - QMap<int, int> m_instanceIdForIndex; -}; - -QT_END_NAMESPACE - -#endif // QSDLGAMEPADBACKEND_P_H diff --git a/src/plugins/gamepads/sdl2/sdl2.json b/src/plugins/gamepads/sdl2/sdl2.json deleted file mode 100644 index d2bd585..0000000 --- a/src/plugins/gamepads/sdl2/sdl2.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Keys": [ "sdl2" ] -} diff --git a/src/plugins/gamepads/sdl2/sdl2.pro b/src/plugins/gamepads/sdl2/sdl2.pro deleted file mode 100644 index 91715a3..0000000 --- a/src/plugins/gamepads/sdl2/sdl2.pro +++ /dev/null @@ -1,17 +0,0 @@ -TARGET = sdl2gamepad -QT += gamepad gamepad-private - -PLUGIN_TYPE = gamepads -PLUGIN_CLASS_NAME = QSdl2GamepadBackendPlugin -load(qt_plugin) - -QMAKE_USE += sdl2 - -HEADERS += qsdlgamepadbackend_p.h -SOURCES += \ - qsdlgamepadbackend.cpp \ - main.cpp - -OTHER_FILES += \ - sdl2.json - diff --git a/src/plugins/gamepads/xinput/main.cpp b/src/plugins/gamepads/xinput/main.cpp deleted file mode 100644 index c157cfd..0000000 --- a/src/plugins/gamepads/xinput/main.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/**************************************************************************** -** -** 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 "qxinputgamepadbackend_p.h" - -QT_BEGIN_NAMESPACE - -class QXInputGamepadBackendPlugin : public QGamepadBackendPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID QtGamepadBackendFactoryInterface_iid FILE "xinput.json") -public: - QGamepadBackend *create(const QString &key, const QStringList ¶mList) override; -}; - -QGamepadBackend *QXInputGamepadBackendPlugin::create(const QString &key, const QStringList ¶mList) -{ - Q_UNUSED(key); - Q_UNUSED(paramList); - - return new QXInputGamepadBackend; -} - -QT_END_NAMESPACE - -#include "main.moc" diff --git a/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp b/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp deleted file mode 100644 index 4d042c9..0000000 --- a/src/plugins/gamepads/xinput/qxinputgamepadbackend.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/**************************************************************************** -** -** 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 "qxinputgamepadbackend_p.h" -#include <QtCore/QLoggingCategory> -#include <QtCore/QThread> -#include <qmath.h> -#include <windows.h> - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcXGB, "qt.gamepad") - -#define POLL_SLEEP_MS 5 -#define POLL_SLOT_CHECK_MS 4000 - -#define XUSER_MAX_COUNT 4 - -#define XINPUT_GAMEPAD_DPAD_UP 0x0001 -#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 -#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 -#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 -#define XINPUT_GAMEPAD_START 0x0010 -#define XINPUT_GAMEPAD_BACK 0x0020 -#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 -#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 -#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 -#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 -#define XINPUT_GAMEPAD_A 0x1000 -#define XINPUT_GAMEPAD_B 0x2000 -#define XINPUT_GAMEPAD_X 0x4000 -#define XINPUT_GAMEPAD_Y 0x8000 - -#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 -#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 -#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 - -struct XINPUT_GAMEPAD -{ - unsigned short wButtons; - unsigned char bLeftTrigger; - unsigned char bRightTrigger; - short sThumbLX; - short sThumbLY; - short sThumbRX; - short sThumbRY; -}; - -struct XINPUT_STATE -{ - unsigned long dwPacketNumber; - XINPUT_GAMEPAD Gamepad; -}; - -typedef DWORD (WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState); -static XInputGetState_t XInputGetState; - -class QXInputThread : public QThread -{ -public: - QXInputThread(QXInputGamepadBackend *backend); - void run() override; - void signalQuit() { m_quit.fetchAndStoreAcquire(1); } - -private: - void dispatch(int idx, XINPUT_GAMEPAD *state); - - QXInputGamepadBackend *m_backend; - QAtomicInt m_quit; - struct Controller { - bool connected; - int skippedPolls; - unsigned long lastPacketNumber; - // State cache. Only want to emit signals for values that really change. - unsigned short buttons; - unsigned char triggers[2]; - double axis[2][2]; - } m_controllers[XUSER_MAX_COUNT]; -}; - -QXInputThread::QXInputThread(QXInputGamepadBackend *backend) - : m_backend(backend), - m_quit(false) -{ - memset(m_controllers, 0, sizeof(m_controllers)); -} - -void QXInputThread::dispatch(int idx, XINPUT_GAMEPAD *state) -{ - static const struct ButtonMap { - unsigned short xbutton; - QGamepadManager::GamepadButton qbutton; - } buttonMap[] = { - { XINPUT_GAMEPAD_DPAD_UP, QGamepadManager::ButtonUp }, - { XINPUT_GAMEPAD_DPAD_DOWN, QGamepadManager::ButtonDown }, - { XINPUT_GAMEPAD_DPAD_LEFT, QGamepadManager::ButtonLeft }, - { XINPUT_GAMEPAD_DPAD_RIGHT, QGamepadManager::ButtonRight }, - { XINPUT_GAMEPAD_START, QGamepadManager::ButtonStart }, - { XINPUT_GAMEPAD_BACK, QGamepadManager::ButtonSelect }, - { XINPUT_GAMEPAD_LEFT_SHOULDER, QGamepadManager::ButtonL1 }, - { XINPUT_GAMEPAD_RIGHT_SHOULDER, QGamepadManager::ButtonR1 }, - { XINPUT_GAMEPAD_LEFT_THUMB, QGamepadManager::ButtonL3 }, - { XINPUT_GAMEPAD_RIGHT_THUMB, QGamepadManager::ButtonR3 }, - { XINPUT_GAMEPAD_A, QGamepadManager::ButtonA }, - { XINPUT_GAMEPAD_B, QGamepadManager::ButtonB }, - { XINPUT_GAMEPAD_X, QGamepadManager::ButtonX }, - { XINPUT_GAMEPAD_Y, QGamepadManager::ButtonY } - }; - for (uint i = 0; i < sizeof(buttonMap) / sizeof(ButtonMap); ++i) { - const unsigned short xb = buttonMap[i].xbutton; - unsigned short isDown = state->wButtons & xb; - if (isDown != (m_controllers[idx].buttons & xb)) { - if (isDown) { - m_controllers[idx].buttons |= xb; - emit m_backend->gamepadButtonPressed(idx, buttonMap[i].qbutton, 1); - } else { - m_controllers[idx].buttons &= ~xb; - emit m_backend->gamepadButtonReleased(idx, buttonMap[i].qbutton); - } - } - } - - if (m_controllers[idx].triggers[0] != state->bLeftTrigger) { - m_controllers[idx].triggers[0] = state->bLeftTrigger; - const double value = state->bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD - ? (state->bLeftTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) - / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) - : 0.0; - if (!qFuzzyIsNull(value)) - emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonL2, value); - else - emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonL2); - } - if (m_controllers[idx].triggers[1] != state->bRightTrigger) { - m_controllers[idx].triggers[1] = state->bRightTrigger; - const double value = state->bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD - ? (state->bRightTrigger - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) - / (255.0 - XINPUT_GAMEPAD_TRIGGER_THRESHOLD) - : 0.0; - if (!qFuzzyIsNull(value)) - emit m_backend->gamepadButtonPressed(idx, QGamepadManager::ButtonR2, value); - else - emit m_backend->gamepadButtonReleased(idx, QGamepadManager::ButtonR2); - } - - double x, y; - if (qSqrt(state->sThumbLX * state->sThumbLX + state->sThumbLY * state->sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { - x = 2 * (state->sThumbLX + 32768.0) / 65535.0 - 1.0; - y = 2 * (-state->sThumbLY + 32768.0) / 65535.0 - 1.0; - } else { - x = y = 0; - } - if (m_controllers[idx].axis[0][0] != x) { - m_controllers[idx].axis[0][0] = x; - emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftX, x); - } - if (m_controllers[idx].axis[0][1] != y) { - m_controllers[idx].axis[0][1] = y; - emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisLeftY, y); - } - if (qSqrt(state->sThumbRX * state->sThumbRX + state->sThumbRY * state->sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { - x = 2 * (state->sThumbRX + 32768.0) / 65535.0 - 1.0; - y = 2 * (-state->sThumbRY + 32768.0) / 65535.0 - 1.0; - } else { - x = y = 0; - } - if (m_controllers[idx].axis[1][0] != x) { - m_controllers[idx].axis[1][0] = x; - emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightX, x); - } - if (m_controllers[idx].axis[1][1] != y) { - m_controllers[idx].axis[1][1] = y; - emit m_backend->gamepadAxisMoved(idx, QGamepadManager::AxisRightY, y); - } -} - -void QXInputThread::run() -{ - qCDebug(lcXGB, "XInput thread running"); - bool firstPoll = true; - while (!m_quit.testAndSetAcquire(1, 0)) { - for (int i = 0; i < XUSER_MAX_COUNT; ++i) { - Controller *controller = m_controllers + i; - - if (!firstPoll && !controller->connected && controller->skippedPolls < POLL_SLOT_CHECK_MS / POLL_SLEEP_MS) { - controller->skippedPolls++; - continue; - } - - firstPoll = false; - controller->skippedPolls = 0; - XINPUT_STATE state; - memset(&state, 0, sizeof(state)); - - if (XInputGetState(i, &state) == ERROR_SUCCESS) { - if (controller->connected) { - if (controller->lastPacketNumber != state.dwPacketNumber) { - controller->lastPacketNumber = state.dwPacketNumber; - dispatch(i, &state.Gamepad); - } - } else { - controller->connected = true; - controller->lastPacketNumber = state.dwPacketNumber; - emit m_backend->gamepadAdded(i); - dispatch(i, &state.Gamepad); - } - } else { - if (controller->connected) { - controller->connected = false; - emit m_backend->gamepadRemoved(i); - } - } - } - - Sleep(POLL_SLEEP_MS); - } - qCDebug(lcXGB, "XInput thread stopping"); -} - -QXInputGamepadBackend::QXInputGamepadBackend() - : m_thread(0) -{ -} - -bool QXInputGamepadBackend::start() -{ - qCDebug(lcXGB) << "start"; - - m_lib.setFileName(QStringLiteral("xinput1_4.dll")); - if (!m_lib.load()) { - m_lib.setFileName(QStringLiteral("xinput1_3.dll")); - m_lib.load(); - } - - if (m_lib.isLoaded()) { - qCDebug(lcXGB, "Loaded XInput library %s", qPrintable(m_lib.fileName())); - XInputGetState = (XInputGetState_t) m_lib.resolve("XInputGetState"); - if (XInputGetState) { - m_thread = new QXInputThread(this); - m_thread->start(); - } else { - qWarning("Failed to resolve XInputGetState"); - } - } else { - qWarning("Failed to load XInput library %s", qPrintable(m_lib.fileName())); - } - - return m_lib.isLoaded(); -} - -void QXInputGamepadBackend::stop() -{ - qCDebug(lcXGB) << "stop"; - m_thread->signalQuit(); - m_thread->wait(); - XInputGetState = 0; -} - -QT_END_NAMESPACE diff --git a/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h b/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h deleted file mode 100644 index 28f5b23..0000000 --- a/src/plugins/gamepads/xinput/qxinputgamepadbackend_p.h +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ -#ifndef QXINPUTGAMEPADBACKEND_P_H -#define QXINPUTGAMEPADBACKEND_P_H - -#include <QtGamepad/QGamepadManager> -#include <QtGamepad/private/qgamepadbackend_p.h> -#include <QtCore/QLibrary> - -QT_BEGIN_NAMESPACE - -class QXInputThread; - -class QXInputGamepadBackend : public QGamepadBackend -{ - Q_OBJECT - -public: - QXInputGamepadBackend(); - bool start() override; - void stop() override; - -private: - QXInputThread *m_thread; - QLibrary m_lib; -}; - -QT_END_NAMESPACE - -#endif // QXINPUTGAMEPADBACKEND_P_H diff --git a/src/plugins/gamepads/xinput/xinput.json b/src/plugins/gamepads/xinput/xinput.json deleted file mode 100644 index f8e3fb2..0000000 --- a/src/plugins/gamepads/xinput/xinput.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Keys": [ "xinput" ] -} diff --git a/src/plugins/gamepads/xinput/xinput.pro b/src/plugins/gamepads/xinput/xinput.pro deleted file mode 100644 index 5dac854..0000000 --- a/src/plugins/gamepads/xinput/xinput.pro +++ /dev/null @@ -1,14 +0,0 @@ -TARGET = xinputgamepad -QT += gamepad gamepad-private - -PLUGIN_TYPE = gamepads -PLUGIN_CLASS_NAME = QXInputGamepadBackendPlugin -load(qt_plugin) - -HEADERS += qxinputgamepadbackend_p.h -SOURCES += \ - qxinputgamepadbackend.cpp \ - main.cpp - -OTHER_FILES += \ - xinput.json diff --git a/src/plugins/joystickinputs/CMakeLists.txt b/src/plugins/joystickinputs/CMakeLists.txt new file mode 100644 index 0000000..1641f8f --- /dev/null +++ b/src/plugins/joystickinputs/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# for each platform, add a subdirectory with the platform name +if(ANDROID) + add_subdirectory(android) +endif() +if(MACOS) + add_subdirectory(macos) +endif() +if(IOS) + add_subdirectory(ios) +endif() +if(WIN32) + add_subdirectory(windows) +endif() +if(LINUX) + add_subdirectory(linux) +endif() + +#add_subdirectory(macos) +#add_subdirectory(ios) +#add_subdirectory(windows) diff --git a/src/plugins/joystickinputs/android/CMakeLists.txt b/src/plugins/joystickinputs/android/CMakeLists.txt new file mode 100644 index 0000000..d55aae7 --- /dev/null +++ b/src/plugins/joystickinputs/android/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +set(java_sources + jar/src/org/qtproject/qt/android/universalinput/QtJoystickInputHandler.java + jar/src/org/qtproject/qt/android/universalinput/QtJoystick.java +) + +qt_internal_add_jar(Qt${QtUniversalInput_VERSION_MAJOR}AndroidJoystickInput + INCLUDE_JARS ${QT_ANDROID_JAR} + SOURCES ${java_sources} + OUTPUT_DIR "${QT_BUILD_DIR}/jar" +) + +qt_path_join(destination ${INSTALL_DATADIR} "jar") + +install_jar(Qt${QtUniversalInput_VERSION_MAJOR}AndroidJoystickInput + DESTINATION ${destination} + COMPONENT Devel +) + + +qt_internal_add_plugin(AndroidJoystickInputPlugin + OUTPUT_NAME androidjoystickinput + PLUGIN_TYPE joystickinputs + DEFAULT_IF ANDROID + SOURCES + androidjoystickinput.cpp androidjoystickinput.h + androidjoystickinputplugin.cpp androidjoystickinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + +) + +set_property( + TARGET + AndroidJoystickInputPlugin + APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES + jar/Qt${QtUniversalInput_VERSION_MAJOR}AndroidJoystickInput.jar +) diff --git a/src/plugins/gamepads/android/src/android.json b/src/plugins/joystickinputs/android/android.json index 6843bd3..6843bd3 100644 --- a/src/plugins/gamepads/android/src/android.json +++ b/src/plugins/joystickinputs/android/android.json diff --git a/src/plugins/joystickinputs/android/androidjoystickinput.cpp b/src/plugins/joystickinputs/android/androidjoystickinput.cpp new file mode 100644 index 0000000..861c1d8 --- /dev/null +++ b/src/plugins/joystickinputs/android/androidjoystickinput.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/android/java_godot_lib_jni.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "androidjoystickinput.h" +#include <QtCore/qnativeinterface.h> +#include <QtUniversalInput/QUniversalInput> + + + +using namespace QtJniTypes; + +Q_DECLARE_JNI_CLASS(KeyEvent, "android/view/KeyEvent") +Q_DECLARE_JNI_CLASS(MotionEvent, "android/view/MotionEvent") + +static void joyConnectionChanged(JNIEnv *, jclass, int deviceId, bool connected, jstring name) +{ + QUniversalInput::instance()->updateJoyConnection(deviceId, connected, QJniObject(name).toString()); +} +Q_DECLARE_JNI_NATIVE_METHOD(joyConnectionChanged) + +static void joyButton(JNIEnv *, jclass, int deviceId, int button, bool pressed) +{ + qDebug() << "joyButton" << deviceId << button << pressed; + QUniversalInput::instance()->joyButton(deviceId, JoyButton(button), pressed); +} +Q_DECLARE_JNI_NATIVE_METHOD(joyButton) + +static void joyAxis(JNIEnv *, jclass, int deviceId, int axis, float value) +{ + QUniversalInput::instance()->joyAxis(deviceId, JoyAxis(axis), value); +} +Q_DECLARE_JNI_NATIVE_METHOD(joyAxis) + +static void joyHat(JNIEnv *, jclass, int deviceId, int hatX, int hatY) +{ + HatMask hat = HatMask::Center; + if (hatX != 0) { + if (hatX < 0) + hat |= HatMask::Left; + else + hat |= HatMask::Right; + } + if (hatY != 0) { + if (hatY < 0) + hat |= HatMask::Up; + else + hat |= HatMask::Down; + } + + QUniversalInput::instance()->joyHat(deviceId, hat); +} +Q_DECLARE_JNI_NATIVE_METHOD(joyHat) + +QT_BEGIN_NAMESPACE + +const char keyEventClass[] = "android/view/KeyEvent"; +inline int keyField(const char *field) +{ + return QJniObject::getStaticField<jint>(keyEventClass, field); +} + + +static void initJNI() +{ + static bool initialized = false; + if (initialized) + return; + initialized = true; + + qWarning() << "initJNI called in Qt Joystick Input Handler"; + + if (!QtJoystickInputHandler::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(joyConnectionChanged), + Q_JNI_NATIVE_METHOD(joyButton), + Q_JNI_NATIVE_METHOD(joyAxis), + Q_JNI_NATIVE_METHOD(joyHat)})) + qCritical("Failed to register native methods for QtJoystickInputHandler"); +} + +AndroidJoystickInput::AndroidJoystickInput() +{ + initJNI(); + m_qtJoystickInputHandler = QtJoystickInputHandler(QtAndroidPrivate::activity()); + QtAndroidPrivate::registerGenericMotionEventListener(this); + QtAndroidPrivate::registerKeyEventListener(this); + start(); +} + +AndroidJoystickInput::~AndroidJoystickInput() +{ + stop(); + QtAndroidPrivate::unregisterGenericMotionEventListener(this); + QtAndroidPrivate::unregisterKeyEventListener(this); +} + +bool AndroidJoystickInput::handleKeyEvent(jobject event) +{ + static int ACTION_DOWN = keyField("ACTION_DOWN"); + static int ACTION_UP = keyField("ACTION_UP"); + QJniObject ev(event); + + // Pass the event to the QtJoystickInputHandler Java class. + if (m_qtJoystickInputHandler.isValid()) { + const int keyCode = ev.callMethod<jint>("getKeyCode", "()I"); + const int action = ev.callMethod<jint>("getAction", "()I"); + + if (action == ACTION_UP) + return m_qtJoystickInputHandler.callMethod<bool>("onKeyUp", keyCode, KeyEvent(event)); + else if (action == ACTION_DOWN) + return m_qtJoystickInputHandler.callMethod<bool>("onKeyDown", keyCode, KeyEvent(event)); + } + + return false; +} + +bool AndroidJoystickInput::handleGenericMotionEvent(jobject event) +{ + if (m_qtJoystickInputHandler.isValid()) + return m_qtJoystickInputHandler.callMethod<bool>("onGenericMotionEvent", MotionEvent(event)); + + return false; +} + +void AndroidJoystickInput::start() +{ + if (QtAndroidPrivate::androidSdkVersion() >= 16) + m_qtJoystickInputHandler.callMethod<void>("register", jlong(this)); +} + +void AndroidJoystickInput::stop() +{ + if (QtAndroidPrivate::androidSdkVersion() >= 16) + m_qtJoystickInputHandler.callMethod<void>("unregister"); +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/android/androidjoystickinput.h b/src/plugins/joystickinputs/android/androidjoystickinput.h new file mode 100644 index 0000000..d79fb43 --- /dev/null +++ b/src/plugins/joystickinputs/android/androidjoystickinput.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef ANDROIDJOYSTICKINPUT_H +#define ANDROIDJOYSTICKINPUT_H + +#include <QtUniversalInput/private/qjoystickinput_p.h> +#include <QtCore/QMutex> +#include <QtCore/QJNIObject.h> +#include <QtCore/qjnitypes.h> +#include <QtCore/private/qjnihelpers_p.h> + +Q_DECLARE_JNI_CLASS(QtJoystickInputHandler, "org/qtproject/qt/android/universalinput/QtJoystickInputHandler"); + +QT_BEGIN_NAMESPACE + +class AndroidJoystickInput : public QJoystickInput, public QtAndroidPrivate::GenericMotionEventListener, public QtAndroidPrivate::KeyEventListener +{ + Q_OBJECT +public: + AndroidJoystickInput(); + ~AndroidJoystickInput(); + + // KeyEventListener interface + bool handleKeyEvent(jobject event) override; + + // GenericMotionEventListener interface + bool handleGenericMotionEvent(jobject event) override; + +private: + void start(); + void stop(); + + QtJniTypes::QtJoystickInputHandler m_qtJoystickInputHandler; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDJOYSTICKINPUT_H diff --git a/src/plugins/joystickinputs/android/androidjoystickinputplugin.cpp b/src/plugins/joystickinputs/android/androidjoystickinputplugin.cpp new file mode 100644 index 0000000..7fc2440 --- /dev/null +++ b/src/plugins/joystickinputs/android/androidjoystickinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "androidjoystickinputplugin.h" +#include "androidjoystickinput.h" + +QT_BEGIN_NAMESPACE + +QJoystickInput *AndroidJoystickInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("android")) + return new AndroidJoystickInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/android/androidjoystickinputplugin.h b/src/plugins/joystickinputs/android/androidjoystickinputplugin.h new file mode 100644 index 0000000..a39c440 --- /dev/null +++ b/src/plugins/joystickinputs/android/androidjoystickinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef ANDROIDJOYSTICKINPUTPLUGIN_H +#define ANDROIDJOYSTICKINPUTPLUGIN_H + +#include <QtUniversalInput/private/qjoystickinputplugin_p.h> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +QT_BEGIN_NAMESPACE + +class AndroidJoystickInputPlugin : public QJoystickInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QJoystickInputFactoryInterface_iid FILE "android.json") + +public: + QJoystickInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDJOYSTICKINPUTPLUGIN_H diff --git a/src/plugins/joystickinputs/android/jar/AndroidManifest.xml b/src/plugins/joystickinputs/android/jar/AndroidManifest.xml new file mode 100644 index 0000000..4636623 --- /dev/null +++ b/src/plugins/joystickinputs/android/jar/AndroidManifest.xml @@ -0,0 +1,6 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.qtproject.qt.android.universalinput" + android:versionCode="1" + android:versionName="1.0" > + <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> +</manifest> diff --git a/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystick.java b/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystick.java new file mode 100644 index 0000000..29a39d6 --- /dev/null +++ b/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystick.java @@ -0,0 +1,28 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +package org.qtproject.qt.android.universalinput; + +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +class Joystick { + int device_id; + String name; + List<Integer> axes = new ArrayList<>(); + protected boolean hasAxisHat = false; + /* + * Keep track of values so we can prevent flooding the engine with useless events. + */ + protected final SparseArray<Float> axesValues = new SparseArray<>(4); + protected int hatX; + protected int hatY; +} diff --git a/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystickInputHandler.java b/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystickInputHandler.java new file mode 100644 index 0000000..b57f34b --- /dev/null +++ b/src/plugins/joystickinputs/android/jar/src/org/qtproject/qt/android/universalinput/QtJoystickInputHandler.java @@ -0,0 +1,308 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +package org.qtproject.qt.android.universalinput; + +import android.app.Activity; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.Build; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class QtJoystickInputHandler implements InputManager.InputDeviceListener { + + private final SparseIntArray m_joystickIds = new SparseIntArray(4); + private final SparseArray<Joystick> m_joystickDevices = new SparseArray<>(4); + private final InputManager m_inputManager; + Activity m_activity; + private long m_nativePtr = 0; + + public QtJoystickInputHandler(Activity activity) { + m_activity = activity; + m_inputManager = (InputManager)activity.getSystemService(Context.INPUT_SERVICE); + } + + public void register(long nativePtr) + { + synchronized (this) { + if (m_inputManager != null) { + m_nativePtr = nativePtr; + m_activity.runOnUiThread(new Runnable() { + @Override + public void run() { + try { + m_inputManager.registerInputDeviceListener(QtJoystickInputHandler.this, null); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + initInputDevices(); + } + } + } + + public void unregister() + { + synchronized (this) { + if (m_inputManager != null) { + m_nativePtr = 0; + m_inputManager.unregisterInputDeviceListener(this); + } + } + } + + private boolean isKeyEventGameDevice(int source) { + if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) + return false; + return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD; + } + + public boolean onKeyUp(final int keyCode, KeyEvent event) { + int source = event.getSource(); + if (isKeyEventGameDevice(source)) { + // Check if the device exists + final int deviceId = event.getDeviceId(); + if (m_joystickIds.indexOfKey(deviceId) >= 0) { + final int button = getButtonValue(keyCode); + final int joyId = m_joystickIds.get(deviceId); + joyButton(joyId, button, false); + return true; + } + } + + return false; + } + + public boolean onKeyDown(final int keyCode, KeyEvent event) { + int source = event.getSource(); + final int deviceId = event.getDeviceId(); + // Check if source is a game device and that the device is a registered gamepad + if (isKeyEventGameDevice(source)) { + if (event.getRepeatCount() > 0) // ignore key echo + return true; + + if (m_joystickIds.indexOfKey(deviceId) >= 0) { + final int button = getButtonValue(keyCode); + final int joyId = m_joystickIds.get(deviceId); + joyButton(joyId, button, true); + return true; + } + } + + return false; + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getActionMasked() == MotionEvent.ACTION_MOVE) { + // Check if the device exists + final int deviceId = event.getDeviceId(); + if (m_joystickIds.indexOfKey(deviceId) >= 0) { + final int joyId = m_joystickIds.get(deviceId); + Joystick joystick = m_joystickDevices.get(deviceId); + if (joystick == null) { + return true; + } + + for (int i = 0; i < joystick.axes.size(); i++) { + final int axis = joystick.axes.get(i); + final float value = event.getAxisValue(axis); + /* + As all axes are polled for each event, only fire an axis event if the value has actually changed. + Prevents flooding Godot with repeated events. + */ + if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) { + // save value to prevent repeats + joystick.axesValues.put(axis, value); + joyAxis(joyId, i, value); + } + } + + if (joystick.hasAxisHat) { + final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X)); + final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (joystick.hatX != hatX || joystick.hatY != hatY) { + joystick.hatX = hatX; + joystick.hatY = hatY; + joyHat(joyId, hatX, hatY); + } + } + return true; + } + } + + return false; + } + + public void initInputDevices() { + int[] deviceIds = m_inputManager.getInputDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice device = m_inputManager.getInputDevice(deviceId); + onInputDeviceAdded(deviceId); + } + } + + private int assignJoystickIdNumber(int deviceId) { + int joyId = 0; + while (m_joystickIds.indexOfValue(joyId) >= 0) { + joyId++; + } + m_joystickIds.put(deviceId, joyId); + return joyId; + } + + @Override + public void onInputDeviceAdded(int deviceId) { + // Check if the device has not been already added + + if (m_joystickIds.indexOfKey(deviceId) >= 0) { + return; + } + + InputDevice device = m_inputManager.getInputDevice(deviceId); + //device can be null if deviceId is not found + if (device == null) { + return; + } + + int sources = device.getSources(); + + // Device may not be a joystick or gamepad + if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD && + (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) { + return; + } + + // Assign first available number. Re-use numbers where possible. + final int id = assignJoystickIdNumber(deviceId); + + final Joystick joystick = new Joystick(); + joystick.device_id = deviceId; + joystick.name = device.getName(); + + Set<Integer> already = new HashSet<>(); + for (InputDevice.MotionRange range : device.getMotionRanges()) { + boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK); + boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD); + if (!isJoystick && !isGamepad) { + continue; + } + final int axis = range.getAxis(); + if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) { + joystick.hasAxisHat = true; + } else { + if (!already.contains(axis)) { + already.add(axis); + joystick.axes.add(axis); + } + } + } + Collections.sort(joystick.axes); + m_joystickDevices.put(deviceId, joystick); + + joyConnectionChanged(id, true, joystick.name); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + // Check if the device has not been already removed + if (m_joystickIds.indexOfKey(deviceId) < 0) { + return; + } + final int joyId = m_joystickIds.get(deviceId); + m_joystickIds.delete(deviceId); + m_joystickDevices.delete(deviceId); + joyConnectionChanged(joyId, false, ""); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + onInputDeviceRemoved(deviceId); + onInputDeviceAdded(deviceId); + } + + public static int getButtonValue(int keyCode) { + int button; + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_A: + button = 0; + break; + case KeyEvent.KEYCODE_BUTTON_B: + button = 1; + break; + case KeyEvent.KEYCODE_BUTTON_X: + button = 2; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + button = 3; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + button = 9; + break; + case KeyEvent.KEYCODE_BUTTON_L2: + button = 15; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + button = 10; + break; + case KeyEvent.KEYCODE_BUTTON_R2: + button = 16; + break; + case KeyEvent.KEYCODE_BUTTON_SELECT: + button = 4; + break; + case KeyEvent.KEYCODE_BUTTON_START: + button = 6; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + button = 7; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + button = 8; + break; + case KeyEvent.KEYCODE_DPAD_UP: + button = 11; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + button = 12; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + button = 13; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + button = 14; + break; + case KeyEvent.KEYCODE_BUTTON_C: + button = 17; + break; + case KeyEvent.KEYCODE_BUTTON_Z: + button = 18; + break; + + default: + button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20; + break; + } + return button; + } + + private static native void joyConnectionChanged(int id, boolean connected, String name); + private static native void joyButton(int id, int button, boolean pressed); + private static native void joyAxis(int id, int axis, float value); + private static native void joyHat(int id, int hatX, int hatY); +} diff --git a/src/plugins/joystickinputs/ios/CMakeLists.txt b/src/plugins/joystickinputs/ios/CMakeLists.txt new file mode 100644 index 0000000..6ee106c --- /dev/null +++ b/src/plugins/joystickinputs/ios/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(IosJoystickInputPlugin + OUTPUT_NAME iosjoystickinput + PLUGIN_TYPE joystickinputs + SOURCES + iosjoystickinput.mm iosjoystickinput.h + iosjoystickinputplugin.cpp iosjoystickinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + ${FWFoundation} + ${FWGameController} +) diff --git a/src/plugins/joystickinputs/ios/ios.json b/src/plugins/joystickinputs/ios/ios.json new file mode 100644 index 0000000..f0b766d --- /dev/null +++ b/src/plugins/joystickinputs/ios/ios.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "ios" ] +} diff --git a/src/plugins/joystickinputs/ios/iosjoystickinput.h b/src/plugins/joystickinputs/ios/iosjoystickinput.h new file mode 100644 index 0000000..e1cf1c0 --- /dev/null +++ b/src/plugins/joystickinputs/ios/iosjoystickinput.h @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef IOSJOYSTICKINPUT_H +#define IOSJOYSTICKINPUT_H + +#include <QtCore/QObject> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +Q_FORWARD_DECLARE_OBJC_CLASS(JoypadIOSObserver); + +QT_BEGIN_NAMESPACE + +class IosJoystickInput : public QJoystickInput{ +public: + IosJoystickInput(); + ~IosJoystickInput(); + + void startProcessing(); + +private: + JoypadIOSObserver *m_observer; +}; + +QT_END_NAMESPACE + +#endif // IOSJOYSTICKINPUT_H diff --git a/src/plugins/joystickinputs/ios/iosjoystickinput.mm b/src/plugins/joystickinputs/ios/iosjoystickinput.mm new file mode 100644 index 0000000..218f195 --- /dev/null +++ b/src/plugins/joystickinputs/ios/iosjoystickinput.mm @@ -0,0 +1,318 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/ios/joypad_ios.mm" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "iosjoystickinput.h" + +#import <GameController/GameController.h> + +@interface JoypadIOSObserver : NSObject + +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadIOSObserver + +- (instancetype)init +{ + self = [super init]; + + if (self) { + self.isObserving = NO; + self.isProcessing = NO; + } + + return self; +} + +- (void)startProcessing +{ + self.isProcessing = YES; + + for (GCController *controller in self.joypadsQueue) { + [self addiOSJoypad:controller]; + } + + [self.joypadsQueue removeAllObjects]; +} + +- (void)startObserving +{ + if (self.isObserving) + return; + + self.isObserving = YES; + + self.connectedJoypads = [NSMutableDictionary dictionary]; + self.joypadsQueue = [NSMutableArray array]; + + // get told when controllers connect, this will be called right away for + // already connected controllers + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasConnected:) + name:GCControllerDidConnectNotification + object:nil]; + + // get told when controllers disconnect + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(controllerWasDisconnected:) + name:GCControllerDidDisconnectNotification + object:nil]; +} + +- (void)finishObserving +{ + if (self.isObserving) + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + self.isObserving = NO; + self.isProcessing = NO; + + self.connectedJoypads = nil; + self.joypadsQueue = nil; +} + +- (void)dealloc +{ + [self finishObserving]; + [super dealloc]; +} + +- (int)getJoyIdForController:(GCController *)controller +{ + NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + + for (NSNumber *key in keys) { + int joy_id = [key intValue]; + return joy_id; + } + + return -1; +} + +- (void)addiOSJoypad:(GCController *)controller +{ + // get a new id for our controller + int joy_id = QUniversalInput::instance()->getUnusedJoyId(); + + if (joy_id == -1) { + printf("Couldn't retrieve new joy id\n"); + return; + } + + // assign our player index + if (controller.playerIndex == GCControllerPlayerIndexUnset) + controller.playerIndex = [self getFreePlayerIndex]; + + + // Report the new controller + QUniversalInput::instance()->updateJoyConnection(joy_id, true, QString::fromUtf8([controller.vendorName UTF8String])); + + // add it to our dictionary, this will retain our controllers + [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]]; + + // set our input handler + [self setControllerInputHandler:controller]; +} + +- (void)controllerWasConnected:(NSNotification *)notification +{ + // get our controller + GCController *controller = (GCController *)notification.object; + + if (!controller) { + printf("Couldn't retrieve new controller\n"); + return; + } + + if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) { + printf("Controller is already registered\n"); + } else if (!self.isProcessing) { + [self.joypadsQueue addObject:controller]; + } else { + [self addiOSJoypad:controller]; + } +} + +- (void)controllerWasDisconnected:(NSNotification *)notification +{ + // find our joystick, there should be only one in our dictionary + GCController *controller = (GCController *)notification.object; + + if (!controller) + return; + + NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + for (NSNumber *key in keys) { + // Report this joystick is no longer there + int joy_id = [key intValue]; + QUniversalInput::instance()->updateJoyConnection(joy_id, false, ""); + + // and remove it from our dictionary + [self.connectedJoypads removeObjectForKey:key]; + } +} + +- (GCControllerPlayerIndex)getFreePlayerIndex { + bool have_player_1 = false; + bool have_player_2 = false; + bool have_player_3 = false; + bool have_player_4 = false; + + if (self.connectedJoypads == nil) { + NSArray *keys = [self.connectedJoypads allKeys]; + for (NSNumber *key in keys) { + GCController *controller = [self.connectedJoypads objectForKey:key]; + if (controller.playerIndex == GCControllerPlayerIndex1) { + have_player_1 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex2) { + have_player_2 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex3) { + have_player_3 = true; + } else if (controller.playerIndex == GCControllerPlayerIndex4) { + have_player_4 = true; + } + } + } + + if (!have_player_1) { + return GCControllerPlayerIndex1; + } else if (!have_player_2) { + return GCControllerPlayerIndex2; + } else if (!have_player_3) { + return GCControllerPlayerIndex3; + } else if (!have_player_4) { + return GCControllerPlayerIndex4; + } else { + return GCControllerPlayerIndexUnset; + } +} + +- (void)setControllerInputHandler:(GCController *)controller +{ + // Hook in the callback handler for the correct gamepad profile. + // This is a bit of a weird design choice on Apples part. + // You need to select the most capable gamepad profile for the + // gamepad attached. + if (controller.extendedGamepad != nil) { + // The extended gamepad profile has all the input you could possibly find on + // a gamepad but will only be active if your gamepad actually has all of + // these... + + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonB) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::B, + gamepad.buttonB.isPressed); + } else if (element == gamepad.buttonX) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.buttonY) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::Y, + gamepad.buttonY.isPressed); + } else if (element == gamepad.leftShoulder) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::LeftShoulder, + gamepad.leftShoulder.isPressed); + } else if (element == gamepad.rightShoulder) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::RightShoulder, + gamepad.rightShoulder.isPressed); + } else if (element == gamepad.dpad) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadUp, + gamepad.dpad.up.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadDown, + gamepad.dpad.down.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadLeft, + gamepad.dpad.left.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadRight, + gamepad.dpad.right.isPressed); + } + + if (element == gamepad.leftThumbstick) { + float value = gamepad.leftThumbstick.xAxis.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::LeftX, value); + value = -gamepad.leftThumbstick.yAxis.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::LeftY, value); + } else if (element == gamepad.rightThumbstick) { + float value = gamepad.rightThumbstick.xAxis.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::RightX, value); + value = -gamepad.rightThumbstick.yAxis.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::RightY, value); + } else if (element == gamepad.leftTrigger) { + float value = gamepad.leftTrigger.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::TriggerLeft, value); + } else if (element == gamepad.rightTrigger) { + float value = gamepad.rightTrigger.value; + QUniversalInput::instance()->joyAxis(joy_id, JoyAxis::TriggerRight, value); + } + }; + } else if (controller.microGamepad != nil) { + // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad + + controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) { + + int joy_id = [self getJoyIdForController:controller]; + + if (element == gamepad.buttonA) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::A, + gamepad.buttonA.isPressed); + } else if (element == gamepad.buttonX) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::X, + gamepad.buttonX.isPressed); + } else if (element == gamepad.dpad) { + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadUp, + gamepad.dpad.up.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadDown, + gamepad.dpad.down.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadLeft, gamepad.dpad.left.isPressed); + QUniversalInput::instance()->joyButton(joy_id, JoyButton::DpadRight, gamepad.dpad.right.isPressed); + } + }; + } +} + +@end + +QT_BEGIN_NAMESPACE + +IosJoystickInput::IosJoystickInput() +{ + m_observer = [[JoypadIOSObserver alloc] init]; + [m_observer startObserving]; + startProcessing(); +} + +IosJoystickInput::~IosJoystickInput() +{ + if (m_observer) { + [m_observer finishObserving]; + m_observer = nil; + } +} + +void IosJoystickInput::startProcessing() +{ + if (m_observer) + [m_observer startProcessing]; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/ios/iosjoystickinputplugin.cpp b/src/plugins/joystickinputs/ios/iosjoystickinputplugin.cpp new file mode 100644 index 0000000..7952f87 --- /dev/null +++ b/src/plugins/joystickinputs/ios/iosjoystickinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "iosjoystickinputplugin.h" +#include "iosjoystickinput.h" + +QT_BEGIN_NAMESPACE + +QJoystickInput *IosJoystickInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("ios")) + return new IosJoystickInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/ios/iosjoystickinputplugin.h b/src/plugins/joystickinputs/ios/iosjoystickinputplugin.h new file mode 100644 index 0000000..c4e04f0 --- /dev/null +++ b/src/plugins/joystickinputs/ios/iosjoystickinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef IOSJOYSTICKINPUTPLUGIN_H +#define IOSJOYSTICKINPUTPLUGIN_H + +#include <QtUniversalInput/private/qjoystickinputplugin_p.h> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +QT_BEGIN_NAMESPACE + +class IosJoystickInputPlugin : public QJoystickInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QJoystickInputFactoryInterface_iid FILE "ios.json") + +public: + QJoystickInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // IOSJOYSTICKINPUTPLUGIN_H diff --git a/src/plugins/joystickinputs/linux/CMakeLists.txt b/src/plugins/joystickinputs/linux/CMakeLists.txt new file mode 100644 index 0000000..7bc01e9 --- /dev/null +++ b/src/plugins/joystickinputs/linux/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(LinuxJoystickInputPlugin + OUTPUT_NAME linuxjoystickinput + PLUGIN_TYPE joystickinputs + SOURCES + linuxjoystickinput.cpp linuxjoystickinput.h + linuxjoystickinputplugin.cpp linuxjoystickinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + # todo: do the dependency check in cmake + udev +) diff --git a/src/plugins/joystickinputs/linux/linux.json b/src/plugins/joystickinputs/linux/linux.json new file mode 100644 index 0000000..8165d39 --- /dev/null +++ b/src/plugins/joystickinputs/linux/linux.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "linux" ] +} diff --git a/src/plugins/joystickinputs/linux/linuxjoystickinput.cpp b/src/plugins/joystickinputs/linux/linuxjoystickinput.cpp new file mode 100644 index 0000000..321c62f --- /dev/null +++ b/src/plugins/joystickinputs/linux/linuxjoystickinput.cpp @@ -0,0 +1,453 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/linuxbsd/joypad_linux.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "linuxjoystickinput.h" + +#include <QtCore/QDir> + +#include <libudev.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <linux/input.h> + +using namespace Qt::Literals::StringLiterals; + +QT_BEGIN_NAMESPACE + +// GODOT begin +#define LONG_BITS (sizeof(long) * 8) +#define NBITS(x) ((((x)-1) / LONG_BITS) + 1) +#define test_bit(nr, addr) (((1UL << ((nr) % LONG_BITS)) & ((addr)[(nr) / LONG_BITS])) != 0) + +static QString ignore_str = u"js"_s; +// GODOT end + +LinuxJoystickInput::LinuxJoystickInput() + : m_udev(nullptr) +{ + m_udev = udev_new(); + if (!m_udev) { + qWarning() << "Could not initialize udev"; + m_udev = nullptr; // ensure udev is nullptr + } + + probeJoypads(); + + m_elapsedTimer.start(); + + // ### Replace with Thread later + startTimer(1); +} + +LinuxJoystickInput::~LinuxJoystickInput() +{ + if (m_udev) + udev_unref(m_udev); + + m_udev = nullptr; +} + +void LinuxJoystickInput::probeJoypads() +{ + if (!m_udev) { + qWarning() << "Could not probe joypads, udev is not initialized"; + return; + } + + struct udev_enumerate *enumerate = udev_enumerate_new(m_udev); + udev_enumerate_add_match_subsystem(enumerate, "input"); + + udev_enumerate_scan_devices(enumerate); + struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); + struct udev_list_entry *entry = nullptr; + udev_list_entry_foreach(entry, devices) { + const char *path = udev_list_entry_get_name(entry); + udev_device *dev = udev_device_new_from_syspath(m_udev, path); + // QString action = udev_device_get_action(dev); + const char *devnode = udev_device_get_devnode(dev); + + if (devnode) { + QString devnode_str = devnode; + + // check if exists + if (std::find(m_attached_devices.begin(), m_attached_devices.end(), devnode_str) != m_attached_devices.end()) + continue; + + if (!devnode_str.contains(ignore_str)) + setupJoypadObject(devnode_str); + } + + udev_device_unref(dev); + } + udev_enumerate_unref(enumerate); +} + +static inline uint16_t BSWAP16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +static inline QString _hex_str(QChar c) +{ + QString str; + str = QString("%1").arg(c.unicode(), 0, 16); + return str; +} + +void LinuxJoystickInput::setupJoypadObject(const QString &device) +{ + auto input = QUniversalInput::instance(); + int id = input->getUnusedJoyId(); + if (id == -1) { + qWarning() << "Could not find unused joypad"; + return; + } + + // GODOT begin ; dont know what is godot and what is ours now + // tries to open the device to check if it's a joystick + int fd = open(device.toUtf8().constData(), O_RDWR | O_NONBLOCK); + if (fd == -1) { + // qWarning() << "Could not open device" << device << "for reading"; + // race condition? the only one that can be opened is xbox360 controller + return; + } + + unsigned long evbit[NBITS(EV_MAX)] = { 0 }; + unsigned long keybit[NBITS(MAX_KEY)] = { 0 }; + unsigned long absbit[NBITS(MAX_ABS)] = { 0 }; + + // add to attached devices so we don't try to open it again + m_attached_devices.push_back(device); + + if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) || + (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) { + close(fd); + return; + } + + // Check if the device supports basic gamepad events + bool has_abs_left = (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit)); + bool has_abs_right = (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit)); + if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && (has_abs_left || has_abs_right))) { + close(fd); + return; + } + + char namebuf[128]; + QString name = ""; + if (ioctl(fd, EVIOCGNAME(sizeof(namebuf)), namebuf) >= 0) + name = namebuf; + + + input_id inpid; + if (ioctl(fd, EVIOCGID, &inpid) < 0) { + close(fd); + return; + } + + // reset gamepad + m_joypads[id] = gamepad(); + auto& joy = m_joypads[id]; + joy.fd = fd; + joy.devpath = QString(device); + joy.attached = true; + joy.id = id; + joy.vibrating = false; + + setupJoypadProperties(&joy); + + char uid[64]; + sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0); + if (inpid.vendor && inpid.product && inpid.version) { + uint16_t vendor = BSWAP16(inpid.vendor); + uint16_t product = BSWAP16(inpid.product); + uint16_t version = BSWAP16(inpid.version); + + sprintf(uid + QString(uid).length(), "%04x%04x%04x%04x%04x%04x", vendor, 0, product, 0, version, 0); + input->updateJoyConnection(id, true, "Udev Joypad", uid); + } else { + QString uidname = uid; + int uidlen = std::min((int)name.length(), 11); + for (int i = 0; i < uidlen; i++) { + uidname = uidname + _hex_str(name[i]); + } + uidname += "00"; + input->updateJoyConnection(id, true, "Udev Joypad", uidname); + } + + // GODOT end +} + +void LinuxJoystickInput::setupJoypadProperties(gamepad* joy) +{ + // GODOT begin + unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; + unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; + + int num_buttons = 0; + int num_axes = 0; + + if ((ioctl(joy->fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) || + (ioctl(joy->fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) { + return; + } + for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) + if (test_bit(i, keybit)) + joy->key_map[i] = num_buttons++; + + for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) + if (test_bit(i, keybit)) + joy->key_map[i] = num_buttons++; + + for (int i = 0; i < ABS_MISC; ++i) { + /* Skip hats */ + if (i == ABS_HAT0X) { + i = ABS_HAT3Y; + continue; + } + if (test_bit(i, absbit)) { + joy->joy_axis[i] = num_axes++; + joy->abs_info[i] = new input_absinfo; + if (ioctl(joy->fd, EVIOCGABS(i), joy->abs_info[i]) < 0) { + delete joy->abs_info[i]; + joy->abs_info[i] = nullptr; + } + } + } + + joy->force_feedback = false; + unsigned long ffbit[NBITS(FF_CNT)]; + if (ioctl(joy->fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) != -1) + if (test_bit(FF_RUMBLE, ffbit)) + joy->force_feedback = true; + + + // GODOT end +} + +void LinuxJoystickInput::closeJoypads() +{ + for (int i = 0; i < JOYPADS_MAX; i++) { + gamepad &joypad = m_joypads[i]; + closeJoypad(joypad, i); + } +} + +void LinuxJoystickInput::closeJoypad(const char *p_devpath) +{ + for (int i = 0; i < JOYPADS_MAX; i++) { + gamepad &joypad = m_joypads[i]; + if (m_joypads[i].devpath == p_devpath) + closeJoypad(joypad, i); + } +} + +void LinuxJoystickInput::closeJoypad(gamepad &p_joypad, int p_id) +{ + auto input = QUniversalInput::instance(); + + if (p_joypad.fd != -1) { + close(p_joypad.fd); + p_joypad.fd = -1; + m_attached_devices.erase(std::find(m_attached_devices.begin(), m_attached_devices.end(), p_joypad.devpath)); + input->updateJoyConnection(p_id, false, ""); + } +} + +static inline float axisCorrect(int value, int min, int max) +{ + return 2.0f * (value - min) / (max - min) - 1.0f; +} + +void LinuxJoystickInput::processJoypads() +{ + auto input = QUniversalInput::instance(); + + for (int i = 0; i < JOYPADS_MAX; i++) { + gamepad& joy = m_joypads[i]; + if (!joy.attached) + continue; + + // get joypad events + input_event event; + std::vector<input_event> events; + while (read(joy.fd, &event, sizeof(event)) > 0) { + events.push_back(event); + } + + if (errno != EAGAIN) { + closeJoypad(joy, i); + continue; + } + + // GODOT begin + + for (const auto& event : events) { + // event may be tainted and out of MAX_KEY range, which will cause + // joy.key_map[event.code] to crash + if (event.code >= MAX_KEY) { + qDebug() << "Joypad event code out of range:" << event.code; + continue; + } + + switch (event.type) { + case EV_KEY: + input->joyButton(joy.id, (JoyButton)joy.key_map[event.code], event.value); + break; + + case EV_ABS: + switch (event.code) { + case ABS_HAT0X: + if (event.value != 0) { + if (event.value < 0) { + joy.dpad = HatMask::Left; + } else { + joy.dpad = HatMask::Right; + } + } else { + joy.dpad = HatMask::Center; + } + input->joyHat(i, joy.dpad); + break; + + case ABS_HAT0Y: + if (event.value != 0) { + if (event.value < 0) { + joy.dpad = HatMask::Up; + } else { + joy.dpad = HatMask::Down; + } + } else { + joy.dpad = HatMask::Center; + } + input->joyHat(i, joy.dpad); + break; + + default: + if (event.code >= MAX_ABS) { + continue; + } + if (joy.abs_info[event.code]) { + // using the min/max values from the device + auto min = joy.abs_info[event.code]->minimum; + auto max = joy.abs_info[event.code]->maximum; + + float value = event.value; + value = axisCorrect(value, min, max); + + JoyAxis axis = JoyAxis::Invalid; + + switch (event.code) { + case ABS_X: + axis = JoyAxis::LeftX; + break; + case ABS_Y: + axis = JoyAxis::LeftY; + break; + case ABS_RX: + axis = JoyAxis::RightX; + break; + case ABS_RY: + axis = JoyAxis::RightY; + break; + case ABS_Z: + axis = JoyAxis::TriggerLeft; + break; + case ABS_RZ: + axis = JoyAxis::TriggerRight; + break; + } + + input->joyAxis(joy.id, axis, value); + } + break; + } + break; + } + } + + if (joy.force_feedback) { + uint64_t timestamp = input->getJoyVibrationTimestamp(joy.id); + float duration = input->getJoyVibrationDuration(joy.id) * 1000.f; + QVector2D strength = input->getJoyVibrationStrength(joy.id); + uint64_t currentTimestamp = QDateTime::currentMSecsSinceEpoch(); + if (currentTimestamp - timestamp <= duration) { + if (!joy.vibrating) + joypadVibrationStart(joy, strength.x(), strength.y(), duration, timestamp); + } else if (joy.vibrating) { + joypadVibrationStop(joy, 0); + } + } + + // GODOT end + } +} + +void LinuxJoystickInput::joypadVibrationStart(gamepad &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) +{ + // GODOT start + + if (!p_joypad.force_feedback || p_joypad.fd == -1 || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) + return; + + if (p_joypad.ff_effect_id != -1) + joypadVibrationStop(p_joypad, p_timestamp); + + struct ff_effect effect; + effect.type = FF_RUMBLE; + effect.id = -1; + effect.u.rumble.weak_magnitude = floor(p_weak_magnitude * (float)0xffff); + effect.u.rumble.strong_magnitude = floor(p_strong_magnitude * (float)0xffff); + effect.replay.length = floor(p_duration); + effect.replay.delay = 0; + + if (ioctl(p_joypad.fd, EVIOCSFF, &effect) < 0) + return; + + struct input_event play; + play.type = EV_FF; + play.code = effect.id; + play.value = 1; + if (write(p_joypad.fd, (const void *)&play, sizeof(play)) == -1) + qWarning() << "Couldn't write to Joypad device."; + + p_joypad.ff_effect_id = effect.id; + + // GODOT end +} + +void LinuxJoystickInput::joypadVibrationStop(gamepad &p_joypad, uint64_t p_timestamp) +{ + Q_UNUSED(p_timestamp); + + // GODOT start + if (!p_joypad.force_feedback || p_joypad.fd == -1 || p_joypad.ff_effect_id == -1) + return; + + if (ioctl(p_joypad.fd, EVIOCRMFF, p_joypad.ff_effect_id) < 0) { + return; + } + + p_joypad.ff_effect_id = -1; + p_joypad.vibrating = false; + // GODOT end +} + +void LinuxJoystickInput::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + probeJoypads(); + processJoypads(); +} + + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/linux/linuxjoystickinput.h b/src/plugins/joystickinputs/linux/linuxjoystickinput.h new file mode 100644 index 0000000..a6cc278 --- /dev/null +++ b/src/plugins/joystickinputs/linux/linuxjoystickinput.h @@ -0,0 +1,101 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/linuxbsd/joypad_linux.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef LINUXJOYSTICKINPUT_H +#define LINUXJOYSTICKINPUT_H + +#include <QtUniversalInput/private/qjoystickinput_p.h> + +#include <QtCore/QElapsedTimer> +#include <QtCore/QLibrary> +#include <QtCore/QThread> + +#include <vector> + +// for JoypadEvent +#include <QtUniversalInput/private/qjoystickinput_p.h> +#include <quniversalinput.h> + +struct udev; +struct input_absinfo; + +QT_BEGIN_NAMESPACE + +class LinuxJoystickInput : public QJoystickInput +{ + Q_OBJECT +public: + LinuxJoystickInput(); + ~LinuxJoystickInput(); + + void probeJoypads(); + void processJoypads(); + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + enum { + JOYPADS_MAX = 16, + JOY_AXIS_COUNT = 6, + MIN_JOY_AXIS = 10, + MAX_JOY_AXIS = 32768, + MAX_JOY_BUTTONS = 128, + KEY_EVENT_BUFFER_SIZE = 512, + MAX_TRIGGER = 1023, // was 255, but xbox one controller max is 1023 + + // from godot linux_joystick.h + MAX_ABS = 63, + MAX_KEY = 767, // Hack because <linux/input.h> can't be included here + }; + + struct gamepad { + int id; + bool attached; + bool confirmed; + int key_map[MAX_KEY]; + int joy_axis[JOY_AXIS_COUNT]; + + HatMask dpad; + + int fd; + QString devpath; + + input_absinfo *abs_info[MAX_ABS] = {}; + + bool force_feedback; + int ff_effect_id; + bool vibrating = false; + + gamepad() { + id = -1; + attached = false; + confirmed = false; + fd = -1; + } + }; + + void setupJoypadObject(const QString& name); + void setupJoypadProperties(gamepad* joy); + void closeJoypads(); + void closeJoypad(const char *p_devpath); + void closeJoypad(gamepad &p_joypad, int p_id); + + void joypadVibrationStart(gamepad &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); + void joypadVibrationStop(gamepad &p_joypad, uint64_t p_timestamp); + + struct udev *m_udev = nullptr; + gamepad m_joypads[JOYPADS_MAX]; // joypad joystick gamestick tomatoe potatoe + std::vector<QString> m_attached_devices; + QElapsedTimer m_elapsedTimer; +}; + +QT_END_NAMESPACE + +#endif // LINUXJOYSTICKINPUT_H diff --git a/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.cpp b/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.cpp new file mode 100644 index 0000000..c234a26 --- /dev/null +++ b/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "linuxjoystickinputplugin.h" +#include "linuxjoystickinput.h" + +QT_BEGIN_NAMESPACE + +QJoystickInput *LinuxJoystickInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("linux")) + return new LinuxJoystickInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.h b/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.h new file mode 100644 index 0000000..7260fa8 --- /dev/null +++ b/src/plugins/joystickinputs/linux/linuxjoystickinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef LINUXJOYSTICKINPUTPLUGIN_H +#define LINUXJOYSTICKINPUTPLUGIN_H + +#include <QtUniversalInput/private/qjoystickinputplugin_p.h> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +QT_BEGIN_NAMESPACE + +class LinuxJoystickInputPlugin : public QJoystickInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QJoystickInputFactoryInterface_iid FILE "linux.json") + +public: + QJoystickInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // LINUXJOYSTICKINPUTPLUGIN_H diff --git a/src/plugins/joystickinputs/macos/CMakeLists.txt b/src/plugins/joystickinputs/macos/CMakeLists.txt new file mode 100644 index 0000000..0e82dfd --- /dev/null +++ b/src/plugins/joystickinputs/macos/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_find_apple_system_framework(FWForceFeedback ForceFeedback) + +qt_internal_add_plugin(MacOSJoystickInputPlugin + OUTPUT_NAME macosjoystickinput + PLUGIN_TYPE joystickinputs + SOURCES + macosjoystickinput.mm macosjoystickinput.h + macosjoystickinputplugin.cpp macosjoystickinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + ${FWIOKit} + ${FWForceFeedback} +) diff --git a/src/plugins/joystickinputs/macos/macos.json b/src/plugins/joystickinputs/macos/macos.json new file mode 100644 index 0000000..a3f3787 --- /dev/null +++ b/src/plugins/joystickinputs/macos/macos.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "macos" ] +} diff --git a/src/plugins/joystickinputs/macos/macosjoystickinput.h b/src/plugins/joystickinputs/macos/macosjoystickinput.h new file mode 100644 index 0000000..a313d4e --- /dev/null +++ b/src/plugins/joystickinputs/macos/macosjoystickinput.h @@ -0,0 +1,104 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef MACOSJOYSTICKINPUT_H +#define MACOSJOYSTICKINPUT_H + +#include <QtUniversalInput/private/qjoystickinput_p.h> +#include <QtCore/QVector> + +#import <ForceFeedback/ForceFeedback.h> +#import <ForceFeedback/ForceFeedbackConstants.h> +#import <IOKit/hid/IOHIDLib.h> +#import <Kernel/IOKit/hidsystem/IOHIDUsageTables.h> + +QT_BEGIN_NAMESPACE + +struct RecElement { + IOHIDElementRef ref; + IOHIDElementCookie cookie; + + uint32_t usage = 0; + + int min = 0; + int max = 0; + + struct Comparator { + bool operator()(const RecElement p_a, const RecElement p_b) const { return p_a.usage < p_b.usage; } + }; +}; + +struct Joypad { + IOHIDDeviceRef deviceRef = nullptr; + + QVector<RecElement> axisElements; + QVector<RecElement> buttonElements; + QVector<RecElement> hatElements; + + int id = 0; + bool offsetHat = false; + + io_service_t forceFeedbackService = 0; + FFCONSTANTFORCE forceFeedbackConstantForce; + FFDeviceObjectReference forceFeedbackDevice = nullptr; + FFEffectObjectReference forcefeedbackObject = nullptr; + quint64 forceFeedbackTimestamp = 0; + LONG *forceFeedbackDirections = nullptr; + FFEFFECT forceFeedbackEffect; + DWORD *forceFeedbackAxes = nullptr; + + void add_hid_elements(CFArrayRef p_array); + void add_hid_element(IOHIDElementRef p_element); + + bool has_element(IOHIDElementCookie p_cookie, QVector<RecElement> *p_list) const; + bool config_force_feedback(io_service_t p_service); + bool check_ff_features(); + + int get_hid_element_state(RecElement *p_element) const; + + void free(); + Joypad(); +}; + +class MacOsJoystickInput : public QJoystickInput +{ + Q_OBJECT +public: + MacOsJoystickInput(); + ~MacOsJoystickInput(); + + void processJoypads(); + + void deviceAdded(IOReturn p_res, IOHIDDeviceRef p_devic); + void deviceRemoved(IOReturn p_res, IOHIDDeviceRef p_device); + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + bool have_device(IOHIDDeviceRef p_device) const; + bool configure_joypad(IOHIDDeviceRef p_device_ref, Joypad *p_joy); + + int get_joy_index(int p_id) const; + int get_joy_ref(IOHIDDeviceRef p_device) const; + + void poll_joypads() const; + void config_hid_manager(CFArrayRef p_matching_array) const; + + void joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp); + void joypad_vibration_stop(int p_id, uint64_t p_timestamp); + + + IOHIDManagerRef m_hidManager; + QVector<Joypad> m_deviceList; +}; + +QT_END_NAMESPACE + +#endif // MACOSJOYSTICKINPUT_H diff --git a/src/plugins/joystickinputs/macos/macosjoystickinput.mm b/src/plugins/joystickinputs/macos/macosjoystickinput.mm new file mode 100644 index 0000000..0a4767a --- /dev/null +++ b/src/plugins/joystickinputs/macos/macosjoystickinput.mm @@ -0,0 +1,623 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "macosjoystickinput.h" +#include <QtUniversalInput/QUniversalInput> +#include <QtEndian> + +QT_BEGIN_NAMESPACE + +#define JOYPAD_LOOP_RUN_MODE CFSTR("QtUniversalInput") + +static MacOsJoystickInput *self = nullptr; + +Joypad::Joypad() +{ + forceFeedbackConstantForce.lMagnitude = 10000; + forceFeedbackEffect.dwDuration = 0; + forceFeedbackEffect.dwSamplePeriod = 0; + forceFeedbackEffect.dwGain = 10000; + forceFeedbackEffect.dwFlags = FFEFF_OBJECTOFFSETS; + forceFeedbackEffect.dwTriggerButton = FFEB_NOTRIGGER; + forceFeedbackEffect.dwStartDelay = 0; + forceFeedbackEffect.dwTriggerRepeatInterval = 0; + forceFeedbackEffect.lpEnvelope = nullptr; + forceFeedbackEffect.cbTypeSpecificParams = sizeof(FFCONSTANTFORCE); + forceFeedbackEffect.lpvTypeSpecificParams = &forceFeedbackConstantForce; + forceFeedbackEffect.dwSize = sizeof(forceFeedbackEffect); +} + +void Joypad::free() +{ + if (deviceRef) + IOHIDDeviceUnscheduleFromRunLoop(deviceRef, CFRunLoopGetCurrent(), JOYPAD_LOOP_RUN_MODE); + + if (forceFeedbackDevice) { + FFDeviceReleaseEffect(forceFeedbackDevice, forcefeedbackObject); + FFReleaseDevice(forceFeedbackDevice); + forceFeedbackDevice = nullptr; + ::free(forceFeedbackAxes); + ::free(forceFeedbackDirections); + } +} + +bool Joypad::has_element(IOHIDElementCookie p_cookie, QVector<RecElement> *p_list) const +{ + for (int i = 0; i < p_list->size(); i++) + if (p_cookie == p_list->at(i).cookie) + return true; + + return false; +} + +int Joypad::get_hid_element_state(RecElement *p_element) const +{ + int value = 0; + if (p_element && p_element->ref) { + IOHIDValueRef valueRef; + if (IOHIDDeviceGetValue(deviceRef, p_element->ref, &valueRef) == kIOReturnSuccess) { + value = SInt32(IOHIDValueGetIntegerValue(valueRef)); + + // Record min and max for auto calibration. + if (value < p_element->min) + p_element->min = value; + + if (value > p_element->max) + p_element->max = value; + } + } + return value; +} + +void Joypad::add_hid_element(IOHIDElementRef p_element) +{ + const CFTypeID elementTypeID = p_element ? CFGetTypeID(p_element) : 0; + + if (p_element && (elementTypeID == IOHIDElementGetTypeID())) { + const IOHIDElementCookie cookie = IOHIDElementGetCookie(p_element); + const uint32_t usagePage = IOHIDElementGetUsagePage(p_element); + const uint32_t usage = IOHIDElementGetUsage(p_element); + QVector<RecElement> *list = nullptr; + + switch (IOHIDElementGetType(p_element)) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: { + switch (usagePage) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_X: + case kHIDUsage_GD_Y: + case kHIDUsage_GD_Z: + case kHIDUsage_GD_Rx: + case kHIDUsage_GD_Ry: + case kHIDUsage_GD_Rz: + case kHIDUsage_GD_Slider: + case kHIDUsage_GD_Dial: + case kHIDUsage_GD_Wheel: + if (!has_element(cookie, &axisElements)) + list = &axisElements; + break; + + case kHIDUsage_GD_Hatswitch: + if (!has_element(cookie, &hatElements)) + list = &hatElements; + break; + case kHIDUsage_GD_DPadUp: + case kHIDUsage_GD_DPadDown: + case kHIDUsage_GD_DPadRight: + case kHIDUsage_GD_DPadLeft: + case kHIDUsage_GD_Start: + case kHIDUsage_GD_Select: + if (!has_element(cookie, &buttonElements)) + list = &buttonElements; + break; + } + break; + + case kHIDPage_Simulation: + switch (usage) { + case kHIDUsage_Sim_Rudder: + case kHIDUsage_Sim_Throttle: + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: + if (!has_element(cookie, &axisElements)) + list = &axisElements; + break; + + default: + break; + } + break; + + case kHIDPage_Button: + case kHIDPage_Consumer: + if (!has_element(cookie, &buttonElements)) + list = &buttonElements; + break; + + default: + break; + } + } break; + + case kIOHIDElementTypeCollection: { + CFArrayRef array = IOHIDElementGetChildren(p_element); + if (array) + add_hid_elements(array); + } break; + + default: + break; + } + + if (list) { // Add to list. + RecElement element; + + element.ref = p_element; + element.usage = usage; + + element.min = (SInt32)IOHIDElementGetLogicalMin(p_element); + element.max = (SInt32)IOHIDElementGetLogicalMax(p_element); + element.cookie = IOHIDElementGetCookie(p_element); + list->push_back(element); + std::sort(list->begin(), list->end(), RecElement::Comparator()); + // list->sort_custom<rec_element::Comparator>(); + } + } +} + +static void hid_element_added(const void *p_value, void *p_parameter) +{ + Joypad *joy = static_cast<Joypad *>(p_parameter); + joy->add_hid_element(IOHIDElementRef(p_value)); +} + +void Joypad::add_hid_elements(CFArrayRef p_array) +{ + CFRange range = { 0, CFArrayGetCount(p_array) }; + CFArrayApplyFunction(p_array, range, hid_element_added, this); +} + +static void joypad_removed_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) +{ + Q_UNUSED(ctx); + Q_UNUSED(sender); + self->deviceRemoved(res, ioHIDDeviceObject); +} + +static void joypad_added_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject) +{ + Q_UNUSED(ctx); + Q_UNUSED(sender); + self->deviceAdded(res, ioHIDDeviceObject); +} + +static bool is_joypad(IOHIDDeviceRef p_device_ref) +{ + int usage_page = 0; + int usage = 0; + CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsagePageKey)); + if (refCF) + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage_page); + + if (usage_page != kHIDPage_GenericDesktop) + return false; + + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDPrimaryUsageKey)); + if (refCF) + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &usage); + + if ((usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController)) + return false; + + return true; +} + +void MacOsJoystickInput::deviceAdded(IOReturn p_res, IOHIDDeviceRef p_device) +{ + if (p_res != kIOReturnSuccess || have_device(p_device)) + return; + + Joypad new_joypad; + if (is_joypad(p_device)) { + configure_joypad(p_device, &new_joypad); + const io_service_t ioservice = IOHIDDeviceGetService(p_device); + if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK) && new_joypad.config_force_feedback(ioservice)) + new_joypad.forceFeedbackService = ioservice; + m_deviceList.push_back(new_joypad); + } + IOHIDDeviceScheduleWithRunLoop(p_device, CFRunLoopGetCurrent(), JOYPAD_LOOP_RUN_MODE); +} + +void MacOsJoystickInput::deviceRemoved(IOReturn p_res, IOHIDDeviceRef p_device) +{ + Q_UNUSED(p_res); + int device = get_joy_ref(p_device); + Q_ASSERT(device != -1); + + auto input = QUniversalInput::instance(); + + input->updateJoyConnection(m_deviceList[device].id, false, ""); + m_deviceList[device].free(); + m_deviceList.removeAt(device); +} + +static QByteArray _hex_str(uint8_t p_byte) +{ + static const char *dict = "0123456789abcdef"; + char ret[3]; + ret[2] = 0; + + ret[0] = dict[p_byte >> 4]; + ret[1] = dict[p_byte & 0xF]; + + return ret; +} + +bool MacOsJoystickInput::configure_joypad(IOHIDDeviceRef p_device_ref, Joypad *p_joy) +{ + p_joy->deviceRef = p_device_ref; + // Get device name. + QByteArray name; + char c_name[256]; + CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); + if (!refCF) + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDManufacturerKey)); + + if ((!refCF) || (!CFStringGetCString((CFStringRef)refCF, c_name, sizeof(c_name), kCFStringEncodingUTF8))) + name = "Unidentified Joypad"; + else + name = c_name; + + auto input = QUniversalInput::instance(); + + int id = input->getUnusedJoyId(); + if (id == -1) + return false; + + p_joy->id = id; + int vendor = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVendorIDKey)); + if (refCF) + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &vendor); + + int product_id = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductIDKey)); + if (refCF) + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &product_id); + + int version = 0; + refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDVersionNumberKey)); + if (refCF) + CFNumberGetValue((CFNumberRef)refCF, kCFNumberSInt32Type, &version); + + if (vendor && product_id) { + uint32_t big3 = qToBigEndian(3); + uint32_t bigVendor = qToBigEndian(vendor); + uint32_t bigProductId = qToBigEndian(product_id); + uint32_t bigVersion = qToBigEndian(version); + + QString uid = QString("%1%2%3%4").arg(big3, 8, 16, QChar('0')).arg(bigVendor, 8, 16, QChar('0')).arg(bigProductId, 8, 16, QChar('0')).arg(bigVersion, 8, 16, QChar('0')); + input->updateJoyConnection(id, true, name, uid); + } else { + // Bluetooth device. + QByteArray guid = "05000000"; + for (int i = 0; i < 12; i++) { + if (i < name.size()) + guid += _hex_str(name[i]); + else + guid += "00"; + } + input->updateJoyConnection(id, true, name, guid); + } + + CFArrayRef array = IOHIDDeviceCopyMatchingElements(p_device_ref, nullptr, kIOHIDOptionsTypeNone); + if (array) { + p_joy->add_hid_elements(array); + CFRelease(array); + } + // Xbox controller hat values start at 1 rather than 0. + p_joy->offsetHat = vendor == 0x45e && + (product_id == 0x0b05 || + product_id == 0x02e0 || + product_id == 0x02fd || + product_id == 0x0b13); + + return true; +} + +#define FF_ERR() \ + { \ + if (ret != FF_OK) { \ + FFReleaseDevice(forceFeedbackDevice); \ + forceFeedbackDevice = nullptr; \ + return false; \ + } \ + } +bool Joypad::config_force_feedback(io_service_t p_service) +{ + HRESULT ret = FFCreateDevice(p_service, &forceFeedbackDevice); + + if (ret != FF_OK) + return false; + + ret = FFDeviceSendForceFeedbackCommand(forceFeedbackDevice, FFSFFC_RESET); + FF_ERR(); + + ret = FFDeviceSendForceFeedbackCommand(forceFeedbackDevice, FFSFFC_SETACTUATORSON); + FF_ERR(); + + if (check_ff_features()) { + ret = FFDeviceCreateEffect(forceFeedbackDevice, kFFEffectType_ConstantForce_ID, &forceFeedbackEffect, &forcefeedbackObject); + FF_ERR(); + return true; + } + FFReleaseDevice(forceFeedbackDevice); + forceFeedbackDevice = nullptr; + return false; +} +#undef FF_ERR + +#define TEST_FF(ff) (features.supportedEffects & (ff)) +bool Joypad::check_ff_features() +{ + FFCAPABILITIES features; + HRESULT ret = FFDeviceGetForceFeedbackCapabilities(forceFeedbackDevice, &features); + if (ret == FF_OK && (features.supportedEffects & FFCAP_ET_CONSTANTFORCE)) { + uint32_t val; + ret = FFDeviceGetForceFeedbackProperty(forceFeedbackDevice, FFPROP_FFGAIN, &val, sizeof(val)); + if (ret != FF_OK) { + return false; + } + int num_axes = features.numFfAxes; + forceFeedbackAxes = (DWORD *)::malloc(sizeof(DWORD) * num_axes); + forceFeedbackDirections = (LONG *)::malloc(sizeof(LONG) * num_axes); + + for (int i = 0; i < num_axes; i++) { + forceFeedbackAxes[i] = features.ffAxes[i]; + forceFeedbackDirections[i] = 0; + } + + forceFeedbackEffect.cAxes = num_axes; + forceFeedbackEffect.rgdwAxes = forceFeedbackAxes; + forceFeedbackEffect.rglDirection = forceFeedbackDirections; + return true; + } + return false; +} + +static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) +{ + int range = (p_max - p_min + 1); + int value = p_value - p_min; + HatMask hat_value = HatMask::Center; + if (range == 4) + value *= 2; + + if (p_offset_hat) + value -= 1; + + switch (value) { + case 0: + hat_value = HatMask::Up; + break; + case 1: + hat_value = (HatMask::Up | HatMask::Right); + break; + case 2: + hat_value = HatMask::Right; + break; + case 3: + hat_value = (HatMask::Down | HatMask::Right); + break; + case 4: + hat_value = HatMask::Down; + break; + case 5: + hat_value = (HatMask::Down | HatMask::Left); + break; + case 6: + hat_value = HatMask::Left; + break; + case 7: + hat_value = (HatMask::Up | HatMask::Left); + break; + default: + hat_value = HatMask::Center; + break; + } + return hat_value; +} + +void MacOsJoystickInput::poll_joypads() const +{ + while (CFRunLoopRunInMode(JOYPAD_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + // No-op. Pending callbacks will fire. + } +} + +static float axis_correct(int p_value, int p_min, int p_max) +{ + // Convert to a value between -1.0f and 1.0f. + return 2.0f * (p_value - p_min) / (p_max - p_min) - 1.0f; +} + +void MacOsJoystickInput::processJoypads() +{ + auto input = QUniversalInput::instance(); + poll_joypads(); + + for (auto &joy : m_deviceList) { + for (int j = 0; j < joy.axisElements.size(); j++) { + RecElement &elem = joy.axisElements[j]; + int value = joy.get_hid_element_state(&elem); + input->joyAxis(joy.id, JoyAxis(j), axis_correct(value, elem.min, elem.max)); + } + for (int j = 0; j < joy.buttonElements.size(); j++) { + int value = joy.get_hid_element_state(&joy.buttonElements[j]); + input->joyButton(joy.id, JoyButton(j), (value >= 1)); + } + for (int j = 0; j < joy.hatElements.size(); j++) { + RecElement &elem = joy.hatElements[j]; + int value = joy.get_hid_element_state(&elem); + HatMask hat_value = process_hat_value(elem.min, elem.max, value, joy.offsetHat); + input->joyHat(joy.id, hat_value); + } + + if (joy.forceFeedbackService) { + uint64_t timestamp = input->getJoyVibrationTimestamp(joy.id); + if (timestamp > joy.forceFeedbackTimestamp) { + QVector2D strength = input->getJoyVibrationStrength(joy.id); + float duration = input->getJoyVibrationDuration(joy.id); + if (strength.x() == 0 && strength.y() == 0) { + joypad_vibration_stop(joy.id, timestamp); + } else { + float gain = qMax(strength.x(), strength.y()); + joypad_vibration_start(joy.id, gain, duration, timestamp); + } + } + } + + } +} + +void MacOsJoystickInput::joypad_vibration_start(int p_id, float p_magnitude, float p_duration, uint64_t p_timestamp) +{ + Joypad *joy = &m_deviceList[get_joy_index(p_id)]; + joy->forceFeedbackTimestamp = p_timestamp; + joy->forceFeedbackEffect.dwDuration = p_duration * FF_SECONDS; + joy->forceFeedbackEffect.dwGain = p_magnitude * FF_FFNOMINALMAX; + FFEffectSetParameters(joy->forcefeedbackObject, &joy->forceFeedbackEffect, FFEP_DURATION | FFEP_GAIN); + FFEffectStart(joy->forcefeedbackObject, 1, 0); +} + +void MacOsJoystickInput::joypad_vibration_stop(int p_id, uint64_t p_timestamp) +{ + Joypad *joy = &m_deviceList[get_joy_index(p_id)]; + joy->forceFeedbackTimestamp = p_timestamp; + FFEffectStop(joy->forcefeedbackObject); +} + +int MacOsJoystickInput::get_joy_index(int p_id) const +{ + for (int i = 0; i < m_deviceList.size(); i++) + if (m_deviceList[i].id == p_id) + return i; + + return -1; +} + +int MacOsJoystickInput::get_joy_ref(IOHIDDeviceRef p_device) const +{ + for (int i = 0; i < m_deviceList.size(); i++) + if (m_deviceList[i].deviceRef == p_device) + return i; + + return -1; +} + +bool MacOsJoystickInput::have_device(IOHIDDeviceRef p_device) const +{ + for (int i = 0; i < m_deviceList.size(); i++) + if (m_deviceList[i].deviceRef == p_device) + return true; + + return false; +} + +static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 usage, int *okay) +{ + CFDictionaryRef retval = nullptr; + CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + + if (pageNumRef && usageNumRef) { + const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) }; + const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef }; + retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + + if (pageNumRef) { + CFRelease(pageNumRef); + } + if (usageNumRef) { + CFRelease(usageNumRef); + } + + if (!retval) { + *okay = 0; + } + + return retval; +} + +void MacOsJoystickInput::config_hid_manager(CFArrayRef p_matching_array) const +{ + CFRunLoopRef runloop = CFRunLoopGetCurrent(); + IOReturn ret = IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone); + Q_ASSERT(ret == kIOReturnSuccess); + + IOHIDManagerSetDeviceMatchingMultiple(m_hidManager, p_matching_array); + IOHIDManagerRegisterDeviceMatchingCallback(m_hidManager, joypad_added_callback, nullptr); + IOHIDManagerRegisterDeviceRemovalCallback(m_hidManager, joypad_removed_callback, nullptr); + IOHIDManagerScheduleWithRunLoop(m_hidManager, runloop, JOYPAD_LOOP_RUN_MODE); + + while (CFRunLoopRunInMode(JOYPAD_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { + // No-op. Callback fires once per existing device. + } +} + +MacOsJoystickInput::MacOsJoystickInput() +{ + self = this; + + int okay = 1; + const void *vals[] = { + (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay), + (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay), + (void *)create_match_dictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay), + }; + const size_t n_elements = sizeof(vals) / sizeof(vals[0]); + CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : nullptr; + + for (size_t i = 0; i < n_elements; i++) + if (vals[i]) + CFRelease((CFTypeRef)vals[i]); + + if (array) { + m_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (m_hidManager) + config_hid_manager(array); + + CFRelease(array); + } + + startTimer(1); +} + +MacOsJoystickInput::~MacOsJoystickInput() +{ + for (auto &joy : m_deviceList) + joy.free(); + m_deviceList.clear(); + + IOHIDManagerUnscheduleFromRunLoop(m_hidManager, CFRunLoopGetCurrent(), JOYPAD_LOOP_RUN_MODE); + IOHIDManagerClose(m_hidManager, kIOHIDOptionsTypeNone); + CFRelease(m_hidManager); + m_hidManager = nullptr; +} + +void MacOsJoystickInput::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + processJoypads(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/macos/macosjoystickinputplugin.cpp b/src/plugins/joystickinputs/macos/macosjoystickinputplugin.cpp new file mode 100644 index 0000000..4128dc2 --- /dev/null +++ b/src/plugins/joystickinputs/macos/macosjoystickinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "macosjoystickinputplugin.h" +#include "macosjoystickinput.h" + +QT_BEGIN_NAMESPACE + +QJoystickInput *MacOsJoystickInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("macos")) + return new MacOsJoystickInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/macos/macosjoystickinputplugin.h b/src/plugins/joystickinputs/macos/macosjoystickinputplugin.h new file mode 100644 index 0000000..8c711ba --- /dev/null +++ b/src/plugins/joystickinputs/macos/macosjoystickinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MACOSJOYSTICKINPUTPLUGIN_H +#define MACOSJOYSTICKINPUTPLUGIN_H + +#include <QtUniversalInput/private/qjoystickinputplugin_p.h> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +QT_BEGIN_NAMESPACE + +class MacOsJoystickInputPlugin : public QJoystickInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QJoystickInputFactoryInterface_iid FILE "macos.json") + +public: + QJoystickInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // MACOSJOYSTICKINPUTPLUGIN_H diff --git a/src/plugins/joystickinputs/windows/CMakeLists.txt b/src/plugins/joystickinputs/windows/CMakeLists.txt new file mode 100644 index 0000000..30ab5b1 --- /dev/null +++ b/src/plugins/joystickinputs/windows/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(WindowsJoystickInputPlugin + OUTPUT_NAME windowsjoystickinput + PLUGIN_TYPE joystickinputs + SOURCES + windowsjoystickinput.cpp windowsjoystickinput.h + windowsjoystickinputplugin.cpp windowsjoystickinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + dinput8 +) diff --git a/src/plugins/joystickinputs/windows/windows.json b/src/plugins/joystickinputs/windows/windows.json new file mode 100644 index 0000000..05032c1 --- /dev/null +++ b/src/plugins/joystickinputs/windows/windows.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "windows" ] +} diff --git a/src/plugins/joystickinputs/windows/windowsjoystickinput.cpp b/src/plugins/joystickinputs/windows/windowsjoystickinput.cpp new file mode 100644 index 0000000..00a12b1 --- /dev/null +++ b/src/plugins/joystickinputs/windows/windowsjoystickinput.cpp @@ -0,0 +1,571 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/windows/joypad_windows.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "windowsjoystickinput.h" + +#include <QtUniversalInput/private/quniversalinput_p.h> + +#include <QtGui/QGuiApplication> +#include <QtGui/QWindow> + +#include <QtCore/QDateTime> + +#include <system_error> + +DWORD WINAPI _xinput_get_state(DWORD dwUserIndex, XINPUT_STATE *pState) +{ + return ERROR_DEVICE_NOT_CONNECTED; +} + +DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) +{ + return ERROR_DEVICE_NOT_CONNECTED; +} + +QT_BEGIN_NAMESPACE + +// Function to convert HRESULT to QString +static QString convertHRESULTToQString(HRESULT hr) +{ + char *msgBuf = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msgBuf, 0, nullptr); + QString msg = QString::fromLocal8Bit(msgBuf); + LocalFree(msgBuf); + return msg; +} + +WindowsJoystickInput::WindowsJoystickInput() +{ + auto qtApp = QGuiApplication::instance(); + if (qtApp && qApp->focusWindow()) + hWnd = (HWND)qApp->focusWindow()->winId(); + // ### Maybe we should delay the initialization until we know there is an available window? + + load_xinput(); + + for (int i = 0; i < JOYPADS_MAX; i++) + attached_joypads[i] = false; + + HRESULT result = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, nullptr); + if (result == DI_OK) { + probeJoypads(); + } else { + qWarning() << "Couldn't initialize DirectInput. Error: " << convertHRESULTToQString(result); + if (result == DIERR_OUTOFMEMORY) { + qWarning("The Windows DirectInput subsystem could not allocate sufficient memory."); + qWarning("Rebooting your PC may solve this issue."); + } + // Ensure dinput is still a nullptr. + dinput = nullptr; + } + + // ### Replace with Thread later + startTimer(1); +} + +WindowsJoystickInput::~WindowsJoystickInput() +{ + close_joypad(); + if (dinput) + dinput->Release(); + unload_xinput(); +} + +void WindowsJoystickInput::probeJoypads() +{ + if (dinput == nullptr) { + qWarning("DirectInput not initialized. Rebooting your PC may solve this issue."); + return; + } + DWORD dwResult; + auto input = QUniversalInput::instance(); + for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { + ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE)); + + dwResult = xinput_get_state(i, &x_joypads[i].state); + if (dwResult == ERROR_SUCCESS) { + int id = input->getUnusedJoyId(); + if (id != -1 && !x_joypads[i].attached) { + x_joypads[i].attached = true; + x_joypads[i].id = id; + x_joypads[i].ff_timestamp = 0; + x_joypads[i].ff_end_timestamp = 0; + x_joypads[i].vibrating = false; + attached_joypads[id] = true; + input->updateJoyConnection(id, true, "XInput Gamepad", "__XINPUT_DEVICE__"); + } + } else if (x_joypads[i].attached) { + x_joypads[i].attached = false; + attached_joypads[x_joypads[i].id] = false; + input->updateJoyConnection(x_joypads[i].id, false, ""); + } + } + + for (int i = 0; i < joypad_count; i++) { + d_joypads[i].confirmed = false; + } + + dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY); + + for (int i = 0; i < joypad_count; i++) { + if (!d_joypads[i].confirmed) { + close_joypad(i); + } + } +} + +void WindowsJoystickInput::processJoypads() +{ + HRESULT hr; + auto input = QUniversalInput::instance(); + for (int i = 0; i < XUSER_MAX_COUNT; i++) { + xinput_gamepad &joy = x_joypads[i]; + if (!joy.attached) { + continue; + } + ZeroMemory(&joy.state, sizeof(XINPUT_STATE)); + + xinput_get_state(i, &joy.state); + if (joy.state.dwPacketNumber != joy.last_packet) { + int button_mask = XINPUT_GAMEPAD_DPAD_UP; + for (int j = 0; j <= 16; j++) { + input->joyButton(joy.id, (JoyButton)j, joy.state.Gamepad.wButtons & button_mask); + button_mask = button_mask * 2; + } + + input->joyAxis(joy.id, JoyAxis::LeftX, axis_correct(joy.state.Gamepad.sThumbLX, true)); + input->joyAxis(joy.id, JoyAxis::LeftY, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true)); + input->joyAxis(joy.id, JoyAxis::RightX, axis_correct(joy.state.Gamepad.sThumbRX, true)); + input->joyAxis(joy.id, JoyAxis::RightY, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true)); + input->joyAxis(joy.id, JoyAxis::TriggerLeft, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true)); + input->joyAxis(joy.id, JoyAxis::TriggerRight, axis_correct(joy.state.Gamepad.bRightTrigger, true, true)); + joy.last_packet = joy.state.dwPacketNumber; + } + uint64_t timestamp = input->getJoyVibrationTimestamp(joy.id); + if (timestamp > joy.ff_timestamp) { + QVector2D strength = input->getJoyVibrationStrength(joy.id); + float duration = input->getJoyVibrationDuration(joy.id); + if (strength.x() == 0 && strength.y() == 0) { + joypad_vibration_stop_xinput(i, timestamp); + } else { + joypad_vibration_start_xinput(i, strength.x(), strength.y(), duration, timestamp); + } + } else if (joy.vibrating && joy.ff_end_timestamp != 0) { + uint64_t currentTime = QDateTime::currentMSecsSinceEpoch(); + if (currentTime >= joy.ff_end_timestamp) + joypad_vibration_stop_xinput(i, currentTime); + } + } + + for (int i = 0; i < JOYPADS_MAX; i++) { + dinput_gamepad *joy = &d_joypads[i]; + + if (!joy->attached) { + continue; + } + + DIJOYSTATE2 js; + hr = joy->di_joy->Poll(); + if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) { + IDirectInputDevice8_Acquire(joy->di_joy); + joy->di_joy->Poll(); + } + + hr = joy->di_joy->GetDeviceState(sizeof(DIJOYSTATE2), &js); + if (FAILED(hr)) { + continue; + } + + post_hat(joy->id, js.rgdwPOV[0]); + + for (int j = 0; j < 128; j++) { + if (js.rgbButtons[j] & 0x80) { + if (!joy->last_buttons[j]) { + input->joyButton(joy->id, (JoyButton)j, true); + joy->last_buttons[j] = true; + } + } else { + if (joy->last_buttons[j]) { + input->joyButton(joy->id, (JoyButton)j, false); + joy->last_buttons[j] = false; + } + } + } + + // on mingw, these constants are not constants + int count = 8; + const LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) }; + int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] }; + + for (int j = 0; j < joy->joy_axis.size(); j++) { + for (int k = 0; k < count; k++) { + if (joy->joy_axis[j] == axes[k]) { + input->joyAxis(joy->id, (JoyAxis)j, axis_correct(values[k])); + break; + } + } + } + } +} + +void WindowsJoystickInput::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + processJoypads(); +} + +bool WindowsJoystickInput::have_device(const GUID &p_guid) +{ + for (int i = 0; i < JOYPADS_MAX; i++) { + if (d_joypads[i].guid == p_guid) { + d_joypads[i].confirmed = true; + return true; + } + } + return false; +} + +bool WindowsJoystickInput::is_xinput_device(const GUID *p_guid) +{ + static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x28DE, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; + static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; + static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; + + if (::memcmp(p_guid, &IID_ValveStreamingGamepad, sizeof(*p_guid)) == 0 || + ::memcmp(p_guid, &IID_X360WiredGamepad, sizeof(*p_guid)) == 0 || + ::memcmp(p_guid, &IID_X360WirelessGamepad, sizeof(*p_guid)) == 0) + return true; + + PRAWINPUTDEVICELIST dev_list = nullptr; + unsigned int dev_list_count = 0; + + if (GetRawInputDeviceList(nullptr, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) + return false; + + dev_list = (PRAWINPUTDEVICELIST)::malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count); + if (dev_list == nullptr) { + qWarning("Out of memory."); + return false; + } + + if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) { + ::free(dev_list); + return false; + } + for (unsigned int i = 0; i < dev_list_count; i++) { + RID_DEVICE_INFO rdi; + char dev_name[128]; + UINT rdiSize = sizeof(rdi); + UINT nameSize = sizeof(dev_name); + + rdi.cbSize = rdiSize; + if ((dev_list[i].dwType == RIM_TYPEHID) && + (GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1) && + (MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)p_guid->Data1) && + (GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICENAME, &dev_name, &nameSize) != (UINT)-1) && + (strstr(dev_name, "IG_") != nullptr)) { + ::free(dev_list); + return true; + } + } + ::free(dev_list); + return false; +} + +static inline uint16_t BSWAP16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +bool WindowsJoystickInput::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) +{ + if (dinput == nullptr) { + qWarning("DirectInput not initialized. Rebooting your PC may solve this issue."); + return false; + } + + HRESULT hr; + auto input = QUniversalInput::instance(); + int num = input->getUnusedJoyId(); + + if (have_device(instance->guidInstance) || num == -1) + return false; + + d_joypads[num] = dinput_gamepad(); + dinput_gamepad *joy = &d_joypads[num]; + + const DWORD devtype = (instance->dwDevType & 0xFF); + + if ((devtype != DI8DEVTYPE_JOYSTICK) && (devtype != DI8DEVTYPE_GAMEPAD) && (devtype != DI8DEVTYPE_1STPERSON) && (devtype != DI8DEVTYPE_DRIVING)) + return false; + + hr = dinput->CreateDevice(instance->guidInstance, &joy->di_joy, nullptr); + + if (FAILED(hr)) + return false; + + const GUID &guid = instance->guidProduct; + char uid[128]; + + if (memcmp(&guid.Data4[2], "PIDVID", 6) != 0) { + qWarning("DirectInput device not recognized."); + return false; + } + + WORD type = BSWAP16(0x03); + WORD vendor = BSWAP16(LOWORD(guid.Data1)); + WORD product = BSWAP16(HIWORD(guid.Data1)); + WORD version = 0; + sprintf_s(uid, "%04x%04x%04x%04x%04x%04x%04x%04x", type, 0, vendor, 0, product, 0, version, 0); + + id_to_change = num; + slider_count = 0; + + joy->di_joy->SetDataFormat(&c_dfDIJoystick2); + joy->di_joy->SetCooperativeLevel(hWnd, DISCL_FOREGROUND); + joy->di_joy->EnumObjects(objectsCallback, this, 0); + std::sort(joy->joy_axis.begin(), joy->joy_axis.end()); + + joy->guid = instance->guidInstance; + input->updateJoyConnection(num, true, QString::fromWCharArray(instance->tszProductName), QString::fromLocal8Bit(uid)); + joy->attached = true; + joy->id = num; + attached_joypads[num] = true; + joy->confirmed = true; + joypad_count++; + return true; +} + +void WindowsJoystickInput::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) +{ + if (ob->dwType & DIDFT_AXIS) { + HRESULT res; + DIPROPRANGE prop_range; + DIPROPDWORD dilong; + LONG ofs; + if (ob->guidType == GUID_XAxis) { + ofs = DIJOFS_X; + } else if (ob->guidType == GUID_YAxis) { + ofs = DIJOFS_Y; + } else if (ob->guidType == GUID_ZAxis) { + ofs = DIJOFS_Z; + } else if (ob->guidType == GUID_RxAxis) { + ofs = DIJOFS_RX; + } else if (ob->guidType == GUID_RyAxis) { + ofs = DIJOFS_RY; + } else if (ob->guidType == GUID_RzAxis) { + ofs = DIJOFS_RZ; + } else if (ob->guidType == GUID_Slider) { + if (slider_count < 2) { + ofs = DIJOFS_SLIDER(slider_count); + slider_count++; + } else { + return; + } + } else { + return; + } + prop_range.diph.dwSize = sizeof(DIPROPRANGE); + prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + prop_range.diph.dwObj = ob->dwType; + prop_range.diph.dwHow = DIPH_BYID; + prop_range.lMin = -MAX_JOY_AXIS; + prop_range.lMax = +MAX_JOY_AXIS; + + dinput_gamepad &joy = d_joypads[p_joy_id]; + + res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_RANGE, &prop_range.diph); + if (FAILED(res)) { + return; + } + + dilong.diph.dwSize = sizeof(dilong); + dilong.diph.dwHeaderSize = sizeof(dilong.diph); + dilong.diph.dwObj = ob->dwType; + dilong.diph.dwHow = DIPH_BYID; + dilong.dwData = 0; + + res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_DEADZONE, &dilong.diph); + if (FAILED(res)) { + return; + } + + joy.joy_axis.push_back(ofs); + } +} + +BOOL CALLBACK WindowsJoystickInput::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) +{ + WindowsJoystickInput *self = static_cast<WindowsJoystickInput *>(p_context); + if (self->is_xinput_device(&p_instance->guidProduct)) { + return DIENUM_CONTINUE; + } + self->setup_dinput_joypad(p_instance); + return DIENUM_CONTINUE; +} + +BOOL CALLBACK WindowsJoystickInput::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_instance, void *p_context) +{ + WindowsJoystickInput *self = static_cast<WindowsJoystickInput *>(p_context); + self->setup_joypad_object(p_instance, self->id_to_change); + + return DIENUM_CONTINUE; +} + +void WindowsJoystickInput::close_joypad(int id) +{ + if (id == -1) { + for (int i = 0; i < JOYPADS_MAX; i++) { + close_joypad(i); + } + return; + } + + if (!d_joypads[id].attached) { + return; + } + + d_joypads[id].di_joy->Unacquire(); + d_joypads[id].di_joy->Release(); + d_joypads[id].attached = false; + attached_joypads[d_joypads[id].id] = false; + d_joypads[id].guid.Data1 = d_joypads[id].guid.Data2 = d_joypads[id].guid.Data3 = 0; + QUniversalInput::instance()->updateJoyConnection(d_joypads[id].id, false, ""); + joypad_count--; +} + +void WindowsJoystickInput::post_hat(int p_device, DWORD p_dpad) +{ + HatMask dpad_val = (HatMask)0; + + // Should be -1 when centered, but according to docs: + // "Some drivers report the centered position of the POV indicator as 65,535. Determine whether the indicator is centered as follows: + // BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);" + // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v%3Dvs.85)#remarks + if (LOWORD(p_dpad) == 0xFFFF) + dpad_val = (HatMask)HatMask::Center; + + if (p_dpad == 0) { + dpad_val = (HatMask)HatMask::Up; + + } else if (p_dpad == 4500) { + dpad_val = (HatMask)(HatMask::Up | HatMask::Right); + + } else if (p_dpad == 9000) { + dpad_val = (HatMask)HatMask::Right; + + } else if (p_dpad == 13500) { + dpad_val = (HatMask)(HatMask::Right | HatMask::Down); + + } else if (p_dpad == 18000) { + dpad_val = (HatMask)HatMask::Down; + + } else if (p_dpad == 22500) { + dpad_val = (HatMask)(HatMask::Down | HatMask::Left); + + } else if (p_dpad == 27000) { + dpad_val = (HatMask)HatMask::Left; + + } else if (p_dpad == 31500) { + dpad_val = (HatMask)(HatMask::Left | HatMask::Up); + } + QUniversalInput::instance()->joyHat(p_device, dpad_val); +} + +float WindowsJoystickInput::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const +{ + if (qFabs(p_val) < MIN_JOY_AXIS) + return p_trigger ? -1.0f : 0.0f; + + if (!p_xinput) + return (float)p_val / MAX_JOY_AXIS; + + // Convert to a value between -1.0f and 1.0f. + if (p_trigger) + return 2.0f * p_val / MAX_TRIGGER - 1.0f; + + float value; + if (p_val < 0) + value = (float)p_val / MAX_JOY_AXIS; + else + value = (float)p_val / (MAX_JOY_AXIS - 1); + + if (p_negate) + value = -value; + + return value; +} + +void WindowsJoystickInput::joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) +{ + xinput_gamepad &joy = x_joypads[p_device]; + if (joy.attached) { + XINPUT_VIBRATION effect; + effect.wLeftMotorSpeed = (65535 * p_strong_magnitude); + effect.wRightMotorSpeed = (65535 * p_weak_magnitude); + if (xinput_set_state(p_device, &effect) == ERROR_SUCCESS) { + joy.ff_timestamp = p_timestamp; + joy.ff_end_timestamp = p_duration == 0 ? 0 : p_timestamp + (uint64_t)(p_duration * 1000); + joy.vibrating = true; + } + } +} + +void WindowsJoystickInput::joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp) +{ + xinput_gamepad &joy = x_joypads[p_device]; + if (joy.attached) { + XINPUT_VIBRATION effect; + effect.wLeftMotorSpeed = 0; + effect.wRightMotorSpeed = 0; + if (xinput_set_state(p_device, &effect) == ERROR_SUCCESS) { + joy.ff_timestamp = p_timestamp; + joy.vibrating = false; + } + } +} + +void WindowsJoystickInput::load_xinput() +{ + xinput_get_state = &_xinput_get_state; + xinput_set_state = &_xinput_set_state; + xinput_dll.setFileName("xinput1_4.dll"); + if (!xinput_dll.load()) { + xinput_dll.setFileName("xinput1_3.dll"); + if (!xinput_dll.load()) { + xinput_dll.setFileName("xinput9_1_0.dll"); + xinput_dll.load(); + } + } + + if (!xinput_dll.isLoaded()) { + qWarning("Could not find XInput, using DirectInput only"); + return; + } + + XInputGetState_t func = (XInputGetState_t)xinput_dll.resolve("XInputGetState"); + XInputSetState_t set_func = (XInputSetState_t)xinput_dll.resolve("XInputSetState"); + + if (!func || !set_func) { + unload_xinput(); + return; + } + xinput_get_state = func; + xinput_set_state = set_func; +} + +void WindowsJoystickInput::unload_xinput() +{ + if (xinput_dll.isLoaded()) + xinput_dll.unload(); +} + + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/windows/windowsjoystickinput.h b/src/plugins/joystickinputs/windows/windowsjoystickinput.h new file mode 100644 index 0000000..eaab459 --- /dev/null +++ b/src/plugins/joystickinputs/windows/windowsjoystickinput.h @@ -0,0 +1,123 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/windows/joypad_windows.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef WINDOWSJOYSTICKINPUT_H +#define WINDOWSJOYSTICKINPUT_H + +#include <QtUniversalInput/private/qjoystickinput_p.h> + +#include <QtCore/QLibrary> + + +#include <windows.h> + +#define DIRECTINPUT_VERSION 0x0800 +#include <dinput.h> +#include <xinput.h> + +QT_BEGIN_NAMESPACE + +class WindowsJoystickInput : public QJoystickInput +{ + Q_OBJECT +public: + WindowsJoystickInput(); + ~WindowsJoystickInput(); + + void probeJoypads(); + void processJoypads(); + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + enum { + JOYPADS_MAX = 16, + JOY_AXIS_COUNT = 6, + MIN_JOY_AXIS = 10, + MAX_JOY_AXIS = 32768, + MAX_JOY_BUTTONS = 128, + KEY_EVENT_BUFFER_SIZE = 512, + MAX_TRIGGER = 255 + }; + + struct dinput_gamepad { + int id; + bool attached; + bool confirmed; + bool last_buttons[MAX_JOY_BUTTONS]; + DWORD last_pad; + + LPDIRECTINPUTDEVICE8 di_joy; + QList<LONG> joy_axis; + GUID guid; + + dinput_gamepad() { + id = -1; + last_pad = -1; + attached = false; + confirmed = false; + di_joy = nullptr; + guid = {}; + + for (int i = 0; i < MAX_JOY_BUTTONS; i++) { + last_buttons[i] = false; + } + } + }; + + struct xinput_gamepad { + int id = 0; + bool attached = false; + bool vibrating = false; + DWORD last_packet = 0; + XINPUT_STATE state; + uint64_t ff_timestamp = 0; + uint64_t ff_end_timestamp = 0; + }; + + typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState); + typedef DWORD(WINAPI *XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); + + HWND hWnd = nullptr; + LPDIRECTINPUT8 dinput = nullptr; + + int id_to_change; + int slider_count; + int joypad_count; + bool attached_joypads[JOYPADS_MAX]; + dinput_gamepad d_joypads[JOYPADS_MAX]; + xinput_gamepad x_joypads[XUSER_MAX_COUNT]; + + static BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context); + static BOOL CALLBACK objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context); + + void setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id); + void close_joypad(int id = -1); + void load_xinput(); + void unload_xinput(); + + void post_hat(int p_device, DWORD p_dpad); + + bool have_device(const GUID &p_guid); + bool is_xinput_device(const GUID *p_guid); + bool setup_dinput_joypad(const DIDEVICEINSTANCE *instance); + void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); + void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp); + + float axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const; + + QLibrary xinput_dll; + XInputGetState_t xinput_get_state = nullptr; + XInputSetState_t xinput_set_state = nullptr; +}; + +QT_END_NAMESPACE + +#endif // WINDOWSJOYSTICKINPUT_H diff --git a/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.cpp b/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.cpp new file mode 100644 index 0000000..da98281 --- /dev/null +++ b/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "windowsjoystickinputplugin.h" +#include "windowsjoystickinput.h" + +QT_BEGIN_NAMESPACE + +QJoystickInput *WindowsJoystickInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("windows")) + return new WindowsJoystickInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.h b/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.h new file mode 100644 index 0000000..a7fb726 --- /dev/null +++ b/src/plugins/joystickinputs/windows/windowsjoystickinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef WINDOWSJOYSTICKINPUTPLUGIN_H +#define WINDOWSJOYSTICKINPUTPLUGIN_H + +#include <QtUniversalInput/private/qjoystickinputplugin_p.h> +#include <QtUniversalInput/private/qjoystickinput_p.h> + +QT_BEGIN_NAMESPACE + +class WindowsJoystickInputPlugin : public QJoystickInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QJoystickInputFactoryInterface_iid FILE "windows.json") + +public: + QJoystickInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // WINDOWSJOYSTICKINPUTPLUGIN_H diff --git a/src/plugins/mouseinputs/CMakeLists.txt b/src/plugins/mouseinputs/CMakeLists.txt new file mode 100644 index 0000000..156e04e --- /dev/null +++ b/src/plugins/mouseinputs/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# for each platform, add a subdirectory with the platform name +#if(ANDROID) +# add_subdirectory(android) +#endif() +if(MACOS) + add_subdirectory(macos) +endif() +#if(IOS) +# add_subdirectory(ios) +#endif() +if(WIN32) + add_subdirectory(windows) +endif() +if(LINUX) + add_subdirectory(linux) +endif() + +#add_subdirectory(macos) +#add_subdirectory(ios) +#add_subdirectory(windows) diff --git a/src/plugins/mouseinputs/linux/CMakeLists.txt b/src/plugins/mouseinputs/linux/CMakeLists.txt new file mode 100644 index 0000000..7d1da23 --- /dev/null +++ b/src/plugins/mouseinputs/linux/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(LinuxMouseInputPlugin + OUTPUT_NAME linuxmouseinput + PLUGIN_TYPE mouseinputs + SOURCES + linuxmouseinput.cpp linuxmouseinput.h + linuxmouseinputplugin.cpp linuxmouseinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate +) diff --git a/src/plugins/mouseinputs/linux/linux.json b/src/plugins/mouseinputs/linux/linux.json new file mode 100644 index 0000000..8165d39 --- /dev/null +++ b/src/plugins/mouseinputs/linux/linux.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "linux" ] +} diff --git a/src/plugins/mouseinputs/linux/linuxmouseinput.cpp b/src/plugins/mouseinputs/linux/linuxmouseinput.cpp new file mode 100644 index 0000000..87cad43 --- /dev/null +++ b/src/plugins/mouseinputs/linux/linuxmouseinput.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "linuxmouseinput.h" +#include <QtUniversalInput/QUniversalInput> +#include <QtEndian> + +#include <QCursor> +#include <QGuiApplication> +#include <QWindow> +#include <QDateTime> + +#include <QCursor> + +QT_BEGIN_NAMESPACE + +static LinuxMouseInput *self = nullptr; + +LinuxMouseInput::LinuxMouseInput() +{ + self = this; + + m_timer = new QTimer(this); + m_timer->setInterval(1); + connect(m_timer, &QTimer::timeout, this, &LinuxMouseInput::onUpdate); + m_timer->start(); +} + +LinuxMouseInput::~LinuxMouseInput() +{ + self = nullptr; + m_timer->stop(); + delete m_timer; +} + +QVector2D LinuxMouseInput::getMouseDelta() +{ + static QPoint lastPos = QCursor::pos(); + QPoint newPos = QCursor::pos(); + QVector2D delta = QVector2D(newPos.toPointF() - lastPos.toPointF()); + lastPos = newPos; + return delta; +} + +void LinuxMouseInput::setCursorCenterOfWindow() +{ + QWindow* window = QGuiApplication::focusWindow(); + if (!window) + return; + QPoint center = window->geometry().center(); + QCursor::setPos(center); +} + +void LinuxMouseInput::onUpdate() +{ + if (!self) + return; + + auto input = QUniversalInput::instance(); + + QVector2D delta = getMouseDelta(); + if (delta.x() == 0 && delta.y() == 0) + return; + + input->mouseMove(delta); + + if (input->isMouseDisabled()) { + setCursorCenterOfWindow(); + // set cursor to blank + QGuiApplication::setOverrideCursor(Qt::BlankCursor); + m_wasDisabled = true; + } else if (m_wasDisabled) { + QGuiApplication::setOverrideCursor(Qt::ArrowCursor); + m_wasDisabled = false; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/linux/linuxmouseinput.h b/src/plugins/mouseinputs/linux/linuxmouseinput.h new file mode 100644 index 0000000..bc67ce4 --- /dev/null +++ b/src/plugins/mouseinputs/linux/linuxmouseinput.h @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef LINUXMOUSEINPUT_H +#define LINUXMOUSEINPUT_H + +#include <QtUniversalInput/private/qmouseinput_p.h> + +#include <QTimer> +#include <QVector2D> + +QT_BEGIN_NAMESPACE + +class LinuxMouseInput : public QMouseInput +{ + Q_OBJECT +public: + LinuxMouseInput(); + ~LinuxMouseInput(); + + QVector2D getMouseDelta(); + void setCursorCenterOfWindow(); + + +public Q_SLOTS: + void onUpdate(); + +private: + QTimer* m_timer = nullptr; + bool m_wasDisabled = false; +}; + +QT_END_NAMESPACE + +#endif // LINUXMOUSEINPUT_H diff --git a/src/plugins/mouseinputs/linux/linuxmouseinputplugin.cpp b/src/plugins/mouseinputs/linux/linuxmouseinputplugin.cpp new file mode 100644 index 0000000..31a1bf1 --- /dev/null +++ b/src/plugins/mouseinputs/linux/linuxmouseinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "linuxmouseinputplugin.h" +#include "linuxmouseinput.h" + +QT_BEGIN_NAMESPACE + +QMouseInput *LinuxMouseInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("linux")) + return new LinuxMouseInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/linux/linuxmouseinputplugin.h b/src/plugins/mouseinputs/linux/linuxmouseinputplugin.h new file mode 100644 index 0000000..a577f49 --- /dev/null +++ b/src/plugins/mouseinputs/linux/linuxmouseinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef LINUXMOUSEINPUTPLUGIN_H +#define LINUXMOUSEINPUTPLUGIN_H + +#include <QtUniversalInput/private/qmouseinputplugin_p.h> +#include <QtUniversalInput/private/qmouseinput_p.h> + +QT_BEGIN_NAMESPACE + +class LinuxMouseInputPlugin : public QMouseInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QMouseInputFactoryInterface_iid FILE "linux.json") + +public: + QMouseInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // LINUXMOUSEINPUTPLUGIN_H diff --git a/src/plugins/mouseinputs/macos/CMakeLists.txt b/src/plugins/mouseinputs/macos/CMakeLists.txt new file mode 100644 index 0000000..5d1005e --- /dev/null +++ b/src/plugins/mouseinputs/macos/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_find_apple_system_framework(FWAppKit AppKit) + +qt_internal_add_plugin(MacOSMouseInputPlugin + OUTPUT_NAME macosmouseinput + PLUGIN_TYPE mouseinputs + SOURCES + macosmouseinput.mm macosmouseinput.h + macosmouseinputplugin.cpp macosmouseinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate + ${FWAppKit} +) diff --git a/src/plugins/mouseinputs/macos/macos.json b/src/plugins/mouseinputs/macos/macos.json new file mode 100644 index 0000000..a3f3787 --- /dev/null +++ b/src/plugins/mouseinputs/macos/macos.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "macos" ] +} diff --git a/src/plugins/mouseinputs/macos/macosmouseinput.h b/src/plugins/mouseinputs/macos/macosmouseinput.h new file mode 100644 index 0000000..aefdc2e --- /dev/null +++ b/src/plugins/mouseinputs/macos/macosmouseinput.h @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef MACOSMOUSEINPUT_H +#define MACOSMOUSEINPUT_H + +#include <QtUniversalInput/private/qmouseinput_p.h> + +#include <QTimer> +#include <QVector2D> + +QT_BEGIN_NAMESPACE + +class MacOsMouseInput : public QMouseInput +{ + Q_OBJECT +public: + MacOsMouseInput(); + ~MacOsMouseInput(); + + QVector2D getMouseDelta(); + void setCursorCenterOfWindow(); + + +public Q_SLOTS: + void onUpdate(); + +private: + QTimer* m_timer = nullptr; + bool m_wasDisabled = false; +}; + +QT_END_NAMESPACE + +#endif // MACOSMOUSEINPUT_H diff --git a/src/plugins/mouseinputs/macos/macosmouseinput.mm b/src/plugins/mouseinputs/macos/macosmouseinput.mm new file mode 100644 index 0000000..abd2a3d --- /dev/null +++ b/src/plugins/mouseinputs/macos/macosmouseinput.mm @@ -0,0 +1,82 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "macosmouseinput.h" +#include <QtUniversalInput/QUniversalInput> +#include <QtEndian> + +#include <AppKit/AppKit.h> +#include <CoreGraphics/CoreGraphics.h> + +#include <QCursor> +#include <QGuiApplication> + +QT_BEGIN_NAMESPACE + +static MacOsMouseInput *self = nullptr; + +MacOsMouseInput::MacOsMouseInput() +{ + self = this; + + m_timer = new QTimer(this); + m_timer->setInterval(1); + connect(m_timer, &QTimer::timeout, this, &MacOsMouseInput::onUpdate); + m_timer->start(); +} + +MacOsMouseInput::~MacOsMouseInput() +{ + self = nullptr; + m_timer->stop(); + delete m_timer; +} + +QVector2D MacOsMouseInput::getMouseDelta() +{ + // kind of a hack, but it works for now + static NSPoint previousLocation; + NSPoint currentLocation = [NSEvent mouseLocation]; + CGPoint delta = CGPointMake(currentLocation.x - previousLocation.x, currentLocation.y - previousLocation.y); + previousLocation = currentLocation; + return QVector2D(float(delta.x), float(delta.y)); +} + +void MacOsMouseInput::setCursorCenterOfWindow() +{ + NSRect windowRect = [[NSApp mainWindow] frame]; + NSPoint windowCenter = NSMakePoint(NSMidX(windowRect), NSMidY(windowRect)); + CGWarpMouseCursorPosition(windowCenter); +} + +void MacOsMouseInput::onUpdate() +{ + if (!self) + return; + + auto input = QUniversalInput::instance(); + + QVector2D delta = getMouseDelta(); + if (delta.x() == 0 && delta.y() == 0) + return; + + input->mouseMove(delta); + + if (input->isMouseDisabled()) { + setCursorCenterOfWindow(); + // set cursor to blank + QGuiApplication::setOverrideCursor(Qt::BlankCursor); + m_wasDisabled = true; + } else if (m_wasDisabled) { + QGuiApplication::setOverrideCursor(Qt::ArrowCursor); + m_wasDisabled = false; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/macos/macosmouseinputplugin.cpp b/src/plugins/mouseinputs/macos/macosmouseinputplugin.cpp new file mode 100644 index 0000000..88411ac --- /dev/null +++ b/src/plugins/mouseinputs/macos/macosmouseinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "macosmouseinputplugin.h" +#include "macosmouseinput.h" + +QT_BEGIN_NAMESPACE + +QMouseInput *MacOsMouseInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("macos")) + return new MacOsMouseInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/macos/macosmouseinputplugin.h b/src/plugins/mouseinputs/macos/macosmouseinputplugin.h new file mode 100644 index 0000000..6245974 --- /dev/null +++ b/src/plugins/mouseinputs/macos/macosmouseinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef MACOSMOUSEINPUTPLUGIN_H +#define MACOSMOUSEINPUTPLUGIN_H + +#include <QtUniversalInput/private/qmouseinputplugin_p.h> +#include <QtUniversalInput/private/qmouseinput_p.h> + +QT_BEGIN_NAMESPACE + +class MacOsMouseInputPlugin : public QMouseInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QMouseInputFactoryInterface_iid FILE "macos.json") + +public: + QMouseInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // MACOSMOUSEINPUTPLUGIN_H diff --git a/src/plugins/mouseinputs/windows/CMakeLists.txt b/src/plugins/mouseinputs/windows/CMakeLists.txt new file mode 100644 index 0000000..c981277 --- /dev/null +++ b/src/plugins/mouseinputs/windows/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(WindowsMouseInputPlugin + OUTPUT_NAME windowsmouseinput + PLUGIN_TYPE mouseinputs + SOURCES + windowsmouseinput.cpp windowsmouseinput.h + windowsmouseinputplugin.cpp windowsmouseinputplugin.h + LIBRARIES + Qt::Core + Qt::Gui + Qt::UniversalInput + Qt::UniversalInputPrivate +) diff --git a/src/plugins/mouseinputs/windows/windows.json b/src/plugins/mouseinputs/windows/windows.json new file mode 100644 index 0000000..05032c1 --- /dev/null +++ b/src/plugins/mouseinputs/windows/windows.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "windows" ] +} diff --git a/src/plugins/mouseinputs/windows/windowsmouseinput.cpp b/src/plugins/mouseinputs/windows/windowsmouseinput.cpp new file mode 100644 index 0000000..d6b86d9 --- /dev/null +++ b/src/plugins/mouseinputs/windows/windowsmouseinput.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.cpp" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#include "windowsmouseinput.h" +#include <QtUniversalInput/QUniversalInput> +#include <QtEndian> + +#include <QCursor> +#include <QGuiApplication> +#include <QWindow> + +QT_BEGIN_NAMESPACE + +static WindowsMouseInput *self = nullptr; + +WindowsMouseInput::WindowsMouseInput() +{ + self = this; + + m_timer = new QTimer(this); + m_timer->setInterval(1); + connect(m_timer, &QTimer::timeout, this, &WindowsMouseInput::onUpdate); + m_timer->start(); +} + +WindowsMouseInput::~WindowsMouseInput() +{ + self = nullptr; + m_timer->stop(); + delete m_timer; +} + +QVector2D WindowsMouseInput::getMouseDelta() +{ + // kind of a hack, but it works for now + static QVector2D previousLocation; + auto pos = QCursor::pos(); + QVector2D currentLocation = {float(pos.x()), float(pos.y())}; + QVector2D delta = currentLocation - previousLocation; + previousLocation = currentLocation; + return delta; +} + +void WindowsMouseInput::setCursorCenterOfWindow() +{ + QWindow* window = QGuiApplication::focusWindow(); + if (!window) + return; + QPoint center = window->geometry().center(); + QCursor::setPos(center); +} + +void WindowsMouseInput::onUpdate() +{ + if (!self) + return; + + auto input = QUniversalInput::instance(); + + QVector2D delta = getMouseDelta(); + if (delta.x() == 0 && delta.y() == 0) { + return; + } + + input->mouseMove(delta); + + if (input->isMouseDisabled()) { + setCursorCenterOfWindow(); + // set cursor to blank + QGuiApplication::setOverrideCursor(Qt::BlankCursor); + m_wasDisabled = true; + } else if (m_wasDisabled) { + QGuiApplication::setOverrideCursor(Qt::ArrowCursor); + m_wasDisabled = false; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/windows/windowsmouseinput.h b/src/plugins/mouseinputs/windows/windowsmouseinput.h new file mode 100644 index 0000000..01b5cfa --- /dev/null +++ b/src/plugins/mouseinputs/windows/windowsmouseinput.h @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/* + Originally based on code from "platform/macos/joypad_macos.h" from Godot Engine v4.0 + Copyright (c) 2014-present Godot Engine contributors + Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. +*/ + +#ifndef WINDOWSMOUSEINPUT_H +#define WINDOWSMOUSEINPUT_H + +#include <QtUniversalInput/private/qmouseinput_p.h> + +#include <QTimer> +#include <QVector2D> + +QT_BEGIN_NAMESPACE + +class WindowsMouseInput : public QMouseInput +{ + Q_OBJECT +public: + WindowsMouseInput(); + ~WindowsMouseInput(); + + QVector2D getMouseDelta(); + void setCursorCenterOfWindow(); + + +public Q_SLOTS: + void onUpdate(); + +private: + QTimer* m_timer = nullptr; + bool m_wasDisabled = false; +}; + +QT_END_NAMESPACE + +#endif // WINDOWSMOUSEINPUT_H diff --git a/src/plugins/mouseinputs/windows/windowsmouseinputplugin.cpp b/src/plugins/mouseinputs/windows/windowsmouseinputplugin.cpp new file mode 100644 index 0000000..516751b --- /dev/null +++ b/src/plugins/mouseinputs/windows/windowsmouseinputplugin.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "windowsmouseinputplugin.h" +#include "windowsmouseinput.h" + +QT_BEGIN_NAMESPACE + +QMouseInput *WindowsMouseInputPlugin::create(const QString &key, const QStringList ¶mList) +{ + Q_UNUSED(paramList); + if (key == QLatin1String("windows")) + return new WindowsMouseInput(); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/mouseinputs/windows/windowsmouseinputplugin.h b/src/plugins/mouseinputs/windows/windowsmouseinputplugin.h new file mode 100644 index 0000000..a527047 --- /dev/null +++ b/src/plugins/mouseinputs/windows/windowsmouseinputplugin.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef WINDOWSMOUSEINPUTPLUGIN_H +#define WINDOWSMOUSEINPUTPLUGIN_H + +#include <QtUniversalInput/private/qmouseinputplugin_p.h> +#include <QtUniversalInput/private/qmouseinput_p.h> + +QT_BEGIN_NAMESPACE + +class WindowsMouseInputPlugin : public QMouseInputPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QMouseInputFactoryInterface_iid FILE "windows.json") + +public: + QMouseInput *create(const QString &key, const QStringList ¶mList) override; +}; + +QT_END_NAMESPACE + +#endif // WINDOWSMOUSEINPUTPLUGIN_H diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro deleted file mode 100644 index ac81e82..0000000 --- a/src/plugins/plugins.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += gamepads |