diff options
Diffstat (limited to 'src/plugins')
104 files changed, 1493 insertions, 1455 deletions
diff --git a/src/plugins/networkinformation/android/jar/build.gradle b/src/plugins/networkinformation/android/jar/build.gradle index 68a9381ad2..ea6d06c257 100644 --- a/src/plugins/networkinformation/android/jar/build.gradle +++ b/src/plugins/networkinformation/android/jar/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' + classpath 'com.android.tools.build:gradle:8.4.0' } } @@ -26,7 +26,7 @@ android { compileSdk 34 defaultConfig { - minSdkVersion 23 + minSdkVersion 28 } sourceSets { diff --git a/src/plugins/platforms/android/CMakeLists.txt b/src/plugins/platforms/android/CMakeLists.txt index d5a275a76c..aaf62dd7e7 100644 --- a/src/plugins/platforms/android/CMakeLists.txt +++ b/src/plugins/platforms/android/CMakeLists.txt @@ -9,7 +9,7 @@ qt_find_package(EGL) qt_internal_add_plugin(QAndroidIntegrationPlugin OUTPUT_NAME qtforandroid PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES android + DEFAULT_IF "android" IN_LIST QT_QPA_PLATFORMS SOURCES androidcontentfileengine.cpp androidcontentfileengine.h androiddeadlockprotector.h @@ -41,6 +41,7 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin qandroidplatformwindow.cpp qandroidplatformwindow.h qandroidsystemlocale.cpp qandroidsystemlocale.h androidwindowembedding.cpp androidwindowembedding.h + androidbackendregister.cpp androidbackendregister.h NO_UNITY_BUILD_SOURCES # Conflicting symbols and macros with androidjnimain.cpp # TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar diff --git a/src/plugins/platforms/android/androidbackendregister.cpp b/src/plugins/platforms/android/androidbackendregister.cpp new file mode 100644 index 0000000000..bfd86138aa --- /dev/null +++ b/src/plugins/platforms/android/androidbackendregister.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "androidbackendregister.h" + +#include "androidjnimain.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcAndroidBackendRegister, "qt.qpa.androidbackendregister") + +Q_DECLARE_JNI_CLASS(BackendRegister, "org/qtproject/qt/android/BackendRegister"); + +bool AndroidBackendRegister::registerNatives() +{ + return QtJniTypes::BackendRegister::registerNativeMethods( + { Q_JNI_NATIVE_SCOPED_METHOD(registerBackend, AndroidBackendRegister), + Q_JNI_NATIVE_SCOPED_METHOD(unregisterBackend, AndroidBackendRegister) }); +} + +void AndroidBackendRegister::registerBackend(JNIEnv *, jclass, jclass interfaceClass, + jobject interface) +{ + if (AndroidBackendRegister *reg = QtAndroid::backendRegister()) { + const QJniObject classObject(static_cast<jobject>(interfaceClass)); + QString name = classObject.callMethod<jstring>("getName").toString(); + name.replace('.', '/'); + + QMutexLocker lock(®->m_registerMutex); + reg->m_register[name] = QJniObject(interface); + } else { + qCWarning(lcAndroidBackendRegister) + << "AndroidBackendRegister pointer is null, cannot register functionality"; + } +} + +void AndroidBackendRegister::unregisterBackend(JNIEnv *, jclass, jclass interfaceClass) +{ + if (AndroidBackendRegister *reg = QtAndroid::backendRegister()) { + const QJniObject classObject(static_cast<jobject>(interfaceClass)); + QString name = classObject.callMethod<jstring>("getName").toString(); + name.replace('.', '/'); + + QMutexLocker lock(®->m_registerMutex); + reg->m_register.remove(name); + } else { + qCWarning(lcAndroidBackendRegister) + << "AndroidBackendRegister pointer is null, cannot unregister functionality"; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/androidbackendregister.h b/src/plugins/platforms/android/androidbackendregister.h new file mode 100644 index 0000000000..c186f7e107 --- /dev/null +++ b/src/plugins/platforms/android/androidbackendregister.h @@ -0,0 +1,67 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef ANDROIDBACKENDREGISTER_H +#define ANDROIDBACKENDREGISTER_H + +#include <type_traits> + +#include <QtCore/qjnienvironment.h> +#include <QtCore/qjnitypes.h> +#include <QtCore/qjniobject.h> + +#include <QtCore/qstring.h> +#include <QtCore/qmap.h> +#include <QtCore/qmutex.h> +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcAndroidBackendRegister) + +template <typename T> +using ValidInterfaceType = std::enable_if_t<std::is_base_of_v<QtJniTypes::JObjectBase, T>, bool>; + +class AndroidBackendRegister +{ +public: + static bool registerNatives(); + + template <typename T, ValidInterfaceType<T> = true> + [[nodiscard]] T getInterface() + { + QMutexLocker lock(&m_registerMutex); + return m_register.value(QString(QtJniTypes::Traits<T>::className().data())); + } + + template <typename Object> + using IsObjectType = + typename std::disjunction<std::is_base_of<QJniObject, Object>, + std::is_base_of<QtJniTypes::JObjectBase, Object>>; + + template <typename Interface, typename Ret, typename... Args, + ValidInterfaceType<Interface> = true> + auto callInterface(const char *func, Args... args) + { + if (const auto obj = getInterface<Interface>(); obj.isValid()) + return obj.template callMethod<Ret, Args...>(func, std::forward<Args>(args)...); + + if constexpr (IsObjectType<Ret>::value) + return Ret(QJniObject()); + if constexpr (!std::is_same_v<Ret, void>) + return Ret{}; + } + +private: + QMutex m_registerMutex; + QMap<QString, QJniObject> m_register; + + static void registerBackend(JNIEnv *, jclass, jclass interfaceClass, jobject interface); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(registerBackend) + static void unregisterBackend(JNIEnv *, jclass, jclass interfaceClass); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(unregisterBackend) +}; + +QT_END_NAMESPACE + +#endif // ANDROIDBACKENDREGISTER_H diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp index db6c601f33..5757f5d8cc 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.cpp +++ b/src/plugins/platforms/android/androidcontentfileengine.cpp @@ -21,7 +21,6 @@ Q_DECLARE_JNI_CLASS(UriType, "android/net/Uri"); Q_DECLARE_JNI_CLASS(Uri, "android/net/Uri"); Q_DECLARE_JNI_CLASS(ParcelFileDescriptorType, "android/os/ParcelFileDescriptor"); Q_DECLARE_JNI_CLASS(CursorType, "android/database/Cursor"); -Q_DECLARE_JNI_TYPE(StringArray, "[Ljava/lang/String;"); static QJniObject &contentResolverInstance() { @@ -375,11 +374,9 @@ public: auto cursor = contentResolverInstance().callMethod<QtJniTypes::CursorType>( "query", uri.object<QtJniTypes::UriType>(), - projection.isEmpty() ? - nullptr : fromStringList(projection).object<QtJniTypes::StringArray>(), + QJniArray(projection), selection.isEmpty() ? nullptr : QJniObject::fromString(selection).object<jstring>(), - selectionArgs.isEmpty() ? - nullptr : fromStringList(selectionArgs).object<QtJniTypes::StringArray>(), + QJniArray(selectionArgs), sortOrder.isEmpty() ? nullptr : QJniObject::fromString(sortOrder).object<jstring>()); if (!cursor.isValid()) return {}; @@ -413,15 +410,6 @@ public: bool moveToNext() { return m_object.callMethod<jboolean>("moveToNext"); } private: - static QJniObject fromStringList(const QStringList &list) - { - QJniEnvironment env; - auto array = env->NewObjectArray(list.size(), env.findClass("java/lang/String"), nullptr); - for (int i = 0; i < list.size(); ++i) - env->SetObjectArrayElement(array, i, QJniObject::fromString(list[i]).object()); - return QJniObject::fromLocalRef(array); - } - QJniObject m_object; }; diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index da5b63ef21..2010a9e03b 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -82,7 +82,7 @@ namespace QtAndroidAccessibility void initialize() { - QtAndroid::qtActivityDelegate().callMethod<void>("initializeAccessibility"); + QtAndroid::initializeAccessibility(); } bool isActive() @@ -470,6 +470,7 @@ namespace QtAndroidAccessibility QAccessible::Role role; QStringList actions; QString description; + QString identifier; bool hasTextSelection = false; int selectionStart = 0; int selectionEnd = 0; @@ -485,6 +486,7 @@ namespace QtAndroidAccessibility info.role = iface->role(); info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface); info.description = descriptionForInterface(iface); + info.identifier = QAccessibleBridgeUtils::accessibleId(iface); QAccessibleTextInterface *textIface = iface->textInterface(); if (textIface && (textIface->selectionCount() > 0)) { info.hasTextSelection = true; @@ -550,6 +552,8 @@ namespace QtAndroidAccessibility //CALL_METHOD(node, "setText", "(Ljava/lang/CharSequence;)V", jdesc) env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc); + QJniObject(node).callMethod<void>("setViewIdResourceName", info.identifier); + return true; } diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index d074e73b9e..2692488ec6 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -24,6 +24,8 @@ Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods"); using namespace QtAndroid; Q_DECLARE_JNI_CLASS(QtLayout, "org/qtproject/qt/android/QtLayout") +Q_DECLARE_JNI_CLASS(QtLayoutInterface, "org/qtproject/qt/android/QtLayoutInterface") +Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface") namespace QtAndroidInput { @@ -36,110 +38,50 @@ namespace QtAndroidInput static QPointer<QWindow> m_mouseGrabber; - GenericMotionEventListener::~GenericMotionEventListener() {} - namespace { - struct GenericMotionEventListeners { - QMutex mutex; - QList<QtAndroidInput::GenericMotionEventListener *> listeners; - }; - } - Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners) - - static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event) - { - jboolean ret = JNI_FALSE; - QMutexLocker locker(&g_genericMotionEventListeners()->mutex); - for (auto *listener : std::as_const(g_genericMotionEventListeners()->listeners)) - ret |= listener->handleGenericMotionEvent(event); - return ret; - } - - KeyEventListener::~KeyEventListener() {} - namespace { - struct KeyEventListeners { - QMutex mutex; - QList<QtAndroidInput::KeyEventListener *> listeners; - }; - } - Q_GLOBAL_STATIC(KeyEventListeners, g_keyEventListeners) - - static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event) - { - jboolean ret = JNI_FALSE; - QMutexLocker locker(&g_keyEventListeners()->mutex); - for (auto *listener : std::as_const(g_keyEventListeners()->listeners)) - ret |= listener->handleKeyEvent(event); - return ret; - } - - void registerGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener) - { - QMutexLocker locker(&g_genericMotionEventListeners()->mutex); - g_genericMotionEventListeners()->listeners.push_back(listener); - } - - void unregisterGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener) - { - QMutexLocker locker(&g_genericMotionEventListeners()->mutex); - g_genericMotionEventListeners()->listeners.removeOne(listener); - } - - void registerKeyEventListener(QtAndroidInput::KeyEventListener *listener) - { - QMutexLocker locker(&g_keyEventListeners()->mutex); - g_keyEventListeners()->listeners.push_back(listener); - } - - void unregisterKeyEventListener(QtAndroidInput::KeyEventListener *listener) - { - QMutexLocker locker(&g_keyEventListeners()->mutex); - g_keyEventListeners()->listeners.removeOne(listener); - } - QJniObject qtLayout() { - return qtActivityDelegate().callMethod<QtJniTypes::QtLayout>("getQtLayout"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + return reg->callInterface<QtJniTypes::QtLayoutInterface, QtJniTypes::QtLayout>( + "getQtLayout"); } void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd) { qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd; - qtInputDelegate().callMethod<void>("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) { - qtInputDelegate().callMethod<void>("showSoftwareKeyboard", - QtAndroidPrivate::activity(), - qtLayout().object<QtJniTypes::QtLayout>(), - left, - top, - width, - height, - inputHints, - enterKeyType); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtInputInterface, void>( + "showSoftwareKeyboard", QtAndroidPrivate::activity(), + qtLayout().object<QtJniTypes::QtLayout>(), left, top, width, height, inputHints, + enterKeyType); qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType; } void resetSoftwareKeyboard() { - qtInputDelegate().callMethod<void>("resetSoftwareKeyboard"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtInputInterface, void>("resetSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD"; } void hideSoftwareKeyboard() { - qtInputDelegate().callMethod<void>("hideSoftwareKeyboard"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtInputInterface, void>("hideSoftwareKeyboard"); qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD"; } bool isSoftwareKeyboardVisible() { - return qtInputDelegate().callMethod<jboolean>("isSoftwareKeyboardVisible"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + return reg->callInterface<QtJniTypes::QtInputInterface, jboolean>( + "isSoftwareKeyboardVisible"); } QRect softwareKeyboardRect() @@ -149,17 +91,17 @@ namespace QtAndroidInput int getSelectHandleWidth() { - return qtInputDelegate().callMethod<jint>("getSelectHandleWidth"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + return reg->callInterface<QtJniTypes::QtInputInterface, jint>("getSelectHandleWidth"); } void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl) { - qtInputDelegate().callMethod<void>("updateHandles", - QtAndroidPrivate::activity(), - qtLayout().object<QtJniTypes::QtLayout>(), - mode, editMenuPos.x(), editMenuPos.y(), editButtons, - cursor.x(), cursor.y(), - anchor.x(), anchor.y(), rtl); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtInputInterface, void>( + "updateHandles", QtAndroidPrivate::activity(), + qtLayout().object<QtJniTypes::QtLayout>(), mode, editMenuPos.x(), editMenuPos.y(), + editButtons, cursor.x(), cursor.y(), anchor.x(), anchor.y(), rtl); } // from https://developer.android.com/reference/android/view/MotionEvent#getButtonState() @@ -324,7 +266,7 @@ namespace QtAndroidInput m_touchPoints.clear(); } - static void touchAdd(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint id, jint action, jboolean /*primary*/, jint x, jint y, + 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; @@ -345,16 +287,25 @@ namespace QtAndroidInput const int dw = availableWidthPixels(); const int dh = availableHeightPixels(); + QWindow *window = QtAndroid::windowFromId(winId); + if (!window) { + qCWarning(lcQpaInputMethods, "Touch event received for non-existing window %d", winId); + return; + } + + QPointF mappedTouchPoint = window->mapToGlobal(QPointF(x, y)); QWindowSystemInterface::TouchPoint touchPoint; touchPoint.id = id; touchPoint.pressure = pressure; touchPoint.rotation = qRadiansToDegrees(rotation); - touchPoint.normalPosition = QPointF(double(x / dw), double(y / dh)); + touchPoint.normalPosition = QPointF((mappedTouchPoint.x() / dw), + (mappedTouchPoint.y() / dh)); touchPoint.state = state; - touchPoint.area = QRectF(x - double(minor * 0.5f), - y - double(major * 0.5f), + 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(); @@ -976,8 +927,6 @@ namespace QtAndroidInput {"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged}, {"keyboardGeometryChanged", "(IIII)V", (void *)keyboardGeometryChanged}, {"handleLocationChanged", "(III)V", (void *)handleLocationChanged}, - {"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)}, - {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)}, }; bool registerNatives(QJniEnvironment &env) diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index 28a2665bf6..629b2937f0 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -31,26 +31,6 @@ namespace QtAndroidInput QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); int getSelectHandleWidth(); - class GenericMotionEventListener - { - public: - virtual ~GenericMotionEventListener(); - virtual bool handleGenericMotionEvent(jobject event) = 0; - }; - - class KeyEventListener - { - public: - virtual ~KeyEventListener(); - virtual bool handleKeyEvent(jobject event) = 0; - }; - - void registerGenericMotionEventListener(GenericMotionEventListener *listener); - void unregisterGenericMotionEventListener(GenericMotionEventListener *listener); - - void registerKeyEventListener(KeyEventListener *listener); - void unregisterKeyEventListener(KeyEventListener *listener); - bool registerNatives(QJniEnvironment &env); } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 9fdcf3936b..5bd2b924fc 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -54,9 +54,6 @@ static jobject m_resourcesObj = nullptr; static jclass m_qtActivityClass = nullptr; static jclass m_qtServiceClass = nullptr; -static QtJniTypes::QtActivityDelegateBase m_activityDelegate = nullptr; -static QtJniTypes::QtInputDelegate m_inputDelegate = nullptr; - static int m_pendingApplicationState = -1; static QBasicMutex m_platformMutex; @@ -85,7 +82,7 @@ static double m_density = 1.0; static AndroidAssetsFileEngineHandler *m_androidAssetsFileEngineHandler = nullptr; static AndroidContentFileEngineHandler *m_androidContentFileEngineHandler = nullptr; - +static AndroidBackendRegister *m_backendRegister = nullptr; static const char m_qtTag[] = "Qt"; static const char m_classErrorMsg[] = "Can't find class \"%s\""; @@ -93,7 +90,8 @@ static const char m_methodErrorMsg[] = "Can't find method \"%s%s\""; Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0); -Q_DECLARE_JNI_CLASS(QtEmbeddedDelegateFactory, "org/qtproject/qt/android/QtEmbeddedDelegateFactory") +Q_DECLARE_JNI_CLASS(QtWindowInterface, "org/qtproject/qt/android/QtWindowInterface") +Q_DECLARE_JNI_CLASS(QtAccessibilityInterface, "org/qtproject/qt/android/QtAccessibilityInterface"); namespace QtAndroid { @@ -184,36 +182,9 @@ namespace QtAndroid // TODO move calls from here to where they logically belong void setSystemUiVisibility(SystemUiVisibility uiVisibility) { - qtActivityDelegate().callMethod<void>("setSystemUiVisibility", jint(uiVisibility)); - } - - // FIXME: avoid direct access to QtActivityDelegate - QtJniTypes::QtActivityDelegateBase qtActivityDelegate() - { - using namespace QtJniTypes; - if (!m_activityDelegate.isValid()) { - if (isQtApplication()) { - auto context = QtAndroidPrivate::activity(); - m_activityDelegate = context.callMethod<QtActivityDelegateBase>("getActivityDelegate"); - } else { - m_activityDelegate = QJniObject::callStaticMethod<QtActivityDelegateBase>( - Traits<QtEmbeddedDelegateFactory>::className(), - "getActivityDelegate", - QtAndroidPrivate::activity()); - } - } - - return m_activityDelegate; - } - - QtJniTypes::QtInputDelegate qtInputDelegate() - { - if (!m_inputDelegate.isValid()) { - m_inputDelegate = qtActivityDelegate().callMethod<QtJniTypes::QtInputDelegate>( - "getInputDelegate"); - } - - return m_inputDelegate; + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtWindowInterface, void>("setSystemUiVisibility", + jint(uiVisibility)); } bool isQtApplication() @@ -234,36 +205,46 @@ namespace QtAndroid return true; } + void initializeAccessibility() + { + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "initializeAccessibility"); + } + void notifyAccessibilityLocationChange(uint accessibilityObjectId) { - qtActivityDelegate().callMethod<void>("notifyLocationChange", accessibilityObjectId); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyLocationChange", accessibilityObjectId); } void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId) { - qtActivityDelegate().callMethod<void>("notifyObjectHide", - accessibilityObjectId, parentObjectId); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyObjectHide", accessibilityObjectId, parentObjectId); } void notifyObjectShow(uint parentObjectId) { - qtActivityDelegate().callMethod<void>("notifyObjectShow", - parentObjectId); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyObjectShow", parentObjectId); } void notifyObjectFocus(uint accessibilityObjectId) { - qtActivityDelegate().callMethod<void>("notifyObjectFocus", accessibilityObjectId); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyObjectFocus", accessibilityObjectId); } void notifyValueChanged(uint accessibilityObjectId, jstring value) { - qtActivityDelegate().callMethod<void>("notifyValueChanged", accessibilityObjectId, value); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyValueChanged", accessibilityObjectId, value); } void notifyScrolledEvent(uint accessibilityObjectId) { - qtActivityDelegate().callMethod<void>("notifyScrolledEvent", accessibilityObjectId); + m_backendRegister->callInterface<QtJniTypes::QtAccessibilityInterface, void>( + "notifyScrolledEvent", accessibilityObjectId); } void notifyNativePluginIntegrationReady(bool ready) @@ -387,6 +368,11 @@ namespace QtAndroid return m_assets; } + AndroidBackendRegister *backendRegister() + { + return m_backendRegister; + } + } // namespace QtAndroid static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring paramsString) @@ -397,6 +383,7 @@ static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring pa m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler(); m_androidContentFileEngineHandler = new AndroidContentFileEngineHandler(); m_mainLibraryHnd = nullptr; + m_backendRegister = new AndroidBackendRegister(); const QStringList argsList = QProcess::splitCommand(QJniObject(paramsString).toString()); @@ -544,6 +531,8 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/) m_androidPlatformIntegration = nullptr; delete m_androidAssetsFileEngineHandler; m_androidAssetsFileEngineHandler = nullptr; + delete m_backendRegister; + m_backendRegister = nullptr; sem_post(&m_exitSemaphore); } @@ -891,7 +880,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) || !QtAndroidDialogHelpers::registerNatives(env) || !QAndroidPlatformClipboard::registerNatives(env) || !QAndroidPlatformWindow::registerNatives(env) - || !QtAndroidWindowEmbedding::registerNatives(env)) { + || !QtAndroidWindowEmbedding::registerNatives(env) + || !AndroidBackendRegister::registerNatives()) { __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 99fff96d2b..9d616b18fb 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -13,6 +13,7 @@ #include <QImage> #include <private/qjnihelpers_p.h> #include <QtCore/QJniObject> +#include <androidbackendregister.h> QT_BEGIN_NAMESPACE @@ -33,6 +34,7 @@ namespace QtAndroid { QBasicMutex *platformInterfaceMutex(); QAndroidPlatformIntegration *androidPlatformIntegration(); + AndroidBackendRegister *backendRegister(); void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration); void setQtThread(QThread *thread); void setViewVisibility(jobject view, bool visible); @@ -48,9 +50,6 @@ namespace QtAndroid AAssetManager *assetManager(); jclass applicationClass(); - QtJniTypes::QtActivityDelegateBase qtActivityDelegate(); - QtJniTypes::QtInputDelegate qtInputDelegate(); - // Keep synchronized with flags in ActivityDelegate.java enum SystemUiVisibility { SYSTEM_UI_VISIBILITY_NORMAL = 0, @@ -63,6 +62,7 @@ namespace QtAndroid jobject createBitmap(int width, int height, QImage::Format format, JNIEnv *env); jobject createBitmapDrawable(jobject bitmap, JNIEnv *env = nullptr); + void initializeAccessibility(); void notifyAccessibilityLocationChange(uint accessibilityObjectId); void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId); void notifyObjectShow(uint parentObjectId); diff --git a/src/plugins/platforms/android/androidjnimenu.cpp b/src/plugins/platforms/android/androidjnimenu.cpp index 8bf37d1af2..5f8a0b92e9 100644 --- a/src/plugins/platforms/android/androidjnimenu.cpp +++ b/src/plugins/platforms/android/androidjnimenu.cpp @@ -20,6 +20,8 @@ QT_BEGIN_NAMESPACE using namespace QtAndroid; +Q_DECLARE_JNI_CLASS(QtMenuInterface, "org/qtproject/qt/android/QtMenuInterface"); + namespace QtAndroidMenu { static QList<QAndroidPlatformMenu *> pendingContextMenus; @@ -44,12 +46,14 @@ namespace QtAndroidMenu void resetMenuBar() { - qtActivityDelegate().callMethod<void>("resetOptionsMenu"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtMenuInterface, void>("resetOptionsMenu"); } void openOptionsMenu() { - qtActivityDelegate().callMethod<void>("openOptionsMenu"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtMenuInterface, void>("openOptionsMenu"); } void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect) @@ -59,16 +63,18 @@ namespace QtAndroidMenu pendingContextMenus.append(visibleMenu); visibleMenu = menu; menu->aboutToShow(); - qtActivityDelegate().callMethod<void>("openContextMenu", - anchorRect.x(), anchorRect.y(), - anchorRect.width(), anchorRect.height()); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtMenuInterface, void>("openContextMenu", anchorRect.x(), + anchorRect.y(), anchorRect.width(), + anchorRect.height()); } void hideContextMenu(QAndroidPlatformMenu *menu) { QMutexLocker lock(&visibleMenuMutex); if (visibleMenu == menu) { - qtActivityDelegate().callMethod<void>("closeContextMenu"); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtMenuInterface, void>("closeContextMenu"); pendingContextMenus.clear(); } else { pendingContextMenus.removeOne(menu); diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp index 9e20b7ac4b..4d752a3cc3 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -57,6 +57,7 @@ Q_DECLARE_JNI_CLASS(Resources, "android/content/res/Resources") Q_DECLARE_JNI_CLASS(Size, "android/util/Size") Q_DECLARE_JNI_CLASS(QtNative, "org/qtproject/qt/android/QtNative") Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager") +Q_DECLARE_JNI_CLASS(QtWindowInterface, "org/qtproject/qt/android/QtWindowInterface") Q_DECLARE_JNI_CLASS(DisplayMode, "android/view/Display$Mode") @@ -162,7 +163,10 @@ void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window) return; m_windowStack.prepend(window); - QtAndroid::qtActivityDelegate().callMethod<void>("addTopLevelWindow", window->nativeWindow()); + + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtWindowInterface, void>("addTopLevelWindow", + window->nativeWindow()); if (window->window()->isVisible()) topVisibleWindowChanged(); @@ -175,7 +179,9 @@ void QAndroidPlatformScreen::removeWindow(QAndroidPlatformWindow *window) if (m_windowStack.contains(window)) qWarning() << "Failed to remove window"; - QtAndroid::qtActivityDelegate().callMethod<void>("removeTopLevelWindow", window->nativeViewId()); + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtWindowInterface, void>("removeTopLevelWindow", + window->nativeViewId()); topVisibleWindowChanged(); } @@ -187,7 +193,10 @@ void QAndroidPlatformScreen::raise(QAndroidPlatformWindow *window) return; if (index > 0) { m_windowStack.move(index, 0); - QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToFront", window->nativeViewId()); + + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtWindowInterface, void>("bringChildToFront", + window->nativeViewId()); } topVisibleWindowChanged(); } @@ -198,7 +207,10 @@ void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window) if (index == -1 || index == (m_windowStack.size() - 1)) return; m_windowStack.move(index, m_windowStack.size() - 1); - QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToBack", window->nativeViewId()); + + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + reg->callInterface<QtJniTypes::QtWindowInterface, void>("bringChildToBack", + window->nativeViewId()); topVisibleWindowChanged(); } diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp index 979f0fb98a..2482160573 100644 --- a/src/plugins/platforms/android/qandroidplatformwindow.cpp +++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp @@ -16,6 +16,10 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window") +Q_DECLARE_JNI_CLASS(QtInputInterface, "org/qtproject/qt/android/QtInputInterface") +Q_DECLARE_JNI_CLASS(QtInputConnectionListener, + "org/qtproject/qt/android/QtInputConnection$QtInputConnectionListener") + QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) : QPlatformWindow(window), m_nativeQtWindow(nullptr), m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr), @@ -55,10 +59,13 @@ QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window) m_nativeParentQtWindow = androidParent->nativeWindow(); } + AndroidBackendRegister *reg = QtAndroid::backendRegister(); + QtJniTypes::QtInputConnectionListener listener = + reg->callInterface<QtJniTypes::QtInputInterface, QtJniTypes::QtInputConnectionListener>( + "getInputConnectionListener"); + m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>( - QNativeInterface::QAndroidApplication::context(), - m_nativeParentQtWindow, - QtAndroid::qtInputDelegate()); + QNativeInterface::QAndroidApplication::context(), m_nativeParentQtWindow, listener); m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId"); if (window->isTopLevel()) diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt index 92e681d8fb..491c61703f 100644 --- a/src/plugins/platforms/cocoa/CMakeLists.txt +++ b/src/plugins/platforms/cocoa/CMakeLists.txt @@ -7,7 +7,7 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin OUTPUT_NAME qcocoa - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa + DEFAULT_IF "cocoa" IN_LIST QT_QPA_PLATFORMS PLUGIN_TYPE platforms SOURCES main.mm diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index c5e40a4087..40c1e90511 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -36,6 +36,23 @@ void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) } switch (event->type()) { + case QAccessible::Announcement: { + auto *announcementEvent = static_cast<QAccessibleAnnouncementEvent *>(event); + auto priorityLevel = (announcementEvent->priority() == QAccessible::AnnouncementPriority::Assertive) + ? NSAccessibilityPriorityHigh + : NSAccessibilityPriorityMedium; + NSDictionary *announcementInfo = @{ + NSAccessibilityPriorityKey: [NSNumber numberWithInt:priorityLevel], + NSAccessibilityAnnouncementKey: announcementEvent->message().toNSString() + }; + // post event for application element, as the comment for + // NSAccessibilityAnnouncementRequestedNotification in the + // NSAccessibilityConstants.h header says + NSAccessibilityPostNotificationWithUserInfo(NSApp, + NSAccessibilityAnnouncementRequestedNotification, + announcementInfo); + break; + } case QAccessible::Focus: { NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification); break; diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 8d4d6d683d..b319dd072e 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -520,6 +520,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return nil; } +- (NSString*) accessibilityIdentifier { + if (QAccessibleInterface *iface = self.qtInterface) + return QAccessibleBridgeUtils::accessibleId(iface).toNSString(); + return nil; +} + - (BOOL) isAccessibilityEnabled { if (QAccessibleInterface *iface = self.qtInterface) return !iface->state().disabled; diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index aaa35a91ef..99759424f9 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -134,24 +134,42 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) case Qt::DragLinkCursor: cocoaCursor = [NSCursor dragLinkCursor]; break; -#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; + case Qt::SizeFDiagCursor: { +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000) + if (@available(macOS 15, *)) { + auto position = [newShape]{ + switch (newShape) { + case Qt::SizeVerCursor: return NSCursorFrameResizePositionTop; + case Qt::SizeHorCursor: return NSCursorFrameResizePositionLeft; + case Qt::SizeBDiagCursor: return NSCursorFrameResizePositionTopRight; + case Qt::SizeFDiagCursor: return NSCursorFrameResizePositionTopLeft; + default: Q_UNREACHABLE(); + } + }(); + cocoaCursor = [NSCursor frameResizeCursorFromPosition:position + inDirections:NSCursorFrameResizeDirectionsAll]; + break; + } +#endif // macOS 15 SDK +#if !defined(QT_APPLE_NO_PRIVATE_APIS) + auto selector = [newShape]{ + switch (newShape) { + case Qt::SizeVerCursor: return @selector(_windowResizeNorthSouthCursor); + case Qt::SizeHorCursor: return @selector(_windowResizeEastWestCursor); + case Qt::SizeBDiagCursor: return @selector(_windowResizeNorthEastSouthWestCursor); + case Qt::SizeFDiagCursor: return @selector(_windowResizeNorthWestSouthEastCursor); + default: Q_UNREACHABLE(); + } + }(); + + if ([NSCursor respondsToSelector:selector]) + cocoaCursor = [NSCursor performSelector:selector]; #endif // QT_APPLE_NO_PRIVATE_APIS + break; + } default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index a8404889e9..3a9f5a8794 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -9,7 +9,6 @@ #include "qcocoahelpers.h" #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qutimimeconverter.h> -#include <QtCore/qsysinfo.h> #include <QtCore/private/qcore_mac_p.h> #include <vector> @@ -137,11 +136,6 @@ bool QCocoaDrag::maybeDragMultipleItems() Q_ASSERT(m_drag && m_drag->mimeData()); Q_ASSERT(m_executed_drop_action == Qt::IgnoreAction); - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave) { - // -dragImage: stopped working in 10.14 first. - return false; - } - const QMacAutoReleasePool pool; NSView *view = m_lastView ? m_lastView : m_lastEvent.window.contentView; diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 044a282686..41170b74ea 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -14,8 +14,6 @@ #include <QtCore/qstringlist.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qabstracteventdispatcher.h> -#include <QtCore/qsysinfo.h> -#include <QtCore/qoperatingsystemversion.h> #include <QtCore/qdir.h> #include <QtCore/qregularexpression.h> #include <QtCore/qpointer.h> diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index be562e5455..2249658189 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -471,25 +471,6 @@ void QCocoaScreen::deliverUpdateRequests() if (!platformWindow->updatesWithDisplayLink()) continue; - // QTBUG-107198: Skip updates in a live resize for a better resize experience. - if (platformWindow->isContentView() && platformWindow->view().inLiveResize) { - const QSurface::SurfaceType surfaceType = window->surfaceType(); - const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface; - const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement] - != NSViewLayerContentsPlacementScaleAxesIndependently; - if (usesMetalLayer && usesNonDefaultContentsPlacement) { - static bool deliverDisplayLinkUpdatesDuringLiveResize = - qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE"); - if (!deliverDisplayLinkUpdatesDuringLiveResize) { - // Must keep the link running, we do not know what the event - // handlers for UpdateRequest (which is not sent now) would do, - // would they trigger a new requestUpdate() or not. - pauseUpdates = false; - continue; - } - } - } - platformWindow->deliverUpdateRequest(); // Another update request was triggered, keep the display link running diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index d9135c76c8..1623b12be6 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -5,7 +5,6 @@ #include "qcocoatheme.h" -#include <QtCore/QOperatingSystemVersion> #include <QtCore/QVariant> #include "qcocoasystemtrayicon.h" @@ -214,12 +213,10 @@ const char *QCocoaTheme::name = "cocoa"; QCocoaTheme::QCocoaTheme() : m_systemPalette(nullptr) { - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) { - m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { - NSAppearance.currentAppearance = NSApp.effectiveAppearance; - handleSystemThemeChange(); - }); - } + m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { + NSAppearance.currentAppearance = NSApp.effectiveAppearance; + handleSystemThemeChange(); + }); m_systemColorObserver = QMacNotificationObserver(nil, NSSystemColorsDidChangeNotification, [this] { @@ -467,6 +464,8 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const return NSEvent.keyRepeatDelay * 1000; case QPlatformTheme::KeyboardAutoRepeatRate: return 1.0 / NSEvent.keyRepeatInterval; + case QPlatformTheme::ShowIconsInMenus: + return false; default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index ba1bc052fb..2036d4bf4c 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -80,6 +80,8 @@ public: QRect normalGeometry() const override; void setCocoaGeometry(const QRect &rect); + QMargins safeAreaMargins() const override; + void setVisible(bool visible) override; void setWindowFlags(Qt::WindowFlags flags) override; void setWindowState(Qt::WindowStates state) override; @@ -122,6 +124,7 @@ public: Q_NOTIFICATION_HANDLER(NSWindowDidMoveNotification) void windowDidMove(); Q_NOTIFICATION_HANDLER(NSWindowDidResizeNotification) void windowDidResize(); + Q_NOTIFICATION_HANDLER(NSWindowWillStartLiveResizeNotification) void windowWillStartLiveResize(); Q_NOTIFICATION_HANDLER(NSWindowDidEndLiveResizeNotification) void windowDidEndLiveResize(); Q_NOTIFICATION_HANDLER(NSWindowDidBecomeKeyNotification) void windowDidBecomeKey(); Q_NOTIFICATION_HANDLER(NSWindowDidResignKeyNotification) void windowDidResignKey(); @@ -186,6 +189,8 @@ public: Q_DECLARE_FLAGS(RecreationReasons, RecreationReason) Q_FLAG(RecreationReasons) + bool inLiveResize() const override; + protected: void recreateWindowIfNeeded(); QCocoaNSWindow *createNSWindow(bool shouldBePanel); @@ -232,6 +237,7 @@ public: // for QNSView bool m_inSetVisible = false; bool m_inSetGeometry = false; bool m_inSetStyleMask = false; + bool m_inLiveResize = false; QCocoaMenuBar *m_menubar = nullptr; @@ -241,6 +247,8 @@ public: // for QNSView int m_registerTouchCount = 0; bool m_resizableTransientParent = false; + QMargins m_lastReportedSafeAreaMargins; + static const int NoAlertRequest; NSInteger m_alertRequest = NoAlertRequest; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 4a245a0f8a..b4c8ae4ba1 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -24,6 +24,7 @@ #include <qpa/qplatformscreen.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/private/qhighdpiscaling_p.h> +#include <QtGui/private/qmetallayer_p.h> #include <QDebug> @@ -285,6 +286,54 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm) } +QMargins QCocoaWindow::safeAreaMargins() const +{ + // The safe area of the view reflects the area not covered by navigation + // bars, tab bars, toolbars, and other ancestor views that might obscure + // the current view (by setting additionalSafeAreaInsets). If the window + // uses NSWindowStyleMaskFullSizeContentView this also includes the area + // of the view covered by the title bar. + QMarginsF viewSafeAreaMargins = { + m_view.safeAreaInsets.left, + m_view.safeAreaInsets.top, + m_view.safeAreaInsets.right, + m_view.safeAreaInsets.bottom + }; + + // The screen's safe area insets represent the distances from the screen's + // edges at which content isn't obscured. The view's safe area margins do + // not include the screen's insets automatically, so we need to manually + // merge them. + auto screenRect = m_view.window.screen.frame; + auto screenInsets = m_view.window.screen.safeAreaInsets; + auto screenRelativeViewBounds = QCocoaScreen::mapFromNative( + [m_view.window convertRectToScreen: + [m_view convertRect:m_view.bounds toView:nil]] + ); + + // The margins are relative to the screen the window is on. + // Note that we do not want represent the area outside of the + // screen as being outside of the safe area. + QMarginsF screenSafeAreaMargins = { + screenInsets.left ? + qMax(0.0f, screenInsets.left - screenRelativeViewBounds.left()) + : 0.0f, + screenInsets.top ? + qMax(0.0f, screenInsets.top - screenRelativeViewBounds.top()) + : 0.0f, + screenInsets.right ? + qMax(0.0f, screenInsets.right + - (screenRect.size.width - screenRelativeViewBounds.right())) + : 0.0f, + screenInsets.bottom ? + qMax(0.0f, screenInsets.bottom + - (screenRect.size.height - screenRelativeViewBounds.bottom())) + : 0.0f + }; + + return (screenSafeAreaMargins | viewSafeAreaMargins).toMargins(); +} + bool QCocoaWindow::startSystemMove() { switch (NSApp.currentEvent.type) { @@ -514,7 +563,7 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) auto *nsWindow = transientCocoaWindow->nativeWindow(); // We only upgrade the window level for "special" windows, to work - // around Qt Designer parenting the designer windows to the widget + // around Qt Widgets Designer parenting the designer windows to the widget // palette window (QTBUG-31779). This should be fixed in designer. if (type != Qt::Window) windowLevel = qMax(windowLevel, nsWindow.level); @@ -1236,8 +1285,26 @@ void QCocoaWindow::windowDidResize() handleWindowStateChanged(); } +void QCocoaWindow::windowWillStartLiveResize() +{ + // Track live resizing for all windows, including + // child windows, so we know if it's safe to update + // the window unthrottled outside of the main thread. + m_inLiveResize = true; +} + +bool QCocoaWindow::inLiveResize() const +{ + // Use member variable to track this instead of reflecting + // NSView.inLiveResize directly, so it can be called from + // non-main threads. + return m_inLiveResize; +} + void QCocoaWindow::windowDidEndLiveResize() { + m_inLiveResize = false; + if (!isContentView()) return; @@ -1435,6 +1502,12 @@ void QCocoaWindow::handleGeometryChange() QWindowSystemInterface::handleGeometryChange(window(), newGeometry); + // Changing the window geometry may affect the safe area margins + if (safeAreaMargins() != m_lastReportedSafeAreaMargins) { + m_lastReportedSafeAreaMargins = safeAreaMargins(); + QWindowSystemInterface::handleSafeAreaMarginsChanged(window()); + } + // Guard against processing window system events during QWindow::setGeometry // calls, which Qt and Qt applications do not expect. if (!m_inSetGeometry) @@ -1617,6 +1690,23 @@ bool QCocoaWindow::updatesWithDisplayLink() const void QCocoaWindow::deliverUpdateRequest() { qCDebug(lcQpaDrawing) << "Delivering update request to" << window(); + + if (auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(m_view.layer)) { + // We attempt a read lock here, so that the animation/render thread is + // prioritized lower than the main thread's displayLayer processing. + // Without this the two threads might fight over the next drawable, + // starving the main thread's presentation of the resized layer. + if (!qtMetalLayer.displayLock.tryLockForRead()) { + qCDebug(lcQpaDrawing) << "Deferring update request" + << "due to" << qtMetalLayer << "needing display"; + return; + } + + // But we don't hold the lock, as the update request can recurse + // back into setNeedsDisplay, which would deadlock. + qtMetalLayer.displayLock.unlock(); + } + QPlatformWindow::deliverUpdateRequest(); } @@ -1663,7 +1753,7 @@ void QCocoaWindow::setupPopupMonitor() | NSEventMaskMouseMoved; s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask handler:^(NSEvent *e){ - if (!QGuiApplicationPrivate::instance()->popupActive()) { + if (!QGuiApplicationPrivate::instance()->activePopupWindow()) { removePopupMonitor(); return; } diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 48ffa5c1cc..eb998b0409 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -21,7 +21,6 @@ #include <QtCore/QDebug> #include <QtCore/QPointer> #include <QtCore/QSet> -#include <QtCore/qsysinfo.h> #include <QtCore/private/qcore_mac_p.h> #include <QtGui/QAccessible> #include <QtGui/QImage> @@ -36,6 +35,9 @@ #endif #include "qcocoaintegration.h" #include <QtGui/private/qmacmimeregistry_p.h> +#include <QtGui/private/qmetallayer_p.h> + +#include <QuartzCore/CATransaction.h> @interface QNSView (Drawing) <CALayerDelegate> - (void)initDrawing; diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index bf102e43f8..61691ab4fb 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -75,7 +75,10 @@ // too late at this point and the QWindow will be non-functional, // but we can at least print a warning. if ([MTLCreateSystemDefaultDevice() autorelease]) { - return [CAMetalLayer layer]; + static bool allowPresentsWithTransaction = + !qEnvironmentVariableIsSet("QT_MTL_NO_TRANSACTION"); + return allowPresentsWithTransaction ? + [QMetalLayer layer] : [CAMetalLayer layer]; } else { qCWarning(lcQpaDrawing) << "Failed to create QWindow::MetalSurface." << "Metal is not supported by any of the GPUs in this system."; @@ -222,8 +225,39 @@ return; } - qCDebug(lcQpaDrawing) << "[QNSView displayLayer]" << m_platformWindow->window(); - m_platformWindow->handleExposeEvent(QRectF::fromCGRect(self.bounds).toRect()); + const auto handleExposeEvent = [&]{ + const auto bounds = QRectF::fromCGRect(self.bounds).toRect(); + qCDebug(lcQpaDrawing) << "[QNSView displayLayer]" << m_platformWindow->window() << bounds; + m_platformWindow->handleExposeEvent(bounds); + }; + + if (auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(self.layer)) { + const bool presentedWithTransaction = qtMetalLayer.presentsWithTransaction; + qtMetalLayer.presentsWithTransaction = YES; + + handleExposeEvent(); + + // If the expose event resulted in a secondary thread requesting that its + // drawable should be presented on the main thread with transaction, do so. + if (auto mainThreadPresentation = qtMetalLayer.mainThreadPresentation) { + mainThreadPresentation(); + qtMetalLayer.mainThreadPresentation = nil; + } + + qtMetalLayer.presentsWithTransaction = presentedWithTransaction; + + // We're done presenting, but we must wait to unlock the display lock + // until the display cycle finishes, as otherwise the render thread may + // step in and present before the transaction commits. The display lock + // is recursive, so setNeedsDisplay can be safely called in the meantime + // without any issue. + QMetaObject::invokeMethod(m_platformWindow, [qtMetalLayer]{ + qCDebug(lcMetalLayer) << "Unlocking" << qtMetalLayer << "after finishing display-cycle"; + qtMetalLayer.displayLock.unlock(); + }, Qt::QueuedConnection); + } else { + handleExposeEvent(); + } } @end diff --git a/src/plugins/platforms/cocoa/qnsview_gestures.mm b/src/plugins/platforms/cocoa/qnsview_gestures.mm index 7c64e3356f..9c5ead072b 100644 --- a/src/plugins/platforms/cocoa/qnsview_gestures.mm +++ b/src/plugins/platforms/cocoa/qnsview_gestures.mm @@ -11,9 +11,6 @@ Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") - (bool)handleGestureAsBeginEnd:(NSEvent *)event { - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) - return false; - if ([event phase] == NSEventPhaseBegan) { [self beginGestureWithEvent:event]; return true; diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index 74ba6f65ac..f536045fec 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -12,7 +12,6 @@ #include "qcocoaintegration.h" #include <qpa/qwindowsysteminterface.h> -#include <qoperatingsystemversion.h> Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events"); @@ -351,7 +350,7 @@ NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window) // not Qt). However, an active popup is expected to grab any mouse event within the // application, so we need to handle those explicitly and trust Qt's isWindowBlocked // implementation to eat events that shouldn't be delivered anyway. - if (isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->popupActive() + if (isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->activePopupWindow() && QGuiApplicationPrivate::instance()->isWindowBlocked(m_platformWindow->window(), nullptr)) { qCDebug(lcQpaWindow) << "Mouse event over modally blocked window" << m_platformWindow->window() << "while popup is open - redirecting"; diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt index 54e96b09e5..062dc30143 100644 --- a/src/plugins/platforms/direct2d/CMakeLists.txt +++ b/src/plugins/platforms/direct2d/CMakeLists.txt @@ -23,7 +23,6 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin ../windows/qwindowskeymapper.cpp ../windows/qwindowskeymapper.h ../windows/qwindowsmenu.cpp ../windows/qwindowsmenu.h ../windows/qwindowsmimeregistry.cpp ../windows/qwindowsmimeregistry.h - ../windows/qwindowsmousehandler.cpp ../windows/qwindowsmousehandler.h ../windows/qwindowsnativeinterface.cpp ../windows/qwindowsnativeinterface.h ../windows/qwindowsole.cpp ../windows/qwindowsole.h ../windows/qwindowsopengltester.cpp ../windows/qwindowsopengltester.h diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.cpp index 2866c68de2..71a59cb08f 100644 --- a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.cpp +++ b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintdevice.cpp @@ -84,10 +84,12 @@ QT_WARNING_POP case QPaintDevice::PdmDevicePixelRatio: return 1; case QPaintDevice::PdmDevicePixelRatioScaled: - return qRound(devicePixelRatioFScale()); + return int(devicePixelRatioFScale()); case QPaintDevice::PdmWidthMM: case QPaintDevice::PdmHeightMM: break; + default: + break; } return -1; diff --git a/src/plugins/platforms/eglfs/CMakeLists.txt b/src/plugins/platforms/eglfs/CMakeLists.txt index a0a6116a45..cb4b5d1eb9 100644 --- a/src/plugins/platforms/eglfs/CMakeLists.txt +++ b/src/plugins/platforms/eglfs/CMakeLists.txt @@ -92,7 +92,7 @@ endif() qt_internal_add_plugin(QEglFSIntegrationPlugin OUTPUT_NAME qeglfs PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES eglfs + DEFAULT_IF "eglfs" IN_LIST QT_QPA_PLATFORMS SOURCES qeglfsmain.cpp DEFINES diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp index d171715e72..87990ad592 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmcursor.cpp @@ -7,6 +7,8 @@ #include "qeglfskmsgbmscreen_p.h" #include "qeglfskmsgbmdevice_p.h" +#include <private/qeglfskmsintegration_p.h> + #include <QtCore/QFile> #include <QtCore/QJsonDocument> #include <QtCore/QJsonObject> @@ -30,8 +32,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - QEglFSKmsGbmCursor::QEglFSKmsGbmCursor(QEglFSKmsGbmScreen *screen) : m_screen(screen) , m_cursorSize(64, 64) // 64x64 is the old standard size, we now try to query the real size below diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp index 89479fc250..a7592ed55e 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp @@ -7,6 +7,7 @@ #include "qeglfskmsgbmscreen_p.h" #include <private/qeglfsintegration_p.h> +#include <private/qeglfskmsintegration_p.h> #include <QtCore/QLoggingCategory> #include <QtCore/private/qcore_unix_p.h> @@ -15,8 +16,6 @@ QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - QEglFSKmsGbmDevice::QEglFSKmsGbmDevice(QKmsScreenConfig *screenConfig, const QString &path) : QEglFSKmsDevice(screenConfig, path) , m_gbm_device(nullptr) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index 8dcfed04db..e2a806f491 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -6,7 +6,9 @@ #include "qeglfskmsgbmscreen_p.h" #include "qeglfskmsgbmdevice_p.h" #include "qeglfskmsgbmcursor_p.h" + #include <private/qeglfsintegration_p.h> +#include <private/qeglfskmsintegration_p.h> #include <QtCore/QLoggingCategory> @@ -18,8 +20,6 @@ QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - QMutex QEglFSKmsGbmScreen::m_nonThreadedFlipMutex; static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp index 3fc46cd224..5af45e63a2 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp @@ -3,14 +3,15 @@ #include "qeglfskmsegldevicescreen.h" #include "qeglfskmsegldevice.h" + +#include <private/qeglfskmsintegration_p.h> + #include <QGuiApplication> #include <QLoggingCategory> #include <errno.h> QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - QEglFSKmsEglDeviceScreen::QEglFSKmsEglDeviceScreen(QEglFSKmsDevice *device, const QKmsOutput &output) : QEglFSKmsScreen(device, output) , m_default_fb_handle(uint32_t(-1)) diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp index 96cdcd8947..c0c9655496 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp @@ -4,14 +4,14 @@ #include "qeglfskmseventreader_p.h" #include "qeglfskmsdevice_p.h" #include "qeglfskmsscreen_p.h" +#include "qeglfskmsintegration_p.h" + #include <QSocketNotifier> #include <QCoreApplication> #include <QLoggingCategory> QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - static void pageFlipHandler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) { Q_UNUSED(fd); diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration_p.h index 36e65a0bd4..26da231092 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration_p.h +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsintegration_p.h @@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE class QKmsDevice; class QKmsScreenConfig; -Q_DECLARE_EXPORTED_LOGGING_CATEGORY(qLcEglfsKmsDebug, Q_EGLFS_EXPORT) +QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(qLcEglfsKmsDebug, Q_EGLFS_EXPORT) class Q_EGLFS_EXPORT QEglFSKmsIntegration : public QEglFSDeviceIntegration { diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp index 369356b761..cc7381fb70 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp @@ -5,7 +5,9 @@ #include "qeglfskmsscreen_p.h" #include "qeglfskmsdevice_p.h" + #include <private/qeglfsintegration_p.h> +#include <private/qeglfskmsintegration_p.h> #include <QtCore/QLoggingCategory> @@ -14,8 +16,6 @@ QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug) - class QEglFSKmsInterruptHandler : public QObject { public: diff --git a/src/plugins/platforms/ios/CMakeLists.txt b/src/plugins/platforms/ios/CMakeLists.txt index 4cc3efc91e..51c1b52cf3 100644 --- a/src/plugins/platforms/ios/CMakeLists.txt +++ b/src/plugins/platforms/ios/CMakeLists.txt @@ -5,10 +5,23 @@ ## QIOSIntegrationPlugin Plugin: ##################################################################### +if(VISIONOS) + include(SwiftIntegration.cmake) + + qt_install(TARGETS QIOSIntegrationPluginSwift + EXPORT "${INSTALL_CMAKE_NAMESPACE}QIOSIntegrationPluginTargets" + DESTINATION "${INSTALL_LIBDIR}" + ) + qt_internal_add_targets_to_additional_targets_export_file( + TARGETS QIOSIntegrationPluginSwift + EXPORT_NAME_PREFIX "${INSTALL_CMAKE_NAMESPACE}QIOSIntegrationPlugin" + ) +endif() + qt_internal_add_plugin(QIOSIntegrationPlugin OUTPUT_NAME qios STATIC # Force static, even in shared builds - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios + DEFAULT_IF "ios" IN_LIST QT_QPA_PLATFORMS PLUGIN_TYPE platforms SOURCES plugin.mm @@ -86,3 +99,7 @@ qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT (TVOS OR VISIONOS) ) add_subdirectory(optional) + +if(VISIONOS) + target_link_libraries(QIOSIntegrationPlugin PRIVATE QIOSIntegrationPluginSwift) +endif() diff --git a/src/plugins/platforms/ios/SwiftIntegration.cmake b/src/plugins/platforms/ios/SwiftIntegration.cmake new file mode 100644 index 0000000000..d52edb3ad2 --- /dev/null +++ b/src/plugins/platforms/ios/SwiftIntegration.cmake @@ -0,0 +1,78 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +set(CMAKE_Swift_COMPILER_TARGET arm64-apple-xros) +if($CACHE{CMAKE_OSX_SYSROOT} MATCHES "^[a-z]+simulator$") + set(CMAKE_Swift_COMPILER_TARGET "${CMAKE_Swift_COMPILER_TARGET}-simulator") +endif() + +cmake_policy(SET CMP0157 NEW) +enable_language(Swift) + +# Verify that we have a new enough compiler +if("${CMAKE_Swift_COMPILER_VERSION}" VERSION_LESS 5.9) + message(FATAL_ERROR "Swift 5.9 required for C++ interoperability") +endif() + +get_target_property(QT_CORE_INCLUDES Qt6::Core INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(QT_GUI_INCLUDES Qt6::Gui INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(QT_CORE_PRIVATE_INCLUDES Qt6::CorePrivate INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(QT_GUI_PRIVATE_INCLUDES Qt6::GuiPrivate INTERFACE_INCLUDE_DIRECTORIES) + +set(target QIOSIntegrationPluginSwift) +# Swift library +set(SWIFT_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/qiosapplication.swift" +) +add_library(${target} STATIC ${SWIFT_SOURCES}) +set_target_properties(${target} PROPERTIES + Swift_MODULE_NAME ${target}) +target_include_directories(${target} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + "${QT_CORE_INCLUDES}" + "${QT_GUI_INCLUDES}" + "${QT_CORE_PRIVATE_INCLUDES}" + "${QT_GUI_PRIVATE_INCLUDES}" + +) +target_compile_options(${target} PUBLIC + $<$<COMPILE_LANGUAGE:Swift>:-cxx-interoperability-mode=default> + $<$<COMPILE_LANGUAGE:Swift>:-Xcc -std=c++17>) + +# Swift to C++ bridging header +set(SWIFT_BRIDGING_HEADER "${CMAKE_CURRENT_BINARY_DIR}/qiosswiftintegration.h") +list(TRANSFORM QT_CORE_INCLUDES PREPEND "-I") +list(TRANSFORM QT_GUI_INCLUDES PREPEND "-I") +list(TRANSFORM QT_CORE_PRIVATE_INCLUDES PREPEND "-I") +list(TRANSFORM QT_GUI_PRIVATE_INCLUDES PREPEND "-I") +add_custom_command( + COMMAND + ${CMAKE_Swift_COMPILER} -frontend -typecheck + ${SWIFT_SOURCES} + -I ${CMAKE_CURRENT_SOURCE_DIR} + ${QT_CORE_INCLUDES} + ${QT_GUI_INCLUDES} + ${QT_CORE_PRIVATE_INCLUDES} + ${QT_GUI_PRIVATE_INCLUDES} + -sdk ${CMAKE_OSX_SYSROOT} + -module-name ${target} + -cxx-interoperability-mode=default + -Xcc -std=c++17 + -emit-clang-header-path "${SWIFT_BRIDGING_HEADER}" + -target ${CMAKE_Swift_COMPILER_TARGET} + OUTPUT + "${SWIFT_BRIDGING_HEADER}" + DEPENDS + ${SWIFT_SOURCES} + ) + +set(header_target "${target}Header") +add_custom_target(${header_target} + DEPENDS "${SWIFT_BRIDGING_HEADER}" +) +# Make sure the "'__bridge_transfer' casts have no effect when not using ARC" +# warning doesn't break warnings-are-error builds. +target_compile_options(${target} INTERFACE + -Wno-error=arc-bridge-casts-disallowed-in-nonarc) + +add_dependencies(${target} ${header_target}) diff --git a/src/plugins/platforms/ios/module.modulemap b/src/plugins/platforms/ios/module.modulemap new file mode 100644 index 0000000000..af42b3e1f5 --- /dev/null +++ b/src/plugins/platforms/ios/module.modulemap @@ -0,0 +1,4 @@ +module QIOSIntegrationPlugin { + header "qiosapplicationdelegate.h" + header "qiosintegration.h" +} diff --git a/src/plugins/platforms/ios/qiosapplication.swift b/src/plugins/platforms/ios/qiosapplication.swift new file mode 100644 index 0000000000..9eb172a896 --- /dev/null +++ b/src/plugins/platforms/ios/qiosapplication.swift @@ -0,0 +1,198 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import SwiftUI +import CompositorServices +import QIOSIntegrationPlugin +import RealityKit + +struct QIOSSwiftApplication: App { + @UIApplicationDelegateAdaptor private var appDelegate: QIOSApplicationDelegate + + var body: some SwiftUI.Scene { + WindowGroup() { + ImmersiveSpaceControlView() + } + + ImmersiveSpace(id: "QIOSImmersiveSpace") { + CompositorLayer(configuration: QIOSLayerConfiguration()) { layerRenderer in + QIOSIntegration.instance().renderCompositorLayer(layerRenderer) + + // Handle any events in the scene. + layerRenderer.onSpatialEvent = { eventCollection in + QIOSIntegration.instance().handleSpatialEvents(jsonStringFromEventCollection(eventCollection)) + } + } + } + // CompositorLayer immersive spaces are always full, and should not need + // to set the immersion style, but lacking this we get a warning in the + // console about not being able to "configure an immersive space with + // selected style 'AutomaticImmersionStyle' since it is not in the list + // of supported styles for this type of content: 'FullImmersionStyle'." + .immersionStyle(selection: .constant(.full), in: .full) + } +} + +public struct QIOSLayerConfiguration: CompositorLayerConfiguration { + public func makeConfiguration(capabilities: LayerRenderer.Capabilities, + configuration: inout LayerRenderer.Configuration) { + // Use reflection to pull out underlying C handles + // FIXME: Use proper bridging APIs when available + let capabilitiesMirror = Mirror(reflecting: capabilities) + let configurationMirror = Mirror(reflecting: configuration) + QIOSIntegration.instance().configureCompositorLayer( + capabilitiesMirror.descendant("c_capabilities") as? cp_layer_renderer_capabilities_t, + configurationMirror.descendant("box", "value") as? cp_layer_renderer_configuration_t + ) + } +} + +public func runSwiftAppMain() { + QIOSSwiftApplication.main() +} + +public class ImmersiveState: ObservableObject { + static let shared = ImmersiveState() + @Published var showImmersiveSpace: Bool = false +} + +struct ImmersiveSpaceControlView: View { + @ObservedObject private var immersiveState = ImmersiveState.shared + + @Environment(\.openImmersiveSpace) var openImmersiveSpace + @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace + + var body: some View { + VStack {} + .onChange(of: immersiveState.showImmersiveSpace) { _, newValue in + Task { + if newValue { + await openImmersiveSpace(id: "QIOSImmersiveSpace") + } else { + await dismissImmersiveSpace() + } + } + } + } +} + +public class ImmersiveSpaceManager : NSObject { + @objc public static func openImmersiveSpace() { + ImmersiveState.shared.showImmersiveSpace = true + } + + @objc public static func dismissImmersiveSpace() { + ImmersiveState.shared.showImmersiveSpace = false + } +} + +extension SpatialEventCollection.Event.Kind: Encodable { + enum CodingKeys: String, CodingKey { + case touch + case directPinch + case indirectPinch + case pointer + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .touch: + try container.encode("touch") + case .directPinch: + try container.encode("directPinch") + case .indirectPinch: + try container.encode("indirectPinch") + case .pointer: + try container.encode("pointer") + @unknown default: + try container.encode("unknown") + } + } +} +extension SpatialEventCollection.Event.Phase: Encodable { + enum CodingKeys: String, CodingKey { + case active + case ending + case cancled + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .active: + try container.encode("active") + case .ended: + try container.encode("ended") + case .cancelled: + try container.encode("canceled") + @unknown default: + try container.encode("unknown") + } + } +} +extension SpatialEventCollection.Event.InputDevicePose: Encodable { + enum CodingKeys: String, CodingKey { + case altitude + case azimuth + case pose3D + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(altitude.radians, forKey: .altitude) + try container.encode(azimuth.radians, forKey: .azimuth) + try container.encode(pose3D, forKey: .pose3D) + } +} + +extension SpatialEventCollection.Event: Encodable { + enum CodingKeys: String, CodingKey { + case id + case timestamp + case kind + case location + case phase + case modifierKeys + case inputDevicePose + case location3D + case selectionRay + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id.hashValue, forKey: .id) + try container.encode(timestamp, forKey: .timestamp) + try container.encode(kind, forKey: .kind) + try container.encode(location, forKey: .location) + try container.encode(phase, forKey: .phase) + try container.encode(modifierKeys.rawValue, forKey: .modifierKeys) + try container.encode(inputDevicePose, forKey: .inputDevicePose) + try container.encode(location3D, forKey: .location3D) + try container.encode(selectionRay, forKey: .selectionRay) + } +} + +extension SpatialEventCollection: Encodable { + enum CodingKeys: String, CodingKey { + case events + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(Array(self), forKey: .events) + } +} + +func jsonStringFromEventCollection(_ eventCollection: SpatialEventCollection) -> String { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + + do { + let jsonData = try encoder.encode(eventCollection) + return String(data: jsonData, encoding: .utf8) ?? "{}" + } catch { + print("Failed to encode event collection: \(error)") + return "{}" + } +} diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.h b/src/plugins/platforms/ios/qiosapplicationdelegate.h index 39bb9fdedb..7e12d64cbf 100644 --- a/src/plugins/platforms/ios/qiosapplicationdelegate.h +++ b/src/plugins/platforms/ios/qiosapplicationdelegate.h @@ -1,6 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef QIOSAPPLICATIONDELEGATE_H +#define QIOSAPPLICATIONDELEGATE_H + #import <UIKit/UIKit.h> #import <QtGui/QtGui> @@ -8,3 +11,5 @@ @interface QIOSApplicationDelegate : UIResponder <UIApplicationDelegate> @end + +#endif // QIOSAPPLICATIONDELEGATE_H diff --git a/src/plugins/platforms/ios/qioseventdispatcher.h b/src/plugins/platforms/ios/qioseventdispatcher.h index b40024ec19..5eee0556f5 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.h +++ b/src/plugins/platforms/ios/qioseventdispatcher.h @@ -16,6 +16,8 @@ public: static QIOSEventDispatcher* create(); bool processPostedEvents() override; + static bool isQtApplication(); + protected: explicit QIOSEventDispatcher(QObject *parent = nullptr); }; diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm index 24d9d88294..710a834bfd 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.mm +++ b/src/plugins/platforms/ios/qioseventdispatcher.mm @@ -5,6 +5,10 @@ #include "qiosapplicationdelegate.h" #include "qiosglobal.h" +#if defined(Q_OS_VISIONOS) +#include "qiosswiftintegration.h" +#endif + #include <QtCore/qprocessordetection.h> #include <QtCore/private/qcoreapplication_p.h> #include <QtCore/private/qthread_p.h> @@ -173,12 +177,16 @@ namespace QAppleLogActivity UIApplicationMain; QAppleLogActivity applicationDidFinishLaunching; } logActivity; + + static bool s_isQtApplication = false; } using namespace QT_PREPEND_NAMESPACE(QtPrivate); extern "C" int qt_main_wrapper(int argc, char *argv[]) { + s_isQtApplication = true; + @autoreleasepool { size_t defaultStackSize = 512 * kBytesPerKiloByte; // Same as secondary threads @@ -202,8 +210,16 @@ extern "C" int qt_main_wrapper(int argc, char *argv[]) logActivity.UIApplicationMain = QT_APPLE_LOG_ACTIVITY( lcEventDispatcher().isDebugEnabled(), "UIApplicationMain").enter(); +#if defined(Q_OS_VISIONOS) + Q_UNUSED(argc); + Q_UNUSED(argv); + qCDebug(lcEventDispatcher) << "Starting Swift app"; + QIOSIntegrationPluginSwift::runSwiftAppMain(); + Q_UNREACHABLE(); +#else qCDebug(lcEventDispatcher) << "Running UIApplicationMain"; return UIApplicationMain(argc, argv, nil, NSStringFromClass([QIOSApplicationDelegate class])); +#endif } } @@ -424,6 +440,11 @@ QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent) QWindowSystemInterface::setSynchronousWindowSystemEvents(true); } +bool QIOSEventDispatcher::isQtApplication() +{ + return s_isQtApplication; +} + /*! Override of the CoreFoundation posted events runloop source callback so that we can send window system (QPA) events in addition to sending diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm index 25ccf2961b..1722e09aaa 100644 --- a/src/plugins/platforms/ios/qiosglobal.mm +++ b/src/plugins/platforms/ios/qiosglobal.mm @@ -5,6 +5,8 @@ #include "qiosapplicationdelegate.h" #include "qiosviewcontroller.h" #include "qiosscreen.h" +#include "quiwindow.h" +#include "qioseventdispatcher.h" #include <QtCore/private/qcore_mac_p.h> @@ -17,17 +19,13 @@ Q_LOGGING_CATEGORY(lcQpaWindowScene, "qt.qpa.window.scene"); bool isQtApplication() { - if (qt_apple_isApplicationExtension()) - return false; - // Returns \c true if the plugin is in full control of the whole application. This means // that we control the application delegate and the top view controller, and can take // actions that impacts all parts of the application. The opposite means that we are // embedded inside a native iOS application, and should be more focused on playing along // with native UIControls, and less inclined to change structures that lies outside the // scope of our QWindows/UIViews. - static bool isQt = ([qt_apple_sharedApplication().delegate isKindOfClass:[QIOSApplicationDelegate class]]); - return isQt; + return QIOSEventDispatcher::isQtApplication(); } bool isRunningOnVisionOS() @@ -126,9 +124,15 @@ UIView *rootViewForScreen(QScreen *screen) Q_UNUSED(iosScreen); #endif - UIWindow *uiWindow = windowScene.keyWindow; - if (!uiWindow && windowScene.windows.count) - uiWindow = windowScene.windows[0]; + UIWindow *uiWindow = qt_objc_cast<QUIWindow*>(windowScene.keyWindow); + if (!uiWindow) { + for (UIWindow *win in windowScene.windows) { + if (qt_objc_cast<QUIWindow*>(win)) { + uiWindow = win; + break; + } + } + } return uiWindow.rootViewController.view; } diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h index 2c7d33cc94..6c2014d048 100644 --- a/src/plugins/platforms/ios/qiosintegration.h +++ b/src/plugins/platforms/ios/qiosintegration.h @@ -16,11 +16,24 @@ #include "qiostextinputoverlay.h" #endif +#if defined(Q_OS_VISIONOS) +#include <swift/bridging> +#endif + QT_BEGIN_NAMESPACE +using namespace QNativeInterface; + class QIOSServices; -class QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration +class +#if defined(Q_OS_VISIONOS) + SWIFT_IMMORTAL_REFERENCE +#endif +QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration +#if defined(Q_OS_VISIONOS) + , public QVisionOSApplication +#endif { Q_OBJECT public: @@ -77,6 +90,18 @@ public: QIOSApplicationState applicationState; +#if defined(Q_OS_VISIONOS) + void openImmersiveSpace() override; + void dismissImmersiveSpace() override; + + using CompositorLayer = QVisionOSApplication::ImmersiveSpaceCompositorLayer; + void setImmersiveSpaceCompositorLayer(CompositorLayer *layer) override; + + void configureCompositorLayer(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t); + void renderCompositorLayer(cp_layer_renderer_t); + void handleSpatialEvents(const char *jsonString); +#endif + private: QPlatformFontDatabase *m_fontDatabase; #if QT_CONFIG(clipboard) @@ -90,6 +115,10 @@ private: #if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS) QIOSTextInputOverlay m_textInputOverlay; #endif + +#if defined(Q_OS_VISIONOS) + CompositorLayer *m_immersiveSpaceCompositorLayer = nullptr; +#endif }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm index 7cd21f83f6..76173ce830 100644 --- a/src/plugins/platforms/ios/qiosintegration.mm +++ b/src/plugins/platforms/ios/qiosintegration.mm @@ -17,6 +17,10 @@ #include "qiosservices.h" #include "qiosoptionalplugininterface.h" +#if defined(Q_OS_VISIONOS) +#include "qiosswiftintegration.h" +#endif + #include <QtGui/qpointingdevice.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qrhibackingstore_p.h> @@ -296,6 +300,52 @@ void QIOSIntegration::setApplicationBadge(qint64 number) // --------------------------------------------------------- +#if defined(Q_OS_VISIONOS) +void QIOSIntegration::openImmersiveSpace() +{ + [ImmersiveSpaceManager openImmersiveSpace]; +} + +void QIOSIntegration::dismissImmersiveSpace() +{ + [ImmersiveSpaceManager dismissImmersiveSpace]; +} + +void QIOSIntegration::setImmersiveSpaceCompositorLayer(CompositorLayer *layer) +{ + m_immersiveSpaceCompositorLayer = layer; +} + +void QIOSIntegration::configureCompositorLayer(cp_layer_renderer_capabilities_t capabilities, + cp_layer_renderer_configuration_t configuration) +{ + if (m_immersiveSpaceCompositorLayer) + m_immersiveSpaceCompositorLayer->configure(capabilities, configuration); +} + +void QIOSIntegration::renderCompositorLayer(cp_layer_renderer_t renderer) +{ + if (m_immersiveSpaceCompositorLayer) + m_immersiveSpaceCompositorLayer->render(renderer); +} + +void QIOSIntegration::handleSpatialEvents(const char *jsonString) +{ + if (m_immersiveSpaceCompositorLayer) { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(QByteArray(jsonString), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "Error parsing JSON: " << error.errorString(); + return; + } + m_immersiveSpaceCompositorLayer->handleSpatialEvents(doc.object()); + } +} + +#endif + +// --------------------------------------------------------- + void *QIOSIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) { if (!window || !window->handle()) diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index 7559979f33..8f3081e276 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -322,7 +322,10 @@ QDpi QIOSScreen::logicalBaseDpi() const qreal QIOSScreen::devicePixelRatio() const { #if defined(Q_OS_VISIONOS) - return 2.0; // Based on what iPad app reports + // Based on what iPad app reports, and what Apple + // documents to be the default scale factor on + // visionOS, and the minimum scale for assets. + return 2.0; #else return [m_uiScreen scale]; #endif diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h index 88afee80c3..86bcc111d3 100644 --- a/src/plugins/platforms/ios/qioswindow.h +++ b/src/plugins/platforms/ios/qioswindow.h @@ -71,10 +71,9 @@ private: UIView *m_view; QRect m_normalGeometry; - int m_windowLevel; void raiseOrLower(bool raise); - void updateWindowLevel(); + int windowLevel() const; bool blockedByModal(); friend class QIOSScreen; diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm index f461a5f55b..7cd3d5f0b0 100644 --- a/src/plugins/platforms/ios/qioswindow.mm +++ b/src/plugins/platforms/ios/qioswindow.mm @@ -36,7 +36,6 @@ enum { QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle) : QPlatformWindow(window) - , m_windowLevel(0) { if (nativeHandle) { m_view = reinterpret_cast<UIView *>(nativeHandle); @@ -127,11 +126,6 @@ void QIOSWindow::setVisible(bool visible) if (!isQtApplication() || !window()->isTopLevel()) return; - // Since iOS doesn't do window management the way a Qt application - // expects, we need to raise and activate windows ourselves: - if (visible) - updateWindowLevel(); - if (blockedByModal()) { if (visible) raise(); @@ -343,8 +337,8 @@ void QIOSWindow::raiseOrLower(bool raise) UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]); if (view.hidden || view == m_view || !view.qwindow) continue; - int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel; - if (m_windowLevel > level || (raise && m_windowLevel == level)) { + int level = static_cast<QIOSWindow *>(view.qwindow->handle())->windowLevel(); + if (windowLevel() > level || (raise && windowLevel() == level)) { [m_view.superview insertSubview:m_view aboveSubview:view]; return; } @@ -359,30 +353,34 @@ void QIOSWindow::raiseOrLower(bool raise) } } -void QIOSWindow::updateWindowLevel() +int QIOSWindow::windowLevel() const { Qt::WindowType type = window()->type(); + int level = 0; + if (type == Qt::ToolTip) - m_windowLevel = 120; + level = 120; else if (window()->flags() & Qt::WindowStaysOnTopHint) - m_windowLevel = 100; + level = 100; else if (window()->isModal()) - m_windowLevel = 40; + level = 40; else if (type == Qt::Popup) - m_windowLevel = 30; + level = 30; else if (type == Qt::SplashScreen) - m_windowLevel = 20; + level = 20; else if (type == Qt::Tool) - m_windowLevel = 10; + level = 10; else - m_windowLevel = 0; + level = 0; - // A window should be in at least the same m_windowLevel as its parent: + // A window should be in at least the same window level as its parent QWindow *transientParent = window()->transientParent(); QIOSWindow *transientParentWindow = transientParent ? static_cast<QIOSWindow *>(transientParent->handle()) : 0; if (transientParentWindow) - m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel); + level = qMax(transientParentWindow->windowLevel(), level); + + return level; } void QIOSWindow::applicationStateChanged(Qt::ApplicationState) @@ -396,6 +394,15 @@ void QIOSWindow::applicationStateChanged(Qt::ApplicationState) qreal QIOSWindow::devicePixelRatio() const { +#if !defined(Q_OS_VISIONOS) + // If the view has not yet been added to a screen, it will not + // pick up its device pixel ratio, so we need to do so manually + // based on the screen we think the window will be added to. + if (!m_view.window.windowScene.screen) + return screen()->devicePixelRatio(); +#endif + + // Otherwise we can rely on the content scale factor return m_view.contentScaleFactor; } diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm index 39b2cb8a50..fa54f61967 100644 --- a/src/plugins/platforms/ios/quiaccessibilityelement.mm +++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm @@ -10,6 +10,8 @@ #include "uistrings_p.h" #include "qioswindow.h" +#include <QtGui/private/qaccessiblebridgeutils_p.h> + QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); @implementation QMacAccessibilityElement @@ -70,6 +72,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement); return iface->text(QAccessible::Name).toNSString(); } + +- (NSString*)accessibilityIdentifier +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); + if (!iface) { + qWarning() << "invalid accessible interface for: " << self.axid; + return @""; + } + return QAccessibleBridgeUtils::accessibleId(iface).toNSString(); +} + - (NSString*)accessibilityHint { QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid); diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index d5808db305..cc4b92b92b 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -92,7 +92,16 @@ inline ulong getTimeStamp(UIEvent *event) { if (self = [self initWithFrame:window->geometry().toCGRect()]) { self.platformWindow = window; + + if (isQtApplication()) + self.hidden = YES; + m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init]; + +#ifndef Q_OS_TVOS + self.multipleTouchEnabled = YES; +#endif + m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleScroll:)]; @@ -109,6 +118,7 @@ inline ulong getTimeStamp(UIEvent *event) m_lastScrollCursorPos = CGPointZero; [self addGestureRecognizer:m_scrollGestureRecognizer]; + // Set up layer if ([self.layer isKindOfClass:CAMetalLayer.class]) { QWindow *window = self.platformWindow->window(); if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) { @@ -119,17 +129,8 @@ inline ulong getTimeStamp(UIEvent *event) qCDebug(lcQpaWindow) << "Set" << self << "color space to" << metalLayer.colorspace; } } - } - - return self; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { #if QT_CONFIG(opengl) - if ([self.layer isKindOfClass:[CAEAGLLayer class]]) { - // Set up EAGL layer + else if ([self.layer isKindOfClass:[CAEAGLLayer class]]) { CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer); eaglLayer.opaque = TRUE; eaglLayer.drawableProperties = @{ @@ -139,37 +140,13 @@ inline ulong getTimeStamp(UIEvent *event) } #endif - if (isQtApplication()) - self.hidden = YES; - -#ifndef Q_OS_TVOS - self.multipleTouchEnabled = YES; +#if defined(Q_OS_VISIONOS) + // Although the "Drawing sharp layer-based content in visionOS" docs + // claim that by default a CALayer rasterizes at a 2x scale this does + // not seem to be the case in practice. So we explicitly set the view's + // scale factor based on the screen, where we hard-code it to 2.0. + self.contentScaleFactor = self.platformWindow->screen()->devicePixelRatio(); #endif - - if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) { - static CGFloat hue = 0.0; - CGFloat lastHue = hue; - for (CGFloat diff = 0; diff < 0.1 || diff > 0.9; diff = fabs(hue - lastHue)) - hue = drand48(); - - #define colorWithBrightness(br) \ - [UIColor colorWithHue:hue saturation:0.5 brightness:br alpha:1.0].CGColor - - self.layer.borderColor = colorWithBrightness(1.0); - self.layer.borderWidth = 1.0; - } - - if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) { - UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero]; - [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]]; - [self addSubview:safeAreaOverlay]; - - safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO; - [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; - [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES; - [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES; - [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; - } } return self; diff --git a/src/plugins/platforms/linuxfb/CMakeLists.txt b/src/plugins/platforms/linuxfb/CMakeLists.txt index 9f75f53828..ba18cea50c 100644 --- a/src/plugins/platforms/linuxfb/CMakeLists.txt +++ b/src/plugins/platforms/linuxfb/CMakeLists.txt @@ -8,7 +8,7 @@ qt_internal_add_plugin(QLinuxFbIntegrationPlugin OUTPUT_NAME qlinuxfb PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES linuxfb + DEFAULT_IF "linuxfb" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qlinuxfbintegration.cpp qlinuxfbintegration.h diff --git a/src/plugins/platforms/minimal/CMakeLists.txt b/src/plugins/platforms/minimal/CMakeLists.txt index f3683deccf..18d8828134 100644 --- a/src/plugins/platforms/minimal/CMakeLists.txt +++ b/src/plugins/platforms/minimal/CMakeLists.txt @@ -10,7 +10,7 @@ qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype) qt_internal_add_plugin(QMinimalIntegrationPlugin OUTPUT_NAME qminimal PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimal + DEFAULT_IF "minimal" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qminimalbackingstore.cpp qminimalbackingstore.h diff --git a/src/plugins/platforms/minimalegl/CMakeLists.txt b/src/plugins/platforms/minimalegl/CMakeLists.txt index a6ec8be781..b93f325b8f 100644 --- a/src/plugins/platforms/minimalegl/CMakeLists.txt +++ b/src/plugins/platforms/minimalegl/CMakeLists.txt @@ -10,7 +10,7 @@ qt_find_package(EGL) qt_internal_add_plugin(QMinimalEglIntegrationPlugin OUTPUT_NAME qminimalegl PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimalegl + DEFAULT_IF "minimalegl" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qminimaleglintegration.cpp qminimaleglintegration.h diff --git a/src/plugins/platforms/offscreen/CMakeLists.txt b/src/plugins/platforms/offscreen/CMakeLists.txt index 09ad9a384d..907c2c9cc6 100644 --- a/src/plugins/platforms/offscreen/CMakeLists.txt +++ b/src/plugins/platforms/offscreen/CMakeLists.txt @@ -8,7 +8,7 @@ qt_internal_add_plugin(QOffscreenIntegrationPlugin OUTPUT_NAME qoffscreen PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES offscreen + DEFAULT_IF "offscreen" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qoffscreencommon.cpp qoffscreencommon.h diff --git a/src/plugins/platforms/qnx/CMakeLists.txt b/src/plugins/platforms/qnx/CMakeLists.txt index 9fb412d8a4..0f9deaa00b 100644 --- a/src/plugins/platforms/qnx/CMakeLists.txt +++ b/src/plugins/platforms/qnx/CMakeLists.txt @@ -8,7 +8,7 @@ qt_internal_add_plugin(QQnxIntegrationPlugin OUTPUT_NAME qqnx PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES qnx + DEFAULT_IF "qnx" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp main.h qqnxabstractcover.h diff --git a/src/plugins/platforms/qnx/qqnxintegration.cpp b/src/plugins/platforms/qnx/qqnxintegration.cpp index cc10f6a00e..b308c956f2 100644 --- a/src/plugins/platforms/qnx/qqnxintegration.cpp +++ b/src/plugins/platforms/qnx/qqnxintegration.cpp @@ -49,6 +49,7 @@ #include <qpa/qwindowsysteminterface.h> #include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qrhibackingstore_p.h> #if !defined(QT_NO_OPENGL) #include "qqnxglcontext.h" @@ -328,8 +329,19 @@ QPlatformWindow *QQnxIntegration::createPlatformWindow(QWindow *window) const QPlatformBackingStore *QQnxIntegration::createPlatformBackingStore(QWindow *window) const { - qCDebug(lcQpaQnx) << Q_FUNC_INFO; - return new QQnxRasterBackingStore(window); + QSurface::SurfaceType surfaceType = window->surfaceType(); + qCDebug(lcQpaQnx) << Q_FUNC_INFO << surfaceType; + switch (surfaceType) { + case QSurface::RasterSurface: + return new QQnxRasterBackingStore(window); +#if !defined(QT_NO_OPENGL) + // Return a QRhiBackingStore for non-raster surface windows + case QSurface::OpenGLSurface: + return new QRhiBackingStore(window); +#endif + default: + return nullptr; + } } #if !defined(QT_NO_OPENGL) diff --git a/src/plugins/platforms/qnx/qqnxkeytranslator.h b/src/plugins/platforms/qnx/qqnxkeytranslator.h index 824f7ad523..5e0dd56e2e 100644 --- a/src/plugins/platforms/qnx/qqnxkeytranslator.h +++ b/src/plugins/platforms/qnx/qqnxkeytranslator.h @@ -55,7 +55,7 @@ int qtKeyForPrivateUseQnxKey( int key ) case KEYCODE_KP_UP: return Qt::Key_Up; case KEYCODE_KP_PG_UP: return Qt::Key_PageUp; case KEYCODE_KP_LEFT: return Qt::Key_Left; - case KEYCODE_KP_FIVE: return Qt::Key_5; + case KEYCODE_KP_FIVE: return Qt::Key_Clear; case KEYCODE_KP_RIGHT: return Qt::Key_Right; case KEYCODE_KP_END: return Qt::Key_End; case KEYCODE_KP_DOWN: return Qt::Key_Down; diff --git a/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt b/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt index 719e5c45e6..406487f1e9 100644 --- a/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt +++ b/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt @@ -6,7 +6,7 @@ qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype) qt_internal_add_plugin(QVkKhrDisplayIntegrationPlugin OUTPUT_NAME qvkkhrdisplay PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vkkhrdisplay + DEFAULT_IF "vkkhrdisplay" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qvkkhrdisplayintegration.cpp qvkkhrdisplayintegration.h diff --git a/src/plugins/platforms/vnc/CMakeLists.txt b/src/plugins/platforms/vnc/CMakeLists.txt index 25cb399bd0..34370807ae 100644 --- a/src/plugins/platforms/vnc/CMakeLists.txt +++ b/src/plugins/platforms/vnc/CMakeLists.txt @@ -8,7 +8,7 @@ qt_internal_add_plugin(QVncIntegrationPlugin OUTPUT_NAME qvnc PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vnc + DEFAULT_IF "vnc" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qvnc.cpp qvnc_p.h diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt index 90c7ec2118..caf5d3dff6 100644 --- a/src/plugins/platforms/wasm/CMakeLists.txt +++ b/src/plugins/platforms/wasm/CMakeLists.txt @@ -7,7 +7,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin OUTPUT_NAME qwasm - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm + DEFAULT_IF "wasm" IN_LIST QT_QPA_PLATFORMS PLUGIN_TYPE platforms SOURCES main.cpp @@ -44,6 +44,8 @@ qt_internal_add_plugin(QWasmIntegrationPlugin Qt::CorePrivate Qt::Gui Qt::GuiPrivate + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/wasm ) # Resources: diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index 4c3cb46ba3..2e430176be 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -284,6 +284,10 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac element = document.call<emscripten::val>("createElement", std::string("div")); } + QString id = QAccessibleBridgeUtils::accessibleId(iface); + if (iface->role() != QAccessible::PageTabList) + element.call<void>("setAttribute", std::string("id"), id.toStdString()); + return element; }(); diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp index 6b2b3d0933..96790ca71f 100644 --- a/src/plugins/platforms/wasm/qwasmdom.cpp +++ b/src/plugins/platforms/wasm/qwasmdom.cpp @@ -104,7 +104,10 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback) if (--m_remainingItemCount > 0) return; - mimeData->setUrls(fileUrls); + QList<QUrl> allUrls; + allUrls.append(mimeData->urls()); + allUrls.append(fileUrls); + mimeData->setUrls(allUrls); m_callback(mimeData); @@ -201,7 +204,11 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback) mimeContext->mimeData->setHtml(data); else if (itemMimeType.isEmpty() || itemMimeType == "text/plain") mimeContext->mimeData->setText(data); // the type can be empty - else { + else if (itemMimeType.isEmpty() || itemMimeType == "text/uri-list") { + QList<QUrl> urls; + urls.append(data); + mimeContext->mimeData->setUrls(urls); + } else { // TODO improve encoding if (data.startsWith("QB64")) { data.remove(0, 4); diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp index c1d6ce3a2a..e418263655 100644 --- a/src/plugins/platforms/wasm/qwasmevent.cpp +++ b/src/plugins/platforms/wasm/qwasmevent.cpp @@ -106,7 +106,7 @@ KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event) const auto code = event["code"].as<std::string>(); const auto webKey = event["key"].as<std::string>(); deadKey = isDeadKeyEvent(webKey.c_str()); - + autoRepeat = event["repeat"].as<bool>(); modifiers = KeyboardModifier::getForEvent(event); key = webKeyToQtKey(code, webKey, deadKey, modifiers); diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h index 6ada5393e3..bd0fb39f11 100644 --- a/src/plugins/platforms/wasm/qwasmevent.h +++ b/src/plugins/platforms/wasm/qwasmevent.h @@ -153,6 +153,7 @@ struct KeyEvent : public Event QFlags<Qt::KeyboardModifier> modifiers; bool deadKey; QString text; + bool autoRepeat; }; struct MouseEvent : public Event diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index b8197c5113..99e9bb22f1 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -128,9 +128,8 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, }); emscripten::val keyFocusWindow; - if (QWasmIntegration::get()->inputContext()) { - QWasmInputContext *wasmContext = - static_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext()); + if (QWasmInputContext *wasmContext = + qobject_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext())) { // if there is an touchscreen input context, // use that window for key input keyFocusWindow = wasmContext->m_inputElement; @@ -502,7 +501,7 @@ bool QWasmWindow::processKey(const KeyEvent &event) const auto result = QWindowSystemInterface::handleKeyEvent( 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key, - event.modifiers, event.text); + event.modifiers, event.text, event.autoRepeat); return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded ? ProceedToNativeEvent : result; diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html index 702ea1f59d..6e93955552 100644 --- a/src/plugins/platforms/wasm/wasm_shell.html +++ b/src/plugins/platforms/wasm/wasm_shell.html @@ -1,3 +1,8 @@ +<!-- +Copyright (C) 2024 The Qt Company Ltd. +SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +--> + <!doctype html> <html lang="en-us"> <head> diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt index 4b92317978..bbb8c46cf6 100644 --- a/src/plugins/platforms/windows/CMakeLists.txt +++ b/src/plugins/platforms/windows/CMakeLists.txt @@ -8,7 +8,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin OUTPUT_NAME qwindows PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES windows + DEFAULT_IF "windows" IN_LIST QT_QPA_PLATFORMS SOURCES main.cpp qtwindowsglobal.h @@ -28,7 +28,6 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin qwindowskeymapper.cpp qwindowskeymapper.h qwindowsmenu.cpp qwindowsmenu.h qwindowsmimeregistry.cpp qwindowsmimeregistry.h - qwindowsmousehandler.cpp qwindowsmousehandler.h qwindowsnativeinterface.cpp qwindowsnativeinterface.h qwindowsole.cpp qwindowsole.h qwindowsopengltester.cpp qwindowsopengltester.h @@ -132,6 +131,8 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_tablete qwindowstabletsupport.cpp qwindowstabletsupport.h INCLUDE_DIRECTORIES ${QtBase_SOURCE_DIR}/src/3rdparty/wintab + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/wintab ) qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_sessionmanager diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index de65a2171d..11a9290a2e 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -7,7 +7,6 @@ #include "qwindowswindow.h" #include "qwindowskeymapper.h" #include "qwindowsnativeinterface.h" -#include "qwindowsmousehandler.h" #include "qwindowspointerhandler.h" #include "qtwindowsglobal.h" #include "qwindowsmenu.h" @@ -141,7 +140,6 @@ struct QWindowsContextPrivate { HDC m_displayContext = nullptr; int m_defaultDPI = 96; QWindowsKeyMapper m_keyMapper; - QWindowsMouseHandler m_mouseHandler; QWindowsPointerHandler m_pointerHandler; QWindowsMimeRegistry m_mimeConverter; QWindowsScreenManager m_screenManager; @@ -162,7 +160,7 @@ bool QWindowsContextPrivate::m_v2DpiAware = false; QWindowsContextPrivate::QWindowsContextPrivate() : m_oleInitializeResult(OleInitialize(nullptr)) { - if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice()) + if (m_pointerHandler.touchDevice()) m_systemInfo |= QWindowsContext::SI_SupportsTouch; m_displayContext = GetDC(nullptr); m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY); @@ -201,6 +199,8 @@ QWindowsContext::~QWindowsContext() if (d->m_powerDummyWindow) DestroyWindow(d->m_powerDummyWindow); + d->m_screenManager.destroyWindow(); + unregisterWindowClasses(); if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) { #ifdef QT_USE_FACTORY_CACHE_REGISTRATION @@ -224,8 +224,7 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) { if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch) return true; - const bool usePointerHandler = (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) != 0; - auto touchDevice = usePointerHandler ? d->m_pointerHandler.touchDevice() : d->m_mouseHandler.touchDevice(); + auto touchDevice = d->m_pointerHandler.touchDevice(); if (touchDevice.isNull()) { const bool mouseEmulation = (integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) == 0; @@ -234,7 +233,6 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) if (touchDevice.isNull()) return false; d->m_pointerHandler.setTouchDevice(touchDevice); - d->m_mouseHandler.setTouchDevice(touchDevice); QWindowSystemInterface::registerInputDevice(touchDevice.data()); d->m_systemInfo |= QWindowsContext::SI_SupportsTouch; @@ -274,15 +272,6 @@ bool QWindowsContext::disposeTablet() #endif } -bool QWindowsContext::initPointer(unsigned integrationOptions) -{ - if (integrationOptions & QWindowsIntegration::DontUseWMPointer) - return false; - - d->m_systemInfo |= QWindowsContext::SI_SupportsPointer; - return true; -} - extern "C" LRESULT QT_WIN_CALLBACK qWindowsPowerWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message != WM_POWERBROADCAST || wParam != PBT_POWERSETTINGCHANGE) @@ -728,16 +717,12 @@ QWindow *QWindowsContext::findWindow(HWND hwnd) const QWindow *QWindowsContext::windowUnderMouse() const { - return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ? - d->m_pointerHandler.windowUnderMouse() : d->m_mouseHandler.windowUnderMouse(); + return d->m_pointerHandler.windowUnderMouse(); } void QWindowsContext::clearWindowUnderMouse() { - if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) - d->m_pointerHandler.clearWindowUnderMouse(); - else - d->m_mouseHandler.clearWindowUnderMouse(); + d->m_pointerHandler.clearWindowUnderMouse(); } /*! @@ -1050,8 +1035,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, switch (et) { case QtWindows::GestureEvent: - if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result); + // TODO??? break; case QtWindows::InputMethodOpenCandidateWindowEvent: case QtWindows::InputMethodCloseCandidateWindowEvent: @@ -1191,16 +1175,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::NonClientMouseEvent: if (!platformWindow->frameStrutEventsEnabled()) break; - if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) - return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); - else - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::NonClientPointerEvent: if (!platformWindow->frameStrutEventsEnabled()) break; - if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) - return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); - break; + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::EnterSizeMoveEvent: platformWindow->setFlag(QWindowsWindow::ResizeMoveActive); if (!IsZoomed(hwnd)) @@ -1214,8 +1193,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, platformWindow->updateRestoreGeometry(); return true; case QtWindows::ScrollEvent: - if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result); + // TODO??? break; case QtWindows::MouseWheelEvent: case QtWindows::MouseEvent: @@ -1226,20 +1204,14 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, window = window->parent(); if (!window) return false; - if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) - return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result); - else - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result); + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result); } break; case QtWindows::TouchEvent: - if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) - return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result); + // TODO??? break; case QtWindows::PointerEvent: - if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) - return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); - break; + return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow(). case QtWindows::FocusOutEvent: handleFocusEvent(et, platformWindow); @@ -1443,7 +1415,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window) // Mouse is left in pressed state after press on size grip (inside window), // no further mouse events are received // For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons. - const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons(); + const Qt::MouseButtons currentButtons = QWindowsPointerHandler::queryMouseButtons(); const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons(); if (currentButtons == appButtons) return; @@ -1459,10 +1431,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window) currentButtons, button, type, keyboardModifiers); } } - if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) - d->m_pointerHandler.clearEvents(); - else - d->m_mouseHandler.clearEvents(); + d->m_pointerHandler.clearEvents(); } bool QWindowsContext::asyncExpose() const diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 0539a22afc..4e9be1af96 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -53,8 +53,7 @@ public: enum SystemInfoFlags { SI_RTL_Extensions = 0x1, - SI_SupportsTouch = 0x2, - SI_SupportsPointer = 0x4, + SI_SupportsTouch = 0x2 }; // Verbose flag set by environment variable QT_QPA_VERBOSE @@ -67,7 +66,6 @@ public: bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only. void registerTouchWindows(); bool initTablet(); - bool initPointer(unsigned integrationOptions); bool disposeTablet(); bool initPowerNotificationHandler(); diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index c6f55c3509..b872ef2ad6 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -11,7 +11,7 @@ #include "qwindowsintegration.h" #include "qwindowsdropdataobject.h" #include "qwindowswindow.h" -#include "qwindowsmousehandler.h" +#include "qwindowspointerhandler.h" #include "qwindowscursor.h" #include "qwindowskeymapper.h" @@ -341,7 +341,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) // In some rare cases, when a mouse button is released but the mouse is static, // grfKeyState will not be updated with these released buttons until the mouse // is moved. So we use the async key state given by queryMouseButtons() instead. - Qt::MouseButtons buttons = QWindowsMouseHandler::queryMouseButtons(); + Qt::MouseButtons buttons = QWindowsPointerHandler::queryMouseButtons(); SCODE result = S_OK; if (fEscapePressed || QWindowsDrag::isCanceled()) { @@ -366,7 +366,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos); QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(), QPointF(localPos), QPointF(globalPos), - QWindowsMouseHandler::queryMouseButtons(), + QWindowsPointerHandler::queryMouseButtons(), Qt::LeftButton, QEvent::MouseButtonRelease); } m_currentButtons = Qt::NoButton; @@ -460,7 +460,7 @@ void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState, const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect); lastModifiers = toQtKeyboardModifiers(grfKeyState); - lastButtons = QWindowsMouseHandler::queryMouseButtons(); + lastButtons = QWindowsPointerHandler::queryMouseButtons(); const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(), @@ -530,7 +530,7 @@ QWindowsOleDropTarget::DragLeave() const auto *keyMapper = QWindowsContext::instance()->keyMapper(); lastModifiers = keyMapper->queryKeyboardModifiers(); - lastButtons = QWindowsMouseHandler::queryMouseButtons(); + lastButtons = QWindowsPointerHandler::queryMouseButtons(); QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier); @@ -559,7 +559,7 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, QWindowsDrag *windowsDrag = QWindowsDrag::instance(); lastModifiers = toQtKeyboardModifiers(grfKeyState); - lastButtons = QWindowsMouseHandler::queryMouseButtons(); + lastButtons = QWindowsPointerHandler::queryMouseButtons(); const QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(), diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp index 0281025b5b..4b524c7a2c 100644 --- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp @@ -5,7 +5,6 @@ #include "qwindowscontext.h" #include "qwindowswindow.h" #include "qwindowsintegration.h" -#include "qwindowsmousehandler.h" #include <QtCore/qdebug.h> #include <QtCore/qobject.h> diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index aa6be266da..487e1d47b6 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -173,8 +173,6 @@ static inline unsigned parseOptions(const QStringList ¶mList, options |= QWindowsIntegration::AlwaysUseNativeMenus; } else if (param == u"menus=none") { options |= QWindowsIntegration::NoNativeMenus; - } else if (param == u"nowmpointer") { - options |= QWindowsIntegration::DontUseWMPointer; } else if (param == u"reverse") { options |= QWindowsIntegration::RtlEnabled; } else if (param == u"darkmode=0") { @@ -209,10 +207,7 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr if (tabletAbsoluteRange >= 0) QWindowsContext::setTabletAbsoluteRange(tabletAbsoluteRange); - if (m_context.initPointer(m_options)) - QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); - else - m_context.initTablet(); + QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication. diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index c271207741..932a69a6b7 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -42,10 +42,9 @@ public: DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts, AlwaysUseNativeMenus = 0x100, NoNativeMenus = 0x200, - DontUseWMPointer = 0x400, - DetectAltGrModifier = 0x800, - RtlEnabled = 0x1000, - FontDatabaseGDI = 0x2000 + DetectAltGrModifier = 0x400, + RtlEnabled = 0x0800, + FontDatabaseGDI = 0x1000 }; explicit QWindowsIntegration(const QStringList ¶mList); diff --git a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp index 8d147e8fa0..71faf4fe3b 100644 --- a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp +++ b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp @@ -923,16 +923,8 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject * const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj); const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false; qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5; - if (hasOrigDibV5) { - qCDebug(lcQpaMime) << "Decoding DIBV5"; - QImage img; - QByteArray data = getData(CF_DIBV5, pDataObj); - QBuffer buffer(&data); - if (readDib(buffer, img)) - return img; - } - //PNG, MS Office place this (undocumented) - if (canGetData(CF_PNG, pDataObj)) { + // PNG, MS Office place this (undocumented) + if (!hasOrigDibV5 && canGetData(CF_PNG, pDataObj)) { qCDebug(lcQpaMime) << "Decoding PNG"; QImage img; QByteArray data = getData(CF_PNG, pDataObj); @@ -940,11 +932,11 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject * return img; } } - //Fallback to DIB - if (canGetData(CF_DIB, pDataObj)) { + + if (canGetDibV5 || canGetData(CF_DIB, pDataObj)) { qCDebug(lcQpaMime) << "Decoding DIB"; QImage img; - QByteArray data = getData(CF_DIBV5, pDataObj); + QByteArray data = getData(canGetDibV5 ? CF_DIBV5 : CF_DIB, pDataObj); QBuffer buffer(&data); if (readDib(buffer, img)) return img; diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp deleted file mode 100644 index 9af9fba408..0000000000 --- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp +++ /dev/null @@ -1,653 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qwindowsmousehandler.h" -#include "qwindowskeymapper.h" -#include "qwindowscontext.h" -#include "qwindowswindow.h" -#include "qwindowsintegration.h" -#include "qwindowsscreen.h" - -#include <qpa/qwindowsysteminterface.h> -#include <QtGui/qguiapplication.h> -#include <QtGui/qscreen.h> -#include <QtGui/qpointingdevice.h> -#include <QtGui/qwindow.h> -#include <QtGui/qcursor.h> - -#include <QtCore/qdebug.h> - -#include <memory> - -#include <windowsx.h> - -QT_BEGIN_NAMESPACE - -static inline void compressMouseMove(MSG *msg) -{ - // Compress mouse move events - if (msg->message == WM_MOUSEMOVE) { - MSG mouseMsg; - while (PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEFIRST, - WM_MOUSELAST, PM_NOREMOVE)) { - if (mouseMsg.message == WM_MOUSEMOVE) { -#define PEEKMESSAGE_IS_BROKEN 1 -#ifdef PEEKMESSAGE_IS_BROKEN - // Since the Windows PeekMessage() function doesn't - // correctly return the wParam for WM_MOUSEMOVE events - // if there is a key release event in the queue - // _before_ the mouse event, we have to also consider - // key release events (kls 2003-05-13): - MSG keyMsg; - bool done = false; - while (PeekMessage(&keyMsg, nullptr, WM_KEYFIRST, WM_KEYLAST, - PM_NOREMOVE)) { - if (keyMsg.time < mouseMsg.time) { - if ((keyMsg.lParam & 0xC0000000) == 0x40000000) { - PeekMessage(&keyMsg, nullptr, keyMsg.message, - keyMsg.message, PM_REMOVE); - } else { - done = true; - break; - } - } else { - break; // no key event before the WM_MOUSEMOVE event - } - } - if (done) - break; -#else - // Actually the following 'if' should work instead of - // the above key event checking, but apparently - // PeekMessage() is broken :-( - if (mouseMsg.wParam != msg.wParam) - break; // leave the message in the queue because - // the key state has changed -#endif - // Update the passed in MSG structure with the - // most recent one. - msg->lParam = mouseMsg.lParam; - msg->wParam = mouseMsg.wParam; - // Extract the x,y coordinates from the lParam as we do in the WndProc - msg->pt.x = GET_X_LPARAM(mouseMsg.lParam); - msg->pt.y = GET_Y_LPARAM(mouseMsg.lParam); - clientToScreen(msg->hwnd, &(msg->pt)); - // Remove the mouse move message - PeekMessage(&mouseMsg, msg->hwnd, WM_MOUSEMOVE, - WM_MOUSEMOVE, PM_REMOVE); - } else { - break; // there was no more WM_MOUSEMOVE event - } - } - } -} - -/*! - \class QWindowsMouseHandler - \brief Windows mouse handler - - Dispatches mouse and touch events. Separate for code cleanliness. - - \internal -*/ - -QWindowsMouseHandler::QWindowsMouseHandler() = default; - -const QPointingDevice *QWindowsMouseHandler::primaryMouse() -{ - static QPointer<const QPointingDevice> result; - if (!result) - result = QPointingDevice::primaryPointingDevice(); - return result; -} - -void QWindowsMouseHandler::clearEvents() -{ - m_lastEventType = QEvent::None; - m_lastEventButton = Qt::NoButton; -} - -Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons() -{ - Qt::MouseButtons result; - const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON); - if (GetAsyncKeyState(VK_LBUTTON) < 0) - result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton; - if (GetAsyncKeyState(VK_RBUTTON) < 0) - result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton; - if (GetAsyncKeyState(VK_MBUTTON) < 0) - result |= Qt::MiddleButton; - if (GetAsyncKeyState(VK_XBUTTON1) < 0) - result |= Qt::XButton1; - if (GetAsyncKeyState(VK_XBUTTON2) < 0) - result |= Qt::XButton2; - return result; -} - -Q_CONSTINIT static QPoint lastMouseMovePos; - -namespace { -struct MouseEvent { - QEvent::Type type; - Qt::MouseButton button; -}; - -#ifndef QT_NO_DEBUG_STREAM -QDebug operator<<(QDebug d, const MouseEvent &e) -{ - QDebugStateSaver saver(d); - d.nospace(); - d << "MouseEvent(" << e.type << ", " << e.button << ')'; - return d; -} -#endif // QT_NO_DEBUG_STREAM -} // namespace - -static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON... -{ - return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton; -} - -static inline MouseEvent eventFromMsg(const MSG &msg) -{ - switch (msg.message) { - case WM_MOUSEMOVE: - return {QEvent::MouseMove, Qt::NoButton}; - case WM_LBUTTONDOWN: - return {QEvent::MouseButtonPress, Qt::LeftButton}; - case WM_LBUTTONUP: - return {QEvent::MouseButtonRelease, Qt::LeftButton}; - case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press - return {QEvent::MouseButtonPress, Qt::LeftButton}; - case WM_MBUTTONDOWN: - return {QEvent::MouseButtonPress, Qt::MiddleButton}; - case WM_MBUTTONUP: - return {QEvent::MouseButtonRelease, Qt::MiddleButton}; - case WM_MBUTTONDBLCLK: - return {QEvent::MouseButtonPress, Qt::MiddleButton}; - case WM_RBUTTONDOWN: - return {QEvent::MouseButtonPress, Qt::RightButton}; - case WM_RBUTTONUP: - return {QEvent::MouseButtonRelease, Qt::RightButton}; - case WM_RBUTTONDBLCLK: - return {QEvent::MouseButtonPress, Qt::RightButton}; - case WM_XBUTTONDOWN: - return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; - case WM_XBUTTONUP: - return {QEvent::MouseButtonRelease, extraButton(msg.wParam)}; - case WM_XBUTTONDBLCLK: - return {QEvent::MouseButtonPress, extraButton(msg.wParam)}; - case WM_NCMOUSEMOVE: - return {QEvent::NonClientAreaMouseMove, Qt::NoButton}; - case WM_NCLBUTTONDOWN: - return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton}; - case WM_NCLBUTTONUP: - return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton}; - case WM_NCLBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton}; - case WM_NCMBUTTONDOWN: - return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton}; - case WM_NCMBUTTONUP: - return {QEvent::NonClientAreaMouseButtonRelease, Qt::MiddleButton}; - case WM_NCMBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton}; - case WM_NCRBUTTONDOWN: - return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; - case WM_NCRBUTTONUP: - return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton}; - case WM_NCRBUTTONDBLCLK: - return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton}; - default: // WM_MOUSELEAVE - break; - } - return {QEvent::None, Qt::NoButton}; -} - -bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd, - QtWindows::WindowsEventType et, - MSG msg, LRESULT *result) -{ - enum : quint64 { signatureMask = 0xffffff00, miWpSignature = 0xff515700 }; - - if (et == QtWindows::MouseWheelEvent) - return translateMouseWheelEvent(window, hwnd, msg, result); - - QPoint winEventPosition(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); - if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) { - RECT clientArea; - GetClientRect(hwnd, &clientArea); - winEventPosition.setX(clientArea.right - winEventPosition.x()); - } - - QPoint clientPosition; - QPoint globalPosition; - if (et & QtWindows::NonClientEventFlag) { - globalPosition = winEventPosition; - clientPosition = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPosition); - } else { - globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition); - auto targetHwnd = hwnd; - if (auto *pw = window->handle()) - targetHwnd = HWND(pw->winId()); - clientPosition = targetHwnd == hwnd - ? winEventPosition - : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPosition); - } - - // Windows sends a mouse move with no buttons pressed to signal "Enter" - // when a window is shown over the cursor. Discard the event and only use - // it for generating QEvent::Enter to be consistent with other platforms - - // X11 and macOS. - bool discardEvent = false; - if (msg.message == WM_MOUSEMOVE) { - const bool samePosition = globalPosition == lastMouseMovePos; - lastMouseMovePos = globalPosition; - if (msg.wParam == 0 && (m_windowUnderMouse.isNull() || samePosition)) - discardEvent = true; - } - - Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - - const QPointingDevice *device = primaryMouse(); - - // Check for events synthesized from touch. Lower byte is touch index, 0 means pen. - static const bool passSynthesizedMouseEvents = - !(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch); - // Check for events synthesized from touch. Lower 7 bits are touch/pen index, bit 8 indicates touch. - // However, when tablet support is active, extraInfo is a packet serial number. This is not a problem - // since we do not want to ignore mouse events coming from a tablet. - // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320.aspx - const auto extraInfo = quint64(GetMessageExtraInfo()); - if ((extraInfo & signatureMask) == miWpSignature) { - if (extraInfo & 0x80) { // Bit 7 indicates touch event, else tablet pen. - source = Qt::MouseEventSynthesizedBySystem; - if (!m_touchDevice.isNull()) - device = m_touchDevice.data(); - if (!passSynthesizedMouseEvents) - return false; - } - } - - const auto *keyMapper = QWindowsContext::instance()->keyMapper(); - const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers(); - const MouseEvent mouseEvent = eventFromMsg(msg); - Qt::MouseButtons buttons; - - if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) - buttons = queryMouseButtons(); - else - buttons = keyStateToMouseButtons(msg.wParam); - - // When the left/right mouse buttons are pressed over the window title bar - // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP - // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE. - // We detect it and generate the missing release events here. (QTBUG-75678) - // The last event vars are cleared on QWindowsContext::handleExitSizeMove() - // to avoid generating duplicated release events. - if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress - && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove) - && (m_lastEventButton & buttons) == 0) { - auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ? - QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease; - QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton, - releaseType, keyModifiers, source); - } - m_lastEventType = mouseEvent.type; - m_lastEventButton = mouseEvent.button; - - if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) { - QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, - globalPosition, buttons, - mouseEvent.button, mouseEvent.type, - keyModifiers, source); - return false; // Allow further event processing (dragging of windows). - } - - *result = 0; - if (msg.message == WM_MOUSELEAVE) { - qCDebug(lcQpaEvents) << mouseEvent << "for" << window << "previous window under mouse=" - << m_windowUnderMouse << "tracked window=" << m_trackedWindow; - - // When moving out of a window, WM_MOUSEMOVE within the moved-to window is received first, - // so if m_trackedWindow is not the window here, it means the cursor has left the - // application. - if (window == m_trackedWindow) { - QWindow *leaveTarget = m_windowUnderMouse ? m_windowUnderMouse : m_trackedWindow; - qCDebug(lcQpaEvents) << "Generating leave event for " << leaveTarget; - QWindowSystemInterface::handleLeaveEvent(leaveTarget); - m_trackedWindow = nullptr; - m_windowUnderMouse = nullptr; - } - return true; - } - - auto *platformWindow = static_cast<QWindowsWindow *>(window->handle()); - - // If the window was recently resized via mouse double-click on the frame or title bar, - // we don't get WM_LBUTTONDOWN or WM_LBUTTONDBLCLK for the second click, - // but we will get at least one WM_MOUSEMOVE with left button down and the WM_LBUTTONUP, - // which will result undesired mouse press and release events. - // To avoid those, we ignore any events with left button down if we didn't - // get the original WM_LBUTTONDOWN/WM_LBUTTONDBLCLK. - if (msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONDBLCLK) { - m_leftButtonDown = true; - } else { - const bool actualLeftDown = buttons & Qt::LeftButton; - if (!m_leftButtonDown && actualLeftDown) { - // Autocapture the mouse for current window to and ignore further events until release. - // Capture is necessary so we don't get WM_MOUSELEAVEs to confuse matters. - // This autocapture is released normally when button is released. - if (!platformWindow->hasMouseCapture()) { - platformWindow->applyCursor(); - platformWindow->setMouseGrabEnabled(true); - platformWindow->setFlag(QWindowsWindow::AutoMouseCapture); - qCDebug(lcQpaEvents) << "Automatic mouse capture for missing buttondown event" << window; - } - m_previousCaptureWindow = window; - return true; - } - if (m_leftButtonDown && !actualLeftDown) - m_leftButtonDown = false; - } - - // In this context, neither an invisible nor a transparent window (transparent regarding mouse - // events, "click-through") can be considered as the window under mouse. - QWindow *currentWindowUnderMouse = platformWindow->hasMouseCapture() ? - QWindowsScreen::windowAt(globalPosition, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window; - while (currentWindowUnderMouse && currentWindowUnderMouse->flags() & Qt::WindowTransparentForInput) - currentWindowUnderMouse = currentWindowUnderMouse->parent(); - // QTBUG-44332: When Qt is running at low integrity level and - // a Qt Window is parented on a Window of a higher integrity process - // using QWindow::fromWinId() (for example, Qt running in a browser plugin) - // ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED) - if (!currentWindowUnderMouse) { - const QRect clientRect(QPoint(0, 0), window->size()); - if (clientRect.contains(winEventPosition)) - currentWindowUnderMouse = window; - } - - compressMouseMove(&msg); - // Qt expects the platform plugin to capture the mouse on - // any button press until release. - if (!platformWindow->hasMouseCapture() - && (mouseEvent.type == QEvent::MouseButtonPress || mouseEvent.type == QEvent::MouseButtonDblClick)) { - platformWindow->setMouseGrabEnabled(true); - platformWindow->setFlag(QWindowsWindow::AutoMouseCapture); - qCDebug(lcQpaEvents) << "Automatic mouse capture " << window; - // Implement "Click to focus" for native child windows (unless it is a native widget window). - if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window) - window->requestActivate(); - } else if (platformWindow->hasMouseCapture() - && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture) - && mouseEvent.type == QEvent::MouseButtonRelease - && !buttons) { - platformWindow->setMouseGrabEnabled(false); - qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window; - } - - const bool hasCapture = platformWindow->hasMouseCapture(); - const bool currentNotCapturing = hasCapture && currentWindowUnderMouse != window; - // Enter new window: track to generate leave event. - // If there is an active capture, only track if the current window is capturing, - // so we don't get extra leave when cursor leaves the application. - if (window != m_trackedWindow && !currentNotCapturing) { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = hwnd; - tme.dwHoverTime = HOVER_DEFAULT; // - if (!TrackMouseEvent(&tme)) - qWarning("TrackMouseEvent failed."); - m_trackedWindow = window; - } - - // No enter or leave events are sent as long as there is an autocapturing window. - if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) { - // Leave is needed if: - // 1) There is no capture and we move from a window to another window. - // Note: Leaving the application entirely is handled in WM_MOUSELEAVE case. - // 2) There is capture and we move out of the capturing window. - // 3) There is a new capture and we were over another window. - if ((m_windowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse - && (!hasCapture || window == m_windowUnderMouse)) - || (hasCapture && m_previousCaptureWindow != window && m_windowUnderMouse - && m_windowUnderMouse != window)) { - qCDebug(lcQpaEvents) << "Synthetic leave for " << m_windowUnderMouse; - QWindowSystemInterface::handleLeaveEvent(m_windowUnderMouse); - if (currentNotCapturing) { - // Clear tracking if capturing and current window is not the capturing window - // to avoid leave when mouse actually leaves the application. - m_trackedWindow = nullptr; - // We are not officially in any window, but we need to set some cursor to clear - // whatever cursor the left window had, so apply the cursor of the capture window. - platformWindow->applyCursor(); - } - } - // Enter is needed if: - // 1) There is no capture and we move to a new window. - // 2) There is capture and we move into the capturing window. - // 3) The capture just ended and we are over non-capturing window. - if ((currentWindowUnderMouse && m_windowUnderMouse != currentWindowUnderMouse - && (!hasCapture || currentWindowUnderMouse == window)) - || (m_previousCaptureWindow && window != m_previousCaptureWindow && currentWindowUnderMouse - && currentWindowUnderMouse != m_previousCaptureWindow)) { - QPoint localPosition; - qCDebug(lcQpaEvents) << "Entering " << currentWindowUnderMouse; - if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderMouse)) { - localPosition = wumPlatformWindow->mapFromGlobal(globalPosition); - wumPlatformWindow->applyCursor(); - } - QWindowSystemInterface::handleEnterEvent(currentWindowUnderMouse, localPosition, globalPosition); - } - // We need to track m_windowUnderMouse separately from m_trackedWindow, as - // Windows mouse tracking will not trigger WM_MOUSELEAVE for leaving window when - // mouse capture is set. - m_windowUnderMouse = currentWindowUnderMouse; - } - - if (!discardEvent && mouseEvent.type != QEvent::None) { - QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, - mouseEvent.button, mouseEvent.type, - keyModifiers, source); - } - m_previousCaptureWindow = hasCapture ? window : nullptr; - // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND - // is sent for unhandled WM_XBUTTONDOWN. - return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK) - || QWindowSystemInterface::flushWindowSystemEvents(); -} - -static bool isValidWheelReceiver(QWindow *candidate) -{ - if (candidate) { - const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate); - if (toplevel->handle() && toplevel->handle()->isForeignWindow()) - return true; - if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel)) - return !ww->testFlag(QWindowsWindow::BlockedByModal); - } - - return false; -} - -static void redirectWheelEvent(QWindow *window, unsigned long timestamp, const QPoint &globalPos, int delta, - Qt::Orientation orientation, Qt::KeyboardModifiers mods) -{ - // Redirect wheel event to one of the following, in order of preference: - // 1) The window under mouse - // 2) The window receiving the event - // If a window is blocked by modality, it can't get the event. - - QWindow *receiver = QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE); - while (receiver && receiver->flags().testFlag(Qt::WindowTransparentForInput)) - receiver = receiver->parent(); - bool handleEvent = true; - if (!isValidWheelReceiver(receiver)) { - receiver = window; - if (!isValidWheelReceiver(receiver)) - handleEvent = false; - } - - if (handleEvent) { - const QPoint point = (orientation == Qt::Vertical) ? QPoint(0, delta) : QPoint(delta, 0); - QWindowSystemInterface::handleWheelEvent(receiver, - timestamp, - QWindowsGeometryHint::mapFromGlobal(receiver, globalPos), - globalPos, QPoint(), point, mods); - } -} - -bool QWindowsMouseHandler::translateMouseWheelEvent(QWindow *window, HWND, - MSG msg, LRESULT *) -{ - const Qt::KeyboardModifiers mods = keyStateToModifiers(int(msg.wParam)); - - int delta; - if (msg.message == WM_MOUSEWHEEL || msg.message == WM_MOUSEHWHEEL) - delta = GET_WHEEL_DELTA_WPARAM(msg.wParam); - else - delta = int(msg.wParam); - - Qt::Orientation orientation = (msg.message == WM_MOUSEHWHEEL - || (mods & Qt::AltModifier)) ? - Qt::Horizontal : Qt::Vertical; - - // according to the MSDN documentation on WM_MOUSEHWHEEL: - // a positive value indicates that the wheel was rotated to the right; - // a negative value indicates that the wheel was rotated to the left. - // Qt defines this value as the exact opposite, so we have to flip the value! - if (msg.message == WM_MOUSEHWHEEL) - delta = -delta; - - const QPoint globalPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam)); - redirectWheelEvent(window, msg.time, globalPos, delta, orientation, mods); - - return true; -} - -bool QWindowsMouseHandler::translateScrollEvent(QWindow *window, HWND, - MSG msg, LRESULT *) -{ - // This is a workaround against some touchpads that send WM_HSCROLL instead of WM_MOUSEHWHEEL. - // We could also handle vertical scroll here but there's no reason to, there's no bug for vertical - // (broken vertical scroll would have been noticed long time ago), so lets keep the change small - // and minimize the chance for regressions. - - int delta = 0; - switch (LOWORD(msg.wParam)) { - case SB_LINELEFT: - delta = 120; - break; - case SB_LINERIGHT: - delta = -120; - break; - case SB_PAGELEFT: - delta = 240; - break; - case SB_PAGERIGHT: - delta = -240; - break; - default: - return false; - } - - redirectWheelEvent(window, msg.time, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier); - - return true; -} - -// from bool QApplicationPrivate::translateTouchEvent() -bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND, - QtWindows::WindowsEventType, - MSG msg, LRESULT *) -{ - using QTouchPoint = QWindowSystemInterface::TouchPoint; - using QTouchPointList = QList<QWindowSystemInterface::TouchPoint>; - - if (!QWindowsContext::instance()->initTouch()) { - qWarning("Unable to initialize touch handling."); - return true; - } - - const QScreen *screen = window->screen(); - if (!screen) - screen = QGuiApplication::primaryScreen(); - if (!screen) - return true; - const QRect screenGeometry = screen->geometry(); - - const int winTouchPointCount = int(msg.wParam); - const auto winTouchInputs = std::make_unique<TOUCHINPUT[]>(winTouchPointCount); - - QTouchPointList touchPoints; - touchPoints.reserve(winTouchPointCount); - QEventPoint::States allStates; - - GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(msg.lParam), - UINT(msg.wParam), winTouchInputs.get(), sizeof(TOUCHINPUT)); - for (int i = 0; i < winTouchPointCount; ++i) { - const TOUCHINPUT &winTouchInput = winTouchInputs[i]; - int id = m_touchInputIDToTouchPointID.value(winTouchInput.dwID, -1); - if (id == -1) { - id = m_touchInputIDToTouchPointID.size(); - m_touchInputIDToTouchPointID.insert(winTouchInput.dwID, id); - } - QTouchPoint touchPoint; - touchPoint.pressure = 1.0; - touchPoint.id = id; - if (m_lastTouchPositions.contains(id)) - touchPoint.normalPosition = m_lastTouchPositions.value(id); - - const QPointF screenPos = QPointF(winTouchInput.x, winTouchInput.y) / qreal(100.); - if (winTouchInput.dwMask & TOUCHINPUTMASKF_CONTACTAREA) - touchPoint.area.setSize(QSizeF(winTouchInput.cxContact, winTouchInput.cyContact) / qreal(100.)); - touchPoint.area.moveCenter(screenPos); - QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(), - screenPos.y() / screenGeometry.height()); - const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition); - touchPoint.normalPosition = normalPosition; - - if (winTouchInput.dwFlags & TOUCHEVENTF_DOWN) { - touchPoint.state = QEventPoint::State::Pressed; - m_lastTouchPositions.insert(id, touchPoint.normalPosition); - } else if (winTouchInput.dwFlags & TOUCHEVENTF_UP) { - touchPoint.state = QEventPoint::State::Released; - m_lastTouchPositions.remove(id); - } else { - touchPoint.state = (stationaryTouchPoint - ? QEventPoint::State::Stationary - : QEventPoint::State::Updated); - m_lastTouchPositions.insert(id, touchPoint.normalPosition); - } - - allStates |= touchPoint.state; - - touchPoints.append(touchPoint); - } - - CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(msg.lParam)); - - // all touch points released, forget the ids we've seen, they may not be reused - if (allStates == QEventPoint::State::Released) - m_touchInputIDToTouchPointID.clear(); - - const auto *keyMapper = QWindowsContext::instance()->keyMapper(); - QWindowSystemInterface::handleTouchEvent(window, - msg.time, - m_touchDevice.data(), - touchPoints, - keyMapper->queryKeyboardModifiers()); - return true; -} - -bool QWindowsMouseHandler::translateGestureEvent(QWindow *window, HWND hwnd, - QtWindows::WindowsEventType, - MSG msg, LRESULT *) -{ - Q_UNUSED(window); - Q_UNUSED(hwnd); - Q_UNUSED(msg); - return false; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.h b/src/plugins/platforms/windows/qwindowsmousehandler.h deleted file mode 100644 index 7fde349f58..0000000000 --- a/src/plugins/platforms/windows/qwindowsmousehandler.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef QWINDOWSMOUSEHANDLER_H -#define QWINDOWSMOUSEHANDLER_H - -#include "qtwindowsglobal.h" -#include <QtCore/qt_windows.h> - -#include <QtCore/qpointer.h> -#include <QtCore/qhash.h> -#include <QtCore/qsharedpointer.h> -#include <QtGui/qevent.h> - -QT_BEGIN_NAMESPACE - -class QWindow; -class QPointingDevice; - -class QWindowsMouseHandler -{ - Q_DISABLE_COPY_MOVE(QWindowsMouseHandler) -public: - using QPointingDevicePtr = QSharedPointer<QPointingDevice>; - - QWindowsMouseHandler(); - - const QPointingDevicePtr &touchDevice() const { return m_touchDevice; } - void setTouchDevice(const QPointingDevicePtr &d) { m_touchDevice = d; } - - bool translateMouseEvent(QWindow *widget, HWND hwnd, - QtWindows::WindowsEventType t, MSG msg, - LRESULT *result); - bool translateTouchEvent(QWindow *widget, HWND hwnd, - QtWindows::WindowsEventType t, MSG msg, - LRESULT *result); - bool translateGestureEvent(QWindow *window, HWND hwnd, - QtWindows::WindowsEventType, - MSG msg, LRESULT *); - bool translateScrollEvent(QWindow *window, HWND hwnd, - MSG msg, LRESULT *result); - - static inline Qt::MouseButtons keyStateToMouseButtons(WPARAM); - static inline Qt::KeyboardModifiers keyStateToModifiers(int); - static inline int mouseButtonsToKeyState(Qt::MouseButtons); - - static Qt::MouseButtons queryMouseButtons(); - QWindow *windowUnderMouse() const { return m_windowUnderMouse.data(); } - void clearWindowUnderMouse() { m_windowUnderMouse = nullptr; } - void clearEvents(); - - static const QPointingDevice *primaryMouse(); - -private: - inline bool translateMouseWheelEvent(QWindow *window, HWND hwnd, - MSG msg, LRESULT *result); - - QPointer<QWindow> m_windowUnderMouse; - QPointer<QWindow> m_trackedWindow; - QHash<DWORD, int> m_touchInputIDToTouchPointID; - QHash<int, QPointF> m_lastTouchPositions; - QPointingDevicePtr m_touchDevice; - bool m_leftButtonDown = false; - QWindow *m_previousCaptureWindow = nullptr; - QEvent::Type m_lastEventType = QEvent::None; - Qt::MouseButton m_lastEventButton = Qt::NoButton; -}; - -Qt::MouseButtons QWindowsMouseHandler::keyStateToMouseButtons(WPARAM wParam) -{ - Qt::MouseButtons mb(Qt::NoButton); - if (wParam & MK_LBUTTON) - mb |= Qt::LeftButton; - if (wParam & MK_MBUTTON) - mb |= Qt::MiddleButton; - if (wParam & MK_RBUTTON) - mb |= Qt::RightButton; - if (wParam & MK_XBUTTON1) - mb |= Qt::XButton1; - if (wParam & MK_XBUTTON2) - mb |= Qt::XButton2; - return mb; -} - -Qt::KeyboardModifiers QWindowsMouseHandler::keyStateToModifiers(int wParam) -{ - Qt::KeyboardModifiers mods(Qt::NoModifier); - if (wParam & MK_CONTROL) - mods |= Qt::ControlModifier; - if (wParam & MK_SHIFT) - mods |= Qt::ShiftModifier; - if (GetKeyState(VK_MENU) < 0) - mods |= Qt::AltModifier; - return mods; -} - -int QWindowsMouseHandler::mouseButtonsToKeyState(Qt::MouseButtons mb) -{ - int result = 0; - if (mb & Qt::LeftButton) - result |= MK_LBUTTON; - if (mb & Qt::MiddleButton) - result |= MK_MBUTTON; - if (mb & Qt::RightButton) - result |= MK_RBUTTON; - if (mb & Qt::XButton1) - result |= MK_XBUTTON1; - if (mb & Qt::XButton2) - result |= MK_XBUTTON2; - return result; -} - -QT_END_NAMESPACE - -#endif // QWINDOWSMOUSEHANDLER_H diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp index 6a790bcc1b..38d1cdd738 100644 --- a/src/plugins/platforms/windows/qwindowsopengltester.cpp +++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp @@ -65,7 +65,9 @@ private: QDirect3D9Handle::QDirect3D9Handle() { +#ifndef QT_NO_OPENGL m_direct3D9 = Direct3DCreate9(D3D_SDK_VERSION); +#endif } QDirect3D9Handle::~QDirect3D9Handle() diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index 71c7217671..7995716444 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -4,7 +4,6 @@ #include <QtCore/qt_windows.h> #include "qwindowspointerhandler.h" -#include "qwindowsmousehandler.h" #if QT_CONFIG(tabletevent) # include "qwindowstabletsupport.h" #endif @@ -39,6 +38,14 @@ enum { qint64 QWindowsPointerHandler::m_nextInputDeviceId = 1; +const QPointingDevice *primaryMouse() +{ + static QPointer<const QPointingDevice> result; + if (!result) + result = QPointingDevice::primaryPointingDevice(); + return result; +} + QWindowsPointerHandler::~QWindowsPointerHandler() { } @@ -215,7 +222,7 @@ static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState) return result; } -static Qt::MouseButtons queryMouseButtons() +Qt::MouseButtons QWindowsPointerHandler::queryMouseButtons() { Qt::MouseButtons result = Qt::NoButton; const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON); @@ -785,7 +792,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window, } Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - const QPointingDevice *device = QWindowsMouseHandler::primaryMouse(); + const QPointingDevice *device = primaryMouse(); // Following the logic of the old mouse handler, only events synthesized // for touch screen are marked as such. On some systems, using the bit 7 of diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.h b/src/plugins/platforms/windows/qwindowspointerhandler.h index b64a8c146a..1827dd9df0 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.h +++ b/src/plugins/platforms/windows/qwindowspointerhandler.h @@ -38,12 +38,15 @@ public: void clearWindowUnderMouse() { m_windowUnderPointer = nullptr; } void clearEvents(); + static Qt::MouseButtons queryMouseButtons(); + private: bool translateTouchEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vTouchInfo, unsigned int count); bool translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPenInfo); bool translateMouseWheelEvent(QWindow *window, QWindow *currentWindowUnderPointer, MSG msg, QPoint globalPos, Qt::KeyboardModifiers keyModifiers); void handleCaptureRelease(QWindow *window, QWindow *currentWindowUnderPointer, HWND hwnd, QEvent::Type eventType, Qt::MouseButtons mouseButtons); void handleEnterLeave(QWindow *window, QWindow *currentWindowUnderPointer, QPoint globalPos); + #if QT_CONFIG(tabletevent) QPointingDevicePtr findTabletDevice(QPointingDevice::PointerType pointerType) const; #endif diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index a50f9fd4b0..1f22fb4f60 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -698,11 +698,15 @@ void QWindowsScreenManager::initialize() handleScreenChanges(); } -QWindowsScreenManager::~QWindowsScreenManager() +void QWindowsScreenManager::destroyWindow() { + qCDebug(lcQpaScreen) << "Destroying display change observer" << m_displayChangeObserver; DestroyWindow(m_displayChangeObserver); + m_displayChangeObserver = nullptr; } +QWindowsScreenManager::~QWindowsScreenManager() = default; + bool QWindowsScreenManager::isSingleScreen() { return QWindowsContext::instance()->screenManager().screens().size() < 2; diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index 0467ab2a0c..ea6a29efe3 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -105,6 +105,7 @@ public: QWindowsScreenManager(); void initialize(); + void destroyWindow(); ~QWindowsScreenManager(); void clearScreens(); diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 96ae6197b4..a89fb1e5bd 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -33,7 +33,6 @@ public: Qt::ColorScheme colorScheme() const override; void requestColorScheme(Qt::ColorScheme scheme) override; - Qt::ColorScheme requestedColorScheme() const { return s_colorSchemeOverride; } static void handleSettingsChanged(); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index ee0b88ba54..4b7ce0a979 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -855,12 +855,6 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) return false; - // the application explicitly overrides the color scheme - if (const auto requestedColorScheme = QWindowsTheme::instance()->requestedColorScheme(); - requestedColorScheme != Qt::ColorScheme::Unknown) { - return requestedColorScheme == Qt::ColorScheme::Dark; - } - // if the application supports a dark border, and the palette is dark (window background color // is darker than the text), then turn dark-border support on, otherwise use a light border. auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w)); @@ -2488,6 +2482,11 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state) GetWindowPlacement(m_data.hwnd, &windowPlacement); const RECT geometry = RECTfromQRect(m_data.restoreGeometry); windowPlacement.rcNormalPosition = geometry; + // Even if the window is hidden, windowPlacement's showCmd is not SW_HIDE, so change it + // manually to avoid unhiding a hidden window with the subsequent call to + // SetWindowPlacement(). + if (!isVisible()) + windowPlacement.showCmd = SW_HIDE; SetWindowPlacement(m_data.hwnd, &windowPlacement); } // QTBUG-17548: We send expose events when receiving WM_Paint, but for diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 024711e7f3..b3cddc11d8 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -347,6 +347,8 @@ public: int savedDpi() const { return m_savedDpi; } qreal dpiRelativeScale(const UINT dpi) const; + bool isFrameless() const { return m_data.flags.testFlag(Qt::FramelessWindowHint); } + private: inline void show_sys() const; inline QWindowsWindowData setWindowFlags_sys(Qt::WindowFlags wt, unsigned flags = 0) const; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp index 1abb412ccd..5892493281 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp @@ -127,6 +127,9 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event return; switch (event->type()) { + case QAccessible::Announcement: + QWindowsUiaMainProvider::raiseNotification(static_cast<QAccessibleAnnouncementEvent *>(event)); + break; case QAccessible::Focus: QWindowsUiaMainProvider::notifyFocusChange(event); break; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 95ddbcced6..07cd522746 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -23,6 +23,7 @@ #include "qwindowsuiaprovidercache.h" #include <QtCore/qloggingcategory.h> +#include <QtGui/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> #include <QtGui/qguiapplication.h> #include <QtGui/qwindow.h> @@ -204,6 +205,24 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event) } } +void QWindowsUiaMainProvider::raiseNotification(QAccessibleAnnouncementEvent *event) +{ + if (QAccessibleInterface *accessible = event->accessibleInterface()) { + if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { + BSTR message = bStrFromQString(event->message()); + QAccessible::AnnouncementPriority prio = event->priority(); + NotificationProcessing processing = (prio == QAccessible::AnnouncementPriority::Assertive) + ? NotificationProcessing_ImportantAll + : NotificationProcessing_All; + BSTR activityId = bStrFromQString(QString::fromLatin1("")); + UiaRaiseNotificationEvent(provider, NotificationKind_Other, processing, message, activityId); + + ::SysFreeString(message); + ::SysFreeString(activityId); + } + } +} + HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface) { HRESULT result = QComObject::QueryInterface(iid, iface); @@ -370,6 +389,93 @@ void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* pRetVal->parray = elements; } +void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface(); + if (!attributesIface) + return; + + QString ariaString; + const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys(); + for (qsizetype i = 0; i < attrKeys.size(); ++i) { + if (i != 0) + ariaString += QStringLiteral(";"); + const QAccessible::Attribute key = attrKeys.at(i); + const QVariant value = attributesIface->attributeValue(key); + // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/ + switch (key) { + case QAccessible::Attribute::Custom: + { + // forward custom attributes as-is + Q_ASSERT((value.canConvert<QHash<QString, QString>>())); + const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>(); + for (auto [name, val] : attrMap.asKeyValueRange()) { + if (name != *attrMap.keyBegin()) + ariaString += QStringLiteral(";"); + ariaString += name + QStringLiteral("=") + val; + } + break; + } + case QAccessible::Attribute::Level: + Q_ASSERT(value.canConvert<int>()); + ariaString += QStringLiteral("level=") + QString::number(value.toInt()); + break; + default: + break; + } + } + + setVariantString(ariaString, pRetVal); +} + +void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal) +{ + Q_ASSERT(accessible); + + QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface(); + if (!attributesIface) + return; + + // currently, only heading styles are implemented here + if (accessible->role() != QAccessible::Role::Heading) + return; + + const QVariant levelVariant = attributesIface->attributeValue(QAccessible::Attribute::Level); + if (!levelVariant.isValid()) + return; + + Q_ASSERT(levelVariant.canConvert<int>()); + // UIA only has styles for heading levels 1-9 + const int level = levelVariant.toInt(); + if (level < 1 || level > 9) + return; + + const int styleId = styleIdForHeadingLevel(level); + setVariantI4(styleId, pRetVal); +} + +int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel) +{ + // only heading levels 1-9 have a corresponding UIA style ID + Q_ASSERT(headingLevel > 0 && headingLevel <= 9); + + static constexpr int styles[] = { + StyleId_Heading1, + StyleId_Heading2, + StyleId_Heading3, + StyleId_Heading4, + StyleId_Heading5, + StyleId_Heading6, + StyleId_Heading7, + StyleId_Heading8, + StyleId_Heading9, + }; + + return styles[headingLevel - 1]; +} + HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp; @@ -393,9 +499,12 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR // Accelerator key. setVariantString(accessible->text(QAccessible::Accelerator), pRetVal); break; + case UIA_AriaPropertiesPropertyId: + setAriaProperties(accessible, pRetVal); + break; case UIA_AutomationIdPropertyId: // Automation ID, which can be used by tools to select a specific control in the UI. - setVariantString(automationIdForAccessible(accessible), pRetVal); + setVariantString(QAccessibleBridgeUtils::accessibleId(accessible), pRetVal); break; case UIA_ClassNamePropertyId: // Class name. @@ -493,31 +602,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR setVariantString(name, pRetVal); break; } + case UIA_StyleIdAttributeId: + setStyle(accessible, pRetVal); + break; default: break; } return S_OK; } -// Generates an ID based on the name of the controls and their parents. -QString QWindowsUiaMainProvider::automationIdForAccessible(const QAccessibleInterface *accessible) -{ - QString result; - if (accessible) { - QObject *obj = accessible->object(); - while (obj) { - QString name = obj->objectName(); - if (name.isEmpty()) - return result; - if (!result.isEmpty()) - result.prepend(u'.'); - result.prepend(name); - obj = obj->parent(); - } - } - return result; -} - HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal) { qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h index 99db0ed318..8ea343e425 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h @@ -34,6 +34,7 @@ public: static void notifyNameChange(QAccessibleEvent *event); static void notifySelectionChange(QAccessibleEvent *event); static void notifyTextChange(QAccessibleEvent *event); + static void raiseNotification(QAccessibleAnnouncementEvent *event); // IUnknown HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override; @@ -58,8 +59,11 @@ public: HRESULT STDMETHODCALLTYPE GetFocus(IRawElementProviderFragment **pRetVal) override; private: - QString automationIdForAccessible(const QAccessibleInterface *accessible); static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal); + static void setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal); + static void setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal); + /** Returns the UIA style ID for a heading level from 1 to 9. */ + static int styleIdForHeadingLevel(int headingLevel); static QMutex m_mutex; }; diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp index 1593a07202..6954a881d0 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp @@ -69,6 +69,14 @@ HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *pProvider, EVE return func.invoke(pProvider, id); } +HRESULT WINAPI UiaRaiseNotificationEvent( + IRawElementProviderSimple *pProvider, NotificationKind notificationKind, + NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId) +{ + static auto func = winapi_func("uiautomationcore", FN(UiaRaiseNotificationEvent)); + return func.invoke(pProvider, notificationKind, notificationProcessing, displayString, activityId); +} + #endif // defined(__MINGW32__) || defined(__MINGW64__) #endif // QT_CONFIG(accessibility) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h index a192b9b0fb..4eb37bafa0 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h @@ -14,8 +14,19 @@ #define UIA_SelectionPattern2Id 10034 #define UIA_IsReadOnlyAttributeId 40015 #define UIA_StrikethroughStyleAttributeId 40026 +#define UIA_StyleIdAttributeId 40034 #define UIA_CaretPositionAttributeId 40038 +#define StyleId_Heading1 70001 +#define StyleId_Heading2 70002 +#define StyleId_Heading3 70003 +#define StyleId_Heading4 70004 +#define StyleId_Heading5 70005 +#define StyleId_Heading6 70006 +#define StyleId_Heading7 70007 +#define StyleId_Heading8 70008 +#define StyleId_Heading9 70009 + enum CaretPosition { CaretPosition_Unknown = 0, CaretPosition_EndOfLine = 1, diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt index e8fb442dd4..4fa6c1e978 100644 --- a/src/plugins/platforms/xcb/CMakeLists.txt +++ b/src/plugins/platforms/xcb/CMakeLists.txt @@ -146,6 +146,10 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_F if(QT_FEATURE_system_xcb_xinput) qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT) else() + qt_internal_extend_target(XcbQpaPrivate + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/xcb + ) set(xinput_source "${PROJECT_SOURCE_DIR}/src/3rdparty/xcb/libxcb/xinput.c") set_source_files_properties( "${xinput_source}" @@ -164,7 +168,7 @@ endif() qt_internal_add_plugin(QXcbIntegrationPlugin OUTPUT_NAME qxcb PLUGIN_TYPE platforms - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES xcb + DEFAULT_IF "xcb" IN_LIST QT_QPA_PLATFORMS SOURCES qxcbmain.cpp DEFINES diff --git a/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h b/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h index c6492f02ae..8e2b3aed22 100644 --- a/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h +++ b/src/plugins/platforms/xcb/gl_integrations/qxcbglintegration.h @@ -14,7 +14,7 @@ class QPlatformOffscreenSurface; class QOffscreenSurface; class QXcbNativeInterfaceHandler; -Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcQpaGl, Q_XCB_EXPORT) +QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcQpaGl, Q_XCB_EXPORT) class Q_XCB_EXPORT QXcbGlIntegration { diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp index 40e2f47354..77157595b8 100644 --- a/src/plugins/platforms/xcb/qxcbclipboard.cpp +++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp @@ -61,7 +61,7 @@ protected: // get the list of targets from the current clipboard owner - we do this // once so that multiple calls to this function don't require multiple // server round trips... - that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::AtomTARGETS)); + that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::AtomTARGETS)).value_or(QByteArray()); if (format_atoms.size() > 0) { const xcb_atom_t *targets = (const xcb_atom_t *) format_atoms.data(); @@ -91,7 +91,7 @@ protected: { auto requestedType = type; if (fmt.isEmpty() || isEmpty()) - return QByteArray(); + return QVariant(); (void)formats(); // trigger update of format list @@ -108,7 +108,11 @@ protected: if (fmtatom == 0) return QVariant(); - return mimeConvertToFormat(m_clipboard->connection(), fmtatom, m_clipboard->getDataInFormat(modeAtom, fmtatom), fmt, requestedType, hasUtf8); + const std::optional<QByteArray> result = m_clipboard->getDataInFormat(modeAtom, fmtatom); + if (!result.has_value()) + return QVariant(); + + return mimeConvertToFormat(m_clipboard->connection(), fmtatom, result.value(), fmt, requestedType, hasUtf8); } private: @@ -776,7 +780,7 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i return nullptr; } -QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm) +std::optional<QByteArray> QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm) { QByteArray buf; QByteArray tmp_buf; @@ -841,17 +845,16 @@ QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb // could consider next request to be still part of this timed out request setRequestor(0); - return QByteArray(); + return std::nullopt; } -QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom) +std::optional<QByteArray> QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom) { return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::Atom_QT_SELECTION)); } -QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) +std::optional<QByteArray> QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time) { - QByteArray buf; xcb_window_t win = requestor(); if (time == 0) time = connection()->time(); @@ -866,17 +869,19 @@ QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, free(ge); if (no_selection) - return buf; + return std::nullopt; xcb_atom_t type; + QByteArray buf; if (clipboardReadProperty(win, property, true, &buf, nullptr, &type, nullptr)) { if (type == atom(QXcbAtom::AtomINCR)) { int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0; - buf = clipboardReadIncrementalProperty(win, property, nbytes, false); + return clipboardReadIncrementalProperty(win, property, nbytes, false); } + return buf; } - return buf; + return std::nullopt; } #endif // QT_NO_CLIPBOARD diff --git a/src/plugins/platforms/xcb/qxcbclipboard.h b/src/plugins/platforms/xcb/qxcbclipboard.h index 79c48a0af5..134daf4be7 100644 --- a/src/plugins/platforms/xcb/qxcbclipboard.h +++ b/src/plugins/platforms/xcb/qxcbclipboard.h @@ -67,13 +67,13 @@ public: void handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event); bool clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format); - QByteArray clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm); + std::optional<QByteArray> clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm); - QByteArray getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom); + std::optional<QByteArray> getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtatom); bool handlePropertyNotify(const xcb_generic_event_t *event); - QByteArray getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t = 0); + std::optional<QByteArray> getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t = 0); int increment() const { return m_maxPropertyRequestDataBytes; } int clipboardTimeout() const { return clipboard_timeout; } diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp index 6e5dd770b9..71335444e9 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -17,7 +17,6 @@ #include <qguiapplication.h> #include <qrect.h> #include <qpainter.h> -#include <qtimer.h> #include <qpa/qwindowsysteminterface.h> @@ -1306,32 +1305,33 @@ QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QMetaType reque QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType requestedType) const { - QByteArray result; - QXcbConnection *c = drag->connection(); QXcbWindow *xcb_window = c->platformWindowFromId(drag->xdnd_dragsource); if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) { QMimeData *data = drag->currentDrag()->mimeData(); if (data->hasFormat(QLatin1StringView(format))) - result = data->data(QLatin1StringView(format)); - return result; + return data->data(QLatin1StringView(format)); + return QVariant(); } QList<xcb_atom_t> atoms = drag->xdnd_types; bool hasUtf8 = false; xcb_atom_t a = mimeAtomForFormat(c, QLatin1StringView(format), requestedType, atoms, &hasUtf8); if (a == XCB_NONE) - return result; + return QVariant(); #ifndef QT_NO_CLIPBOARD if (c->selectionOwner(c->atom(QXcbAtom::AtomXdndSelection)) == XCB_NONE) - return result; // should never happen? + return QVariant(); // should never happen? xcb_atom_t xdnd_selection = c->atom(QXcbAtom::AtomXdndSelection); - result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime()); + const std::optional<QByteArray> result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime()); + if (!result.has_value()) + return QVariant(); + return mimeConvertToFormat(c, a, result.value(), QLatin1StringView(format), requestedType, hasUtf8); +#else + return QVariant(); #endif - - return mimeConvertToFormat(c, a, result, QLatin1StringView(format), requestedType, hasUtf8); } bool QXcbDropData::hasFormat_sys(const QString &format) const diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index 06f4b66edb..0ce337726e 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -23,6 +23,7 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t : QXcbObject(connection) , m_screen(screen) , m_number(number) + , m_xSettings(new QXcbXSettings(this)) { const QByteArray cmAtomName = "_NET_WM_CM_S" + QByteArray::number(m_number); m_net_wm_cm_atom = connection->internAtom(cmAtomName.constData()); @@ -129,10 +130,6 @@ void QXcbVirtualDesktop::setPrimaryScreen(QPlatformScreen *s) QXcbXSettings *QXcbVirtualDesktop::xSettings() const { - if (!m_xSettings) { - QXcbVirtualDesktop *self = const_cast<QXcbVirtualDesktop *>(this); - self->m_xSettings = new QXcbXSettings(self); - } return m_xSettings; } diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf index 10bc1fd407..6d83b084f7 100644 --- a/src/plugins/sqldrivers/.cmake.conf +++ b/src/plugins/sqldrivers/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.8.0") +set(QT_REPO_MODULE_VERSION "6.9.0") diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp index f6eed5e227..44443aaacb 100644 --- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp +++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp @@ -3,11 +3,13 @@ #include "qsql_ibase_p.h" #include <QtCore/qcoreapplication.h> +#include <QtCore/qendian.h> #include <QtCore/qdatetime.h> #include <QtCore/qtimezone.h> #include <QtCore/qdeadlinetimer.h> #include <QtCore/qdebug.h> #include <QtCore/qlist.h> +#include <QtCore/private/qlocale_tools_p.h> #include <QtCore/qloggingcategory.h> #include <QtCore/qmap.h> #include <QtCore/qmutex.h> @@ -41,6 +43,10 @@ using namespace Qt::StringLiterals; #define blr_boolean_dtype blr_bool #endif +#if (defined(QT_SUPPORTS_INT128) || defined(QT_USE_MSVC_INT128)) && (FB_API_VER >= 40) +#define IBASE_INT128_SUPPORTED +#endif + constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2; #if (FB_API_VER >= 40) @@ -91,7 +97,9 @@ static void enlargeDA(XSQLDA *&sqlda, int n) static void initDA(XSQLDA *sqlda) { for (int i = 0; i < sqlda->sqld; ++i) { - switch (sqlda->sqlvar[i].sqltype & ~1) { + XSQLVAR &sqlvar = sqlda->sqlvar[i]; + const auto sqltype = (sqlvar.sqltype & ~1); + switch (sqltype) { case SQL_INT64: case SQL_LONG: case SQL_SHORT: @@ -100,32 +108,35 @@ static void initDA(XSQLDA *sqlda) case SQL_TIMESTAMP: #if (FB_API_VER >= 40) case SQL_TIMESTAMP_TZ: +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: +#endif #endif case SQL_TYPE_TIME: case SQL_TYPE_DATE: case SQL_TEXT: case SQL_BLOB: case SQL_BOOLEAN: - sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen]; + sqlvar.sqldata = new char[sqlvar.sqllen]; break; case SQL_ARRAY: - sqlda->sqlvar[i].sqldata = new char[sizeof(ISC_QUAD)]; - memset(sqlda->sqlvar[i].sqldata, 0, sizeof(ISC_QUAD)); + sqlvar.sqldata = new char[sizeof(ISC_QUAD)]; + memset(sqlvar.sqldata, 0, sizeof(ISC_QUAD)); break; case SQL_VARYING: - sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen + sizeof(short)]; + sqlvar.sqldata = new char[sqlvar.sqllen + sizeof(short)]; break; default: // not supported - do not bind. - sqlda->sqlvar[i].sqldata = 0; - qCWarning(lcIbase, "initDA: unknown sqltype: %d", sqlda->sqlvar[i].sqltype & ~1); + sqlvar.sqldata = 0; + qCWarning(lcIbase, "initDA: unknown sqltype: %d", sqltype); break; } - if (sqlda->sqlvar[i].sqltype & 1) { - sqlda->sqlvar[i].sqlind = new short[1]; - *(sqlda->sqlvar[i].sqlind) = 0; + if (sqlvar.sqltype & 1) { + sqlvar.sqlind = new short[1]; + *(sqlvar.sqlind) = 0; } else { - sqlda->sqlvar[i].sqlind = 0; + sqlvar.sqlind = 0; } } } @@ -190,6 +201,10 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale) return (hasScale ? QMetaType::Double : QMetaType::Int); case SQL_INT64: return (hasScale ? QMetaType::Double : QMetaType::LongLong); +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: + return (hasScale ? QMetaType::Double : QMetaType::LongLong); +#endif case SQL_FLOAT: case SQL_DOUBLE: return QMetaType::Double; @@ -215,42 +230,35 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale) return QMetaType::UnknownType; } -static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) +// InterBase and FireBird use the modified Julian Date with 1858-11-17 as day 0. +static constexpr auto s_ibaseBaseDate = QDate::fromJulianDay(2400001); + +static inline ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) { - static const QTime midnight(0, 0, 0, 0); - static const QDate basedate(1858, 11, 17); ISC_TIMESTAMP ts; - ts.timestamp_time = midnight.msecsTo(dt.time()) * 10; - ts.timestamp_date = basedate.daysTo(dt.date()); + ts.timestamp_time = dt.time().msecsSinceStartOfDay() * 10; + ts.timestamp_date = s_ibaseBaseDate.daysTo(dt.date()); return ts; } -static QDateTime fromTimeStamp(char *buffer) +static inline QDateTime fromTimeStamp(const char *buffer) { - static const QDate bd(1858, 11, 17); - QTime t(0, 0); - QDate d; - // have to demangle the structure ourselves because isc_decode_time // strips the msecs - auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer); - t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10)); - d = bd.addDays(timebuf->timestamp_date); + auto timebuf = reinterpret_cast<const ISC_TIMESTAMP *>(buffer); + const QTime t = QTime::fromMSecsSinceStartOfDay(timebuf->timestamp_time / 10); + const QDate d = s_ibaseBaseDate.addDays(timebuf->timestamp_date); return QDateTime(d, t); } #if (FB_API_VER >= 40) -QDateTime fromTimeStampTz(char *buffer) +static inline QDateTime fromTimeStampTz(const char *buffer) { - static const QDate bd(1858, 11, 17); - QTime t(0, 0); - QDate d; - // have to demangle the structure ourselves because isc_decode_time // strips the msecs - auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer); - t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10)); - d = bd.addDays(timebuf->utc_timestamp.timestamp_date); + auto timebuf = reinterpret_cast<const ISC_TIMESTAMP_TZ *>(buffer); + const QTime t = QTime::fromMSecsSinceStartOfDay(timebuf->utc_timestamp.timestamp_time / 10); + const QDate d = s_ibaseBaseDate.addDays(timebuf->utc_timestamp.timestamp_date); quint16 fpTzID = timebuf->time_zone; QByteArray timeZoneName = qFbTzIdToIanaIdMap()->value(fpTzID); @@ -260,53 +268,40 @@ QDateTime fromTimeStampTz(char *buffer) return {}; } -ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt) +static inline ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt) { - static const QTime midnight(0, 0, 0, 0); - static const QDate basedate(1858, 11, 17); ISC_TIMESTAMP_TZ ts; - ts.utc_timestamp.timestamp_time = midnight.msecsTo(dt.time()) * 10; - ts.utc_timestamp.timestamp_date = basedate.daysTo(dt.date()); + ts.utc_timestamp.timestamp_time = dt.time().msecsSinceStartOfDay() * 10; + ts.utc_timestamp.timestamp_date = s_ibaseBaseDate.daysTo(dt.date()); ts.time_zone = qIanaIdToFbTzIdMap()->value(dt.timeZone().id().simplified(), 0); return ts; } #endif -static ISC_TIME toTime(QTime t) +static inline ISC_TIME toTime(QTime t) { - static const QTime midnight(0, 0, 0, 0); - return (ISC_TIME)midnight.msecsTo(t) * 10; + return t.msecsSinceStartOfDay() * 10; } -static QTime fromTime(char *buffer) +static inline QTime fromTime(const char *buffer) { - QTime t(0, 0); // have to demangle the structure ourselves because isc_decode_time // strips the msecs - t = t.addMSecs(int((*(ISC_TIME*)buffer) / 10)); - - return t; + const auto timebuf = reinterpret_cast<const ISC_TIME *>(buffer); + return QTime::fromMSecsSinceStartOfDay(*timebuf / 10); } -static ISC_DATE toDate(QDate t) +static inline ISC_DATE toDate(QDate t) { - static const QDate basedate(1858, 11, 17); - ISC_DATE date; - - date = basedate.daysTo(t); - return date; -} + return s_ibaseBaseDate.daysTo(t); + } -static QDate fromDate(char *buffer) +static inline QDate fromDate(const char *buffer) { - static const QDate bd(1858, 11, 17); - QDate d; - // have to demangle the structure ourselves because isc_decode_time // strips the msecs - d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date)); - - return d; + const auto tsbuf = reinterpret_cast<const ISC_TIMESTAMP *>(buffer); + return s_ibaseBaseDate.addDays(int(tsbuf->timestamp_date)); } struct QIBaseEventBuffer { @@ -347,21 +342,14 @@ public: QSqlQuery qry(q->createResult()); qry.setForwardOnly(true); qry.exec(QString("select * from RDB$TIME_ZONES"_L1)); - if (qry.lastError().type()) { - q->setLastError(QSqlError( - QCoreApplication::translate("QIBaseDriver", - "failed to query time zone mapping from system table"), - qry.lastError().databaseText(), - QSqlError::StatementError, - qry.lastError().nativeErrorCode())); - + if (qry.lastError().isValid()) { + qCInfo(lcIbase) << "Table 'RDB$TIME_ZONES' not found - not timezone support available"; return; } while (qry.next()) { - auto record = qry.record(); - quint16 fbTzId = record.value(0).value<quint16>(); - QByteArray ianaId = record.value(1).toByteArray().simplified(); + quint16 fbTzId = qry.value(0).value<quint16>(); + QByteArray ianaId = qry.value(1).toByteArray().simplified(); qFbTzIdToIanaIdMap()->insert(fbTzId, ianaId); qIanaIdToFbTzIdMap()->insert(ianaId, fbTzId); } @@ -408,6 +396,32 @@ protected: QSqlRecord record() const override; template<typename T> + static QString numberToHighPrecision(T val, int scale) + { + const bool negative = val < 0; + QString number; +#ifdef IBASE_INT128_SUPPORTED + if constexpr (std::is_same_v<qinternalint128, T>) { + number = negative ? qint128toBasicLatin(val * -1) + : qint128toBasicLatin(val); + } else +#endif + number = negative ? QString::number(qAbs(val)) + : QString::number(val); + auto len = number.size(); + scale *= -1; + if (scale >= len) { + number = QString(scale - len + 1, u'0') + number; + len = number.size(); + } + const auto sepPos = len - scale; + number = number.left(sepPos) + u'.' + number.mid(sepPos); + if (negative) + number = u'-' + number; + return number; + } + + template<typename T> QVariant applyScale(T val, int scale) const { if (scale >= 0) @@ -420,28 +434,36 @@ protected: return QVariant(qint64(val * pow(10.0, scale))); case QSql::LowPrecisionDouble: return QVariant(double(val * pow(10.0, scale))); - case QSql::HighPrecision: { - const bool negative = val < 0; - QString number; - if constexpr (std::is_signed_v<T> || negative) - number = QString::number(qAbs(val)); - else - number = QString::number(val); - auto len = number.size(); - scale *= -1; - if (scale >= len) { - number = QString(scale - len + 1, u'0') + number; - len = number.size(); - } - const auto sepPos = len - scale; - number = number.left(sepPos) + u'.' + number.mid(sepPos); - if (negative) - number = u'-' + number; - return QVariant(number); - } + case QSql::HighPrecision: + return QVariant(numberToHighPrecision(val, scale)); } return QVariant(val); } + + template<typename T> + void setWithScale(const QVariant &val, int scale, char *data) + { + auto ptr = reinterpret_cast<T *>(data); + if (scale < 0) { + double d = floor(0.5 + val.toDouble() * pow(10.0, scale * -1)); +#ifdef IBASE_INT128_SUPPORTED + if constexpr (std::is_same_v<qinternalint128, T>) { + quint64 lower = quint64(d); + quint64 tmp = quint64(std::numeric_limits<quint32>::max()) + 1; + d /= tmp; + d /= tmp; + quint64 higher = quint64(d); + qinternalint128 result = higher; + result <<= 64; + result += lower; + *ptr = static_cast<T>(result); + } else +#endif + *ptr = static_cast<T>(d); + } + else + *ptr = val.value<T>(); + } }; class QIBaseResultPrivate: public QSqlCachedResultPrivate @@ -487,6 +509,7 @@ public: XSQLDA *sqlda; // output sqlda XSQLDA *inda; // input parameters int queryType; + mutable QSqlRecord cachedRecord; }; @@ -518,6 +541,7 @@ void QIBaseResultPrivate::cleanup() delDA(inda); queryType = -1; + cachedRecord.clear(); q->cleanup(); } @@ -551,9 +575,8 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) return QVariant(); unsigned short len = 0; - QByteArray ba; - int chunkSize = QIBaseChunkSize; - ba.resize(chunkSize); + constexpr auto chunkSize = QIBaseChunkSize; + QByteArray ba(chunkSize, Qt::Uninitialized); qsizetype read = 0; while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) { read += len; @@ -576,9 +599,10 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId) } template<typename T> -static QList<QVariant> toList(char** buf, int count) +static QList<QVariant> toList(const char **buf, int count) { QList<QVariant> res; + res.reserve(count); for (int i = 0; i < count; ++i) { res.append(*(T*)(*buf)); *buf += sizeof(T); @@ -586,8 +610,8 @@ static QList<QVariant> toList(char** buf, int count) return res; } -static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim, - short* numElements, ISC_ARRAY_DESC *arrayDesc) +static const char *readArrayBuffer(QList<QVariant>& list, const char *buffer, short curDim, + const short *numElements, ISC_ARRAY_DESC *arrayDesc) { const short dim = arrayDesc->array_desc_dimensions - 1; const unsigned char dataType = arrayDesc->array_desc_dtype; @@ -675,8 +699,9 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) if (!arr) return list; - QByteArray relname(sqlda->sqlvar[pos].relname, sqlda->sqlvar[pos].relname_length); - QByteArray sqlname(sqlda->sqlvar[pos].sqlname, sqlda->sqlvar[pos].sqlname_length); + const XSQLVAR &sqlvar = sqlda->sqlvar[pos]; + const auto relname = QByteArray::fromRawData(sqlvar.relname, sqlvar.relname_length); + const auto sqlname = QByteArray::fromRawData(sqlvar.sqlname, sqlvar.sqlname_length); isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), @@ -684,19 +709,18 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) return list; - int arraySize = 1, subArraySize; + int arraySize = 1; short dimensions = desc.array_desc_dimensions; QVarLengthArray<short> numElements(dimensions); for(int i = 0; i < dimensions; ++i) { - subArraySize = (desc.array_desc_bounds[i].array_bound_upper - - desc.array_desc_bounds[i].array_bound_lower + 1); + short subArraySize = (desc.array_desc_bounds[i].array_bound_upper - + desc.array_desc_bounds[i].array_bound_lower + 1); numElements[i] = subArraySize; arraySize = subArraySize * arraySize; } ISC_LONG bufLen; - QByteArray ba; /* varying arrayelements are stored with 2 trailing null bytes indicating the length of the string */ @@ -708,14 +732,13 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr) bufLen = desc.array_desc_length * arraySize; } - - ba.resize(int(bufLen)); + QByteArray ba(bufLen, Qt::Uninitialized); isc_array_get_slice(status, &ibase, &trans, arr, &desc, ba.data(), &bufLen); if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get array data"), QSqlError::StatementError)) return list; - readArrayBuffer(list, ba.data(), 0, numElements.data(), &desc); + readArrayBuffer(list, ba.constData(), 0, numElements.constData(), &desc); return QVariant(list); } @@ -752,16 +775,16 @@ static char* qFillBufferWithString(char *buffer, const QString& string, if (str.length() < buflen) buflen = str.length(); if (array) { // interbase stores varying arrayelements different than normal varying elements - memcpy(buffer, str.data(), buflen); + memcpy(buffer, str.constData(), buflen); memset(buffer + buflen, 0, tmpBuflen - buflen); } else { *(short*)buffer = buflen; // first two bytes is the length - memcpy(buffer + sizeof(short), str.data(), buflen); + memcpy(buffer + sizeof(short), str.constData(), buflen); } buffer += tmpBuflen; } else { str = str.leftJustified(buflen, ' ', true); - memcpy(buffer, str.data(), buflen); + memcpy(buffer, str.constData(), buflen); buffer += buflen; } return buffer; @@ -778,9 +801,10 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, bounds[curDim].array_bound_lower + 1); if (list.size() != elements) { // size mismatch - error = "Expected size: %1. Supplied size: %2"_L1; - error = "Array size mismatch. Fieldname: %1 "_L1 - + error.arg(elements).arg(list.size()); + error = QCoreApplication::translate( + "QIBaseResult", + "Array size mismatch. Field name: %3, expected size: %1. Supplied size: %2") + .arg(elements).arg(list.size()); return 0; } @@ -788,7 +812,9 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list, for (const auto &elem : list) { if (elem.typeId() != QMetaType::QVariantList) { // dimensions mismatch - error = "Array dimensons mismatch. Fieldname: %1"_L1; + error = QCoreApplication::translate( + "QIBaseResult", + "Array dimensions mismatch. Field name: %1"); return 0; } @@ -869,11 +895,12 @@ bool QIBaseResultPrivate::writeArray(qsizetype column, const QList<QVariant> &li { Q_Q(QIBaseResult); QString error; - ISC_QUAD *arrayId = (ISC_QUAD*) inda->sqlvar[column].sqldata; ISC_ARRAY_DESC desc; - QByteArray relname(inda->sqlvar[column].relname, inda->sqlvar[column].relname_length); - QByteArray sqlname(inda->sqlvar[column].sqlname, inda->sqlvar[column].sqlname_length); + XSQLVAR &sqlvar = inda->sqlvar[column]; + ISC_QUAD *arrayId = (ISC_QUAD*) sqlvar.sqldata; + const auto relname = QByteArray::fromRawData(sqlvar.relname, sqlvar.relname_length); + const auto sqlname = QByteArray::fromRawData(sqlvar.sqlname, sqlvar.sqlname_length); isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc); if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"), @@ -897,20 +924,21 @@ bool QIBaseResultPrivate::writeArray(qsizetype column, const QList<QVariant> &li desc.array_desc_length += 2; bufLen = desc.array_desc_length * arraySize; - QByteArray ba; - ba.resize(int(bufLen)); + QByteArray ba(bufLen, Qt::Uninitialized); if (list.size() > arraySize) { - error = "Array size mismatch: size of %1 is %2, size of provided list is %3"_L1; + error = QCoreApplication::translate( + "QIBaseResult", + "Array size mismatch: size of %1 is %2, size of provided list is %3"); error = error.arg(QLatin1StringView(sqlname)).arg(arraySize).arg(list.size()); - q->setLastError(QSqlError(error, ""_L1, QSqlError::StatementError)); + q->setLastError(QSqlError(error, {}, QSqlError::StatementError)); return false; } if (!createArrayBuffer(ba.data(), list, - qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0), + qIBaseTypeName(desc.array_desc_dtype, sqlvar.sqlscale < 0), 0, &desc, error)) { - q->setLastError(QSqlError(error.arg(QLatin1StringView(sqlname)), ""_L1, + q->setLastError(QSqlError(error.arg(QLatin1StringView(sqlname)), {}, QSqlError::StatementError)); return false; } @@ -1078,79 +1106,73 @@ bool QIBaseResult::exec() return false; } for (qsizetype para = 0; para < values.count(); ++para) { - if (!d->inda->sqlvar[para].sqldata) + const XSQLVAR &sqlvar = d->inda->sqlvar[para]; + if (!sqlvar.sqldata) // skip unknown datatypes continue; const QVariant &val = values[para]; - if (d->inda->sqlvar[para].sqltype & 1) { + if (sqlvar.sqltype & 1) { if (QSqlResultPrivate::isVariantNull(val)) { // set null indicator - *(d->inda->sqlvar[para].sqlind) = -1; + *(sqlvar.sqlind) = -1; // and set the value to 0, otherwise it would count as empty string. // it seems to be working with just setting sqlind to -1 - //*((char*)d->inda->sqlvar[para].sqldata) = 0; + //*((char*)sqlvar.sqldata) = 0; continue; } // a value of 0 means non-null. - *(d->inda->sqlvar[para].sqlind) = 0; + *(sqlvar.sqlind) = 0; } else { if (QSqlResultPrivate::isVariantNull(val)) { qCWarning(lcIbase) << "QIBaseResult::exec: Null value replaced by default (zero)"_L1 - << "value for type of column"_L1 << d->inda->sqlvar[para].ownname + << "value for type of column"_L1 << sqlvar.ownname << ", which is not nullable."_L1; } } - switch(d->inda->sqlvar[para].sqltype & ~1) { + const auto sqltype = sqlvar.sqltype & ~1; + switch (sqltype) { case SQL_INT64: - if (d->inda->sqlvar[para].sqlscale < 0) - *((qint64*)d->inda->sqlvar[para].sqldata) = - (qint64)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); - else - *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); + setWithScale<qint64>(val, sqlvar.sqlscale, sqlvar.sqldata); + break; +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: + setWithScale<qinternalint128>(val, sqlvar.sqlscale, + sqlvar.sqldata); break; +#endif case SQL_LONG: - if (d->inda->sqlvar[para].sqllen == 4) { - if (d->inda->sqlvar[para].sqlscale < 0) - *((qint32*)d->inda->sqlvar[para].sqldata) = - (qint32)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); - else - *((qint32*)d->inda->sqlvar[para].sqldata) = (qint32)val.toInt(); - } else { - *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong(); - } + if (sqlvar.sqllen == 4) + setWithScale<qint32>(val, sqlvar.sqlscale, sqlvar.sqldata); + else + setWithScale<qint64>(val, 0, sqlvar.sqldata); break; case SQL_SHORT: - if (d->inda->sqlvar[para].sqlscale < 0) - *((short*)d->inda->sqlvar[para].sqldata) = - (short)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1)); - else - *((short*)d->inda->sqlvar[para].sqldata) = (short)val.toInt(); + setWithScale<qint16>(val, sqlvar.sqlscale, sqlvar.sqldata); break; case SQL_FLOAT: - *((float*)d->inda->sqlvar[para].sqldata) = (float)val.toDouble(); + *((float*)sqlvar.sqldata) = val.toFloat(); break; case SQL_DOUBLE: - *((double*)d->inda->sqlvar[para].sqldata) = val.toDouble(); + *((double*)sqlvar.sqldata) = val.toDouble(); break; case SQL_TIMESTAMP: - *((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime()); + *((ISC_TIMESTAMP*)sqlvar.sqldata) = toTimeStamp(val.toDateTime()); break; #if (FB_API_VER >= 40) case SQL_TIMESTAMP_TZ: - *((ISC_TIMESTAMP_TZ*)d->inda->sqlvar[para].sqldata) = toTimeStampTz(val.toDateTime()); + *((ISC_TIMESTAMP_TZ*)sqlvar.sqldata) = toTimeStampTz(val.toDateTime()); break; #endif case SQL_TYPE_TIME: - *((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime()); + *((ISC_TIME*)sqlvar.sqldata) = toTime(val.toTime()); break; case SQL_TYPE_DATE: - *((ISC_DATE*)d->inda->sqlvar[para].sqldata) = toDate(val.toDate()); + *((ISC_DATE*)sqlvar.sqldata) = toDate(val.toDate()); break; case SQL_VARYING: case SQL_TEXT: - qFillBufferWithString(d->inda->sqlvar[para].sqldata, val.toString(), - d->inda->sqlvar[para].sqllen, - (d->inda->sqlvar[para].sqltype & ~1) == SQL_VARYING, false); + qFillBufferWithString(sqlvar.sqldata, val.toString(), sqlvar.sqllen, + sqltype == SQL_VARYING, false); break; case SQL_BLOB: ok &= d->writeBlob(para, val.toByteArray()); @@ -1159,11 +1181,11 @@ bool QIBaseResult::exec() ok &= d->writeArray(para, val.toList()); break; case SQL_BOOLEAN: - *((bool*)d->inda->sqlvar[para].sqldata) = val.toBool(); + *((bool*)sqlvar.sqldata) = val.toBool(); break; default: qCWarning(lcIbase, "QIBaseResult::exec: Unknown datatype %d", - d->inda->sqlvar[para].sqltype & ~1); + sqltype); break; } } @@ -1241,15 +1263,12 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) for (int i = 0; i < d->sqlda->sqld; ++i) { int idx = rowIdx + i; - char *buf = d->sqlda->sqlvar[i].sqldata; - int size = d->sqlda->sqlvar[i].sqllen; - Q_ASSERT(buf); + const XSQLVAR &sqlvar = d->sqlda->sqlvar[i]; - if ((d->sqlda->sqlvar[i].sqltype & 1) && *d->sqlda->sqlvar[i].sqlind) { + if ((sqlvar.sqltype & 1) && *sqlvar.sqlind) { // null value QVariant v; - v.convert(QMetaType(qIBaseTypeName2(d->sqlda->sqlvar[i].sqltype, - d->sqlda->sqlvar[i].sqlscale < 0))); + v.convert(QMetaType(qIBaseTypeName2(sqlvar.sqltype, sqlvar.sqlscale < 0))); if (v.userType() == QMetaType::Double) { switch(numericalPrecisionPolicy()) { case QSql::LowPrecisionInt32: @@ -1270,29 +1289,44 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) continue; } - switch(d->sqlda->sqlvar[i].sqltype & ~1) { + const char *buf = sqlvar.sqldata; + int size = sqlvar.sqllen; + Q_ASSERT(buf); + const auto sqltype = sqlvar.sqltype & ~1; + switch (sqltype) { case SQL_VARYING: // pascal strings - a short with a length information followed by the data row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf); break; case SQL_INT64: { - Q_ASSERT(d->sqlda->sqlvar[i].sqllen == sizeof(qint64)); + Q_ASSERT(sqlvar.sqllen == sizeof(qint64)); const auto val = *(qint64 *)buf; - const auto scale = d->sqlda->sqlvar[i].sqlscale; + const auto scale = sqlvar.sqlscale; row[idx] = applyScale(val, scale); break; } +#ifdef IBASE_INT128_SUPPORTED + case SQL_INT128: { + Q_ASSERT(sqlvar.sqllen == sizeof(qinternalint128)); + const qinternalint128 val128 = qFromUnaligned<qinternalint128>(buf); + const auto scale = sqlvar.sqlscale; + row[idx] = numberToHighPrecision(val128, scale); + if (numericalPrecisionPolicy() != QSql::HighPrecision) + row[idx] = applyScale(row[idx].toDouble(), 0); + break; + } +#endif case SQL_LONG: - if (d->sqlda->sqlvar[i].sqllen == 4) { + if (sqlvar.sqllen == 4) { const auto val = *(qint32 *)buf; - const auto scale = d->sqlda->sqlvar[i].sqlscale; + const auto scale = sqlvar.sqlscale; row[idx] = applyScale(val, scale); } else row[idx] = QVariant(*(qint64*)buf); break; case SQL_SHORT: { const auto val = *(short *)buf; - const auto scale = d->sqlda->sqlvar[i].sqlscale; + const auto scale = sqlvar.sqlscale; row[idx] = applyScale(val, scale); break; } @@ -1330,8 +1364,7 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) #endif default: // unknown type - don't even try to fetch - qCWarning(lcIbase, "gotoNext: unknown sqltype: %d", - d->sqlda->sqlvar[i].sqltype & ~1); + qCWarning(lcIbase, "gotoNext: unknown sqltype: %d", sqltype); row[idx] = QVariant(); break; } @@ -1449,13 +1482,14 @@ int QIBaseResult::numRowsAffected() QSqlRecord QIBaseResult::record() const { Q_D(const QIBaseResult); - QSqlRecord rec; if (!isActive() || !d->sqlda) - return rec; + return {}; + + if (!d->cachedRecord.isEmpty()) + return d->cachedRecord; - XSQLVAR v; for (int i = 0; i < d->sqlda->sqld; ++i) { - v = d->sqlda->sqlvar[i]; + const XSQLVAR &v = d->sqlda->sqlvar[i]; QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(), QMetaType(qIBaseTypeName2(v.sqltype, v.sqlscale < 0)), QString::fromLatin1(v.relname, v.relname_length)); @@ -1481,9 +1515,9 @@ QSqlRecord QIBaseResult::record() const f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional); } } - rec.append(f); + d->cachedRecord.append(f); } - return rec; + return d->cachedRecord; } QVariant QIBaseResult::handle() const @@ -1574,18 +1608,18 @@ bool QIBaseDriver::open(const QString &db, ba.append(char(isc_dpb_version1)); ba.append(char(isc_dpb_user_name)); ba.append(char(usr.length())); - ba.append(usr.data(), usr.length()); + ba.append(usr.constData(), usr.length()); ba.append(char(isc_dpb_password)); ba.append(char(pass.length())); - ba.append(pass.data(), pass.length()); + ba.append(pass.constData(), pass.length()); ba.append(char(isc_dpb_lc_ctype)); ba.append(char(enc.length())); - ba.append(enc.data(), enc.length()); + ba.append(enc.constData(), enc.length()); if (!role.isEmpty()) { ba.append(char(isc_dpb_sql_role_name)); ba.append(char(role.length())); - ba.append(role.data(), role.length()); + ba.append(role.constData(), role.length()); } QString portString; @@ -1597,7 +1631,7 @@ bool QIBaseDriver::open(const QString &db, ldb += host + portString + u':'; ldb += db; isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()), - &d->ibase, ba.size(), ba.data()); + &d->ibase, ba.size(), ba.constData()); if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"), QSqlError::ConnectionError)) { setOpenError(true); @@ -1891,7 +1925,7 @@ bool QIBaseDriver::subscribeToNotification(const QString &name) eBuffer->resultBuffer); if (status[0] == 1 && status[1]) { - setLastError(QSqlError(QString::fromLatin1("Could not subscribe to event notifications for %1.").arg(name))); + setLastError(QSqlError(tr("Could not subscribe to event notifications for %1.").arg(name))); d->eventBuffers.remove(name); qFreeEventBuffer(eBuffer); return false; @@ -1920,7 +1954,7 @@ bool QIBaseDriver::unsubscribeFromNotification(const QString &name) isc_cancel_events(status, &d->ibase, &eBuffer->eventId); if (status[0] == 1 && status[1]) { - setLastError(QSqlError(QString::fromLatin1("Could not unsubscribe from event notifications for %1.").arg(name))); + setLastError(QSqlError(tr("Could not unsubscribe from event notifications for %1.").arg(name))); return false; } diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 976911d458..77137f3b3c 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE -static Q_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc") +Q_STATIC_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc") using namespace Qt::StringLiterals; diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt index 4203a5c437..e667034c2e 100644 --- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt +++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt @@ -60,6 +60,8 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sq SQLITE_OMIT_COMPLETE INCLUDE_DIRECTORIES ../../../3rdparty/sqlite + ATTRIBUTION_FILE_DIR_PATHS + ../../../3rdparty/sqlite ) qt_internal_extend_target(QSQLiteDriverPlugin CONDITION CMAKE_BUILD_TYPE STREQUAL Release AND NOT QT_FEATURE_system_sqlite diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp index c574772fd7..05af02e156 100644 --- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp +++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp @@ -39,7 +39,7 @@ Q_DECLARE_METATYPE(sqlite3_stmt*) QT_BEGIN_NAMESPACE -static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite") +Q_STATIC_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite") using namespace Qt::StringLiterals; diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index 3f57f284e6..226da495f4 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -21,6 +21,7 @@ #include <QtCore/private/qcore_mac_p.h> #include <QtGui/qpainterpath.h> +#include <QtGui/qstylehints.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qpa/qplatformfontdatabase.h> #include <QtGui/qpa/qplatformtheme.h> @@ -160,7 +161,10 @@ const int pushButtonBevelRectOffsets[3] = { QVector<QPointer<QObject> > QMacStylePrivate::scrollBars; -bool isDarkMode() { return QGuiApplicationPrivate::platformTheme()->colorScheme() == Qt::ColorScheme::Dark; } +static inline bool isDarkMode() +{ + return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; +} #if QT_CONFIG(tabwidget) /* diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index 784f491681..63663c74aa 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -9,12 +9,15 @@ #include <qstyleoption.h> #include <qpainter.h> #include <QGraphicsDropShadowEffect> +#include <QLatin1StringView> #include <QtWidgets/qcombobox.h> #include <QtWidgets/qcommandlinkbutton.h> #include <QtWidgets/qgraphicsview.h> #include <QtWidgets/qlistview.h> #include <QtWidgets/qmenu.h> +#if QT_CONFIG(mdiarea) #include <QtWidgets/qmdiarea.h> +#endif #include <QtWidgets/qtextedit.h> #include <QtWidgets/qtreeview.h> @@ -25,6 +28,7 @@ QT_BEGIN_NAMESPACE const static int topLevelRoundingRadius = 8; //Radius for toplevel items like popups for round corners const static int secondLevelRoundingRadius = 4; //Radius for second level items like hovered menu item round corners +constexpr QLatin1StringView originalWidthProperty("_q_windows11_style_original_width"); enum WINUI3Color { subtleHighlightColor, //Subtle highlight based on alpha used for hovered elements @@ -134,6 +138,8 @@ static void drawArrow(const QStyle *style, const QStyleOptionToolButton *toolbut */ QWindows11Style::QWindows11Style() : QWindowsVistaStyle(*new QWindows11StylePrivate) { + highContrastTheme = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Unknown; + colorSchemeIndex = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Light ? 0 : 1; } /*! @@ -142,6 +148,8 @@ QWindows11Style::QWindows11Style() : QWindowsVistaStyle(*new QWindows11StylePriv */ QWindows11Style::QWindows11Style(QWindows11StylePrivate &dd) : QWindowsVistaStyle(dd) { + highContrastTheme = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Unknown; + colorSchemeIndex = QGuiApplicationPrivate::styleHints->colorScheme() == Qt::ColorScheme::Light ? 0 : 1; } /*! @@ -1738,6 +1746,10 @@ int QWindows11Style::styleHint(StyleHint hint, const QStyleOption *opt, return 0; case QStyle::SH_ItemView_ShowDecorationSelected: return 1; + case QStyle::SH_Slider_AbsoluteSetButtons: + return Qt::LeftButton; + case QStyle::SH_Slider_PageSetButtons: + return 0; default: return QWindowsVistaStyle::styleHint(hint, opt, widget, returnData); } @@ -2036,6 +2048,13 @@ void QWindows11Style::polish(QWidget* widget) QLineEdit *le = cb->lineEdit(); le->setFrame(false); } + } else if (widget->inherits("QAbstractSpinBox")) { + const int minWidth = 2 * 24 + 40; + const int originalWidth = widget->size().width(); + if (originalWidth < minWidth) { + widget->resize(minWidth, widget->size().height()); + widget->setProperty(originalWidthProperty.constData(), originalWidth); + } } else if (widget->inherits("QAbstractButton") || widget->inherits("QToolButton")) { widget->setAutoFillBackground(false); } else if (qobject_cast<QGraphicsView *>(widget) && !qobject_cast<QTextEdit *>(widget)) { @@ -2043,7 +2062,11 @@ void QWindows11Style::polish(QWidget* widget) pal.setColor(QPalette::Base, pal.window().color()); widget->setPalette(pal); } else if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget); - scrollarea && !qobject_cast<QMdiArea *>(widget)) { + scrollarea +#if QT_CONFIG(mdiarea) + && !qobject_cast<QMdiArea *>(widget) +#endif + ) { QPalette pal = scrollarea->viewport()->palette(); const QPalette originalPalette = pal; pal.setColor(scrollarea->viewport()->backgroundRole(), Qt::transparent); @@ -2062,11 +2085,22 @@ void QWindows11Style::unpolish(QWidget *widget) { QWindowsVistaStyle::unpolish(widget); if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget); - scrollarea && !qobject_cast<QMdiArea *>(widget)) { + scrollarea +#if QT_CONFIG(mdiarea) + && !qobject_cast<QMdiArea *>(widget) +#endif + ) { const QPalette pal = scrollarea->viewport()->property("_q_original_background_palette").value<QPalette>(); scrollarea->viewport()->setPalette(pal); scrollarea->viewport()->setProperty("_q_original_background_palette", QVariant()); } + if (widget->inherits("QAbstractSpinBox")) { + const QVariant originalWidth = widget->property(originalWidthProperty.constData()); + if (originalWidth.isValid()) { + widget->resize(originalWidth.toInt(), widget->size().height()); + widget->setProperty(originalWidthProperty.constData(), QVariant()); + } + } } /* |