diff options
Diffstat (limited to 'src/plugins')
97 files changed, 1501 insertions, 523 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/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..07f2786cc6 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() @@ -976,8 +918,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/qandroidplatformtheme.cpp b/src/plugins/platforms/android/qandroidplatformtheme.cpp index 7b9072df69..99eeabac1d 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.cpp +++ b/src/plugins/platforms/android/qandroidplatformtheme.cpp @@ -161,7 +161,10 @@ QJsonObject AndroidStyle::loadStyleData() if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar)) stylePath += slashChar; - if (QAndroidPlatformIntegration::colorScheme() == Qt::ColorScheme::Dark) + const Qt::ColorScheme colorScheme = QAndroidPlatformTheme::instance() + ? QAndroidPlatformTheme::instance()->colorScheme() + : QAndroidPlatformIntegration::colorScheme(); + if (colorScheme == Qt::ColorScheme::Dark) stylePath += "darkUiMode/"_L1; Q_ASSERT(!stylePath.isEmpty()); @@ -423,9 +426,19 @@ void QAndroidPlatformTheme::showPlatformMenuBar() Qt::ColorScheme QAndroidPlatformTheme::colorScheme() const { + if (m_colorSchemeOverride != Qt::ColorScheme::Unknown) + return m_colorSchemeOverride; return QAndroidPlatformIntegration::colorScheme(); } +void QAndroidPlatformTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + m_colorSchemeOverride = scheme; + QMetaObject::invokeMethod(qGuiApp, [this]{ + updateColorScheme(); + }); +} + static inline int paletteType(QPlatformTheme::Palette type) { switch (type) { diff --git a/src/plugins/platforms/android/qandroidplatformtheme.h b/src/plugins/platforms/android/qandroidplatformtheme.h index ce3d6d5f73..1b4ab5664d 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.h +++ b/src/plugins/platforms/android/qandroidplatformtheme.h @@ -40,6 +40,8 @@ public: QPlatformMenuItem *createPlatformMenuItem() const override; void showPlatformMenuBar() override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; + const QPalette *palette(Palette type = SystemPalette) const override; const QFont *font(Font type = SystemFont) const override; QIconEngine *createIconEngine(const QString &iconName) const override; @@ -57,6 +59,7 @@ private: std::shared_ptr<AndroidStyle> m_androidStyleData; QPalette m_defaultPalette; QFont m_systemFont; + Qt::ColorScheme m_colorSchemeOverride = Qt::ColorScheme::Unknown; }; QT_END_NAMESPACE 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/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.h b/src/plugins/platforms/cocoa/qcocoatheme.h index c49d83feae..97e0f633a7 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -44,6 +44,7 @@ public: static const char *name; + void requestColorScheme(Qt::ColorScheme scheme) override; void handleSystemThemeChange(); #ifndef QT_NO_SHORTCUT diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index f4fbfadbe4..f3f3e05b50 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] { @@ -478,6 +475,23 @@ Qt::ColorScheme QCocoaTheme::colorScheme() const return m_colorScheme; } +void QCocoaTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + NSAppearance *appearance = nil; + switch (scheme) { + case Qt::ColorScheme::Dark: + appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; + break; + case Qt::ColorScheme::Light: + appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; + break; + case Qt::ColorScheme::Unknown: + break; + } + if (appearance != NSApp.effectiveAppearance) + NSApplication.sharedApplication.appearance = appearance; +} + /* Update the theme's color scheme based on the current appearance. 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/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/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm index fca0432426..09e2f2f4c3 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm @@ -30,14 +30,14 @@ case QFileDialogOptions::AnyFile: case QFileDialogOptions::ExistingFile: case QFileDialogOptions::ExistingFiles: - [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeContent]]; - [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeItem]]; - [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeData]]; + [docTypes addObject:UTTypeContent]; + [docTypes addObject:UTTypeItem]; + [docTypes addObject:UTTypeData]; break; // Showing files is not supported in Directory mode in iOS case QFileDialogOptions::Directory: case QFileDialogOptions::DirectoryOnly: - [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeFolder]]; + [docTypes addObject:UTTypeFolder]; break; } } 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/qiostextinputoverlay.mm b/src/plugins/platforms/ios/qiostextinputoverlay.mm index 01046334a1..83170c1851 100644 --- a/src/plugins/platforms/ios/qiostextinputoverlay.mm +++ b/src/plugins/platforms/ios/qiostextinputoverlay.mm @@ -434,7 +434,7 @@ static void executeBlockWithoutAnimation(Block block) if (enabled) { _focusView = [reinterpret_cast<UIView *>(qApp->focusWindow()->winId()) retain]; - _desktopView = [qt_apple_sharedApplication().keyWindow.rootViewController.view retain]; + _desktopView = [presentationWindow(nullptr).rootViewController.view retain]; Q_ASSERT(_focusView && _desktopView && _desktopView.superview); [_desktopView addGestureRecognizer:self]; } else { diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index f0a404a61a..70e2c37ff1 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -4,6 +4,8 @@ #ifndef QIOSTHEME_H #define QIOSTHEME_H +#import <UIKit/UIKit.h> + #include <QtCore/QHash> #include <QtGui/QPalette> #include <qpa/qplatformtheme.h> @@ -22,6 +24,7 @@ public: QVariant themeHint(ThemeHint hint) const override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; #if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS) QPlatformMenuItem* createPlatformMenuItem() const override; @@ -37,9 +40,11 @@ public: static const char *name; static void initializeSystemPalette(); + static void applyTheme(UIWindow *window); private: static QPalette s_systemPalette; + static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; QMacNotificationObserver m_contentSizeCategoryObserver; }; diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm index 3853de9cf1..0b420a875a 100644 --- a/src/plugins/platforms/ios/qiostheme.mm +++ b/src/plugins/platforms/ios/qiostheme.mm @@ -154,6 +154,9 @@ Qt::ColorScheme QIOSTheme::colorScheme() const // the OS reports itself as always being in dark mode. return Qt::ColorScheme::Dark; #else + if (s_colorSchemeOverride != Qt::ColorScheme::Unknown) + return s_colorSchemeOverride; + // Set the appearance based on the QUIWindow // Fallback to the UIScreen if no window is created yet UIUserInterfaceStyle appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle; @@ -171,6 +174,38 @@ Qt::ColorScheme QIOSTheme::colorScheme() const #endif } +void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme) +{ +#if defined(Q_OS_VISIONOS) + Q_UNUSED(scheme); +#else + s_colorSchemeOverride = scheme; + + const NSArray<UIWindow *> *windows = qt_apple_sharedApplication().windows; + for (UIWindow *window in windows) { + // don't apply a theme to windows we don't own + if (qt_objc_cast<QUIWindow*>(window)) + applyTheme(window); + } +#endif +} + +void QIOSTheme::applyTheme(UIWindow *window) +{ + const UIUserInterfaceStyle style = []{ + switch (s_colorSchemeOverride) { + case Qt::ColorScheme::Dark: + return UIUserInterfaceStyleDark; + case Qt::ColorScheme::Light: + return UIUserInterfaceStyleLight; + case Qt::ColorScheme::Unknown: + return UIUserInterfaceStyleUnspecified; + } + }(); + + window.overrideUserInterfaceStyle = style; +} + const QFont *QIOSTheme::font(Font type) const { const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); 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 6a1080e238..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); @@ -55,8 +54,10 @@ QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle) connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged); - if (QPlatformWindow::parent()) - setParent(QPlatformWindow::parent()); + // Always set parent, even if we don't have a parent window, + // as we use setParent to reparent top levels into our desktop + // manager view. + setParent(QPlatformWindow::parent()); if (!isForeignWindow()) { // Resolve default window geometry in case it was not set before creating the @@ -125,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(); @@ -341,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; } @@ -357,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) @@ -394,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/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm index 7c910b6d9e..783e243e10 100644 --- a/src/plugins/platforms/ios/quiwindow.mm +++ b/src/plugins/platforms/ios/quiwindow.mm @@ -22,6 +22,15 @@ return self; } +- (instancetype)initWithWindowScene:(UIWindowScene *)windowScene +{ + if ((self = [super initWithWindowScene:windowScene])) + self->_sendingEvent = NO; + + QIOSTheme::applyTheme(self); + return self; +} + - (void)sendEvent:(UIEvent *)event { QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES); 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..775946aaf9 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 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/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index ddf8140c48..0490b2bfe0 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -361,10 +361,14 @@ QList<QWasmWindow *> QWasmScreen::allWindows() { QList<QWasmWindow *> windows; for (auto *child : childStack()) { - QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively); - std::transform( - list.begin(), list.end(), std::back_inserter(windows), - [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); }); + const QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively); + for (auto child : list) { + auto handle = child->handle(); + if (handle) { + auto wnd = static_cast<QWasmWindow *>(handle); + windows.push_back(wnd); + } + } windows.push_back(child); } return windows; 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..8cd84e208b 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 diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index de65a2171d..8c0261d568 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -201,6 +201,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 diff --git a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp index 8d147e8fa0..00cd792b5c 100644 --- a/src/plugins/platforms/windows/qwindowsmimeregistry.cpp +++ b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp @@ -944,7 +944,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject * if (canGetData(CF_DIB, pDataObj)) { qCDebug(lcQpaMime) << "Decoding DIB"; QImage img; - QByteArray data = getData(CF_DIBV5, pDataObj); + QByteArray data = getData(CF_DIB, pDataObj); QBuffer buffer(&data); if (readDib(buffer, img)) return img; 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/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.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index e8a324aedb..b6017c7692 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -301,6 +301,9 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) { + QColor foreground, background, + accent, accentDark, accentDarker, accentDarkest, + accentLight, accentLighter, accentLightest; #if QT_CONFIG(cpp_winrt) using namespace winrt::Windows::UI::ViewManagement; const auto settings = UISettings(); @@ -308,32 +311,37 @@ void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API // returns the old system colors, not the dark mode colors. If the background is black (which it // usually), then override it with a dark gray instead so that we can go up and down the lightness. - const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); - const QColor background = [&settings]() -> QColor { - auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); - if (systemBackground == Qt::black) - systemBackground = QColor(0x1E, 0x1E, 0x1E); - return systemBackground; - }(); - - const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); - const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); - const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); - const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); - const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); - const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); - const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); -#else - const QColor foreground = Qt::white; - const QColor background = QColor(0x1E, 0x1E, 0x1E); - const QColor accent = qt_accentColor(AccentColorNormal); - const QColor accentDark = accent.darker(120); - const QColor accentDarker = accentDark.darker(120); - const QColor accentDarkest = accentDarker.darker(120); - const QColor accentLight = accent.lighter(120); - const QColor accentLighter = accentLight.lighter(120); - const QColor accentLightest = accentLighter.lighter(120); + if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) { + // the system is actually running in dark mode, so UISettings will give us dark colors + foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); + background = [&settings]() -> QColor { + auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); + if (systemBackground == Qt::black) + systemBackground = QColor(0x1E, 0x1E, 0x1E); + return systemBackground; + }(); + + accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); + accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); + accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); + accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); + accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); + accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); + accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); + } else #endif + { + // If the system is running in light mode, then we need to make up our own dark palette + foreground = Qt::white; + background = QColor(0x1E, 0x1E, 0x1E); + accent = qt_accentColor(AccentColorNormal); + accentDark = accent.darker(120); + accentDarker = accentDark.darker(120); + accentDarkest = accentDarker.darker(120); + accentLight = accent.lighter(120); + accentLighter = accentLight.lighter(120); + accentLightest = accentLighter.lighter(120); + } const QColor linkColor = accent; const QColor buttonColor = background.lighter(200); @@ -549,13 +557,25 @@ Qt::ColorScheme QWindowsTheme::effectiveColorScheme() { if (queryHighContrast()) return Qt::ColorScheme::Unknown; - return s_colorScheme; + if (s_colorSchemeOverride != Qt::ColorScheme::Unknown) + return s_colorSchemeOverride; + if (s_colorScheme != Qt::ColorScheme::Unknown) + return s_colorScheme; + return queryColorScheme(); +} + +void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + s_colorSchemeOverride = scheme; + handleSettingsChanged(); } void QWindowsTheme::handleSettingsChanged() { - const auto newColorScheme = QWindowsTheme::queryColorScheme(); - const bool colorSchemeChanged = newColorScheme != QWindowsTheme::s_colorScheme; + const auto oldColorScheme = s_colorScheme; + s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry + const auto newColorScheme = effectiveColorScheme(); + const bool colorSchemeChanged = newColorScheme != oldColorScheme; s_colorScheme = newColorScheme; auto integration = QWindowsIntegration::instance(); integration->updateApplicationBadge(); diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 6109122944..a89fb1e5bd 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -32,6 +32,7 @@ public: QVariant themeHint(ThemeHint) const override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; static void handleSettingsChanged(); @@ -79,6 +80,8 @@ private: static QWindowsTheme *m_instance; static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown; + static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; + QPalette *m_palettes[NPalettes]; QFont *m_fonts[NFonts]; QList<QSize> m_fileIconSizes; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 5d96d40af5..4b7ce0a979 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -850,7 +850,8 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) { if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) return false; - // the application has explicitly opted out of dark frames + + // the user of the application has explicitly opted out of dark frames if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) return false; @@ -2481,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/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..96758e7181 100644 --- a/src/plugins/platforms/xcb/CMakeLists.txt +++ b/src/plugins/platforms/xcb/CMakeLists.txt @@ -164,7 +164,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..7a44e4bb4f 100644 --- a/src/plugins/platforms/xcb/qxcbdrag.cpp +++ b/src/plugins/platforms/xcb/qxcbdrag.cpp @@ -1306,32 +1306,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..7cec986883 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; @@ -225,7 +240,7 @@ static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt) return ts; } -static QDateTime fromTimeStamp(char *buffer) +static QDateTime fromTimeStamp(const char *buffer) { static const QDate bd(1858, 11, 17); QTime t(0, 0); @@ -233,14 +248,14 @@ static QDateTime fromTimeStamp(char *buffer) // have to demangle the structure ourselves because isc_decode_time // strips the msecs - auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer); + auto timebuf = reinterpret_cast<const ISC_TIMESTAMP *>(buffer); t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10)); d = bd.addDays(timebuf->timestamp_date); return QDateTime(d, t); } #if (FB_API_VER >= 40) -QDateTime fromTimeStampTz(char *buffer) +QDateTime fromTimeStampTz(const char *buffer) { static const QDate bd(1858, 11, 17); QTime t(0, 0); @@ -248,7 +263,7 @@ QDateTime fromTimeStampTz(char *buffer) // have to demangle the structure ourselves because isc_decode_time // strips the msecs - auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer); + auto timebuf = reinterpret_cast<const ISC_TIMESTAMP_TZ *>(buffer); t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10)); d = bd.addDays(timebuf->utc_timestamp.timestamp_date); quint16 fpTzID = timebuf->time_zone; @@ -278,12 +293,13 @@ static ISC_TIME toTime(QTime t) return (ISC_TIME)midnight.msecsTo(t) * 10; } -static QTime fromTime(char *buffer) +static 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)); + const auto timebuf = reinterpret_cast<const ISC_TIME *>(buffer); + t = t.addMSecs(int((*timebuf) / 10)); return t; } @@ -297,14 +313,15 @@ static ISC_DATE toDate(QDate t) return date; } -static QDate fromDate(char *buffer) +static 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)); + const auto tsbuf = reinterpret_cast<const ISC_TIMESTAMP *>(buffer); + d = bd.addDays(int(tsbuf->timestamp_date)); return d; } @@ -347,21 +364,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 +418,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 +456,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 +531,7 @@ public: XSQLDA *sqlda; // output sqlda XSQLDA *inda; // input parameters int queryType; + mutable QSqlRecord cachedRecord; }; @@ -518,6 +563,7 @@ void QIBaseResultPrivate::cleanup() delDA(inda); queryType = -1; + cachedRecord.clear(); q->cleanup(); } @@ -551,9 +597,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 +621,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 +632,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 +721,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 +731,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 +754,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 +797,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 +823,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. Fieldname: %3, Expected size: %1. Supplied size: %2") + .arg(elements).arg(list.size()); return 0; } @@ -788,7 +834,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 dimensons mismatch. Fieldname: %1"); return 0; } @@ -869,11 +917,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 +946,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 +1128,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 +1203,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 +1285,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 +1311,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 +1386,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 +1504,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 +1537,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 +1630,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 +1653,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 +1947,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 +1976,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/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/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()); + } + } } /* |