diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-03-23 21:06:47 +0200 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-03-23 21:06:47 +0200 |
commit | 42e4ae042a4c86e58bcb8b6d2d59ba4a988285b4 (patch) | |
tree | a7f25b1d335a834a7c3ae104cee913ef3a4dd4fb /src/plugins | |
parent | 4ee4fc18b4067b90efa46ca9baba74f53b54d9ec (diff) | |
parent | 168ff3419f256fdb35b586275d293fc0cd773fe1 (diff) | |
download | qtbase-42e4ae042a4c86e58bcb8b6d2d59ba4a988285b4.tar.gz |
Merge remote-tracking branch 'origin/tqtc/lts-5.15.9' into tqtc/lts-5.15-opensourcev5.15.9-lts-lgpl5.15
Change-Id: Iaff6b55275e50d19973e1020853d8622587069f9
Diffstat (limited to 'src/plugins')
27 files changed, 622 insertions, 138 deletions
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index 989d0d18f4..afe15677b6 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -50,6 +50,7 @@ #include <QtCore/private/qjnihelpers_p.h> #include <QtCore/private/qjni_p.h> #include <QtGui/private/qhighdpiscaling_p.h> +#include <QtCore/QObject> #include "qdebug.h" @@ -75,6 +76,28 @@ namespace QtAndroidAccessibility static bool m_accessibilityActivated = false; + // This object is needed to schedule the execution of the code that + // deals with accessibility instances to the Qt main thread. + // Because of that almost every method here is split into two parts. + // The _helper part is executed in the context of m_accessibilityContext + // on the main thread. The other part is executed in Java thread. + static QObject *m_accessibilityContext = nullptr; + + // This method is called from the Qt main thread, and normally a + // QGuiApplication instance will be used as a parent. + void createAccessibilityContextObject(QObject *parent) + { + if (m_accessibilityContext) + m_accessibilityContext->deleteLater(); + m_accessibilityContext = new QObject(parent); + } + + template <typename Func, typename Ret> + void runInObjectContext(QObject *context, Func &&func, Ret *retVal) + { + QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal); + } + void initialize() { QJNIObjectPrivate::callStaticMethod<void>(QtAndroid::applicationClass(), @@ -115,9 +138,12 @@ namespace QtAndroidAccessibility QtAndroid::notifyAccessibilityLocationChange(); } + static int parentId_helper(int objectId); // forward declaration + void notifyObjectHide(uint accessibilityObjectId) { - QtAndroid::notifyObjectHide(accessibilityObjectId); + const auto parentObjectId = parentId_helper(accessibilityObjectId); + QtAndroid::notifyObjectHide(accessibilityObjectId, parentObjectId); } void notifyObjectFocus(uint accessibilityObjectId) @@ -125,7 +151,15 @@ namespace QtAndroidAccessibility QtAndroid::notifyObjectFocus(accessibilityObjectId); } - static jintArray childIdListForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId) + static jstring jvalueForAccessibleObject(int objectId); // forward declaration + + void notifyValueChanged(uint accessibilityObjectId) + { + jstring value = jvalueForAccessibleObject(accessibilityObjectId); + QtAndroid::notifyValueChanged(accessibilityObjectId, value); + } + + static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId) { QAccessibleInterface *iface = interfaceFromId(objectId); if (iface && iface->isValid()) { @@ -137,6 +171,18 @@ namespace QtAndroidAccessibility if (child && child->isValid()) ifaceIdArray.append(QAccessible::uniqueId(child)); } + return ifaceIdArray; + } + return {}; + } + + static jintArray childIdListForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId) + { + if (m_accessibilityContext) { + QVarLengthArray<jint, 8> ifaceIdArray; + runInObjectContext(m_accessibilityContext, [objectId]() { + return childIdListForAccessibleObject_helper(objectId); + }, &ifaceIdArray); jintArray jArray = env->NewIntArray(jsize(ifaceIdArray.count())); env->SetIntArrayRegion(jArray, 0, ifaceIdArray.count(), ifaceIdArray.data()); return jArray; @@ -145,7 +191,7 @@ namespace QtAndroidAccessibility return env->NewIntArray(jsize(0)); } - static jint parentId(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + static int parentId_helper(int objectId) { QAccessibleInterface *iface = interfaceFromId(objectId); if (iface && iface->isValid()) { @@ -159,7 +205,18 @@ namespace QtAndroidAccessibility return -1; } - static jobject screenRect(JNIEnv *env, jobject /*thiz*/, jint objectId) + static jint parentId(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + { + jint result = -1; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return parentId_helper(objectId); + }, &result); + } + return result; + } + + static QRect screenRect_helper(int objectId) { QRect rect; QAccessibleInterface *iface = interfaceFromId(objectId); @@ -171,14 +228,24 @@ namespace QtAndroidAccessibility const auto parentRect = QHighDpi::toNativePixels(iface->parent()->rect(), iface->parent()->window()); rect = rect.intersected(parentRect); } + return rect; + } + static jobject screenRect(JNIEnv *env, jobject /*thiz*/, jint objectId) + { + QRect rect; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return screenRect_helper(objectId); + }, &rect); + } jclass rectClass = env->FindClass("android/graphics/Rect"); jmethodID ctor = env->GetMethodID(rectClass, "<init>", "(IIII)V"); jobject jrect = env->NewObject(rectClass, ctor, rect.left(), rect.top(), rect.right(), rect.bottom()); return jrect; } - static jint hitTest(JNIEnv */*env*/, jobject /*thiz*/, jfloat x, jfloat y) + static int hitTest_helper(float x, float y) { QAccessibleInterface *root = interfaceFromId(-1); if (root && root->isValid()) { @@ -196,17 +263,29 @@ namespace QtAndroidAccessibility return -1; } + static jint hitTest(JNIEnv */*env*/, jobject /*thiz*/, jfloat x, jfloat y) + { + jint result = -1; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [x, y]() { + return hitTest_helper(x, y); + }, &result); + } + return result; + } + static void invokeActionOnInterfaceInMainThread(QAccessibleActionInterface* actionInterface, const QString& action) { + // Queue the action and return back to Java thread, so that we do not + // block it for too long QMetaObject::invokeMethod(qApp, [actionInterface, action]() { actionInterface->doAction(action); - }); + }, Qt::QueuedConnection); } - static jboolean clickAction(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + static bool clickAction_helper(int objectId) { -// qDebug() << "A11Y: CLICK: " << objectId; QAccessibleInterface *iface = interfaceFromId(objectId); if (!iface || !iface->isValid() || !iface->actionInterface()) return false; @@ -225,20 +304,45 @@ namespace QtAndroidAccessibility return true; } - static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + static jboolean clickAction(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + { + bool result = false; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return clickAction_helper(objectId); + }, &result); + } + return result; + } + + static bool scroll_helper(int objectId, const QString &actionName) { QAccessibleInterface *iface = interfaceFromId(objectId); if (iface && iface->isValid()) - return QAccessibleBridgeUtils::performEffectiveAction(iface, QAccessibleActionInterface::increaseAction()); + return QAccessibleBridgeUtils::performEffectiveAction(iface, actionName); return false; } + static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) + { + bool result = false; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return scroll_helper(objectId, QAccessibleActionInterface::increaseAction()); + }, &result); + } + return result; + } + static jboolean scrollBackward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId) { - QAccessibleInterface *iface = interfaceFromId(objectId); - if (iface && iface->isValid()) - return QAccessibleBridgeUtils::performEffectiveAction(iface, QAccessibleActionInterface::decreaseAction()); - return false; + bool result = false; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return scroll_helper(objectId, QAccessibleActionInterface::decreaseAction()); + }, &result); + } + return result; } @@ -251,66 +355,173 @@ if (!clazz) { \ //__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); + static QString textFromValue(QAccessibleInterface *iface) + { + QString valueStr; + QAccessibleValueInterface *valueIface = iface->valueInterface(); + if (valueIface) { + const QVariant valueVar = valueIface->currentValue(); + const auto type = static_cast<QMetaType::Type>(valueVar.type()); + if (type == QMetaType::Double || type == QMetaType::Float) { + // QVariant's toString() formats floating-point values with + // FloatingPointShortest, which is not an accessible + // representation; nor, in many cases, is it suitable to the UI + // element whose value we're looking at. So roll our own + // A11Y-friendly conversion to string. + const double val = valueVar.toDouble(); + // Try to use minimumStepSize() to determine precision + bool stepIsValid = false; + const double step = qAbs(valueIface->minimumStepSize().toDouble(&stepIsValid)); + if (!stepIsValid || qFuzzyIsNull(step)) { + // Ignore step, use default precision + valueStr = qFuzzyIsNull(val) ? QStringLiteral("0") : QString::number(val, 'f'); + } else { + const int precision = [](double s) { + int count = 0; + while (s < 1. && !qFuzzyCompare(s, 1.)) { + ++count; + s *= 10; + } + // If s is now 1.25, we want to show some more digits, + // but don't want to get silly with a step like 1./7; + // so only include a few extra digits. + const int stop = count + 3; + const auto fractional = [](double v) { + double whole = 0.0; + std::modf(v + 0.5, &whole); + return qAbs(v - whole); + }; + s = fractional(s); + while (count < stop && !qFuzzyIsNull(s)) { + ++count; + s = fractional(s * 10); + } + return count; + }(step); + valueStr = qFuzzyIsNull(val / step) ? QStringLiteral("0") + : QString::number(val, 'f', precision); + } + } else { + valueStr = valueVar.toString(); + } + } + return valueStr; + } + static jstring jvalueForAccessibleObject(int objectId) + { + QAccessibleInterface *iface = interfaceFromId(objectId); + const QString value = textFromValue(iface); + QJNIEnvironmentPrivate env; + jstring jstr = env->NewString((jchar*)value.constData(), (jsize)value.size()); +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + return jstr; + } - static jstring descriptionForAccessibleObject_helper(JNIEnv *env, QAccessibleInterface *iface) + static QString descriptionForInterface(QAccessibleInterface *iface) { QString desc; if (iface && iface->isValid()) { + bool hasValue = false; desc = iface->text(QAccessible::Name); if (desc.isEmpty()) desc = iface->text(QAccessible::Description); if (desc.isEmpty()) { desc = iface->text(QAccessible::Value); - if (desc.isEmpty()) { - if (QAccessibleValueInterface *valueIface = iface->valueInterface()) { - desc= valueIface->currentValue().toString(); - } - } + hasValue = !desc.isEmpty(); + } + if (!hasValue) { + if (!desc.isEmpty()) + desc.append(QChar(QChar::Space)); + desc.append(textFromValue(iface)); } } - return env->NewString((jchar*) desc.constData(), (jsize) desc.size()); + return desc; } - static jstring descriptionForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId) + static QString descriptionForAccessibleObject_helper(int objectId) { QAccessibleInterface *iface = interfaceFromId(objectId); - return descriptionForAccessibleObject_helper(env, iface); + return descriptionForInterface(iface); } - static bool populateNode(JNIEnv *env, jobject /*thiz*/, jint objectId, jobject node) + static jstring descriptionForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId) { + QString desc; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return descriptionForAccessibleObject_helper(objectId); + }, &desc); + } + return env->NewString((jchar*) desc.constData(), (jsize) desc.size()); + } + + + struct NodeInfo + { + bool valid = false; + QAccessible::State state; + QStringList actions; + QString description; + bool hasTextSelection = false; + int selectionStart = 0; + int selectionEnd = 0; + }; + + static NodeInfo populateNode_helper(int objectId) + { + NodeInfo info; QAccessibleInterface *iface = interfaceFromId(objectId); - if (!iface || !iface->isValid()) { + if (iface && iface->isValid()) { + info.valid = true; + info.state = iface->state(); + info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface); + info.description = descriptionForInterface(iface); + QAccessibleTextInterface *textIface = iface->textInterface(); + if (textIface && (textIface->selectionCount() > 0)) { + info.hasTextSelection = true; + textIface->selection(0, &info.selectionStart, &info.selectionEnd); + } + } + return info; + } + + static jboolean populateNode(JNIEnv *env, jobject /*thiz*/, jint objectId, jobject node) + { + NodeInfo info; + if (m_accessibilityContext) { + runInObjectContext(m_accessibilityContext, [objectId]() { + return populateNode_helper(objectId); + }, &info); + } + if (!info.valid) { __android_log_print(ANDROID_LOG_WARN, m_qtTag, "Accessibility: populateNode for Invalid ID"); return false; } - QAccessible::State state = iface->state(); - const QStringList actions = QAccessibleBridgeUtils::effectiveActionNames(iface); - const bool hasClickableAction = actions.contains(QAccessibleActionInterface::pressAction()) - || actions.contains(QAccessibleActionInterface::toggleAction()); - const bool hasIncreaseAction = actions.contains(QAccessibleActionInterface::increaseAction()); - const bool hasDecreaseAction = actions.contains(QAccessibleActionInterface::decreaseAction()); - // try to fill in the text property, this is what the screen reader reads - jstring jdesc = descriptionForAccessibleObject_helper(env, iface); - - if (QAccessibleTextInterface *textIface = iface->textInterface()) { - if (m_setTextSelectionMethodID && textIface->selectionCount() > 0) { - int startSelection; - int endSelection; - textIface->selection(0, &startSelection, &endSelection); - env->CallVoidMethod(node, m_setTextSelectionMethodID, startSelection, endSelection); - } + const bool hasClickableAction = + info.actions.contains(QAccessibleActionInterface::pressAction()) || + info.actions.contains(QAccessibleActionInterface::toggleAction()); + const bool hasIncreaseAction = + info.actions.contains(QAccessibleActionInterface::increaseAction()); + const bool hasDecreaseAction = + info.actions.contains(QAccessibleActionInterface::decreaseAction()); + + if (info.hasTextSelection && m_setTextSelectionMethodID) { + env->CallVoidMethod(node, m_setTextSelectionMethodID, info.selectionStart, + info.selectionEnd); } - env->CallVoidMethod(node, m_setCheckableMethodID, (bool)state.checkable); - env->CallVoidMethod(node, m_setCheckedMethodID, (bool)state.checked); - env->CallVoidMethod(node, m_setEditableMethodID, state.editable); - env->CallVoidMethod(node, m_setEnabledMethodID, !state.disabled); - env->CallVoidMethod(node, m_setFocusableMethodID, (bool)state.focusable); - env->CallVoidMethod(node, m_setFocusedMethodID, (bool)state.focused); - env->CallVoidMethod(node, m_setVisibleToUserMethodID, !state.invisible); + env->CallVoidMethod(node, m_setCheckableMethodID, (bool)info.state.checkable); + env->CallVoidMethod(node, m_setCheckedMethodID, (bool)info.state.checked); + env->CallVoidMethod(node, m_setEditableMethodID, info.state.editable); + env->CallVoidMethod(node, m_setEnabledMethodID, !info.state.disabled); + env->CallVoidMethod(node, m_setFocusableMethodID, (bool)info.state.focusable); + env->CallVoidMethod(node, m_setFocusedMethodID, (bool)info.state.focused); + env->CallVoidMethod(node, m_setVisibleToUserMethodID, !info.state.invisible); env->CallVoidMethod(node, m_setScrollableMethodID, hasIncreaseAction || hasDecreaseAction); env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction); @@ -326,7 +537,9 @@ if (!clazz) { \ if (hasDecreaseAction) env->CallVoidMethod(node, m_addActionMethodID, (int)0x00002000); // ACTION_SCROLL_BACKWARD defined in AccessibilityNodeInfo - + // try to fill in the text property, this is what the screen reader reads + jstring jdesc = env->NewString((jchar*)info.description.constData(), + (jsize)info.description.size()); //CALL_METHOD(node, "setText", "(Ljava/lang/CharSequence;)V", jdesc) env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc); diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h index de9d32a099..a6e2edee5e 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.h +++ b/src/plugins/platforms/android/androidjniaccessibility.h @@ -44,6 +44,8 @@ QT_BEGIN_NAMESPACE +class QObject; + namespace QtAndroidAccessibility { void initialize(); @@ -52,6 +54,8 @@ namespace QtAndroidAccessibility void notifyLocationChange(); void notifyObjectHide(uint accessibilityObjectId); void notifyObjectFocus(uint accessibilityObjectId); + void notifyValueChanged(uint accessibilityObjectId); + void createAccessibilityContextObject(QObject *parent); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 05516929bf..1abd23485e 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -263,18 +263,14 @@ namespace QtAndroidInput } } - static void touchEnd(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/) + static QTouchDevice *getTouchDevice() { - if (m_touchPoints.isEmpty()) - return; - - QMutexLocker lock(QtAndroid::platformInterfaceMutex()); QAndroidPlatformIntegration *platformIntegration = QtAndroid::androidPlatformIntegration(); if (!platformIntegration) - return; + return nullptr; QTouchDevice *touchDevice = platformIntegration->touchDevice(); - if (touchDevice == 0) { + if (!touchDevice) { touchDevice = new QTouchDevice; touchDevice->setType(QTouchDevice::TouchScreen); touchDevice->setCapabilities(QTouchDevice::Position @@ -285,10 +281,37 @@ namespace QtAndroidInput platformIntegration->setTouchDevice(touchDevice); } + return touchDevice; + } + + static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/) + { + if (m_touchPoints.isEmpty()) + return; + + QMutexLocker lock(QtAndroid::platformInterfaceMutex()); + QTouchDevice *touchDevice = getTouchDevice(); + if (!touchDevice) + return; + QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint()); QWindowSystemInterface::handleTouchEvent(window, touchDevice, m_touchPoints); } + static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/) + { + if (m_touchPoints.isEmpty()) + return; + + QMutexLocker lock(QtAndroid::platformInterfaceMutex()); + QTouchDevice *touchDevice = getTouchDevice(); + if (!touchDevice) + return; + + QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint()); + QWindowSystemInterface::handleTouchCancelEvent(window, touchDevice); + } + static bool isTabletEventSupported(JNIEnv */*env*/, jobject /*thiz*/) { #if QT_CONFIG(tabletevent) @@ -840,6 +863,7 @@ namespace QtAndroidInput {"touchBegin","(I)V",(void*)touchBegin}, {"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd}, {"touchEnd","(II)V",(void*)touchEnd}, + {"touchCancel", "(I)V", (void *)touchCancel}, {"mouseDown", "(III)V", (void *)mouseDown}, {"mouseUp", "(III)V", (void *)mouseUp}, {"mouseMove", "(III)V", (void *)mouseMove}, diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 8c6d9d9be3..9796a4c9c6 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org> -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -107,7 +107,6 @@ static sem_t m_exitSemaphore, m_terminateSemaphore; QHash<int, AndroidSurfaceClient *> m_surfaces; static QBasicMutex m_surfacesMutex; -static int m_surfaceId = 1; static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr; @@ -231,9 +230,10 @@ namespace QtAndroid QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyAccessibilityLocationChange"); } - void notifyObjectHide(uint accessibilityObjectId) + void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId) { - QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectHide","(I)V", accessibilityObjectId); + QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectHide", "(II)V", + accessibilityObjectId, parentObjectId); } void notifyObjectFocus(uint accessibilityObjectId) @@ -241,6 +241,13 @@ namespace QtAndroid QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId); } + void notifyValueChanged(uint accessibilityObjectId, jstring value) + { + QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyValueChanged", + "(ILjava/lang/String;)V", accessibilityObjectId, + value); + } + void notifyQtAndroidPluginRunning(bool running) { QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running); @@ -340,6 +347,12 @@ namespace QtAndroid return manufacturer + QLatin1Char(' ') + model; } + jint generateViewId() + { + return QJNIObjectPrivate::callStaticMethod<jint>("android/view/View","generateViewId", + "()I"); + } + int createSurface(AndroidSurfaceClient *client, const QRect &geometry, bool onTop, int imageDepth) { QJNIEnvironmentPrivate env; @@ -347,7 +360,7 @@ namespace QtAndroid return -1; m_surfacesMutex.lock(); - int surfaceId = m_surfaceId++; + jint surfaceId = generateViewId(); m_surfaces[surfaceId] = client; m_surfacesMutex.unlock(); @@ -370,7 +383,7 @@ namespace QtAndroid int insertNativeView(jobject view, const QRect &geometry) { m_surfacesMutex.lock(); - const int surfaceId = m_surfaceId++; + jint surfaceId = generateViewId(); m_surfaces[surfaceId] = nullptr; // dummy m_surfacesMutex.unlock(); @@ -554,16 +567,21 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/) vm->AttachCurrentThread(&env, &args); } + // Register type for invokeMethod() calls. + qRegisterMetaType<Qt::ScreenOrientation>("Qt::ScreenOrientation"); + // Register resources if they are available if (QFile{QStringLiteral("assets:/android_rcc_bundle.rcc")}.exists()) QResource::registerResource(QStringLiteral("assets:/android_rcc_bundle.rcc")); - QVarLengthArray<const char *> params(m_applicationParams.size()); - for (int i = 0; i < m_applicationParams.size(); i++) - params[i] = static_cast<const char *>(m_applicationParams[i].constData()); + const int argc = m_applicationParams.size(); + QVarLengthArray<char *> argv(argc + 1); + for (int i = 0; i < argc; i++) + argv[i] = m_applicationParams[i].data(); + argv[argc] = nullptr; startQtAndroidPluginCalled.fetchAndAddRelease(1); - int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data())); + int ret = m_main(argc, argv.data()); if (m_mainLibraryHnd) { int res = dlclose(m_mainLibraryHnd); @@ -655,12 +673,11 @@ static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/, jint widthPixels, jint heightPixels, jint desktopWidthPixels, jint desktopHeightPixels, jdouble xdpi, jdouble ydpi, - jdouble scaledDensity, jdouble density, bool forceUpdate) + jdouble scaledDensity, jdouble density, jfloat refreshRate) { // Android does not give us the correct screen size for immersive mode, but // the surface does have the right size - bool updateDesktopSize = m_desktopWidthPixels != desktopWidthPixels; widthPixels = qMax(widthPixels, desktopWidthPixels); heightPixels = qMax(heightPixels, desktopHeightPixels); @@ -678,12 +695,13 @@ static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/, widthPixels, heightPixels); } else { - m_androidPlatformIntegration->setDisplayMetrics(qRound(double(widthPixels) / xdpi * 25.4), - qRound(double(heightPixels) / ydpi * 25.4)); - m_androidPlatformIntegration->setScreenSize(widthPixels, heightPixels); - if (updateDesktopSize || forceUpdate) { - m_androidPlatformIntegration->setDesktopSize(desktopWidthPixels, desktopHeightPixels); - } + const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4), + qRound(double(heightPixels) / ydpi * 25.4)); + const QSize screenSize(widthPixels, heightPixels); + const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels); + m_androidPlatformIntegration->setScreenSizeParameters(physicalSize, screenSize, + availableGeometry); + m_androidPlatformIntegration->setRefreshRate(refreshRate); } } @@ -778,12 +796,22 @@ static void handleOrientationChanged(JNIEnv */*env*/, jobject /*thiz*/, jint new QAndroidPlatformIntegration::setScreenOrientation(screenOrientation, native); QMutexLocker lock(&m_platformMutex); if (m_androidPlatformIntegration) { - QPlatformScreen *screen = m_androidPlatformIntegration->screen(); - QWindowSystemInterface::handleScreenOrientationChange(screen->screen(), - screenOrientation); + QAndroidPlatformScreen *screen = m_androidPlatformIntegration->screen(); + // Use invokeMethod to keep the certain order of the "geometry change" + // and "orientation change" event handling. + if (screen) { + QMetaObject::invokeMethod(screen, "setOrientation", Qt::AutoConnection, + Q_ARG(Qt::ScreenOrientation, screenOrientation)); + } } } +static void handleRefreshRateChanged(JNIEnv */*env*/, jclass /*cls*/, jfloat refreshRate) +{ + if (m_androidPlatformIntegration) + m_androidPlatformIntegration->setRefreshRate(refreshRate); +} + static void onActivityResult(JNIEnv */*env*/, jclass /*cls*/, jint requestCode, jint resultCode, @@ -809,14 +837,15 @@ static JNINativeMethod methods[] = { {"quitQtCoreApplication", "()V", (void *)quitQtCoreApplication}, {"terminateQt", "()V", (void *)terminateQt}, {"waitForServiceSetup", "()V", (void *)waitForServiceSetup}, - {"setDisplayMetrics", "(IIIIDDDDZ)V", (void *)setDisplayMetrics}, + {"setDisplayMetrics", "(IIIIDDDDF)V", (void *)setDisplayMetrics}, {"setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface}, {"updateWindow", "()V", (void *)updateWindow}, {"updateApplicationState", "(I)V", (void *)updateApplicationState}, {"handleOrientationChanged", "(II)V", (void *)handleOrientationChanged}, {"onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult}, {"onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent}, - {"onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind} + {"onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind}, + {"handleRefreshRateChanged", "(F)V", (void *)handleRefreshRateChanged} }; #define FIND_AND_CHECK_CLASS(CLASS_NAME) \ diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 641b2dbdb4..240a0b83f9 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -96,8 +96,9 @@ namespace QtAndroid jobject createBitmapDrawable(jobject bitmap, JNIEnv *env = 0); void notifyAccessibilityLocationChange(); - void notifyObjectHide(uint accessibilityObjectId); + void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId); void notifyObjectFocus(uint accessibilityObjectId); + void notifyValueChanged(uint accessibilityObjectId, jstring value); void notifyQtAndroidPluginRunning(bool running); const char *classErrorMsgFmt(); diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp index cc05dad749..706d1bb4fa 100644 --- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp +++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp @@ -66,7 +66,15 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent * QtAndroidAccessibility::notifyObjectHide(event->uniqueId()); } else if (event->type() == QAccessible::Focus) { QtAndroidAccessibility::notifyObjectFocus(event->uniqueId()); + } else if (event->type() == QAccessible::ValueChanged) { + QtAndroidAccessibility::notifyValueChanged(event->uniqueId()); } } +void QAndroidPlatformAccessibility::setRootObject(QObject *obj) +{ + QPlatformAccessibility::setRootObject(obj); + QtAndroidAccessibility::createAccessibilityContextObject(obj); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.h b/src/plugins/platforms/android/qandroidplatformaccessibility.h index 8216c05fa6..df3fe43a04 100644 --- a/src/plugins/platforms/android/qandroidplatformaccessibility.h +++ b/src/plugins/platforms/android/qandroidplatformaccessibility.h @@ -52,6 +52,7 @@ public: ~QAndroidPlatformAccessibility(); void notifyAccessibilityUpdate(QAccessibleEvent *event) override; + void setRootObject(QObject *obj) override; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp index 3074dee26f..6fe0caba2e 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.cpp +++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp @@ -188,9 +188,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList ¶ m_primaryScreen = new QAndroidPlatformScreen(); QWindowSystemInterface::handleScreenAdded(m_primaryScreen); - m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight)); - m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight)); - m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight)); + const QSize physicalSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight); + const QSize screenSize(m_defaultScreenWidth, m_defaultScreenHeight); + const QRect geometry(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight); + m_primaryScreen->setSizeParameters(physicalSize, screenSize, geometry); m_mainThread = QThread::currentThread(); @@ -463,10 +464,10 @@ void QAndroidPlatformIntegration::setScreenOrientation(Qt::ScreenOrientation cur void QAndroidPlatformIntegration::flushPendingUpdates() { - m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth, - m_defaultPhysicalSizeHeight)); - m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight)); - m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight)); + const QSize physicalSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight); + const QSize screenSize(m_defaultScreenWidth, m_defaultScreenHeight); + const QRect geometry(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight); + m_primaryScreen->setSizeParameters(physicalSize, screenSize, geometry); } #ifndef QT_NO_ACCESSIBILITY @@ -494,6 +495,23 @@ void QAndroidPlatformIntegration::setScreenSize(int width, int height) QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height))); } +void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize, + const QSize &screenSize, + const QRect &availableGeometry) +{ + if (m_primaryScreen) { + QMetaObject::invokeMethod(m_primaryScreen, "setSizeParameters", Qt::AutoConnection, + Q_ARG(QSize, physicalSize), Q_ARG(QSize, screenSize), + Q_ARG(QRect, availableGeometry)); + } +} + +void QAndroidPlatformIntegration::setRefreshRate(qreal refreshRate) +{ + if (m_primaryScreen) + QMetaObject::invokeMethod(m_primaryScreen, "setRefreshRate", Qt::AutoConnection, + Q_ARG(qreal, refreshRate)); +} #if QT_CONFIG(vulkan) QPlatformVulkanInstance *QAndroidPlatformIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h index ecbde4f951..f34972b81e 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.h +++ b/src/plugins/platforms/android/qandroidplatformintegration.h @@ -96,6 +96,13 @@ public: virtual void setDesktopSize(int width, int height); virtual void setDisplayMetrics(int width, int height); void setScreenSize(int width, int height); + // The 3 methods above were replaced by a new one, so that we could have + // a better control over "geometry changed" event handling. Technically + // they are no longer used and can be removed. Not doing it now, because + // I'm not sure if it might be helpful to have them or not. + void setScreenSizeParameters(const QSize &physicalSize, const QSize &screenSize, + const QRect &availableGeometry); + void setRefreshRate(qreal refreshRate); bool isVirtualDesktop() { return true; } QPlatformFontDatabase *fontDatabase() const override; diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp index 7e036868fc..0f60202eb9 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -55,6 +55,7 @@ #include <android/native_window_jni.h> #include <qguiapplication.h> +#include <QtCore/private/qjnihelpers_p.h> #include <QtGui/QGuiApplication> #include <QtGui/QWindow> #include <QtGui/private/qwindow_p.h> @@ -104,6 +105,42 @@ QAndroidPlatformScreen::QAndroidPlatformScreen() m_physicalSize.setHeight(QAndroidPlatformIntegration::m_defaultPhysicalSizeHeight); m_physicalSize.setWidth(QAndroidPlatformIntegration::m_defaultPhysicalSizeWidth); connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidPlatformScreen::applicationStateChanged); + + QJNIObjectPrivate activity(QtAndroid::activity()); + if (!activity.isValid()) + return; + QJNIObjectPrivate display; + if (QtAndroidPrivate::androidSdkVersion() < 30) { + display = activity.callObjectMethod("getWindowManager", "()Landroid/view/WindowManager;") + .callObjectMethod("getDefaultDisplay", "()Landroid/view/Display;"); + } else { + display = activity.callObjectMethod("getDisplay", "()Landroid/view/Display;"); + } + if (!display.isValid()) + return; + m_name = display.callObjectMethod("getName", "()Ljava/lang/String;").toString(); + m_refreshRate = display.callMethod<jfloat>("getRefreshRate"); + if (QtAndroidPrivate::androidSdkVersion() < 23) { + m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate }; + return; + } + QJNIEnvironmentPrivate env; + const jint currentMode = display.callObjectMethod("getMode", "()Landroid/view/Display$Mode;") + .callMethod<jint>("getModeId"); + const auto modes = display.callObjectMethod("getSupportedModes", + "()[Landroid/view/Display$Mode;"); + const auto modesArray = jobjectArray(modes.object()); + const auto sz = env->GetArrayLength(modesArray); + for (jsize i = 0; i < sz; ++i) { + auto mode = QJNIObjectPrivate::fromLocalRef(env->GetObjectArrayElement(modesArray, i)); + if (currentMode == mode.callMethod<jint>("getModeId")) + m_currentMode = m_modes.size(); + m_modes << Mode { .size = QSize { mode.callMethod<jint>("getPhysicalHeight"), + mode.callMethod<jint>("getPhysicalWidth") }, + .refreshRate = mode.callMethod<jfloat>("getRefreshRate") }; + } + if (m_modes.isEmpty()) + m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate }; } QAndroidPlatformScreen::~QAndroidPlatformScreen() @@ -243,6 +280,37 @@ void QAndroidPlatformScreen::setSize(const QSize &size) QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry()); } +void QAndroidPlatformScreen::setSizeParameters(const QSize &physicalSize, const QSize &size, + const QRect &availableGeometry) +{ + // The goal of this method is to set all geometry-related parameters + // at the same time and generate only one screen geometry change event. + m_physicalSize = physicalSize; + m_size = size; + // If available geometry has changed, the event will be handled in + // setAvailableGeometry. Otherwise we need to explicitly handle it to + // retain the behavior, because setSize() does the handling unconditionally. + if (m_availableGeometry != availableGeometry) { + setAvailableGeometry(availableGeometry); + } else { + QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), + this->availableGeometry()); + } +} + +void QAndroidPlatformScreen::setRefreshRate(qreal refreshRate) +{ + if (refreshRate == m_refreshRate) + return; + m_refreshRate = refreshRate; + QWindowSystemInterface::handleScreenRefreshRateChange(QPlatformScreen::screen(), refreshRate); +} + +void QAndroidPlatformScreen::setOrientation(Qt::ScreenOrientation orientation) +{ + QWindowSystemInterface::handleScreenOrientationChange(QPlatformScreen::screen(), orientation); +} + void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect) { QMutexLocker lock(&m_surfaceMutex); diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h index 54b3c5b8a8..e73ea31a86 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.h +++ b/src/plugins/platforms/android/qandroidplatformscreen.h @@ -69,6 +69,12 @@ public: QImage::Format format() const override { return m_format; } QSizeF physicalSize() const override { return m_physicalSize; } + QString name() const override { return m_name; } + QVector<Mode> modes() const override { return m_modes; } + int currentMode() const override { return m_currentMode; } + int preferredMode() const override { return m_currentMode; } + qreal refreshRate() const override { return m_refreshRate; } + inline QWindow *topWindow() const; QWindow *topLevelAt(const QPoint & p) const override; @@ -87,6 +93,10 @@ public slots: void setPhysicalSize(const QSize &size); void setAvailableGeometry(const QRect &rect); void setSize(const QSize &size); + void setSizeParameters(const QSize &physicalSize, const QSize &size, + const QRect &availableGeometry); + void setRefreshRate(qreal refreshRate); + void setOrientation(Qt::ScreenOrientation orientation); protected: bool event(QEvent *event) override; @@ -100,6 +110,10 @@ protected: int m_depth; QImage::Format m_format; QSizeF m_physicalSize; + qreal m_refreshRate; + QString m_name; + QVector<Mode> m_modes; + int m_currentMode = 0; private: QDpi logicalDpi() const override; diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index 8ca72ec619..c963ff937c 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -45,6 +45,15 @@ #include <QtGui/QBitmap> +#if !defined(QT_APPLE_NO_PRIVATE_APIS) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end +#endif // QT_APPLE_NO_PRIVATE_APIS + QT_BEGIN_NAMESPACE QCocoaCursor::QCocoaCursor() @@ -116,7 +125,7 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) return nil; const Qt::CursorShape newShape = cursor->shape(); - NSCursor *cocoaCursor; + NSCursor *cocoaCursor = nil; // Check for a suitable built-in NSCursor first: switch (newShape) { @@ -157,7 +166,29 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) case Qt::DragLinkCursor: cocoaCursor = [NSCursor dragLinkCursor]; break; - default : { +#if !defined(QT_APPLE_NO_PRIVATE_APIS) + case Qt::SizeVerCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthSouthCursor]; + break; + case Qt::SizeHorCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)]) + cocoaCursor = [NSCursor _windowResizeEastWestCursor]; + break; + case Qt::SizeBDiagCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthEastSouthWestCursor]; + break; + case Qt::SizeFDiagCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthWestSouthEastCursor]; + break; +#endif // QT_APPLE_NO_PRIVATE_APIS + default: + break; + } + + if (!cocoaCursor) { // No suitable OS cursor exist, use cursors provided // by Qt for the rest. Check for a cached cursor: cocoaCursor = m_cursors.value(newShape); @@ -172,8 +203,6 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) m_cursors.insert(newShape, cocoaCursor); } - - break; } } return cocoaCursor; } diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 5dac9b9a91..7c3f69b408 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -292,7 +292,12 @@ static QString strippedText(QString s) } } - QString qtFileName = QFileInfo(QString::fromNSString(filename)).fileName(); + // Treat symbolic links and aliases to directories like directories + QFileInfo fileInfo(QString::fromNSString(filename)); + if (fileInfo.isSymLink() && QFileInfo(fileInfo.symLinkTarget()).isDir()) + return YES; + + QString qtFileName = fileInfo.fileName(); // No filter means accept everything bool nameMatches = mSelectedNameFilter->isEmpty(); // Check if the current file name filter accepts the file: diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index 581ea01fcc..c03c191376 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -230,13 +230,13 @@ static QString displayName(CGDirectDisplayID displayID) NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary( display, kIODisplayOnlyPreferredName) autorelease]; - if ([[info objectForKey:@kDisplayVendorID] longValue] != CGDisplayVendorNumber(displayID)) + if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID)) continue; - if ([[info objectForKey:@kDisplayProductID] longValue] != CGDisplayModelNumber(displayID)) + if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID)) continue; - if ([[info objectForKey:@kDisplaySerialNumber] longValue] != CGDisplaySerialNumber(displayID)) + if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID)) continue; NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName]; diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index ef80a47e0f..73995639aa 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -678,7 +678,11 @@ } } else if (theEvent.momentumPhase == NSEventPhaseBegan) { Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull()); - phase = Qt::ScrollUpdate; // Send as update, it has a delta + // If we missed finding a momentum NSEventPhaseBegan when the non-momentum + // phase ended we need to treat this as a scroll begin, to not confuse client + // code. Otherwise we treat it as a continuation of the existing scroll. + phase = m_scrolling ? Qt::ScrollUpdate : Qt::ScrollBegin; + m_scrolling = true; } else if (theEvent.momentumPhase == NSEventPhaseChanged) { phase = Qt::ScrollMomentum; } else if (theEvent.phase == NSEventPhaseCancelled @@ -690,6 +694,16 @@ Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary); } + // Sanitize deltas for events that should not result in scrolling. + // On macOS 12.1 this phase has been observed to report deltas. + if (theEvent.phase == NSEventPhaseCancelled) { + if (!pixelDelta.isNull() || !angleDelta.isNull()) { + qCInfo(lcQpaMouse) << "Ignoring unexpected delta for" << theEvent; + pixelDelta = QPoint(); + angleDelta = QPoint(); + } + } + // Prevent keyboard modifier state from changing during scroll event streams. // A two-finger trackpad flick generates a stream of scroll events. We want // the keyboard modifier state to be the state at the beginning of the diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm index ba1fa55892..f365403502 100644 --- a/src/plugins/platforms/cocoa/qnsview_tablet.mm +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -148,9 +148,6 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) device = QTabletEvent::Stylus; } else { switch (bits & 0x0F06) { - case 0x0802: - device = QTabletEvent::Stylus; - break; case 0x0902: device = QTabletEvent::Airbrush; break; @@ -163,8 +160,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) case 0x0804: device = QTabletEvent::RotationStylus; break; - default: - device = QTabletEvent::NoDevice; + default: // usually 0x0802, but 0 on iPad sidecar with Apple Pencil + device = QTabletEvent::Stylus; } } return device; diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h index dba6f24fc5..2fe3c9e382 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h @@ -41,6 +41,8 @@ #include "qiosfiledialog.h" -@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate, UINavigationControllerDelegate> +@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate, + UINavigationControllerDelegate, + UIAdaptivePresentationControllerDelegate> - (instancetype)initWithQIOSFileDialog:(QIOSFileDialog *)fileDialog; @end diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm index c1b641e839..da78d41115 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm @@ -72,6 +72,7 @@ m_fileDialog = fileDialog; self.modalPresentationStyle = UIModalPresentationFormSheet; self.delegate = self; + self.presentationController.delegate = self; if (m_fileDialog->options()->fileMode() == QFileDialogOptions::ExistingFiles) self.allowsMultipleSelection = YES; @@ -100,4 +101,18 @@ emit m_fileDialog->reject(); } +- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController +{ + Q_UNUSED(presentationController); + + // "Called on the delegate when the user has taken action to dismiss the + // presentation successfully, after all animations are finished. + // This is not called if the presentation is dismissed programatically." + + // So if document picker's view was dismissed, for example by swiping it away, + // we got this method called. But not if the dialog was cancelled or a file + // was selected. + emit m_fileDialog->reject(); +} + @end diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index 6fa5a8a2b3..1f49f0aefd 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -48,6 +48,7 @@ #include <qpa/qplatformnativeinterface.h> #include <QtPlatformHeaders/qwglnativecontext.h> +#include <private/qsystemlibrary_p.h> #include <algorithm> #include <wingdi.h> @@ -162,19 +163,25 @@ QFunctionPointer QWindowsOpengl32DLL::resolve(const char *name) bool QWindowsOpengl32DLL::init(bool softwareRendering) { - const QByteArray opengl32 = QByteArrayLiteral("opengl32.dll"); - const QByteArray swopengl = QByteArrayLiteral("opengl32sw.dll"); + const QByteArray opengl32 = QByteArrayLiteral("opengl32"); + const QByteArray swopengl = QByteArrayLiteral("opengl32sw"); + bool useSystemLib = false; QByteArray openglDll = qgetenv("QT_OPENGL_DLL"); - if (openglDll.isEmpty()) + if (openglDll.isEmpty()) { openglDll = softwareRendering ? swopengl : opengl32; + useSystemLib = !softwareRendering; + } openglDll = openglDll.toLower(); m_nonOpengl32 = openglDll != opengl32; qCDebug(lcQpaGl) << "Qt: Using WGL and OpenGL from" << openglDll; - m_lib = ::LoadLibraryA(openglDll.constData()); + if (useSystemLib) + m_lib = QSystemLibrary::load((wchar_t*)(QString::fromLatin1(openglDll).utf16())); + else + m_lib = LoadLibraryA(openglDll.constData()); if (!m_lib) { qErrnoWarning(::GetLastError(), "Failed to load %s", openglDll.constData()); return false; @@ -184,7 +191,7 @@ bool QWindowsOpengl32DLL::init(bool softwareRendering) // Load opengl32.dll always. GDI functions like ChoosePixelFormat do // GetModuleHandle for opengl32.dll and behave differently (and call back into // opengl32) when the module is present. This is fine for dummy contexts and windows. - ::LoadLibraryA("opengl32.dll"); + QSystemLibrary::load(L"opengl32"); } wglCreateContext = reinterpret_cast<HGLRC (WINAPI *)(HDC)>(resolve("wglCreateContext")); diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp index d7d186e804..9eb4011bf2 100644 --- a/src/plugins/platforms/windows/qwindowsopengltester.cpp +++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp @@ -49,6 +49,7 @@ #include <QtCore/qstandardpaths.h> #include <QtCore/qlibraryinfo.h> #include <QtCore/qhash.h> +#include <private/qsystemlibrary_p.h> #ifndef QT_NO_OPENGL #include <private/qopengl_p.h> @@ -396,7 +397,7 @@ bool QWindowsOpenGLTester::testDesktopGL() // Test #1: Load opengl32.dll and try to resolve an OpenGL 2 function. // This will typically fail on systems that do not have a real OpenGL driver. - lib = LoadLibraryA("opengl32.dll"); + lib = QSystemLibrary::load(L"opengl32"); if (lib) { CreateContext = reinterpret_cast<CreateContextType>( reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglCreateContext"))); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 3f7f925877..9820d1ec2c 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -2683,32 +2683,30 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const // This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the // taskbar when maximized - if ((testFlag(WithinMaximize) || window()->windowStates().testFlag(Qt::WindowMinimized)) - && (m_data.flags.testFlag(Qt::FramelessWindowHint) - || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))) { - const QScreen *screen = window()->screen(); - - // Documentation of MINMAXINFO states that it will only work for the primary screen - if (screen && screen == QGuiApplication::primaryScreen()) { - const QRect availableGeometry = QHighDpi::toNativePixels(screen->availableGeometry(), screen); + if (m_data.flags.testFlag(Qt::FramelessWindowHint) + || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint))) { + if (QPlatformScreen *currentScreen = screen()) { + const QRect geometry = currentScreen->geometry(); + const QRect availableGeometry = currentScreen->availableGeometry(); mmi->ptMaxSize.y = availableGeometry.height(); // Width, because you can have the taskbar on the sides too. mmi->ptMaxSize.x = availableGeometry.width(); // If you have the taskbar on top, or on the left you don't want it at (0,0): - mmi->ptMaxPosition.x = availableGeometry.x(); - mmi->ptMaxPosition.y = availableGeometry.y(); + QPoint availablePositionDiff = geometry.topLeft() - availableGeometry.topLeft(); + mmi->ptMaxPosition.x = availablePositionDiff.x(); + mmi->ptMaxPosition.y = availablePositionDiff.y(); if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) { - const int borderWidth = getBorderWidth(screen->handle()); + const int borderWidth = getBorderWidth(currentScreen); mmi->ptMaxSize.x += borderWidth * 2; mmi->ptMaxSize.y += borderWidth * 2; mmi->ptMaxTrackSize = mmi->ptMaxSize; mmi->ptMaxPosition.x -= borderWidth; mmi->ptMaxPosition.y -= borderWidth; } - } else if (!screen){ - qWarning("window()->screen() returned a null screen"); + } else { + qWarning("screen() returned a null screen"); } } diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp index 5e406017ca..e8c9c27ba8 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp @@ -58,7 +58,7 @@ const xcb_visualtype_t *QXcbGlxWindow::createVisual() { QXcbScreen *scr = xcbScreen(); if (!scr) - return nullptr; + return QXcbWindow::createVisual(); qCDebug(lcQpaGl) << "Requested format before FBConfig/Visual selection:" << m_format; @@ -71,10 +71,13 @@ const xcb_visualtype_t *QXcbGlxWindow::createVisual() flags |= QGLX_SUPPORTS_SRGB; } + const auto formatBackup = m_format; XVisualInfo *visualInfo = qglx_findVisualInfo(dpy, scr->screenNumber(), &m_format, GLX_WINDOW_BIT, flags); if (!visualInfo) { - qWarning() << "No XVisualInfo for format" << m_format; - return nullptr; + qCDebug(lcQpaGl) << "No XVisualInfo for format" << m_format; + // restore initial format before requesting it again + m_format = formatBackup; + return QXcbWindow::createVisual(); } const xcb_visualtype_t *xcb_visualtype = scr->visualForId(visualInfo->visualid); XFree(visualInfo); diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 27a2526df1..5a36cfa0d3 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -645,7 +645,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo continue; if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf", - atomName(vci.label).constData(), value, vci.min, vci.max); + atomName(atom(vci.label)).constData(), value, vci.min, vci.max); if (value > vci.max) value = vci.max; if (value < vci.min) diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index ffda7b6a57..dfc9c7d7a3 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -1639,7 +1639,7 @@ void QXcbWindow::setWmWindowType(QXcbWindowFunctions::WmWindowTypes types, Qt::W break; } - if ((flags & Qt::FramelessWindowHint) && !(type & QXcbWindowFunctions::KdeOverride)) { + if ((flags & Qt::FramelessWindowHint) && !(types & QXcbWindowFunctions::KdeOverride)) { // override netwm type - quick and easy for KDE noborder atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE)); } diff --git a/src/plugins/printsupport/cups/qcupsprintengine.cpp b/src/plugins/printsupport/cups/qcupsprintengine.cpp index 43d5e119ad..1f9cbc4565 100644 --- a/src/plugins/printsupport/cups/qcupsprintengine.cpp +++ b/src/plugins/printsupport/cups/qcupsprintengine.cpp @@ -89,8 +89,10 @@ void QCupsPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &v break; case PPK_Duplex: { QPrint::DuplexMode mode = QPrint::DuplexMode(value.toInt()); - if (mode != d->duplex && d->m_printDevice.supportedDuplexModes().contains(mode)) + if (d->m_printDevice.supportedDuplexModes().contains(mode)) { d->duplex = mode; + d->duplexRequestedExplicitly = true; + } break; } case PPK_PrinterName: @@ -277,9 +279,12 @@ void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter) m_printDevice.swap(printDevice); printerName = m_printDevice.id(); - // Check if new printer supports current settings, otherwise us defaults - if (duplex != QPrint::DuplexAuto && !m_printDevice.supportedDuplexModes().contains(duplex)) + // in case a duplex value was explicitly set, check if new printer supports current value, + // otherwise use device default + if (!duplexRequestedExplicitly || !m_printDevice.supportedDuplexModes().contains(duplex)) { duplex = m_printDevice.defaultDuplexMode(); + duplexRequestedExplicitly = false; + } QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color; if (!m_printDevice.supportedColorModes().contains(colorMode)) grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale; diff --git a/src/plugins/printsupport/cups/qcupsprintengine_p.h b/src/plugins/printsupport/cups/qcupsprintengine_p.h index c021b0c643..8f81fe478d 100644 --- a/src/plugins/printsupport/cups/qcupsprintengine_p.h +++ b/src/plugins/printsupport/cups/qcupsprintengine_p.h @@ -100,6 +100,7 @@ private: QStringList cupsOptions; QString cupsTempFile; QPrint::DuplexMode duplex; + bool duplexRequestedExplicitly = false; }; QT_END_NAMESPACE diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index 2a37a951f4..adbeec8da7 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -1680,8 +1680,8 @@ bool QMacStylePrivate::CocoaControl::getCocoaButtonTypeAndBezelStyle(NSButtonTyp *bezelStyle = NSShadowlessSquareBezelStyle; break; case Button_PushButton: - *buttonType = NSPushOnPushOffButton; - *bezelStyle = NSRoundedBezelStyle; + *buttonType = NSButtonTypeMomentaryPushIn; + *bezelStyle = NSBezelStyleRounded; break; default: return false; @@ -3725,8 +3725,15 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter pb.frame = frameRect.toCGRect(); pb.enabled = isEnabled; + + // With the 'momentary push in' type this gives an impression of a + // button in a 'pressed' state (the 'momentary push in' does + // not show its state otherwise): [pb highlight:isPressed]; - pb.state = isHighlighted && !isPressed ? NSOnState : NSOffState; + // For default/checked button this will give the required + // button accent color: + pb.keyEquivalent = isHighlighted ? @"\r" : @""; + d->drawNSViewInRect(pb, frameRect, p, ^(CGContextRef, const CGRect &r) { [pb.cell drawBezelWithFrame:r inView:pb.superview]; }); @@ -3768,16 +3775,22 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter const bool hasText = !btn.text.isEmpty(); const bool isActive = btn.state & State_Active; const bool isPressed = btn.state & State_Sunken; + const bool isDefault = (btn.features & QStyleOptionButton::DefaultButton && !d->autoDefaultButton) + || d->autoDefaultButton == btn.styleObject; const auto ct = cocoaControlType(&btn, w); if (!hasMenu && ct != QMacStylePrivate::Button_SquareButton) { - if (isPressed - || (isActive && isEnabled - && ((btn.state & State_On) - || ((btn.features & QStyleOptionButton::DefaultButton) && !d->autoDefaultButton) - || d->autoDefaultButton == btn.styleObject))) - btn.palette.setColor(QPalette::ButtonText, Qt::white); + if (isPressed || (isActive && isEnabled && ((btn.state & State_On) || isDefault))) + btn.palette.setColor(QPalette::ButtonText, Qt::white); + } + + if (!isDarkMode() && QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) { + if (!isDefault && !(btn.state & State_On)) { + // On macOS 12 it's a gray button, white text color (if set in the + // previous statement) would be almost invisible. + btn.palette.setColor(QPalette::ButtonText, Qt::black); + } } if ((!hasIcon && !hasMenu) || (hasIcon && !hasText)) { @@ -3955,6 +3968,13 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter } pb.frame = frameRect.toCGRect(); + if (!isPopupButton) { + // Note: these days we use 'momentary push in' for Button_PushButton, + // but tabs are also rendered using NSButton/ButtonPushButton, and + // here we need 'push on/off' to make it work (tab highlight colors). + pb.buttonType = NSButtonTypePushOnPushOff; + } + pb.enabled = isEnabled; [pb highlight:isPressed]; // Set off state when inactive. See needsInactiveHack for when it's selected |