1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 BogDan Vatra <bogdan@kde.org>
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Gamepad module
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 #include "qandroidgamepadbackend_p.h"
37
38 #include <QtCore/QCoreApplication>
39 #include <QtCore/QEvent>
40 #include <QtCore/QPair>
41 #include <QtCore/QThread>
42 #include <QtCore/QVector>
43
44 #include <functional>
45 #include <vector>
46
47 #include <QVariant>
48 #include <jni.h>
49 #include <math.h>
50
51 QT_BEGIN_NAMESPACE
52 namespace {
53 const QLatin1String AXES_KEY("axes");
54 const QLatin1String BUTTONS_KEY("buttons");
55
56 class FunctionEvent : public QEvent
57 {
58 typedef std::function<void()> Function;
59 public:
FunctionEvent(const Function & func)60 explicit FunctionEvent(const Function &func)
61 : QEvent(QEvent::User)
62 , m_function(func)
63 {}
call()64 inline void call() { m_function(); }
runOnQtThread(QObject * receiver,const Function & func)65 static inline void runOnQtThread(QObject *receiver, const Function &func) {
66 if (qApp->thread() == QThread::currentThread())
67 func();
68 else
69 qApp->postEvent(receiver, new FunctionEvent(func));
70 }
71
72 private:
73 Function m_function;
74 };
75
76 const char keyEventClass[] = "android/view/KeyEvent";
keyField(const char * field)77 inline int keyField(const char *field)
78 {
79 return QJNIObjectPrivate::getStaticField<jint>(keyEventClass, field);
80 }
81
82 const char motionEventClass[] = "android/view/MotionEvent";
motionField(const char * field)83 inline int motionField(const char *field)
84 {
85 return QJNIObjectPrivate::getStaticField<jint>(motionEventClass, field);
86 }
87
88 const char inputDeviceClass[] = "android/view/InputDevice";
inputDeviceField(const char * field)89 inline int inputDeviceField(const char *field)
90 {
91 return QJNIObjectPrivate::getStaticField<jint>(inputDeviceClass, field);
92 }
93
94
95 struct DefaultMapping : public QAndroidGamepadBackend::Mapping {
DefaultMapping__anona1af88740111::DefaultMapping96 DefaultMapping()
97 {
98 buttonsMap[keyField("KEYCODE_BUTTON_A")] = QGamepadManager::ButtonA;
99 buttonsMap[keyField("KEYCODE_BUTTON_B")] = QGamepadManager::ButtonB;
100 buttonsMap[keyField("KEYCODE_BUTTON_X")] = QGamepadManager::ButtonX;
101 buttonsMap[keyField("KEYCODE_BUTTON_Y")] = QGamepadManager::ButtonY;
102 buttonsMap[keyField("KEYCODE_BUTTON_L1")] = QGamepadManager::ButtonL1;
103 buttonsMap[keyField("KEYCODE_BUTTON_R1")] = QGamepadManager::ButtonR1;
104 buttonsMap[keyField("KEYCODE_BUTTON_L2")] = QGamepadManager::ButtonL2;
105 buttonsMap[keyField("KEYCODE_BUTTON_R2")] = QGamepadManager::ButtonR2;
106 buttonsMap[keyField("KEYCODE_BUTTON_SELECT")] = QGamepadManager::ButtonSelect;
107 buttonsMap[keyField("KEYCODE_BUTTON_START")] = QGamepadManager::ButtonStart;
108 buttonsMap[keyField("KEYCODE_BUTTON_THUMBL")] = QGamepadManager::ButtonL3;
109 buttonsMap[keyField("KEYCODE_BUTTON_THUMBR")] = QGamepadManager::ButtonR3;
110 buttonsMap[keyField("KEYCODE_DPAD_UP")] = QGamepadManager::ButtonUp;
111 buttonsMap[keyField("KEYCODE_DPAD_DOWN")] = QGamepadManager::ButtonDown;
112 buttonsMap[keyField("KEYCODE_DPAD_RIGHT")] = QGamepadManager::ButtonRight;
113 buttonsMap[keyField("KEYCODE_DPAD_LEFT")] = QGamepadManager::ButtonLeft;
114 buttonsMap[keyField("KEYCODE_DPAD_CENTER")] = QGamepadManager::ButtonCenter;
115 buttonsMap[keyField("KEYCODE_BUTTON_MODE")] = QGamepadManager::ButtonGuide;
116
117 if (QtAndroidPrivate::androidSdkVersion() >= 12) {
118 axisMap[motionField("AXIS_X")].gamepadAxis = QGamepadManager::AxisLeftX;
119 axisMap[motionField("AXIS_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
120 axisMap[motionField("AXIS_HAT_X")].gamepadAxis = QGamepadManager::AxisLeftX;
121 axisMap[motionField("AXIS_HAT_Y")].gamepadAxis = QGamepadManager::AxisLeftY;
122 axisMap[motionField("AXIS_Z")].gamepadAxis = QGamepadManager::AxisRightX;
123 axisMap[motionField("AXIS_RZ")].gamepadAxis = QGamepadManager::AxisRightY;
124 {
125 auto &axis = axisMap[motionField("AXIS_LTRIGGER")];
126 axis.gamepadAxis = QGamepadManager::AxisInvalid;
127 axis.gamepadMinButton = QGamepadManager::ButtonL2;
128 axis.gamepadMaxButton = QGamepadManager::ButtonL2;
129 }
130 {
131 auto &axis = axisMap[motionField("AXIS_RTRIGGER")];
132 axis.gamepadAxis = QGamepadManager::AxisInvalid;
133 axis.gamepadMinButton = QGamepadManager::ButtonR2;
134 axis.gamepadMaxButton = QGamepadManager::ButtonR2;
135 }
136
137 allAndroidAxes.push_back(motionField("AXIS_X"));
138 allAndroidAxes.push_back(motionField("AXIS_Y"));
139 allAndroidAxes.push_back(motionField("AXIS_Z"));
140 allAndroidAxes.push_back(motionField("AXIS_RZ"));
141 allAndroidAxes.push_back(motionField("AXIS_BRAKE"));
142 allAndroidAxes.push_back(motionField("AXIS_GAS"));
143
144 for (int i = 1; i < 16; ++i)
145 allAndroidAxes.push_back(motionField(QByteArray("AXIS_GENERIC_").append(QByteArray::number(i)).constData()));
146
147 allAndroidAxes.push_back(motionField("AXIS_HAT_X"));
148 allAndroidAxes.push_back(motionField("AXIS_HAT_Y"));
149 allAndroidAxes.push_back(motionField("AXIS_LTRIGGER"));
150 allAndroidAxes.push_back(motionField("AXIS_RTRIGGER"));
151 allAndroidAxes.push_back(motionField("AXIS_RUDDER"));
152 allAndroidAxes.push_back(motionField("AXIS_THROTTLE"));
153 allAndroidAxes.push_back(motionField("AXIS_WHEEL"));
154 }
155
156 if (QtAndroidPrivate::androidSdkVersion() >= 12) {
157 acceptedSources.push_back(inputDeviceField("SOURCE_GAMEPAD"));
158 acceptedSources.push_back(inputDeviceField("SOURCE_CLASS_JOYSTICK"));
159 if (QtAndroidPrivate::androidSdkVersion() >= 21) {
160 acceptedSources.push_back(inputDeviceField("SOURCE_HDMI"));
161 }
162 } else {
163 acceptedSources.push_back(inputDeviceField("SOURCE_DPAD"));
164 }
165
166 ACTION_DOWN = keyField("ACTION_DOWN");
167 ACTION_UP = keyField("ACTION_UP");
168 ACTION_MULTIPLE = keyField("ACTION_MULTIPLE");
169 FLAG_LONG_PRESS = keyField("FLAG_LONG_PRESS");
170 }
171 std::vector<int> acceptedSources;
172 std::vector<int> allAndroidAxes;
173 int ACTION_DOWN, ACTION_MULTIPLE, ACTION_UP, FLAG_LONG_PRESS;
174 };
175
onInputDeviceAdded(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)176 void onInputDeviceAdded(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
177 {
178 if (!qtNativePtr)
179 return;
180 reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->addDevice(deviceId);
181 }
onInputDeviceRemoved(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)182 void onInputDeviceRemoved(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
183 {
184 if (!qtNativePtr)
185 return;
186 reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->removeDevice(deviceId);
187 }
onInputDeviceChanged(JNIEnv *,jclass,jlong qtNativePtr,int deviceId)188 void onInputDeviceChanged(JNIEnv *, jclass, jlong qtNativePtr, int deviceId)
189 {
190 if (!qtNativePtr)
191 return;
192 reinterpret_cast<QAndroidGamepadBackend*>(qtNativePtr)->updateDevice(deviceId);
193 }
194
195 static JNINativeMethod methods[] = {
196 {"onInputDeviceAdded", "(JI)V", (void *)onInputDeviceAdded},
197 {"onInputDeviceRemoved", "(JI)V", (void *)onInputDeviceRemoved},
198 {"onInputDeviceChanged", "(JI)V", (void *)onInputDeviceChanged}
199 };
200
201 const char qtGamePadClassName[] = "org/qtproject/qt5/android/gamepad/QtGamepad";
202
setAxisInfo(QJNIObjectPrivate & event,int axis,QAndroidGamepadBackend::Mapping::AndroidAxisInfo & info)203 inline void setAxisInfo(QJNIObjectPrivate &event, int axis, QAndroidGamepadBackend::Mapping::AndroidAxisInfo &info)
204 {
205 QJNIObjectPrivate device(event.callObjectMethod("getDevice", "()Landroid/view/InputDevice;"));
206 if (device.isValid()) {
207 const int source = event.callMethod<jint>("getSource", "()I");
208 QJNIObjectPrivate motionRange = device.callObjectMethod("getMotionRange","(II)Landroid/view/InputDevice$MotionRange;", axis, source);
209 if (motionRange.isValid()) {
210 info.flatArea = motionRange.callMethod<jfloat>("getFlat", "()F");
211 info.minValue = motionRange.callMethod<jfloat>("getMin", "()F");
212 info.maxValue = motionRange.callMethod<jfloat>("getMax", "()F");
213 info.fuzz = motionRange.callMethod<jfloat>("getFuzz", "()F");
214 return;
215 }
216 }
217 info.flatArea = 0;
218 }
219
220 } // namespace
221
Q_GLOBAL_STATIC(DefaultMapping,g_defaultMapping)222 Q_GLOBAL_STATIC(DefaultMapping, g_defaultMapping)
223
224 void QAndroidGamepadBackend::Mapping::AndroidAxisInfo::restoreSavedData(const QVariantMap &value)
225 {
226 gamepadAxis = QGamepadManager::GamepadAxis(value[QLatin1String("axis")].toInt());
227 gamepadMinButton = QGamepadManager::GamepadButton(value[QLatin1String("minButton")].toInt());
228 gamepadMaxButton = QGamepadManager::GamepadButton(value[QLatin1String("maxButton")].toInt());
229 }
230
dataToSave() const231 QVariantMap QAndroidGamepadBackend::Mapping::AndroidAxisInfo::dataToSave() const
232 {
233 QVariantMap data;
234 data[QLatin1String("axis")] = gamepadAxis;
235 data[QLatin1String("minButton")] = gamepadMinButton;
236 data[QLatin1String("maxButton")] = gamepadMaxButton;
237 return data;
238 }
239
QAndroidGamepadBackend(QObject * parent)240 QAndroidGamepadBackend::QAndroidGamepadBackend(QObject *parent)
241 : QGamepadBackend(parent)
242 {
243 QtAndroidPrivate::registerGenericMotionEventListener(this);
244 QtAndroidPrivate::registerKeyEventListener(this);
245 }
246
~QAndroidGamepadBackend()247 QAndroidGamepadBackend::~QAndroidGamepadBackend()
248 {
249 QtAndroidPrivate::unregisterGenericMotionEventListener(this);
250 QtAndroidPrivate::unregisterKeyEventListener(this);
251 }
252
addDevice(int deviceId)253 void QAndroidGamepadBackend::addDevice(int deviceId)
254 {
255 if (deviceId == -1)
256 return;
257
258 QMutexLocker lock(&m_mutex);
259 QJNIObjectPrivate inputDevice = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDevice", "(I)Landroid/view/InputDevice;", deviceId);
260 int sources = inputDevice.callMethod<jint>("getSources", "()I");
261 bool acceptable = false;
262 for (int source : g_defaultMapping()->acceptedSources) {
263 if ( (source & sources) == source) {
264 acceptable = true;
265 break;
266 }
267 }
268
269 if (acceptable) {
270 m_devices.insert(deviceId, *g_defaultMapping());
271 int productId = qHash(inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString());
272 m_devices[deviceId].productId = productId;
273 if (productId) {
274 QVariant settings = readSettings(productId);
275 if (!settings.isNull()) {
276 auto &deviceInfo = m_devices[deviceId];
277 deviceInfo.needsConfigure = false;
278
279 QVariantMap data = settings.toMap()[AXES_KEY].toMap();
280 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
281 deviceInfo.axisMap[it.key().toInt()].restoreSavedData(it.value().toMap());
282
283 data = settings.toMap()[BUTTONS_KEY].toMap();
284 for (QVariantMap::const_iterator it = data.begin(); it != data.end(); ++it)
285 deviceInfo.buttonsMap[it.key().toInt()] = QGamepadManager::GamepadButton(it.value().toInt());
286 }
287 }
288 FunctionEvent::runOnQtThread(this, [this, deviceId]{
289 emit gamepadAdded(deviceId);
290 });
291 }
292 }
293
updateDevice(int deviceId)294 void QAndroidGamepadBackend::updateDevice(int deviceId)
295 {
296 Q_UNUSED(deviceId)
297 //QMutexLocker lock(&m_mutex);
298 }
299
removeDevice(int deviceId)300 void QAndroidGamepadBackend::removeDevice(int deviceId)
301 {
302 QMutexLocker lock(&m_mutex);
303 if (m_devices.remove(deviceId)) {
304 FunctionEvent::runOnQtThread(this, [this, deviceId]{
305 emit gamepadRemoved(deviceId);
306 });
307 }
308 }
309
event(QEvent * ev)310 bool QAndroidGamepadBackend::event(QEvent *ev)
311 {
312 if (ev->type() == QEvent::User) {
313 static_cast<FunctionEvent*>(ev)->call();
314 return true;
315 }
316 return QGamepadBackend::event(ev);
317 }
318
isConfigurationNeeded(int deviceId)319 bool QAndroidGamepadBackend::isConfigurationNeeded(int deviceId)
320 {
321 QMutexLocker lock(&m_mutex);
322 auto it = m_devices.find(deviceId);
323 if (it == m_devices.end())
324 return false;
325 return it->needsConfigure;
326 }
327
configureButton(int deviceId,QGamepadManager::GamepadButton button)328 bool QAndroidGamepadBackend::configureButton(int deviceId, QGamepadManager::GamepadButton button)
329 {
330 QMutexLocker lock(&m_mutex);
331 auto it = m_devices.find(deviceId);
332 if (it == m_devices.end())
333 return false;
334
335 it.value().calibrateButton = button;
336 return true;
337 }
338
configureAxis(int deviceId,QGamepadManager::GamepadAxis axis)339 bool QAndroidGamepadBackend::configureAxis(int deviceId, QGamepadManager::GamepadAxis axis)
340 {
341 QMutexLocker lock(&m_mutex);
342 auto it = m_devices.find(deviceId);
343 if (it == m_devices.end())
344 return false;
345
346 it.value().calibrateAxis = axis;
347 return true;
348 }
349
setCancelConfigureButton(int deviceId,QGamepadManager::GamepadButton button)350 bool QAndroidGamepadBackend::setCancelConfigureButton(int deviceId, QGamepadManager::GamepadButton button)
351 {
352 QMutexLocker lock(&m_mutex);
353 auto it = m_devices.find(deviceId);
354 if (it == m_devices.end())
355 return false;
356
357 it.value().cancelConfigurationButton = button;
358 return true;
359 }
360
resetConfiguration(int deviceId)361 void QAndroidGamepadBackend::resetConfiguration(int deviceId)
362 {
363 QMutexLocker lock(&m_mutex);
364 auto it = m_devices.find(deviceId);
365 if (it == m_devices.end())
366 return;
367
368 it.value().axisMap.clear();
369 it.value().buttonsMap.clear();
370 it.value().calibrateButton = QGamepadManager::ButtonInvalid;
371 it.value().calibrateAxis = QGamepadManager::AxisInvalid;
372 it.value().cancelConfigurationButton = QGamepadManager::ButtonInvalid;
373 it.value().needsConfigure = false;
374 }
375
handleKeyEvent(jobject event)376 bool QAndroidGamepadBackend::handleKeyEvent(jobject event)
377 {
378 QJNIObjectPrivate ev(event);
379 QMutexLocker lock(&m_mutex);
380 const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
381 const auto deviceIt = m_devices.find(deviceId);
382 if (deviceIt == m_devices.end())
383 return false;
384
385 const int action = ev.callMethod<jint>("getAction", "()I");
386 if (action != g_defaultMapping()->ACTION_DOWN &&
387 action != g_defaultMapping()->ACTION_UP) {
388 return false;
389 }
390 const int flags = ev.callMethod<jint>("getFlags", "()I");
391 if ((flags & g_defaultMapping()->FLAG_LONG_PRESS) == g_defaultMapping()->FLAG_LONG_PRESS)
392 return false;
393
394 const int keyCode = ev.callMethod<jint>("getKeyCode", "()I");
395 auto &deviceMap = deviceIt.value();
396
397 if (deviceMap.cancelConfigurationButton != QGamepadManager::ButtonInvalid &&
398 (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid ||
399 deviceMap.calibrateAxis != QGamepadManager::AxisInvalid)) {
400
401 const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
402 if (buttonsMapIt != deviceMap.buttonsMap.end() &&
403 deviceMap.cancelConfigurationButton == buttonsMapIt.value()) {
404 deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
405 deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
406 FunctionEvent::runOnQtThread(this, [this, deviceId]{
407 emit configurationCanceled(deviceId);
408 });
409 return true;
410 }
411 }
412
413 if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) {
414 deviceMap.buttonsMap[keyCode] = deviceMap.calibrateButton;
415 auto but = deviceMap.calibrateButton;
416 deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
417 saveData(deviceMap);
418 FunctionEvent::runOnQtThread(this, [this, deviceId, but]{
419 emit buttonConfigured(deviceId, but);
420 });
421 }
422
423 const auto buttonsMapIt = deviceMap.buttonsMap.find(keyCode);
424 if (buttonsMapIt == deviceMap.buttonsMap.end())
425 return false;
426
427 const auto button = buttonsMapIt.value();
428 if (action == g_defaultMapping()->ACTION_DOWN) {
429 FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
430 emit gamepadButtonPressed(deviceId, button, 1.0);
431 });
432 } else {
433 FunctionEvent::runOnQtThread(this, [this, deviceId, button]{
434 emit gamepadButtonReleased(deviceId, button);
435 });
436 }
437 return true;
438 }
439
handleGenericMotionEvent(jobject event)440 bool QAndroidGamepadBackend::handleGenericMotionEvent(jobject event)
441 {
442 // GenericMotionEvent was introduced in API-12
443 Q_ASSERT(QtAndroidPrivate::androidSdkVersion() >= 12);
444
445 QJNIObjectPrivate ev(event);
446 QMutexLocker lock(&m_mutex);
447 const int deviceId = ev.callMethod<jint>("getDeviceId", "()I");
448 const auto deviceIt = m_devices.find(deviceId);
449 if (deviceIt == m_devices.end())
450 return false;
451
452 auto &deviceMap = deviceIt.value();
453 if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid ||
454 deviceMap.calibrateButton != QGamepadManager::ButtonInvalid) {
455 double lastValue = 0;
456 int lastAxis = -1;
457 for (int axis : g_defaultMapping()->allAndroidAxes) {
458 double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", axis);
459 if (fabs(value) > fabs(lastValue)) {
460 lastValue = value;
461 lastAxis = axis;
462 }
463 }
464
465 if (!lastValue || lastAxis == -1)
466 return false;
467
468 if (deviceMap.calibrateAxis != QGamepadManager::AxisInvalid) {
469 deviceMap.axisMap[lastAxis].gamepadAxis = deviceMap.calibrateAxis;
470 auto axis = deviceMap.calibrateAxis;
471 deviceMap.calibrateAxis = QGamepadManager::AxisInvalid;
472 saveData(deviceMap);
473 FunctionEvent::runOnQtThread(this, [this, deviceId, axis]{
474 emit axisConfigured(deviceId, axis);
475 });
476 } else if (deviceMap.calibrateButton != QGamepadManager::ButtonInvalid &&
477 deviceMap.calibrateButton != QGamepadManager::ButtonUp &&
478 deviceMap.calibrateButton != QGamepadManager::ButtonDown &&
479 deviceMap.calibrateButton != QGamepadManager::ButtonLeft &&
480 deviceMap.calibrateButton != QGamepadManager::ButtonRight) {
481 auto &axis = deviceMap.axisMap[lastAxis];
482 axis.gamepadAxis = QGamepadManager::AxisInvalid;
483 setAxisInfo(ev, lastAxis, axis);
484 bool save = false;
485 if (lastValue == axis.minValue) {
486 axis.gamepadMinButton = deviceMap.calibrateButton;
487 if (axis.gamepadMaxButton == QGamepadManager::ButtonInvalid)
488 axis.gamepadMaxButton = deviceMap.calibrateButton;
489 save = true;
490 } else if (lastValue == axis.maxValue) {
491 axis.gamepadMaxButton = deviceMap.calibrateButton;
492 if (axis.gamepadMinButton == QGamepadManager::ButtonInvalid)
493 axis.gamepadMinButton = deviceMap.calibrateButton;
494 save = true;
495 }
496
497 if (save) {
498 auto but = deviceMap.calibrateButton;
499 deviceMap.calibrateButton = QGamepadManager::ButtonInvalid;
500 saveData(deviceMap);
501 FunctionEvent::runOnQtThread(this, [this, deviceId, but]{
502 emit buttonConfigured(deviceId, but);
503 });
504 }
505 }
506 }
507
508 typedef QPair<QGamepadManager::GamepadAxis, double> GamepadAxisValue;
509 QVector<GamepadAxisValue> axisValues;
510 typedef QPair<QGamepadManager::GamepadButton, double> GamepadButtonValue;
511 QVector<GamepadButtonValue> buttonValues;
512 auto setValue = [&axisValues, &buttonValues](Mapping::AndroidAxisInfo &axisInfo, double value) {
513 if (axisInfo.setValue(value)) {
514 if (axisInfo.gamepadAxis != QGamepadManager::AxisInvalid) {
515 axisValues.push_back(GamepadAxisValue(axisInfo.gamepadAxis, axisInfo.lastValue));
516 } else {
517 if (axisInfo.lastValue < 0) {
518 buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMinButton, axisInfo.lastValue));
519 axisInfo.gamepadLastButton = axisInfo.gamepadMinButton;
520 } else if (axisInfo.lastValue > 0) {
521 buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadMaxButton, axisInfo.lastValue));
522 axisInfo.gamepadLastButton = axisInfo.gamepadMaxButton;
523 } else {
524 buttonValues.push_back(GamepadButtonValue(axisInfo.gamepadLastButton, 0.0));
525 axisInfo.gamepadLastButton = QGamepadManager::ButtonInvalid;
526 }
527 }
528 }
529 };
530 for (auto it = deviceMap.axisMap.begin(); it != deviceMap.axisMap.end(); ++it) {
531 auto &axisInfo = it.value();
532 if (axisInfo.flatArea == -1)
533 setAxisInfo(ev, it.key(), axisInfo);
534 const int historicalValues = ev.callMethod<jint>("getHistorySize", "()I");
535 for (int i = 0; i < historicalValues; ++i) {
536 double value = ev.callMethod<jfloat>("getHistoricalAxisValue", "(II)F", it.key(), i);
537 setValue(axisInfo, value);
538 }
539 double value = ev.callMethod<jfloat>("getAxisValue", "(I)F", it.key());
540 setValue(axisInfo, value);
541 }
542
543 if (!axisValues.isEmpty()) {
544 FunctionEvent::runOnQtThread(this, [this, deviceId, axisValues]{
545 for (const auto &axisValue : axisValues)
546 emit gamepadAxisMoved(deviceId, axisValue.first, axisValue.second);
547 });
548 }
549
550 if (!buttonValues.isEmpty()) {
551 FunctionEvent::runOnQtThread(this, [this, deviceId, buttonValues]{
552 for (const auto &buttonValue : buttonValues)
553 if (buttonValue.second)
554 emit gamepadButtonPressed(deviceId, buttonValue.first, fabs(buttonValue.second));
555 else
556 emit gamepadButtonReleased(deviceId, buttonValue.first);
557 });
558 }
559
560 return false;
561 }
562
start()563 bool QAndroidGamepadBackend::start()
564 {
565 {
566 QMutexLocker lock(&m_mutex);
567 if (QtAndroidPrivate::androidSdkVersion() >= 16) {
568 if (!m_qtGamepad.isValid())
569 m_qtGamepad = QJNIObjectPrivate(qtGamePadClassName, "(Landroid/app/Activity;)V", QtAndroidPrivate::activity());
570 m_qtGamepad.callMethod<void>("register", "(J)V", jlong(this));
571 }
572 }
573
574 QJNIObjectPrivate ids = QJNIObjectPrivate::callStaticObjectMethod(inputDeviceClass, "getDeviceIds", "()[I");
575 jintArray jarr = jintArray(ids.object());
576 QJNIEnvironmentPrivate env;
577 size_t sz = env->GetArrayLength(jarr);
578 jint *buff = env->GetIntArrayElements(jarr, nullptr);
579 for (size_t i = 0; i < sz; ++i)
580 addDevice(buff[i]);
581 env->ReleaseIntArrayElements(jarr, buff, 0);
582 return true;
583 }
584
stop()585 void QAndroidGamepadBackend::stop()
586 {
587 QMutexLocker lock(&m_mutex);
588 if (QtAndroidPrivate::androidSdkVersion() >= 16 && m_qtGamepad.isValid())
589 m_qtGamepad.callMethod<void>("unregister", "()V");
590 }
591
saveData(const QAndroidGamepadBackend::Mapping & deviceInfo)592 void QAndroidGamepadBackend::saveData(const QAndroidGamepadBackend::Mapping &deviceInfo)
593 {
594 if (!deviceInfo.productId)
595 return ;
596
597 QVariantMap settings, data;
598 for (auto it = deviceInfo.axisMap.begin(); it != deviceInfo.axisMap.end(); ++it)
599 data[QString::number(it.key())] = it.value().dataToSave();
600 settings[AXES_KEY] = data;
601
602 data.clear();
603 for (auto it = deviceInfo.buttonsMap.begin(); it != deviceInfo.buttonsMap.end(); ++it)
604 data[QString::number(it.key())] = it.value();
605 settings[BUTTONS_KEY] = data;
606
607 saveSettings(deviceInfo.productId, settings);
608 }
609
JNI_OnLoad(JavaVM * vm,void *)610 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
611 {
612 static bool initialized = false;
613 if (initialized)
614 return JNI_VERSION_1_6;
615 initialized = true;
616
617 JNIEnv* env;
618 // get the JNIEnv pointer.
619 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
620 return JNI_ERR;
621
622 // search for Java class which declares the native methods
623 jclass javaClass = env->FindClass("org/qtproject/qt5/android/gamepad/QtGamepad");
624 if (!javaClass)
625 return JNI_ERR;
626
627 // register our native methods
628 if (env->RegisterNatives(javaClass, methods,
629 sizeof(methods) / sizeof(methods[0])) < 0) {
630 return JNI_ERR;
631 }
632 return JNI_VERSION_1_6;
633 }
634
635 QT_END_NAMESPACE
636