diff options
Diffstat (limited to 'src/plugins')
47 files changed, 1254 insertions, 362 deletions
diff --git a/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml b/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml index 9c67a38c57..30c326d06f 100644 --- a/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml +++ b/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml @@ -14,6 +14,12 @@ <arg name="w" direction="in" type="i"/> <arg name="h" direction="in" type="i"/> </method> + <method name='SetCursorLocationRelative'> + <arg name="x" direction="in" type="i"/> + <arg name="y" direction="in" type="i"/> + <arg name="w" direction="in" type="i"/> + <arg name="h" direction="in" type="i"/> + </method> <method name="FocusIn"/> <method name="FocusOut"/> <method name="Reset"/> diff --git a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h index 3d11706c06..31a181eec2 100644 --- a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h +++ b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h @@ -114,6 +114,13 @@ public Q_SLOTS: // METHODS return asyncCallWithArgumentList(QLatin1String("SetCursorLocation"), argumentList); } + inline QDBusPendingReply<> SetCursorLocationRelative(int x, int y, int w, int h) + { + QList<QVariant> argumentList; + argumentList << QVariant::fromValue(x) << QVariant::fromValue(y) << QVariant::fromValue(w) << QVariant::fromValue(h); + return asyncCallWithArgumentList(QLatin1String("SetCursorLocationRelative"), argumentList); + } + inline QDBusPendingReply<> SetEngine(const QString &name) { QList<QVariant> argumentList; diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp index b3fb88c907..774051c314 100644 --- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp +++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp @@ -257,10 +257,31 @@ void QIBusPlatformInputContext::cursorRectChanged() QWindow *inputWindow = qApp->focusWindow(); if (!inputWindow) return; - r.moveTopLeft(inputWindow->mapToGlobal(r.topLeft())); + if (!inputWindow->screen()) + return; + + if (QGuiApplication::platformName().startsWith("wayland")) { + auto margins = inputWindow->frameMargins(); + r.translate(margins.left(), margins.top()); + qreal scale = inputWindow->devicePixelRatio(); + QRect newRect = QRect(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale); + if (debug) + qDebug() << "microFocus" << newRect; + d->context->SetCursorLocationRelative(newRect.x(), newRect.y(), + newRect.width(), newRect.height()); + return; + } + + // x11/xcb + auto screenGeometry = inputWindow->screen()->geometry(); + auto point = inputWindow->mapToGlobal(r.topLeft()); + qreal scale = inputWindow->devicePixelRatio(); + auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft(); + QRect newRect(native, r.size() * scale); if (debug) - qDebug() << "microFocus" << r; - d->context->SetCursorLocation(r.x(), r.y(), r.width(), r.height()); + qDebug() << "microFocus" << newRect; + d->context->SetCursorLocation(newRect.x(), newRect.y(), + newRect.width(), newRect.height()); } void QIBusPlatformInputContext::setFocusObject(QObject *object) diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp index 087112eee9..0f58b037e7 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.cpp +++ b/src/plugins/platforms/android/androidcontentfileengine.cpp @@ -43,15 +43,28 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qjnienvironment.h> #include <QtCore/qjniobject.h> - -#include <QDebug> +#include <QtCore/qurl.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qmimedatabase.h> using namespace QNativeInterface; -AndroidContentFileEngine::AndroidContentFileEngine(const QString &f) - : m_file(f) +static QJniObject &contentResolverInstance() +{ + static QJniObject contentResolver; + if (!contentResolver.isValid()) { + contentResolver = QJniObject(QNativeInterface::QAndroidApplication::context()) + .callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;"); + } + + return contentResolver; +} + +AndroidContentFileEngine::AndroidContentFileEngine(const QString &filename) + : m_initialFile(filename), + m_documentFile(DocumentFile::parseFromAnyUri(filename)) { - setFileName(f); + setFileName(filename); } bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode) @@ -62,6 +75,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'); @@ -69,12 +103,10 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode) openModeStr += QLatin1Char('a'); } - m_pfd = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", - "openParcelFdForContentUrl", - "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", - QAndroidApplication::context(), - QJniObject::fromString(fileName(DefaultName)).object(), - QJniObject::fromString(openModeStr).object()); + m_pfd = contentResolverInstance().callObjectMethod("openFileDescriptor", + "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", + m_documentFile->uri().object(), + QJniObject::fromString(openModeStr).object()); if (!m_pfd.isValid()) return false; @@ -82,8 +114,7 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode) const auto fd = m_pfd.callMethod<jint>("getFd", "()I"); if (fd < 0) { - m_pfd.callMethod<void>("close", "()V"); - m_pfd = QJniObject(); + closeNativeFileDescriptor(); return false; } @@ -92,47 +123,127 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode) bool AndroidContentFileEngine::close() { + closeNativeFileDescriptor(); + return QFSFileEngine::close(); +} + +void AndroidContentFileEngine::closeNativeFileDescriptor() +{ if (m_pfd.isValid()) { m_pfd.callMethod<void>("close", "()V"); m_pfd = QJniObject(); } - - return QFSFileEngine::close(); } qint64 AndroidContentFileEngine::size() const { - const jlong size = QJniObject::callStaticMethod<jlong>( - "org/qtproject/qt/android/QtNative", "getSize", - "(Landroid/content/Context;Ljava/lang/String;)J", QAndroidApplication::context(), - QJniObject::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 = QJniObject::callStaticMethod<jboolean>( - "org/qtproject/qt/android/QtNative", "checkIfDir", - "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(), - QJniObject::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 : QJniObject::callStaticMethod<jboolean>( - "org/qtproject/qt/android/QtNative", "checkFileExists", - "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(), - QJniObject::fromString(fileName(DefaultName)).object()); - if (!exists && !isDir) + if (!m_documentFile->exists()) return flags; - if (isDir) { - flags = DirectoryType | commonFlags; + + flags = ExistsFlag; + if (!m_documentFile->canRead()) + return flags; + + flags |= ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm; + + if (m_documentFile->isDirectory()) { + flags |= DirectoryType; } else { - flags = FileType | commonFlags; - const bool writable = QJniObject::callStaticMethod<jboolean>( - "org/qtproject/qt/android/QtNative", "checkIfWritable", - "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(), - QJniObject::fromString(fileName(DefaultName)).object()); - if (writable) + flags |= FileType; + if (m_documentFile->canWrite()) flags |= WriteOwnerPerm|WriteUserPerm|WriteGroupPerm|WriteOtherPerm; } return type & flags; @@ -147,18 +258,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); } @@ -200,37 +311,539 @@ 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 = QJniObject::callStaticMethod<jboolean>( - "org/qtproject/qt/android/QtNative", "checkIfDir", - "(Landroid/content/Context;Ljava/lang/String;)Z", - QAndroidApplication::context(), - QJniObject::fromString(path()).object()); - if (isDir) { - QJniObject objArray = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", - "listContentsFromTreeUri", - "(Landroid/content/Context;Ljava/lang/String;)[Ljava/lang/String;", - QAndroidApplication::context(), - QJniObject::fromString(path()).object()); - if (objArray.isValid()) { - QJniEnvironment env; - const jsize length = env->GetArrayLength(objArray.object<jarray>()); - for (int i = 0; i != length; ++i) { - m_entries << QJniObject(env->GetObjectArrayElement( - objArray.object<jobjectArray>(), 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 QJniObject &object) + : m_object{object} { } + + ~Cursor() + { + if (m_object.isValid()) + 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 + { + 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); + QJniEnvironment env; + const auto blobArray = blob.object<jbyteArray>(); + 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 QJniObject &uri, + const QStringList &projection = {}, + const QString &selection = {}, + const QStringList &selectionArgs = {}, + const QString &sortOrder = {}) + { + 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 : QJniObject::fromString(selection).object(), + selectionArgs.isEmpty() ? nullptr : fromStringList(selectionArgs).object(), + sortOrder.isEmpty() ? nullptr : QJniObject::fromString(sortOrder).object()); + if (!cursor.isValid()) + return {}; + return std::make_unique<Cursor>(cursor); + } + + static QVariant queryColumn(const QJniObject &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 QJniObject fromStringList(const QStringList &list) + { + QJniEnvironment env; + auto array = env->NewObjectArray(list.size(), env->FindClass("java/lang/String"), nullptr); + for (int i = 0; i < list.size(); ++i) + env->SetObjectArrayElement(array, i, QJniObject::fromString(list[i]).object()); + return QJniObject::fromLocalRef(array); + } + + QJniObject m_object; +}; + +// 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 QJniObject &uri) +{ + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "getDocumentId", + "(Landroid/net/Uri;)Ljava/lang/String;", + uri.object()).toString(); +} + +QString treeDocumentId(const QJniObject &uri) +{ + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "getTreeDocumentId", + "(Landroid/net/Uri;)Ljava/lang/String;", + uri.object()).toString(); +} + +QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString &parentDocumentId) +{ + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "buildChildDocumentsUriUsingTree", + "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;", + uri.object(), + QJniObject::fromString(parentDocumentId).object()); + +} + +QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &documentId) +{ + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "buildDocumentUriUsingTree", + "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;", + treeUri.object(), + QJniObject::fromString(documentId).object()); +} + +bool isDocumentUri(const QJniObject &uri) +{ + return QJniObject::callStaticMethod<jboolean>("android/provider/DocumentsContract", + "isDocumentUri", + "(Landroid/content/Context;Landroid/net/Uri;)Z", + QNativeInterface::QAndroidApplication::context(), + uri.object()); +} + +bool isTreeUri(const QJniObject &uri) +{ + return QJniObject::callStaticMethod<jboolean>("android/provider/DocumentsContract", + "isTreeUri", + "(Landroid/net/Uri;)Z", + uri.object()); +} + +QJniObject createDocument(const QJniObject &parentDocumentUri, const QString &mimeType, + const QString &displayName) +{ + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "createDocument", + "(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;", + contentResolverInstance().object(), + parentDocumentUri.object(), + QJniObject::fromString(mimeType).object(), + QJniObject::fromString(displayName).object()); +} + +bool deleteDocument(const QJniObject &documentUri) +{ + const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt(); + if (!(flags & Document::FLAG_SUPPORTS_DELETE)) + return {}; + + return QJniObject::callStaticMethod<jboolean>("android/provider/DocumentsContract", + "deleteDocument", + "(Landroid/content/ContentResolver;Landroid/net/Uri;)Z", + contentResolverInstance().object(), + documentUri.object()); +} + +QJniObject moveDocument(const QJniObject &sourceDocumentUri, + const QJniObject &sourceParentDocumentUri, + const QJniObject &targetParentDocumentUri) +{ + const int flags = Cursor::queryColumn(sourceDocumentUri, Document::COLUMN_FLAGS).toInt(); + if (!(flags & Document::FLAG_SUPPORTS_MOVE)) + return {}; + + return QJniObject::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()); +} + +QJniObject renameDocument(const QJniObject &documentUri, const QString &displayName) +{ + const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt(); + if (!(flags & Document::FLAG_SUPPORTS_RENAME)) + return {}; + + return QJniObject::callStaticObjectMethod("android/provider/DocumentsContract", + "renameDocument", + "(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;", + contentResolverInstance().object(), + documentUri.object(), + QJniObject::fromString(displayName).object()); +} +} // End DocumentsContract namespace + +// Start of DocumentFile + +using namespace DocumentsContract; + +namespace { +class MakeableDocumentFile : public DocumentFile +{ +public: + MakeableDocumentFile(const QJniObject &uri, const DocumentFilePtr &parent = {}) + : DocumentFile(uri, parent) + {} +}; +} + +DocumentFile::DocumentFile(const QJniObject &uri, + const DocumentFilePtr &parent) + : m_uri{uri} + , m_parent{parent} +{} + +QJniObject parseUri(const QString &uri) +{ + return QJniObject::callStaticObjectMethod("android/net/Uri", + "parse", + "(Ljava/lang/String;)Landroid/net/Uri;", + QJniObject::fromString(uri).object()); +} + +DocumentFilePtr DocumentFile::parseFromAnyUri(const QString &fileName) +{ + const QJniObject 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 QJniObject &uri) +{ + return std::make_shared<MakeableDocumentFile>(uri); +} + +DocumentFilePtr DocumentFile::fromTreeUri(const QJniObject &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 QJniObject &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 +{ + const auto context = QJniObject(QNativeInterface::QAndroidApplication::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 +{ + const auto context = QJniObject(QNativeInterface::QAndroidApplication::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) +{ + QJniObject 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 + QJniObject srcParentUri = fromTreeUri(parseUri(parent))->uri(); + const QString destParent = newName.left(lastSeparatorIndex(newName)); + QJniObject 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 37831a2259..f3813b55a1 100644 --- a/src/plugins/platforms/android/androidcontentfileengine.h +++ b/src/plugins/platforms/android/androidcontentfileengine.h @@ -41,7 +41,11 @@ #define ANDROIDCONTENTFILEENGINE_H #include <private/qfsfileengine_p.h> + #include <QtCore/qjniobject.h> +#include <QtCore/qlist.h> + +using DocumentFilePtr = std::shared_ptr<class DocumentFile>; class AndroidContentFileEngine : public QFSFileEngine { @@ -50,14 +54,24 @@ public: 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; - QJniObject m_pfd; + void closeNativeFileDescriptor(); + QString m_initialFile; + QJniObject m_pfd; + DocumentFilePtr m_documentFile; }; class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler @@ -76,9 +90,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 QJniObject &uri); + static DocumentFilePtr fromTreeUri(const QJniObject &treeUri); + + DocumentFilePtr createFile(const QString &mimeType, const QString &displayName); + DocumentFilePtr createDirectory(const QString &displayName); + const QJniObject &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 QJniObject &uri, const std::shared_ptr<DocumentFile> &parent); + +protected: + QJniObject 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 731937dd8f..54579447d3 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" @@ -94,6 +95,14 @@ namespace QtAndroidAccessibility 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); diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp index 90988f6b0d..43b381d4ce 100644 --- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp +++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp @@ -142,6 +142,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. QJniObject files = QJniObject::callStaticObjectMethod(QtAndroid::applicationClass(), "listAssetContent", "(Landroid/content/res/AssetManager;Ljava/lang/String;)[Ljava/lang/String;", @@ -220,7 +222,7 @@ public: return m_currentIterator->currentFileName(); } - virtual QString currentFilePath() const + QString currentFilePath() const override { if (!m_currentIterator) return {}; @@ -392,8 +394,13 @@ public: } else { auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8()); if (assetDir) { - if (AAssetDir_getNextFileName(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); } } diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp index 1ec5867a38..07c5198092 100644 --- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp +++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp @@ -41,8 +41,9 @@ #include "qandroidplatformfiledialoghelper.h" #include <androidjnimain.h> -#include <QtCore/QJniObject> +#include <QCoreApplication> +#include <QtCore/QJniObject> #include <QMimeDatabase> #include <QMimeType> #include <QRegularExpression> @@ -129,6 +130,22 @@ void QAndroidPlatformFileDialogHelper::setInitialFileName(const QString &title) extraTitle.object(), QJniObject::fromString(title).object()); } +void QAndroidPlatformFileDialogHelper::setInitialDirectoryUri(const QString &directory) +{ + if (directory.isEmpty()) + return; + + if (QNativeInterface::QAndroidApplication::sdkVersion() < 26) + return; + + const auto extraInitialUri = QJniObject::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(), + QJniObject::fromString(directory).object()); +} + void QAndroidPlatformFileDialogHelper::setOpenableCategory() { const QJniObject CATEGORY_OPENABLE = QJniObject::getStaticObjectField( @@ -211,11 +228,8 @@ 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) { - // TODO: The initial folder to show at the start should be handled by EXTRA_INITIAL_URI - // Take only the file name. + if (selectedFiles.size() > 0) setInitialFileName(selectedFiles.first().fileName()); - } } else if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) { switch (options()->fileMode()) { case QFileDialogOptions::FileMode::DirectoryOnly: @@ -239,6 +253,8 @@ bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::Win setMimeTypes(); } + setInitialDirectoryUri(m_directory.toString()); + QtAndroidPrivate::registerActivityResultListener(this); m_activity.callMethod<void>("startActivityForResult", "(Landroid/content/Intent;I)V", m_intent.object(), REQUEST_CODE); @@ -252,6 +268,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 f573e72fef..6071821c38 100644 --- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h +++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h @@ -68,8 +68,8 @@ public: void setFilter() override {} QList<QUrl> selectedFiles() const override { return m_selectedFile; } void selectFile(const QUrl &) override {} - QUrl directory() const override { return QUrl(); } - void setDirectory(const QUrl &) override {} + 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; @@ -77,12 +77,14 @@ private: QJniObject getFileDialogIntent(const QString &intentType); void takePersistableUriPermission(const QJniObject &uri); 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; QJniObject m_intent; const QJniObject m_activity; }; diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp index d9f4c6ab88..7b2d981a2d 100644 --- a/src/plugins/platforms/android/qandroidplatformscreen.cpp +++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp @@ -502,7 +502,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/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index e69bafce7b..b85fa3a555 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -67,12 +67,6 @@ public Q_SLOTS: void onAppFocusWindowChanged(QWindow *window); private: - /* - Function to return the default background pixmap. - Needed by QWizard in the Qt widget module. - */ - Q_INVOKABLE QPixmap defaultBackgroundPixmapForQWizard(); - Q_INVOKABLE void clearCurrentThreadCocoaEventDispatcherInterruptFlag(); static void registerDraggedTypes(const QStringList &types); diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 7dc5e9099d..91457d7235 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -114,35 +114,6 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInter return nullptr; } -QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard() -{ - // Note: starting with macOS 10.14, the KeyboardSetupAssistant app bundle no - // longer contains the "Background.png" image. This function then returns a - // null pixmap. - const int ExpectedImageWidth = 242; - const int ExpectedImageHeight = 414; - QCFType<CFArrayRef> urls = LSCopyApplicationURLsForBundleIdentifier( - CFSTR("com.apple.KeyboardSetupAssistant"), nullptr); - if (urls && CFArrayGetCount(urls) > 0) { - CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0); - QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url); - if (bundle) { - url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), nullptr); - if (url) { - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, nullptr); - QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr); - if (image) { - int width = CGImageGetWidth(image); - int height = CGImageGetHeight(image); - if (width == ExpectedImageWidth && height == ExpectedImageHeight) - return QPixmap::fromImage(qt_mac_toQImage(image)); - } - } - } - } - return QPixmap(); -} - void QCocoaNativeInterface::clearCurrentThreadCocoaEventDispatcherInterruptFlag() { QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index d3f1a211de..fa7cff9be9 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -117,6 +117,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); @end @interface QNSView (ComplexText) <NSTextInputClient> +@property (readonly) QObject* focusObject; @end @implementation QNSView { diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm index 24d289f6d3..e84aa88bf6 100644 --- a/src/plugins/platforms/cocoa/qnsview_complextext.mm +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -43,6 +43,17 @@ // ------------- Text insertion ------------- +- (QObject*)focusObject +{ + // The text input system may still hold a reference to our QNSView, + // even after QCocoaWindow has been destructed, delivering text input + // events to us, so we need to guard for this situation explicitly. + if (!m_platformWindow) + return nullptr; + + return m_platformWindow->window()->focusObject(); +} + /* Inserts the given text, potentially replacing existing text. @@ -88,8 +99,7 @@ } } - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (queryInputMethod(focusObject)) { + if (queryInputMethod(self.focusObject)) { QInputMethodEvent inputMethodEvent; const bool isAttributedString = [text isKindOfClass:NSAttributedString.class]; @@ -111,7 +121,7 @@ inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength); } - QCoreApplication::sendEvent(focusObject, &inputMethodEvent); + QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent); } m_composingText.clear(); @@ -122,6 +132,9 @@ { Q_UNUSED(sender); + if (!m_platformWindow) + return; + // Depending on the input method, pressing enter may // result in simply dismissing the input method editor, // without confirming the composition. In other cases @@ -278,7 +291,7 @@ // Update the composition, now that we've computed the replacement range m_composingText = preeditString; - if (QObject *focusObject = m_platformWindow->window()->focusObject()) { + if (QObject *focusObject = self.focusObject) { m_composingFocusObject = focusObject; if (queryInputMethod(focusObject)) { QInputMethodEvent event(preeditString, preeditAttributes); @@ -320,8 +333,7 @@ */ - (NSRange)markedRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, Qt::ImAbsolutePosition)) { + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) { int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt(); // The cursor position as reflected by Qt::ImAbsolutePosition is not @@ -356,7 +368,7 @@ << "for focus object" << m_composingFocusObject; if (!m_composingText.isEmpty()) { - QObject *focusObject = m_platformWindow->window()->focusObject(); + QObject *focusObject = self.focusObject; if (queryInputMethod(focusObject)) { QInputMethodEvent e; e.setCommitString(m_composingText); @@ -417,8 +429,7 @@ */ - (NSRange)selectedRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) { // Unfortunately the Qt::InputMethodQuery values are all relative @@ -465,8 +476,7 @@ */ - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) { const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt(); const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString(); @@ -502,8 +512,8 @@ Q_UNUSED(range); Q_UNUSED(actualRange); - QWindow *window = m_platformWindow->window(); - if (queryInputMethod(window->focusObject())) { + QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr; + if (window && queryInputMethod(window->focusObject())) { QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect(); cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft())); return QCocoaScreen::mapToNative(cursorRect); diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp index ee7749d57b..9c132dd1e6 100644 --- a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp @@ -44,6 +44,7 @@ #include <qpa/qwindowsysteminterface.h> #include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFunctions> #include <QtCore/QFile> #include <QtCore/QJsonDocument> #include <QtCore/QJsonArray> @@ -151,16 +152,18 @@ void QEglFSCursor::createShaderPrograms() void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image) { + Q_ASSERT(QOpenGLContext::currentContext()); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); if (!*texture) - glGenTextures(1, texture); - glBindTexture(GL_TEXTURE_2D, *texture); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */, - GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); + f->glGenTextures(1, texture); + f->glBindTexture(GL_TEXTURE_2D, *texture); + f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + f->glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */, + GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); } void QEglFSCursor::initCursorAtlas() @@ -372,8 +375,8 @@ void QEglFSCursor::paintOnScreen() // to deal with the changes we make. struct StateSaver { - StateSaver() { - f = QOpenGLContext::currentContext()->functions(); + StateSaver(QOpenGLFunctions* func) { + f = func; vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(QOpenGLContext::currentContext()); static bool windowsChecked = false; @@ -464,11 +467,9 @@ struct StateSaver void QEglFSCursor::draw(const QRectF &r) { - StateSaver stateSaver; - - // one time initialization - if (!QOpenGLFunctions::d_ptr) - initializeOpenGLFunctions(); + Q_ASSERT(QOpenGLContext::currentContext()); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + StateSaver stateSaver(f); QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData; if (!gfx.program) { @@ -517,13 +518,13 @@ void QEglFSCursor::draw(const QRectF &r) s2, t1 }; - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, cursorTexture); + f->glActiveTexture(GL_TEXTURE0); + f->glBindTexture(GL_TEXTURE_2D, cursorTexture); if (stateSaver.vaoHelper->isValid()) stateSaver.vaoHelper->glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + f->glBindBuffer(GL_ARRAY_BUFFER, 0); gfx.program->enableAttributeArray(0); gfx.program->enableAttributeArray(1); @@ -533,13 +534,13 @@ void QEglFSCursor::draw(const QRectF &r) gfx.program->setUniformValue(gfx.textureEntry, 0); gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix); - glDisable(GL_CULL_FACE); - glFrontFace(GL_CCW); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top + f->glDisable(GL_CULL_FACE); + f->glFrontFace(GL_CCW); + f->glEnable(GL_BLEND); + f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + f->glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); gfx.program->disableAttributeArray(0); gfx.program->disableAttributeArray(1); diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor_p.h b/src/plugins/platforms/eglfs/api/qeglfscursor_p.h index ea1212ace8..ba35899348 100644 --- a/src/plugins/platforms/eglfs/api/qeglfscursor_p.h +++ b/src/plugins/platforms/eglfs/api/qeglfscursor_p.h @@ -56,7 +56,6 @@ #include <qpa/qplatformscreen.h> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtGui/QMatrix4x4> -#include <QtGui/QOpenGLFunctions> #include <QtGui/private/qinputdevicemanager_p.h> #include <QtCore/qlist.h> @@ -94,7 +93,6 @@ struct QEglFSCursorData { }; class Q_EGLFS_EXPORT QEglFSCursor : public QPlatformCursor - , protected QOpenGLFunctions { Q_OBJECT public: diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp index ba9b35f36f..07465641ee 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp @@ -387,7 +387,17 @@ void QEglFSKmsGbmScreen::flip() return; } + auto gbmRelease = qScopeGuard([this]{ + m_flipPending = false; + gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next); + m_gbm_bo_next = nullptr; + }); + FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next); + if (!fb) { + qWarning("FrameBuffer not available. Cannot flip"); + return; + } ensureModeSet(fb->fb); const QKmsOutput &thisOutput(output()); @@ -419,9 +429,6 @@ void QEglFSKmsGbmScreen::flip() this); if (ret) { qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name())); - m_flipPending = false; - gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next); - m_gbm_bo_next = nullptr; return; } } @@ -464,6 +471,8 @@ void QEglFSKmsGbmScreen::flip() #if QT_CONFIG(drm_atomic) device()->threadLocalAtomicCommit(this); #endif + + gbmRelease.dismiss(); } void QEglFSKmsGbmScreen::flipFinished() diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index 915fc4791f..1bafa33902 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -108,8 +108,8 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen) + (void)screenConnected:(NSNotification*)notification { - Q_ASSERT_X(QIOSIntegration::instance(), Q_FUNC_INFO, - "Screen connected before QIOSIntegration creation"); + if (!QIOSIntegration::instance()) + return; // Will be added when QIOSIntegration is created QWindowSystemInterface::handleScreenAdded(new QIOSScreen([notification object])); } diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm index 9b76a8328a..bf5e1f1c3a 100644 --- a/src/plugins/platforms/ios/quiview_accessibility.mm +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -59,9 +59,11 @@ if (!iface || iface->state().invisible) 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 diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index dd63d4f3ac..83fecff43c 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -83,6 +83,7 @@ #include <QtCore/quuid.h> #include <QtCore/private/qsystemlibrary_p.h> #include <QtCore/private/qwinregistry_p.h> +#include <QtCore/private/qfactorycacheregistration_p.h> #include <QtGui/private/qwindowsguieventdispatcher_p.h> @@ -327,8 +328,12 @@ QWindowsContext::~QWindowsContext() DestroyWindow(d->m_powerDummyWindow); unregisterWindowClasses(); - if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) + if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) { +#ifdef QT_USE_FACTORY_CACHE_REGISTRATION + detail::QWinRTFactoryCacheRegistration::clearAllCaches(); +#endif OleUninitialize(); + } d->m_screenManager.clearScreens(); // Order: Potentially calls back to the windows. if (d->m_displayContext) @@ -1338,12 +1343,16 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::ExposeEvent: return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result); case QtWindows::NonClientMouseEvent: - if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) + if (!platformWindow->frameStrutEventsEnabled()) + break; + if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); else return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result); case QtWindows::NonClientPointerEvent: - if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled()) + if (!platformWindow->frameStrutEventsEnabled()) + break; + if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer)) return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result); break; case QtWindows::EnterSizeMoveEvent: @@ -1587,9 +1596,13 @@ void QWindowsContext::handleExitSizeMove(QWindow *window) ? QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease; for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) { if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) { - QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, - currentButtons, button, type, - keyboardModifiers); + if (type == QEvent::NonClientAreaMouseButtonRelease) { + QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos, + currentButtons, button, type, keyboardModifiers); + } else { + QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, + currentButtons, button, type, keyboardModifiers); + } } } if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 1457e5048d..cd75b7c96f 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -282,7 +282,9 @@ private: void QWindowsDialogThread::run() { qCDebug(lcQpaDialogs) << '>' << __FUNCTION__; + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); m_dialog->exec(m_owner); + CoUninitialize(); qCDebug(lcQpaDialogs) << '<' << __FUNCTION__; } diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp index 24d4cf3043..b47ded766f 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.cpp +++ b/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -684,7 +684,8 @@ IDropTargetHelper* QWindowsDrag::dropHelper() { // We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop() static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect) { - HWND hwnd = ::GetFocus(); + QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse(); + const HWND hwnd = underMouse ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus(); bool starting = false; for (;;) { diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp index 9a8506d971..9c90b5497a 100644 --- a/src/plugins/platforms/windows/qwindowsglcontext.cpp +++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp @@ -580,11 +580,14 @@ static int choosePixelFormat(HDC hdc, iAttributes[i++] = WGL_NUMBER_OVERLAYS_ARB; iAttributes[i++] = 1; } + // must be the one before the last one const int samples = format.samples(); const bool sampleBuffersRequested = samples > 1 && testFlag(staticContext.extensions, QOpenGLStaticContext::SampleBuffers); + int sampleBuffersKeyPosition = 0; int samplesValuePosition = 0; if (sampleBuffersRequested) { + sampleBuffersKeyPosition = i; iAttributes[i++] = WGL_SAMPLE_BUFFERS_ARB; iAttributes[i++] = TRUE; iAttributes[i++] = WGL_SAMPLES_ARB; @@ -596,9 +599,9 @@ static int choosePixelFormat(HDC hdc, } // must be the last bool srgbRequested = format.colorSpace() == QColorSpace::SRgb; - int srgbValuePosition = 0; + int srgbCapableKeyPosition = 0; if (srgbRequested) { - srgbValuePosition = i; + srgbCapableKeyPosition = i; iAttributes[i++] = WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT; iAttributes[i++] = TRUE; } @@ -612,8 +615,9 @@ static int choosePixelFormat(HDC hdc, && numFormats >= 1; if (valid || (!sampleBuffersRequested && !srgbRequested)) break; + // NB reductions must be done in reverse order (disable the last first, then move on to the one before that, etc.) if (srgbRequested) { - iAttributes[srgbValuePosition] = 0; + iAttributes[srgbCapableKeyPosition] = 0; srgbRequested = false; } else if (sampleBuffersRequested) { if (iAttributes[samplesValuePosition] > 1) { @@ -621,11 +625,8 @@ static int choosePixelFormat(HDC hdc, } else if (iAttributes[samplesValuePosition] == 1) { // Fallback in case it is unable to initialize with any // samples to avoid falling back to the GDI path - // NB: The sample attributes needs to be at the end for this - // to work correctly - iAttributes[samplesValuePosition - 1] = FALSE; + iAttributes[sampleBuffersKeyPosition] = 0; iAttributes[samplesValuePosition] = 0; - iAttributes[samplesValuePosition + 1] = 0; } else { break; } diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmime.cpp index 51efb095fc..768e243e12 100644 --- a/src/plugins/platforms/windows/qwindowsmime.cpp +++ b/src/plugins/platforms/windows/qwindowsmime.cpp @@ -480,7 +480,7 @@ public: bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const { int cf = getCf(formatetc); - return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText(); + return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText(); } /* @@ -581,7 +581,8 @@ QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QList<FORMATETC> formatics; if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) { formatics += setCf(CF_UNICODETEXT); - formatics += setCf(CF_TEXT); + if (GetACP() != CP_UTF8) + formatics += setCf(CF_TEXT); } return formatics; } diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp index 0a96d5a0f5..14bd152621 100644 --- a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp +++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp @@ -210,6 +210,7 @@ void QWindowsSystemTrayIcon::updateIcon(const QIcon &icon) qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this; if (icon.cacheKey() == m_icon.cacheKey()) return; + m_icon = icon; const HICON hIconToDestroy = createIcon(icon); if (ensureInstalled()) sendTrayMessage(NIM_MODIFY); @@ -448,8 +449,15 @@ bool QWindowsSystemTrayIcon::winEvent(const MSG &message, long *result) QWindowsPopupMenu::notifyTriggered(LOWORD(message.wParam)); break; default: - if (message.message == MYWM_TASKBARCREATED) // self-registered message id (tray crashed) + if (message.message == MYWM_TASKBARCREATED) { + // self-registered message id to handle that + // - screen resolution/DPR changed + const QIcon oldIcon = m_icon; + m_icon = QIcon(); // updateIcon is a no-op if the icon doesn't change + updateIcon(oldIcon); + // - or tray crashed sendTrayMessage(NIM_ADD); + } break; } return false; diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index bb639a59bc..c7951b3026 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -593,20 +593,22 @@ void QWindowsTheme::refreshFonts() clearFonts(); if (!QGuiApplication::desktopSettingsAware()) return; + + const int dpi = 96; NONCLIENTMETRICS ncm; - auto &screenManager = QWindowsContext::instance()->screenManager(); - QWindowsContext::nonClientMetricsForScreen(&ncm, screenManager.screens().value(0)); + QWindowsContext::nonClientMetrics(&ncm, dpi); qCDebug(lcQpaWindows) << __FUNCTION__ << ncm; - const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont); - const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont); - const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont); - const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont); + + const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi); + const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi); + const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi); + const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi); QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize()); fixedFont.setStyleHint(QFont::TypeWriter); LOGFONT lfIconTitleFont; - SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0); - const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont); + SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi); + const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi); m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont()); m_fonts[MenuFont] = new QFont(menuFont); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 3f6a63bb9e..1618e20b43 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; @@ -276,6 +279,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/xcb/gl_integrations/xcb_glx/qglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp index 81ece71f02..7ffd63a65d 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp @@ -431,14 +431,19 @@ QGLXContext::QGLXContext(Display *display, GLXContext context, void *visualInfo, int numConfigs = 0; static const int attribs[] = { GLX_FBCONFIG_ID, configId, None }; configs = glXChooseFBConfig(m_display, screenNumber, attribs, &numConfigs); - if (!configs || numConfigs < 1) { + if (!configs) { + qWarning("QGLXContext: Failed to find config(invalid arguments for glXChooseFBConfig)"); + return; + } else if (numConfigs < 1) { qWarning("QGLXContext: Failed to find config"); + XFree(configs); return; } if (configs && numConfigs > 1) // this is suspicious so warn but let it continue qWarning("QGLXContext: Multiple configs for FBConfig ID %d", configId); m_config = configs[0]; + XFree(configs); } Q_ASSERT(vinfo || m_config); diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.c b/src/plugins/platforms/xcb/qt_xlib_wrapper.c index 3ff7120291..d45f468e8c 100644 --- a/src/plugins/platforms/xcb/qt_xlib_wrapper.c +++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.c @@ -5,8 +5,7 @@ ** ** This file is part of the plugins of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:COMM$ -** +** $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 @@ -15,25 +14,26 @@ ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** -** $QT_END_LICENSE$ -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** +** 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$ ** ******************************************************************************/ diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.h b/src/plugins/platforms/xcb/qt_xlib_wrapper.h index 3f77068348..a656c2e50a 100644 --- a/src/plugins/platforms/xcb/qt_xlib_wrapper.h +++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.h @@ -5,8 +5,7 @@ ** ** This file is part of the plugins of the Qt Toolkit. ** -** $QT_BEGIN_LICENSE:COMM$ -** +** $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 @@ -15,25 +14,26 @@ ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** -** $QT_END_LICENSE$ -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** -** +** 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$ ** ******************************************************************************/ diff --git a/src/plugins/platforms/xcb/qxcbatom.cpp b/src/plugins/platforms/xcb/qxcbatom.cpp index 1785513ac8..d485bcfd8a 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 217c048ca4..ca6f8e7089 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/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index f4b829350a..22f7f5cde8 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -837,8 +837,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(); @@ -870,8 +870,6 @@ xcb_timestamp_t QXcbConnection::getTimestamp() xcb_timestamp_t timestamp = pn->time; free(event); - xcb_delete_property(xcb_connection(), window, dummyAtom); - return timestamp; } diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index 51a905ba75..10f260c292 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -287,11 +287,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)); @@ -303,6 +298,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(); @@ -1329,9 +1329,10 @@ void QXcbWindow::setWindowIcon(const QIcon &icon) if (!icon_data.isEmpty()) { // Ignore icon exceeding maximum xcb request length - if (icon_data.size() > xcb_get_maximum_request_length(xcb_connection())) { - qWarning("Ignoring window icon: Size %llu exceeds maximum xcb request length %u.", - icon_data.size(), xcb_get_maximum_request_length(xcb_connection())); + if (quint64(icon_data.size()) > quint64(xcb_get_maximum_request_length(xcb_connection()))) { + qWarning() << "Ignoring window icon" << icon_data.size() + << "exceeds maximum xcb request length" + << xcb_get_maximum_request_length(xcb_connection()); return; } xcb_change_property(xcb_connection(), diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp index 6533288b2f..2385407030 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp @@ -122,7 +122,7 @@ QGtk3Theme::QGtk3Theme() if (qEnvironmentVariableIsEmpty("XCURSOR_SIZE")) { const int cursorSize = gtkSetting<gint>("gtk-cursor-theme-size"); if (cursorSize > 0) - qputenv("XCURSOR_SIZE", QString::number(cursorSize).toUtf8()); + qputenv("XCURSOR_SIZE", QByteArray::number(cursorSize)); } if (qEnvironmentVariableIsEmpty("XCURSOR_THEME")) { const QString cursorTheme = gtkSetting("gtk-cursor-theme-name"); diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf index f6f7fed367..55ff681c65 100644 --- a/src/plugins/sqldrivers/.cmake.conf +++ b/src/plugins/sqldrivers/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.2.7") +set(QT_REPO_MODULE_VERSION "6.2.8") diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp index a2bf992b1a..30df01c85e 100644 --- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp +++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -64,6 +64,11 @@ Q_DECLARE_METATYPE(MYSQL_RES*) Q_DECLARE_METATYPE(MYSQL*) Q_DECLARE_METATYPE(MYSQL_STMT*) +// MYSQL_TYPE_JSON was introduced with MySQL 5.7.9 +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID < 50709 +#define MYSQL_TYPE_JSON 245 +#endif + // MySQL above version 8 removed my_bool typedef while MariaDB kept it, // by redefining it we can regain source compatibility. using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr)); @@ -262,6 +267,7 @@ static QMetaType qDecodeMYSQLType(int mysqltype, uint flags) case FIELD_TYPE_MEDIUM_BLOB : case FIELD_TYPE_LONG_BLOB : case FIELD_TYPE_GEOMETRY : + case MYSQL_TYPE_JSON : type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString; break; default: @@ -300,7 +306,8 @@ static bool qIsBlob(int t) return t == MYSQL_TYPE_TINY_BLOB || t == MYSQL_TYPE_BLOB || t == MYSQL_TYPE_MEDIUM_BLOB - || t == MYSQL_TYPE_LONG_BLOB; + || t == MYSQL_TYPE_LONG_BLOB + || t == MYSQL_TYPE_JSON; } static bool qIsInteger(int t) diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp index 359fd33d2f..3d229f03a1 100644 --- a/src/plugins/sqldrivers/oci/qsql_oci.cpp +++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp @@ -163,6 +163,15 @@ public: ~QOCIDateTime(); OCIDateTime *dateTime; static QDateTime fromOCIDateTime(OCIEnv *env, OCIError *err, OCIDateTime *dt); + static QString toOffsetString(const QDateTime &dt) + { + const auto offset = dt.offsetFromUtc(); + const auto offsetAbs = qAbs(offset) / 60; + return QString::asprintf("%c%02d:%02d", + offset >= 0 ? '+' : '-', + offsetAbs / 60, + offsetAbs % 60); + } }; QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt) @@ -172,8 +181,8 @@ QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt) if (dt.isValid()) { const QDate date = dt.date(); const QTime time = dt.time(); - // Zone in +hh:mm format (stripping UTC prefix from OffsetName) - QString timeZone = dt.timeZone().displayName(dt, QTimeZone::OffsetName).mid(3); + // Zone in +hh:mm format + const QString timeZone = toOffsetString(dt); const OraText *tz = reinterpret_cast<const OraText *>(timeZone.utf16()); OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec() * 1000000, diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index a789ebf5a1..be764da13e 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -92,24 +92,39 @@ inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, qsize return result; } +template <size_t SizeOfChar = sizeof(SQLTCHAR)> +void toSQLTCHARImpl(QVarLengthArray<SQLTCHAR> &result, const QString &input); // primary template undefined + +template <typename Container> +void do_append(QVarLengthArray<SQLTCHAR> &result, const Container &c) +{ + result.append(reinterpret_cast<const SQLTCHAR *>(c.data()), c.size()); +} + +template <> +void toSQLTCHARImpl<1>(QVarLengthArray<SQLTCHAR> &result, const QString &input) +{ + const auto u8 = input.toUtf8(); + do_append(result, u8); +} + +template <> +void toSQLTCHARImpl<2>(QVarLengthArray<SQLTCHAR> &result, const QString &input) +{ + do_append(result, input); +} + +template <> +void toSQLTCHARImpl<4>(QVarLengthArray<SQLTCHAR> &result, const QString &input) +{ + const auto u32 = input.toUcs4(); + do_append(result, u32); +} + inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input) { QVarLengthArray<SQLTCHAR> result; - result.resize(input.size()); - switch(sizeof(SQLTCHAR)) { - case 1: - memcpy(result.data(), input.toUtf8().data(), input.size()); - break; - case 2: - memcpy(result.data(), input.unicode(), input.size() * 2); - break; - case 4: - memcpy(result.data(), input.toUcs4().data(), input.size() * 4); - break; - default: - qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR))); - } - result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't. + toSQLTCHARImpl(result, input); return result; } @@ -730,10 +745,15 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess f.setAutoValue(isAutoValue(hStmt, i)); QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE); SQLSMALLINT tableNameLen; - r = SQLColAttribute(hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName.data(), - TABLENAMESIZE, &tableNameLen, 0); + r = SQLColAttribute(hStmt, + i + 1, + SQL_DESC_BASE_TABLE_NAME, + tableName.data(), + SQLSMALLINT(tableName.size() * sizeof(SQLTCHAR)), // SQLColAttribute needs/returns size in bytes + &tableNameLen, + 0); if (r == SQL_SUCCESS) - f.setTableName(fromSQLTCHAR(tableName, tableNameLen)); + f.setTableName(fromSQLTCHAR(tableName, tableNameLen / sizeof(SQLTCHAR))); return f; } @@ -763,6 +783,14 @@ QChar QODBCDriverPrivate::quoteChar() return quote; } +static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, const QString &val) +{ + auto encoded = toSQLTCHAR(val); + return SQLSetConnectAttr(handle, attr, + encoded.data(), + SQLINTEGER(encoded.size() * sizeof(SQLTCHAR))); // size in bytes +} + bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) { @@ -798,10 +826,7 @@ bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) v = val.toUInt(); r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0); } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) { - val.utf16(); // 0 terminate - r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, - toSQLTCHAR(val).data(), - SQLINTEGER(val.length() * sizeof(SQLTCHAR))); + r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val); } else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) { if (val.toUpper() == QLatin1String("SQL_TRUE")) { v = SQL_TRUE; @@ -816,10 +841,7 @@ bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts) v = val.toUInt(); r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0); } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) { - val.utf16(); // 0 terminate - r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, - toSQLTCHAR(val).data(), - SQLINTEGER(val.length() * sizeof(SQLTCHAR))); + r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val); } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) { if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) { v = SQL_OPT_TRACE_OFF; @@ -1022,9 +1044,12 @@ bool QODBCResult::reset (const QString& query) return false; } - r = SQLExecDirect(d->hStmt, - toSQLTCHAR(query).data(), - (SQLINTEGER) query.length()); + { + auto encoded = toSQLTCHAR(query); + r = SQLExecDirect(d->hStmt, + encoded.data(), + SQLINTEGER(encoded.size())); + } if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", "Unable to execute statement"), QSqlError::StatementError, d)); @@ -1373,9 +1398,12 @@ bool QODBCResult::prepare(const QString& query) return false; } - r = SQLPrepare(d->hStmt, - toSQLTCHAR(query).data(), - (SQLINTEGER) query.length()); + { + auto encoded = toSQLTCHAR(query); + r = SQLPrepare(d->hStmt, + encoded.data(), + SQLINTEGER(encoded.size())); + } if (r != SQL_SUCCESS) { setLastError(qMakeError(QCoreApplication::translate("QODBCResult", @@ -1403,7 +1431,7 @@ bool QODBCResult::exec() SQLCloseCursor(d->hStmt); QVariantList &values = boundValues(); - QByteArrayList tmpStorage(values.count(), QByteArray()); // holds temporary buffers + QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter() QVarLengthArray<SQLLEN, 32> indicators(values.count()); memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN)); @@ -1618,36 +1646,36 @@ bool QODBCResult::exec() case QMetaType::QString: if (d->unicode) { QByteArray &ba = tmpStorage[i]; - QString str = val.toString(); + { + const auto encoded = toSQLTCHAR(val.toString()); + ba = QByteArray(reinterpret_cast<const char *>(encoded.data()), + encoded.size() * sizeof(SQLTCHAR)); + } + if (*ind != SQL_NULL_DATA) - *ind = str.length() * sizeof(SQLTCHAR); - const qsizetype strSize = str.length() * sizeof(SQLTCHAR); + *ind = ba.size(); if (bindValueType(i) & QSql::Out) { - const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str)); - ba = QByteArray((const char *)a.constData(), int(a.size() * sizeof(SQLTCHAR))); r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], SQL_C_TCHAR, - strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, 0, // god knows... don't change this! 0, - ba.data(), + const_cast<char *>(ba.constData()), // don't detach ba.size(), ind); break; } - ba = QByteArray(reinterpret_cast<const char *>(toSQLTCHAR(str).constData()), - int(strSize)); r = SQLBindParameter(d->hStmt, i + 1, qParamType[bindValueType(i) & QSql::InOut], SQL_C_TCHAR, - strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, - strSize, + ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR, + ba.size(), 0, - const_cast<char *>(ba.constData()), + const_cast<char *>(ba.constData()), // don't detach ba.size(), ind); break; @@ -1758,10 +1786,11 @@ bool QODBCResult::exec() case QMetaType::QString: if (d->unicode) { if (bindValueType(i) & QSql::Out) { - const QByteArray &first = tmpStorage.at(i); - QVarLengthArray<SQLTCHAR> array; - array.append((const SQLTCHAR *)first.constData(), first.size()); - values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR)); + const QByteArray &bytes = tmpStorage.at(i); + const auto strSize = bytes.size() / sizeof(SQLTCHAR); + QVarLengthArray<SQLTCHAR> string(strSize); + memcpy(string.data(), bytes.data(), strSize * sizeof(SQLTCHAR)); + values[i] = fromSQLTCHAR(string); } break; } @@ -2008,14 +2037,16 @@ bool QODBCDriver::open(const QString & db, SQLSMALLINT cb; QVarLengthArray<SQLTCHAR> connOut(1024); memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR)); - r = SQLDriverConnect(d->hDbc, - NULL, - toSQLTCHAR(connQStr).data(), - (SQLSMALLINT)connQStr.length(), - connOut.data(), - 1024, - &cb, - /*SQL_DRIVER_NOPROMPT*/0); + { + auto encoded = toSQLTCHAR(connQStr); + r = SQLDriverConnect(d->hDbc, + nullptr, + encoded.data(), SQLSMALLINT(encoded.size()), + connOut.data(), + 1024, + &cb, + /*SQL_DRIVER_NOPROMPT*/0); + } if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) { setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d)); @@ -2125,7 +2156,10 @@ void QODBCDriverPrivate::checkUnicode() hDbc, &hStmt); - r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS); + { + auto encoded = toSQLTCHAR(QLatin1String("select 'test'")); + r = SQLExecDirect(hStmt, encoded.data(), SQLINTEGER(encoded.size())); + } if (r == SQL_SUCCESS) { r = SQLFetch(hStmt); if (r == SQL_SUCCESS) { @@ -2394,17 +2428,15 @@ QStringList QODBCDriver::tables(QSql::TableType type) const if (tableType.isEmpty()) return tl; - QString joinedTableTypeString = tableType.join(QLatin1Char(',')); + { + auto joinedTableTypeString = toSQLTCHAR(tableType.join(u',')); - r = SQLTables(hStmt, - NULL, - 0, - NULL, - 0, - NULL, - 0, - toSQLTCHAR(joinedTableTypeString).data(), - joinedTableTypeString.length() /* characters, not bytes */); + r = SQLTables(hStmt, + nullptr, 0, + nullptr, 0, + nullptr, 0, + joinedTableTypeString.data(), joinedTableTypeString.size()); + } if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d); @@ -2477,28 +2509,30 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, SQL_IS_UINTEGER); - r = SQLPrimaryKeys(hStmt, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length() /* in characters, not in bytes */); + { + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLPrimaryKeys(hStmt, + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size()); + } // if the SQLPrimaryKeys() call does not succeed (e.g the driver // does not support it) - try an alternative method to get hold of // the primary index (e.g MS Access and FoxPro) if (r != SQL_SUCCESS) { - r = SQLSpecialColumns(hStmt, - SQL_BEST_ROWID, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length(), - SQL_SCOPE_CURROW, - SQL_NULLABLE); + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLSpecialColumns(hStmt, + SQL_BEST_ROWID, + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size(), + SQL_SCOPE_CURROW, + SQL_NULLABLE); if (r != SQL_SUCCESS) { qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d); @@ -2579,15 +2613,17 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, SQL_IS_UINTEGER); - r = SQLColumns(hStmt, - catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(), - catalog.length(), - schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(), - schema.length(), - toSQLTCHAR(table).data(), - table.length(), - NULL, - 0); + { + auto c = toSQLTCHAR(catalog); + auto s = toSQLTCHAR(schema); + auto t = toSQLTCHAR(table); + r = SQLColumns(hStmt, + catalog.isEmpty() ? nullptr : c.data(), c.size(), + schema.isEmpty() ? nullptr : s.data(), s.size(), + t.data(), t.size(), + nullptr, + 0); + } if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d); diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index ae0029d415..168ff49d64 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -2589,10 +2589,13 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW case PM_ToolBarFrameWidth: ret = 1; break; - case PM_ScrollView_ScrollBarOverlap: - ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay ? - pixelMetric(PM_ScrollBarExtent, opt, widget) : 0; + case PM_ScrollView_ScrollBarOverlap: { + const QStyle *realStyle = widget ? widget->style() : proxy(); + ret = realStyle->styleHint(SH_ScrollBar_Transient, opt, widget) + ? realStyle->pixelMetric(PM_ScrollBarExtent, opt, widget) + : 0; break; + } default: ret = QCommonStyle::pixelMetric(metric, opt, widget); break; @@ -3320,7 +3323,7 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai NSButtonCell *triangleCell = static_cast<NSButtonCell *>(d->cocoaCell(cw)); [triangleCell setState:(opt->state & State_Open) ? NSControlStateValueOn : NSControlStateValueOff]; bool viewHasFocus = (w && w->hasFocus()) || (opt->state & State_HasFocus); - [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleDark : NSBackgroundStyleLight]; + [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleEmphasized : NSBackgroundStyleNormal]; d->setupNSGraphicsContext(cg, NO); @@ -5256,7 +5259,8 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex const auto cocoaSize = d->effectiveAquaSizeConstrain(opt, widget); const CGFloat maxExpandScale = expandedKnobWidths[cocoaSize] / knobWidths[cocoaSize]; - const bool isTransient = proxy()->styleHint(SH_ScrollBar_Transient, opt, widget); + const QStyle *realStyle = widget ? widget->style() : proxy(); + const bool isTransient = realStyle->styleHint(SH_ScrollBar_Transient, opt, widget); if (!isTransient) d->stopAnimation(opt->styleObject); bool wasActive = false; diff --git a/src/plugins/tls/openssl/qtls_openssl.cpp b/src/plugins/tls/openssl/qtls_openssl.cpp index 9d9e23cc42..b3870d39ac 100644 --- a/src/plugins/tls/openssl/qtls_openssl.cpp +++ b/src/plugins/tls/openssl/qtls_openssl.cpp @@ -524,7 +524,7 @@ void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj) handshakeInterrupted = false; fetchAuthorityInformation = false; - caToFetch = QSslCertificate{}; + caToFetch.reset(); } void TlsCryptographOpenSSL::checkSettingSslContext(QSharedPointer<QSslContext> tlsContext) @@ -783,7 +783,7 @@ void TlsCryptographOpenSSL::enableHandshakeContinuation() void TlsCryptographOpenSSL::cancelCAFetch() { fetchAuthorityInformation = false; - caToFetch = QSslCertificate{}; + caToFetch.reset(); } void TlsCryptographOpenSSL::continueHandshake() @@ -1834,7 +1834,7 @@ void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate t Q_ASSERT(q); //Done, fetched already: - caToFetch = QSslCertificate{}; + caToFetch.reset(); if (fetchAuthorityInformation) { if (!q->sslConfiguration().caCertificates().contains(trustedRoot)) diff --git a/src/plugins/tls/openssl/qtls_openssl_p.h b/src/plugins/tls/openssl/qtls_openssl_p.h index 8728f27f78..9a1dbf0c66 100644 --- a/src/plugins/tls/openssl/qtls_openssl_p.h +++ b/src/plugins/tls/openssl/qtls_openssl_p.h @@ -156,7 +156,7 @@ private: bool handshakeInterrupted = false; bool fetchAuthorityInformation = false; - QSslCertificate caToFetch; + std::optional<QSslCertificate> caToFetch; bool inSetAndEmitError = false; bool pendingFatalAlert = false; diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp index 04a2725fe8..6c9f4319c4 100644 --- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp @@ -64,7 +64,13 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl"); +#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) +constexpr auto DefaultWarningLevel = QtCriticalMsg; +#else +constexpr auto DefaultWarningLevel = QtDebugMsg; +#endif + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl", DefaultWarningLevel); Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) diff --git a/src/plugins/tls/securetransport/qtls_st.cpp b/src/plugins/tls/securetransport/qtls_st.cpp index 97731e79e3..60b30e6f8c 100644 --- a/src/plugins/tls/securetransport/qtls_st.cpp +++ b/src/plugins/tls/securetransport/qtls_st.cpp @@ -407,6 +407,7 @@ void TlsCryptographSecureTransport::disconnectFromHost() if (context) { if (!shutdown) { SSLClose(context); + context.reset(nullptr); shutdown = true; } } @@ -1148,8 +1149,6 @@ bool TlsCryptographSecureTransport::verifyPeerTrust() QCFType<CFDataRef> certData = cert.toDer().toCFData(); if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData)) CFArrayAppendValue(certArray, secRef); - else - qCWarning(lcSecureTransport, "Failed to create SecCertificate from QSslCertificate"); } SecTrustSetAnchorCertificates(trust, certArray); diff --git a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp index cdecdee9b2..fba52fa1a8 100644 --- a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp +++ b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp @@ -42,6 +42,7 @@ #include <QtNetwork/qsslcertificate.h> +#include <QtCore/qloggingcategory.h> #include <QtCore/qglobal.h> #include <QtCore/qdebug.h> @@ -57,6 +58,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcX509, "qt.mac.shared.x509"); + #ifdef Q_OS_MACOS namespace { @@ -110,6 +113,52 @@ bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain) return false; } +bool canDERBeParsed(CFDataRef derData, const QSslCertificate &qtCert) +{ + // We are observing certificates, that while accepted when we copy them + // from the keychain(s), later give us 'Failed to create SslCertificate + // from QSslCertificate'. It's interesting to know at what step the failure + // occurred. Let's check it and skip it below if it's not valid. + + auto checkDer = [](CFDataRef derData, const char *source) + { + Q_ASSERT(source); + Q_ASSERT(derData); + + const auto cfLength = CFDataGetLength(derData); + if (cfLength <= 0) { + qCWarning(lcX509) << source << "returned faulty DER data with invalid length."; + return false; + } + + QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, derData); + if (!secRef) { + qCWarning(lcX509) << source << "returned faulty DER data which cannot be parsed back."; + return false; + } + return true; + }; + + if (!checkDer(derData, "SecCertificateCopyData")) { + qCDebug(lcX509) << "Faulty QSslCertificate is:" << qtCert;// Just in case we managed to parse something. + return false; + } + + // Generic parser failed? + if (qtCert.isNull()) { + qCWarning(lcX509, "QSslCertificate failed to parse DER"); + return false; + } + + const QCFType<CFDataRef> qtDerData = qtCert.toDer().toCFData(); + if (!checkDer(qtDerData, "QSslCertificate")) { + qCWarning(lcX509) << "Faulty QSslCertificate is:" << qtCert; + return false; + } + + return true; +} + } // unnamed namespace #endif // Q_OS_MACOS @@ -130,8 +179,19 @@ QList<QSslCertificate> systemCaCertificates() SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert); if (isCaCertificateTrusted(cfCert, dom)) { - if (derData) - systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + if (derData) { + const auto newCert = QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + if (!canDERBeParsed(derData, newCert)) { + // Last attempt to get some information about the certificate: + CFShow(cfCert); + continue; + } + systemCerts << newCert; + } else { + // "Returns NULL if the data passed in the certificate parameter + // is not a valid certificate object." + qCWarning(lcX509, "SecCertificateCopyData returned invalid DER data (nullptr)."); + } } } } |