Source code
Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtGui/qtguiglobal.h>
#include "androidjniinput.h"
#include "androidjnimenu.h"
#include "androidjnimain.h"
#include "qandroidplatformintegration.h"
#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtGui/private/qpointingdevice_p.h>
#include <QTouchEvent>
#include <QPointer>
#include <QGuiApplication>
#include <QThread>
#include <QtMath>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
using namespace QtAndroid;
Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface")
namespace QtAndroidInput
{
static bool m_ignoreMouseEvents = false;
static Qt::MouseButtons m_lastSeenButtons = Qt::NoButton;
static QRect m_softwareKeyboardRect;
static QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
static QPointer<QWindow> m_mouseGrabber;
static bool m_backKeyPressAccepted = false;
static bool m_menuKeyPressAccepted = false;
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
{
qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>("updateSelection", selStart, selEnd,
candidatesStart, candidatesEnd);
}
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>(
"showSoftwareKeyboard", QtAndroidPrivate::activity(),
left, top, width, height, inputHints,
enterKeyType);
qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
}
void resetSoftwareKeyboard()
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface>("resetSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD";
}
void hideSoftwareKeyboard()
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface>("hideSoftwareKeyboard");
qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD";
}
bool isSoftwareKeyboardVisible()
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
return reg->callInterface<QtJniTypes::QtInputInterface, jboolean>(
"isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
{
return m_softwareKeyboardRect;
}
int getSelectHandleWidth()
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
return reg->callInterface<QtJniTypes::QtInputInterface, jint>("getSelectionHandleWidth");
}
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
AndroidBackendRegister *reg = QtAndroid::backendRegister();
reg->callInterface<QtJniTypes::QtInputInterface, void>(
"updateHandles", mode, editMenuPos.x(), editMenuPos.y(),
editButtons, cursor.x(), cursor.y(), anchor.x(), anchor.y(), rtl);
}
enum AndroidMouseButton {
BUTTON_PRIMARY = 0x00000001,
BUTTON_SECONDARY = 0x00000002,
BUTTON_TERTIARY = 0x00000004,
BUTTON_BACK = 0x00000008,
BUTTON_FORWARD = 0x00000010,
BUTTON_STYLUS_PRIMARY = 0x00000020,
BUTTON_STYLUS_SECONDARY = 0x00000040,
};
Q_DECLARE_FLAGS(AndroidMouseButtons, AndroidMouseButton)
static Qt::MouseButtons toMouseButtons(jint j_buttons)
{
const auto buttons = static_cast<AndroidMouseButtons>(j_buttons);
Qt::MouseButtons mouseButtons;
if (buttons.testFlag(BUTTON_PRIMARY))
mouseButtons.setFlag(Qt::LeftButton);
if (buttons.testFlag(BUTTON_SECONDARY))
mouseButtons.setFlag(Qt::RightButton);
if (buttons.testFlag(BUTTON_TERTIARY))
mouseButtons.setFlag(Qt::MiddleButton);
if (buttons.testFlag(BUTTON_BACK))
mouseButtons.setFlag(Qt::BackButton);
if (buttons.testFlag(BUTTON_FORWARD))
mouseButtons.setFlag(Qt::ForwardButton);
if (buttons.testFlag(BUTTON_STYLUS_PRIMARY))
mouseButtons.setFlag(Qt::LeftButton);
if (buttons.testFlag(BUTTON_STYLUS_SECONDARY))
mouseButtons.setFlag(Qt::RightButton);
// Fall back to left button
if (Q_UNLIKELY(buttons != 0 && mouseButtons == Qt::NoButton)) {
qWarning() << "Unhandled button value:" << buttons << "Falling back to Qt::LeftButton";
mouseButtons = Qt::LeftButton;
}
return mouseButtons;
}
static Qt::KeyboardModifiers mapAndroidModifiers(jint modifiers)
{
Qt::KeyboardModifiers qmodifiers;
if (modifiers & 0x00000001) // META_SHIFT_ON
qmodifiers |= Qt::ShiftModifier;
if (modifiers & 0x00000002) // META_ALT_ON
qmodifiers |= Qt::AltModifier;
if (modifiers & 0x00000004) // META_SYM_ON
qmodifiers |= Qt::MetaModifier;
if (modifiers & 0x00001000) // META_CTRL_ON
qmodifiers |= Qt::ControlModifier;
if (modifiers & 0x00010000) // META_META_ON
qmodifiers |= Qt::MetaModifier;
return qmodifiers;
}
static void sendMouseButtonEvents(QWindow *topLevel, QPoint localPos, QPoint globalPos,
jint mouseButtonState, jint metaState, QEvent::Type type)
{
const Qt::MouseButtons qtButtons = toMouseButtons(mouseButtonState);
const bool mouseReleased = type == QEvent::MouseButtonRelease && qtButtons == Qt::NoButton;
const Qt::MouseButtons eventButtons = mouseReleased ? m_lastSeenButtons : qtButtons;
m_lastSeenButtons = qtButtons;
static_assert (sizeof(eventButtons) <= sizeof(uint), "Qt::MouseButtons size changed. Adapt code.");
if (eventButtons == Qt::NoButton)
return;
const Qt::KeyboardModifiers modifiers = mapAndroidModifiers(metaState);
for (uint buttonInt = 0x1; static_cast<uint>(eventButtons) >= buttonInt; buttonInt <<= 1) {
const auto button = static_cast<Qt::MouseButton>(buttonInt);
if (eventButtons.testFlag(button)) {
QWindowSystemInterface::handleMouseEvent(topLevel, localPos, globalPos,
qtButtons, button, type, modifiers);
}
}
}
static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y,
jint mouseButtonState, jint metaState)
{
if (m_ignoreMouseEvents)
return;
const QPoint localPos(x,y);
QWindow *window = windowFromId(winId);
m_mouseGrabber = window;
const QPoint globalPos = window && window->handle() ?
window->handle()->mapToGlobal(localPos) : localPos;
sendMouseButtonEvents(window, localPos, globalPos, mouseButtonState, metaState,
QEvent::MouseButtonPress);
}
static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y,
jint mouseButtonState, jint metaState)
{
const QPoint localPos(x,y);
QWindow *window = m_mouseGrabber.data();
if (!window)
window = windowFromId(winId);
const QPoint globalPos = window && window->handle() ?
window->handle()->mapToGlobal(localPos) : localPos;
sendMouseButtonEvents(window, localPos, globalPos, mouseButtonState, metaState,
QEvent::MouseButtonRelease);
m_ignoreMouseEvents = false;
m_mouseGrabber.clear();
}
static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y,
jint mouseButtonState, jint metaState)
{
if (m_ignoreMouseEvents)
return;
const QPoint localPos(x,y);
QWindow *window = m_mouseGrabber.data();
if (!window)
window = windowFromId(winId);
const QPoint globalPos = window && window->handle() ?
window->handle()->mapToGlobal(localPos) : localPos;
const Qt::MouseButtons qtButtons = toMouseButtons(mouseButtonState);
m_lastSeenButtons = qtButtons;
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
qtButtons, Qt::NoButton, QEvent::MouseMove,
mapAndroidModifiers(metaState));
}
static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y,
jfloat hdelta, jfloat vdelta, jint metaState)
{
if (m_ignoreMouseEvents)
return;
const QPoint localPos(x,y);
QWindow *window = m_mouseGrabber.data();
if (!window)
window = windowFromId(winId);
const QPoint globalPos = window && window->handle() ?
window->handle()->mapToGlobal(localPos) : localPos;
const QPoint angleDelta(hdelta * 120, vdelta * 120);
QWindowSystemInterface::handleWheelEvent(window,
localPos,
globalPos,
QPoint(),
angleDelta,
mapAndroidModifiers(metaState));
}
static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y,
jint metaState)
{
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
const QPoint localPos(x,y);
QWindow *window = windowFromId(winId);
const QPoint globalPos = window && window->handle() ?
window->handle()->mapToGlobal(localPos) : localPos;
if (inputContext && qGuiApp)
QMetaObject::invokeMethod(inputContext, "longPress", Q_ARG(int, globalPos.x()), Q_ARG(int, globalPos.y()));
//### TODO: add proper API for Qt 5.2
static bool rightMouseFromLongPress = qEnvironmentVariableIntValue("QT_ANDROID_ENABLE_RIGHT_MOUSE_FROM_LONG_PRESS");
if (!rightMouseFromLongPress)
return;
m_ignoreMouseEvents = true;
// Click right button if no other button is already pressed.
if (!m_mouseGrabber) {
const Qt::KeyboardModifiers modifiers = mapAndroidModifiers(metaState);
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::RightButton), Qt::RightButton,
QEvent::MouseButtonPress, modifiers);
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::NoButton), Qt::RightButton,
QEvent::MouseButtonRelease, modifiers);
}
}
static void touchBegin(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/)
{
m_touchPoints.clear();
}
static void touchAdd(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint id, jint action, jboolean /*primary*/, jint x, jint y,
jfloat major, jfloat minor, jfloat rotation, jfloat pressure)
{
QEventPoint::State state = QEventPoint::State::Stationary;
switch (action) {
case 0:
state = QEventPoint::State::Pressed;
break;
case 1:
state = QEventPoint::State::Updated;
break;
case 2:
state = QEventPoint::State::Stationary;
break;
case 3:
state = QEventPoint::State::Released;
break;
}
QWindow *window = QtAndroid::windowFromId(winId);
if (!window) {
qCWarning(lcQpaInputMethods, "Touch event received for non-existing window %d", winId);
return;
}
QPointF mappedTouchPoint;
if (window->handle())
mappedTouchPoint = window->handle()->mapToGlobalF(QPointF(x, y));
else
mappedTouchPoint = window->mapToGlobal(QPointF(x, y));
QWindowSystemInterface::TouchPoint touchPoint;
// Start numbering touch points from 1
touchPoint.id = id + 1;
touchPoint.pressure = pressure;
touchPoint.rotation = qRadiansToDegrees(rotation);
touchPoint.state = state;
touchPoint.area = QRectF(mappedTouchPoint.x() - double(minor * 0.5f),
mappedTouchPoint.y() - double(major * 0.5f),
double(minor),
double(major));
m_touchPoints.push_back(touchPoint);
if (state == QEventPoint::State::Pressed) {
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
QMetaObject::invokeMethod(inputContext, "touchDown", Q_ARG(int, mappedTouchPoint.x()), Q_ARG(int, mappedTouchPoint.y()));
}
}
static QPointingDevice *getTouchDevice()
{
QAndroidPlatformIntegration *platformIntegration = QtAndroid::androidPlatformIntegration();
if (!platformIntegration)
return nullptr;
QPointingDevice *touchDevice = platformIntegration->touchDevice();
if (!touchDevice) {
touchDevice = new QPointingDevice("Android touchscreen", 1,
QInputDevice::DeviceType::TouchScreen,
QPointingDevice::PointerType::Finger,
QPointingDevice::Capability::Position
| QPointingDevice::Capability::Area
| QPointingDevice::Capability::Pressure,
10, 0);
QWindowSystemInterface::registerInputDevice(touchDevice);
platformIntegration->setTouchDevice(touchDevice);
}
QPointingDevicePrivate::get(touchDevice)->setAvailableVirtualGeometry(
platformIntegration->screen()->availableGeometry());
return touchDevice;
}
static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint winId, jint /*action*/,
jint metaState)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
QWindow *window = QtAndroid::windowFromId(winId);
if (!window)
return;
QWindowSystemInterface::handleTouchEvent(window, touchDevice,
m_touchPoints, mapAndroidModifiers(metaState));
}
static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint winId, jint metaState)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
QWindow *window = QtAndroid::windowFromId(winId);
if (!window)
return;
QWindowSystemInterface::handleTouchCancelEvent(window, touchDevice,
mapAndroidModifiers(metaState));
}
static bool isTabletEventSupported(JNIEnv */*env*/, jobject /*thiz*/)
{
#if QT_CONFIG(tabletevent)
return true;
#else
return false;
#endif // QT_CONFIG(tabletevent)
}
static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint deviceId, jlong time, jint action,
jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure, jint metaState)
{
#if QT_CONFIG(tabletevent)
const QPointF localPos(x, y);
QWindow *window = windowFromId(winId);
const QPointF globalPosF = window && window->handle() ?
window->handle()->mapFromGlobalF(localPos) : localPos;
// Galaxy Note with plain Android:
// 0 1 0 stylus press
// 2 1 0 stylus drag
// 1 1 0 stylus release
// 0 1 2 stylus press with side-button held
// 2 1 2 stylus drag with side-button held
// 1 1 2 stylus release with side-button held
// Galaxy Note 4 with Samsung firmware:
// 0 1 0 stylus press
// 2 1 0 stylus drag
// 1 1 0 stylus release
// 211 1 2 stylus press with side-button held
// 213 1 2 stylus drag with side-button held
// 212 1 2 stylus release with side-button held
// when action == ACTION_UP (1) it's a release; otherwise we say which button is pressed
Qt::MouseButtons buttons = Qt::NoButton;
switch (action) {
case 1: // ACTION_UP
case 6: // ACTION_POINTER_UP, happens if stylus is not the primary pointer
case 212: // stylus release while side-button held on Galaxy Note 4
buttons = Qt::NoButton;
break;
default: // action is press or drag
if (buttonState == 0)
buttons = Qt::LeftButton;
else // 2 means RightButton
buttons = Qt::MouseButtons(buttonState);
break;
}
qCDebug(lcQpaInputMethods) << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
QWindowSystemInterface::handleTabletEvent(window, ulong(time),
localPos, globalPosF, int(QInputDevice::DeviceType::Stylus), pointerType,
buttons, pressure, 0, 0, 0., 0., 0, deviceId, mapAndroidModifiers(metaState));
#endif // QT_CONFIG(tabletevent)
}
static QKeyCombination mapAndroidKey(int key)
{
// 0--9 0x00000007 -- 0x00000010
if (key >= 0x00000007 && key <= 0x00000010)
return QKeyCombination::fromCombined(Qt::Key_0 + key - 0x00000007);
// A--Z 0x0000001d -- 0x00000036
if (key >= 0x0000001d && key <= 0x00000036)
return QKeyCombination::fromCombined(Qt::Key_A + key - 0x0000001d);
// F1--F12 0x00000083 -- 0x0000008e
if (key >= 0x00000083 && key <= 0x0000008e)
return QKeyCombination::fromCombined(Qt::Key_F1 + key - 0x00000083);
// NUMPAD_0--NUMPAD_9 0x00000090 -- 0x00000099
if (key >= 0x00000090 && key <= 0x00000099)
return QKeyCombination::fromCombined(Qt::KeypadModifier | Qt::Key_0 + key - 0x00000090);
// BUTTON_1--KEYCODE_BUTTON_16 0x000000bc -- 0x000000cb
switch (key) {
case 0x00000000: // KEYCODE_UNKNOWN
return Qt::Key_unknown;
case 0x00000001: // KEYCODE_SOFT_LEFT
return Qt::Key_Left;
case 0x00000002: // KEYCODE_SOFT_RIGHT
return Qt::Key_Right;
// 0x00000003: // KEYCODE_HOME is never delivered to applications.
case 0x00000004: // KEYCODE_BACK
return Qt::Key_Back;
case 0x00000005: // KEYCODE_CALL
return Qt::Key_Call;
case 0x00000006: // KEYCODE_ENDCALL
return Qt::Key_Hangup;
// 0--9 0x00000007 -- 0x00000010
case 0x00000011: // KEYCODE_STAR
return Qt::Key_Asterisk;
case 0x00000012: // KEYCODE_POUND
return Qt::Key_NumberSign;
case 0x00000013: //KEYCODE_DPAD_UP
return Qt::Key_Up;
case 0x00000014: // KEYCODE_DPAD_DOWN
return Qt::Key_Down;
case 0x00000015: //KEYCODE_DPAD_LEFT
return Qt::Key_Left;
case 0x00000016: //KEYCODE_DPAD_RIGHT
return Qt::Key_Right;
case 0x00000017: // KEYCODE_DPAD_CENTER
return Qt::Key_Enter;
case 0x00000018: // KEYCODE_VOLUME_UP
return Qt::Key_VolumeUp;
case 0x00000019: // KEYCODE_VOLUME_DOWN
return Qt::Key_VolumeDown;
case 0x0000001a:
return Qt::Key_PowerOff;
case 0x0000001b: // KEYCODE_CAMERA
return Qt::Key_Camera;
case 0x0000001c: // KEYCODE_CLEAR
return Qt::Key_Clear;
// A--Z 0x0000001d -- 0x00000036
case 0x00000037: // KEYCODE_COMMA
return Qt::Key_Comma;
case 0x00000038: // KEYCODE_PERIOD
return Qt::Key_Period;
case 0x00000039: // KEYCODE_ALT_LEFT
case 0x0000003a: // KEYCODE_ALT_RIGHT
return Qt::Key_Alt;
case 0x0000003b: // KEYCODE_SHIFT_LEFT
case 0x0000003c: // KEYCODE_SHIFT_RIGHT
return Qt::Key_Shift;
case 0x0000003d: // KEYCODE_TAB
return Qt::Key_Tab;
case 0x0000003e: // KEYCODE_SPACE
return Qt::Key_Space;
case 0x0000003f: // KEYCODE_SYM
return Qt::Key_Meta;
case 0x00000040: // KEYCODE_EXPLORER
return Qt::Key_Explorer;
case 0x00000041: //KEYCODE_ENVELOPE
return Qt::Key_LaunchMail;
case 0x00000042: // KEYCODE_ENTER
return Qt::Key_Return;
case 0x00000043: // KEYCODE_DEL
return Qt::Key_Backspace;
case 0x00000044: // KEYCODE_GRAVE
return Qt::Key_QuoteLeft;
case 0x00000045: // KEYCODE_MINUS
return Qt::Key_Minus;
case 0x00000046: // KEYCODE_EQUALS
return Qt::Key_Equal;
case 0x00000047: // KEYCODE_LEFT_BRACKET
return Qt::Key_BracketLeft;
case 0x00000048: // KEYCODE_RIGHT_BRACKET
return Qt::Key_BracketRight;
case 0x00000049: // KEYCODE_BACKSLASH
return Qt::Key_Backslash;
case 0x0000004a: // KEYCODE_SEMICOLON
return Qt::Key_Semicolon;
case 0x0000004b: // KEYCODE_APOSTROPHE
return Qt::Key_Apostrophe;
case 0x0000004c: // KEYCODE_SLASH
return Qt::Key_Slash;
case 0x0000004d: // KEYCODE_AT
return Qt::Key_At;
case 0x0000004e: // KEYCODE_NUM
return Qt::Key_Alt;
case 0x0000004f: // KEYCODE_HEADSETHOOK
return QKeyCombination::fromCombined(0);
case 0x00000050: // KEYCODE_FOCUS
return Qt::Key_CameraFocus;
case 0x00000051: // KEYCODE_PLUS
return Qt::Key_Plus;
case 0x00000052: // KEYCODE_MENU
return Qt::Key_Menu;
case 0x00000053: // KEYCODE_NOTIFICATION
return QKeyCombination::fromCombined(0);
case 0x00000054: // KEYCODE_SEARCH
return Qt::Key_Search;
case 0x00000055: // KEYCODE_MEDIA_PLAY_PAUSE
return Qt::Key_MediaTogglePlayPause;
case 0x00000056: // KEYCODE_MEDIA_STOP
return Qt::Key_MediaStop;
case 0x00000057: // KEYCODE_MEDIA_NEXT
return Qt::Key_MediaNext;
case 0x00000058: // KEYCODE_MEDIA_PREVIOUS
return Qt::Key_MediaPrevious;
case 0x00000059: // KEYCODE_MEDIA_REWIND
return Qt::Key_AudioRewind;
case 0x0000005a: // KEYCODE_MEDIA_FAST_FORWARD
return Qt::Key_AudioForward;
case 0x0000005b: // KEYCODE_MUTE
return Qt::Key_MicMute;
case 0x0000005c: // KEYCODE_PAGE_UP
return Qt::Key_PageUp;
case 0x0000005d: // KEYCODE_PAGE_DOWN
return Qt::Key_PageDown;
case 0x0000005e: // KEYCODE_PICTSYMBOLS
return QKeyCombination::fromCombined(0);
case 0x00000060: // KEYCODE_BUTTON_A
case 0x00000061: // KEYCODE_BUTTON_B
case 0x00000062: // KEYCODE_BUTTON_B
case 0x00000063: // KEYCODE_BUTTON_X
case 0x00000064: // KEYCODE_BUTTON_Y
case 0x00000065: // KEYCODE_BUTTON_Z
case 0x00000066: // KEYCODE_BUTTON_L1
case 0x00000067: // KEYCODE_BUTTON_R1
case 0x00000068: // KEYCODE_BUTTON_L2
case 0x00000069: // KEYCODE_BUTTON_R2
case 0x0000006a: // KEYCODE_BUTTON_THUMBL
case 0x0000006b: // KEYCODE_BUTTON_THUMBR
case 0x0000006c: // KEYCODE_BUTTON_START
case 0x0000006d: // KEYCODE_BUTTON_SELECT
case 0x0000006e: // KEYCODE_BUTTON_MODE
return QKeyCombination::fromCombined(0);
case 0x0000006f: // KEYCODE_ESCAPE
return Qt::Key_Escape;
case 0x00000070: // KEYCODE_FORWARD_DEL
return Qt::Key_Delete;
case 0x00000071: // KEYCODE_CTRL_LEFT
case 0x00000072: // KEYCODE_CTRL_RIGHT
return Qt::Key_Control;
case 0x00000073: // KEYCODE_CAPS_LOCK
return Qt::Key_CapsLock;
case 0x00000074: // KEYCODE_SCROLL_LOCK
return Qt::Key_ScrollLock;
case 0x00000075: // KEYCODE_META_LEFT
case 0x00000076: // KEYCODE_META_RIGHT
return Qt::Key_Meta;
case 0x00000077: // KEYCODE_FUNCTION
return QKeyCombination::fromCombined(0);
case 0x00000078: // KEYCODE_SYSRQ
return Qt::Key_Print;
case 0x00000079: // KEYCODE_BREAK
return Qt::Key_Pause;
case 0x0000007a: // KEYCODE_MOVE_HOME
return Qt::Key_Home;
case 0x0000007b: // KEYCODE_MOVE_END
return Qt::Key_End;
case 0x0000007c: // KEYCODE_MOVE_INSERT
return Qt::Key_Insert;
case 0x0000007d: // KEYCODE_FORWARD
return Qt::Key_Forward;
case 0x0000007e: // KEYCODE_MEDIA_PLAY
return Qt::Key_MediaPlay;
case 0x0000007f: // KEYCODE_MEDIA_PAUSE
return Qt::Key_MediaPause;
case 0x00000080: // KEYCODE_MEDIA_CLOSE
case 0x00000081: // KEYCODE_MEDIA_EJECT
return Qt::Key_Eject;
case 0x00000082: // KEYCODE_MEDIA_RECORD
return Qt::Key_MediaRecord;
// F1--F12 0x00000083 -- 0x0000008e
case 0x0000008f: // KEYCODE_NUM_LOCK
return Qt::Key_NumLock;
// NUMPAD_0--NUMPAD_9 0x00000090 -- 0x00000099
case 0x0000009a: // KEYCODE_NUMPAD_DIVIDE
return Qt::KeypadModifier | Qt::Key_Slash;
case 0x0000009b: // KEYCODE_NUMPAD_MULTIPLY
return Qt::KeypadModifier | Qt::Key_Asterisk;
case 0x0000009c: // KEYCODE_NUMPAD_SUBTRACT
return Qt::KeypadModifier | Qt::Key_Minus;
case 0x0000009d: // KEYCODE_NUMPAD_ADD
return Qt::KeypadModifier | Qt::Key_Plus;
case 0x0000009e: // KEYCODE_NUMPAD_DOT
return Qt::KeypadModifier | Qt::Key_Period;
case 0x0000009f: // KEYCODE_NUMPAD_COMMA
return Qt::KeypadModifier | Qt::Key_Comma;
case 0x000000a0: // KEYCODE_NUMPAD_ENTER
return Qt::Key_Enter;
case 0x000000a1: // KEYCODE_NUMPAD_EQUALS
return Qt::KeypadModifier | Qt::Key_Equal;
case 0x000000a2: // KEYCODE_NUMPAD_LEFT_PAREN
return Qt::Key_ParenLeft;
case 0x000000a3: // KEYCODE_NUMPAD_RIGHT_PAREN
return Qt::Key_ParenRight;
case 0x000000a4: // KEYCODE_VOLUME_MUTE
return Qt::Key_VolumeMute;
case 0x000000a5: // KEYCODE_INFO
return Qt::Key_Info;
case 0x000000a6: // KEYCODE_CHANNEL_UP
return Qt::Key_ChannelUp;
case 0x000000a7: // KEYCODE_CHANNEL_DOWN
return Qt::Key_ChannelDown;
case 0x000000a8: // KEYCODE_ZOOM_IN
return Qt::Key_ZoomIn;
case 0x000000a9: // KEYCODE_ZOOM_OUT
return Qt::Key_ZoomOut;
case 0x000000aa: // KEYCODE_TV
case 0x000000ab: // KEYCODE_WINDOW
return QKeyCombination::fromCombined(0);
case 0x000000ac: // KEYCODE_GUIDE
return Qt::Key_Guide;
case 0x000000ad: // KEYCODE_DVR
return QKeyCombination::fromCombined(0);
case 0x000000ae: // KEYCODE_BOOKMARK
return Qt::Key_AddFavorite;
case 0x000000af: // KEYCODE_CAPTIONS
return Qt::Key_Subtitle;
case 0x000000b0: // KEYCODE_SETTINGS
return Qt::Key_Settings;
case 0x000000b1: // KEYCODE_TV_POWER
case 0x000000b2: // KEYCODE_TV_INPUT
case 0x000000b3: // KEYCODE_STB_POWER
case 0x000000b4: // KEYCODE_STB_INPUT
case 0x000000b5: // KEYCODE_AVR_POWER
case 0x000000b6: // KEYCODE_AVR_INPUT
return QKeyCombination::fromCombined(0);
case 0x000000b7: // KEYCODE_PROG_RED
return Qt::Key_Red;
case 0x000000b8: // KEYCODE_PROG_GREEN
return Qt::Key_Green;
case 0x000000b9: // KEYCODE_PROG_YELLOW
return Qt::Key_Yellow;
case 0x000000ba: // KEYCODE_PROG_BLUE
return Qt::Key_Blue;
// 0x000000bb: // KEYCODE_APP_SWITCH is not sent by the Android O.S.
// BUTTON_1--KEYCODE_BUTTON_16 0x000000bc -- 0x000000cb
case 0x000000cc: // KEYCODE_LANGUAGE_SWITCH
case 0x000000cd: // KEYCODE_MANNER_MODE do we need such a thing?
case 0x000000ce: // KEYCODE_3D_MODE
case 0x000000cf: // KEYCODE_CONTACTS
return QKeyCombination::fromCombined(0);
case 0x000000d0: // KEYCODE_CALENDAR
return Qt::Key_Calendar;
case 0x000000d1: // KEYCODE_MUSIC
return Qt::Key_Music;
case 0x000000d2: // KEYCODE_CALCULATOR
return Qt::Key_Calculator;
// 0x000000d3 -- 0x000000da some japanese specific keys, someone who understand what is about should check !
// 0x000000db: // KEYCODE_ASSIST not delivered to applications.
case 0x000000dc: // KEYCODE_BRIGHTNESS_DOWN
return Qt::Key_KeyboardBrightnessDown;
case 0x000000dd: // KEYCODE_BRIGHTNESS_UP
return Qt::Key_KeyboardBrightnessUp;
case 0x000000de: // KEYCODE_MEDIA_AUDIO_TRACK
return Qt::Key_AudioCycleTrack;
default:
qWarning() << "Unhandled key code " << key << '!';
return QKeyCombination::fromCombined(0);
}
}
// maps 0 to the empty string, and anything else to a single-character string
static inline QString toString(jint unicode)
{
return unicode ? QString(QChar(unicode)) : QString();
}
static bool sendKeyEventSynchronously(QEvent::Type type, int key,
Qt::KeyboardModifiers modifiers, const QString &text,
bool autoRepeat)
{
bool accepted = false;
const auto sendEvent = [&] {
accepted = QWindowSystemInterface::handleKeyEvent<
QWindowSystemInterface::SynchronousDelivery>(
nullptr, type, key, modifiers, text, autoRepeat);
};
// A cross-thread QWSI flush can return another queued event's acceptance.
if (QThread::isMainThread())
sendEvent();
else if (!QMetaObject::invokeMethod(qGuiApp, sendEvent, Qt::BlockingQueuedConnection))
qWarning("Failed to deliver Android key event on the GUI thread");
return accepted;
}
static void keyDown(JNIEnv */*env*/, jobject /*thiz*/, jint key, jint unicode, jint modifier, jboolean autoRepeat)
{
const int qtKeyCombination = mapAndroidKey(key).toCombined();
const auto qtModifiers = mapAndroidModifiers(modifier);
const auto isBackKey = qtKeyCombination == Qt::Key_Back;
const auto isMenuKey = qtKeyCombination == Qt::Key_Menu;
const auto text = toString(unicode);
bool accepted = false;
if (isBackKey || isMenuKey) {
accepted = sendKeyEventSynchronously(QEvent::KeyPress, qtKeyCombination,
qtModifiers, text, autoRepeat);
} else {
QWindowSystemInterface::handleKeyEvent(
nullptr, QEvent::KeyPress, qtKeyCombination, qtModifiers, text, autoRepeat);
}
m_backKeyPressAccepted = isBackKey && accepted;
m_menuKeyPressAccepted = isMenuKey && accepted;
}
static void moveActivityToBackground() {
auto iface = qGuiApp->nativeInterface<QNativeInterface::QAndroidApplication>();
iface->runOnAndroidMainThread([iface]() {
if (!iface->isActivityContext())
return;
iface->context().callMethod<jboolean>("moveTaskToBack", true);
});
}
static void keyUp(JNIEnv */*env*/, jobject /*thiz*/, jint key, jint unicode, jint modifier, jboolean autoRepeat)
{
const int qtKeyCombination = mapAndroidKey(key).toCombined();
const auto qtModifiers = mapAndroidModifiers(modifier);
const auto isBackKey = qtKeyCombination == Qt::Key_Back;
const auto isMenuKey = qtKeyCombination == Qt::Key_Menu;
const auto text = toString(unicode);
if (!isBackKey && !isMenuKey) {
QWindowSystemInterface::handleKeyEvent(
nullptr, QEvent::KeyRelease, qtKeyCombination, qtModifiers, text, autoRepeat);
return;
}
const bool accepted = sendKeyEventSynchronously(QEvent::KeyRelease, qtKeyCombination,
qtModifiers, text, autoRepeat);
if (isBackKey && !m_backKeyPressAccepted && !accepted)
moveActivityToBackground();
else if (isMenuKey && !m_menuKeyPressAccepted && !accepted)
QtAndroidMenu::openOptionsMenu();
}
static void keyboardVisibilityChanged(JNIEnv */*env*/, jobject /*thiz*/, jboolean visibility)
{
if (!visibility)
m_softwareKeyboardRect = QRect();
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp) {
inputContext->emitInputPanelVisibleChanged();
if (!visibility) {
inputContext->emitKeyboardRectChanged();
QMetaObject::invokeMethod(inputContext, "hideSelectionHandles", Qt::QueuedConnection);
}
}
qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
}
static void keyboardGeometryChanged(JNIEnv */*env*/, jobject /*thiz*/, jint x, jint y, jint w, jint h)
{
QRect r = QRect(x, y, w, h);
if (r == m_softwareKeyboardRect)
return;
m_softwareKeyboardRect = r;
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
inputContext->emitKeyboardRectChanged();
qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDRECTCHANGED" << m_softwareKeyboardRect;
}
static void handleLocationChanged(JNIEnv */*env*/, jobject /*thiz*/, int id, int x, int y)
{
qCDebug(lcQpaInputMethods) << "@@@ handleLocationChanged" << id << x << y;
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
// Queued, as Java reads nothing back and blocking here can deadlock.
if (inputContext && qGuiApp)
QMetaObject::invokeMethod(inputContext, "handleLocationChanged", Qt::QueuedConnection,
Q_ARG(int, id), Q_ARG(int, x), Q_ARG(int, y));
}
static const JNINativeMethod methods[] = {
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(III)V",(void*)touchEnd},
{"touchCancel", "(II)V", (void *)touchCancel},
{"mouseDown", "(IIIII)V", (void *)mouseDown},
{"mouseUp", "(IIIII)V", (void *)mouseUp},
{"mouseMove", "(IIIII)V", (void *)mouseMove},
{"mouseWheel", "(IIIFFI)V", (void *)mouseWheel},
{"longPress", "(IIII)V", (void *)longPress},
{"isTabletEventSupported", "()Z", (void *)isTabletEventSupported},
{"tabletEvent", "(IIJIIIFFFI)V", (void *)tabletEvent},
{"keyDown", "(IIIZ)V", (void *)keyDown},
{"keyUp", "(IIIZ)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
{"keyboardGeometryChanged", "(IIII)V", (void *)keyboardGeometryChanged},
{"handleLocationChanged", "(III)V", (void *)handleLocationChanged},
};
bool registerNatives(QJniEnvironment &env)
{
if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtInputDelegate>::className(),
methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
return true;
}
}
QT_END_NAMESPACE