summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.cpp812
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.h66
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.cpp368
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.h7
-rw-r--r--src/plugins/platforms/android/androidjniclipboard.cpp20
-rw-r--r--src/plugins/platforms/android/androidjniinput.cpp63
-rw-r--r--src/plugins/platforms/android/androidjniinput.h6
-rw-r--r--src/plugins/platforms/android/androidjnimain.cpp118
-rw-r--r--src/plugins/platforms/android/androidjnimain.h7
-rw-r--r--src/plugins/platforms/android/extract-dummy.cpp5
-rw-r--r--src/plugins/platforms/android/extract.cpp15
-rw-r--r--src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp76
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.cpp133
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.h5
-rw-r--r--src/plugins/platforms/android/qandroidplatformaccessibility.cpp12
-rw-r--r--src/plugins/platforms/android/qandroidplatformaccessibility.h1
-rw-r--r--src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp46
-rw-r--r--src/plugins/platforms/android/qandroidplatformfiledialoghelper.h8
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.cpp37
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.h7
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.cpp70
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.h14
-rw-r--r--src/plugins/platforms/android/qandroidplatformservices.cpp11
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm9
-rw-r--r--src/plugins/platforms/cocoa/qcocoacursor.mm37
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.mm6
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm7
-rw-r--r--src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm8
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm19
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.h10
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm147
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm70
-rw-r--r--src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h2
-rw-r--r--src/plugins/platforms/cocoa/qnsview.h1
-rw-r--r--src/plugins/platforms/cocoa/qnsview_dragging.mm4
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm112
-rw-r--r--src/plugins/platforms/cocoa/qnsview_tablet.mm7
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm41
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfscursor.cpp12
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp17
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp51
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.h6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp2
-rw-r--r--src/plugins/platforms/ios/qiosdocumentpickercontroller.h4
-rw-r--r--src/plugins/platforms/ios/qiosdocumentpickercontroller.mm15
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm32
-rw-r--r--src/plugins/platforms/ios/qiosmessagedialog.mm20
-rw-r--r--src/plugins/platforms/ios/qiosscreen.mm2
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.mm23
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.mm14
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.mm24
-rw-r--r--src/plugins/platforms/ios/quiview.mm12
-rw-r--r--src/plugins/platforms/ios/quiview_accessibility.mm9
-rw-r--r--src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp2
-rw-r--r--src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp2
-rw-r--r--src/plugins/platforms/minimal/qminimalintegration.cpp8
-rw-r--r--src/plugins/platforms/minimal/qminimalintegration.h5
-rw-r--r--src/plugins/platforms/vnc/qvnc.cpp12
-rw-r--r--src/plugins/platforms/vnc/qvncclient.cpp2
-rw-r--r--src/plugins/platforms/vnc/qvncscreen.cpp7
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp6
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.cpp16
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.cpp159
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.h7
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp1
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp14
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h1
-rw-r--r--src/plugins/platforms/windows/openglblacklists/default.json2
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.cpp13
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.h3
-rw-r--r--src/plugins/platforms/windows/qwindowscursor.cpp5
-rw-r--r--src/plugins/platforms/windows/qwindowsdialoghelpers.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowsdrag.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowsglcontext.cpp17
-rw-r--r--src/plugins/platforms/windows/qwindowsmousehandler.cpp9
-rw-r--r--src/plugins/platforms/windows/qwindowsopengltester.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowspointerhandler.cpp50
-rw-r--r--src/plugins/platforms/windows/qwindowspointerhandler.h3
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp36
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp7
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp37
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp17
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp9
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qtessellator.cpp1
-rw-r--r--src/plugins/platforms/xcb/qt_xlib_wrapper.c44
-rw-r--r--src/plugins/platforms/xcb/qt_xlib_wrapper.h54
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.h2
-rw-r--r--src/plugins/platforms/xcb/qxcbclipboard.cpp28
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.cpp81
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_basic.cpp10
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_xi2.cpp8
-rw-r--r--src/plugins/platforms/xcb/qxcbcursor.cpp11
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.cpp35
-rw-r--r--src/plugins/platforms/xcb/qxcbeventdispatcher.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbeventqueue.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbmime.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbnativeinterface.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbnativeinterface.h2
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.cpp25
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.h2
-rw-r--r--src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.cpp36
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.h9
-rw-r--r--src/plugins/platforms/xcb/xcb_qpa_lib.pro2
117 files changed, 2617 insertions, 868 deletions
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 6c95a9b126..a03cc86b74 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2019 Volker Krause <vkrause@kde.org>
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
@@ -39,15 +40,51 @@
#include "androidcontentfileengine.h"
-#include <private/qjni_p.h>
#include <private/qjnihelpers_p.h>
-#include <QDebug>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qmimedatabase.h>
+#include <QtCore/qdebug.h>
-AndroidContentFileEngine::AndroidContentFileEngine(const QString &f)
- : m_file(f)
+class JniExceptionCleaner
{
- setFileName(f);
+public:
+ JniExceptionCleaner() { clearException(); };
+ ~JniExceptionCleaner() { clearException(); }
+
+ bool clean() { return clearException(); };
+private:
+ bool clearException()
+ {
+ QJNIEnvironmentPrivate env;
+ if (Q_UNLIKELY(env->ExceptionCheck())) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return true;
+ }
+ return false;
+ }
+};
+
+static QJNIObjectPrivate &contentResolverInstance()
+{
+ static QJNIObjectPrivate contentResolver;
+ if (!contentResolver.isValid()) {
+ JniExceptionCleaner cleaner;
+ contentResolver = QJNIObjectPrivate(QtAndroidPrivate::context())
+ .callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
+ }
+
+ return contentResolver;
+}
+
+AndroidContentFileEngine::AndroidContentFileEngine(const QString &filename)
+ : m_initialFile(filename),
+ m_documentFile(DocumentFile::parseFromAnyUri(filename))
+{
+ setFileName(filename);
}
bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
@@ -58,6 +95,27 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
}
if (openMode & QFileDevice::WriteOnly) {
openModeStr += QLatin1Char('w');
+ if (!m_documentFile->exists()) {
+ if (QUrl(m_initialFile).path().startsWith(QLatin1String("/tree/"))) {
+ const int lastSeparatorIndex = m_initialFile.lastIndexOf(QLatin1Char('/'));
+ const QString fileName = m_initialFile.mid(lastSeparatorIndex + 1);
+
+ QString mimeType;
+ const auto mimeTypes = QMimeDatabase().mimeTypesForFileName(fileName);
+ if (!mimeTypes.empty())
+ mimeType = mimeTypes.first().name();
+ else
+ mimeType = QLatin1String("application/octet-stream");
+
+ if (m_documentFile->parent()) {
+ auto createdFile = m_documentFile->parent()->createFile(mimeType, fileName);
+ if (createdFile)
+ m_documentFile = createdFile;
+ }
+ } else {
+ qWarning() << "open(): non-existent content URI with a document type provided";
+ }
+ }
}
if (openMode & QFileDevice::Truncate) {
openModeStr += QLatin1Char('t');
@@ -65,53 +123,149 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode)
openModeStr += QLatin1Char('a');
}
- const auto fd = QJNIObjectPrivate::callStaticMethod<jint>("org/qtproject/qt5/android/QtNative",
- "openFdForContentUrl",
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)I",
- QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object(),
- QJNIObjectPrivate::fromString(openModeStr).object());
+ JniExceptionCleaner cleaner;
+ m_pfd = contentResolverInstance().callObjectMethod("openFileDescriptor",
+ "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
+ m_documentFile->uri().object(),
+ QJNIObjectPrivate::fromString(openModeStr).object());
+
+ if (!m_pfd.isValid())
+ return false;
+
+ const auto fd = m_pfd.callMethod<jint>("getFd", "()I");
if (fd < 0) {
+ closeNativeFileDescriptor();
return false;
}
- return QFSFileEngine::open(openMode, fd, QFile::AutoCloseHandle);
+ return QFSFileEngine::open(openMode, fd, QFile::DontCloseHandle);
+}
+
+bool AndroidContentFileEngine::close()
+{
+ closeNativeFileDescriptor();
+ return QFSFileEngine::close();
+}
+
+void AndroidContentFileEngine::closeNativeFileDescriptor()
+{
+ if (m_pfd.isValid()) {
+ JniExceptionCleaner cleaner;
+ m_pfd.callMethod<void>("close", "()V");
+ m_pfd = QJNIObjectPrivate();
+ }
}
qint64 AndroidContentFileEngine::size() const
{
- const jlong size = QJNIObjectPrivate::callStaticMethod<jlong>(
- "org/qtproject/qt5/android/QtNative", "getSize",
- "(Landroid/content/Context;Ljava/lang/String;)J", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
- return (qint64)size;
+ return m_documentFile->length();
+}
+
+bool AndroidContentFileEngine::remove()
+{
+ return m_documentFile->remove();
+}
+
+bool AndroidContentFileEngine::rename(const QString &newName)
+{
+ if (m_documentFile->rename(newName)) {
+ m_initialFile = m_documentFile->uri().toString();
+ return true;
+ }
+ return false;
+}
+
+bool AndroidContentFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const
+{
+ QString tmp = dirName;
+ tmp.remove(m_initialFile);
+
+ QStringList dirParts = tmp.split(QLatin1Char('/'));
+ dirParts.removeAll("");
+
+ if (dirParts.isEmpty())
+ return false;
+
+ auto createdDir = m_documentFile;
+ bool allDirsCreated = true;
+ for (const auto &dir : dirParts) {
+ // Find if the sub-dir already exists and then don't re-create it
+ bool subDirExists = false;
+ for (const DocumentFilePtr &subDir : m_documentFile->listFiles()) {
+ if (dir == subDir->name() && subDir->isDirectory()) {
+ createdDir = subDir;
+ subDirExists = true;
+ }
+ }
+
+ if (!subDirExists) {
+ createdDir = createdDir->createDirectory(dir);
+ if (!createdDir) {
+ allDirsCreated = false;
+ break;
+ }
+ }
+
+ if (!createParentDirectories)
+ break;
+ }
+
+ return allDirsCreated;
+}
+
+bool AndroidContentFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
+{
+ if (recurseParentDirectories)
+ qWarning() << "rmpath(): Unsupported for Content URIs";
+
+ const QString dirFileName = QUrl(dirName).fileName();
+ bool deleted = false;
+ for (const DocumentFilePtr &dir : m_documentFile->listFiles()) {
+ if (dirFileName == dir->name() && dir->isDirectory()) {
+ deleted = dir->remove();
+ break;
+ }
+ }
+
+ return deleted;
+}
+
+QByteArray AndroidContentFileEngine::id() const
+{
+ return m_documentFile->id().toUtf8();
+}
+
+QDateTime AndroidContentFileEngine::fileTime(FileTime time) const
+{
+ switch (time) {
+ case FileTime::ModificationTime:
+ return m_documentFile->lastModified();
+ break;
+ default:
+ break;
+ }
+
+ return QDateTime();
}
AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlags type) const
{
- FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
FileFlags flags;
- const bool isDir = QJNIObjectPrivate::callStaticMethod<jboolean>(
- "org/qtproject/qt5/android/QtNative", "checkIfDir",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
- // If it is a directory then we know it exists so there is no reason to explicitly check
- const bool exists = isDir ? true : QJNIObjectPrivate::callStaticMethod<jboolean>(
- "org/qtproject/qt5/android/QtNative", "checkFileExists",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
- if (!exists && !isDir)
+ if (!m_documentFile->exists())
+ return flags;
+
+ flags = ExistsFlag;
+ if (!m_documentFile->canRead())
return flags;
- if (isDir) {
- flags = DirectoryType | commonFlags;
+
+ flags |= ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm;
+
+ if (m_documentFile->isDirectory()) {
+ flags |= DirectoryType;
} else {
- flags = FileType | commonFlags;
- const bool writable = QJNIObjectPrivate::callStaticMethod<jboolean>(
- "org/qtproject/qt5/android/QtNative", "checkIfWritable",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(fileName(DefaultName)).object());
- if (writable)
+ flags |= FileType;
+ if (m_documentFile->canWrite())
flags |= WriteOwnerPerm|WriteUserPerm|WriteGroupPerm|WriteOtherPerm;
}
return type & flags;
@@ -126,18 +280,18 @@ QString AndroidContentFileEngine::fileName(FileName f) const
case DefaultName:
case AbsoluteName:
case CanonicalName:
- return m_file;
+ return m_documentFile->uri().toString();
case BaseName:
- {
- const int pos = m_file.lastIndexOf(QChar(QLatin1Char('/')));
- return m_file.mid(pos);
- }
+ return m_documentFile->name();
default:
- return QString();
+ break;
}
+
+ return QString();
}
-QAbstractFileEngine::Iterator *AndroidContentFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames)
+QAbstractFileEngine::Iterator *AndroidContentFileEngine::beginEntryList(QDir::Filters filters,
+ const QStringList &filterNames)
{
return new AndroidContentFileEngineIterator(filters, filterNames);
}
@@ -179,37 +333,557 @@ QString AndroidContentFileEngineIterator::next()
bool AndroidContentFileEngineIterator::hasNext() const
{
- if (m_index == -1) {
- if (path().isEmpty())
+ if (m_index == -1 && m_files.isEmpty()) {
+ const auto currentPath = path();
+ if (currentPath.isEmpty())
return false;
- const bool isDir = QJNIObjectPrivate::callStaticMethod<jboolean>(
- "org/qtproject/qt5/android/QtNative", "checkIfDir",
- "(Landroid/content/Context;Ljava/lang/String;)Z",
- QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(path()).object());
- if (isDir) {
- QJNIObjectPrivate objArray = QJNIObjectPrivate::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
- "listContentsFromTreeUri",
- "(Landroid/content/Context;Ljava/lang/String;)[Ljava/lang/String;",
- QtAndroidPrivate::context(),
- QJNIObjectPrivate::fromString(path()).object());
- if (objArray.isValid()) {
- QJNIEnvironmentPrivate env;
- const jsize length = env->GetArrayLength(static_cast<jarray>(objArray.object()));
- for (int i = 0; i != length; ++i) {
- m_entries << QJNIObjectPrivate(env->GetObjectArrayElement(
- static_cast<jobjectArray>(objArray.object()), i)).toString();
- }
- }
- }
- m_index = 0;
+
+ const auto iterDoc = DocumentFile::parseFromAnyUri(currentPath);
+ if (iterDoc->isDirectory())
+ for (const auto &doc : iterDoc->listFiles())
+ m_files.append(doc);
}
- return m_index < m_entries.size();
+
+ return m_index < (m_files.size() - 1);
}
QString AndroidContentFileEngineIterator::currentFileName() const
{
- if (m_index <= 0 || m_index > m_entries.size())
+ if (m_index < 0 || m_index > m_files.size())
return QString();
- return m_entries.at(m_index - 1);
+ // Returns a full path since contstructing a content path from the file name
+ // and a tree URI only will not point to a valid file URI.
+ return m_files.at(m_index)->uri().toString();
+}
+
+QString AndroidContentFileEngineIterator::currentFilePath() const
+{
+ return currentFileName();
+}
+
+// Start of Cursor
+
+class Cursor
+{
+public:
+ explicit Cursor(const QJNIObjectPrivate &object)
+ : m_object{object} { }
+
+ ~Cursor()
+ {
+ if (m_object.isValid()) {
+ JniExceptionCleaner cleaner;
+ m_object.callMethod<void>("close");
+ }
+ }
+
+ enum Type {
+ FIELD_TYPE_NULL = 0x00000000,
+ FIELD_TYPE_INTEGER = 0x00000001,
+ FIELD_TYPE_FLOAT = 0x00000002,
+ FIELD_TYPE_STRING = 0x00000003,
+ FIELD_TYPE_BLOB = 0x00000004
+ };
+
+ QVariant data(int columnIndex) const
+ {
+ JniExceptionCleaner cleaner;
+ int type = m_object.callMethod<jint>("getType", "(I)I", columnIndex);
+ switch (type) {
+ case FIELD_TYPE_NULL:
+ return {};
+ case FIELD_TYPE_INTEGER:
+ return QVariant::fromValue(m_object.callMethod<jlong>("getLong", "(I)J", columnIndex));
+ case FIELD_TYPE_FLOAT:
+ return QVariant::fromValue(m_object.callMethod<jdouble>("getDouble", "(I)D",
+ columnIndex));
+ case FIELD_TYPE_STRING:
+ return QVariant::fromValue(m_object.callObjectMethod("getString",
+ "(I)Ljava/lang/String;",
+ columnIndex).toString());
+ case FIELD_TYPE_BLOB: {
+ auto blob = m_object.callObjectMethod("getBlob", "(I)[B", columnIndex);
+ QJNIEnvironmentPrivate env;
+ const auto blobArray = static_cast<jbyteArray>(blob.object());
+ const int size = env->GetArrayLength(blobArray);
+ const auto byteArray = env->GetByteArrayElements(blobArray, nullptr);
+ QByteArray data{reinterpret_cast<const char *>(byteArray), size};
+ env->ReleaseByteArrayElements(blobArray, byteArray, 0);
+ return QVariant::fromValue(data);
+ }
+ }
+ return {};
+ }
+
+ static std::unique_ptr<Cursor> queryUri(const QJNIObjectPrivate &uri,
+ const QStringList &projection = {},
+ const QString &selection = {},
+ const QStringList &selectionArgs = {},
+ const QString &sortOrder = {})
+ {
+ JniExceptionCleaner cleaner;
+ auto cursor = contentResolverInstance().callObjectMethod("query",
+ "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;",
+ uri.object(),
+ projection.isEmpty() ? nullptr : fromStringList(projection).object(),
+ selection.isEmpty() ? nullptr : QJNIObjectPrivate::fromString(selection).object(),
+ selectionArgs.isEmpty() ? nullptr : fromStringList(selectionArgs).object(),
+ sortOrder.isEmpty() ? nullptr : QJNIObjectPrivate::fromString(sortOrder).object());
+ if (!cursor.isValid())
+ return {};
+ return std::make_unique<Cursor>(cursor);
+ }
+
+ static QVariant queryColumn(const QJNIObjectPrivate &uri, const QString &column)
+ {
+ const auto query = queryUri(uri, {column});
+ if (!query)
+ return {};
+
+ if (query->rowCount() != 1 || query->columnCount() != 1)
+ return {};
+ query->moveToFirst();
+ return query->data(0);
+ }
+
+ bool isNull(int columnIndex) const
+ {
+ return m_object.callMethod<jboolean>("isNull", "(I)Z", columnIndex);
+ }
+
+ int columnCount() const { return m_object.callMethod<jint>("getColumnCount"); }
+ int rowCount() const { return m_object.callMethod<jint>("getCount"); }
+ int row() const { return m_object.callMethod<jint>("getPosition"); }
+ bool isFirst() const { return m_object.callMethod<jboolean>("isFirst"); }
+ bool isLast() const { return m_object.callMethod<jboolean>("isLast"); }
+ bool moveToFirst() { return m_object.callMethod<jboolean>("moveToFirst"); }
+ bool moveToLast() { return m_object.callMethod<jboolean>("moveToLast"); }
+ bool moveToNext() { return m_object.callMethod<jboolean>("moveToNext"); }
+
+private:
+ static QJNIObjectPrivate fromStringList(const QStringList &list)
+ {
+ QJNIEnvironmentPrivate env;
+ JniExceptionCleaner cleaner;
+ auto array = env->NewObjectArray(list.size(), env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < list.size(); ++i)
+ env->SetObjectArrayElement(array, i, QJNIObjectPrivate::fromString(list[i]).object());
+ return QJNIObjectPrivate::fromLocalRef(array);
+ }
+
+ QJNIObjectPrivate m_object;
+};
+
+// End of Cursor
+
+// Start of DocumentsContract
+
+/*!
+ *
+ * DocumentsContract Api.
+ * Check https://developer.android.com/reference/android/provider/DocumentsContract
+ * for more information.
+ *
+ * \note This does not implement all facilities of the native API.
+ *
+ */
+namespace DocumentsContract
+{
+
+namespace Document {
+const QLatin1String COLUMN_DISPLAY_NAME("_display_name");
+const QLatin1String COLUMN_DOCUMENT_ID("document_id");
+const QLatin1String COLUMN_FLAGS("flags");
+const QLatin1String COLUMN_LAST_MODIFIED("last_modified");
+const QLatin1String COLUMN_MIME_TYPE("mime_type");
+const QLatin1String COLUMN_SIZE("_size");
+
+constexpr int FLAG_DIR_SUPPORTS_CREATE = 0x00000008;
+constexpr int FLAG_SUPPORTS_DELETE = 0x00000004;
+constexpr int FLAG_SUPPORTS_MOVE = 0x00000100;
+constexpr int FLAG_SUPPORTS_RENAME = 0x00000040;
+constexpr int FLAG_SUPPORTS_WRITE = 0x00000002;
+constexpr int FLAG_VIRTUAL_DOCUMENT = 0x00000200;
+
+const QLatin1String MIME_TYPE_DIR("vnd.android.document/directory");
+} // namespace Document
+
+QString documentId(const QJNIObjectPrivate &uri)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "getDocumentId",
+ "(Landroid/net/Uri;)Ljava/lang/String;",
+ uri.object()).toString();
+}
+
+QString treeDocumentId(const QJNIObjectPrivate &uri)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "getTreeDocumentId",
+ "(Landroid/net/Uri;)Ljava/lang/String;",
+ uri.object()).toString();
+}
+
+QJNIObjectPrivate buildChildDocumentsUriUsingTree(const QJNIObjectPrivate &uri, const QString &parentDocumentId)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "buildChildDocumentsUriUsingTree",
+ "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;",
+ uri.object(),
+ QJNIObjectPrivate::fromString(parentDocumentId).object());
+
+}
+
+QJNIObjectPrivate buildDocumentUriUsingTree(const QJNIObjectPrivate &treeUri, const QString &documentId)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "buildDocumentUriUsingTree",
+ "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;",
+ treeUri.object(),
+ QJNIObjectPrivate::fromString(documentId).object());
+}
+
+bool isDocumentUri(const QJNIObjectPrivate &uri)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticMethod<jboolean>("android/provider/DocumentsContract",
+ "isDocumentUri",
+ "(Landroid/content/Context;Landroid/net/Uri;)Z",
+ QtAndroidPrivate::context(),
+ uri.object());
}
+
+bool isTreeUri(const QJNIObjectPrivate &uri)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticMethod<jboolean>("android/provider/DocumentsContract",
+ "isTreeUri",
+ "(Landroid/net/Uri;)Z",
+ uri.object());
+}
+
+QJNIObjectPrivate createDocument(const QJNIObjectPrivate &parentDocumentUri, const QString &mimeType,
+ const QString &displayName)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "createDocument",
+ "(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;",
+ contentResolverInstance().object(),
+ parentDocumentUri.object(),
+ QJNIObjectPrivate::fromString(mimeType).object(),
+ QJNIObjectPrivate::fromString(displayName).object());
+}
+
+bool deleteDocument(const QJNIObjectPrivate &documentUri)
+{
+ const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_DELETE))
+ return {};
+
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticMethod<jboolean>("android/provider/DocumentsContract",
+ "deleteDocument",
+ "(Landroid/content/ContentResolver;Landroid/net/Uri;)Z",
+ contentResolverInstance().object(),
+ documentUri.object());
+}
+
+QJNIObjectPrivate moveDocument(const QJNIObjectPrivate &sourceDocumentUri,
+ const QJNIObjectPrivate &sourceParentDocumentUri,
+ const QJNIObjectPrivate &targetParentDocumentUri)
+{
+ const int flags = Cursor::queryColumn(sourceDocumentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_MOVE))
+ return {};
+
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "moveDocument",
+ "(Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/net/Uri;Landroid/net/Uri;)Landroid/net/Uri;",
+ contentResolverInstance().object(),
+ sourceDocumentUri.object(),
+ sourceParentDocumentUri.object(),
+ targetParentDocumentUri.object());
+}
+
+QJNIObjectPrivate renameDocument(const QJNIObjectPrivate &documentUri, const QString &displayName)
+{
+ const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_RENAME))
+ return {};
+
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/provider/DocumentsContract",
+ "renameDocument",
+ "(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;",
+ contentResolverInstance().object(),
+ documentUri.object(),
+ QJNIObjectPrivate::fromString(displayName).object());
+}
+} // End DocumentsContract namespace
+
+// Start of DocumentFile
+
+using namespace DocumentsContract;
+
+namespace {
+class MakeableDocumentFile : public DocumentFile
+{
+public:
+ MakeableDocumentFile(const QJNIObjectPrivate &uri, const DocumentFilePtr &parent = {})
+ : DocumentFile(uri, parent)
+ {}
+};
+}
+
+DocumentFile::DocumentFile(const QJNIObjectPrivate &uri,
+ const DocumentFilePtr &parent)
+ : m_uri{uri}
+ , m_parent{parent}
+{}
+
+QJNIObjectPrivate parseUri(const QString &uri)
+{
+ JniExceptionCleaner cleaner;
+ return QJNIObjectPrivate::callStaticObjectMethod("android/net/Uri",
+ "parse",
+ "(Ljava/lang/String;)Landroid/net/Uri;",
+ QJNIObjectPrivate::fromString(uri).object());
+}
+
+DocumentFilePtr DocumentFile::parseFromAnyUri(const QString &fileName)
+{
+ const QJNIObjectPrivate uri = parseUri(fileName);
+
+ if (DocumentsContract::isDocumentUri(uri))
+ return fromSingleUri(uri);
+
+ const QString documentType = QLatin1String("/document/");
+ const QString treeType = QLatin1String("/tree/");
+
+ const int treeIndex = fileName.indexOf(treeType);
+ const int documentIndex = fileName.indexOf(documentType);
+ const int index = fileName.lastIndexOf(QLatin1Char('/'));
+
+ if (index <= treeIndex + treeType.size() || index <= documentIndex + documentType.size())
+ return fromTreeUri(uri);
+
+ const QString parentUrl = fileName.left(index);
+ DocumentFilePtr parentDocFile = fromTreeUri(parseUri(parentUrl));
+
+ const QString baseName = fileName.mid(index);
+ const QString fileUrl = parentUrl + QUrl::toPercentEncoding(baseName);
+
+ DocumentFilePtr docFile = std::make_shared<MakeableDocumentFile>(parseUri(fileUrl));
+ if (parentDocFile && parentDocFile->isDirectory())
+ docFile->m_parent = parentDocFile;
+
+ return docFile;
+}
+
+DocumentFilePtr DocumentFile::fromSingleUri(const QJNIObjectPrivate &uri)
+{
+ return std::make_shared<MakeableDocumentFile>(uri);
+}
+
+DocumentFilePtr DocumentFile::fromTreeUri(const QJNIObjectPrivate &treeUri)
+{
+ QString docId;
+ if (isDocumentUri(treeUri))
+ docId = documentId(treeUri);
+ else
+ docId = treeDocumentId(treeUri);
+
+ return std::make_shared<MakeableDocumentFile>(buildDocumentUriUsingTree(treeUri, docId));
+}
+
+DocumentFilePtr DocumentFile::createFile(const QString &mimeType, const QString &displayName)
+{
+ if (isDirectory()) {
+ return std::make_shared<MakeableDocumentFile>(
+ createDocument(m_uri, mimeType, displayName),
+ shared_from_this());
+ }
+ return {};
+}
+
+DocumentFilePtr DocumentFile::createDirectory(const QString &displayName)
+{
+ if (isDirectory()) {
+ return std::make_shared<MakeableDocumentFile>(
+ createDocument(m_uri, Document::MIME_TYPE_DIR, displayName),
+ shared_from_this());
+ }
+ return {};
+}
+
+const QJNIObjectPrivate &DocumentFile::uri() const
+{
+ return m_uri;
+}
+
+const DocumentFilePtr &DocumentFile::parent() const
+{
+ return m_parent;
+}
+
+QString DocumentFile::name() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_DISPLAY_NAME).toString();
+}
+
+QString DocumentFile::id() const
+{
+ return DocumentsContract::documentId(uri());
+}
+
+QString DocumentFile::mimeType() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_MIME_TYPE).toString();
+}
+
+bool DocumentFile::isDirectory() const
+{
+ return mimeType() == Document::MIME_TYPE_DIR;
+}
+
+bool DocumentFile::isFile() const
+{
+ const QString type = mimeType();
+ return type != Document::MIME_TYPE_DIR && !type.isEmpty();
+}
+
+bool DocumentFile::isVirtual() const
+{
+ return isDocumentUri(m_uri) && (Cursor::queryColumn(m_uri,
+ Document::COLUMN_FLAGS).toInt() & Document::FLAG_VIRTUAL_DOCUMENT);
+}
+
+QDateTime DocumentFile::lastModified() const
+{
+ const auto timeVariant = Cursor::queryColumn(m_uri, Document::COLUMN_LAST_MODIFIED);
+ if (timeVariant.isValid())
+ return QDateTime::fromMSecsSinceEpoch(timeVariant.toLongLong());
+ return {};
+}
+
+int64_t DocumentFile::length() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_SIZE).toLongLong();
+}
+
+namespace {
+constexpr int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;
+constexpr int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002;
+}
+
+bool DocumentFile::canRead() const
+{
+ JniExceptionCleaner cleaner;
+ const auto context = QJNIObjectPrivate(QtAndroidPrivate::context());
+ const bool selfUriPermission = context.callMethod<jint>("checkCallingOrSelfUriPermission",
+ "(Landroid/net/Uri;I)I",
+ m_uri.object(),
+ FLAG_GRANT_READ_URI_PERMISSION);
+ if (selfUriPermission != 0)
+ return false;
+
+ return !mimeType().isEmpty();
+}
+
+bool DocumentFile::canWrite() const
+{
+ JniExceptionCleaner cleaner;
+ const auto context = QJNIObjectPrivate(QtAndroidPrivate::context());
+ const bool selfUriPermission = context.callMethod<jint>("checkCallingOrSelfUriPermission",
+ "(Landroid/net/Uri;I)I",
+ m_uri.object(),
+ FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (selfUriPermission != 0)
+ return false;
+
+ const QString type = mimeType();
+ if (type.isEmpty())
+ return false;
+
+ const int flags = Cursor::queryColumn(m_uri, Document::COLUMN_FLAGS).toInt();
+ if (flags & Document::FLAG_SUPPORTS_DELETE)
+ return true;
+
+ const bool supportsWrite = (flags & Document::FLAG_SUPPORTS_WRITE);
+ const bool isDir = (type == Document::MIME_TYPE_DIR);
+ const bool dirSupportsCreate = (isDir && (flags & Document::FLAG_DIR_SUPPORTS_CREATE));
+
+ return dirSupportsCreate || supportsWrite;
+}
+
+bool DocumentFile::remove()
+{
+ return deleteDocument(m_uri);
+}
+
+bool DocumentFile::exists() const
+{
+ return !name().isEmpty();
+}
+
+std::vector<DocumentFilePtr> DocumentFile::listFiles()
+{
+ std::vector<DocumentFilePtr> res;
+ const auto childrenUri = buildChildDocumentsUriUsingTree(m_uri, documentId(m_uri));
+ const auto query = Cursor::queryUri(childrenUri, {Document::COLUMN_DOCUMENT_ID});
+ if (!query)
+ return res;
+
+ while (query->moveToNext()) {
+ const auto uri = buildDocumentUriUsingTree(m_uri, query->data(0).toString());
+ res.push_back(std::make_shared<MakeableDocumentFile>(uri, shared_from_this()));
+ }
+ return res;
+}
+
+bool DocumentFile::rename(const QString &newName)
+{
+ QJNIObjectPrivate uri;
+ if (newName.startsWith(QLatin1String("content://"))) {
+ auto lastSeparatorIndex = [](const QString &file) {
+ int posDecoded = file.lastIndexOf(QLatin1Char('/'));
+ int posEncoded = file.lastIndexOf(QUrl::toPercentEncoding(QLatin1String("/")));
+ return posEncoded > posDecoded ? posEncoded : posDecoded;
+ };
+
+ // first try to see if the new file is under the same tree and thus used rename only
+ const QString parent = m_uri.toString().left(lastSeparatorIndex(m_uri.toString()));
+ if (newName.contains(parent)) {
+ QString displayName = newName.mid(lastSeparatorIndex(newName));
+ if (displayName.startsWith(QLatin1Char('/')))
+ displayName.remove(0, 1);
+ else if (displayName.startsWith(QUrl::toPercentEncoding(QLatin1String("/"))))
+ displayName.remove(0, 3);
+
+ uri = renameDocument(m_uri, displayName);
+ } else {
+ // Move
+ QJNIObjectPrivate srcParentUri = fromTreeUri(parseUri(parent))->uri();
+ const QString destParent = newName.left(lastSeparatorIndex(newName));
+ QJNIObjectPrivate targetParentUri = fromTreeUri(parseUri(destParent))->uri();
+ uri = moveDocument(m_uri, srcParentUri, targetParentUri);
+ }
+ } else {
+ uri = renameDocument(m_uri, newName);
+ }
+
+ if (uri.isValid()) {
+ m_uri = uri;
+ return true;
+ }
+
+ return false;
+}
+
+// End of DocumentFile
diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h
index 31eaf9b0ab..a45f2753d6 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.h
+++ b/src/plugins/platforms/android/androidcontentfileengine.h
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2019 Volker Krause <vkrause@kde.org>
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
@@ -41,20 +42,37 @@
#define ANDROIDCONTENTFILEENGINE_H
#include <private/qfsfileengine_p.h>
+#include <private/qjni_p.h>
+#include <QtCore/qlist.h>
+
+
+using DocumentFilePtr = std::shared_ptr<class DocumentFile>;
class AndroidContentFileEngine : public QFSFileEngine
{
public:
AndroidContentFileEngine(const QString &fileName);
bool open(QIODevice::OpenMode openMode) override;
+ bool close() override;
qint64 size() const override;
+ bool remove() override;
+ bool rename(const QString &newName) override;
+ bool mkdir(const QString &dirName, bool createParentDirectories) const override;
+ bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
+ QByteArray id() const override;
+ bool caseSensitive() const override { return true; }
+ QDateTime fileTime(FileTime time) const override;
FileFlags fileFlags(FileFlags type = FileInfoAll) const override;
QString fileName(FileName file = DefaultName) const override;
QAbstractFileEngine::Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
QAbstractFileEngine::Iterator *endEntryList() override;
+
private:
- QString m_file;
+ void closeNativeFileDescriptor();
+ QString m_initialFile;
+ QJNIObjectPrivate m_pfd;
+ DocumentFilePtr m_documentFile;
};
class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
@@ -73,9 +91,51 @@ public:
QString next() override;
bool hasNext() const override;
QString currentFileName() const override;
+ QString currentFilePath() const override;
private:
- mutable QStringList m_entries;
- mutable int m_index = -1;
+ mutable QList<DocumentFilePtr> m_files;
+ mutable qsizetype m_index = -1;
+};
+
+/*!
+ *
+ * DocumentFile Api.
+ * Check https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile
+ * for more information.
+ *
+ */
+class DocumentFile : public std::enable_shared_from_this<DocumentFile>
+{
+public:
+ static DocumentFilePtr parseFromAnyUri(const QString &filename);
+ static DocumentFilePtr fromSingleUri(const QJNIObjectPrivate &uri);
+ static DocumentFilePtr fromTreeUri(const QJNIObjectPrivate &treeUri);
+
+ DocumentFilePtr createFile(const QString &mimeType, const QString &displayName);
+ DocumentFilePtr createDirectory(const QString &displayName);
+ const QJNIObjectPrivate &uri() const;
+ const DocumentFilePtr &parent() const;
+ QString name() const;
+ QString id() const;
+ QString mimeType() const;
+ bool isDirectory() const;
+ bool isFile() const;
+ bool isVirtual() const;
+ QDateTime lastModified() const;
+ int64_t length() const;
+ bool canRead() const;
+ bool canWrite() const;
+ bool remove();
+ bool exists() const;
+ std::vector<DocumentFilePtr> listFiles();
+ bool rename(const QString &newName);
+
+protected:
+ DocumentFile(const QJNIObjectPrivate &uri, const std::shared_ptr<DocumentFile> &parent);
+
+protected:
+ QJNIObjectPrivate m_uri;
+ DocumentFilePtr m_parent;
};
#endif // ANDROIDCONTENTFILEENGINE_H
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp
index 989d0d18f4..adad9dde98 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.cpp
+++ b/src/plugins/platforms/android/androidjniaccessibility.cpp
@@ -37,6 +37,7 @@
**
****************************************************************************/
+#include "androiddeadlockprotector.h"
#include "androidjniaccessibility.h"
#include "androidjnimain.h"
#include "qandroidplatformintegration.h"
@@ -50,6 +51,7 @@
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/private/qjni_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtCore/QObject>
#include "qdebug.h"
@@ -69,12 +71,49 @@ namespace QtAndroidAccessibility
static jmethodID m_setEnabledMethodID = 0;
static jmethodID m_setFocusableMethodID = 0;
static jmethodID m_setFocusedMethodID = 0;
+ static jmethodID m_setHeadingMethodID = 0;
static jmethodID m_setScrollableMethodID = 0;
static jmethodID m_setTextSelectionMethodID = 0;
static jmethodID m_setVisibleToUserMethodID = 0;
static bool m_accessibilityActivated = false;
+ // This object is needed to schedule the execution of the code that
+ // deals with accessibility instances to the Qt main thread.
+ // Because of that almost every method here is split into two parts.
+ // The _helper part is executed in the context of m_accessibilityContext
+ // on the main thread. The other part is executed in Java thread.
+ static QPointer<QObject> m_accessibilityContext = nullptr;
+
+ // This method is called from the Qt main thread, and normally a
+ // QGuiApplication instance will be used as a parent.
+ void createAccessibilityContextObject(QObject *parent)
+ {
+ if (m_accessibilityContext)
+ m_accessibilityContext->deleteLater();
+ m_accessibilityContext = new QObject(parent);
+ }
+
+ template <typename Func, typename Ret>
+ void runInObjectContext(QObject *context, Func &&func, Ret *retVal)
+ {
+ AndroidDeadlockProtector protector;
+ if (!protector.acquire()) {
+ __android_log_print(ANDROID_LOG_WARN, m_qtTag,
+ "Could not run accessibility call in object context, accessing "
+ "main thread could lead to deadlock");
+ return;
+ }
+
+ if (!QtAndroid::blockEventLoopsWhenSuspended()
+ || QGuiApplication::applicationState() != Qt::ApplicationSuspended) {
+ QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal);
+ } else {
+ __android_log_print(ANDROID_LOG_WARN, m_qtTag,
+ "Could not run accessibility call in object context, event loop suspended.");
+ }
+ }
+
void initialize()
{
QJNIObjectPrivate::callStaticMethod<void>(QtAndroid::applicationClass(),
@@ -110,14 +149,17 @@ namespace QtAndroidAccessibility
return iface;
}
- void notifyLocationChange()
+ void notifyLocationChange(uint accessibilityObjectId)
{
- QtAndroid::notifyAccessibilityLocationChange();
+ QtAndroid::notifyAccessibilityLocationChange(accessibilityObjectId);
}
+ static int parentId_helper(int objectId); // forward declaration
+
void notifyObjectHide(uint accessibilityObjectId)
{
- QtAndroid::notifyObjectHide(accessibilityObjectId);
+ const auto parentObjectId = parentId_helper(accessibilityObjectId);
+ QtAndroid::notifyObjectHide(accessibilityObjectId, parentObjectId);
}
void notifyObjectFocus(uint accessibilityObjectId)
@@ -125,7 +167,20 @@ namespace QtAndroidAccessibility
QtAndroid::notifyObjectFocus(accessibilityObjectId);
}
- static jintArray childIdListForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ static jstring jvalueForAccessibleObject(int objectId); // forward declaration
+
+ void notifyValueChanged(uint accessibilityObjectId)
+ {
+ jstring value = jvalueForAccessibleObject(accessibilityObjectId);
+ QtAndroid::notifyValueChanged(accessibilityObjectId, value);
+ }
+
+ void notifyScrolledEvent(uint accessiblityObjectId)
+ {
+ QtAndroid::notifyScrolledEvent(accessiblityObjectId);
+ }
+
+ static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
if (iface && iface->isValid()) {
@@ -137,6 +192,18 @@ namespace QtAndroidAccessibility
if (child && child->isValid())
ifaceIdArray.append(QAccessible::uniqueId(child));
}
+ return ifaceIdArray;
+ }
+ return {};
+ }
+
+ static jintArray childIdListForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ {
+ if (m_accessibilityContext) {
+ QVarLengthArray<jint, 8> ifaceIdArray;
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return childIdListForAccessibleObject_helper(objectId);
+ }, &ifaceIdArray);
jintArray jArray = env->NewIntArray(jsize(ifaceIdArray.count()));
env->SetIntArrayRegion(jArray, 0, ifaceIdArray.count(), ifaceIdArray.data());
return jArray;
@@ -145,7 +212,7 @@ namespace QtAndroidAccessibility
return env->NewIntArray(jsize(0));
}
- static jint parentId(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ static int parentId_helper(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
if (iface && iface->isValid()) {
@@ -159,7 +226,18 @@ namespace QtAndroidAccessibility
return -1;
}
- static jobject screenRect(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ static jint parentId(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ {
+ jint result = -1;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return parentId_helper(objectId);
+ }, &result);
+ }
+ return result;
+ }
+
+ static QRect screenRect_helper(int objectId, bool clip = true)
{
QRect rect;
QAccessibleInterface *iface = interfaceFromId(objectId);
@@ -167,18 +245,28 @@ namespace QtAndroidAccessibility
rect = QHighDpi::toNativePixels(iface->rect(), iface->window());
}
// If the widget is not fully in-bound in its parent then we have to clip the rectangle to draw
- if (iface && iface->parent() && iface->parent()->isValid()) {
+ if (clip && iface && iface->parent() && iface->parent()->isValid()) {
const auto parentRect = QHighDpi::toNativePixels(iface->parent()->rect(), iface->parent()->window());
rect = rect.intersected(parentRect);
}
+ return rect;
+ }
+ static jobject screenRect(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ {
+ QRect rect;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return screenRect_helper(objectId);
+ }, &rect);
+ }
jclass rectClass = env->FindClass("android/graphics/Rect");
jmethodID ctor = env->GetMethodID(rectClass, "<init>", "(IIII)V");
jobject jrect = env->NewObject(rectClass, ctor, rect.left(), rect.top(), rect.right(), rect.bottom());
return jrect;
}
- static jint hitTest(JNIEnv */*env*/, jobject /*thiz*/, jfloat x, jfloat y)
+ static int hitTest_helper(float x, float y)
{
QAccessibleInterface *root = interfaceFromId(-1);
if (root && root->isValid()) {
@@ -196,17 +284,29 @@ namespace QtAndroidAccessibility
return -1;
}
+ static jint hitTest(JNIEnv */*env*/, jobject /*thiz*/, jfloat x, jfloat y)
+ {
+ jint result = -1;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [x, y]() {
+ return hitTest_helper(x, y);
+ }, &result);
+ }
+ return result;
+ }
+
static void invokeActionOnInterfaceInMainThread(QAccessibleActionInterface* actionInterface,
const QString& action)
{
+ // Queue the action and return back to Java thread, so that we do not
+ // block it for too long
QMetaObject::invokeMethod(qApp, [actionInterface, action]() {
actionInterface->doAction(action);
- });
+ }, Qt::QueuedConnection);
}
- static jboolean clickAction(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ static bool clickAction_helper(int objectId)
{
-// qDebug() << "A11Y: CLICK: " << objectId;
QAccessibleInterface *iface = interfaceFromId(objectId);
if (!iface || !iface->isValid() || !iface->actionInterface())
return false;
@@ -225,20 +325,65 @@ namespace QtAndroidAccessibility
return true;
}
- static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ static jboolean clickAction(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ {
+ bool result = false;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return clickAction_helper(objectId);
+ }, &result);
+ }
+ return result;
+ }
+
+ static bool scroll_helper(int objectId, const QString &actionName)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
if (iface && iface->isValid())
- return QAccessibleBridgeUtils::performEffectiveAction(iface, QAccessibleActionInterface::increaseAction());
+ return QAccessibleBridgeUtils::performEffectiveAction(iface, actionName);
return false;
}
+ static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
+ {
+ bool result = false;
+
+ const auto& ids = childIdListForAccessibleObject_helper(objectId);
+ if (ids.isEmpty())
+ return false;
+
+ const int firstChildId = ids.first();
+ const QRect oldPosition = screenRect_helper(firstChildId, false);
+
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return scroll_helper(objectId, QAccessibleActionInterface::increaseAction());
+ }, &result);
+ }
+
+ // Don't check for position change if the call was not successful
+ return result && oldPosition != screenRect_helper(firstChildId, false);
+ }
+
static jboolean scrollBackward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
{
- QAccessibleInterface *iface = interfaceFromId(objectId);
- if (iface && iface->isValid())
- return QAccessibleBridgeUtils::performEffectiveAction(iface, QAccessibleActionInterface::decreaseAction());
- return false;
+ bool result = false;
+
+ const auto& ids = childIdListForAccessibleObject_helper(objectId);
+ if (ids.isEmpty())
+ return false;
+
+ const int firstChildId = ids.first();
+ const QRect oldPosition = screenRect_helper(firstChildId, false);
+
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return scroll_helper(objectId, QAccessibleActionInterface::decreaseAction());
+ }, &result);
+ }
+
+ // Don't check for position change if the call was not successful
+ return result && oldPosition != screenRect_helper(firstChildId, false);
}
@@ -251,68 +396,182 @@ if (!clazz) { \
//__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE);
+ static QString textFromValue(QAccessibleInterface *iface)
+ {
+ QString valueStr;
+ QAccessibleValueInterface *valueIface = iface->valueInterface();
+ if (valueIface) {
+ const QVariant valueVar = valueIface->currentValue();
+ const auto type = static_cast<QMetaType::Type>(valueVar.type());
+ if (type == QMetaType::Double || type == QMetaType::Float) {
+ // QVariant's toString() formats floating-point values with
+ // FloatingPointShortest, which is not an accessible
+ // representation; nor, in many cases, is it suitable to the UI
+ // element whose value we're looking at. So roll our own
+ // A11Y-friendly conversion to string.
+ const double val = valueVar.toDouble();
+ // Try to use minimumStepSize() to determine precision
+ bool stepIsValid = false;
+ const double step = qAbs(valueIface->minimumStepSize().toDouble(&stepIsValid));
+ if (!stepIsValid || qFuzzyIsNull(step)) {
+ // Ignore step, use default precision
+ valueStr = qFuzzyIsNull(val) ? QStringLiteral("0") : QString::number(val, 'f');
+ } else {
+ const int precision = [](double s) {
+ int count = 0;
+ while (s < 1. && !qFuzzyCompare(s, 1.)) {
+ ++count;
+ s *= 10;
+ }
+ // If s is now 1.25, we want to show some more digits,
+ // but don't want to get silly with a step like 1./7;
+ // so only include a few extra digits.
+ const int stop = count + 3;
+ const auto fractional = [](double v) {
+ double whole = 0.0;
+ std::modf(v + 0.5, &whole);
+ return qAbs(v - whole);
+ };
+ s = fractional(s);
+ while (count < stop && !qFuzzyIsNull(s)) {
+ ++count;
+ s = fractional(s * 10);
+ }
+ return count;
+ }(step);
+ valueStr = qFuzzyIsNull(val / step) ? QStringLiteral("0")
+ : QString::number(val, 'f', precision);
+ }
+ } else {
+ valueStr = valueVar.toString();
+ }
+ }
+ return valueStr;
+ }
+ static jstring jvalueForAccessibleObject(int objectId)
+ {
+ QAccessibleInterface *iface = interfaceFromId(objectId);
+ const QString value = textFromValue(iface);
+ QJNIEnvironmentPrivate env;
+ jstring jstr = env->NewString((jchar*)value.constData(), (jsize)value.size());
+#ifdef QT_DEBUG
+ env->ExceptionDescribe();
+#endif // QT_DEBUG
+ env->ExceptionClear();
+ return jstr;
+ }
- static jstring descriptionForAccessibleObject_helper(JNIEnv *env, QAccessibleInterface *iface)
+ static QString descriptionForInterface(QAccessibleInterface *iface)
{
QString desc;
if (iface && iface->isValid()) {
+ bool hasValue = false;
desc = iface->text(QAccessible::Name);
if (desc.isEmpty())
desc = iface->text(QAccessible::Description);
if (desc.isEmpty()) {
desc = iface->text(QAccessible::Value);
- if (desc.isEmpty()) {
- if (QAccessibleValueInterface *valueIface = iface->valueInterface()) {
- desc= valueIface->currentValue().toString();
- }
+ hasValue = !desc.isEmpty();
+ }
+ if (!hasValue && iface->valueInterface()) {
+ const QString valueStr = textFromValue(iface);
+ if (!valueStr.isEmpty()) {
+ if (!desc.isEmpty())
+ desc.append(QChar(QChar::Space));
+ desc.append(valueStr);
}
}
}
- return env->NewString((jchar*) desc.constData(), (jsize) desc.size());
+ return desc;
}
- static jstring descriptionForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ static QString descriptionForAccessibleObject_helper(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
- return descriptionForAccessibleObject_helper(env, iface);
+ return descriptionForInterface(iface);
}
- static bool populateNode(JNIEnv *env, jobject /*thiz*/, jint objectId, jobject node)
+ static jstring descriptionForAccessibleObject(JNIEnv *env, jobject /*thiz*/, jint objectId)
+ {
+ QString desc;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return descriptionForAccessibleObject_helper(objectId);
+ }, &desc);
+ }
+ return env->NewString((jchar*) desc.constData(), (jsize) desc.size());
+ }
+
+
+ struct NodeInfo
+ {
+ bool valid = false;
+ QAccessible::State state;
+ QAccessible::Role role;
+ QStringList actions;
+ QString description;
+ bool hasTextSelection = false;
+ int selectionStart = 0;
+ int selectionEnd = 0;
+ };
+
+ static NodeInfo populateNode_helper(int objectId)
{
+ NodeInfo info;
QAccessibleInterface *iface = interfaceFromId(objectId);
- if (!iface || !iface->isValid()) {
+ if (iface && iface->isValid()) {
+ info.valid = true;
+ info.state = iface->state();
+ info.role = iface->role();
+ info.actions = QAccessibleBridgeUtils::effectiveActionNames(iface);
+ info.description = descriptionForInterface(iface);
+ QAccessibleTextInterface *textIface = iface->textInterface();
+ if (textIface && (textIface->selectionCount() > 0)) {
+ info.hasTextSelection = true;
+ textIface->selection(0, &info.selectionStart, &info.selectionEnd);
+ }
+ }
+ return info;
+ }
+
+ static jboolean populateNode(JNIEnv *env, jobject /*thiz*/, jint objectId, jobject node)
+ {
+ NodeInfo info;
+ if (m_accessibilityContext) {
+ runInObjectContext(m_accessibilityContext, [objectId]() {
+ return populateNode_helper(objectId);
+ }, &info);
+ }
+ if (!info.valid) {
__android_log_print(ANDROID_LOG_WARN, m_qtTag, "Accessibility: populateNode for Invalid ID");
return false;
}
- QAccessible::State state = iface->state();
- const QStringList actions = QAccessibleBridgeUtils::effectiveActionNames(iface);
- const bool hasClickableAction = actions.contains(QAccessibleActionInterface::pressAction())
- || actions.contains(QAccessibleActionInterface::toggleAction());
- const bool hasIncreaseAction = actions.contains(QAccessibleActionInterface::increaseAction());
- const bool hasDecreaseAction = actions.contains(QAccessibleActionInterface::decreaseAction());
- // try to fill in the text property, this is what the screen reader reads
- jstring jdesc = descriptionForAccessibleObject_helper(env, iface);
-
- if (QAccessibleTextInterface *textIface = iface->textInterface()) {
- if (m_setTextSelectionMethodID && textIface->selectionCount() > 0) {
- int startSelection;
- int endSelection;
- textIface->selection(0, &startSelection, &endSelection);
- env->CallVoidMethod(node, m_setTextSelectionMethodID, startSelection, endSelection);
- }
+ const bool hasClickableAction =
+ info.actions.contains(QAccessibleActionInterface::pressAction()) ||
+ info.actions.contains(QAccessibleActionInterface::toggleAction());
+ const bool hasIncreaseAction =
+ info.actions.contains(QAccessibleActionInterface::increaseAction());
+ const bool hasDecreaseAction =
+ info.actions.contains(QAccessibleActionInterface::decreaseAction());
+
+ if (info.hasTextSelection && m_setTextSelectionMethodID) {
+ env->CallVoidMethod(node, m_setTextSelectionMethodID, info.selectionStart,
+ info.selectionEnd);
}
- env->CallVoidMethod(node, m_setCheckableMethodID, (bool)state.checkable);
- env->CallVoidMethod(node, m_setCheckedMethodID, (bool)state.checked);
- env->CallVoidMethod(node, m_setEditableMethodID, state.editable);
- env->CallVoidMethod(node, m_setEnabledMethodID, !state.disabled);
- env->CallVoidMethod(node, m_setFocusableMethodID, (bool)state.focusable);
- env->CallVoidMethod(node, m_setFocusedMethodID, (bool)state.focused);
- env->CallVoidMethod(node, m_setVisibleToUserMethodID, !state.invisible);
+ env->CallVoidMethod(node, m_setCheckableMethodID, (bool)info.state.checkable);
+ env->CallVoidMethod(node, m_setCheckedMethodID, (bool)info.state.checked);
+ env->CallVoidMethod(node, m_setEditableMethodID, info.state.editable);
+ env->CallVoidMethod(node, m_setEnabledMethodID, !info.state.disabled);
+ env->CallVoidMethod(node, m_setFocusableMethodID, (bool)info.state.focusable);
+ env->CallVoidMethod(node, m_setFocusedMethodID, (bool)info.state.focused);
+ if (m_setHeadingMethodID)
+ env->CallVoidMethod(node, m_setHeadingMethodID, info.role == QAccessible::Heading);
+ env->CallVoidMethod(node, m_setVisibleToUserMethodID, !info.state.invisible);
env->CallVoidMethod(node, m_setScrollableMethodID, hasIncreaseAction || hasDecreaseAction);
- env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction);
+ env->CallVoidMethod(node, m_setClickableMethodID, hasClickableAction || info.role == QAccessible::Link);
// Add ACTION_CLICK
if (hasClickableAction)
@@ -326,7 +585,9 @@ if (!clazz) { \
if (hasDecreaseAction)
env->CallVoidMethod(node, m_addActionMethodID, (int)0x00002000); // ACTION_SCROLL_BACKWARD defined in AccessibilityNodeInfo
-
+ // try to fill in the text property, this is what the screen reader reads
+ jstring jdesc = env->NewString((jchar*)info.description.constData(),
+ (jsize)info.description.size());
//CALL_METHOD(node, "setText", "(Ljava/lang/CharSequence;)V", jdesc)
env->CallVoidMethod(node, m_setContentDescriptionMethodID, jdesc);
@@ -374,6 +635,9 @@ if (!clazz) { \
GET_AND_CHECK_STATIC_METHOD(m_setEnabledMethodID, nodeInfoClass, "setEnabled", "(Z)V");
GET_AND_CHECK_STATIC_METHOD(m_setFocusableMethodID, nodeInfoClass, "setFocusable", "(Z)V");
GET_AND_CHECK_STATIC_METHOD(m_setFocusedMethodID, nodeInfoClass, "setFocused", "(Z)V");
+ if (QtAndroidPrivate::androidSdkVersion() >= 28) {
+ GET_AND_CHECK_STATIC_METHOD(m_setHeadingMethodID, nodeInfoClass, "setHeading", "(Z)V");
+ }
GET_AND_CHECK_STATIC_METHOD(m_setScrollableMethodID, nodeInfoClass, "setScrollable", "(Z)V");
GET_AND_CHECK_STATIC_METHOD(m_setVisibleToUserMethodID, nodeInfoClass, "setVisibleToUser", "(Z)V");
GET_AND_CHECK_STATIC_METHOD(m_setTextSelectionMethodID, nodeInfoClass, "setTextSelection", "(II)V");
diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h
index de9d32a099..212131ff62 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.h
+++ b/src/plugins/platforms/android/androidjniaccessibility.h
@@ -44,14 +44,19 @@
QT_BEGIN_NAMESPACE
+class QObject;
+
namespace QtAndroidAccessibility
{
void initialize();
bool isActive();
bool registerNatives(JNIEnv *env);
- void notifyLocationChange();
+ void notifyLocationChange(uint accessibilityObjectId);
void notifyObjectHide(uint accessibilityObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
+ void notifyValueChanged(uint accessibilityObjectId);
+ void notifyScrolledEvent(uint accessibilityObjectId);
+ void createAccessibilityContextObject(QObject *parent);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjniclipboard.cpp b/src/plugins/platforms/android/androidjniclipboard.cpp
index c20ac456b1..763ecc0f62 100644
--- a/src/plugins/platforms/android/androidjniclipboard.cpp
+++ b/src/plugins/platforms/android/androidjniclipboard.cpp
@@ -70,26 +70,24 @@ namespace QtAndroidClipboard
void setClipboardMimeData(QMimeData *data)
{
clearClipboardData();
- if (data->hasText()) {
+ if (data->hasUrls()) {
+ QList<QUrl> urls = data->urls();
+ for (const auto &u : qAsConst(urls)) {
+ QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "setClipboardUri",
+ "(Ljava/lang/String;)V",
+ QJNIObjectPrivate::fromString(u.toEncoded()).object());
+ }
+ } else if (data->hasText()) { // hasText || hasUrls, so the order matter here.
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
"setClipboardText", "(Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(data->text()).object());
- }
- if (data->hasHtml()) {
+ } else if (data->hasHtml()) {
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
"setClipboardHtml",
"(Ljava/lang/String;Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(data->text()).object(),
QJNIObjectPrivate::fromString(data->html()).object());
}
- if (data->hasUrls()) {
- QList<QUrl> urls = data->urls();
- for (const auto &u : qAsConst(urls)) {
- QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "setClipboardUri",
- "(Ljava/lang/String;)V",
- QJNIObjectPrivate::fromString(u.toEncoded()).object());
- }
- }
}
QMimeData *getClipboardMimeData()
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index fe1aff0cc4..bb77752734 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -59,7 +59,6 @@ using namespace QtAndroid;
namespace QtAndroidInput
{
static bool m_ignoreMouseEvents = false;
- static bool m_softwareKeyboardVisible = false;
static QRect m_softwareKeyboardRect;
static QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
@@ -80,16 +79,15 @@ namespace QtAndroidInput
candidatesEnd);
}
- void showSoftwareKeyboard(int left, int top, int width, int height, int editorHeight, int inputHints, int enterKeyType)
+ void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
"showSoftwareKeyboard",
- "(IIIIIII)V",
+ "(IIIIII)V",
left,
top,
width,
height,
- editorHeight,
inputHints,
enterKeyType);
#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
@@ -115,7 +113,7 @@ namespace QtAndroidInput
bool isSoftwareKeyboardVisible()
{
- return m_softwareKeyboardVisible;
+ return QJNIObjectPrivate::callStaticMethod<jboolean>(applicationClass(), "isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
@@ -123,6 +121,11 @@ namespace QtAndroidInput
return m_softwareKeyboardRect;
}
+ int getSelectHandleWidth()
+ {
+ return QJNIObjectPrivate::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth");
+ }
+
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
QJNIObjectPrivate::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V",
@@ -131,17 +134,6 @@ namespace QtAndroidInput
anchor.x(), anchor.y(), rtl);
}
- void updateInputItemRectangle(int left, int top, int width, int height)
- {
- QJNIObjectPrivate::callStaticMethod<void>(applicationClass(),
- "updateInputItemRectangle",
- "(IIII)V",
- left,
- top,
- width,
- height);
- }
-
static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
{
if (m_ignoreMouseEvents)
@@ -276,18 +268,14 @@ namespace QtAndroidInput
}
}
- static void touchEnd(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/)
+ static QTouchDevice *getTouchDevice()
{
- if (m_touchPoints.isEmpty())
- return;
-
- QMutexLocker lock(QtAndroid::platformInterfaceMutex());
QAndroidPlatformIntegration *platformIntegration = QtAndroid::androidPlatformIntegration();
if (!platformIntegration)
- return;
+ return nullptr;
QTouchDevice *touchDevice = platformIntegration->touchDevice();
- if (touchDevice == 0) {
+ if (!touchDevice) {
touchDevice = new QTouchDevice;
touchDevice->setType(QTouchDevice::TouchScreen);
touchDevice->setCapabilities(QTouchDevice::Position
@@ -298,10 +286,37 @@ namespace QtAndroidInput
platformIntegration->setTouchDevice(touchDevice);
}
+ return touchDevice;
+ }
+
+ static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/)
+ {
+ if (m_touchPoints.isEmpty())
+ return;
+
+ QMutexLocker lock(QtAndroid::platformInterfaceMutex());
+ QTouchDevice *touchDevice = getTouchDevice();
+ if (!touchDevice)
+ return;
+
QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
QWindowSystemInterface::handleTouchEvent(window, touchDevice, m_touchPoints);
}
+ static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/)
+ {
+ if (m_touchPoints.isEmpty())
+ return;
+
+ QMutexLocker lock(QtAndroid::platformInterfaceMutex());
+ QTouchDevice *touchDevice = getTouchDevice();
+ if (!touchDevice)
+ return;
+
+ QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
+ QWindowSystemInterface::handleTouchCancelEvent(window, touchDevice);
+ }
+
static bool isTabletEventSupported(JNIEnv */*env*/, jobject /*thiz*/)
{
#if QT_CONFIG(tabletevent)
@@ -806,7 +821,6 @@ namespace QtAndroidInput
static void keyboardVisibilityChanged(JNIEnv */*env*/, jobject /*thiz*/, jboolean visibility)
{
- m_softwareKeyboardVisible = visibility;
if (!visibility)
m_softwareKeyboardRect = QRect();
@@ -854,6 +868,7 @@ namespace QtAndroidInput
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(II)V",(void*)touchEnd},
+ {"touchCancel", "(I)V", (void *)touchCancel},
{"mouseDown", "(III)V", (void *)mouseDown},
{"mouseUp", "(III)V", (void *)mouseUp},
{"mouseMove", "(III)V", (void *)mouseMove},
diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h
index c1442f1904..4b2aef07ca 100644
--- a/src/plugins/platforms/android/androidjniinput.h
+++ b/src/plugins/platforms/android/androidjniinput.h
@@ -49,7 +49,7 @@ QT_BEGIN_NAMESPACE
namespace QtAndroidInput
{
// Software keyboard support
- void showSoftwareKeyboard(int top, int left, int width, int editorHeight, int height, int inputHints, int enterKeyType);
+ void showSoftwareKeyboard(int top, int left, int width, int height, int inputHints, int enterKeyType);
void resetSoftwareKeyboard();
void hideSoftwareKeyboard();
bool isSoftwareKeyboardVisible();
@@ -57,11 +57,11 @@ namespace QtAndroidInput
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd);
// Software keyboard support
- // edit field resize
- void updateInputItemRectangle(int left, int top, int width, int height);
// cursor/selection handles
void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false);
+ int getSelectHandleWidth();
+
bool registerNatives(JNIEnv *env);
}
diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp
index 9e4007b37a..577a7905b9 100644
--- a/src/plugins/platforms/android/androidjnimain.cpp
+++ b/src/plugins/platforms/android/androidjnimain.cpp
@@ -1,7 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
@@ -60,10 +60,11 @@
#include "qandroideventdispatcher.h"
#include <android/api-level.h>
-#include <QtCore/qresource.h>
-#include <QtCore/qthread.h>
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/private/qjni_p.h>
+#include <QtCore/qbasicatomic.h>
+#include <QtCore/qresource.h>
+#include <QtCore/qthread.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
@@ -106,7 +107,6 @@ static sem_t m_exitSemaphore, m_terminateSemaphore;
QHash<int, AndroidSurfaceClient *> m_surfaces;
static QBasicMutex m_surfacesMutex;
-static int m_surfaceId = 1;
static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr;
@@ -125,6 +125,8 @@ static const char m_qtTag[] = "Qt";
static const char m_classErrorMsg[] = "Can't find class \"%s\"";
static const char m_methodErrorMsg[] = "Can't find method \"%s%s\"";
+static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
+
namespace QtAndroid
{
QBasicMutex *platformInterfaceMutex()
@@ -223,14 +225,17 @@ namespace QtAndroid
m_statusBarShowing = false;
}
- void notifyAccessibilityLocationChange()
+ void notifyAccessibilityLocationChange(uint accessibilityObjectId)
{
- QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyAccessibilityLocationChange");
+ QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass,
+ "notifyAccessibilityLocationChange",
+ "(I)V", accessibilityObjectId);
}
- void notifyObjectHide(uint accessibilityObjectId)
+ void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId)
{
- QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectHide","(I)V", accessibilityObjectId);
+ QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectHide", "(II)V",
+ accessibilityObjectId, parentObjectId);
}
void notifyObjectFocus(uint accessibilityObjectId)
@@ -238,6 +243,24 @@ namespace QtAndroid
QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId);
}
+ void notifyValueChanged(uint accessibilityObjectId, jstring value)
+ {
+ QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyValueChanged",
+ "(ILjava/lang/String;)V", accessibilityObjectId,
+ value);
+ }
+
+ void notifyScrolledEvent(uint accessibilityObjectId)
+ {
+ QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyScrolledEvent", "(I)V",
+ accessibilityObjectId);
+ }
+
+ void notifyQtAndroidPluginRunning(bool running)
+ {
+ QJNIObjectPrivate::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running);
+ }
+
jobject createBitmap(QImage img, JNIEnv *env)
{
if (!m_bitmapClass)
@@ -332,6 +355,12 @@ namespace QtAndroid
return manufacturer + QLatin1Char(' ') + model;
}
+ jint generateViewId()
+ {
+ return QJNIObjectPrivate::callStaticMethod<jint>("android/view/View","generateViewId",
+ "()I");
+ }
+
int createSurface(AndroidSurfaceClient *client, const QRect &geometry, bool onTop, int imageDepth)
{
QJNIEnvironmentPrivate env;
@@ -339,7 +368,7 @@ namespace QtAndroid
return -1;
m_surfacesMutex.lock();
- int surfaceId = m_surfaceId++;
+ jint surfaceId = generateViewId();
m_surfaces[surfaceId] = client;
m_surfacesMutex.unlock();
@@ -362,7 +391,7 @@ namespace QtAndroid
int insertNativeView(jobject view, const QRect &geometry)
{
m_surfacesMutex.lock();
- const int surfaceId = m_surfaceId++;
+ jint surfaceId = generateViewId();
m_surfaces[surfaceId] = nullptr; // dummy
m_surfacesMutex.unlock();
@@ -546,15 +575,21 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/)
vm->AttachCurrentThread(&env, &args);
}
+ // Register type for invokeMethod() calls.
+ qRegisterMetaType<Qt::ScreenOrientation>("Qt::ScreenOrientation");
+
// Register resources if they are available
if (QFile{QStringLiteral("assets:/android_rcc_bundle.rcc")}.exists())
QResource::registerResource(QStringLiteral("assets:/android_rcc_bundle.rcc"));
- QVarLengthArray<const char *> params(m_applicationParams.size());
- for (int i = 0; i < m_applicationParams.size(); i++)
- params[i] = static_cast<const char *>(m_applicationParams[i].constData());
+ const int argc = m_applicationParams.size();
+ QVarLengthArray<char *> argv(argc + 1);
+ for (int i = 0; i < argc; i++)
+ argv[i] = m_applicationParams[i].data();
+ argv[argc] = nullptr;
- int ret = m_main(m_applicationParams.length(), const_cast<char **>(params.data()));
+ startQtAndroidPluginCalled.fetchAndAddRelease(1);
+ int ret = m_main(argc, argv.data());
if (m_mainLibraryHnd) {
int res = dlclose(m_mainLibraryHnd);
@@ -601,7 +636,9 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
QAndroidEventDispatcherStopper::instance()->goingToStop(false);
}
- sem_wait(&m_terminateSemaphore);
+ if (startQtAndroidPluginCalled.loadAcquire())
+ sem_wait(&m_terminateSemaphore);
+
sem_destroy(&m_terminateSemaphore);
env->DeleteGlobalRef(m_applicationClass);
@@ -644,32 +681,28 @@ static void setDisplayMetrics(JNIEnv */*env*/, jclass /*clazz*/,
jint widthPixels, jint heightPixels,
jint desktopWidthPixels, jint desktopHeightPixels,
jdouble xdpi, jdouble ydpi,
- jdouble scaledDensity, jdouble density)
+ jdouble scaledDensity, jdouble density, jfloat refreshRate)
{
- // Android does not give us the correct screen size for immersive mode, but
- // the surface does have the right size
-
- widthPixels = qMax(widthPixels, desktopWidthPixels);
- heightPixels = qMax(heightPixels, desktopHeightPixels);
-
m_desktopWidthPixels = desktopWidthPixels;
m_desktopHeightPixels = desktopHeightPixels;
m_scaledDensity = scaledDensity;
m_density = density;
+ const QSize screenSize(widthPixels, heightPixels);
+ // available geometry always starts from top left
+ const QRect availableGeometry(0, 0, desktopWidthPixels, desktopHeightPixels);
+ const QSize physicalSize(qRound(double(widthPixels) / xdpi * 25.4),
+ qRound(double(heightPixels) / ydpi * 25.4));
+
QMutexLocker lock(&m_platformMutex);
if (!m_androidPlatformIntegration) {
- QAndroidPlatformIntegration::setDefaultDisplayMetrics(desktopWidthPixels,
- desktopHeightPixels,
- qRound(double(widthPixels) / xdpi * 25.4),
- qRound(double(heightPixels) / ydpi * 25.4),
- widthPixels,
- heightPixels);
+ QAndroidPlatformIntegration::setDefaultDisplayMetrics(
+ availableGeometry.width(), availableGeometry.height(), physicalSize.width(),
+ physicalSize.height(), screenSize.width(), screenSize.height());
} else {
- m_androidPlatformIntegration->setDisplayMetrics(qRound(double(widthPixels) / xdpi * 25.4),
- qRound(double(heightPixels) / ydpi * 25.4));
- m_androidPlatformIntegration->setScreenSize(widthPixels, heightPixels);
- m_androidPlatformIntegration->setDesktopSize(desktopWidthPixels, desktopHeightPixels);
+ m_androidPlatformIntegration->setScreenSizeParameters(physicalSize, screenSize,
+ availableGeometry);
+ m_androidPlatformIntegration->setRefreshRate(refreshRate);
}
}
@@ -764,12 +797,22 @@ static void handleOrientationChanged(JNIEnv */*env*/, jobject /*thiz*/, jint new
QAndroidPlatformIntegration::setScreenOrientation(screenOrientation, native);
QMutexLocker lock(&m_platformMutex);
if (m_androidPlatformIntegration) {
- QPlatformScreen *screen = m_androidPlatformIntegration->screen();
- QWindowSystemInterface::handleScreenOrientationChange(screen->screen(),
- screenOrientation);
+ QAndroidPlatformScreen *screen = m_androidPlatformIntegration->screen();
+ // Use invokeMethod to keep the certain order of the "geometry change"
+ // and "orientation change" event handling.
+ if (screen) {
+ QMetaObject::invokeMethod(screen, "setOrientation", Qt::AutoConnection,
+ Q_ARG(Qt::ScreenOrientation, screenOrientation));
+ }
}
}
+static void handleRefreshRateChanged(JNIEnv */*env*/, jclass /*cls*/, jfloat refreshRate)
+{
+ if (m_androidPlatformIntegration)
+ m_androidPlatformIntegration->setRefreshRate(refreshRate);
+}
+
static void onActivityResult(JNIEnv */*env*/, jclass /*cls*/,
jint requestCode,
jint resultCode,
@@ -795,14 +838,15 @@ static JNINativeMethod methods[] = {
{"quitQtCoreApplication", "()V", (void *)quitQtCoreApplication},
{"terminateQt", "()V", (void *)terminateQt},
{"waitForServiceSetup", "()V", (void *)waitForServiceSetup},
- {"setDisplayMetrics", "(IIIIDDDD)V", (void *)setDisplayMetrics},
+ {"setDisplayMetrics", "(IIIIDDDDF)V", (void *)setDisplayMetrics},
{"setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface},
{"updateWindow", "()V", (void *)updateWindow},
{"updateApplicationState", "(I)V", (void *)updateApplicationState},
{"handleOrientationChanged", "(II)V", (void *)handleOrientationChanged},
{"onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult},
{"onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent},
- {"onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind}
+ {"onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind},
+ {"handleRefreshRateChanged", "(F)V", (void *)handleRefreshRateChanged}
};
#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h
index 72b864de19..cc2839c20e 100644
--- a/src/plugins/platforms/android/androidjnimain.h
+++ b/src/plugins/platforms/android/androidjnimain.h
@@ -95,9 +95,12 @@ namespace QtAndroid
jobject createBitmap(int width, int height, QImage::Format format, JNIEnv *env);
jobject createBitmapDrawable(jobject bitmap, JNIEnv *env = 0);
- void notifyAccessibilityLocationChange();
- void notifyObjectHide(uint accessibilityObjectId);
+ void notifyAccessibilityLocationChange(uint accessibilityObjectId);
+ void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
+ void notifyValueChanged(uint accessibilityObjectId, jstring value);
+ void notifyScrolledEvent(uint accessibilityObjectId);
+ void notifyQtAndroidPluginRunning(bool running);
const char *classErrorMsgFmt();
const char *methodErrorMsgFmt();
diff --git a/src/plugins/platforms/android/extract-dummy.cpp b/src/plugins/platforms/android/extract-dummy.cpp
index fdce8ec64c..8cd317be4d 100644
--- a/src/plugins/platforms/android/extract-dummy.cpp
+++ b/src/plugins/platforms/android/extract-dummy.cpp
@@ -44,8 +44,3 @@ extern "C" JNIEXPORT jintArray JNICALL Java_org_qtproject_qt5_android_ExtractSty
{
return 0;
}
-
-extern "C" JNIEXPORT jintArray JNICALL Java_org_qtproject_qt5_android_ExtractStyle_extractChunkInfo20(JNIEnv *, jobject, jbyteArray)
-{
- return 0;
-}
diff --git a/src/plugins/platforms/android/extract.cpp b/src/plugins/platforms/android/extract.cpp
index acffa353f1..6ce6153966 100644
--- a/src/plugins/platforms/android/extract.cpp
+++ b/src/plugins/platforms/android/extract.cpp
@@ -1,5 +1,6 @@
/****************************************************************************
**
+** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
** Contact: https://www.qt.io/licensing/
**
@@ -123,20 +124,6 @@ extern "C" JNIEXPORT jintArray JNICALL Java_org_qtproject_qt5_android_ExtractSty
return result;
}
-extern "C" JNIEXPORT jintArray JNICALL Java_org_qtproject_qt5_android_ExtractStyle_extractChunkInfo20(JNIEnv * env, jobject obj, jbyteArray chunkObj)
-{
- size_t chunkSize = env->GetArrayLength(chunkObj);
- void* storage = alloca(chunkSize);
- env->GetByteArrayRegion(chunkObj, 0, chunkSize,
- reinterpret_cast<jbyte*>(storage));
-
- if (!env->ExceptionCheck())
- return Java_org_qtproject_qt5_android_ExtractStyle_extractNativeChunkInfo20(env, obj, long(storage));
- else
- env->ExceptionClear();
- return 0;
-}
-
static inline void fill9patchOffsets(Res_png_9patch20* patch) {
patch->xDivsOffset = sizeof(Res_png_9patch20);
patch->yDivsOffset = patch->xDivsOffset + (patch->numXDivs * sizeof(int32_t));
diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
index 07776a4a76..180dc248d6 100644
--- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
+++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
@@ -86,6 +86,7 @@ struct AssetItem {
}
Type type = Type::File;
QString name;
+ qint64 size = -1;
};
using AssetItemList = QVector<AssetItem>;
@@ -139,6 +140,8 @@ public:
FolderIterator(const QString &path)
: m_path(path)
{
+ // Note that empty dirs in the assets dir before the build are not going to be
+ // included in the final apk, so no empty folders should expected to be listed.
QJNIObjectPrivate files = QJNIObjectPrivate::callStaticObjectMethod(QtAndroid::applicationClass(),
"listAssetContent",
"(Landroid/content/res/AssetManager;Ljava/lang/String;)[Ljava/lang/String;",
@@ -217,7 +220,7 @@ public:
return m_currentIterator->currentFileName();
}
- virtual QString currentFilePath() const
+ QString currentFilePath() const override
{
if (!m_currentIterator)
return {};
@@ -261,7 +264,7 @@ public:
bool open(QIODevice::OpenMode openMode) override
{
- if (m_isFolder || (openMode & QIODevice::WriteOnly))
+ if (!m_assetInfo || m_assetInfo->type != AssetItem::Type::File || (openMode & QIODevice::WriteOnly))
return false;
close();
m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
@@ -275,14 +278,13 @@ public:
m_assetFile = 0;
return true;
}
- m_isFolder = false;
return false;
}
qint64 size() const override
{
- if (m_assetFile)
- return AAsset_getLength(m_assetFile);
+ if (m_assetInfo)
+ return m_assetInfo->size;
return -1;
}
@@ -326,10 +328,12 @@ public:
{
FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
FileFlags flags;
- if (m_assetFile)
- flags = FileType | commonFlags;
- else if (m_isFolder)
- flags = DirectoryType | commonFlags;
+ if (m_assetInfo) {
+ if (m_assetInfo->type == AssetItem::Type::File)
+ flags = FileType | commonFlags;
+ else if (m_assetInfo->type == AssetItem::Type::Folder)
+ flags = DirectoryType | commonFlags;
+ }
return type & flags;
}
@@ -364,21 +368,48 @@ public:
return;
close();
m_fileName = cleanedAssetPath(file);
- switch (FolderIterator::fileType(m_fileName)) {
- case AssetItem::Type::File:
- open(QIODevice::ReadOnly);
- break;
- case AssetItem::Type::Folder:
- m_isFolder = true;
- break;
- case AssetItem::Type::Invalid:
- break;
+
+ {
+ QMutexLocker lock(&m_assetsInfoCacheMutex);
+ QSharedPointer<AssetItem> *assetInfoPtr = m_assetsInfoCache.object(m_fileName);
+ if (assetInfoPtr) {
+ m_assetInfo = *assetInfoPtr;
+ return;
+ }
}
+
+ QSharedPointer<AssetItem> *newAssetInfoPtr = new QSharedPointer<AssetItem>(new AssetItem);
+
+ m_assetInfo = *newAssetInfoPtr;
+ m_assetInfo->name = m_fileName;
+ m_assetInfo->type = AssetItem::Type::Invalid;
+
+ m_assetFile = AAssetManager_open(m_assetManager, m_fileName.toUtf8(), AASSET_MODE_BUFFER);
+
+ if (m_assetFile) {
+ m_assetInfo->type = AssetItem::Type::File;
+ m_assetInfo->size = AAsset_getLength(m_assetFile);
+ } else {
+ auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8());
+ if (assetDir) {
+ if (AAssetDir_getNextFileName(assetDir)
+ || (!FolderIterator::fromCache(m_fileName, false)->empty())) {
+ // If AAssetDir_getNextFileName is not valid, it still can be a directory that
+ // contains only other directories (no files). FolderIterator will not be called
+ // on the directory containing files so it should not be too time consuming now.
+ m_assetInfo->type = AssetItem::Type::Folder;
+ }
+ AAssetDir_close(assetDir);
+ }
+ }
+
+ QMutexLocker lock(&m_assetsInfoCacheMutex);
+ m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr);
}
Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override
{
- if (m_isFolder)
+ if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder)
return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName);
return nullptr;
}
@@ -388,9 +419,14 @@ private:
AAssetManager *m_assetManager = nullptr;
// initialize with a name that can't be used as a file name
QString m_fileName = QLatin1String(".");
- bool m_isFolder = false;
+ QSharedPointer<AssetItem> m_assetInfo;
+
+ static QCache<QString, QSharedPointer<AssetItem>> m_assetsInfoCache;
+ static QMutex m_assetsInfoCacheMutex;
};
+QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE")));
+QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex;
AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler()
{
diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp
index 687cced1e2..8a44482d44 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.cpp
+++ b/src/plugins/platforms/android/qandroidinputcontext.cpp
@@ -95,6 +95,7 @@ private:
static QAndroidInputContext *m_androidInputContext = 0;
static char const *const QtNativeInputConnectionClassName = "org/qtproject/qt5/android/QtNativeInputConnection";
static char const *const QtExtractedTextClassName = "org/qtproject/qt5/android/QtExtractedText";
+static char const *const QtObjectType = "QDialog";
static jclass m_extractedTextClass = 0;
static jmethodID m_classConstructorMethodID = 0;
static jfieldID m_partialEndOffsetFieldID = 0;
@@ -506,14 +507,19 @@ QAndroidInputContext::QAndroidInputContext()
m_androidInputContext = this;
QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
- this, &QAndroidInputContext::updateInputItemRectangle);
+ this, &QAndroidInputContext::updateSelectionHandles);
QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::anchorRectangleChanged,
this, &QAndroidInputContext::updateSelectionHandles);
QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::inputItemClipRectangleChanged, this, [this]{
auto im = qGuiApp->inputMethod();
if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) ||
!im->inputItemClipRectangle().contains(im->cursorRectangle())) {
- m_handleMode = Hidden;
+ // Undoes the hidden request if the only reason for the hidden is that
+ // X of the cursorRectangle or X of the anchorRectangle is less than 0.
+ const int rectX = im->inputItemClipRectangle().x();
+ if (im->cursorRectangle().x() > rectX && im->anchorRectangle().x() > rectX)
+ m_handleMode = Hidden;
+
updateSelectionHandles();
}
});
@@ -622,13 +628,13 @@ void QAndroidInputContext::updateSelectionHandles()
if (noHandles)
return;
+ QWindow *window = qGuiApp->focusWindow();
auto im = qGuiApp->inputMethod();
- if (!m_focusObject || ((m_handleMode & 0xff) == Hidden)) {
+ if (!m_focusObject || ((m_handleMode & 0xff) == Hidden) || !window) {
// Hide the handles
QtAndroidInput::updateHandles(Hidden);
return;
}
- QWindow *window = qGuiApp->focusWindow();
double pixelDensity = window
? QHighDpiScaling::factor(window)
: QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen());
@@ -646,14 +652,25 @@ void QAndroidInputContext::updateSelectionHandles()
}
auto curRect = im->cursorRectangle();
- QPoint cursorPoint = qGuiApp->focusWindow()->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
- QPoint editMenuPoint(cursorPoint.x(), cursorPoint.y());
+ QPoint cursorPointGlobal = window->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
+ QPoint cursorPoint(curRect.center().x(), curRect.bottom());
+ int x = curRect.x();
+ int y = curRect.y();
+
+ // Use x and y for the editMenuPoint from the cursorPointGlobal when the cursor is in the Dialog
+ if (cursorPointGlobal != cursorPoint) {
+ x = cursorPointGlobal.x();
+ y = cursorPointGlobal.y();
+ }
+
+ QPoint editMenuPoint(x, y);
m_handleMode &= ShowEditPopup;
m_handleMode |= ShowCursor;
uint32_t buttons = EditContext::PasteButton;
if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
buttons |= EditContext::SelectAllButton;
- QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons, cursorPoint * pixelDensity);
+ QtAndroidInput::updateHandles(m_handleMode, editMenuPoint * pixelDensity, buttons,
+ cursorPointGlobal * pixelDensity);
// The VK is hidden, reset the timer
if (m_hideCursorHandleTimer.isActive())
m_hideCursorHandleTimer.start();
@@ -666,10 +683,30 @@ void QAndroidInputContext::updateSelectionHandles()
if (cpos > anchor)
std::swap(leftRect, rightRect);
- QPoint leftPoint(leftRect.bottomLeft().toPoint() * pixelDensity);
- QPoint righPoint(rightRect.bottomRight().toPoint() * pixelDensity);
- QPoint editPoint(leftRect.united(rightRect).topLeft().toPoint() * pixelDensity);
- QtAndroidInput::updateHandles(m_handleMode, editPoint, EditContext::AllButtons, leftPoint, righPoint,
+ // Move the left or right select handle to the center from the screen edge
+ // the select handle is close to or over the screen edge. Otherwise, the
+ // select handle might go out of the screen and it would be impossible to drag.
+ QPoint leftPoint(window->mapToGlobal(leftRect.bottomLeft().toPoint()));
+ QPoint rightPoint(window->mapToGlobal(rightRect.bottomRight().toPoint()));
+ static int m_selectHandleWidth = 0;
+ // For comparison, get the width of the handle.
+ // Only half of the width will protrude from the cursor on each side
+ if (m_selectHandleWidth == 0)
+ m_selectHandleWidth = QtAndroidInput::getSelectHandleWidth() / 2;
+
+ int rightSideOfScreen = QtAndroid::androidPlatformIntegration()->screen()->availableGeometry().right();
+
+ // Check if handle will fit the screen on left side. If not, then move it closer to the center
+ if (leftPoint.x() <= m_selectHandleWidth)
+ leftPoint.setX(m_selectHandleWidth / pixelDensity);
+
+ // Check if handle will fit the screen on right side. If not, then move it closer to the center
+ if (rightPoint.x() >= (rightSideOfScreen / pixelDensity) - m_selectHandleWidth)
+ rightPoint.setX((rightSideOfScreen / pixelDensity) - (m_selectHandleWidth / pixelDensity));
+
+ QPoint editPoint(window->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint()));
+ QtAndroidInput::updateHandles(m_handleMode, editPoint * pixelDensity, EditContext::AllButtons,
+ leftPoint * pixelDensity, rightPoint * pixelDensity,
query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
m_hideCursorHandleTimer.stop();
}
@@ -693,7 +730,17 @@ void QAndroidInputContext::handleLocationChanged(int handleId, int x, int y)
double pixelDensity = window
? QHighDpiScaling::factor(window)
: QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen());
- QPointF point(x / pixelDensity, y / pixelDensity);
+ auto object = m_focusObject->parent();
+ int dialogMoveX = 0;
+ while (object) {
+ if (QString::compare(object->metaObject()->className(),
+ QtObjectType, Qt::CaseInsensitive) == 0) {
+ dialogMoveX += object->property("x").toInt();
+ }
+ object = object->parent();
+ };
+
+ QPointF point((x / pixelDensity) - dialogMoveX, y / pixelDensity);
point.setY(point.y() - leftRect.width() / 2);
QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition
@@ -931,50 +978,12 @@ void QAndroidInputContext::showInputPanel()
else
m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
- QRect rect = cursorRect();
+ QRect rect = inputItemRectangle();
QtAndroidInput::showSoftwareKeyboard(rect.left(), rect.top(), rect.width(), rect.height(),
- inputItemRectangle().height(),
query->value(Qt::ImHints).toUInt(),
query->value(Qt::ImEnterKeyType).toUInt());
}
-QRect QAndroidInputContext::cursorRect()
-{
- QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
- // if single line, we do not want to mess with the editor's position, as we do not
- // have to follow the cursor in vertical axis
- if (query.isNull()
- || (query->value(Qt::ImHints).toUInt() & Qt::ImhMultiLine) != Qt::ImhMultiLine)
- return {};
-
- auto im = qGuiApp->inputMethod();
- if (!im)
- return {};
-
- const auto cursorRect= im->cursorRectangle().toRect();
- QRect finalRect(inputItemRectangle());
- const QWindow *window = qGuiApp->focusWindow();
- const double pd = window
- ? QHighDpiScaling::factor(window)
- : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen());
- finalRect.setY(cursorRect.y() * pd);
- finalRect.setHeight(cursorRect.height() * pd);
- //fiddle a bit with vert margins, so the tracking rectangle is not too tight.
- finalRect += QMargins(0, cursorRect.height() / 4, 0, cursorRect.height() / 4);
- return finalRect;
-}
-
-void QAndroidInputContext::updateInputItemRectangle()
-{
- QRect rect = cursorRect();
-
- if (!rect.isValid())
- return;
- QtAndroidInput::updateInputItemRectangle(rect.left(), rect.top(),
- rect.width(), rect.height());
- updateSelectionHandles();
-}
-
void QAndroidInputContext::showInputPanelLater(Qt::ApplicationState state)
{
if (state != Qt::ApplicationActive)
@@ -1234,13 +1243,21 @@ bool QAndroidInputContext::focusObjectStopComposing()
m_composingCursor = -1;
- // Moving Qt's cursor to where the preedit cursor used to be
- QList<QInputMethodEvent::Attribute> attributes;
- attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
-
- QInputMethodEvent event(QString(), attributes);
- event.setCommitString(m_composingText);
- sendInputMethodEvent(&event);
+ {
+ // commit the composing test
+ QList<QInputMethodEvent::Attribute> attributes;
+ QInputMethodEvent event(QString(), attributes);
+ event.setCommitString(m_composingText);
+ sendInputMethodEvent(&event);
+ }
+ {
+ // Moving Qt's cursor to where the preedit cursor used to be
+ QList<QInputMethodEvent::Attribute> attributes;
+ attributes.append(
+ QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0));
+ QInputMethodEvent event(QString(), attributes);
+ sendInputMethodEvent(&event);
+ }
return true;
}
diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h
index 02a66c367a..6b72cca15b 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.h
+++ b/src/plugins/platforms/android/qandroidinputcontext.h
@@ -41,6 +41,7 @@
#ifndef ANDROIDINPUTCONTEXT_H
#define ANDROIDINPUTCONTEXT_H
+#include <QtCore/QPointer>
#include <qpa/qplatforminputcontext.h>
#include <functional>
#include <jni.h>
@@ -138,7 +139,6 @@ public:
public slots:
void safeCall(const std::function<void()> &func, Qt::ConnectionType conType = Qt::BlockingQueuedConnection);
void updateCursorPosition();
- void updateInputItemRectangle();
void updateSelectionHandles();
void handleLocationChanged(int handleId, int x, int y);
void touchDown(int x, int y);
@@ -155,7 +155,6 @@ private:
bool focusObjectIsComposing() const;
void focusObjectStartComposing();
bool focusObjectStopComposing();
- QRect cursorRect();
private:
ExtractedText m_extractedText;
@@ -165,7 +164,7 @@ private:
QMetaObject::Connection m_updateCursorPosConnection;
HandleModes m_handleMode;
int m_batchEditNestingLevel;
- QObject *m_focusObject;
+ QPointer<QObject> m_focusObject;
QTimer m_hideCursorHandleTimer;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QAndroidInputContext::HandleModes)
diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
index cc05dad749..b29190d3e9 100644
--- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
+++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
@@ -61,12 +61,22 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *
// so that the element can be moved on the screen if it's focused.
if (event->type() == QAccessible::LocationChanged) {
- QtAndroidAccessibility::notifyLocationChange();
+ QtAndroidAccessibility::notifyLocationChange(event->uniqueId());
} else if (event->type() == QAccessible::ObjectHide) {
QtAndroidAccessibility::notifyObjectHide(event->uniqueId());
} else if (event->type() == QAccessible::Focus) {
QtAndroidAccessibility::notifyObjectFocus(event->uniqueId());
+ } else if (event->type() == QAccessible::ValueChanged) {
+ QtAndroidAccessibility::notifyValueChanged(event->uniqueId());
+ } else if (event->type() == QAccessible::ScrollingEnd) {
+ QtAndroidAccessibility::notifyScrolledEvent(event->uniqueId());
}
}
+void QAndroidPlatformAccessibility::setRootObject(QObject *obj)
+{
+ QPlatformAccessibility::setRootObject(obj);
+ QtAndroidAccessibility::createAccessibilityContextObject(obj);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.h b/src/plugins/platforms/android/qandroidplatformaccessibility.h
index 8216c05fa6..df3fe43a04 100644
--- a/src/plugins/platforms/android/qandroidplatformaccessibility.h
+++ b/src/plugins/platforms/android/qandroidplatformaccessibility.h
@@ -52,6 +52,7 @@ public:
~QAndroidPlatformAccessibility();
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
+ void setRootObject(QObject *obj) override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
index 6bb3372380..b4f1c95746 100644
--- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
@@ -45,6 +45,7 @@
#include <QMimeType>
#include <QMimeDatabase>
#include <QRegularExpression>
+#include <QUrl>
QT_BEGIN_NAMESPACE
@@ -118,7 +119,7 @@ void QAndroidPlatformFileDialogHelper::takePersistableUriPermission(const QJNIOb
uri.object(), modeFlags);
}
-void QAndroidPlatformFileDialogHelper::setIntentTitle(const QString &title)
+void QAndroidPlatformFileDialogHelper::setInitialFileName(const QString &title)
{
const QJNIObjectPrivate extraTitle = QJNIObjectPrivate::getStaticObjectField(
JniIntentClass, "EXTRA_TITLE", "Ljava/lang/String;");
@@ -127,6 +128,22 @@ void QAndroidPlatformFileDialogHelper::setIntentTitle(const QString &title)
extraTitle.object(), QJNIObjectPrivate::fromString(title).object());
}
+void QAndroidPlatformFileDialogHelper::setInitialDirectoryUri(const QString &directory)
+{
+ if (directory.isEmpty())
+ return;
+
+ if (QtAndroidPrivate::androidSdkVersion() < 26)
+ return;
+
+ const auto extraInitialUri = QJNIObjectPrivate::getStaticObjectField(
+ "android/provider/DocumentsContract", "EXTRA_INITIAL_URI", "Ljava/lang/String;");
+ m_intent.callObjectMethod("putExtra",
+ "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
+ extraInitialUri.object(),
+ QJNIObjectPrivate::fromString(directory).object());
+}
+
void QAndroidPlatformFileDialogHelper::setOpenableCategory()
{
const QJNIObjectPrivate CATEGORY_OPENABLE = QJNIObjectPrivate::getStaticObjectField(
@@ -147,10 +164,10 @@ QStringList nameFilterExtensions(const QString nameFilters)
{
QStringList ret;
#if QT_CONFIG(regularexpression)
- QRegularExpression re("(\\*\\.?\\w*)");
+ QRegularExpression re("(\\*\\.[a-z .]+)");
QRegularExpressionMatchIterator i = re.globalMatch(nameFilters);
while (i.hasNext())
- ret << i.next().captured(1);
+ ret << i.next().captured(1).trimmed();
#endif // QT_CONFIG(regularexpression)
ret.removeAll("*");
return ret;
@@ -159,23 +176,24 @@ QStringList nameFilterExtensions(const QString nameFilters)
void QAndroidPlatformFileDialogHelper::setMimeTypes()
{
QStringList mimeTypes = options()->mimeTypeFilters();
- const QString nameFilter = options()->initiallySelectedNameFilter();
+ const QStringList nameFilters = options()->nameFilters();
+ const QString nameFilter = nameFilters.isEmpty() ? QString() : nameFilters.first();
- if (mimeTypes.isEmpty() && !nameFilter.isEmpty()) {
+ if (!nameFilter.isEmpty()) {
QMimeDatabase db;
for (const QString &filter : nameFilterExtensions(nameFilter))
- mimeTypes.append(db.mimeTypeForFile(filter).name());
+ mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
}
- QString type = !mimeTypes.isEmpty() ? mimeTypes.at(0) : QLatin1String("*/*");
+ const QString initialType = mimeTypes.size() == 1 ? mimeTypes.at(0) : QLatin1String("*/*");
m_intent.callObjectMethod("setType", "(Ljava/lang/String;)Landroid/content/Intent;",
- QJNIObjectPrivate::fromString(type).object());
+ QJNIObjectPrivate::fromString(initialType).object());
if (!mimeTypes.isEmpty()) {
const QJNIObjectPrivate extraMimeType = QJNIObjectPrivate::getStaticObjectField(
JniIntentClass, "EXTRA_MIME_TYPES", "Ljava/lang/String;");
- QJNIObjectPrivate mimeTypesArray = QJNIObjectPrivate::callStaticObjectMethod(
+ const QJNIObjectPrivate mimeTypesArray = QJNIObjectPrivate::callStaticObjectMethod(
"org/qtproject/qt5/android/QtNative",
"getStringArray",
"(Ljava/lang/String;)[Ljava/lang/String;",
@@ -207,6 +225,9 @@ bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::Win
if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
m_intent = getFileDialogIntent("ACTION_CREATE_DOCUMENT");
+ const QList<QUrl> selectedFiles = options()->initiallySelectedFiles();
+ if (selectedFiles.size() > 0)
+ setInitialFileName(selectedFiles.first().fileName());
} else if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) {
switch (options()->fileMode()) {
case QFileDialogOptions::FileMode::DirectoryOnly:
@@ -230,7 +251,7 @@ bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::Win
setMimeTypes();
}
- setIntentTitle(options()->windowTitle());
+ setInitialDirectoryUri(m_directory.toString());
QtAndroidPrivate::registerActivityResultListener(this);
m_activity.callMethod<void>("startActivityForResult", "(Landroid/content/Intent;I)V",
@@ -245,6 +266,11 @@ void QAndroidPlatformFileDialogHelper::hide()
QtAndroidPrivate::unregisterActivityResultListener(this);
}
+void QAndroidPlatformFileDialogHelper::setDirectory(const QUrl &directory)
+{
+ m_directory = directory;
+}
+
void QAndroidPlatformFileDialogHelper::exec()
{
m_eventLoop.exec(QEventLoop::DialogExec);
diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
index 5a7a28a8a0..20fc9fdccd 100644
--- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
+++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
@@ -67,21 +67,23 @@ public:
void setFilter() override {};
QList<QUrl> selectedFiles() const override { return m_selectedFile; };
void selectFile(const QUrl &file) override { Q_UNUSED(file) };
- QUrl directory() const override { return QUrl(); };
- void setDirectory(const QUrl &directory) override { Q_UNUSED(directory) };
+ QUrl directory() const override { return m_directory; }
+ void setDirectory(const QUrl &directory) override;
bool defaultNameFilterDisables() const override { return false; };
bool handleActivityResult(jint requestCode, jint resultCode, jobject data) override;
private:
QJNIObjectPrivate getFileDialogIntent(const QString &intentType);
void takePersistableUriPermission(const QJNIObjectPrivate &uri);
- void setIntentTitle(const QString &title);
+ void setInitialFileName(const QString &title);
+ void setInitialDirectoryUri(const QString &directory);
void setOpenableCategory();
void setAllowMultipleSelections(bool allowMultiple);
void setMimeTypes();
QEventLoop m_eventLoop;
QList<QUrl> m_selectedFile;
+ QUrl m_directory;
QJNIObjectPrivate m_intent;
const QJNIObjectPrivate m_activity;
};
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp
index aaeb9199d8..6fe0caba2e 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.cpp
+++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp
@@ -90,6 +90,7 @@ Qt::ScreenOrientation QAndroidPlatformIntegration::m_orientation = Qt::PrimaryOr
Qt::ScreenOrientation QAndroidPlatformIntegration::m_nativeOrientation = Qt::PrimaryOrientation;
bool QAndroidPlatformIntegration::m_showPasswordEnabled = false;
+static bool m_running = false;
void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteArray &resource)
{
@@ -158,6 +159,10 @@ void QAndroidPlatformNativeInterface::customEvent(QEvent *event)
api->accessibility()->setActive(QtAndroidAccessibility::isActive());
#endif // QT_NO_ACCESSIBILITY
+ if (!m_running) {
+ m_running = true;
+ QtAndroid::notifyQtAndroidPluginRunning(m_running);
+ }
api->flushPendingUpdates();
}
@@ -183,9 +188,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
m_primaryScreen = new QAndroidPlatformScreen();
QWindowSystemInterface::handleScreenAdded(m_primaryScreen);
- m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight));
- m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight));
- m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight));
+ const QSize physicalSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight);
+ const QSize screenSize(m_defaultScreenWidth, m_defaultScreenHeight);
+ const QRect geometry(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight);
+ m_primaryScreen->setSizeParameters(physicalSize, screenSize, geometry);
m_mainThread = QThread::currentThread();
@@ -458,10 +464,10 @@ void QAndroidPlatformIntegration::setScreenOrientation(Qt::ScreenOrientation cur
void QAndroidPlatformIntegration::flushPendingUpdates()
{
- m_primaryScreen->setPhysicalSize(QSize(m_defaultPhysicalSizeWidth,
- m_defaultPhysicalSizeHeight));
- m_primaryScreen->setSize(QSize(m_defaultScreenWidth, m_defaultScreenHeight));
- m_primaryScreen->setAvailableGeometry(QRect(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight));
+ const QSize physicalSize(m_defaultPhysicalSizeWidth, m_defaultPhysicalSizeHeight);
+ const QSize screenSize(m_defaultScreenWidth, m_defaultScreenHeight);
+ const QRect geometry(0, 0, m_defaultGeometryWidth, m_defaultGeometryHeight);
+ m_primaryScreen->setSizeParameters(physicalSize, screenSize, geometry);
}
#ifndef QT_NO_ACCESSIBILITY
@@ -489,6 +495,23 @@ void QAndroidPlatformIntegration::setScreenSize(int width, int height)
QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height)));
}
+void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize,
+ const QSize &screenSize,
+ const QRect &availableGeometry)
+{
+ if (m_primaryScreen) {
+ QMetaObject::invokeMethod(m_primaryScreen, "setSizeParameters", Qt::AutoConnection,
+ Q_ARG(QSize, physicalSize), Q_ARG(QSize, screenSize),
+ Q_ARG(QRect, availableGeometry));
+ }
+}
+
+void QAndroidPlatformIntegration::setRefreshRate(qreal refreshRate)
+{
+ if (m_primaryScreen)
+ QMetaObject::invokeMethod(m_primaryScreen, "setRefreshRate", Qt::AutoConnection,
+ Q_ARG(qreal, refreshRate));
+}
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QAndroidPlatformIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h
index ecbde4f951..f34972b81e 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.h
+++ b/src/plugins/platforms/android/qandroidplatformintegration.h
@@ -96,6 +96,13 @@ public:
virtual void setDesktopSize(int width, int height);
virtual void setDisplayMetrics(int width, int height);
void setScreenSize(int width, int height);
+ // The 3 methods above were replaced by a new one, so that we could have
+ // a better control over "geometry changed" event handling. Technically
+ // they are no longer used and can be removed. Not doing it now, because
+ // I'm not sure if it might be helpful to have them or not.
+ void setScreenSizeParameters(const QSize &physicalSize, const QSize &screenSize,
+ const QRect &availableGeometry);
+ void setRefreshRate(qreal refreshRate);
bool isVirtualDesktop() { return true; }
QPlatformFontDatabase *fontDatabase() const override;
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp
index 7e036868fc..b2ca70ce45 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.cpp
+++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp
@@ -55,6 +55,7 @@
#include <android/native_window_jni.h>
#include <qguiapplication.h>
+#include <QtCore/private/qjnihelpers_p.h>
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
#include <QtGui/private/qwindow_p.h>
@@ -104,6 +105,42 @@ QAndroidPlatformScreen::QAndroidPlatformScreen()
m_physicalSize.setHeight(QAndroidPlatformIntegration::m_defaultPhysicalSizeHeight);
m_physicalSize.setWidth(QAndroidPlatformIntegration::m_defaultPhysicalSizeWidth);
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidPlatformScreen::applicationStateChanged);
+
+ QJNIObjectPrivate activity(QtAndroid::activity());
+ if (!activity.isValid())
+ return;
+ QJNIObjectPrivate display;
+ if (QtAndroidPrivate::androidSdkVersion() < 30) {
+ display = activity.callObjectMethod("getWindowManager", "()Landroid/view/WindowManager;")
+ .callObjectMethod("getDefaultDisplay", "()Landroid/view/Display;");
+ } else {
+ display = activity.callObjectMethod("getDisplay", "()Landroid/view/Display;");
+ }
+ if (!display.isValid())
+ return;
+ m_name = display.callObjectMethod("getName", "()Ljava/lang/String;").toString();
+ m_refreshRate = display.callMethod<jfloat>("getRefreshRate");
+ if (QtAndroidPrivate::androidSdkVersion() < 23) {
+ m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate };
+ return;
+ }
+ QJNIEnvironmentPrivate env;
+ const jint currentMode = display.callObjectMethod("getMode", "()Landroid/view/Display$Mode;")
+ .callMethod<jint>("getModeId");
+ const auto modes = display.callObjectMethod("getSupportedModes",
+ "()[Landroid/view/Display$Mode;");
+ const auto modesArray = jobjectArray(modes.object());
+ const auto sz = env->GetArrayLength(modesArray);
+ for (jsize i = 0; i < sz; ++i) {
+ auto mode = QJNIObjectPrivate::fromLocalRef(env->GetObjectArrayElement(modesArray, i));
+ if (currentMode == mode.callMethod<jint>("getModeId"))
+ m_currentMode = m_modes.size();
+ m_modes << Mode { .size = QSize { mode.callMethod<jint>("getPhysicalHeight"),
+ mode.callMethod<jint>("getPhysicalWidth") },
+ .refreshRate = mode.callMethod<jfloat>("getRefreshRate") };
+ }
+ if (m_modes.isEmpty())
+ m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate };
}
QAndroidPlatformScreen::~QAndroidPlatformScreen()
@@ -243,6 +280,37 @@ void QAndroidPlatformScreen::setSize(const QSize &size)
QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry());
}
+void QAndroidPlatformScreen::setSizeParameters(const QSize &physicalSize, const QSize &size,
+ const QRect &availableGeometry)
+{
+ // The goal of this method is to set all geometry-related parameters
+ // at the same time and generate only one screen geometry change event.
+ m_physicalSize = physicalSize;
+ m_size = size;
+ // If available geometry has changed, the event will be handled in
+ // setAvailableGeometry. Otherwise we need to explicitly handle it to
+ // retain the behavior, because setSize() does the handling unconditionally.
+ if (m_availableGeometry != availableGeometry) {
+ setAvailableGeometry(availableGeometry);
+ } else {
+ QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(),
+ this->availableGeometry());
+ }
+}
+
+void QAndroidPlatformScreen::setRefreshRate(qreal refreshRate)
+{
+ if (refreshRate == m_refreshRate)
+ return;
+ m_refreshRate = refreshRate;
+ QWindowSystemInterface::handleScreenRefreshRateChange(QPlatformScreen::screen(), refreshRate);
+}
+
+void QAndroidPlatformScreen::setOrientation(Qt::ScreenOrientation orientation)
+{
+ QWindowSystemInterface::handleScreenOrientationChange(QPlatformScreen::screen(), orientation);
+}
+
void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect)
{
QMutexLocker lock(&m_surfaceMutex);
@@ -429,7 +497,7 @@ static const int androidLogicalDpi = 72;
QDpi QAndroidPlatformScreen::logicalDpi() const
{
- qreal lDpi = QtAndroid::scaledDensity() * androidLogicalDpi;
+ qreal lDpi = QtAndroid::pixelDensity() * androidLogicalDpi;
return QDpi(lDpi, lDpi);
}
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h
index 54b3c5b8a8..e73ea31a86 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.h
+++ b/src/plugins/platforms/android/qandroidplatformscreen.h
@@ -69,6 +69,12 @@ public:
QImage::Format format() const override { return m_format; }
QSizeF physicalSize() const override { return m_physicalSize; }
+ QString name() const override { return m_name; }
+ QVector<Mode> modes() const override { return m_modes; }
+ int currentMode() const override { return m_currentMode; }
+ int preferredMode() const override { return m_currentMode; }
+ qreal refreshRate() const override { return m_refreshRate; }
+
inline QWindow *topWindow() const;
QWindow *topLevelAt(const QPoint & p) const override;
@@ -87,6 +93,10 @@ public slots:
void setPhysicalSize(const QSize &size);
void setAvailableGeometry(const QRect &rect);
void setSize(const QSize &size);
+ void setSizeParameters(const QSize &physicalSize, const QSize &size,
+ const QRect &availableGeometry);
+ void setRefreshRate(qreal refreshRate);
+ void setOrientation(Qt::ScreenOrientation orientation);
protected:
bool event(QEvent *event) override;
@@ -100,6 +110,10 @@ protected:
int m_depth;
QImage::Format m_format;
QSizeF m_physicalSize;
+ qreal m_refreshRate;
+ QString m_name;
+ QVector<Mode> m_modes;
+ int m_currentMode = 0;
private:
QDpi logicalDpi() const override;
diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp
index c095613ce7..e317627770 100644
--- a/src/plugins/platforms/android/qandroidplatformservices.cpp
+++ b/src/plugins/platforms/android/qandroidplatformservices.cpp
@@ -59,12 +59,13 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
// if the file is local, we need to pass the MIME type, otherwise Android
// does not start an Intent to view this file
QLatin1String fileScheme("file");
- if ((url.scheme().isEmpty() || url.scheme() == fileScheme) && QFile::exists(url.path())) {
- // a real URL including the scheme is needed, else the Intent can not be started
+
+ // a real URL including the scheme is needed, else the Intent can not be started
+ if (url.scheme().isEmpty())
url.setScheme(fileScheme);
- QMimeDatabase mimeDb;
- mime = mimeDb.mimeTypeForUrl(url).name();
- }
+
+ if (url.scheme() == fileScheme)
+ mime = QMimeDatabase().mimeTypeForUrl(url).name();
QJNIObjectPrivate urlString = QJNIObjectPrivate::fromString(url.toString());
QJNIObjectPrivate mimeString = QJNIObjectPrivate::fromString(mime);
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
index ad40c6b0cb..e1f664c9da 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
@@ -246,7 +246,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return iface->text(QAccessible::Name).toNSString();
}
-- (BOOL) accessibilityEnabledAttribute {
+- (BOOL) isAccessibilityEnabled {
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
if (!iface || !iface->isValid())
return false;
diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
index 5ad1f9d7bb..a57a38773f 100644
--- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
@@ -324,9 +324,9 @@ public:
bool show(Qt::WindowModality windowModality, QWindow *parent)
{
Q_UNUSED(parent);
- if (windowModality != Qt::WindowModal)
+ if (windowModality != Qt::ApplicationModal)
[mDelegate showModelessPanel];
- // no need to show a Qt::WindowModal dialog here, because it's necessary to call exec() in that case
+ // no need to show a Qt::ApplicationModal dialog here, because it will be shown in runApplicationModalPanel
return true;
}
@@ -390,9 +390,8 @@ void QCocoaColorDialogHelper::exec()
bool QCocoaColorDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent)
{
- if (windowModality == Qt::WindowModal)
- windowModality = Qt::ApplicationModal;
-
+ if (windowModality == Qt::ApplicationModal)
+ windowModality = Qt::WindowModal;
// Workaround for Apple rdar://25792119: If you invoke
// -setShowsAlpha: multiple times before showing the color
// picker, its height grows irrevocably. Instead, only
diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm
index 8ca72ec619..c963ff937c 100644
--- a/src/plugins/platforms/cocoa/qcocoacursor.mm
+++ b/src/plugins/platforms/cocoa/qcocoacursor.mm
@@ -45,6 +45,15 @@
#include <QtGui/QBitmap>
+#if !defined(QT_APPLE_NO_PRIVATE_APIS)
+@interface NSCursor()
++ (id)_windowResizeNorthWestSouthEastCursor;
++ (id)_windowResizeNorthEastSouthWestCursor;
++ (id)_windowResizeNorthSouthCursor;
++ (id)_windowResizeEastWestCursor;
+@end
+#endif // QT_APPLE_NO_PRIVATE_APIS
+
QT_BEGIN_NAMESPACE
QCocoaCursor::QCocoaCursor()
@@ -116,7 +125,7 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
return nil;
const Qt::CursorShape newShape = cursor->shape();
- NSCursor *cocoaCursor;
+ NSCursor *cocoaCursor = nil;
// Check for a suitable built-in NSCursor first:
switch (newShape) {
@@ -157,7 +166,29 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
case Qt::DragLinkCursor:
cocoaCursor = [NSCursor dragLinkCursor];
break;
- default : {
+#if !defined(QT_APPLE_NO_PRIVATE_APIS)
+ case Qt::SizeVerCursor:
+ if ([NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)])
+ cocoaCursor = [NSCursor _windowResizeNorthSouthCursor];
+ break;
+ case Qt::SizeHorCursor:
+ if ([NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)])
+ cocoaCursor = [NSCursor _windowResizeEastWestCursor];
+ break;
+ case Qt::SizeBDiagCursor:
+ if ([NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)])
+ cocoaCursor = [NSCursor _windowResizeNorthEastSouthWestCursor];
+ break;
+ case Qt::SizeFDiagCursor:
+ if ([NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)])
+ cocoaCursor = [NSCursor _windowResizeNorthWestSouthEastCursor];
+ break;
+#endif // QT_APPLE_NO_PRIVATE_APIS
+ default:
+ break;
+ }
+
+ if (!cocoaCursor) {
// No suitable OS cursor exist, use cursors provided
// by Qt for the rest. Check for a cached cursor:
cocoaCursor = m_cursors.value(newShape);
@@ -172,8 +203,6 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
m_cursors.insert(newShape, cocoaCursor);
}
-
- break; }
}
return cocoaCursor;
}
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm
index 4bd1b129bd..2af9b8f556 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.mm
+++ b/src/plugins/platforms/cocoa/qcocoadrag.mm
@@ -221,6 +221,12 @@ bool QCocoaDrag::maybeDragMultipleItems()
// contains a combined picture for all urls we drag.
auto imageOrNil = dragImage;
for (const auto &qtUrl : qtUrls) {
+ if (!qtUrl.isValid())
+ continue;
+
+ if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items.
+ continue;
+
NSURL *nsUrl = qtUrl.toNSURL();
auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease];
const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index 5dac9b9a91..7c3f69b408 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -292,7 +292,12 @@ static QString strippedText(QString s)
}
}
- QString qtFileName = QFileInfo(QString::fromNSString(filename)).fileName();
+ // Treat symbolic links and aliases to directories like directories
+ QFileInfo fileInfo(QString::fromNSString(filename));
+ if (fileInfo.isSymLink() && QFileInfo(fileInfo.symLinkTarget()).isDir())
+ return YES;
+
+ QString qtFileName = fileInfo.fileName();
// No filter means accept everything
bool nameMatches = mSelectedNameFilter->isEmpty();
// Check if the current file name filter accepts the file:
diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
index 7748c304e3..7b13259d55 100644
--- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
@@ -314,9 +314,9 @@ public:
bool show(Qt::WindowModality windowModality, QWindow *parent)
{
Q_UNUSED(parent);
- if (windowModality != Qt::WindowModal)
+ if (windowModality != Qt::ApplicationModal)
[mDelegate showModelessPanel];
- // no need to show a Qt::WindowModal dialog here, because it's necessary to call exec() in that case
+ // no need to show a Qt::ApplicationModal dialog here, because it will be shown in runApplicationModalPanel
return true;
}
@@ -380,8 +380,8 @@ void QCocoaFontDialogHelper::exec()
bool QCocoaFontDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent)
{
- if (windowModality == Qt::WindowModal)
- windowModality = Qt::ApplicationModal;
+ if (windowModality == Qt::ApplicationModal)
+ windowModality = Qt::WindowModal;
sharedFontPanel()->init(this);
return sharedFontPanel()->show(windowModality, parent);
}
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm
index 0b03a98fd6..68af82b5cd 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenu.mm
@@ -290,27 +290,26 @@ void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
QMacAutoReleasePool pool;
if (enable) {
bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
- NSMenuItem *previousItem = nil;
+ NSMenuItem *lastVisibleItem = nil;
for (NSMenuItem *item in m_nativeMenu.itemArray) {
if (item.separatorItem) {
+ // hide item if previous was a separator, or if it's explicitly hidden
+ bool hideItem = previousIsSeparator;
if (auto *cocoaItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem)
- cocoaItem->setVisible(!previousIsSeparator);
- item.hidden = previousIsSeparator;
+ hideItem = previousIsSeparator || !cocoaItem->isVisible();
+ item.hidden = hideItem;
}
if (!item.hidden) {
- previousItem = item;
- previousIsSeparator = previousItem.separatorItem;
+ lastVisibleItem = item;
+ previousIsSeparator = lastVisibleItem.separatorItem;
}
}
// We now need to check the final item since we don't want any separators at the end of the list.
- if (previousItem && previousIsSeparator) {
- if (auto *cocoaItem = qt_objc_cast<QCocoaNSMenuItem *>(previousItem).platformMenuItem)
- cocoaItem->setVisible(false);
- previousItem.hidden = YES;
- }
+ if (lastVisibleItem && lastVisibleItem.separatorItem)
+ lastVisibleItem.hidden = YES;
} else {
for (auto *item : qAsConst(m_menuItems)) {
if (!item->isSeparator())
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h
index 029d29be9d..e6bbf14367 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.h
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h
@@ -117,6 +117,7 @@ public:
inline bool isMerged() const { return m_merged; }
inline bool isEnabled() const { return m_enabled && m_parentEnabled; }
inline bool isSeparator() const { return m_isSeparator; }
+ inline bool isVisible() const { return m_isVisible; }
QCocoaMenu *menu() const { return m_menu; }
MenuRole effectiveRole() const;
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h
index dcf6f1c753..448b281665 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.h
+++ b/src/plugins/platforms/cocoa/qcocoascreen.h
@@ -45,6 +45,7 @@
#include "qcocoacursor.h"
#include <qpa/qplatformintegration.h>
+#include <QtCore/private/qcore_mac_p.h>
QT_BEGIN_NAMESPACE
@@ -64,8 +65,8 @@ public:
QImage::Format format() const override { return m_format; }
qreal devicePixelRatio() const override { return m_devicePixelRatio; }
QSizeF physicalSize() const override { return m_physicalSize; }
- QDpi logicalDpi() const override { return m_logicalDpi; }
- QDpi logicalBaseDpi() const override { return m_logicalDpi; }
+ QDpi logicalDpi() const override { return QDpi(72, 72); }
+ QDpi logicalBaseDpi() const override { return QDpi(72, 72); }
qreal refreshRate() const override { return m_refreshRate; }
QString name() const override { return m_name; }
QPlatformCursor *cursor() const override { return m_cursor; }
@@ -96,8 +97,8 @@ private:
static void updateScreens();
static void cleanupScreens();
- static bool updateScreensIfNeeded();
- static NSArray *s_screenConfigurationBeforeUpdate;
+ static QMacNotificationObserver s_screenParameterObserver;
+ static CGDisplayReconfigurationCallBack s_displayReconfigurationCallBack;
static void add(CGDirectDisplayID displayId);
QCocoaScreen(CGDirectDisplayID displayId);
@@ -112,7 +113,6 @@ private:
QRect m_geometry;
QRect m_availableGeometry;
- QDpi m_logicalDpi;
qreal m_refreshRate = 0;
int m_depth = 0;
QString m_name;
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm
index 6a3172fb19..203df61d82 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.mm
+++ b/src/plugins/platforms/cocoa/qcocoascreen.mm
@@ -72,91 +72,33 @@ namespace CoreGraphics {
Q_ENUM_NS(DisplayChange)
}
-NSArray *QCocoaScreen::s_screenConfigurationBeforeUpdate = nil;
+QMacNotificationObserver QCocoaScreen::s_screenParameterObserver;
+CGDisplayReconfigurationCallBack QCocoaScreen::s_displayReconfigurationCallBack = nullptr;
void QCocoaScreen::initializeScreens()
{
updateScreens();
- CGDisplayRegisterReconfigurationCallback([](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
+ s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
Q_UNUSED(userInfo);
- // Displays are reconfigured in batches, and we want to update our screens
- // once a batch ends, so that all the states of the displays are up to date.
- static int displayReconfigurationsInProgress = 0;
-
const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
- qCDebug(lcQpaScreen).verbosity(0).nospace() << "Display " << displayId
- << (beforeReconfigure ? " about to reconfigure" : " was ")
- << QFlags<CoreGraphics::DisplayChange>(flags)
- << " with " << displayReconfigurationsInProgress
- << " display configuration(s) in progress";
-
- if (!flags) {
- // CGDisplayRegisterReconfigurationCallback has been observed to be called
- // with flags unset. This seems like a bug. The callback is not paired with
- // a matching "completion" callback either, so we don't know whether to treat
- // it as a begin or end of reconfigure.
- return;
- }
-
- if (beforeReconfigure) {
- if (!displayReconfigurationsInProgress++) {
- // There might have been a screen reconfigure before this that
- // we didn't process yet, so do that now if that's the case.
- updateScreensIfNeeded();
-
- Q_ASSERT(!s_screenConfigurationBeforeUpdate);
- s_screenConfigurationBeforeUpdate = NSScreen.screens;
- qCDebug(lcQpaScreen, "Display reconfigure transaction started"
- " with screen configuration %p", s_screenConfigurationBeforeUpdate);
-
- static void (^tryScreenUpdate)();
- tryScreenUpdate = ^void () {
- qCDebug(lcQpaScreen) << "Attempting screen update from runloop block";
- if (!updateScreensIfNeeded())
- CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, tryScreenUpdate);
- };
- CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, tryScreenUpdate);
- }
- } else {
- Q_ASSERT_X(displayReconfigurationsInProgress, "QCococaScreen",
- "Display configuration transactions are expected to be balanced");
+ qCDebug(lcQpaScreen).verbosity(0) << "Display" << displayId
+ << (beforeReconfigure ? "beginning" : "finished") << "reconfigure"
+ << QFlags<CoreGraphics::DisplayChange>(flags);
- if (!--displayReconfigurationsInProgress) {
- qCDebug(lcQpaScreen) << "Display reconfigure transaction completed";
- // We optimistically update now, in case the NSScreens have changed
- updateScreensIfNeeded();
- }
- }
- }, nullptr);
+ if (!beforeReconfigure)
+ updateScreens();
+ };
+ CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
- static QMacNotificationObserver screenParameterObserver(NSApplication.sharedApplication,
+ s_screenParameterObserver = QMacNotificationObserver(NSApplication.sharedApplication,
NSApplicationDidChangeScreenParametersNotification, [&]() {
qCDebug(lcQpaScreen) << "Received screen parameter change notification";
- updateScreensIfNeeded(); // As a last resort we update screens here
+ updateScreens();
});
}
-bool QCocoaScreen::updateScreensIfNeeded()
-{
- if (!s_screenConfigurationBeforeUpdate) {
- qCDebug(lcQpaScreen) << "QScreens have already been updated, all good";
- return true;
- }
-
- if (s_screenConfigurationBeforeUpdate == NSScreen.screens) {
- qCDebug(lcQpaScreen) << "Still waiting for NSScreen configuration change";
- return false;
- }
-
- qCDebug(lcQpaScreen, "NSScreen configuration changed to %p", NSScreen.screens);
- updateScreens();
-
- s_screenConfigurationBeforeUpdate = nil;
- return true;
-}
-
/*
Update the list of available QScreens, and the properties of existing screens.
@@ -164,6 +106,18 @@ bool QCocoaScreen::updateScreensIfNeeded()
*/
void QCocoaScreen::updateScreens()
{
+ // Adding, updating, or removing a screen below might trigger
+ // Qt or the application to move a window to a different screen,
+ // recursing back here via QCocoaWindow::windowDidChangeScreen.
+ // The update code is not re-entrant, so bail out if we end up
+ // in this situation. The screens will stabilize eventually.
+ static bool updatingScreens = false;
+ if (updatingScreens) {
+ qCInfo(lcQpaScreen) << "Skipping screen update, already updating";
+ return;
+ }
+ QBoolBlocker recursionGuard(updatingScreens);
+
uint32_t displayCount = 0;
if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess)
qFatal("Failed to get number of online displays");
@@ -239,6 +193,12 @@ void QCocoaScreen::cleanupScreens()
// Remove screens in reverse order to avoid crash in case of multiple screens
for (QScreen *screen : backwards(QGuiApplication::screens()))
static_cast<QCocoaScreen*>(screen->handle())->remove();
+
+ Q_ASSERT(s_displayReconfigurationCallBack);
+ CGDisplayRemoveReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
+ s_displayReconfigurationCallBack = nullptr;
+
+ s_screenParameterObserver.remove();
}
void QCocoaScreen::remove()
@@ -282,13 +242,13 @@ static QString displayName(CGDirectDisplayID displayID)
NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary(
display, kIODisplayOnlyPreferredName) autorelease];
- if ([[info objectForKey:@kDisplayVendorID] longValue] != CGDisplayVendorNumber(displayID))
+ if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID))
continue;
- if ([[info objectForKey:@kDisplayProductID] longValue] != CGDisplayModelNumber(displayID))
+ if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID))
continue;
- if ([[info objectForKey:@kDisplaySerialNumber] longValue] != CGDisplaySerialNumber(displayID))
+ if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID))
continue;
NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName];
@@ -310,15 +270,17 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
Q_ASSERT(isOnline());
+ // Some properties are only available via NSScreen
+ NSScreen *nsScreen = nativeScreen();
+ if (!nsScreen) {
+ qCDebug(lcQpaScreen) << "Corresponding NSScreen not yet available. Deferring update";
+ return;
+ }
+
const QRect previousGeometry = m_geometry;
const QRect previousAvailableGeometry = m_availableGeometry;
- const QDpi previousLogicalDpi = m_logicalDpi;
const qreal previousRefreshRate = m_refreshRate;
- // Some properties are only available via NSScreen
- NSScreen *nsScreen = nativeScreen();
- Q_ASSERT(nsScreen);
-
// The reference screen for the geometry is always the primary screen
QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect();
@@ -331,8 +293,6 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
CGSize size = CGDisplayScreenSize(m_displayId);
m_physicalSize = QSizeF(size.width, size.height);
- m_logicalDpi.first = 72;
- m_logicalDpi.second = 72;
QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
@@ -344,8 +304,6 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
if (didChangeGeometry)
QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry());
- if (m_logicalDpi != previousLogicalDpi)
- QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second);
if (m_refreshRate != previousRefreshRate)
QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate);
}
@@ -358,6 +316,11 @@ void QCocoaScreen::requestUpdate()
{
Q_ASSERT(m_displayId);
+ if (!isOnline()) {
+ qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
+ return;
+ }
+
if (!m_displayLink) {
CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink);
CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
@@ -674,8 +637,8 @@ bool QCocoaScreen::isOnline() const
// returning -1 to signal that the displayId is invalid. Some functions
// will also assert or even crash in this case, so it's important that
// we double check if a display is online before calling other functions.
- auto isOnline = CGDisplayIsOnline(m_displayId);
- static const uint32_t kCGDisplayIsDisconnected = int32_t(-1);
+ int isOnline = CGDisplayIsOnline(m_displayId);
+ static const int kCGDisplayIsDisconnected = 0xffffffff;
return isOnline != kCGDisplayIsDisconnected && isOnline;
}
@@ -714,13 +677,17 @@ QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const
QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen)
{
- if (s_screenConfigurationBeforeUpdate) {
- qCWarning(lcQpaScreen) << "Trying to resolve screen while waiting for screen reconfigure!";
- if (!updateScreensIfNeeded())
- qCWarning(lcQpaScreen) << "Failed to do last minute screen update. Expect crashes.";
+ auto displayId = nsScreen.qt_displayId;
+ auto *cocoaScreen = get(displayId);
+ if (!cocoaScreen) {
+ qCWarning(lcQpaScreen) << "Failed to map" << nsScreen
+ << "to QCocoaScreen. Doing last minute update.";
+ updateScreens();
+ cocoaScreen = get(displayId);
+ if (!cocoaScreen)
+ qCWarning(lcQpaScreen) << "Last minute update failed!";
}
-
- return get(nsScreen.qt_displayId);
+ return cocoaScreen;
}
QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm
index d73b028afb..b6ab9c0bbc 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.mm
+++ b/src/plugins/platforms/cocoa/qcocoatheme.mm
@@ -465,11 +465,11 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
if (iconType != 0) {
QPixmap pixmap;
IconRef icon = nullptr;
- GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon);
+ QT_IGNORE_DEPRECATIONS(GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon));
if (icon) {
pixmap = qt_mac_convert_iconref(icon, size.width(), size.height());
- ReleaseIconRef(icon);
+ QT_IGNORE_DEPRECATIONS(ReleaseIconRef(icon));
}
return pixmap;
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h
index 4688598da7..1d0948a0ec 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.h
+++ b/src/plugins/platforms/cocoa/qcocoawindow.h
@@ -56,6 +56,8 @@
#include <MoltenVK/mvk_vulkan.h>
#endif
+#include <QHash>
+
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DEBUG_STREAM
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index 5be65f2141..6bfdd82e19 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -104,7 +104,7 @@ static void qRegisterNotificationCallbacks()
if (QNSView *qnsView = qnsview_cast(notification.object))
cocoaWindows += qnsView.platformWindow;
} else {
- qCWarning(lcCocoaNotifications) << "Unhandled notifcation"
+ qCWarning(lcCocoaNotifications) << "Unhandled notification"
<< notification.name << "for" << notification.object;
return;
}
@@ -367,12 +367,18 @@ void QCocoaWindow::setVisible(bool visible)
if (window()->windowState() != Qt::WindowMinimized) {
if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) {
// Show the window as a sheet
- [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil];
+ NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
+ if (!nativeParentWindow.attachedSheet)
+ [nativeParentWindow beginSheet:m_view.window completionHandler:nil];
+ else
+ [nativeParentWindow beginCriticalSheet:m_view.window completionHandler:nil];
} else if (window()->modality() == Qt::ApplicationModal) {
// Show the window as application modal
eventDispatcher()->beginModalSession(window());
} else if (m_view.window.canBecomeKeyWindow) {
- bool shouldBecomeKeyNow = !NSApp.modalWindow || m_view.window.worksWhenModal;
+ bool shouldBecomeKeyNow = !NSApp.modalWindow
+ || m_view.window.worksWhenModal
+ || !NSApp.modalWindow.visible;
// Panels with becomesKeyOnlyIfNeeded set should not activate until a view
// with needsPanelToBecomeKey, for example a line edit, is clicked.
@@ -522,7 +528,10 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable;
if (frameless) {
- // No further customizations for frameless since there are no window decorations.
+ // Frameless windows do not display the traffic lights buttons for
+ // e.g. minimize, however StyleMaskMiniaturizable is required to allow
+ // programmatic minimize.
+ styleMask |= NSWindowStyleMaskMiniaturizable;
} else if (flags & Qt::CustomizeWindowHint) {
if (flags & Qt::WindowTitleHint)
styleMask |= NSWindowStyleMaskTitled;
@@ -545,9 +554,11 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
if (m_drawContentBorderGradient)
styleMask |= NSWindowStyleMaskTexturedBackground;
- // Don't wipe fullscreen state
+ // Don't wipe existing states
if (m_view.window.styleMask & NSWindowStyleMaskFullScreen)
styleMask |= NSWindowStyleMaskFullScreen;
+ if (m_view.window.styleMask & NSWindowStyleMaskFullSizeContentView)
+ styleMask |= NSWindowStyleMaskFullSizeContentView;
return styleMask;
}
@@ -676,9 +687,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
switch (currentState) {
case Qt::WindowMinimized:
[nsWindow deminiaturize:sender];
- Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
- "[NSWindow deminiaturize:] is synchronous");
- break;
+ // Deminiaturizing is not synchronous, so we need to wait for the
+ // NSWindowDidMiniaturizeNotification before continuing to apply
+ // the new state.
+ return;
case Qt::WindowFullScreen: {
toggleFullScreen();
// Exiting fullscreen is not synchronous, so we need to wait for the
@@ -842,7 +854,15 @@ void QCocoaWindow::windowDidDeminiaturize()
if (!isContentView())
return;
+ Qt::WindowState requestedState = window()->windowState();
+
handleWindowStateChanged();
+
+ if (requestedState != windowState() && requestedState != Qt::WindowMinimized) {
+ // We were only going out of minimized as an intermediate step before
+ // progressing into the final step, so re-sync the desired state.
+ applyWindowState(requestedState);
+ }
}
void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
@@ -1265,11 +1285,15 @@ void QCocoaWindow::windowDidChangeScreen()
return;
// Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
- auto *currentScreen = QCocoaScreen::get(m_view.window.screen);
+ NSScreen *nsScreen = m_view.window.screen;
+
+ qCDebug(lcQpaWindow) << window() << "did change" << nsScreen;
+ QCocoaScreen::updateScreens();
+
auto *previousScreen = static_cast<QCocoaScreen*>(screen());
+ auto *currentScreen = QCocoaScreen::get(nsScreen);
- Q_ASSERT_X(!m_view.window.screen || currentScreen,
- "QCocoaWindow", "Failed to get QCocoaScreen for NSScreen");
+ qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
// Note: The previous screen may be the same as the current screen, either because
// a) the screen was just reconfigured, which still results in AppKit sending an
@@ -1282,7 +1306,6 @@ void QCocoaWindow::windowDidChangeScreen()
// device-pixel ratio may have changed, and needs to be delivered to all
// windows, both top level and child windows.
- qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(
window(), currentScreen ? currentScreen->screen() : nullptr);
@@ -1307,10 +1330,19 @@ void QCocoaWindow::windowWillClose()
bool QCocoaWindow::windowShouldClose()
{
qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window();
+
// This callback should technically only determine if the window
// should (be allowed to) close, but since our QPA API to determine
// that also involves actually closing the window we do both at the
// same time, instead of doing the latter in windowWillClose.
+
+ // If the window is closed, we will release and deallocate the NSWindow.
+ // But frames higher up in the stack might still expect the window to
+ // be alive, since the windowShouldClose: callback is technically only
+ // supposed to answer YES or NO. To ensure the window is still alive
+ // we put an autorelease in the closest pool (typically the runloop).
+ [[m_view.window retain] autorelease];
+
return QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(window());
}
@@ -1714,6 +1746,20 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor)
view.cursor = cursor;
[m_view.window invalidateCursorRectsForView:m_view];
+
+ // There's a bug in AppKit where calling invalidateCursorRectsForView when
+ // there's an override cursor active (for example when hovering over the
+ // window frame), will not result in a cursorUpdate: callback. To work around
+ // this we synthesize a cursor update event and call the callback ourselves,
+ // if we detect that the mouse is currently over the view.
+ auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
+ auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
+ if ([m_view hitTest:locationInSuperview] == m_view) {
+ [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
+ location:locationInWindow modifierFlags:0 timestamp:0
+ windowNumber:m_view.window.windowNumber context:nil
+ eventNumber:0 trackingNumber:0 userData:0]];
+ }
}
void QCocoaWindow::registerTouch(bool enable)
diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h
index e070ba977d..0896917334 100644
--- a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h
+++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h
@@ -43,6 +43,8 @@
#include <qpa/qplatformgraphicsbuffer.h>
#include <private/qcore_mac_p.h>
+#include <CoreGraphics/CGColorSpace.h>
+
QT_BEGIN_NAMESPACE
class QIOSurfaceGraphicsBuffer : public QPlatformGraphicsBuffer
diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h
index 0a18afe3a6..1aa400af8d 100644
--- a/src/plugins/platforms/cocoa/qnsview.h
+++ b/src/plugins/platforms/cocoa/qnsview.h
@@ -61,6 +61,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSView);
@interface QNSView (MouseAPI)
- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent;
+- (bool)closePopups:(NSEvent *)theEvent;
- (void)resetMouseButtons;
@end
diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm
index 978d73f7d9..a4e4ad2f62 100644
--- a/src/plugins/platforms/cocoa/qnsview_dragging.mm
+++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm
@@ -297,7 +297,9 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
Q_ASSERT(nativeDrag);
nativeDrag->exitDragLoop();
- nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation));
+ // for internal drag'n'drop, don't override the action the drop event accepted
+ if (!nativeDrag->currentDrag())
+ nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation));
// Qt starts drag-and-drop on a mouse button press event. Cococa in
// this case won't send the matching release event, so we have to
diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm
index b42f2d0e7e..81f2e4fd58 100644
--- a/src/plugins/platforms/cocoa/qnsview_mouse.mm
+++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm
@@ -93,7 +93,7 @@
- (void)resetMouseButtons
{
- qCDebug(lcQpaMouse) << "Reseting mouse buttons";
+ qCDebug(lcQpaMouse) << "Resetting mouse buttons";
m_buttons = Qt::NoButton;
m_frameStrutButtons = Qt::NoButton;
}
@@ -103,21 +103,14 @@
if (!m_platformWindow)
return;
- // get m_buttons in sync
- // Don't send frme strut events if we are in the middle of a mouse drag.
- if (m_buttons != Qt::NoButton)
- return;
-
switch (theEvent.type) {
case NSEventTypeLeftMouseDown:
- case NSEventTypeLeftMouseDragged:
m_frameStrutButtons |= Qt::LeftButton;
break;
case NSEventTypeLeftMouseUp:
m_frameStrutButtons &= ~Qt::LeftButton;
break;
case NSEventTypeRightMouseDown:
- case NSEventTypeRightMouseDragged:
m_frameStrutButtons |= Qt::RightButton;
break;
case NSEventTypeRightMouseUp:
@@ -132,6 +125,22 @@
break;
}
+ // m_buttons can sometimes get out of sync with the button state in AppKit
+ // E.g if the QNSView where a drag starts is reparented to another window
+ // while the drag is ongoing, it will not get the corresponding mouseUp
+ // call. This will result in m_buttons to be stuck on Qt::LeftButton.
+ // Since we know which buttons was pressed/released directly on the frame
+ // strut, we can rectify m_buttons here so that we at least don't return early
+ // from the drag test underneath because of the faulty m_buttons state.
+ // FIXME: get m_buttons in sync with AppKit/NSEvent all over in QNSView.
+ m_buttons &= ~m_frameStrutButtons;
+
+ if (m_buttons != Qt::NoButton) {
+ // Don't send frame strut events if we are in the middle of
+ // a mouse drag that didn't start on the frame strut.
+ return;
+ }
+
NSWindow *window = [self window];
NSPoint windowPoint = [theEvent locationInWindow];
@@ -176,6 +185,39 @@
QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(),
timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType);
}
+
+- (bool)closePopups:(NSEvent *)theEvent
+{
+ QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack();
+ if (!popups->isEmpty()) {
+ // Check if the click is outside all popups.
+ bool inside = false;
+ QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]);
+ for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) {
+ if ((*it)->geometry().contains(qtScreenPoint.toPoint())) {
+ inside = true;
+ break;
+ }
+ }
+ // Close the popups if the click was outside.
+ if (!inside) {
+ bool selfClosed = false;
+ Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type();
+ while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) {
+ selfClosed = self == popup->view();
+ QWindowSystemInterface::handleCloseEvent(popup->window());
+ QWindowSystemInterface::flushWindowSystemEvents();
+ if (!m_platformWindow)
+ return true; // Bail out if window was destroyed
+ }
+ // Consume the mouse event when closing the popup, except for tool tips
+ // were it's expected that the event is processed normally.
+ if (type != Qt::ToolTip || selfClosed)
+ return true;
+ }
+ }
+ return false;
+}
@end
@implementation QNSView (Mouse)
@@ -381,34 +423,8 @@
// that particular poup type (for example context menus). However, Qt expects
// that plain popup QWindows will also be closed, so we implement the logic
// here as well.
- QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack();
- if (!popups->isEmpty()) {
- // Check if the click is outside all popups.
- bool inside = false;
- QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]);
- for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) {
- if ((*it)->geometry().contains(qtScreenPoint.toPoint())) {
- inside = true;
- break;
- }
- }
- // Close the popups if the click was outside.
- if (!inside) {
- bool selfClosed = false;
- Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type();
- while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) {
- selfClosed = self == popup->view();
- QWindowSystemInterface::handleCloseEvent(popup->window());
- QWindowSystemInterface::flushWindowSystemEvents();
- if (!m_platformWindow)
- return; // Bail out if window was destroyed
- }
- // Consume the mouse event when closing the popup, except for tool tips
- // were it's expected that the event is processed normally.
- if (type != Qt::ToolTip || selfClosed)
- return;
- }
- }
+ if ([self closePopups:theEvent])
+ return;
QPointF qtWindowPoint;
QPointF qtScreenPoint;
@@ -655,14 +671,18 @@
// had time to emit a momentum phase event.
if ([NSApp nextEventMatchingMask:NSEventMaskScrollWheel untilDate:[NSDate distantPast]
inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) {
- Q_ASSERT(pixelDelta.isNull() && angleDelta.isNull());
- return; // Ignore this event, as it has a delta of 0,0
+ return; // Ignore, even if it has delta
+ } else {
+ phase = Qt::ScrollEnd;
+ m_scrolling = false;
}
- phase = Qt::ScrollEnd;
- m_scrolling = false;
} else if (theEvent.momentumPhase == NSEventPhaseBegan) {
Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull());
- phase = Qt::ScrollUpdate; // Send as update, it has a delta
+ // If we missed finding a momentum NSEventPhaseBegan when the non-momentum
+ // phase ended we need to treat this as a scroll begin, to not confuse client
+ // code. Otherwise we treat it as a continuation of the existing scroll.
+ phase = m_scrolling ? Qt::ScrollUpdate : Qt::ScrollBegin;
+ m_scrolling = true;
} else if (theEvent.momentumPhase == NSEventPhaseChanged) {
phase = Qt::ScrollMomentum;
} else if (theEvent.phase == NSEventPhaseCancelled
@@ -674,6 +694,16 @@
Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary);
}
+ // Sanitize deltas for events that should not result in scrolling.
+ // On macOS 12.1 this phase has been observed to report deltas.
+ if (theEvent.phase == NSEventPhaseCancelled) {
+ if (!pixelDelta.isNull() || !angleDelta.isNull()) {
+ qCInfo(lcQpaMouse) << "Ignoring unexpected delta for" << theEvent;
+ pixelDelta = QPoint();
+ angleDelta = QPoint();
+ }
+ }
+
// Prevent keyboard modifier state from changing during scroll event streams.
// A two-finger trackpad flick generates a stream of scroll events. We want
// the keyboard modifier state to be the state at the beginning of the
diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm
index ba1fa55892..f365403502 100644
--- a/src/plugins/platforms/cocoa/qnsview_tablet.mm
+++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm
@@ -148,9 +148,6 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent)
device = QTabletEvent::Stylus;
} else {
switch (bits & 0x0F06) {
- case 0x0802:
- device = QTabletEvent::Stylus;
- break;
case 0x0902:
device = QTabletEvent::Airbrush;
break;
@@ -163,8 +160,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent)
case 0x0804:
device = QTabletEvent::RotationStylus;
break;
- default:
- device = QTabletEvent::NoDevice;
+ default: // usually 0x0802, but 0 on iPad sidecar with Apple Pencil
+ device = QTabletEvent::Stylus;
}
}
return device;
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index 8967636fd2..75235a9863 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -104,7 +104,7 @@ static bool isMouseEvent(NSEvent *ev)
// Unfortunately there's no NSWindowListOrderedBackToFront,
// so we have to manually reverse the order using an array.
- NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new];
+ NSMutableArray<NSWindow *> *windows = [[NSMutableArray<NSWindow *> new] autorelease];
[application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
usingBlock:^(NSWindow *window, BOOL *) {
// For some reason AppKit will give us nil-windows, skip those
@@ -321,8 +321,18 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
- (NSColor *)backgroundColor
{
- return self.styleMask == NSWindowStyleMaskBorderless ?
- [NSColor clearColor] : [super backgroundColor];
+ // FIXME: Plumb to a WA_NoSystemBackground-like window flag,
+ // or a QWindow::backgroundColor() property. In the meantime
+ // we assume that if you have translucent content, without a
+ // frame then you intend to do all background drawing yourself.
+ const QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
+ if (!self.opaque && window && window->flags().testFlag(Qt::FramelessWindowHint))
+ return [NSColor clearColor];
+
+ // This still allows you to have translucent content with a frame,
+ // where the system background (or color set via NSWindow) will
+ // shine through.
+ return [super backgroundColor];
}
- (void)sendEvent:(NSEvent*)theEvent
@@ -347,18 +357,29 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return;
}
+ const bool mouseEventInFrameStrut = [theEvent, self]{
+ if (isMouseEvent(theEvent)) {
+ const NSPoint loc = theEvent.locationInWindow;
+ const NSRect windowFrame = [self convertRectFromScreen:self.frame];
+ const NSRect contentFrame = self.contentView.frame;
+ if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
+ return true;
+ }
+ return false;
+ }();
+ // Any mouse-press in the frame of the window, including the title bar buttons, should
+ // close open popups. Presses within the window's content are handled to do that in the
+ // NSView::mouseDown implementation.
+ if (theEvent.type == NSEventTypeLeftMouseDown && mouseEventInFrameStrut)
+ [qnsview_cast(m_platformWindow->view()) closePopups:theEvent];
+
[super sendEvent:theEvent];
if (!m_platformWindow)
return; // Platform window went away while processing event
- if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
- NSPoint loc = [theEvent locationInWindow];
- NSRect windowFrame = [self convertRectFromScreen:self.frame];
- NSRect contentFrame = self.contentView.frame;
- if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
- [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
- }
+ if (m_platformWindow->frameStrutEventsEnabled() && mouseEventInFrameStrut)
+ [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
}
- (void)closeAndRelease
diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
index 98e05195ee..a3d6df7d11 100644
--- a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
@@ -343,8 +343,7 @@ void QEglFSCursor::paintOnScreen()
// screens are siblings of each other. When not enabled, the sibling list
// only contains m_screen itself.
for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
- if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot)
- && QOpenGLContext::currentContext()->screen() == screen->screen())
+ if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot))
{
cr.translate(-screen->geometry().topLeft());
const QSize screenSize = screen->geometry().size();
@@ -468,11 +467,12 @@ void QEglFSCursor::draw(const QRectF &r)
{
StateSaver stateSaver;
- QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
- if (!gfx.program) {
- // one time initialization
+ // one time initialization
+ if (!QOpenGLFunctions::d_ptr)
initializeOpenGLFunctions();
+ QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
+ if (!gfx.program) {
createShaderPrograms();
if (!gfx.atlasTexture) {
@@ -548,3 +548,5 @@ void QEglFSCursor::draw(const QRectF &r)
}
QT_END_NAMESPACE
+
+#include "moc_qeglfscursor_p.cpp"
diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
index b985386a4e..02567ad0b3 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
@@ -410,3 +410,5 @@ EGLConfig QEglFSDeviceIntegration::chooseConfig(EGLDisplay display, const QSurfa
}
QT_END_NAMESPACE
+
+#include "moc_qeglfsdeviceintegration_p.cpp"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
index 503419cf91..ff18b43a3d 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
@@ -83,7 +83,13 @@ bool QEglFSKmsGbmDevice::open()
setFd(fd);
- m_eventReader.create(this);
+ if (usesEventReader()) {
+ qCDebug(qLcEglfsKmsDebug, "Using dedicated drm event reading thread");
+ m_eventReader.create(this);
+ } else {
+ qCDebug(qLcEglfsKmsDebug, "Not using dedicated drm event reading thread; "
+ "threaded multi-screen setups may experience problems");
+ }
return true;
}
@@ -92,7 +98,8 @@ void QEglFSKmsGbmDevice::close()
{
// Note: screens are gone at this stage.
- m_eventReader.destroy();
+ if (usesEventReader())
+ m_eventReader.destroy();
if (m_gbm_device) {
gbm_device_destroy(m_gbm_device);
@@ -169,4 +176,10 @@ void QEglFSKmsGbmDevice::registerScreen(QPlatformScreen *screen,
m_globalCursor->reevaluateVisibilityForScreens();
}
+bool QEglFSKmsGbmDevice::usesEventReader() const
+{
+ static const bool eventReaderThreadDisabled = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_NO_EVENT_READER_THREAD");
+ return !eventReaderThreadDisabled;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h
index f1476f8ffa..01dc86309e 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.h
@@ -44,6 +44,7 @@
#include "qeglfskmsgbmcursor.h"
#include <qeglfskmsdevice.h>
+#include <qeglfskmseventreader.h>
#include <gbm.h>
@@ -75,11 +76,14 @@ public:
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings) override;
+ bool usesEventReader() const;
+ QEglFSKmsEventReader *eventReader() { return &m_eventReader; }
+
private:
Q_DISABLE_COPY(QEglFSKmsGbmDevice)
gbm_device *m_gbm_device;
-
+ QEglFSKmsEventReader m_eventReader;
QEglFSKmsGbmCursor *m_globalCursor;
};
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
index 95b51c9601..31cf5f0353 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
@@ -56,6 +56,8 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
+QMutex QEglFSKmsGbmScreen::m_nonThreadedFlipMutex;
+
static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat)
{
Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
@@ -262,6 +264,20 @@ void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
}
}
+void QEglFSKmsGbmScreen::nonThreadedPageFlipHandler(int fd,
+ unsigned int sequence,
+ unsigned int tv_sec,
+ unsigned int tv_usec,
+ void *user_data)
+{
+ Q_UNUSED(fd);
+ Q_UNUSED(sequence);
+ Q_UNUSED(tv_sec);
+ Q_UNUSED(tv_usec);
+ QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data);
+ screen->flipFinished();
+}
+
void QEglFSKmsGbmScreen::waitForFlip()
{
if (m_headless || m_cloneSource)
@@ -271,12 +287,24 @@ void QEglFSKmsGbmScreen::waitForFlip()
if (!m_gbm_bo_next)
return;
- m_flipMutex.lock();
- device()->eventReader()->startWaitFlip(this, &m_flipMutex, &m_flipCond);
- m_flipCond.wait(&m_flipMutex);
- m_flipMutex.unlock();
-
- flipFinished();
+ QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
+ if (dev->usesEventReader()) {
+ m_flipMutex.lock();
+ dev->eventReader()->startWaitFlip(this, &m_flipMutex, &m_flipCond);
+ m_flipCond.wait(&m_flipMutex);
+ m_flipMutex.unlock();
+ flipFinished();
+ } else {
+ QMutexLocker lock(&m_nonThreadedFlipMutex);
+ while (m_gbm_bo_next) {
+ drmEventContext drmEvent;
+ memset(&drmEvent, 0, sizeof(drmEvent));
+ drmEvent.version = 2;
+ drmEvent.vblank_handler = nullptr;
+ drmEvent.page_flip_handler = nonThreadedPageFlipHandler;
+ drmHandleEvent(device()->fd(), &drmEvent);
+ }
+ }
#if QT_CONFIG(drm_atomic)
device()->threadLocalAtomicReset();
@@ -359,20 +387,21 @@ void QEglFSKmsGbmScreen::flip()
if (d.screen != this) {
d.screen->ensureModeSet(fb->fb);
d.cloneFlipPending = true;
+ QKmsOutput &destOutput(d.screen->output());
if (device()->hasAtomicSupport()) {
#if QT_CONFIG(drm_atomic)
drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
if (request) {
- drmModeAtomicAddProperty(request, d.screen->output().eglfs_plane->id,
- d.screen->output().eglfs_plane->framebufferPropertyId, fb->fb);
- drmModeAtomicAddProperty(request, d.screen->output().eglfs_plane->id,
- d.screen->output().eglfs_plane->crtcPropertyId, op.crtc_id);
+ drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id,
+ destOutput.eglfs_plane->framebufferPropertyId, fb->fb);
+ drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id,
+ destOutput.eglfs_plane->crtcPropertyId, destOutput.crtc_id);
}
#endif
} else {
int ret = drmModePageFlip(fd,
- d.screen->output().crtc_id,
+ destOutput.crtc_id,
fb->fb,
DRM_MODE_PAGE_FLIP_EVENT,
d.screen);
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h
index 69feeee703..b00d338b43 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.h
@@ -75,6 +75,11 @@ private:
void ensureModeSet(uint32_t fb);
void cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen);
void updateFlipStatus();
+ static void nonThreadedPageFlipHandler(int fd,
+ unsigned int sequence,
+ unsigned int tv_sec,
+ unsigned int tv_usec,
+ void *user_data);
gbm_surface *m_gbm_surface;
@@ -84,6 +89,7 @@ private:
QMutex m_flipMutex;
QWaitCondition m_flipCond;
+ static QMutex m_nonThreadedFlipMutex;
QScopedPointer<QEglFSKmsGbmCursor> m_cursor;
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.h
index 34908aa60f..fc83a620d9 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.h
@@ -42,7 +42,6 @@
#define QEGLFSKMSDEVICE_H
#include "private/qeglfsglobal_p.h"
-#include "qeglfskmseventreader.h"
#include <QtKmsSupport/private/qkmsdevice_p.h>
QT_BEGIN_NAMESPACE
@@ -56,11 +55,6 @@ public:
bool isPrimary,
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings) override;
-
- QEglFSKmsEventReader *eventReader() { return &m_eventReader; }
-
-protected:
- QEglFSKmsEventReader m_eventReader;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
index ce5a721906..33382cb372 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
@@ -61,7 +61,7 @@ private:
QEglFSX11Integration *m_integration;
};
-QAtomicInt running;
+static QBasicAtomicInt running;
void EventReader::run()
{
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
index dba6f24fc5..2fe3c9e382 100644
--- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
+++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
@@ -41,6 +41,8 @@
#include "qiosfiledialog.h"
-@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate, UINavigationControllerDelegate>
+@interface QIOSDocumentPickerController : UIDocumentPickerViewController <UIDocumentPickerDelegate,
+ UINavigationControllerDelegate,
+ UIAdaptivePresentationControllerDelegate>
- (instancetype)initWithQIOSFileDialog:(QIOSFileDialog *)fileDialog;
@end
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
index c1b641e839..da78d41115 100644
--- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
+++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
@@ -72,6 +72,7 @@
m_fileDialog = fileDialog;
self.modalPresentationStyle = UIModalPresentationFormSheet;
self.delegate = self;
+ self.presentationController.delegate = self;
if (m_fileDialog->options()->fileMode() == QFileDialogOptions::ExistingFiles)
self.allowsMultipleSelection = YES;
@@ -100,4 +101,18 @@
emit m_fileDialog->reject();
}
+- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
+{
+ Q_UNUSED(presentationController);
+
+ // "Called on the delegate when the user has taken action to dismiss the
+ // presentation successfully, after all animations are finished.
+ // This is not called if the presentation is dismissed programatically."
+
+ // So if document picker's view was dismissed, for example by swiping it away,
+ // we got this method called. But not if the dialog was cancelled or a file
+ // was selected.
+ emit m_fileDialog->reject();
+}
+
@end
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 985eecdb1d..ac75367d7f 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -160,7 +160,7 @@ static QUIView *focusView()
return;
// Enable hide-keyboard gesture
- self.enabled = YES;
+ self.enabled = m_context->isInputPanelVisible();
m_context->scrollToCursor();
}
@@ -402,7 +402,7 @@ void QIOSInputContext::updateKeyboardState(NSNotification *notification)
// The isInputPanelVisible() property is based on whether or not the virtual keyboard
// is visible on screen, and does not follow the logic of the iOS WillShow and WillHide
// notifications which are not emitted for undocked keyboards, and are buggy when dealing
- // with input-accesosory-views. The reason for using frameEnd here (the future state),
+ // with input-accessory-views. The reason for using frameEnd here (the future state),
// instead of the current state reflected in frameBegin, is that QInputMethod::isVisible()
// is documented to reflect the future state in the case of animated transitions.
m_keyboardState.keyboardVisible = CGRectIntersectsRect(frameEnd, [UIScreen mainScreen].bounds);
@@ -727,12 +727,34 @@ bool QIOSInputContext::inputMethodAccepted() const
*/
void QIOSInputContext::reset()
{
- qImDebug("updating Qt::ImQueryAll and unmarking text");
+ qImDebug("releasing text responder");
+
+ // UIKit will sometimes, for unknown reasons, unset the input delegate on the
+ // current text responder. This seems to happen as a result of us calling
+ // [self.inputDelegate textDidChange:self] from [m_textResponder reset].
+ // But it won't be set to nil directly, only after a character is typed on
+ // the input panel after the reset. This strange behavior seems to be related
+ // to us overriding [QUIView setInteraction] to ignore UITextInteraction. If we
+ // didn't do that, the delegate would be kept. But not overriding that function
+ // has its own share of issues, so it seems better to keep that way for now.
+ // Instead, we choose to recreate the text responder as a brute-force solution
+ // until we have better knowledge of what is going on (or implement the new
+ // UITextInteraction protocol).
+ const auto oldResponder = m_textResponder;
+ [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)];
+ [m_textResponder notifyInputDelegate:Qt::ImQueryInput];
+ [m_textResponder autorelease];
+ m_textResponder = nullptr;
update(Qt::ImQueryAll);
- [m_textResponder setMarkedText:@"" selectedRange:NSMakeRange(0, 0)];
- [m_textResponder notifyInputDelegate:Qt::ImQueryInput];
+ // If update() didn't end up creating a new text responder, oldResponder will still be
+ // the first responder. In that case we need to resign it, so that the input panel hides.
+ // (the input panel will apparently not hide if the first responder is only released).
+ if ([oldResponder isFirstResponder]) {
+ qImDebug("IM not enabled, resigning autoreleased text responder as first responder");
+ [oldResponder resignFirstResponder];
+ }
}
/*!
diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm
index 254922701a..773b034e2a 100644
--- a/src/plugins/platforms/ios/qiosmessagedialog.mm
+++ b/src/plugins/platforms/ios/qiosmessagedialog.mm
@@ -47,6 +47,7 @@
#include "qiosglobal.h"
#include "quiview.h"
+#include "qiosscreen.h"
#include "qiosmessagedialog.h"
QIOSMessageDialog::QIOSMessageDialog()
@@ -147,6 +148,25 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win
}
UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window : qt_apple_sharedApplication().keyWindow;
+ if (!window) {
+ qCDebug(lcQpaWindow, "Attempting to exec a dialog without any window/widget visible.");
+
+ auto *primaryScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
+ Q_ASSERT(primaryScreen);
+
+ window = primaryScreen->uiWindow();
+ if (window.hidden) {
+ // With a window hidden, an attempt to present view controller
+ // below fails with a warning, that a view "is not a part of
+ // any view hierarchy". The UIWindow is initially hidden,
+ // as unhiding it is what hides the splash screen.
+ window.hidden = NO;
+ }
+ }
+
+ if (!window)
+ return false;
+
[window.rootViewController presentViewController:m_alertController animated:YES completion:nil];
return true;
}
diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm
index a83d495043..a5eadca38d 100644
--- a/src/plugins/platforms/ios/qiosscreen.mm
+++ b/src/plugins/platforms/ios/qiosscreen.mm
@@ -289,7 +289,7 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
if (!qt_apple_isApplicationExtension()) {
for (UIWindow *existingWindow in qt_apple_sharedApplication().windows) {
if (existingWindow.screen == m_uiScreen) {
- m_uiWindow = [m_uiWindow retain];
+ m_uiWindow = [existingWindow retain];
break;
}
}
diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm
index a7f94dd31f..8b6537f4a7 100644
--- a/src/plugins/platforms/ios/qiostextresponder.mm
+++ b/src/plugins/platforms/ios/qiostextresponder.mm
@@ -514,15 +514,23 @@
// from within a undo callback.
NSUndoManager *undoMgr = self.undoManager;
[undoMgr removeAllActions];
+
+ [undoMgr beginUndoGrouping];
+ [undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil];
+ [undoMgr endUndoGrouping];
[undoMgr beginUndoGrouping];
[undoMgr registerUndoWithTarget:self selector:@selector(undo) object:nil];
[undoMgr endUndoGrouping];
- // Schedule an operation that we immediately pop off to be able to schedule a redo
+ // Schedule operations that we immediately pop off to be able to schedule redos
+ [undoMgr beginUndoGrouping];
+ [undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil];
+ [undoMgr endUndoGrouping];
[undoMgr beginUndoGrouping];
[undoMgr registerUndoWithTarget:self selector:@selector(registerRedo) object:nil];
[undoMgr endUndoGrouping];
[undoMgr undo];
+ [undoMgr undo];
// Note that, perhaps because of a bug in UIKit, the buttons on the shortcuts bar ends up
// disabled if a undo/redo callback doesn't lead to a [UITextInputDelegate textDidChange].
@@ -530,6 +538,11 @@
// become disabled when there is nothing more to undo (Qt didn't change anything upon receiving
// an undo request). This seems to be OK behavior, so we let it stay like that unless it shows
// to cause problems.
+
+ // QTBUG-63393: Having two operations on the rebuilt undo stack keeps the undo/redo widgets
+ // always enabled on the shortcut bar. This workaround was found by experimenting with
+ // removing the removeAllActions call, and is related to the unknown internal implementation
+ // details of how the shortcut bar updates the dimming of its buttons.
});
}
@@ -873,20 +886,20 @@
[self sendEventToFocusObject:e];
}
-- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
+- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(UITextRange *)range
{
Q_UNUSED(writingDirection);
Q_UNUSED(range);
// Writing direction is handled by QLocale
}
-- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
+- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
{
Q_UNUSED(position);
Q_UNUSED(direction);
if (QLocale::system().textDirection() == Qt::RightToLeft)
- return UITextWritingDirectionRightToLeft;
- return UITextWritingDirectionLeftToRight;
+ return NSWritingDirectionRightToLeft;
+ return NSWritingDirectionLeftToRight;
}
- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm
index cd4af46ef7..cf8324505a 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.mm
+++ b/src/plugins/platforms/ios/qiosviewcontroller.mm
@@ -145,9 +145,13 @@
UIWindow *uiWindow = self.window;
if (uiWindow.screen != [UIScreen mainScreen] && self.subviews.count == 1) {
- // Removing the last view of an external screen, go back to mirror mode
- uiWindow.screen = [UIScreen mainScreen];
- uiWindow.hidden = YES;
+ // We're about to remove the last view of an external screen, so go back
+ // to mirror mode, but defer it until after the view has been removed,
+ // to ensure that we don't try to layout the view that's being removed.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ uiWindow.hidden = YES;
+ uiWindow.screen = [UIScreen mainScreen];
+ });
}
}
@@ -246,6 +250,7 @@
@implementation QIOSViewController {
BOOL m_updatingProperties;
QMetaObject::Connection m_focusWindowChangeConnection;
+ QMetaObject::Connection m_appStateChangedConnection;
}
#ifndef Q_OS_TVOS
@@ -274,7 +279,7 @@
});
QIOSApplicationState *applicationState = &QIOSIntegration::instance()->applicationState;
- QObject::connect(applicationState, &QIOSApplicationState::applicationStateDidChange,
+ m_appStateChangedConnection = QObject::connect(applicationState, &QIOSApplicationState::applicationStateDidChange,
[self](Qt::ApplicationState oldState, Qt::ApplicationState newState) {
if (oldState == Qt::ApplicationSuspended && newState != Qt::ApplicationSuspended) {
// We may have ignored an earlier layout because the application was suspended,
@@ -294,6 +299,7 @@
- (void)dealloc
{
QObject::disconnect(m_focusWindowChangeConnection);
+ QObject::disconnect(m_appStateChangedConnection);
[super dealloc];
}
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm
index 3154536aad..3ebded0e00 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.mm
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm
@@ -123,8 +123,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
if (val) {
return val->currentValue().toString().toNSString();
} else if (QAccessibleTextInterface *text = iface->textInterface()) {
- // FIXME doesn't work?
- return text->text(0, text->characterCount() - 1).toNSString();
+ return text->text(0, text->characterCount()).toNSString();
}
return [super accessibilityHint];
@@ -158,8 +157,27 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
if (state.searchEdit)
traits |= UIAccessibilityTraitSearchField;
- if (iface->role() == QAccessible::Button)
+ if (state.selected)
+ traits |= UIAccessibilityTraitSelected;
+
+ const auto accessibleRole = iface->role();
+ if (accessibleRole == QAccessible::Button) {
traits |= UIAccessibilityTraitButton;
+ } else if (accessibleRole == QAccessible::EditableText) {
+ static auto defaultTextFieldTraits = []{
+ auto *textField = [[[UITextField alloc] initWithFrame:CGRectZero] autorelease];
+ return textField.accessibilityTraits;
+ }();
+ traits |= defaultTextFieldTraits;
+ } else if (accessibleRole == QAccessible::Graphic) {
+ traits |= UIAccessibilityTraitImage;
+ } else if (accessibleRole == QAccessible::Heading) {
+ traits |= UIAccessibilityTraitHeader;
+ } else if (accessibleRole == QAccessible::Link) {
+ traits |= UIAccessibilityTraitLink;
+ } else if (accessibleRole == QAccessible::StaticText) {
+ traits |= UIAccessibilityTraitStaticText;
+ }
if (iface->valueInterface())
traits |= UIAccessibilityTraitAdjustable;
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index 6ff495ab85..9289cc68c9 100644
--- a/src/plugins/platforms/ios/quiview.mm
+++ b/src/plugins/platforms/ios/quiview.mm
@@ -465,7 +465,10 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
} else {
- QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ // Send the touch event asynchronously, as the application might spin a recursive
+ // event loop in response to the touch event (a dialog e.g.), which will deadlock
+ // the UIKit event delivery system (QTBUG-98651).
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
}
}
@@ -571,7 +574,12 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime];
QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration());
- QWindowSystemInterface::handleTouchCancelEvent(self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice());
+
+ // Send the touch event asynchronously, as the application might spin a recursive
+ // event loop in response to the touch event (a dialog e.g.), which will deadlock
+ // the UIKit event delivery system (QTBUG-98651).
+ QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>(
+ self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice());
}
- (int)mapPressTypeToKey:(UIPress*)press
diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm
index 6612dc131e..3e2d4c59ae 100644
--- a/src/plugins/platforms/ios/quiview_accessibility.mm
+++ b/src/plugins/platforms/ios/quiview_accessibility.mm
@@ -59,13 +59,20 @@
if (!iface)
return;
- [self createAccessibleElement: iface];
for (int i = 0; i < iface->childCount(); ++i)
[self createAccessibleContainer: iface->child(i)];
+
+ // The container element must go last, so that it underlays all its children
+ [self createAccessibleElement:iface];
}
- (void)initAccessibility
{
+ // The window may have gone away, but with the view
+ // temporarily caught in the a11y subsystem.
+ if (!self.platformWindow)
+ return;
+
static bool init = false;
if (!init)
QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true);
diff --git a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
index dcc1ef2790..2c9ca5648c 100644
--- a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
+++ b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
@@ -466,3 +466,5 @@ QPixmap QLinuxFbDrmScreen::grabWindow(WId wid, int x, int y, int width, int heig
}
QT_END_NAMESPACE
+
+#include "moc_qlinuxfbdrmscreen.cpp"
diff --git a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
index cb8962d4b8..7467df2b93 100644
--- a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
+++ b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
@@ -448,3 +448,5 @@ QPixmap QLinuxFbScreen::grabWindow(WId wid, int x, int y, int width, int height)
QT_END_NAMESPACE
+#include "moc_qlinuxfbscreen.cpp"
+
diff --git a/src/plugins/platforms/minimal/qminimalintegration.cpp b/src/plugins/platforms/minimal/qminimalintegration.cpp
index 0c2c0d0b68..b69e603b01 100644
--- a/src/plugins/platforms/minimal/qminimalintegration.cpp
+++ b/src/plugins/platforms/minimal/qminimalintegration.cpp
@@ -42,6 +42,7 @@
#include <QtGui/private/qpixmap_raster_p.h>
#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
@@ -200,6 +201,13 @@ QAbstractEventDispatcher *QMinimalIntegration::createEventDispatcher() const
#endif
}
+QPlatformNativeInterface *QMinimalIntegration::nativeInterface() const
+{
+ if (!m_nativeInterface)
+ m_nativeInterface.reset(new QPlatformNativeInterface);
+ return m_nativeInterface.get();
+}
+
QMinimalIntegration *QMinimalIntegration::instance()
{
return static_cast<QMinimalIntegration *>(QGuiApplicationPrivate::platformIntegration());
diff --git a/src/plugins/platforms/minimal/qminimalintegration.h b/src/plugins/platforms/minimal/qminimalintegration.h
index f9c66e0c3e..c384c28fba 100644
--- a/src/plugins/platforms/minimal/qminimalintegration.h
+++ b/src/plugins/platforms/minimal/qminimalintegration.h
@@ -43,6 +43,8 @@
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformscreen.h>
+#include <qscopedpointer.h>
+
QT_BEGIN_NAMESPACE
class QMinimalScreen : public QPlatformScreen
@@ -82,12 +84,15 @@ public:
QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
QAbstractEventDispatcher *createEventDispatcher() const override;
+ QPlatformNativeInterface *nativeInterface() const override;
+
unsigned options() const { return m_options; }
static QMinimalIntegration *instance();
private:
mutable QPlatformFontDatabase *m_fontDatabase;
+ mutable QScopedPointer<QPlatformNativeInterface> m_nativeInterface;
QMinimalScreen *m_primaryScreen;
unsigned m_options;
};
diff --git a/src/plugins/platforms/vnc/qvnc.cpp b/src/plugins/platforms/vnc/qvnc.cpp
index d21e50c248..5ae548bed7 100644
--- a/src/plugins/platforms/vnc/qvnc.cpp
+++ b/src/plugins/platforms/vnc/qvnc.cpp
@@ -477,6 +477,9 @@ void QRfbRawEncoder::write()
// server->screen()->geometry().height());
// }
+ const QImage screenImage = client->server()->screenImage();
+ rgn &= screenImage.rect();
+
const auto rectsInRegion = rgn.rectCount();
{
@@ -492,8 +495,6 @@ void QRfbRawEncoder::write()
if (rectsInRegion <= 0)
return;
- const QImage screenImage = client->server()->screenImage();
-
for (const QRect &tileRect: rgn) {
const QRfbRect rect(tileRect.x(), tileRect.y(),
tileRect.width(), tileRect.height());
@@ -670,11 +671,10 @@ void QVncServer::newConnection()
void QVncServer::discardClient(QVncClient *client)
{
clients.removeOne(client);
+ qvnc_screen->disableClientCursor(client);
client->deleteLater();
- if (clients.isEmpty()) {
- qvnc_screen->disableClientCursor(client);
+ if (clients.isEmpty())
qvnc_screen->setPowerState(QPlatformScreen::PowerStateOff);
- }
}
inline QImage QVncServer::screenImage() const
@@ -683,3 +683,5 @@ inline QImage QVncServer::screenImage() const
}
QT_END_NAMESPACE
+
+#include "moc_qvnc_p.cpp"
diff --git a/src/plugins/platforms/vnc/qvncclient.cpp b/src/plugins/platforms/vnc/qvncclient.cpp
index 186922a6b8..c169d44d1f 100644
--- a/src/plugins/platforms/vnc/qvncclient.cpp
+++ b/src/plugins/platforms/vnc/qvncclient.cpp
@@ -666,3 +666,5 @@ bool QVncClient::pixelConversionNeeded() const
}
QT_END_NAMESPACE
+
+#include "moc_qvncclient.cpp"
diff --git a/src/plugins/platforms/vnc/qvncscreen.cpp b/src/plugins/platforms/vnc/qvncscreen.cpp
index abbdbbc530..00abbdab27 100644
--- a/src/plugins/platforms/vnc/qvncscreen.cpp
+++ b/src/plugins/platforms/vnc/qvncscreen.cpp
@@ -147,9 +147,10 @@ void QVncScreen::disableClientCursor(QVncClient *client)
if (clientCount == 0) {
delete clientCursor;
clientCursor = nullptr;
- }
- mCursor = new QFbCursor(this);
+ if (mCursor == nullptr)
+ mCursor = new QFbCursor(this);
+ }
#else
Q_UNUSED(client)
#endif
@@ -214,3 +215,5 @@ QFbScreen::Flags QVncScreen::flags() const
QT_END_NAMESPACE
+#include "moc_qvncscreen.cpp"
+
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp
index 0ece812972..103f3201b2 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp
@@ -126,7 +126,11 @@ void QWasmCompositor::removeWindow(QWasmWindow *window)
m_windowStack.removeAll(window);
m_compositedWindows.remove(window);
- notifyTopWindowChanged(window);
+ if (!m_windowStack.isEmpty() && !QGuiApplication::focusWindow()) {
+ auto lastWindow = m_windowStack.last();
+ lastWindow->requestActivateWindow();
+ notifyTopWindowChanged(lastWindow);
+ }
}
void QWasmCompositor::setVisible(QWasmWindow *window, bool visible)
diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp
index 61204517ce..4b4bb61071 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcursor.cpp
@@ -41,20 +41,24 @@ using namespace emscripten;
void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
{
- if (!windowCursor || !window)
+ if (!window)
return;
QScreen *screen = window->screen();
if (!screen)
return;
- // Bitmap and custom cursors are not implemented (will fall back to "auto")
- if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor)
- qWarning() << "QWasmCursor: bitmap and custom cursors are not supported";
+ QByteArray htmlCursorName;
+ if (windowCursor) {
- QByteArray htmlCursorName = cursorShapeToHtml(windowCursor->shape());
+ // Bitmap and custom cursors are not implemented (will fall back to "auto")
+ if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor)
+ qWarning() << "QWasmCursor: bitmap and custom cursors are not supported";
+
+ htmlCursorName = cursorShapeToHtml(windowCursor->shape());
+ }
if (htmlCursorName.isEmpty())
- htmlCursorName = "auto";
+ htmlCursorName = "default";
// Set cursor on the canvas
val canvas = QWasmScreen::get(screen)->canvas();
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
index 41ece2ccf4..a76b02ce77 100644
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
+++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
@@ -136,139 +136,11 @@ static constexpr const auto KeyTbl = qMakeArray(
Emkb2Qt< Qt::Key_F21, 'F','2','1' >,
Emkb2Qt< Qt::Key_F22, 'F','2','2' >,
Emkb2Qt< Qt::Key_F23, 'F','2','3' >,
- Emkb2Qt< Qt::Key_Space, ' ' >,
- Emkb2Qt< Qt::Key_Comma, ',' >,
- Emkb2Qt< Qt::Key_Minus, '-' >,
- Emkb2Qt< Qt::Key_Period, '.' >,
- Emkb2Qt< Qt::Key_Slash, '/' >,
- Emkb2Qt< Qt::Key_0, 'D','i','g','i','t','0' >,
- Emkb2Qt< Qt::Key_1, 'D','i','g','i','t','1' >,
- Emkb2Qt< Qt::Key_2, 'D','i','g','i','t','2' >,
- Emkb2Qt< Qt::Key_3, 'D','i','g','i','t','3' >,
- Emkb2Qt< Qt::Key_4, 'D','i','g','i','t','4' >,
- Emkb2Qt< Qt::Key_5, 'D','i','g','i','t','5' >,
- Emkb2Qt< Qt::Key_6, 'D','i','g','i','t','6' >,
- Emkb2Qt< Qt::Key_7, 'D','i','g','i','t','7' >,
- Emkb2Qt< Qt::Key_8, 'D','i','g','i','t','8' >,
- Emkb2Qt< Qt::Key_9, 'D','i','g','i','t','9' >,
- Emkb2Qt< Qt::Key_Semicolon, ';' >,
- Emkb2Qt< Qt::Key_Equal, '=' >,
- Emkb2Qt< Qt::Key_A, 'K','e','y','A' >,
- Emkb2Qt< Qt::Key_B, 'K','e','y','B' >,
- Emkb2Qt< Qt::Key_C, 'K','e','y','C' >,
- Emkb2Qt< Qt::Key_D, 'K','e','y','D' >,
- Emkb2Qt< Qt::Key_E, 'K','e','y','E' >,
- Emkb2Qt< Qt::Key_F, 'K','e','y','F' >,
- Emkb2Qt< Qt::Key_G, 'K','e','y','G' >,
- Emkb2Qt< Qt::Key_H, 'K','e','y','H' >,
- Emkb2Qt< Qt::Key_I, 'K','e','y','I' >,
- Emkb2Qt< Qt::Key_J, 'K','e','y','J' >,
- Emkb2Qt< Qt::Key_K, 'K','e','y','K' >,
- Emkb2Qt< Qt::Key_L, 'K','e','y','L' >,
- Emkb2Qt< Qt::Key_M, 'K','e','y','M' >,
- Emkb2Qt< Qt::Key_N, 'K','e','y','N' >,
- Emkb2Qt< Qt::Key_O, 'K','e','y','O' >,
- Emkb2Qt< Qt::Key_P, 'K','e','y','P' >,
- Emkb2Qt< Qt::Key_Q, 'K','e','y','Q' >,
- Emkb2Qt< Qt::Key_R, 'K','e','y','R' >,
- Emkb2Qt< Qt::Key_S, 'K','e','y','S' >,
- Emkb2Qt< Qt::Key_T, 'K','e','y','T' >,
- Emkb2Qt< Qt::Key_U, 'K','e','y','U' >,
- Emkb2Qt< Qt::Key_V, 'K','e','y','V' >,
- Emkb2Qt< Qt::Key_W, 'K','e','y','W' >,
- Emkb2Qt< Qt::Key_X, 'K','e','y','X' >,
- Emkb2Qt< Qt::Key_Y, 'K','e','y','Y' >,
- Emkb2Qt< Qt::Key_Z, 'K','e','y','Z' >,
- Emkb2Qt< Qt::Key_BracketLeft, '[' >,
- Emkb2Qt< Qt::Key_Backslash, '\\' >,
- Emkb2Qt< Qt::Key_BracketRight, ']' >,
- Emkb2Qt< Qt::Key_Apostrophe, '\'' >,
- Emkb2Qt< Qt::Key_QuoteLeft, 'B','a','c','k','q','u','o','t','e' >,
- Emkb2Qt< Qt::Key_multiply, 'N','u','m','p','a','d','M','u','l','t','i','p','l','y' >,
- Emkb2Qt< Qt::Key_Minus, 'N','u','m','p','a','d','S','u','b','t','r','a','c','t' >,
- Emkb2Qt< Qt::Key_Period, 'N','u','m','p','a','d','D','e','c','i','m','a','l' >,
- Emkb2Qt< Qt::Key_Plus, 'N','u','m','p','a','d','A','d','d' >,
- Emkb2Qt< Qt::Key_division, 'N','u','m','p','a','d','D','i','v','i','d','e' >,
- Emkb2Qt< Qt::Key_Equal, 'N','u','m','p','a','d','E','q','u','a','l' >,
- Emkb2Qt< Qt::Key_0, 'N','u','m','p','a','d','0' >,
- Emkb2Qt< Qt::Key_1, 'N','u','m','p','a','d','1' >,
- Emkb2Qt< Qt::Key_2, 'N','u','m','p','a','d','2' >,
- Emkb2Qt< Qt::Key_3, 'N','u','m','p','a','d','3' >,
- Emkb2Qt< Qt::Key_4, 'N','u','m','p','a','d','4' >,
- Emkb2Qt< Qt::Key_5, 'N','u','m','p','a','d','5' >,
- Emkb2Qt< Qt::Key_6, 'N','u','m','p','a','d','6' >,
- Emkb2Qt< Qt::Key_7, 'N','u','m','p','a','d','7' >,
- Emkb2Qt< Qt::Key_8, 'N','u','m','p','a','d','8' >,
- Emkb2Qt< Qt::Key_9, 'N','u','m','p','a','d','9' >,
- Emkb2Qt< Qt::Key_Comma, 'N','u','m','p','a','d','C','o','m','m','a' >,
- Emkb2Qt< Qt::Key_Enter, 'N','u','m','p','a','d','E','n','t','e','r' >,
Emkb2Qt< Qt::Key_Paste, 'P','a','s','t','e' >,
Emkb2Qt< Qt::Key_AltGr, 'A','l','t','R','i','g','h','t' >,
Emkb2Qt< Qt::Key_Help, 'H','e','l','p' >,
- Emkb2Qt< Qt::Key_Equal, '=' >,
Emkb2Qt< Qt::Key_yen, 'I','n','t','l','Y','e','n' >,
-
- Emkb2Qt< Qt::Key_Exclam, '\x21' >,
- Emkb2Qt< Qt::Key_QuoteDbl, '\x22' >,
- Emkb2Qt< Qt::Key_NumberSign, '\x23' >,
- Emkb2Qt< Qt::Key_Dollar, '\x24' >,
- Emkb2Qt< Qt::Key_Percent, '\x25' >,
- Emkb2Qt< Qt::Key_Ampersand, '\x26' >,
- Emkb2Qt< Qt::Key_ParenLeft, '\x28' >,
- Emkb2Qt< Qt::Key_ParenRight, '\x29' >,
- Emkb2Qt< Qt::Key_Asterisk, '\x2a' >,
- Emkb2Qt< Qt::Key_Plus, '\x2b' >,
- Emkb2Qt< Qt::Key_Colon, '\x3a' >,
- Emkb2Qt< Qt::Key_Semicolon, '\x3b' >,
- Emkb2Qt< Qt::Key_Less, '\x3c' >,
- Emkb2Qt< Qt::Key_Equal, '\x3d' >,
- Emkb2Qt< Qt::Key_Greater, '\x3e' >,
- Emkb2Qt< Qt::Key_Question, '\x3f' >,
- Emkb2Qt< Qt::Key_At, '\x40' >,
- Emkb2Qt< Qt::Key_BracketLeft, '\x5b' >,
- Emkb2Qt< Qt::Key_Backslash, '\x5c' >,
- Emkb2Qt< Qt::Key_BracketRight, '\x5d' >,
- Emkb2Qt< Qt::Key_AsciiCircum, '\x5e' >,
- Emkb2Qt< Qt::Key_Underscore, '\x5f' >,
- Emkb2Qt< Qt::Key_QuoteLeft, '\x60'>,
- Emkb2Qt< Qt::Key_BraceLeft, '\x7b'>,
- Emkb2Qt< Qt::Key_Bar, '\x7c'>,
- Emkb2Qt< Qt::Key_BraceRight, '\x7d'>,
- Emkb2Qt< Qt::Key_AsciiTilde, '\x7e'>,
- Emkb2Qt< Qt::Key_Space, '\x20' >,
- Emkb2Qt< Qt::Key_Comma, '\x2c' >,
- Emkb2Qt< Qt::Key_Minus, '\x2d' >,
- Emkb2Qt< Qt::Key_Period, '\x2e' >,
- Emkb2Qt< Qt::Key_Slash, '\x2f' >,
- Emkb2Qt< Qt::Key_Apostrophe, '\x27' >,
- Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >,
-
- Emkb2Qt< Qt::Key_Agrave, '\xc3','\xa0' >,
- Emkb2Qt< Qt::Key_Aacute, '\xc3','\xa1' >,
- Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\xa2' >,
- Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\xa4' >,
- Emkb2Qt< Qt::Key_AE, '\xc3','\xa6' >,
- Emkb2Qt< Qt::Key_Atilde, '\xc3','\xa3' >,
- Emkb2Qt< Qt::Key_Aring, '\xc3','\xa5' >,
- Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\xa7' >,
- Emkb2Qt< Qt::Key_Egrave, '\xc3','\xa8' >,
- Emkb2Qt< Qt::Key_Eacute, '\xc3','\xa9' >,
- Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\xaa' >,
- Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\xab' >,
- Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\xae' >,
- Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\xaf' >,
- Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\xb4' >,
- Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\xb6' >,
- Emkb2Qt< Qt::Key_Ograve, '\xc3','\xb2' >,
- Emkb2Qt< Qt::Key_Oacute, '\xc3','\xb3' >,
- Emkb2Qt< Qt::Key_Ooblique, '\xc3','\xb8' >,
- Emkb2Qt< Qt::Key_Otilde, '\xc3','\xb5' >,
- Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\xbb' >,
- Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\xbc' >,
- Emkb2Qt< Qt::Key_Ugrave, '\xc3','\xb9' >,
- Emkb2Qt< Qt::Key_Uacute, '\xc3','\xba' >,
- Emkb2Qt< Qt::Key_Ntilde, '\xc3','\xb1' >,
- Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\xbf' >
+ Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >
>::Data{}
);
@@ -469,29 +341,21 @@ Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent
if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) {
qtKey = static_cast<Qt::Key>(it1->qt);
}
-
- } else if (qstrncmp(emscriptKey->code, "Key", 3) == 0 || qstrncmp(emscriptKey->code, "Numpad", 6) == 0 ||
- qstrncmp(emscriptKey->code, "Digit", 5) == 0) {
- emkb2qt_t searchKey{emscriptKey->code, 0}; // search emcsripten code
- auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey);
- if (it1 != KeyTbl.end() && !(searchKey < *it1)) {
- qtKey = static_cast<Qt::Key>(it1->qt);
- }
}
-
if (qtKey == Qt::Key_unknown) {
- emkb2qt_t searchKey{emscriptKey->key, 0}; // search unicode key
+ emkb2qt_t searchKey{emscriptKey->key, 0};
+ // search key
auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey);
if (it1 != KeyTbl.end() && !(searchKey < *it1)) {
qtKey = static_cast<Qt::Key>(it1->qt);
}
}
- if (qtKey == Qt::Key_unknown) {//try harder with shifted number keys
- emkb2qt_t searchKey1{emscriptKey->key, 0};
- for (auto it1 = KeyTbl.cbegin(); it1 != KeyTbl.end(); ++it1)
- if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) {
- qtKey = static_cast<Qt::Key>(it1->qt);
- }
+
+ if (qtKey == Qt::Key_unknown) {
+ // cast to unicode key
+ QString str = QString::fromUtf8(emscriptKey->key);
+ ushort c = str.unicode()->toUpper().unicode(); // uppercase
+ qtKey = static_cast<Qt::Key>(c);
}
return qtKey;
@@ -586,9 +450,6 @@ void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEven
if (resizeMode == QWasmWindow::ResizeNone)
window2 = screen()->compositor()->windowAt(globalPoint, 5);
- if (lastWindow && lastWindow->cursor() != Qt::ArrowCursor) {
- lastWindow->setCursor(Qt::ArrowCursor);
- }
if (window2 == nullptr) {
window2 = lastWindow;
} else {
@@ -817,7 +678,7 @@ int QWasmEventTranslator::handleTouch(int eventType, const EmscriptenTouchEvent
quint64 QWasmEventTranslator::getTimestamp()
{
- return QDeadlineTimer::current().deadlineNSecs() / 1000;
+ return static_cast<quint64>(emscripten_performance_now());
}
struct KeyMapping { Qt::Key from, to; };
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h
index 93a074a51e..c91fee02f3 100644
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.h
+++ b/src/plugins/platforms/wasm/qwasmeventtranslator.h
@@ -42,6 +42,7 @@
#endif
#include <QHash>
#include <QCursor>
+#include <QPointer>
QT_BEGIN_NAMESPACE
@@ -86,9 +87,9 @@ private:
QMap <int, QPointF> pressedTouchIds;
private:
- QWindow *draggedWindow;
- QWindow *pressedWindow;
- QWindow *lastWindow;
+ QPointer<QWindow> draggedWindow;
+ QPointer<QWindow> pressedWindow;
+ QPointer<QWindow> lastWindow;
Qt::MouseButtons pressedButtons;
QWasmWindow::ResizeMode resizeMode;
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index eba7bbecf1..099ab54643 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -57,7 +57,6 @@ QWasmScreen::QWasmScreen(const emscripten::val &canvas)
{
m_compositor = new QWasmCompositor(this);
m_eventTranslator = new QWasmEventTranslator(this);
- installCanvasResizeObserver();
updateQScreenAndCanvasRenderSize();
m_canvas.call<void>("focus");
}
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index f95335f891..7c724111e8 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -171,7 +171,12 @@ WId QWasmWindow::winId() const
void QWasmWindow::propagateSizeHints()
{
-// get rid of base class warning
+ QRect rect = windowGeometry();
+ if (rect.size().width() < windowMinimumSize().width()
+ && rect.size().height() < windowMinimumSize().height()) {
+ rect.setSize(windowMinimumSize());
+ setGeometry(rect);
+ }
}
void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global,
@@ -408,4 +413,11 @@ bool QWasmWindow::hasTitleBar() const
&& !window()->flags().testFlag(Qt::Popup);
}
+void QWasmWindow::requestActivateWindow()
+{
+ if (window()->isTopLevel())
+ raise();
+ QPlatformWindow::requestActivateWindow();
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index a098172649..2a3eaed8eb 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -74,6 +74,7 @@ public:
QRect normalGeometry() const override;
qreal devicePixelRatio() const override;
void requestUpdate() override;
+ void requestActivateWindow() override;
QWasmScreen *platformScreen() const;
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
diff --git a/src/plugins/platforms/windows/openglblacklists/default.json b/src/plugins/platforms/windows/openglblacklists/default.json
index e37351f9e0..072acdd115 100644
--- a/src/plugins/platforms/windows/openglblacklists/default.json
+++ b/src/plugins/platforms/windows/openglblacklists/default.json
@@ -93,7 +93,7 @@
},
{
"id": 8,
- "description": "Standard VGA: Insufficent support for OpenGL, D3D9 and D3D11",
+ "description": "Standard VGA: Insufficient support for OpenGL, D3D9 and D3D11",
"vendor_id": "0x0000",
"device_id": ["0x0000"],
"os": {
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index fa757b0edc..3e0059ca90 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -252,14 +252,12 @@ QWindowsContext *QWindowsContext::m_instance = nullptr;
\internal
*/
-typedef QHash<HWND, QWindowsWindow *> HandleBaseWindowHash;
-
struct QWindowsContextPrivate {
QWindowsContextPrivate();
unsigned m_systemInfo = 0;
QSet<QString> m_registeredWindowClassNames;
- HandleBaseWindowHash m_windows;
+ QWindowsContext::HandleBaseWindowHash m_windows;
HDC m_displayContext = nullptr;
int m_defaultDPI = 96;
QWindowsKeyMapper m_keyMapper;
@@ -513,6 +511,11 @@ QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const
return d->m_keyMapper.possibleKeys(e);
}
+QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows()
+{
+ return d->m_windows;
+}
+
QSharedPointer<QWindowCreationContext> QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx)
{
const QSharedPointer<QWindowCreationContext> old = d->m_creationContext;
@@ -818,6 +821,8 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c
if (!(cwexFlags & CWP_SKIPTRANSPARENT)
&& (GetWindowLongPtr(child, GWL_EXSTYLE) & WS_EX_TRANSPARENT)) {
const HWND nonTransparentChild = ChildWindowFromPointEx(*hwnd, point, cwexFlags | CWP_SKIPTRANSPARENT);
+ if (!nonTransparentChild || nonTransparentChild == *hwnd)
+ return false;
if (QWindowsWindow *nonTransparentWindow = context->findPlatformWindow(nonTransparentChild)) {
*result = nonTransparentWindow;
*hwnd = nonTransparentChild;
@@ -921,7 +926,7 @@ static inline QString errorMessageFromComError(const _com_error &comError)
TCHAR *message = nullptr;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
nullptr, DWORD(comError.Error()), MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
- message, 0, nullptr);
+ reinterpret_cast<LPWSTR>(&message), 0, nullptr);
if (message) {
const QString result = QString::fromWCharArray(message).trimmed();
LocalFree(static_cast<HLOCAL>(message));
diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h
index c89b8b91f4..8eca31e8bc 100644
--- a/src/plugins/platforms/windows/qwindowscontext.h
+++ b/src/plugins/platforms/windows/qwindowscontext.h
@@ -157,6 +157,7 @@ class QWindowsContext
{
Q_DISABLE_COPY_MOVE(QWindowsContext)
public:
+ using HandleBaseWindowHash = QHash<HWND, QWindowsWindow *>;
enum SystemInfoFlags
{
@@ -236,6 +237,8 @@ public:
bool useRTLExtensions() const;
QList<int> possibleKeys(const QKeyEvent *e) const;
+ HandleBaseWindowHash &windows();
+
static bool isSessionLocked();
QWindowsMimeConverter &mimeConverter() const;
diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp
index 338bb9ff8f..be0f2bad70 100644
--- a/src/plugins/platforms/windows/qwindowscursor.cpp
+++ b/src/plugins/platforms/windows/qwindowscursor.cpp
@@ -652,6 +652,11 @@ void QWindowsCursor::clearOverrideCursor()
SetCursor(m_overriddenCursor);
m_overriddenCursor = m_overrideCursor = nullptr;
}
+ auto &windows = QWindowsContext::instance()->windows();
+ for (auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
+ if (it.value()->screen() == m_screen)
+ it.value()->setFlag(QWindowsWindow::RestoreOverrideCursor);
+ }
}
QPoint QWindowsCursor::mousePosition()
diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
index 6fc90035ed..0ebe5bd413 100644
--- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
+++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
@@ -148,7 +148,7 @@ void eatMouseMove()
Base classes for native dialogs (using the CLSID-based
dialog interfaces "IFileDialog", etc. available from Windows
- Vista on) that mimick the behaviour of their QDialog
+ Vista on) that mimic the behavior of their QDialog
counterparts as close as possible.
Instances of derived classes are controlled by
diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp
index ba049cf359..612e8cd90f 100644
--- a/src/plugins/platforms/windows/qwindowsdrag.cpp
+++ b/src/plugins/platforms/windows/qwindowsdrag.cpp
@@ -420,7 +420,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
}
/*!
- \brief Give feedback: Change cursor accoding to action.
+ \brief Give feedback: Change cursor according to action.
*/
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp
index 6fa5a8a2b3..1f49f0aefd 100644
--- a/src/plugins/platforms/windows/qwindowsglcontext.cpp
+++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp
@@ -48,6 +48,7 @@
#include <qpa/qplatformnativeinterface.h>
#include <QtPlatformHeaders/qwglnativecontext.h>
+#include <private/qsystemlibrary_p.h>
#include <algorithm>
#include <wingdi.h>
@@ -162,19 +163,25 @@ QFunctionPointer QWindowsOpengl32DLL::resolve(const char *name)
bool QWindowsOpengl32DLL::init(bool softwareRendering)
{
- const QByteArray opengl32 = QByteArrayLiteral("opengl32.dll");
- const QByteArray swopengl = QByteArrayLiteral("opengl32sw.dll");
+ const QByteArray opengl32 = QByteArrayLiteral("opengl32");
+ const QByteArray swopengl = QByteArrayLiteral("opengl32sw");
+ bool useSystemLib = false;
QByteArray openglDll = qgetenv("QT_OPENGL_DLL");
- if (openglDll.isEmpty())
+ if (openglDll.isEmpty()) {
openglDll = softwareRendering ? swopengl : opengl32;
+ useSystemLib = !softwareRendering;
+ }
openglDll = openglDll.toLower();
m_nonOpengl32 = openglDll != opengl32;
qCDebug(lcQpaGl) << "Qt: Using WGL and OpenGL from" << openglDll;
- m_lib = ::LoadLibraryA(openglDll.constData());
+ if (useSystemLib)
+ m_lib = QSystemLibrary::load((wchar_t*)(QString::fromLatin1(openglDll).utf16()));
+ else
+ m_lib = LoadLibraryA(openglDll.constData());
if (!m_lib) {
qErrnoWarning(::GetLastError(), "Failed to load %s", openglDll.constData());
return false;
@@ -184,7 +191,7 @@ bool QWindowsOpengl32DLL::init(bool softwareRendering)
// Load opengl32.dll always. GDI functions like ChoosePixelFormat do
// GetModuleHandle for opengl32.dll and behave differently (and call back into
// opengl32) when the module is present. This is fine for dummy contexts and windows.
- ::LoadLibraryA("opengl32.dll");
+ QSystemLibrary::load(L"opengl32");
}
wglCreateContext = reinterpret_cast<HGLRC (WINAPI *)(HDC)>(resolve("wglCreateContext"));
diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
index 09a99ffd02..e82b2a5155 100644
--- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp
+++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
@@ -280,8 +280,13 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
globalPosition = winEventPosition;
clientPosition = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPosition);
} else {
- clientPosition = winEventPosition;
globalPosition = QWindowsGeometryHint::mapToGlobal(hwnd, winEventPosition);
+ auto targetHwnd = hwnd;
+ if (auto *pw = window->handle())
+ targetHwnd = HWND(pw->winId());
+ clientPosition = targetHwnd == hwnd
+ ? winEventPosition
+ : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPosition);
}
// Windows sends a mouse move with no buttons pressed to signal "Enter"
@@ -494,7 +499,7 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}
if (!discardEvent && mouseEvent.type != QEvent::None) {
- QWindowSystemInterface::handleMouseEvent(window, winEventPosition, globalPosition, buttons,
+ QWindowSystemInterface::handleMouseEvent(window,clientPosition, globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
keyModifiers, source);
}
diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp
index d7d186e804..9eb4011bf2 100644
--- a/src/plugins/platforms/windows/qwindowsopengltester.cpp
+++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp
@@ -49,6 +49,7 @@
#include <QtCore/qstandardpaths.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qhash.h>
+#include <private/qsystemlibrary_p.h>
#ifndef QT_NO_OPENGL
#include <private/qopengl_p.h>
@@ -396,7 +397,7 @@ bool QWindowsOpenGLTester::testDesktopGL()
// Test #1: Load opengl32.dll and try to resolve an OpenGL 2 function.
// This will typically fail on systems that do not have a real OpenGL driver.
- lib = LoadLibraryA("opengl32.dll");
+ lib = QSystemLibrary::load(L"opengl32");
if (lib) {
CreateContext = reinterpret_cast<CreateContextType>(
reinterpret_cast<QFunctionPointer>(::GetProcAddress(lib, "wglCreateContext")));
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
index 85cf310b62..c1a2c363f2 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
@@ -447,6 +447,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
{
Q_UNUSED(hwnd);
+ auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
+
if (et & QtWindows::NonClientEventFlag)
return false; // Let DefWindowProc() handle Non Client messages.
@@ -456,10 +458,19 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (msg.message == WM_POINTERCAPTURECHANGED) {
QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice,
QWindowsKeyMapper::queryKeyboardModifiers());
- m_lastTouchPositions.clear();
+ m_lastTouchPoints.clear();
return true;
}
+ if (msg.message == WM_POINTERLEAVE) {
+ for (quint32 i = 0; i < count; ++i) {
+ const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
+ int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
+ if (id != -1)
+ m_lastTouchPoints.remove(id);
+ }
+ }
+
// Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
if (msg.message > WM_POINTERUP)
return false;
@@ -470,8 +481,6 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (!screen)
return false;
- auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
-
const QRect screenGeometry = screen->geometry();
QList<QWindowSystemInterface::TouchPoint> touchPoints;
@@ -483,6 +492,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
<< " count=" << Qt::dec << count;
Qt::TouchPointStates allStates;
+ QSet<int> inputIds;
for (quint32 i = 0; i < count; ++i) {
if (QWindowsContext::verbose > 1)
@@ -495,14 +505,17 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
if (id == -1) {
+ // Start tracking after fingers touch the screen. Ignore bogus updates after touch is released.
+ if ((touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) == 0)
+ continue;
id = m_touchInputIDToTouchPointID.size();
m_touchInputIDToTouchPointID.insert(pointerId, id);
}
touchPoint.id = id;
touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ?
touchInfo[i].pressure / 1024.0 : 1.0;
- if (m_lastTouchPositions.contains(touchPoint.id))
- touchPoint.normalPosition = m_lastTouchPositions.value(touchPoint.id);
+ if (m_lastTouchPoints.contains(touchPoint.id))
+ touchPoint.normalPosition = m_lastTouchPoints.value(touchPoint.id).normalPosition;
const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x,
touchInfo[i].pointerInfo.ptPixelLocation.y);
@@ -518,22 +531,36 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
touchPoint.state = Qt::TouchPointPressed;
- m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition);
+ m_lastTouchPoints.insert(touchPoint.id, touchPoint);
} else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) {
touchPoint.state = Qt::TouchPointReleased;
- m_lastTouchPositions.remove(touchPoint.id);
+ m_lastTouchPoints.remove(touchPoint.id);
} else {
touchPoint.state = stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved;
- m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition);
+ m_lastTouchPoints.insert(touchPoint.id, touchPoint);
}
allStates |= touchPoint.state;
touchPoints.append(touchPoint);
+ inputIds.insert(touchPoint.id);
// Avoid getting repeated messages for this frame if there are multiple pointerIds
QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
}
+ // Some devices send touches for each finger in a different message/frame, instead of consolidating
+ // them in the same frame as we were expecting. We account for missing unreleased touches here.
+ for (auto tp : qAsConst(m_lastTouchPoints)) {
+ if (!inputIds.contains(tp.id)) {
+ tp.state = Qt::TouchPointStationary;
+ allStates |= tp.state;
+ touchPoints.append(tp);
+ }
+ }
+
+ if (touchPoints.count() == 0)
+ return false;
+
// all touch points released, forget the ids we've seen.
if (allStates == Qt::TouchPointReleased)
m_touchInputIDToTouchPointID.clear();
@@ -712,8 +739,13 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
globalPos = eventPos;
localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
} else {
- localPos = eventPos;
globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
+ auto targetHwnd = hwnd;
+ if (auto *pw = window->handle())
+ targetHwnd = HWND(pw->winId());
+ localPos = targetHwnd == hwnd
+ ? eventPos
+ : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
}
const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.h b/src/plugins/platforms/windows/qwindowspointerhandler.h
index 8874db27e3..73fd418ca2 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.h
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.h
@@ -47,6 +47,7 @@
#include <QtCore/qscopedpointer.h>
#include <QtCore/qhash.h>
#include <QtGui/qevent.h>
+#include <qpa/qwindowsysteminterface.h>
QT_BEGIN_NAMESPACE
@@ -74,7 +75,7 @@ private:
void handleEnterLeave(QWindow *window, QWindow *currentWindowUnderPointer, QPoint globalPos);
QTouchDevice *m_touchDevice = nullptr;
- QHash<int, QPointF> m_lastTouchPositions;
+ QHash<int, QWindowSystemInterface::TouchPoint> m_lastTouchPoints;
QHash<DWORD, int> m_touchInputIDToTouchPointID;
QPointer<QWindow> m_windowUnderPointer;
QPointer<QWindow> m_currentWindow;
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index d2c22f4100..9d9180e4f5 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -2047,7 +2047,7 @@ HDC QWindowsWindow::getDC()
}
/*!
- Relases the HDC for the window or does nothing in
+ Releases the HDC for the window or does nothing in
case it was obtained from WinAPI BeginPaint within a WM_PAINT event.
\sa getDC()
@@ -2156,6 +2156,7 @@ QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt,
QWindowsWindowData result = m_data;
result.flags = creationData.flags;
result.embedded = creationData.embedded;
+ result.hasFrame = (creationData.style & (WS_DLGFRAME | WS_THICKFRAME));
return result;
}
@@ -2682,32 +2683,30 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
// This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the
// taskbar when maximized
- if ((testFlag(WithinMaximize) || window()->windowStates().testFlag(Qt::WindowMinimized))
- && (m_data.flags.testFlag(Qt::FramelessWindowHint)
- || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))) {
- const QScreen *screen = window()->screen();
-
- // Documentation of MINMAXINFO states that it will only work for the primary screen
- if (screen && screen == QGuiApplication::primaryScreen()) {
- const QRect availableGeometry = QHighDpi::toNativePixels(screen->availableGeometry(), screen);
+ if (m_data.flags.testFlag(Qt::FramelessWindowHint)
+ || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint))) {
+ if (QPlatformScreen *currentScreen = screen()) {
+ const QRect geometry = currentScreen->geometry();
+ const QRect availableGeometry = currentScreen->availableGeometry();
mmi->ptMaxSize.y = availableGeometry.height();
// Width, because you can have the taskbar on the sides too.
mmi->ptMaxSize.x = availableGeometry.width();
// If you have the taskbar on top, or on the left you don't want it at (0,0):
- mmi->ptMaxPosition.x = availableGeometry.x();
- mmi->ptMaxPosition.y = availableGeometry.y();
+ QPoint availablePositionDiff = availableGeometry.topLeft() - geometry.topLeft();
+ mmi->ptMaxPosition.x = availablePositionDiff.x();
+ mmi->ptMaxPosition.y = availablePositionDiff.y();
if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
- const int borderWidth = getBorderWidth(screen->handle());
+ const int borderWidth = getBorderWidth(currentScreen);
mmi->ptMaxSize.x += borderWidth * 2;
mmi->ptMaxSize.y += borderWidth * 2;
mmi->ptMaxTrackSize = mmi->ptMaxSize;
mmi->ptMaxPosition.x -= borderWidth;
mmi->ptMaxPosition.y -= borderWidth;
}
- } else if (!screen){
- qWarning("window()->screen() returned a null screen");
+ } else {
+ qWarning("screen() returned a null screen");
}
}
@@ -2819,7 +2818,14 @@ void QWindowsWindow::applyCursor()
void QWindowsWindow::setCursor(const CursorHandlePtr &c)
{
#ifndef QT_NO_CURSOR
- if (c->handle() != m_cursor->handle()) {
+ bool changed = c->handle() != m_cursor->handle();
+ // QTBUG-98856: Cursors can get out of sync after restoring override
+ // cursors on native windows. Force an update.
+ if (testFlag(RestoreOverrideCursor)) {
+ clearFlag(RestoreOverrideCursor);
+ changed = true;
+ }
+ if (changed) {
const bool apply = applyNewCursor(window());
qCDebug(lcQpaWindows) << window() << __FUNCTION__
<< c->handle() << " doApply=" << apply;
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
index 6cd3cd979a..ac207aa48f 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
@@ -225,7 +225,8 @@ public:
WithinDpiChanged = 0x400000,
VulkanSurface = 0x800000,
ResizeMoveActive = 0x1000000,
- DisableNonClientScaling = 0x2000000
+ DisableNonClientScaling = 0x2000000,
+ RestoreOverrideCursor = 0x4000000
};
QWindowsWindow(QWindow *window, const QWindowsWindowData &data);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
index 93b9622655..50888d4a8e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
@@ -58,6 +58,7 @@ QT_BEGIN_NAMESPACE
using namespace QWindowsUiAutomation;
+bool QWindowsUiaAccessibility::m_accessibleActive = false;
QWindowsUiaAccessibility::QWindowsUiaAccessibility()
{
@@ -72,6 +73,7 @@ bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARA
{
// Start handling accessibility internally
QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true);
+ m_accessibleActive = true;
// Ignoring all requests while starting up / shutting down
if (QCoreApplication::startingUp() || QCoreApplication::closingDown())
@@ -131,6 +133,11 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
if (!event)
return;
+ // Ignore events sent before the first UI Automation
+ // request or while QAccessible is being activated.
+ if (!m_accessibleActive)
+ return;
+
switch (event->type()) {
case QAccessible::PopupMenuStart:
playSystemSound(QStringLiteral("MenuPopup"));
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
index 48b4f9fa6a..ac01a51e76 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
@@ -56,6 +56,8 @@ public:
virtual ~QWindowsUiaAccessibility();
static bool handleWmGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult);
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
+private:
+ static bool m_accessibleActive;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
index 3bb0d08da9..a01104a8e3 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
@@ -74,10 +74,13 @@ QT_BEGIN_NAMESPACE
using namespace QWindowsUiAutomation;
+QMutex QWindowsUiaMainProvider::m_mutex;
// Returns a cached instance of the provider for a specific acessible interface.
QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible)
{
+ QMutexLocker locker(&m_mutex);
+
if (!accessible)
return nullptr;
@@ -108,19 +111,11 @@ void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
// If this is a table/tree/list, raise event for the focused cell/item instead.
- if (accessible->tableInterface()) {
- int count = accessible->childCount();
- for (int i = 0; i < count; ++i) {
- QAccessibleInterface *item = accessible->child(i);
- if (item && item->isValid() && item->state().focused) {
- accessible = item;
- break;
- }
- }
- }
- if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
+ if (accessible->tableInterface())
+ if (QAccessibleInterface *child = accessible->focusChild())
+ accessible = child;
+ if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible))
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId);
- }
}
}
@@ -214,12 +209,16 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve
void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
- if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
- VARIANT oldVal, newVal;
- clearVariant(&oldVal);
- setVariantString(accessible->text(QAccessible::Name), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
- ::SysFreeString(newVal.bstrVal);
+ // Restrict notification to combo boxes, which need it for accessibility,
+ // in order to avoid slowdowns with unnecessary notifications.
+ if (accessible->role() == QAccessible::ComboBox) {
+ if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
+ VARIANT oldVal, newVal;
+ clearVariant(&oldVal);
+ setVariantString(accessible->text(QAccessible::Name), &newVal);
+ QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
+ ::SysFreeString(newVal.bstrVal);
+ }
}
}
}
@@ -275,6 +274,8 @@ ULONG QWindowsUiaMainProvider::AddRef()
ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release()
{
+ QMutexLocker locker(&m_mutex);
+
if (!--m_ref) {
delete this;
return 0;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
index f7320388f7..8aadabd227 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
@@ -47,6 +47,7 @@
#include <QtCore/qpointer.h>
#include <QtCore/qsharedpointer.h>
+#include <QtCore/qmutex.h>
#include <QtCore/qt_windows.h>
#include <QtGui/qaccessible.h>
@@ -98,6 +99,7 @@ public:
private:
QString automationIdForAccessible(const QAccessibleInterface *accessible);
ULONG m_ref;
+ static QMutex m_mutex;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
index c55e827a46..0483cf4263 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
@@ -75,7 +75,7 @@ void QWindowsUiaProviderCache::insert(QAccessible::Id id, QWindowsUiaBaseProvide
m_providerTable[id] = provider;
m_inverseTable[provider] = id;
// Connects the destroyed signal to our slot, to remove deleted objects from the cache.
- QObject::connect(provider, &QObject::destroyed, this, &QWindowsUiaProviderCache::objectDestroyed);
+ QObject::connect(provider, &QObject::destroyed, this, &QWindowsUiaProviderCache::objectDestroyed, Qt::DirectConnection);
}
}
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
index 682b8c19c0..12bdc9e6b7 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
@@ -132,22 +132,19 @@ void setVariantString(const QString &value, VARIANT *variant)
void rectToNativeUiaRect(const QRect &rect, const QWindow *w, UiaRect *uiaRect)
{
if (w && uiaRect) {
- const qreal factor = QHighDpiScaling::factor(w);
- uiaRect->left = qreal(rect.x()) * factor;
- uiaRect->top = qreal(rect.y()) * factor;
- uiaRect->width = qreal(rect.width()) * factor;
- uiaRect->height = qreal(rect.height()) * factor;
+ QRectF r = QHighDpi::toNativePixels(QRectF(rect), w);
+ uiaRect->left =r.x();
+ uiaRect->top = r.y();
+ uiaRect->width = r.width();
+ uiaRect->height = r.height();
}
}
// Scales a point from native coordinates, according to high dpi settings.
void nativeUiaPointToPoint(const UiaPoint &uiaPoint, const QWindow *w, QPoint *point)
{
- if (w && point) {
- const qreal factor = QHighDpiScaling::factor(w);
- point->setX(int(std::lround(uiaPoint.x / factor)));
- point->setY(int(std::lround(uiaPoint.y / factor)));
- }
+ if (w && point)
+ *point = QHighDpi::fromNativePixels(QPoint(uiaPoint.x, uiaPoint.y), w);
}
// Maps an accessibility role ID to an UI Automation control type ID.
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp
index 5e406017ca..e8c9c27ba8 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qxcbglxwindow.cpp
@@ -58,7 +58,7 @@ const xcb_visualtype_t *QXcbGlxWindow::createVisual()
{
QXcbScreen *scr = xcbScreen();
if (!scr)
- return nullptr;
+ return QXcbWindow::createVisual();
qCDebug(lcQpaGl) << "Requested format before FBConfig/Visual selection:" << m_format;
@@ -71,10 +71,13 @@ const xcb_visualtype_t *QXcbGlxWindow::createVisual()
flags |= QGLX_SUPPORTS_SRGB;
}
+ const auto formatBackup = m_format;
XVisualInfo *visualInfo = qglx_findVisualInfo(dpy, scr->screenNumber(), &m_format, GLX_WINDOW_BIT, flags);
if (!visualInfo) {
- qWarning() << "No XVisualInfo for format" << m_format;
- return nullptr;
+ qCDebug(lcQpaGl) << "No XVisualInfo for format" << m_format;
+ // restore initial format before requesting it again
+ m_format = formatBackup;
+ return QXcbWindow::createVisual();
}
const xcb_visualtype_t *xcb_visualtype = scr->visualForId(visualInfo->visualid);
XFree(visualInfo);
diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
index 1afa00cfc9..118af6ce73 100644
--- a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
@@ -41,6 +41,7 @@
#include <QRect>
#include <QList>
+#include <QMap>
#include <QDebug>
#include <qmath.h>
diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.c b/src/plugins/platforms/xcb/qt_xlib_wrapper.c
new file mode 100644
index 0000000000..d45f468e8c
--- /dev/null
+++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.c
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "qt_xlib_wrapper.h"
+
+#include <X11/Xlib.h>
+
+void qt_XFlush(Display *dpy) { XFlush(dpy); }
diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.h b/src/plugins/platforms/xcb/qt_xlib_wrapper.h
new file mode 100644
index 0000000000..a656c2e50a
--- /dev/null
+++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef QT_XLIB_WRAPPER_H
+#define QT_XLIB_WRAPPER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct _XDisplay Display;
+ void qt_XFlush(Display *dpy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // QT_XLIB_WRAPPER_H
diff --git a/src/plugins/platforms/xcb/qxcbatom.cpp b/src/plugins/platforms/xcb/qxcbatom.cpp
index 780816605a..a769ddadbd 100644
--- a/src/plugins/platforms/xcb/qxcbatom.cpp
+++ b/src/plugins/platforms/xcb/qxcbatom.cpp
@@ -90,6 +90,8 @@ static const char *xcb_atomnames = {
"_QT_CLOSE_CONNECTION\0"
+ "_QT_GET_TIMESTAMP\0"
+
"_MOTIF_WM_HINTS\0"
"DTWM_IS_RUNNING\0"
diff --git a/src/plugins/platforms/xcb/qxcbatom.h b/src/plugins/platforms/xcb/qxcbatom.h
index 9cf93ec314..1ce6cca573 100644
--- a/src/plugins/platforms/xcb/qxcbatom.h
+++ b/src/plugins/platforms/xcb/qxcbatom.h
@@ -91,6 +91,8 @@ public:
// Qt/XCB specific
_QT_CLOSE_CONNECTION,
+ _QT_GET_TIMESTAMP,
+
_MOTIF_WM_HINTS,
DTWM_IS_RUNNING,
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
index 0a4d675606..2f37ee64b5 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp
@@ -71,7 +71,7 @@ public:
break;
default:
- qWarning("QXcbClipboardMime: Internal error: Unsupported clipboard mode");
+ qCWarning(lcQpaClipboard, "QXcbClipboardMime: Internal error: Unsupported clipboard mode");
break;
}
}
@@ -265,7 +265,7 @@ QXcbClipboard::~QXcbClipboard()
if (auto event = waitForClipboardEvent(m_owner, XCB_SELECTION_NOTIFY, true)) {
free(event);
} else {
- qWarning("QXcbClipboard: Unable to receive an event from the "
+ qCWarning(lcQpaClipboard, "QXcbClipboard: Unable to receive an event from the "
"clipboard manager in a reasonable time");
}
}
@@ -371,7 +371,7 @@ void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
xcb_set_selection_owner(xcb_connection(), newOwner, modeAtom, connection()->time());
if (getSelectionOwner(modeAtom) != newOwner) {
- qWarning("QXcbClipboard::setMimeData: Cannot set X11 selection owner");
+ qCWarning(lcQpaClipboard, "QXcbClipboard::setMimeData: Cannot set X11 selection owner");
}
emitChanged(mode);
@@ -538,7 +538,7 @@ void QXcbClipboard::handleSelectionClearRequest(xcb_selection_clear_event_t *eve
void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
{
if (requestor() && req->requestor == requestor()) {
- qWarning("QXcbClipboard: Selection request should be caught before");
+ qCWarning(lcQpaClipboard, "QXcbClipboard: Selection request should be caught before");
return;
}
@@ -553,7 +553,8 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
QMimeData *d;
QClipboard::Mode mode = modeForAtom(req->selection);
if (mode > QClipboard::Selection) {
- qWarning() << "QXcbClipboard: Unknown selection" << connection()->atomName(req->selection);
+ qCWarning(lcQpaClipboard, "QXcbClipboard: Unknown selection %s",
+ connection()->atomName(req->selection).constData());
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
return;
}
@@ -561,14 +562,14 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
d = m_clientClipboard[mode];
if (!d) {
- qWarning("QXcbClipboard: Cannot transfer data, no data available");
+ qCWarning(lcQpaClipboard, "QXcbClipboard: Cannot transfer data, no data available");
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
return;
}
if (m_timestamp[mode] == XCB_CURRENT_TIME // we don't own the selection anymore
|| (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
- qWarning("QXcbClipboard: SelectionRequest too old");
+ qCDebug(lcQpaClipboard, "QXcbClipboard: SelectionRequest too old");
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
return;
}
@@ -623,7 +624,7 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
property, XCB_ATOM_INTEGER, 32, 1, &m_timestamp[mode]);
ret = property;
} else {
- qWarning("QXcbClipboard: Invalid data timestamp");
+ qCWarning(lcQpaClipboard, "QXcbClipboard: Invalid data timestamp");
}
} else if (target == targetsAtom) {
ret = sendTargetsSelection(d, req->requestor, property);
@@ -728,7 +729,7 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property,
// recover -- this shouldn't normally happen, but it doesn't
// hurt to be defensive
if ((int)(buffer_offset + length) > buffer->size()) {
- qWarning("QXcbClipboard: buffer overflow");
+ qCWarning(lcQpaClipboard, "QXcbClipboard: buffer overflow");
length = buffer->size() - buffer_offset;
// escape loop
@@ -835,6 +836,8 @@ QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb
alloc_error = buf.size() != nbytes+1;
}
+ QElapsedTimer timer;
+ timer.start();
for (;;) {
connection()->flush();
xcb_generic_event_t *ge = waitForClipboardEvent(win, XCB_PROPERTY_NOTIFY);
@@ -870,9 +873,11 @@ QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb
tmp_buf.resize(0);
offset += length;
}
- } else {
- break;
}
+
+ const auto elapsed = timer.elapsed();
+ if (elapsed > clipboard_timeout)
+ break;
}
// timed out ... create a new requestor window, otherwise the requestor
@@ -921,4 +926,5 @@ QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target,
QT_END_NAMESPACE
+#include "moc_qxcbclipboard.cpp"
#include "qxcbclipboard.moc"
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index 9abdae6a7c..013ca7369f 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
@@ -71,6 +71,10 @@
#undef explicit
#include <xcb/xinput.h>
+#if QT_CONFIG(xcb_xlib)
+#include "qt_xlib_wrapper.h"
+#endif
+
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input")
@@ -221,6 +225,12 @@ void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *mess
}
#define CASE_PRINT_AND_RETURN(name) case name : PRINT_AND_RETURN(#name);
+#define XI_PRINT_AND_RETURN(name) { \
+ qCDebug(log, "%s | XInput Event(%s) | sequence: %d", message, name, sequence); \
+ return; \
+}
+#define XI_CASE_PRINT_AND_RETURN(name) case name : XI_PRINT_AND_RETURN(#name);
+
switch (response_type) {
CASE_PRINT_AND_RETURN( XCB_KEY_PRESS );
CASE_PRINT_AND_RETURN( XCB_KEY_RELEASE );
@@ -255,7 +265,44 @@ void QXcbConnection::printXcbEvent(const QLoggingCategory &log, const char *mess
CASE_PRINT_AND_RETURN( XCB_COLORMAP_NOTIFY );
CASE_PRINT_AND_RETURN( XCB_CLIENT_MESSAGE );
CASE_PRINT_AND_RETURN( XCB_MAPPING_NOTIFY );
- CASE_PRINT_AND_RETURN( XCB_GE_GENERIC );
+ case XCB_GE_GENERIC: {
+ if (hasXInput2() && isXIEvent(event)) {
+ auto *xiDeviceEvent = reinterpret_cast<const xcb_input_button_press_event_t*>(event); // qt_xcb_input_device_event_t
+ switch (xiDeviceEvent->event_type) {
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_KEY_PRESS );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_KEY_RELEASE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BUTTON_PRESS );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BUTTON_RELEASE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_MOTION );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_ENTER );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_LEAVE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_FOCUS_IN );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_FOCUS_OUT );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_HIERARCHY );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_PROPERTY );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_KEY_PRESS );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_KEY_RELEASE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_BUTTON_PRESS );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_BUTTON_RELEASE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_MOTION );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_BEGIN );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_UPDATE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_END );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_TOUCH_OWNERSHIP );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_BEGIN );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_UPDATE );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_RAW_TOUCH_END );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BARRIER_HIT );
+ XI_CASE_PRINT_AND_RETURN( XCB_INPUT_BARRIER_LEAVE );
+ default:
+ qCDebug(log, "%s | XInput Event(other type) | sequence: %d", message, sequence);
+ return;
+ }
+ } else {
+ qCDebug(log, "%s | %s(%d) | sequence: %d", message, "XCB_GE_GENERIC", response_type, sequence);
+ return;
+ }
+ }
}
// XFixes
if (isXFixesType(response_type, XCB_XFIXES_SELECTION_NOTIFY))
@@ -445,12 +492,12 @@ void QXcbConnection::printXcbError(const char *message, xcb_generic_error_t *err
uint clamped_error_code = qMin<uint>(error->error_code, (sizeof(xcb_errors) / sizeof(xcb_errors[0])) - 1);
uint clamped_major_code = qMin<uint>(error->major_code, (sizeof(xcb_protocol_request_codes) / sizeof(xcb_protocol_request_codes[0])) - 1);
- qCWarning(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d",
- message,
- int(error->error_code), xcb_errors[clamped_error_code],
- int(error->sequence), int(error->resource_id),
- int(error->major_code), xcb_protocol_request_codes[clamped_major_code],
- int(error->minor_code));
+ qCDebug(lcQpaXcb, "%s: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d",
+ message,
+ int(error->error_code), xcb_errors[clamped_error_code],
+ int(error->sequence), int(error->resource_id),
+ int(error->major_code), xcb_protocol_request_codes[clamped_major_code],
+ int(error->minor_code));
}
static Qt::MouseButtons translateMouseButtons(int s)
@@ -755,8 +802,8 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
{
// send a dummy event to myself to get the timestamp from X server.
xcb_window_t window = rootWindow();
- xcb_atom_t dummyAtom = atom(QXcbAtom::CLIP_TEMPORARY);
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_APPEND, window, dummyAtom,
+ xcb_atom_t dummyAtom = atom(QXcbAtom::_QT_GET_TIMESTAMP);
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, dummyAtom,
XCB_ATOM_INTEGER, 32, 0, nullptr);
connection()->flush();
@@ -788,14 +835,18 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
xcb_timestamp_t timestamp = pn->time;
free(event);
- xcb_delete_property(xcb_connection(), window, dummyAtom);
-
return timestamp;
}
xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const
{
- return Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom)->owner;
+ auto reply = Q_XCB_REPLY(xcb_get_selection_owner, xcb_connection(), atom);
+ if (!reply) {
+ qCDebug(lcQpaXcb) << "failed to query selection owner";
+ return XCB_NONE;
+ }
+
+ return reply->owner;
}
xcb_window_t QXcbConnection::getQtSelectionOwner()
@@ -1019,6 +1070,10 @@ void QXcbConnection::processXcbEvents(QEventLoop::ProcessEventsFlags flags)
m_eventQueue->flushBufferedEvents();
}
+#if QT_CONFIG(xcb_xlib)
+ qt_XFlush(static_cast<Display *>(xlib_display()));
+#endif
+
xcb_flush(xcb_connection());
}
@@ -1150,3 +1205,5 @@ void QXcbConnectionGrabber::release()
}
QT_END_NAMESPACE
+
+#include "moc_qxcbconnection.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
index 18dee89adb..115a196769 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
@@ -179,7 +179,13 @@ xcb_atom_t QXcbBasicConnection::internAtom(const char *name)
if (!name || *name == 0)
return XCB_NONE;
- return Q_XCB_REPLY(xcb_intern_atom, m_xcbConnection, false, strlen(name), name)->atom;
+ auto reply = Q_XCB_REPLY(xcb_intern_atom, m_xcbConnection, false, strlen(name), name);
+ if (!reply) {
+ qCDebug(lcQpaXcb) << "failed to query intern atom: " << name;
+ return XCB_NONE;
+ }
+
+ return reply->atom;
}
QByteArray QXcbBasicConnection::atomName(xcb_atom_t atom)
@@ -424,3 +430,5 @@ void QXcbBasicConnection::initializeXKB()
}
QT_END_NAMESPACE
+
+#include "moc_qxcbconnection_basic.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
index 27a2526df1..1ced02f31d 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
@@ -591,8 +591,12 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail,
fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
- if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event))
+ if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
xi2ProcessTouch(xiDeviceEvent, platformWindow);
+ } else { // When the window cannot be matched, delete it from touchPoints
+ if (TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid))
+ dev->touchPoints.remove((xiDeviceEvent->detail % INT_MAX));
+ }
break;
}
} else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) {
@@ -645,7 +649,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
continue;
if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf",
- atomName(vci.label).constData(), value, vci.min, vci.max);
+ atomName(atom(vci.label)).constData(), value, vci.min, vci.max);
if (value > vci.max)
value = vci.max;
if (value < vci.min)
diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp
index 42c7a52bd4..4210bf428e 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.cpp
+++ b/src/plugins/platforms/xcb/qxcbcursor.cpp
@@ -534,6 +534,8 @@ bool updateCursorTheme(void *dpy, const QByteArray &theme) {
Q_UNUSED(screen);
Q_UNUSED(name);
QXcbCursor *self = static_cast<QXcbCursor *>(handle);
+ self->m_cursorHash.clear();
+
updateCursorTheme(self->connection()->xlib_display(),property.toByteArray());
}
@@ -559,14 +561,16 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape)
int cursorId = cursorIdForShape(cshape);
xcb_cursor_t cursor = XCB_NONE;
- // Try Xcursor first
#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
+ if (m_screen->xSettings()->initialized())
+ m_screen->xSettings()->registerCallbackForProperty("Gtk/CursorThemeName",cursorThemePropertyChanged,this);
+
+ // Try Xcursor first
if (cshape >= 0 && cshape <= Qt::LastCursor) {
void *dpy = connection()->xlib_display();
cursor = loadCursor(dpy, cshape);
if (!cursor && !m_gtkCursorThemeInitialized && m_screen->xSettings()->initialized()) {
QByteArray gtkCursorTheme = m_screen->xSettings()->setting("Gtk/CursorThemeName").toByteArray();
- m_screen->xSettings()->registerCallbackForProperty("Gtk/CursorThemeName",cursorThemePropertyChanged,this);
if (updateCursorTheme(dpy,gtkCursorTheme)) {
cursor = loadCursor(dpy, cshape);
}
@@ -668,7 +672,8 @@ void QXcbCursor::setPos(const QPoint &pos)
{
QXcbVirtualDesktop *virtualDesktop = nullptr;
queryPointer(connection(), &virtualDesktop, nullptr);
- xcb_warp_pointer(xcb_connection(), XCB_NONE, virtualDesktop->root(), 0, 0, 0, 0, pos.x(), pos.y());
+ if (virtualDesktop)
+ xcb_warp_pointer(xcb_connection(), XCB_NONE, virtualDesktop->root(), 0, 0, 0, 0, pos.x(), pos.y());
xcb_flush(xcb_connection());
}
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index e0aaabbbdf..299835ba1e 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -785,7 +785,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
Q_ASSERT(w);
QRect geometry = w->geometry();
- p -= geometry.topLeft();
+ p -= w->isEmbedded() ? w->mapToGlobal(geometry.topLeft()) : geometry.topLeft();
if (!w || !w->window() || (w->window()->type() == Qt::Desktop))
return;
@@ -1041,21 +1041,30 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
Qt::DropActions supported_drop_actions;
QMimeData *dropData = nullptr;
+ // this could be a same-application drop, just proxied due to
+ // some XEMBEDding, so try to find the real QMimeData used
+ // based on the timestamp for this drop.
+ int at = findTransactionByTime(target_time);
+ if (at != -1) {
+ qCDebug(lcQpaXDnd) << "found one transaction via findTransactionByTime()";
+ dropData = transactions.at(at).drag->mimeData();
+ // Can't use the source QMimeData if we need the image conversion code from xdndObtainData
+ if (dropData && dropData->hasImage())
+ dropData = 0;
+ }
+ // if we can't find it, then use the data in the drag manager
if (currentDrag()) {
- dropData = currentDrag()->mimeData();
+ if (!dropData)
+ dropData = currentDrag()->mimeData();
supported_drop_actions = Qt::DropActions(l[4]);
} else {
- dropData = m_dropData;
+ if (!dropData)
+ dropData = m_dropData;
supported_drop_actions = accepted_drop_action | toDropActions(drop_actions);
}
if (!dropData)
return;
- // ###
- // int at = findXdndDropTransactionByTime(target_time);
- // if (at != -1)
- // dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
- // if we can't find it, then use the data in the drag manager
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
@@ -1064,7 +1073,12 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
currentWindow.data(), dropData, currentPosition, supported_drop_actions,
buttons, modifiers);
- setExecutedDropAction(response.acceptedAction());
+ Qt::DropAction acceptedAaction = response.acceptedAction();
+ if (!response.isAccepted()) {
+ // Ignore a failed drag
+ acceptedAaction = Qt::IgnoreAction;
+ }
+ setExecutedDropAction(acceptedAaction);
xcb_client_message_event_t finished = {};
finished.response_type = XCB_CLIENT_MESSAGE;
@@ -1074,7 +1088,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
finished.type = atom(QXcbAtom::XdndFinished);
finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
finished.data.data32[1] = response.isAccepted(); // flags
- finished.data.data32[2] = toXdndAction(response.acceptedAction());
+ finished.data.data32[2] = toXdndAction(acceptedAaction);
qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;
@@ -1280,6 +1294,7 @@ void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event
bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
{
+ qCDebug(lcQpaXDnd) << "dndEnable" << w << on;
// Windows announce that they support the XDND protocol by creating a window property XdndAware.
if (on) {
QXcbWindow *window = nullptr;
diff --git a/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp b/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp
index 3cb2a5b5ef..eb0280cfb7 100644
--- a/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp
+++ b/src/plugins/platforms/xcb/qxcbeventdispatcher.cpp
@@ -160,3 +160,5 @@ QAbstractEventDispatcher *QXcbEventDispatcher::createEventDispatcher(QXcbConnect
}
QT_END_NAMESPACE
+
+#include "moc_qxcbeventdispatcher.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbeventqueue.cpp b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
index 9f3c381216..14f53106e9 100644
--- a/src/plugins/platforms/xcb/qxcbeventqueue.cpp
+++ b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
@@ -400,3 +400,5 @@ bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
}
QT_END_NAMESPACE
+
+#include "moc_qxcbeventqueue.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
index e8286381a2..f43b2b66e1 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
@@ -441,7 +441,7 @@ static xkb_layout_index_t lockedGroup(quint16 state)
void QXcbKeyboard::updateXKBStateFromCore(quint16 state)
{
- if (m_config && !connection()->hasXKB()) {
+ if (m_config) {
struct xkb_state *xkbState = m_xkbState.get();
xkb_mod_mask_t modsDepressed = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_DEPRESSED);
xkb_mod_mask_t modsLatched = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_LATCHED);
@@ -463,7 +463,7 @@ void QXcbKeyboard::updateXKBStateFromCore(quint16 state)
void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo)
{
- if (m_config && !connection()->hasXKB()) {
+ if (m_config) {
auto *mods = static_cast<xcb_input_modifier_info_t *>(modInfo);
auto *group = static_cast<xcb_input_group_info_t *>(groupInfo);
const xkb_state_component changedComponents
diff --git a/src/plugins/platforms/xcb/qxcbmime.cpp b/src/plugins/platforms/xcb/qxcbmime.cpp
index 0b3219f792..bbce69614b 100644
--- a/src/plugins/platforms/xcb/qxcbmime.cpp
+++ b/src/plugins/platforms/xcb/qxcbmime.cpp
@@ -319,3 +319,5 @@ xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString
}
QT_END_NAMESPACE
+
+#include "moc_qxcbmime.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
index 30fa6864ac..c96bcb1dab 100644
--- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
+++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
@@ -675,3 +675,5 @@ QString QXcbNativeInterface::dumpNativeWindows(WId root) const
}
QT_END_NAMESPACE
+
+#include "moc_qxcbnativeinterface.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h
index 4656f46be5..f80f23f0cb 100644
--- a/src/plugins/platforms/xcb/qxcbnativeinterface.h
+++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h
@@ -133,8 +133,6 @@ signals:
private:
const QByteArray m_nativeEventType = QByteArrayLiteral("xcb_generic_event_t");
- xcb_atom_t m_sysTraySelectionAtom = XCB_ATOM_NONE;
-
static QXcbScreen *qPlatformScreenForWindow(QWindow *window);
QList<QXcbNativeInterfaceHandler *> m_handlers;
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index 707896b457..9edba7257e 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -114,17 +114,13 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t
}
auto dpiChangedCallback = [](QXcbVirtualDesktop *desktop, const QByteArray &, const QVariant &property, void *) {
- bool ok;
- int dpiTimes1k = property.toInt(&ok);
- if (!ok)
+ if (!desktop->setDpiFromXSettings(property))
return;
- int dpi = dpiTimes1k / 1024;
- if (desktop->m_forcedDpi == dpi)
- return;
- desktop->m_forcedDpi = dpi;
+ const auto dpi = desktop->forcedDpi();
for (QXcbScreen *screen : desktop->connection()->screens())
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen->QPlatformScreen::screen(), dpi, dpi);
};
+ setDpiFromXSettings(xSettings()->setting("Xft/DPI"));
xSettings()->registerCallbackForProperty("Xft/DPI", dpiChangedCallback, nullptr);
}
@@ -275,7 +271,7 @@ void QXcbVirtualDesktop::handleScreenChange(xcb_randr_screen_change_notify_event
_NET_WORKAREA means with multiple attached monitors. This gets worse when monitors have
different dimensions and/or screens are not virtually aligned. In Qt we want the available
geometry per monitor (QScreen), not desktop (represented by _NET_WORKAREA). WM specification
- does not have an atom for this. Thus, QScreen is limted by the lack of support from the
+ does not have an atom for this. Thus, QScreen is limited by the lack of support from the
underlying system.
One option could be that Qt does WM's job of calculating this by subtracting geometries of
@@ -425,6 +421,19 @@ void QXcbVirtualDesktop::readXResources()
}
}
+bool QXcbVirtualDesktop::setDpiFromXSettings(const QVariant &property)
+{
+ bool ok;
+ int dpiTimes1k = property.toInt(&ok);
+ if (!ok)
+ return false;
+ int dpi = dpiTimes1k / 1024;
+ if (m_forcedDpi == dpi)
+ return false;
+ m_forcedDpi = dpi;
+ return true;
+}
+
QSurfaceFormat QXcbVirtualDesktop::surfaceFormatFor(const QSurfaceFormat &format) const
{
const xcb_visualid_t xcb_visualid = connection()->hasDefaultVisualId() ? connection()->defaultVisualId()
diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h
index bf48dd5d5b..c3a59dc9d1 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.h
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
@@ -118,6 +118,8 @@ private:
QByteArray &stringValue);
void readXResources();
+ bool setDpiFromXSettings(const QVariant &property);
+
xcb_screen_t *m_screen;
const int m_number;
QList<QPlatformScreen *> m_screens;
diff --git a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
index ff5ad98cd2..7f1fc7f5eb 100644
--- a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
+++ b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
@@ -176,3 +176,5 @@ xcb_visualid_t QXcbSystemTrayTracker::netSystemTrayVisual()
}
QT_END_NAMESPACE
+
+#include "moc_qxcbsystemtraytracker.cpp"
diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp
index 050182537d..45bac8ee85 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.cpp
+++ b/src/plugins/platforms/xcb/qxcbwindow.cpp
@@ -93,6 +93,8 @@ enum {
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
+
Q_DECLARE_TYPEINFO(xcb_rectangle_t, Q_PRIMITIVE_TYPE);
#undef FocusIn
@@ -258,7 +260,7 @@ enum : quint32 {
| XCB_EVENT_MASK_POINTER_MOTION,
transparentForInputEventMask = baseEventMask
- | XCB_EVENT_MASK_VISIBILITY_CHANGE | XCB_EVENT_MASK_RESIZE_REDIRECT
+ | XCB_EVENT_MASK_VISIBILITY_CHANGE
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
| XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_OWNER_GRAB_BUTTON
};
@@ -297,11 +299,6 @@ void QXcbWindow::create()
return;
}
- QPlatformWindow::setGeometry(rect);
-
- if (platformScreen != currentScreen)
- QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen());
-
const QSize minimumSize = windowMinimumSize();
if (rect.width() > 0 || rect.height() > 0) {
rect.setWidth(qBound(1, rect.width(), XCOORD_MAX));
@@ -313,6 +310,11 @@ void QXcbWindow::create()
rect.setHeight(QHighDpi::toNativePixels(int(defaultWindowHeight), platformScreen->QPlatformScreen::screen()));
}
+ QPlatformWindow::setGeometry(rect);
+
+ if (platformScreen != currentScreen)
+ QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen());
+
xcb_window_t xcb_parent_id = platformScreen->root();
if (parent()) {
xcb_parent_id = static_cast<QXcbWindow *>(parent())->xcb_window();
@@ -555,6 +557,7 @@ void QXcbWindow::destroy()
}
m_mapped = false;
+ m_recreationReasons = RecreationNotNeeded;
if (m_pendingSyncRequest)
m_pendingSyncRequest->invalidate();
@@ -689,6 +692,11 @@ void QXcbWindow::setVisible(bool visible)
void QXcbWindow::show()
{
if (window()->isTopLevel()) {
+ if (m_recreationReasons != RecreationNotNeeded) {
+ qCDebug(lcQpaWindow) << "QXcbWindow: need to recreate window" << window() << m_recreationReasons;
+ create();
+ m_recreationReasons = RecreationNotNeeded;
+ }
// update WM_NORMAL_HINTS
propagateSizeHints();
@@ -698,7 +706,7 @@ void QXcbWindow::show()
if (isTransient(window())) {
const QWindow *tp = window()->transientParent();
if (tp && tp->handle())
- transientXcbParent = static_cast<const QXcbWindow *>(tp->handle())->winId();
+ transientXcbParent = tp->handle()->winId();
// Default to client leader if there is no transient parent, else modal dialogs can
// be hidden by their parents.
if (!transientXcbParent)
@@ -904,6 +912,12 @@ void QXcbWindow::setWindowFlags(Qt::WindowFlags flags)
if (type == Qt::Popup)
flags |= Qt::X11BypassWindowManagerHint;
+ Qt::WindowFlags oldflags = window()->flags();
+ if ((oldflags & Qt::WindowStaysOnTopHint) != (flags & Qt::WindowStaysOnTopHint))
+ m_recreationReasons |= WindowStaysOnTopHintChanged;
+ if ((oldflags & Qt::WindowStaysOnBottomHint) != (flags & Qt::WindowStaysOnBottomHint))
+ m_recreationReasons |= WindowStaysOnBottomHintChanged;
+
const quint32 mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
const quint32 values[] = {
// XCB_CW_OVERRIDE_REDIRECT
@@ -1330,6 +1344,12 @@ void QXcbWindow::setWindowIcon(const QIcon &icon)
}
if (!icon_data.isEmpty()) {
+ // Ignore icon exceeding maximum xcb request length
+ if (size_t(icon_data.size()) > xcb_get_maximum_request_length(xcb_connection())) {
+ qWarning("Ignoring window icon: Size %d exceeds maximum xcb request length %u.",
+ icon_data.size(), xcb_get_maximum_request_length(xcb_connection()));
+ return;
+ }
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
@@ -1619,7 +1639,7 @@ void QXcbWindow::setWmWindowType(QXcbWindowFunctions::WmWindowTypes types, Qt::W
break;
}
- if ((flags & Qt::FramelessWindowHint) && !(type & QXcbWindowFunctions::KdeOverride)) {
+ if ((flags & Qt::FramelessWindowHint) && !(types & QXcbWindowFunctions::KdeOverride)) {
// override netwm type - quick and easy for KDE noborder
atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
}
diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h
index 6f5c1f5ed9..8de486c6d2 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.h
+++ b/src/plugins/platforms/xcb/qxcbwindow.h
@@ -74,6 +74,13 @@ public:
Q_DECLARE_FLAGS(NetWmStates, NetWmState)
+ enum RecreationReason {
+ RecreationNotNeeded = 0,
+ WindowStaysOnTopHintChanged = 0x1,
+ WindowStaysOnBottomHintChanged = 0x2
+ };
+ Q_DECLARE_FLAGS(RecreationReasons, RecreationReason)
+
QXcbWindow(QWindow *window);
~QXcbWindow();
@@ -281,6 +288,8 @@ protected:
int m_swapInterval = -1;
qreal m_sizeHintsScaleFactor = 1.0;
+
+ RecreationReasons m_recreationReasons = RecreationNotNeeded;
};
class QXcbForeignWindow : public QXcbWindow
diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro
index a5d05faa9c..587bbcef68 100644
--- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro
+++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro
@@ -70,6 +70,8 @@ DEFINES += QT_BUILD_XCB_PLUGIN
qtConfig(xcb-xlib) {
QMAKE_USE += xcb_xlib
+ SOURCES += qt_xlib_wrapper.c
+ HEADERS += qt_xlib_wrapper.h
}
qtConfig(xcb-sm) {