Submitted By: Pierre Labastie Date: 2023-10-10 Initial Package Version: 5.15.11 Upstream Status: Applied (according to KDE) Origin: KDE patch set Description: Patch set maintained by the KDE folks see https://dot.kde.org/2021/04/06/announcing-kdes-qt-5-patch-collection Patch generated using the procedure in https://wiki.linuxfromscratch.org/blfs/ticket/17476#comment:3 Submodule qtbase da6e9583..c672f8bf: diff --git a/qtbase/mkspecs/common/android/qplatformdefs.h b/qtbase/mkspecs/common/android/qplatformdefs.h index f75bc4093b..2bd59410d4 100644 --- a/qtbase/mkspecs/common/android/qplatformdefs.h +++ b/qtbase/mkspecs/common/android/qplatformdefs.h @@ -144,11 +144,7 @@ #define QT_SIGNAL_ARGS int #define QT_SIGNAL_IGNORE SIG_IGN -#if defined(__GLIBC__) && (__GLIBC__ >= 2) #define QT_SOCKLEN_T socklen_t -#else -#define QT_SOCKLEN_T int -#endif #if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) #define QT_SNPRINTF ::snprintf diff --git a/qtbase/mkspecs/linux-clang/qplatformdefs.h b/qtbase/mkspecs/linux-clang/qplatformdefs.h index a818d973f0..c1ab72fbc6 100644 --- a/qtbase/mkspecs/linux-clang/qplatformdefs.h +++ b/qtbase/mkspecs/linux-clang/qplatformdefs.h @@ -79,14 +79,6 @@ #define QT_USE_XOPEN_LFS_EXTENSIONS #include "../common/posix/qplatformdefs.h" -#undef QT_SOCKLEN_T - -#if defined(__GLIBC__) && (__GLIBC__ >= 2) -#define QT_SOCKLEN_T socklen_t -#else -#define QT_SOCKLEN_T int -#endif - #if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) #define QT_SNPRINTF ::snprintf #define QT_VSNPRINTF ::vsnprintf diff --git a/qtbase/mkspecs/linux-g++/qplatformdefs.h b/qtbase/mkspecs/linux-g++/qplatformdefs.h index 13523f0702..4d2750d9ec 100644 --- a/qtbase/mkspecs/linux-g++/qplatformdefs.h +++ b/qtbase/mkspecs/linux-g++/qplatformdefs.h @@ -79,14 +79,6 @@ #define QT_USE_XOPEN_LFS_EXTENSIONS #include "../common/posix/qplatformdefs.h" -#undef QT_SOCKLEN_T - -#if defined(__GLIBC__) && (__GLIBC__ < 2) -#define QT_SOCKLEN_T int -#else -#define QT_SOCKLEN_T socklen_t -#endif - #if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) #define QT_SNPRINTF ::snprintf #define QT_VSNPRINTF ::vsnprintf diff --git a/qtbase/mkspecs/linux-llvm/qplatformdefs.h b/qtbase/mkspecs/linux-llvm/qplatformdefs.h index dc750ab1ef..d3cc39b47f 100644 --- a/qtbase/mkspecs/linux-llvm/qplatformdefs.h +++ b/qtbase/mkspecs/linux-llvm/qplatformdefs.h @@ -80,14 +80,6 @@ #define QT_USE_XOPEN_LFS_EXTENSIONS #include "../common/posix/qplatformdefs.h" -#undef QT_SOCKLEN_T - -#if defined(__GLIBC__) && (__GLIBC__ >= 2) -#define QT_SOCKLEN_T socklen_t -#else -#define QT_SOCKLEN_T int -#endif - #if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) #define QT_SNPRINTF ::snprintf #define QT_VSNPRINTF ::vsnprintf diff --git a/qtbase/mkspecs/linux-lsb-g++/qplatformdefs.h b/qtbase/mkspecs/linux-lsb-g++/qplatformdefs.h index 4c4e53da2a..83baffb3e3 100644 --- a/qtbase/mkspecs/linux-lsb-g++/qplatformdefs.h +++ b/qtbase/mkspecs/linux-lsb-g++/qplatformdefs.h @@ -85,16 +85,9 @@ #include "../common/posix/qplatformdefs.h" #undef QT_OPEN_LARGEFILE -#undef QT_SOCKLEN_T #define QT_OPEN_LARGEFILE 0 -#if defined(__GLIBC__) && (__GLIBC__ >= 2) -#define QT_SOCKLEN_T socklen_t -#else -#define QT_SOCKLEN_T int -#endif - #ifndef SIOCGIFBRDADDR # define SIOCGIFBRDADDR 0x8919 #endif diff --git a/qtbase/mkspecs/lynxos-g++/qplatformdefs.h b/qtbase/mkspecs/lynxos-g++/qplatformdefs.h index 4339ea2b23..6007af0055 100644 --- a/qtbase/mkspecs/lynxos-g++/qplatformdefs.h +++ b/qtbase/mkspecs/lynxos-g++/qplatformdefs.h @@ -72,14 +72,6 @@ #include "../common/posix/qplatformdefs.h" -#undef QT_SOCKLEN_T - -#if defined(__GLIBC__) && (__GLIBC__ >= 2) -#define QT_SOCKLEN_T socklen_t -#else -#define QT_SOCKLEN_T int -#endif - #if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) #define QT_SNPRINTF ::snprintf #define QT_VSNPRINTF ::vsnprintf diff --git a/qtbase/src/3rdparty/angle/src/libANGLE/HandleAllocator.cpp b/qtbase/src/3rdparty/angle/src/libANGLE/HandleAllocator.cpp index c3c184258f..32af3f8f29 100644 --- a/qtbase/src/3rdparty/angle/src/libANGLE/HandleAllocator.cpp +++ b/qtbase/src/3rdparty/angle/src/libANGLE/HandleAllocator.cpp @@ -10,6 +10,7 @@ #include "libANGLE/HandleAllocator.h" #include +#include #include "common/debug.h" diff --git a/qtbase/src/3rdparty/forkfd/forkfd_linux.c b/qtbase/src/3rdparty/forkfd/forkfd_linux.c index ffe0e9a5e2..b1f5408d11 100644 --- a/qtbase/src/3rdparty/forkfd/forkfd_linux.c +++ b/qtbase/src/3rdparty/forkfd/forkfd_linux.c @@ -82,7 +82,8 @@ static int sys_clone(unsigned long cloneflags, int *ptid) return syscall(__NR_clone, cloneflags, child_stack, stack_size, ptid, newtls, ctid); #elif defined(__arc__) || defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \ defined(__nds32__) || defined(__hppa__) || defined(__powerpc__) || defined(__i386__) || \ - defined(__x86_64__) || defined(__xtensa__) || defined(__alpha__) || defined(__riscv) + defined(__x86_64__) || defined(__xtensa__) || defined(__alpha__) || defined(__riscv) || \ + defined(__loongarch__) /* ctid and newtls are inverted on CONFIG_CLONE_BACKWARDS architectures, * but since both values are 0, there's no harm. */ return syscall(__NR_clone, cloneflags, child_stack, ptid, ctid, newtls); diff --git a/qtbase/src/corelib/global/qglobal.cpp b/qtbase/src/corelib/global/qglobal.cpp index 5ad82c259d..ecf7b1efaa 100644 --- a/qtbase/src/corelib/global/qglobal.cpp +++ b/qtbase/src/corelib/global/qglobal.cpp @@ -97,6 +97,10 @@ # include #endif +#if defined(Q_OS_MACOS) +#include +#endif + #ifdef Q_OS_UNIX #include #include @@ -2133,6 +2137,15 @@ QT_WARNING_POP static const char *osVer_helper(QOperatingSystemVersion version = QOperatingSystemVersion::current()) { #ifdef Q_OS_MACOS + if (version.majorVersion() == 13) + return "Ventura"; + if (version.majorVersion() == 12) + return "Monterey"; + // Compare against predefined constant to handle 10.16/11.0 + if (QVersionNumber(QOperatingSystemVersion::MacOSBigSur.majorVersion(), + QOperatingSystemVersion::MacOSBigSur.minorVersion(), QOperatingSystemVersion::MacOSBigSur.microVersion()).isPrefixOf( + QVersionNumber(version.majorVersion(), version.minorVersion(), version.microVersion()))) + return "Big Sur"; if (version.majorVersion() == 10) { switch (version.minorVersion()) { case 9: @@ -2147,13 +2160,15 @@ static const char *osVer_helper(QOperatingSystemVersion version = QOperatingSyst return "High Sierra"; case 14: return "Mojave"; + case 15: + return "Catalina"; } } // unknown, future version #else Q_UNUSED(version); #endif - return 0; + return nullptr; } #endif @@ -2278,7 +2293,7 @@ static const char *osVer_helper(QOperatingSystemVersion version = QOperatingSyst } #undef Q_WINVER // unknown, future version - return 0; + return nullptr; } #endif diff --git a/qtbase/src/corelib/global/qglobal.h b/qtbase/src/corelib/global/qglobal.h index 450c1e586a..ff7167d9cc 100644 --- a/qtbase/src/corelib/global/qglobal.h +++ b/qtbase/src/corelib/global/qglobal.h @@ -307,6 +307,8 @@ typedef double qreal; # define QT_DEPRECATED_CONSTRUCTOR # undef Q_DECL_ENUMERATOR_DEPRECATED # define Q_DECL_ENUMERATOR_DEPRECATED +# undef Q_DECL_ENUMERATOR_DEPRECATED_X +# define Q_DECL_ENUMERATOR_DEPRECATED_X(ignored) #endif #ifndef QT_DEPRECATED_WARNINGS_SINCE diff --git a/qtbase/src/corelib/global/qlogging.cpp b/qtbase/src/corelib/global/qlogging.cpp index 292116cc47..0f253d4a27 100644 --- a/qtbase/src/corelib/global/qlogging.cpp +++ b/qtbase/src/corelib/global/qlogging.cpp @@ -189,6 +189,17 @@ static int checked_var_value(const char *varname) return ok ? value : 1; } +static bool is_fatal_count_down(QAtomicInt &n) +{ + // it's fatal if the current value is exactly 1, + // otherwise decrement if it's non-zero + + int v = n.loadRelaxed(); + while (v != 0 && !n.testAndSetRelaxed(v, v - 1, v)) + ; + return v == 1; // we exited the loop, so either v == 0 or CAS succeeded to set n from v to v-1 +} + static bool isFatal(QtMsgType msgType) { if (msgType == QtFatalMsg) @@ -196,18 +207,12 @@ static bool isFatal(QtMsgType msgType) if (msgType == QtCriticalMsg) { static QAtomicInt fatalCriticals = checked_var_value("QT_FATAL_CRITICALS"); - - // it's fatal if the current value is exactly 1, - // otherwise decrement if it's non-zero - return fatalCriticals.loadRelaxed() && fatalCriticals.fetchAndAddRelaxed(-1) == 1; + return is_fatal_count_down(fatalCriticals); } if (msgType == QtWarningMsg || msgType == QtCriticalMsg) { static QAtomicInt fatalWarnings = checked_var_value("QT_FATAL_WARNINGS"); - - // it's fatal if the current value is exactly 1, - // otherwise decrement if it's non-zero - return fatalWarnings.loadRelaxed() && fatalWarnings.fetchAndAddRelaxed(-1) == 1; + return is_fatal_count_down(fatalWarnings); } return false; diff --git a/qtbase/src/corelib/global/qnamespace.h b/qtbase/src/corelib/global/qnamespace.h index deab11f729..486b63fa3f 100644 --- a/qtbase/src/corelib/global/qnamespace.h +++ b/qtbase/src/corelib/global/qnamespace.h @@ -1867,7 +1867,7 @@ public: QT_Q_ENUM(TimerType) QT_Q_ENUM(ScrollPhase) QT_Q_ENUM(MouseEventSource) - QT_Q_FLAG(MouseEventFlag) + QT_Q_FLAG(MouseEventFlags) QT_Q_ENUM(ChecksumType) QT_Q_ENUM(HighDpiScaleFactorRoundingPolicy) QT_Q_ENUM(TabFocusBehavior) diff --git a/qtbase/src/corelib/io/qfilesystemengine_win.cpp b/qtbase/src/corelib/io/qfilesystemengine_win.cpp index 81d3a71986..c86ed1f9b0 100644 --- a/qtbase/src/corelib/io/qfilesystemengine_win.cpp +++ b/qtbase/src/corelib/io/qfilesystemengine_win.cpp @@ -664,14 +664,14 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) return QFileSystemEntry(ret, QFileSystemEntry::FromInternalPath()); } -#if defined(Q_CC_MINGW) && WINVER < 0x0602 // Windows 8 onwards +#if defined(Q_CC_MINGW) && WINVER < 0x0602 && _WIN32_WINNT < _WIN32_WINNT_WIN8 // Windows 8 onwards typedef struct _FILE_ID_INFO { ULONGLONG VolumeSerialNumber; FILE_ID_128 FileId; } FILE_ID_INFO, *PFILE_ID_INFO; -#endif // if defined (Q_CC_MINGW) && WINVER < 0x0602 +#endif // if defined(Q_CC_MINGW) && WINVER < 0x0602 && _WIN32_WINNT < _WIN32_WINNT_WIN8 // File ID for Windows up to version 7 and FAT32 drives static inline QByteArray fileId(HANDLE handle) diff --git a/qtbase/src/corelib/io/qfilesystemwatcher_inotify.cpp b/qtbase/src/corelib/io/qfilesystemwatcher_inotify.cpp index 94d9d06bcb..27e0b13b0b 100644 --- a/qtbase/src/corelib/io/qfilesystemwatcher_inotify.cpp +++ b/qtbase/src/corelib/io/qfilesystemwatcher_inotify.cpp @@ -366,7 +366,9 @@ void QInotifyFileSystemWatcherEngine::readFromInotify() // qDebug("QInotifyFileSystemWatcherEngine::readFromInotify"); int buffSize = 0; - ioctl(inotifyFd, FIONREAD, (char *) &buffSize); + if (ioctl(inotifyFd, FIONREAD, (char *) &buffSize) == -1 || buffSize == 0) + return; + QVarLengthArray buffer(buffSize); buffSize = read(inotifyFd, buffer.data(), buffSize); char *at = buffer.data(); diff --git a/qtbase/src/corelib/io/qfsfileengine.cpp b/qtbase/src/corelib/io/qfsfileengine.cpp index 3042eac2f0..a862ce9166 100644 --- a/qtbase/src/corelib/io/qfsfileengine.cpp +++ b/qtbase/src/corelib/io/qfsfileengine.cpp @@ -361,7 +361,7 @@ bool QFSFileEnginePrivate::openFd(QIODevice::OpenMode openMode, int fd) // Seek to the end when in Append mode. if (openMode & QFile::Append) { - int ret; + QT_OFF_T ret; do { ret = QT_LSEEK(fd, 0, SEEK_END); } while (ret == -1 && errno == EINTR); diff --git a/qtbase/src/corelib/io/qfsfileengine_unix.cpp b/qtbase/src/corelib/io/qfsfileengine_unix.cpp index 4610e9306c..65e921c15a 100644 --- a/qtbase/src/corelib/io/qfsfileengine_unix.cpp +++ b/qtbase/src/corelib/io/qfsfileengine_unix.cpp @@ -141,7 +141,7 @@ bool QFSFileEnginePrivate::nativeOpen(QIODevice::OpenMode openMode) // Seek to the end when in Append mode. if (flags & QFile::Append) { - int ret; + QT_OFF_T ret; do { ret = QT_LSEEK(fd, 0, SEEK_END); } while (ret == -1 && errno == EINTR); diff --git a/qtbase/src/corelib/itemmodels/qabstractitemmodel.cpp b/qtbase/src/corelib/itemmodels/qabstractitemmodel.cpp index 997a634e76..1c1ca7394d 100644 --- a/qtbase/src/corelib/itemmodels/qabstractitemmodel.cpp +++ b/qtbase/src/corelib/itemmodels/qabstractitemmodel.cpp @@ -52,6 +52,8 @@ #include #include +#include + #include QT_BEGIN_NAMESPACE @@ -213,7 +215,7 @@ bool QPersistentModelIndex::operator<(const QPersistentModelIndex &other) const if (d && other.d) return d->index < other.d->index; - return d < other.d; + return std::less<>{}(d, other.d); } /*! diff --git a/qtbase/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/qtbase/src/corelib/itemmodels/qsortfilterproxymodel.cpp index 3d7fe43cd3..f2871a2da7 100644 --- a/qtbase/src/corelib/itemmodels/qsortfilterproxymodel.cpp +++ b/qtbase/src/corelib/itemmodels/qsortfilterproxymodel.cpp @@ -939,8 +939,9 @@ void QSortFilterProxyModelPrivate::insert_source_items( q->beginInsertColumns(proxy_parent, proxy_start, proxy_end); } - for (int i = 0; i < source_items.size(); ++i) - proxy_to_source.insert(proxy_start + i, source_items.at(i)); + // TODO: use the range QList::insert() overload once it is implemented (QTBUG-58633). + proxy_to_source.insert(proxy_start, source_items.size(), 0); + std::copy(source_items.cbegin(), source_items.cend(), proxy_to_source.begin() + proxy_start); build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); @@ -3131,8 +3132,9 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex & if (d->filter_data.isEmpty()) return true; + + int column_count = d->model->columnCount(source_parent); if (d->filter_column == -1) { - int column_count = d->model->columnCount(source_parent); for (int column = 0; column < column_count; ++column) { QModelIndex source_index = d->model->index(source_row, column, source_parent); QString key = d->model->data(source_index, d->filter_role).toString(); @@ -3141,9 +3143,10 @@ bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex & } return false; } - QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent); - if (!source_index.isValid()) // the column may not exist + + if (d->filter_column >= column_count) // the column may not exist return true; + QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent); QString key = d->model->data(source_index, d->filter_role).toString(); return d->filter_data.hasMatch(key); } diff --git a/qtbase/src/corelib/kernel/qtranslator.cpp b/qtbase/src/corelib/kernel/qtranslator.cpp index bdcd016630..e4375e7e40 100644 --- a/qtbase/src/corelib/kernel/qtranslator.cpp +++ b/qtbase/src/corelib/kernel/qtranslator.cpp @@ -907,7 +907,7 @@ static QString getMessage(const uchar *m, const uchar *end, const char *context, goto end; case Tag_Translation: { int len = read32(m); - if (len % 1) + if (len & 1) return QString(); m += 4; if (!numerus--) { diff --git a/qtbase/src/corelib/mimetypes/qmimedatabase.cpp b/qtbase/src/corelib/mimetypes/qmimedatabase.cpp index 9de22cef33..ff868a3268 100644 --- a/qtbase/src/corelib/mimetypes/qmimedatabase.cpp +++ b/qtbase/src/corelib/mimetypes/qmimedatabase.cpp @@ -389,20 +389,23 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa // Disambiguate conflicting extensions (if magic matching found something) if (candidateByData.isValid() && magicAccuracy > 0) { const QString sniffedMime = candidateByData.name(); - // If the sniffedMime matches a glob match, use it + // If the sniffedMime matches a highest-weight glob match, use it if (candidatesByName.m_matchingMimeTypes.contains(sniffedMime)) { *accuracyPtr = 100; return candidateByData; } - for (const QString &m : qAsConst(candidatesByName.m_matchingMimeTypes)) { + for (const QString &m : qAsConst(candidatesByName.m_allMatchingMimeTypes)) { if (inherits(m, sniffedMime)) { // We have magic + pattern pointing to this, so it's a pretty good match *accuracyPtr = 100; return mimeTypeForName(m); } } - *accuracyPtr = magicAccuracy; - return candidateByData; + if (candidatesByName.m_allMatchingMimeTypes.isEmpty()) { + // No glob, use magic + *accuracyPtr = magicAccuracy; + return candidateByData; + } } } diff --git a/qtbase/src/corelib/mimetypes/qmimeglobpattern.cpp b/qtbase/src/corelib/mimetypes/qmimeglobpattern.cpp index b1de8907b2..fa8f4c545d 100644 --- a/qtbase/src/corelib/mimetypes/qmimeglobpattern.cpp +++ b/qtbase/src/corelib/mimetypes/qmimeglobpattern.cpp @@ -83,7 +83,10 @@ void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const Q } if (!m_matchingMimeTypes.contains(mimeType)) { m_matchingMimeTypes.append(mimeType); - m_allMatchingMimeTypes.append(mimeType); + if (replace) + m_allMatchingMimeTypes.prepend(mimeType); // highest-weight first + else + m_allMatchingMimeTypes.append(mimeType); m_knownSuffixLength = knownSuffixLength; } } diff --git a/qtbase/src/corelib/mimetypes/qmimeprovider.cpp b/qtbase/src/corelib/mimetypes/qmimeprovider.cpp index 258dddf8cb..5125704cf1 100644 --- a/qtbase/src/corelib/mimetypes/qmimeprovider.cpp +++ b/qtbase/src/corelib/mimetypes/qmimeprovider.cpp @@ -242,21 +242,28 @@ void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobM return; Q_ASSERT(m_cacheFile); const QString lowerFileName = fileName.toLower(); + int numMatches = 0; // Check literals (e.g. "Makefile") - matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName); - // Check complex globs (e.g. "callgrind.out[0-9]*") - matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName); + numMatches = matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName); // Check the very common *.txt cases with the suffix tree - const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset); - const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset); - const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4); - matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.length() - 1, false); - if (result.m_matchingMimeTypes.isEmpty()) - matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true); + if (numMatches == 0) { + const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset); + const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset); + const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4); + if (matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.length() - 1, false)) { + ++numMatches; + } else if (matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true)) { + ++numMatches; + } + } + // Check complex globs (e.g. "callgrind.out[0-9]*" or "README*") + if (numMatches == 0) + matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName); } -void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) +int QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) { + int numMatches = 0; const int numGlobs = cacheFile->getUint32(off); //qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset; for (int i = 0; i < numGlobs; ++i) { @@ -272,9 +279,12 @@ void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile //qDebug() << pattern << mimeType << weight << caseSensitive; QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive); - if (glob.matchFileName(fileName)) + if (glob.matchFileName(fileName)) { result.addMatch(QLatin1String(mimeType), weight, pattern); + ++numMatches; + } } + return numMatches; } bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, QMimeBinaryProvider::CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck) diff --git a/qtbase/src/corelib/mimetypes/qmimeprovider_p.h b/qtbase/src/corelib/mimetypes/qmimeprovider_p.h index f9c8ef384c..5b328a7d5e 100644 --- a/qtbase/src/corelib/mimetypes/qmimeprovider_p.h +++ b/qtbase/src/corelib/mimetypes/qmimeprovider_p.h @@ -115,7 +115,7 @@ public: private: struct CacheFile; - void matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int offset, const QString &fileName); + int matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int offset, const QString &fileName); bool matchSuffixTree(QMimeGlobMatchResult &result, CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck); bool matchMagicRule(CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data); QLatin1String iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime); diff --git a/qtbase/src/corelib/serialization/qcborvalue.cpp b/qtbase/src/corelib/serialization/qcborvalue.cpp index 89a928d348..3a8c2cb9ec 100644 --- a/qtbase/src/corelib/serialization/qcborvalue.cpp +++ b/qtbase/src/corelib/serialization/qcborvalue.cpp @@ -2123,7 +2123,8 @@ QCborArray QCborValue::toArray(const QCborArray &defaultValue) const Q_ASSERT(n == -1 || container == nullptr); if (n < 0) dd = container; - return dd ? QCborArray(*dd) : defaultValue; + // return QCborArray(*dd); but that's UB if dd is nullptr + return dd ? QCborArray(*dd) : QCborArray(); } /*! @@ -2165,7 +2166,8 @@ QCborMap QCborValue::toMap(const QCborMap &defaultValue) const Q_ASSERT(n == -1 || container == nullptr); if (n < 0) dd = container; - return dd ? QCborMap(*dd) : defaultValue; + // return QCborMap(*dd); but that's UB if dd is nullptr + return dd ? QCborMap(*dd) : QCborMap(); } /*! diff --git a/qtbase/src/corelib/serialization/qxmlstream.cpp b/qtbase/src/corelib/serialization/qxmlstream.cpp index 0ac5548178..0d49cbac19 100644 --- a/qtbase/src/corelib/serialization/qxmlstream.cpp +++ b/qtbase/src/corelib/serialization/qxmlstream.cpp @@ -160,7 +160,7 @@ enum { StreamEOF = ~0U }; addData() or by waiting for it to arrive on the device(). \value UnexpectedElementError The parser encountered an element - that was different to those it expected. + or token that was different to those it expected. */ @@ -295,13 +295,34 @@ QXmlStreamEntityResolver *QXmlStreamReader::entityResolver() const QXmlStreamReader is a well-formed XML 1.0 parser that does \e not include external parsed entities. As long as no error occurs, the - application code can thus be assured that the data provided by the - stream reader satisfies the W3C's criteria for well-formed XML. For - example, you can be certain that all tags are indeed nested and - closed properly, that references to internal entities have been - replaced with the correct replacement text, and that attributes have - been normalized or added according to the internal subset of the - DTD. + application code can thus be assured, that + \list + \li the data provided by the stream reader satisfies the W3C's + criteria for well-formed XML, + \li tokens are provided in a valid order. + \endlist + + Unless QXmlStreamReader raises an error, it guarantees the following: + \list + \li All tags are nested and closed properly. + \li References to internal entities have been replaced with the + correct replacement text. + \li Attributes have been normalized or added according to the + internal subset of the \l DTD. + \li Tokens of type \l StartDocument happen before all others, + aside from comments and processing instructions. + \li At most one DOCTYPE element (a token of type \l DTD) is present. + \li If present, the DOCTYPE appears before all other elements, + aside from StartDocument, comments and processing instructions. + \endlist + + In particular, once any token of type \l StartElement, \l EndElement, + \l Characters, \l EntityReference or \l EndDocument is seen, no + tokens of type StartDocument or DTD will be seen. If one is present in + the input stream, out of order, an error is raised. + + \note The token types \l Comment and \l ProcessingInstruction may appear + anywhere in the stream. If an error occurs while parsing, atEnd() and hasError() return true, and error() returns the error that occurred. The functions @@ -620,6 +641,7 @@ QXmlStreamReader::TokenType QXmlStreamReader::readNext() d->token = -1; return readNext(); } + d->checkToken(); return d->type; } @@ -740,6 +762,14 @@ static const short QXmlStreamReader_tokenTypeString_indices[] = { }; +static const char QXmlStreamReader_XmlContextString[] = + "Prolog\0" + "Body\0"; + +static const short QXmlStreamReader_XmlContextString_indices[] = { + 0, 7 +}; + /*! \property QXmlStreamReader::namespaceProcessing The namespace-processing flag of the stream reader @@ -775,6 +805,16 @@ QString QXmlStreamReader::tokenString() const QXmlStreamReader_tokenTypeString_indices[d->type]); } +/*! + \internal + \return \param ctxt (Prolog/Body) as a string. + */ +QString contextString(QXmlStreamReaderPrivate::XmlContext ctxt) +{ + return QLatin1String(QXmlStreamReader_XmlContextString + + QXmlStreamReader_XmlContextString_indices[static_cast(ctxt)]); +} + #endif // QT_NO_XMLSTREAMREADER QXmlStreamPrivateTagStack::QXmlStreamPrivateTagStack() @@ -866,6 +906,8 @@ void QXmlStreamReaderPrivate::init() type = QXmlStreamReader::NoToken; error = QXmlStreamReader::NoError; + currentContext = XmlContext::Prolog; + foundDTD = false; } /* @@ -1302,15 +1344,18 @@ inline int QXmlStreamReaderPrivate::fastScanContentCharList() return n; } -inline int QXmlStreamReaderPrivate::fastScanName(int *prefix) +// Fast scan an XML attribute name (e.g. "xml:lang"). +inline QXmlStreamReaderPrivate::FastScanNameResult +QXmlStreamReaderPrivate::fastScanName(Value *val) { int n = 0; uint c; while ((c = getChar()) != StreamEOF) { if (n >= 4096) { // This is too long to be a sensible name, and - // can exhaust memory - return 0; + // can exhaust memory, or the range of decltype(*prefix) + raiseNamePrefixTooLongError(); + return {}; } switch (c) { case '\n': @@ -1339,23 +1384,23 @@ inline int QXmlStreamReaderPrivate::fastScanName(int *prefix) case '+': case '*': putChar(c); - if (prefix && *prefix == n+1) { - *prefix = 0; + if (val && val->prefix == n + 1) { + val->prefix = 0; putChar(':'); --n; } - return n; + return FastScanNameResult(n); case ':': - if (prefix) { - if (*prefix == 0) { - *prefix = n+2; + if (val) { + if (val->prefix == 0) { + val->prefix = n + 2; } else { // only one colon allowed according to the namespace spec. putChar(c); - return n; + return FastScanNameResult(n); } } else { putChar(c); - return n; + return FastScanNameResult(n); } Q_FALLTHROUGH(); default: @@ -1364,12 +1409,12 @@ inline int QXmlStreamReaderPrivate::fastScanName(int *prefix) } } - if (prefix) - *prefix = 0; + if (val) + val->prefix = 0; int pos = textBuffer.size() - n; putString(textBuffer, pos); textBuffer.resize(pos); - return 0; + return FastScanNameResult(0); } enum NameChar { NameBeginning, NameNotBeginning, NotName }; @@ -1878,6 +1923,14 @@ void QXmlStreamReaderPrivate::raiseWellFormedError(const QString &message) raiseError(QXmlStreamReader::NotWellFormedError, message); } +void QXmlStreamReaderPrivate::raiseNamePrefixTooLongError() +{ + // TODO: add a ImplementationLimitsExceededError and use it instead + raiseError(QXmlStreamReader::NotWellFormedError, + QXmlStream::tr("Length of XML attribute name exceeds implementation limits (4KiB " + "characters).")); +} + void QXmlStreamReaderPrivate::parseError() { @@ -4050,6 +4103,92 @@ void QXmlStreamWriter::writeCurrentToken(const QXmlStreamReader &reader) } } +static bool isTokenAllowedInContext(QXmlStreamReader::TokenType type, + QXmlStreamReaderPrivate::XmlContext loc) +{ + switch (type) { + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::DTD: + return loc == QXmlStreamReaderPrivate::XmlContext::Prolog; + + case QXmlStreamReader::StartElement: + case QXmlStreamReader::EndElement: + case QXmlStreamReader::Characters: + case QXmlStreamReader::EntityReference: + case QXmlStreamReader::EndDocument: + return loc == QXmlStreamReaderPrivate::XmlContext::Body; + + case QXmlStreamReader::Comment: + case QXmlStreamReader::ProcessingInstruction: + return true; + + case QXmlStreamReader::NoToken: + case QXmlStreamReader::Invalid: + return false; + default: + return false; + } +} + +/*! + \internal + \brief QXmlStreamReader::isValidToken + \return \c true if \param type is a valid token type. + \return \c false if \param type is an unexpected token, + which indicates a non-well-formed or invalid XML stream. + */ +bool QXmlStreamReaderPrivate::isValidToken(QXmlStreamReader::TokenType type) +{ + // Don't change currentContext, if Invalid or NoToken occur in the prolog + if (type == QXmlStreamReader::Invalid || type == QXmlStreamReader::NoToken) + return false; + + // If a token type gets rejected in the body, there is no recovery + const bool result = isTokenAllowedInContext(type, currentContext); + if (result || currentContext == XmlContext::Body) + return result; + + // First non-Prolog token observed => switch context to body and check again. + currentContext = XmlContext::Body; + return isTokenAllowedInContext(type, currentContext); +} + +/*! + \internal + Checks token type and raises an error, if it is invalid + in the current context (prolog/body). + */ +void QXmlStreamReaderPrivate::checkToken() +{ + Q_Q(QXmlStreamReader); + + // The token type must be consumed, to keep track if the body has been reached. + const XmlContext context = currentContext; + const bool ok = isValidToken(type); + + // Do nothing if an error has been raised already (going along with an unexpected token) + if (error != QXmlStreamReader::Error::NoError) + return; + + if (!ok) { + raiseError(QXmlStreamReader::UnexpectedElementError, + QLatin1String("Unexpected token type %1 in %2.") + .arg(q->tokenString(), contextString(context))); + return; + } + + if (type != QXmlStreamReader::DTD) + return; + + // Raise error on multiple DTD tokens + if (foundDTD) { + raiseError(QXmlStreamReader::UnexpectedElementError, + QLatin1String("Found second DTD token in %1.").arg(contextString(context))); + } else { + foundDTD = true; + } +} + /*! \fn bool QXmlStreamAttributes::hasAttribute(const QString &qualifiedName) const \since 4.5 diff --git a/qtbase/src/corelib/serialization/qxmlstream.g b/qtbase/src/corelib/serialization/qxmlstream.g index 4321fed68a..8c6a1a5887 100644 --- a/qtbase/src/corelib/serialization/qxmlstream.g +++ b/qtbase/src/corelib/serialization/qxmlstream.g @@ -516,7 +516,16 @@ public: int fastScanLiteralContent(); int fastScanSpace(); int fastScanContentCharList(); - int fastScanName(int *prefix = nullptr); + + struct FastScanNameResult { + FastScanNameResult() : ok(false) {} + explicit FastScanNameResult(int len) : addToLen(len), ok(true) { } + operator bool() { return ok; } + int operator*() { Q_ASSERT(ok); return addToLen; } + int addToLen; + bool ok; + }; + FastScanNameResult fastScanName(Value *val = nullptr); inline int fastScanNMTOKEN(); @@ -525,6 +534,7 @@ public: void raiseError(QXmlStreamReader::Error error, const QString& message = QString()); void raiseWellFormedError(const QString &message); + void raiseNamePrefixTooLongError(); QXmlStreamEntityResolver *entityResolver; @@ -1811,7 +1821,12 @@ space_opt ::= space; qname ::= LETTER; /. case $rule_number: { - sym(1).len += fastScanName(&sym(1).prefix); + Value &val = sym(1); + if (auto res = fastScanName(&val)) + val.len += *res; + else + return false; + if (atEnd) { resume($rule_number); return false; @@ -1822,7 +1837,11 @@ qname ::= LETTER; name ::= LETTER; /. case $rule_number: - sym(1).len += fastScanName(); + if (auto res = fastScanName()) + sym(1).len += *res; + else + return false; + if (atEnd) { resume($rule_number); return false; diff --git a/qtbase/src/corelib/serialization/qxmlstream_p.h b/qtbase/src/corelib/serialization/qxmlstream_p.h index e5bde7b98e..be7b1fe665 100644 --- a/qtbase/src/corelib/serialization/qxmlstream_p.h +++ b/qtbase/src/corelib/serialization/qxmlstream_p.h @@ -804,6 +804,17 @@ public: #endif bool atEnd; + enum class XmlContext + { + Prolog, + Body, + }; + + XmlContext currentContext = XmlContext::Prolog; + bool foundDTD = false; + bool isValidToken(QXmlStreamReader::TokenType type); + void checkToken(); + /*! \sa setType() */ @@ -1005,7 +1016,16 @@ public: int fastScanLiteralContent(); int fastScanSpace(); int fastScanContentCharList(); - int fastScanName(int *prefix = nullptr); + + struct FastScanNameResult { + FastScanNameResult() : ok(false) {} + explicit FastScanNameResult(int len) : addToLen(len), ok(true) { } + operator bool() { return ok; } + int operator*() { Q_ASSERT(ok); return addToLen; } + int addToLen; + bool ok; + }; + FastScanNameResult fastScanName(Value *val = nullptr); inline int fastScanNMTOKEN(); @@ -1014,6 +1034,7 @@ public: void raiseError(QXmlStreamReader::Error error, const QString& message = QString()); void raiseWellFormedError(const QString &message); + void raiseNamePrefixTooLongError(); QXmlStreamEntityResolver *entityResolver; @@ -1939,7 +1960,12 @@ bool QXmlStreamReaderPrivate::parse() break; case 262: { - sym(1).len += fastScanName(&sym(1).prefix); + Value &val = sym(1); + if (auto res = fastScanName(&val)) + val.len += *res; + else + return false; + if (atEnd) { resume(262); return false; @@ -1947,7 +1973,11 @@ bool QXmlStreamReaderPrivate::parse() } break; case 263: - sym(1).len += fastScanName(); + if (auto res = fastScanName()) + sym(1).len += *res; + else + return false; + if (atEnd) { resume(263); return false; diff --git a/qtbase/src/corelib/text/qlocale_data_p.h b/qtbase/src/corelib/text/qlocale_data_p.h index c5e6a0d461..c613e4e537 100644 --- a/qtbase/src/corelib/text/qlocale_data_p.h +++ b/qtbase/src/corelib/text/qlocale_data_p.h @@ -1340,7 +1340,7 @@ static const QLocaleData locale_data[] = { { 25, 6, 126, 46, 44, 59, 37, 48, 45, 43, 101, 12300, 12301, 12302, 12303, 166,5 , 166,5 , 176,5 , 176,5 , 415,8 , 402,13 , 209,6 , 226,13 , 2022,21 , 1980,28 , 2008,14 , 2022,21 , 1980,28 , 2008,14 , 58,2 , 55,2 , 283,3 , 5,17 , 22,23 , {77,79,80}, 137,4 , 3174,13 , 4,4 , 13,6 , 589,4 , 602,9 , 2, 1, 7, 6, 7 }, // Chinese/Traditional Han/Macau { 25, 6, 208, 46, 44, 59, 37, 48, 45, 43, 101, 12300, 12301, 12302, 12303, 166,5 , 166,5 , 171,5 , 171,5 , 394,8 , 423,14 , 209,6 , 226,13 , 2022,21 , 1980,28 , 2008,14 , 2022,21 , 1980,28 , 2008,14 , 58,2 , 55,2 , 45,4 , 5,17 , 22,23 , {84,87,68}, 6,1 , 3187,13 , 4,4 , 13,6 , 589,4 , 611,2 , 2, 0, 7, 6, 7 }, // Chinese/Traditional Han/Taiwan { 26, 7, 74, 46, 44, 59, 37, 48, 45, 43, 101, 8220, 8221, 8216, 8217, 0,6 , 0,6 , 0,6 , 0,6 , 53,10 , 63,17 , 37,5 , 8,10 , 0,28 , 0,28 , 85,14 , 0,28 , 0,28 , 85,14 , 0,2 , 0,2 , 45,4 , 5,17 , 22,23 , {69,85,82}, 14,1 , 0,7 , 8,5 , 4,0 , 0,0 , 0,0 , 2, 1, 1, 6, 7 }, // Corsican/Latin/France - { 27, 7, 54, 44, 46, 59, 37, 48, 8722, 43, 101, 8222, 8220, 8218, 8216, 0,6 , 0,6 , 159,7 , 159,7 , 437,13 , 450,19 , 37,5 , 87,12 , 2043,28 , 2071,58 , 2129,14 , 2043,28 , 2071,58 , 2143,14 , 0,2 , 0,2 , 286,7 , 5,17 , 22,23 , {72,82,75}, 141,3 , 3200,60 , 19,5 , 4,0 , 613,8 , 621,8 , 2, 1, 1, 6, 7 }, // Croatian/Latin/Croatia + { 27, 7, 54, 44, 46, 59, 37, 48, 8722, 43, 101, 8222, 8220, 8218, 8216, 0,6 , 0,6 , 159,7 , 159,7 , 437,13 , 450,19 , 37,5 , 87,12 , 2043,28 , 2071,58 , 2129,14 , 2043,28 , 2071,58 , 2143,14 , 0,2 , 0,2 , 286,7 , 5,17 , 22,23 , {69,85,82}, 14,1 , 3455,19 , 19,5 , 4,0 , 613,8 , 621,8 , 2, 1, 1, 6, 7 }, // Croatian/Latin/Croatia { 27, 7, 27, 44, 46, 59, 37, 48, 8722, 43, 101, 8222, 8220, 8218, 8216, 0,6 , 0,6 , 159,7 , 159,7 , 469,9 , 450,19 , 37,5 , 87,12 , 2043,28 , 2071,58 , 2143,14 , 2043,28 , 2071,58 , 2143,14 , 0,2 , 0,2 , 286,7 , 5,17 , 22,23 , {66,65,77}, 144,2 , 3260,85 , 19,5 , 4,0 , 613,8 , 629,19 , 2, 1, 1, 6, 7 }, // Croatian/Latin/Bosnia And Herzegowina { 28, 7, 57, 44, 160, 59, 37, 48, 45, 43, 101, 8222, 8220, 8218, 8216, 0,6 , 0,6 , 181,7 , 181,7 , 156,8 , 478,17 , 55,4 , 59,9 , 2157,21 , 2178,49 , 2227,14 , 2157,21 , 2178,49 , 2227,14 , 60,4 , 57,4 , 293,5 , 5,17 , 22,23 , {67,90,75}, 146,2 , 3345,68 , 19,5 , 4,0 , 648,7 , 655,5 , 2, 0, 1, 6, 7 }, // Czech/Latin/Czech Republic { 29, 7, 58, 44, 46, 59, 37, 48, 45, 43, 101, 8220, 8221, 8216, 8217, 0,6 , 0,6 , 188,8 , 188,8 , 495,10 , 505,23 , 239,5 , 244,10 , 2241,28 , 2269,51 , 2320,14 , 2334,35 , 2269,51 , 2320,14 , 0,2 , 0,2 , 0,5 , 5,17 , 22,23 , {68,75,75}, 148,3 , 3413,42 , 19,5 , 4,0 , 660,5 , 665,7 , 2, 0, 1, 6, 7 }, // Danish/Latin/Denmark diff --git a/qtbase/src/corelib/text/qstringiterator_p.h b/qtbase/src/corelib/text/qstringiterator_p.h index b31c7673c2..731a241407 100644 --- a/qtbase/src/corelib/text/qstringiterator_p.h +++ b/qtbase/src/corelib/text/qstringiterator_p.h @@ -123,16 +123,20 @@ public: { Q_ASSERT_X(hasNext(), Q_FUNC_INFO, "iterator hasn't a next item"); - if (Q_UNLIKELY((pos++)->isHighSurrogate())) + if (Q_UNLIKELY((pos++)->isHighSurrogate())) { + Q_ASSERT(pos < e && pos->isLowSurrogate()); ++pos; + } } inline uint peekNextUnchecked() const { Q_ASSERT_X(hasNext(), Q_FUNC_INFO, "iterator hasn't a next item"); - if (Q_UNLIKELY(pos->isHighSurrogate())) + if (Q_UNLIKELY(pos->isHighSurrogate())) { + Q_ASSERT(pos + 1 < e && pos[1].isLowSurrogate()); return QChar::surrogateToUcs4(pos[0], pos[1]); + } return pos->unicode(); } @@ -158,8 +162,10 @@ public: Q_ASSERT_X(hasNext(), Q_FUNC_INFO, "iterator hasn't a next item"); const QChar cur = *pos++; - if (Q_UNLIKELY(cur.isHighSurrogate())) + if (Q_UNLIKELY(cur.isHighSurrogate())) { + Q_ASSERT(pos < e && pos->isLowSurrogate()); return QChar::surrogateToUcs4(cur, *pos++); + } return cur.unicode(); } @@ -199,16 +205,20 @@ public: { Q_ASSERT_X(hasPrevious(), Q_FUNC_INFO, "iterator hasn't a previous item"); - if (Q_UNLIKELY((--pos)->isLowSurrogate())) + if (Q_UNLIKELY((--pos)->isLowSurrogate())) { + Q_ASSERT(pos > i && pos[-1].isHighSurrogate()); --pos; + } } inline uint peekPreviousUnchecked() const { Q_ASSERT_X(hasPrevious(), Q_FUNC_INFO, "iterator hasn't a previous item"); - if (Q_UNLIKELY(pos[-1].isLowSurrogate())) + if (Q_UNLIKELY(pos[-1].isLowSurrogate())) { + Q_ASSERT(pos > i + 1 && pos[-2].isHighSurrogate()); return QChar::surrogateToUcs4(pos[-2], pos[-1]); + } return pos[-1].unicode(); } @@ -233,8 +243,10 @@ public: Q_ASSERT_X(hasPrevious(), Q_FUNC_INFO, "iterator hasn't a previous item"); const QChar cur = *--pos; - if (Q_UNLIKELY(cur.isLowSurrogate())) + if (Q_UNLIKELY(cur.isLowSurrogate())) { + Q_ASSERT(pos > i && pos[-1].isHighSurrogate()); return QChar::surrogateToUcs4(*--pos, cur); + } return cur.unicode(); } diff --git a/qtbase/src/corelib/thread/qfutex_p.h b/qtbase/src/corelib/thread/qfutex_p.h index f287b752d7..e294537787 100644 --- a/qtbase/src/corelib/thread/qfutex_p.h +++ b/qtbase/src/corelib/thread/qfutex_p.h @@ -52,6 +52,7 @@ // #include +#include QT_BEGIN_NAMESPACE @@ -106,16 +107,13 @@ namespace QtLinuxFutex { inline int _q_futex(int *addr, int op, int val, quintptr val2 = 0, int *addr2 = nullptr, int val3 = 0) noexcept { - // A futex call ensures total ordering on the futex words - // (in either success or failure of the call). Instruct TSAN accordingly, - // as TSAN does not understand the futex(2) syscall. - _q_tsan_release(addr, addr2); + QtTsan::futexRelease(addr, addr2); // we use __NR_futex because some libcs (like Android's bionic) don't // provide SYS_futex etc. int result = syscall(__NR_futex, addr, op | FUTEX_PRIVATE_FLAG, val, val2, addr2, val3); - _q_tsan_acquire(addr, addr2); + QtTsan::futexAcquire(addr, addr2); return result; } diff --git a/qtbase/src/corelib/thread/qmutex.cpp b/qtbase/src/corelib/thread/qmutex.cpp index 310d1cb14f..7097122d8e 100644 --- a/qtbase/src/corelib/thread/qmutex.cpp +++ b/qtbase/src/corelib/thread/qmutex.cpp @@ -152,6 +152,7 @@ public: /*! \enum QMutex::RecursionMode + \obsolete Use QRecursiveMutex to create a recursive mutex. \value Recursive In this mode, a thread can lock the same mutex multiple times and the mutex won't be unlocked @@ -173,6 +174,7 @@ public: /*! Constructs a new mutex. The mutex is created in an unlocked state. + \obsolete Use QRecursiveMutex to create a recursive mutex. If \a mode is QMutex::Recursive, a thread can lock the same mutex multiple times and the mutex won't be unlocked until a @@ -197,7 +199,7 @@ QMutex::QMutex(RecursionMode mode) QMutex::~QMutex() { QMutexData *d = d_ptr.loadRelaxed(); - if (isRecursive()) { + if (QBasicMutex::isRecursive()) { delete static_cast(d); } else if (d) { #ifndef QT_LINUX_FUTEX diff --git a/qtbase/src/corelib/thread/qmutex.h b/qtbase/src/corelib/thread/qmutex.h index 73c9e00663..1bae573a03 100644 --- a/qtbase/src/corelib/thread/qmutex.h +++ b/qtbase/src/corelib/thread/qmutex.h @@ -42,6 +42,7 @@ #include #include +#include #include #if __has_include() @@ -77,19 +78,37 @@ public: // BasicLockable concept inline void lock() QT_MUTEX_LOCK_NOEXCEPT { + QtTsan::mutexPreLock(this, 0u); + if (!fastTryLock()) lockInternal(); + + QtTsan::mutexPostLock(this, 0u, 0); } // BasicLockable concept inline void unlock() noexcept { Q_ASSERT(d_ptr.loadRelaxed()); //mutex must be locked + + QtTsan::mutexPreUnlock(this, 0u); + if (!fastTryUnlock()) unlockInternal(); + + QtTsan::mutexPostUnlock(this, 0u); } bool tryLock() noexcept { - return fastTryLock(); + unsigned tsanFlags = QtTsan::TryLock; + QtTsan::mutexPreLock(this, tsanFlags); + + const bool success = fastTryLock(); + + if (!success) + tsanFlags |= QtTsan::TryLockFailed; + QtTsan::mutexPostLock(this, tsanFlags, 0); + + return success; } // Lockable concept @@ -134,8 +153,16 @@ public: #else QMutex() { d_ptr.storeRelaxed(nullptr); } #endif +#if QT_DEPRECATED_SINCE(5,15) enum RecursionMode { NonRecursive, Recursive }; + QT_DEPRECATED_VERSION_X(5, 15, "Use QRecursiveMutex instead of a recursive QMutex") explicit QMutex(RecursionMode mode); + + QT_DEPRECATED_VERSION_X(5, 15, "Use QRecursiveMutex instead of a recursive QMutex") + bool isRecursive() const noexcept + { return QBasicMutex::isRecursive(); } +#endif + ~QMutex(); // BasicLockable concept @@ -166,9 +193,6 @@ public: } #endif - bool isRecursive() const noexcept - { return QBasicMutex::isRecursive(); } - private: Q_DISABLE_COPY(QMutex) friend class QMutexLocker; diff --git a/qtbase/src/corelib/thread/qthreadpool.cpp b/qtbase/src/corelib/thread/qthreadpool.cpp index cdeb077233..5b1e4f4c33 100644 --- a/qtbase/src/corelib/thread/qthreadpool.cpp +++ b/qtbase/src/corelib/thread/qthreadpool.cpp @@ -602,8 +602,12 @@ bool QThreadPool::tryStart(std::function functionToRun) return false; QRunnable *runnable = QRunnable::create(std::move(functionToRun)); + Q_ASSERT(runnable->ref == 0); + ++runnable->ref; if (d->tryStart(runnable)) return true; + --runnable->ref; + Q_ASSERT(runnable->ref == 0); delete runnable; return false; } diff --git a/qtbase/src/corelib/thread/qtsan_impl.h b/qtbase/src/corelib/thread/qtsan_impl.h new file mode 100644 index 0000000000..580a738b91 --- /dev/null +++ b/qtbase/src/corelib/thread/qtsan_impl.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation. +** Copyright (C) 2022 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTSAN_IMPL_H +#define QTSAN_IMPL_H + +#include + +#if (__has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__)) && __has_include() +# define QT_BUILDING_UNDER_TSAN +# include +#endif + +QT_BEGIN_NAMESPACE + +namespace QtTsan { +#ifdef QT_BUILDING_UNDER_TSAN +inline void futexAcquire(void *addr, void *addr2 = nullptr) +{ + // A futex call ensures total ordering on the futex words + // (in either success or failure of the call). Instruct TSAN accordingly, + // as TSAN does not understand the futex(2) syscall (or equivalent). + ::__tsan_acquire(addr); + if (addr2) + ::__tsan_acquire(addr2); +} + +inline void futexRelease(void *addr, void *addr2 = nullptr) +{ + if (addr2) + ::__tsan_release(addr2); + ::__tsan_release(addr); +} + +inline void mutexPreLock(void *addr, unsigned flags) +{ + ::__tsan_mutex_pre_lock(addr, flags); +} + +inline void mutexPostLock(void *addr, unsigned flags, int recursion) +{ + ::__tsan_mutex_post_lock(addr, flags, recursion); +} + +inline void mutexPreUnlock(void *addr, unsigned flags) +{ + ::__tsan_mutex_pre_unlock(addr, flags); +} + +inline void mutexPostUnlock(void *addr, unsigned flags) +{ + ::__tsan_mutex_post_unlock(addr, flags); +} + +enum : unsigned { + MutexWriteReentrant = ::__tsan_mutex_write_reentrant, + TryLock = ::__tsan_mutex_try_lock, + TryLockFailed = ::__tsan_mutex_try_lock_failed, +}; +#else +inline void futexAcquire(void *, void * = nullptr) {} +inline void futexRelease(void *, void * = nullptr) {} + +enum : unsigned { + MutexWriteReentrant, + TryLock, + TryLockFailed, +}; +inline void mutexPreLock(void *, unsigned) {} +inline void mutexPostLock(void *, unsigned, int) {} +inline void mutexPreUnlock(void *, unsigned) {} +inline void mutexPostUnlock(void *, unsigned) {} +#endif // QT_BUILDING_UNDER_TSAN +} // namespace QtTsan + +QT_END_NAMESPACE + +#endif // QTSAN_IMPL_H diff --git a/qtbase/src/corelib/thread/qwaitcondition_unix.cpp b/qtbase/src/corelib/thread/qwaitcondition_unix.cpp index 88b058f410..0f1da4dc9b 100644 --- a/qtbase/src/corelib/thread/qwaitcondition_unix.cpp +++ b/qtbase/src/corelib/thread/qwaitcondition_unix.cpp @@ -213,7 +213,7 @@ bool QWaitCondition::wait(QMutex *mutex, QDeadlineTimer deadline) { if (! mutex) return false; - if (mutex->isRecursive()) { + if (static_cast(mutex)->isRecursive()) { qWarning("QWaitCondition: cannot wait on recursive mutexes"); return false; } diff --git a/qtbase/src/corelib/time/qtimezone.cpp b/qtbase/src/corelib/time/qtimezone.cpp index 0309e43e52..3d451696a1 100644 --- a/qtbase/src/corelib/time/qtimezone.cpp +++ b/qtbase/src/corelib/time/qtimezone.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 John Layt +** Copyright (C) 2020 John Layt ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -975,9 +975,15 @@ QList QTimeZone::windowsIdToIanaIds(const QByteArray &windowsId, } #ifndef QT_NO_DATASTREAM +// Invalid, as an IANA ID: too long, starts with - and has other invalid characters in it +static inline QString invalidId() { return QStringLiteral("-No Time Zone Specified!"); } + QDataStream &operator<<(QDataStream &ds, const QTimeZone &tz) { - tz.d->serialize(ds); + if (tz.isValid()) + tz.d->serialize(ds); + else + ds << invalidId(); return ds; } @@ -985,7 +991,9 @@ QDataStream &operator>>(QDataStream &ds, QTimeZone &tz) { QString ianaId; ds >> ianaId; - if (ianaId == QLatin1String("OffsetFromUtc")) { + if (ianaId == invalidId()) { + tz = QTimeZone(); + } else if (ianaId == QLatin1String("OffsetFromUtc")) { int utcOffset; QString name; QString abbreviation; diff --git a/qtbase/src/dbus/qdbusintegrator.cpp b/qtbase/src/dbus/qdbusintegrator.cpp index a4cbfecc98..9ccbbbb37d 100644 --- a/qtbase/src/dbus/qdbusintegrator.cpp +++ b/qtbase/src/dbus/qdbusintegrator.cpp @@ -1135,7 +1135,13 @@ void QDBusConnectionPrivate::closeConnection() } } - qDeleteAll(pendingCalls); + for (auto it = pendingCalls.begin(); it != pendingCalls.end(); ++it) { + auto call = *it; + if (!call->ref.deref()) { + delete call; + } + } + pendingCalls.clear(); // Disconnect all signals from signal hooks and from the object tree to // avoid QObject::destroyed being sent to dbus daemon thread which has diff --git a/qtbase/src/gui/configure.json b/qtbase/src/gui/configure.json index 1f08795c57..12c95742d2 100644 --- a/qtbase/src/gui/configure.json +++ b/qtbase/src/gui/configure.json @@ -834,7 +834,8 @@ "// embedded devices, are not intended to be used together with X. EGL support", "// has to be disabled in plugins like xcb in this case since the native display,", "// window and pixmap types will be different than what an X-based platform", - "// plugin would expect." + "// plugin would expect.", + "#define USE_X11" ], "include": [ "EGL/egl.h", "X11/Xlib.h" ], "main": [ diff --git a/qtbase/src/gui/image/qbmphandler.cpp b/qtbase/src/gui/image/qbmphandler.cpp index 96f1e8cb1d..0e73bbbdb0 100644 --- a/qtbase/src/gui/image/qbmphandler.cpp +++ b/qtbase/src/gui/image/qbmphandler.cpp @@ -150,16 +150,42 @@ static QDataStream &operator<<(QDataStream &s, const BMP_INFOHDR &bi) return s; } -static int calc_shift(uint mask) +static uint calc_shift(uint mask) { - int result = 0; - while (mask && !(mask & 1)) { + uint result = 0; + while ((mask >= 0x100) || (!(mask & 1) && mask)) { result++; mask >>= 1; } return result; } +static uint calc_scale(uint low_mask) +{ + uint result = 8; + while (low_mask && result) { + result--; + low_mask >>= 1; + } + return result; +} + +static inline uint apply_scale(uint value, uint scale) +{ + if (!(scale & 0x07)) // return immediately if scale == 8 or 0 + return value; + + uint filled = 8 - scale; + uint result = value << scale; + + do { + result |= result >> filled; + filled <<= 1; + } while (filled < 8); + + return result; +} + static bool read_dib_fileheader(QDataStream &s, BMP_FILEHDR &bf) { // read BMP file header @@ -222,14 +248,14 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, uint green_mask = 0; uint blue_mask = 0; uint alpha_mask = 0; - int red_shift = 0; - int green_shift = 0; - int blue_shift = 0; - int alpha_shift = 0; - int red_scale = 0; - int green_scale = 0; - int blue_scale = 0; - int alpha_scale = 0; + uint red_shift = 0; + uint green_shift = 0; + uint blue_shift = 0; + uint alpha_shift = 0; + uint red_scale = 0; + uint green_scale = 0; + uint blue_scale = 0; + uint alpha_scale = 0; if (!d->isSequential()) d->seek(startpos + BMP_FILEHDR_SIZE + bi.biSize); // goto start of colormap or masks @@ -308,19 +334,19 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, red_shift = calc_shift(red_mask); if (((red_mask >> red_shift) + 1) == 0) return false; - red_scale = 256 / ((red_mask >> red_shift) + 1); + red_scale = calc_scale(red_mask >> red_shift); green_shift = calc_shift(green_mask); if (((green_mask >> green_shift) + 1) == 0) return false; - green_scale = 256 / ((green_mask >> green_shift) + 1); + green_scale = calc_scale(green_mask >> green_shift); blue_shift = calc_shift(blue_mask); if (((blue_mask >> blue_shift) + 1) == 0) return false; - blue_scale = 256 / ((blue_mask >> blue_shift) + 1); + blue_scale = calc_scale(blue_mask >> blue_shift); alpha_shift = calc_shift(alpha_mask); if (((alpha_mask >> alpha_shift) + 1) == 0) return false; - alpha_scale = 256 / ((alpha_mask >> alpha_shift) + 1); + alpha_scale = calc_scale(alpha_mask >> alpha_shift); } else if (comp == BMP_RGB && (nbits == 24 || nbits == 32)) { blue_mask = 0x000000ff; green_mask = 0x0000ff00; @@ -328,17 +354,15 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, blue_shift = 0; green_shift = 8; red_shift = 16; - blue_scale = green_scale = red_scale = 1; + blue_scale = green_scale = red_scale = 0; } else if (comp == BMP_RGB && nbits == 16) { blue_mask = 0x001f; green_mask = 0x03e0; red_mask = 0x7c00; blue_shift = 0; - green_shift = 2; - red_shift = 7; - red_scale = 1; - green_scale = 1; - blue_scale = 8; + green_shift = 5; + red_shift = 10; + blue_scale = green_scale = red_scale = 3; } #if 0 @@ -544,10 +568,10 @@ static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, qint64 offset, c |= *(uchar*)(b+2)<<16; if (nbits > 24) c |= *(uchar*)(b+3)<<24; - *p++ = qRgba(((c & red_mask) >> red_shift) * red_scale, - ((c & green_mask) >> green_shift) * green_scale, - ((c & blue_mask) >> blue_shift) * blue_scale, - transp ? ((c & alpha_mask) >> alpha_shift) * alpha_scale : 0xff); + *p++ = qRgba(apply_scale((c & red_mask) >> red_shift, red_scale), + apply_scale((c & green_mask) >> green_shift, green_scale), + apply_scale((c & blue_mask) >> blue_shift, blue_scale), + transp ? apply_scale((c & alpha_mask) >> alpha_shift, alpha_scale) : 0xff); b += nbits/8; } } diff --git a/qtbase/src/gui/image/qimage.cpp b/qtbase/src/gui/image/qimage.cpp index 53bb3beb10..04e1a00429 100644 --- a/qtbase/src/gui/image/qimage.cpp +++ b/qtbase/src/gui/image/qimage.cpp @@ -4694,6 +4694,8 @@ QImage QImage::smoothScaled(int w, int h) const { static QImage rotated90(const QImage &image) { QImage out(image.height(), image.width(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); @@ -4722,6 +4724,8 @@ static QImage rotated180(const QImage &image) return image.mirrored(true, true); QImage out(image.width(), image.height(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); @@ -4734,6 +4738,8 @@ static QImage rotated180(const QImage &image) static QImage rotated270(const QImage &image) { QImage out(image.height(), image.width(), image.format()); + if (out.isNull()) + return out; copyMetadata(&out, image); if (image.colorCount() > 0) out.setColorTable(image.colorTable()); diff --git a/qtbase/src/gui/image/qimage_neon.cpp b/qtbase/src/gui/image/qimage_neon.cpp index 9dbcb11db5..c17f76f2b0 100644 --- a/qtbase/src/gui/image/qimage_neon.cpp +++ b/qtbase/src/gui/image/qimage_neon.cpp @@ -54,7 +54,7 @@ Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, cons // align dst on 128 bits const int offsetToAlignOn16Bytes = (reinterpret_cast(dst) >> 2) & 0x3; - for (int i = 0; i < offsetToAlignOn16Bytes; ++i) { + for (int i = 0; i < qMin(len, offsetToAlignOn16Bytes); ++i) { *dst++ = qRgb(src[0], src[1], src[2]); src += 3; } diff --git a/qtbase/src/gui/image/qimagereader.cpp b/qtbase/src/gui/image/qimagereader.cpp index 5cb7e1328e..1274622d53 100644 --- a/qtbase/src/gui/image/qimagereader.cpp +++ b/qtbase/src/gui/image/qimagereader.cpp @@ -515,9 +515,9 @@ QImageReaderPrivate::QImageReaderPrivate(QImageReader *qq) */ QImageReaderPrivate::~QImageReaderPrivate() { + delete handler; if (deleteDevice) delete device; - delete handler; } /*! @@ -774,12 +774,12 @@ bool QImageReader::decideFormatFromContent() const */ void QImageReader::setDevice(QIODevice *device) { + delete d->handler; + d->handler = nullptr; if (d->device && d->deleteDevice) delete d->device; d->device = device; d->deleteDevice = false; - delete d->handler; - d->handler = nullptr; d->text.clear(); } diff --git a/qtbase/src/gui/image/qimagewriter.cpp b/qtbase/src/gui/image/qimagewriter.cpp index 33f5e491c7..a679f25757 100644 --- a/qtbase/src/gui/image/qimagewriter.cpp +++ b/qtbase/src/gui/image/qimagewriter.cpp @@ -349,9 +349,9 @@ QImageWriter::QImageWriter(const QString &fileName, const QByteArray &format) */ QImageWriter::~QImageWriter() { + delete d->handler; if (d->deleteDevice) delete d->device; - delete d->handler; delete d; } @@ -396,13 +396,13 @@ QByteArray QImageWriter::format() const */ void QImageWriter::setDevice(QIODevice *device) { + delete d->handler; + d->handler = nullptr; if (d->device && d->deleteDevice) delete d->device; d->device = device; d->deleteDevice = false; - delete d->handler; - d->handler = nullptr; } /*! diff --git a/qtbase/src/gui/kernel/qhighdpiscaling.cpp b/qtbase/src/gui/kernel/qhighdpiscaling.cpp index 85ff58c14c..a433e94c22 100644 --- a/qtbase/src/gui/kernel/qhighdpiscaling.cpp +++ b/qtbase/src/gui/kernel/qhighdpiscaling.cpp @@ -580,9 +580,8 @@ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor) else qNamedScreenScaleFactors()->insert(name, factor); - // hack to force re-evaluation of screen geometry if (screen->handle()) - screen->d_func()->setPlatformScreen(screen->handle()); // updates geometries based on scale factor + screen->d_func()->updateLogicalDpi(); } QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen) diff --git a/qtbase/src/gui/kernel/qkeysequence.cpp b/qtbase/src/gui/kernel/qkeysequence.cpp index 56cd2d02bc..d448429f6c 100644 --- a/qtbase/src/gui/kernel/qkeysequence.cpp +++ b/qtbase/src/gui/kernel/qkeysequence.cpp @@ -701,6 +701,10 @@ static const struct { { Qt::Key_TouchpadToggle, QT_TRANSLATE_NOOP("QShortcut", "Touchpad Toggle") }, { Qt::Key_TouchpadOn, QT_TRANSLATE_NOOP("QShortcut", "Touchpad On") }, { Qt::Key_TouchpadOff, QT_TRANSLATE_NOOP("QShortcut", "Touchpad Off") }, + { Qt::Key_Shift, QT_TRANSLATE_NOOP("QShortcut", "Shift") }, + { Qt::Key_Control, QT_TRANSLATE_NOOP("QShortcut", "Control") }, + { Qt::Key_Alt, QT_TRANSLATE_NOOP("QShortcut", "Alt") }, + { Qt::Key_Meta, QT_TRANSLATE_NOOP("QShortcut", "Meta") }, }; static Q_CONSTEXPR int numKeyNames = sizeof keyname / sizeof *keyname; diff --git a/qtbase/src/gui/kernel/qplatformservices.cpp b/qtbase/src/gui/kernel/qplatformservices.cpp index fdc6a6c4aa..ac47f98c5d 100644 --- a/qtbase/src/gui/kernel/qplatformservices.cpp +++ b/qtbase/src/gui/kernel/qplatformservices.cpp @@ -55,6 +55,19 @@ QT_BEGIN_NAMESPACE \brief The QPlatformServices provides the backend for desktop-related functionality. */ +/*! + \enum QPlatformServices::Capability + + Capabilities are used to determine a specific platform service's availability. + + \value ColorPickingFromScreen The platform natively supports color picking from screen. + This capability indicates that the platform supports "opaque" color picking, where the + platform implements a complete user experience for color picking and outputs a color. + This is in contrast to the application implementing the color picking user experience + (taking care of showing a cross hair, instructing the platform integration to obtain + the color at a given pixel, etc.). The related service function is pickColor(). + */ + QPlatformServices::QPlatformServices() { } @@ -85,5 +98,16 @@ QByteArray QPlatformServices::desktopEnvironment() const return QByteArray("UNKNOWN"); } +QPlatformServiceColorPicker *QPlatformServices::colorPicker(QWindow *parent) +{ + Q_UNUSED(parent); + return nullptr; +} + +bool QPlatformServices::hasCapability(Capability capability) const +{ + Q_UNUSED(capability) + return false; +} QT_END_NAMESPACE diff --git a/qtbase/src/gui/kernel/qplatformservices.h b/qtbase/src/gui/kernel/qplatformservices.h index 5de96cfa7d..a8b2a4ce71 100644 --- a/qtbase/src/gui/kernel/qplatformservices.h +++ b/qtbase/src/gui/kernel/qplatformservices.h @@ -50,16 +50,32 @@ // #include +#include QT_BEGIN_NAMESPACE class QUrl; +class QWindow; + +class Q_GUI_EXPORT QPlatformServiceColorPicker : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + virtual void pickColor() = 0; +Q_SIGNALS: + void colorPicked(const QColor &color); +}; class Q_GUI_EXPORT QPlatformServices { public: Q_DISABLE_COPY_MOVE(QPlatformServices) + enum Capability { + ColorPicking, + }; + QPlatformServices(); virtual ~QPlatformServices() { } @@ -67,6 +83,10 @@ public: virtual bool openDocument(const QUrl &url); virtual QByteArray desktopEnvironment() const; + + virtual bool hasCapability(Capability capability) const; + + virtual QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr); }; QT_END_NAMESPACE diff --git a/qtbase/src/gui/kernel/qplatformtheme.cpp b/qtbase/src/gui/kernel/qplatformtheme.cpp index 71521c0339..2325873245 100644 --- a/qtbase/src/gui/kernel/qplatformtheme.cpp +++ b/qtbase/src/gui/kernel/qplatformtheme.cpp @@ -163,6 +163,8 @@ QT_BEGIN_NAMESPACE \value ShowShortcutsInContextMenus (bool) Whether to display shortcut key sequences in context menus. + \value ButtonPressKeys (QList) A list of keys that can be used to press buttons via keyboard input. + \sa themeHint(), QStyle::pixelMetric() */ @@ -563,6 +565,8 @@ QVariant QPlatformTheme::defaultThemeHint(ThemeHint hint) } case MouseQuickSelectionThreshold: return QVariant(10); + case ButtonPressKeys: + return QVariant::fromValue(QList({ Qt::Key_Space, Qt::Key_Select })); } return QVariant(); } diff --git a/qtbase/src/gui/kernel/qplatformtheme.h b/qtbase/src/gui/kernel/qplatformtheme.h index 3185fc4541..7e6c9d5740 100644 --- a/qtbase/src/gui/kernel/qplatformtheme.h +++ b/qtbase/src/gui/kernel/qplatformtheme.h @@ -120,7 +120,8 @@ public: TouchDoubleTapDistance, ShowShortcutsInContextMenus, IconFallbackSearchPaths, - MouseQuickSelectionThreshold + MouseQuickSelectionThreshold, + ButtonPressKeys }; enum DialogType { diff --git a/qtbase/src/gui/kernel/qscreen.cpp b/qtbase/src/gui/kernel/qscreen.cpp index 990272b0c2..d371dd60ab 100644 --- a/qtbase/src/gui/kernel/qscreen.cpp +++ b/qtbase/src/gui/kernel/qscreen.cpp @@ -77,6 +77,12 @@ QScreen::QScreen(QPlatformScreen *screen) d->setPlatformScreen(screen); } +void QScreenPrivate::updateLogicalDpi() +{ + logicalDpi = QPlatformScreen::overrideDpi(platformScreen->logicalDpi()); + updateGeometriesWithSignals(); // updates geometries based on scale factor +} + void QScreenPrivate::updateGeometriesWithSignals() { const QRect oldGeometry = geometry; diff --git a/qtbase/src/gui/kernel/qscreen_p.h b/qtbase/src/gui/kernel/qscreen_p.h index 7da542c25e..e50fc3190b 100644 --- a/qtbase/src/gui/kernel/qscreen_p.h +++ b/qtbase/src/gui/kernel/qscreen_p.h @@ -70,6 +70,7 @@ public: geometry = platformScreen->deviceIndependentGeometry(); availableGeometry = QHighDpi::fromNative(platformScreen->availableGeometry(), QHighDpiScaling::factor(platformScreen), geometry.topLeft()); } + void updateLogicalDpi(); void updatePrimaryOrientation(); void updateGeometriesWithSignals(); diff --git a/qtbase/src/gui/kernel/qshapedpixmapdndwindow.cpp b/qtbase/src/gui/kernel/qshapedpixmapdndwindow.cpp index bb0d8e4ee7..b98fcc61e7 100644 --- a/qtbase/src/gui/kernel/qshapedpixmapdndwindow.cpp +++ b/qtbase/src/gui/kernel/qshapedpixmapdndwindow.cpp @@ -56,7 +56,7 @@ QShapedPixmapWindow::QShapedPixmapWindow(QScreen *screen) QSurfaceFormat format; format.setAlphaBufferSize(8); setFormat(format); - setFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint + setFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus); } diff --git a/qtbase/src/gui/kernel/qwindow.cpp b/qtbase/src/gui/kernel/qwindow.cpp index 639817257e..c2f7dc6776 100644 --- a/qtbase/src/gui/kernel/qwindow.cpp +++ b/qtbase/src/gui/kernel/qwindow.cpp @@ -644,7 +644,7 @@ bool QWindow::isVisible() const into an actual native surface. However, the window remains hidden until setVisible() is called. Note that it is not usually necessary to call this function directly, as it will be implicitly - called by show(), setVisible(), and other functions that require access to the platform + called by show(), setVisible(), winId(), and other functions that require access to the platform resources. Call destroy() to free the platform resources if necessary. @@ -660,6 +660,9 @@ void QWindow::create() /*! Returns the window's platform id. + \note This function will cause the platform window to be created if it is not already. + Returns 0, if the platform window creation failed. + For platforms where this id might be useful, the value returned will uniquely represent the window inside the corresponding screen. @@ -672,6 +675,9 @@ WId QWindow::winId() const if(!d->platformWindow) const_cast(this)->create(); + if (!d->platformWindow) + return 0; + return d->platformWindow->winId(); } diff --git a/qtbase/src/gui/opengl/qopengltexture.cpp b/qtbase/src/gui/opengl/qopengltexture.cpp index 5490ad8025..afd3d8dbe6 100644 --- a/qtbase/src/gui/opengl/qopengltexture.cpp +++ b/qtbase/src/gui/opengl/qopengltexture.cpp @@ -3725,6 +3725,12 @@ void QOpenGLTexture::setData(const QImage& image, MipMapGeneration genMipMaps) return; } + QImage glImage = image.convertToFormat(QImage::Format_RGBA8888); + if (glImage.isNull()) { + qWarning("QOpenGLTexture::setData() failed to convert image"); + return; + } + if (context->isOpenGLES() && context->format().majorVersion() < 3) setFormat(QOpenGLTexture::RGBAFormat); else @@ -3735,7 +3741,6 @@ void QOpenGLTexture::setData(const QImage& image, MipMapGeneration genMipMaps) allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); // Upload pixel data and generate mipmaps - QImage glImage = image.convertToFormat(QImage::Format_RGBA8888); QOpenGLPixelTransferOptions uploadOptions; uploadOptions.setAlignment(1); setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, glImage.constBits(), &uploadOptions); diff --git a/qtbase/src/gui/painting/qcolortrclut_p.h b/qtbase/src/gui/painting/qcolortrclut_p.h index 76a6a60803..24fd522e6c 100644 --- a/qtbase/src/gui/painting/qcolortrclut_p.h +++ b/qtbase/src/gui/painting/qcolortrclut_p.h @@ -118,6 +118,7 @@ public: return QRgba64::fromRgba64(r, g, b, qAlpha(rgb32) * 257); #endif } + QRgba64 toLinear64(QRgba64) const = delete; QRgb toLinear(QRgb rgb32) const { diff --git a/qtbase/src/gui/painting/qdrawhelper.cpp b/qtbase/src/gui/painting/qdrawhelper.cpp index a61793508a..5ba2d277b7 100644 --- a/qtbase/src/gui/painting/qdrawhelper.cpp +++ b/qtbase/src/gui/painting/qdrawhelper.cpp @@ -6091,7 +6091,7 @@ static inline void alphargbblend_argb32(quint32 *dst, uint coverage, const QRgba static inline void rgbBlendPixel(QRgba64 &dst, int coverage, QRgba64 slinear, const QColorTrcLut *colorProfile) { // Do a gammacorrected RGB alphablend... - const QRgba64 dlinear = colorProfile ? colorProfile->toLinear64(dst) : dst; + const QRgba64 dlinear = colorProfile ? colorProfile->toLinear(dst) : dst; QRgba64 blend = rgbBlend(dlinear, slinear, coverage); diff --git a/qtbase/src/gui/painting/qfixed_p.h b/qtbase/src/gui/painting/qfixed_p.h index 846592881c..57d750a4b3 100644 --- a/qtbase/src/gui/painting/qfixed_p.h +++ b/qtbase/src/gui/painting/qfixed_p.h @@ -54,6 +54,7 @@ #include #include "QtCore/qdebug.h" #include "QtCore/qpoint.h" +#include #include "QtCore/qsize.h" QT_BEGIN_NAMESPACE @@ -182,6 +183,14 @@ Q_DECL_CONSTEXPR inline bool operator<(int i, const QFixed &f) { return i * 64 < Q_DECL_CONSTEXPR inline bool operator>(const QFixed &f, int i) { return f.value() > i * 64; } Q_DECL_CONSTEXPR inline bool operator>(int i, const QFixed &f) { return i * 64 > f.value(); } +inline bool qAddOverflow(QFixed v1, QFixed v2, QFixed *r) +{ + int val; + bool result = add_overflow(v1.value(), v2.value(), &val); + r->setValue(val); + return result; +} + #ifndef QT_NO_DEBUG_STREAM inline QDebug &operator<<(QDebug &dbg, const QFixed &f) { return dbg << f.toReal(); } diff --git a/qtbase/src/gui/painting/qpainterpath.cpp b/qtbase/src/gui/painting/qpainterpath.cpp index f9544a3241..d80fafeaf1 100644 --- a/qtbase/src/gui/painting/qpainterpath.cpp +++ b/qtbase/src/gui/painting/qpainterpath.cpp @@ -1253,7 +1253,7 @@ void QPainterPath::addText(const QPointF &point, const QFont &f, const QString & if (si.analysis.flags < QScriptAnalysis::TabOrObject) { QGlyphLayout glyphs = eng->shapedGlyphs(&si); - QFontEngine *fe = f.d->engineForScript(si.analysis.script); + QFontEngine *fe = eng->fontEngine(si); Q_ASSERT(fe); fe->addOutlineToPath(x, y, glyphs, this, si.analysis.bidiLevel % 2 diff --git a/qtbase/src/gui/painting/qpdf.cpp b/qtbase/src/gui/painting/qpdf.cpp index 3066744f1b..2c8d3c3b53 100644 --- a/qtbase/src/gui/painting/qpdf.cpp +++ b/qtbase/src/gui/painting/qpdf.cpp @@ -2760,6 +2760,8 @@ int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, return gradientBrush(brush, matrix, gStateObject); } + matrix = brush.transform() * matrix; + if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0) *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity), qRound(pen.color().alpha() * opacity)); diff --git a/qtbase/src/gui/rhi/qshader_p_p.h b/qtbase/src/gui/rhi/qshader_p_p.h index ec9d25971f..4a5a7a6d51 100644 --- a/qtbase/src/gui/rhi/qshader_p_p.h +++ b/qtbase/src/gui/rhi/qshader_p_p.h @@ -68,13 +68,13 @@ struct Q_GUI_EXPORT QShaderPrivate { } - QShaderPrivate(const QShaderPrivate *other) + QShaderPrivate(const QShaderPrivate &other) : ref(1), - qsbVersion(other->qsbVersion), - stage(other->stage), - desc(other->desc), - shaders(other->shaders), - bindings(other->bindings) + qsbVersion(other.qsbVersion), + stage(other.stage), + desc(other.desc), + shaders(other.shaders), + bindings(other.bindings) { } diff --git a/qtbase/src/gui/rhi/qshaderdescription_p_p.h b/qtbase/src/gui/rhi/qshaderdescription_p_p.h index ec2b0b6b4c..3da33a8a2b 100644 --- a/qtbase/src/gui/rhi/qshaderdescription_p_p.h +++ b/qtbase/src/gui/rhi/qshaderdescription_p_p.h @@ -63,16 +63,16 @@ struct Q_GUI_EXPORT QShaderDescriptionPrivate localSize[0] = localSize[1] = localSize[2] = 0; } - QShaderDescriptionPrivate(const QShaderDescriptionPrivate *other) + QShaderDescriptionPrivate(const QShaderDescriptionPrivate &other) : ref(1), - inVars(other->inVars), - outVars(other->outVars), - uniformBlocks(other->uniformBlocks), - pushConstantBlocks(other->pushConstantBlocks), - storageBlocks(other->storageBlocks), - combinedImageSamplers(other->combinedImageSamplers), - storageImages(other->storageImages), - localSize(other->localSize) + inVars(other.inVars), + outVars(other.outVars), + uniformBlocks(other.uniformBlocks), + pushConstantBlocks(other.pushConstantBlocks), + storageBlocks(other.storageBlocks), + combinedImageSamplers(other.combinedImageSamplers), + storageImages(other.storageImages), + localSize(other.localSize) { } diff --git a/qtbase/src/gui/text/qfontdatabase.cpp b/qtbase/src/gui/text/qfontdatabase.cpp index 2011f935a9..7aa6228948 100644 --- a/qtbase/src/gui/text/qfontdatabase.cpp +++ b/qtbase/src/gui/text/qfontdatabase.cpp @@ -983,7 +983,7 @@ QFontEngine *loadSingleEngine(int script, if (style->key.stretch != 0 && request.stretch != 0 && (request.styleName.isEmpty() || request.styleName != style->styleName)) { def.stretch = (request.stretch * 100 + style->key.stretch / 2) / style->key.stretch; - } else { + } else if (request.stretch == QFont::AnyStretch) { def.stretch = 100; } diff --git a/qtbase/src/gui/text/qtextengine.cpp b/qtbase/src/gui/text/qtextengine.cpp index 6336fadf74..a6c66e5d2d 100644 --- a/qtbase/src/gui/text/qtextengine.cpp +++ b/qtbase/src/gui/text/qtextengine.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. diff --git a/qtbase/src/gui/text/qtextlayout.cpp b/qtbase/src/gui/text/qtextlayout.cpp index 30f07ba69b..f60597a076 100644 --- a/qtbase/src/gui/text/qtextlayout.cpp +++ b/qtbase/src/gui/text/qtextlayout.cpp @@ -1336,13 +1336,16 @@ void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition bool rightToLeft = d->isRightToLeft(); if (itm >= 0) { const QScriptItem &si = d->layoutData->items.at(itm); - if (si.ascent >= 0) - base = si.ascent; - if (si.descent >= 0) - descent = si.descent; + // objects need some special treatment as they can have special alignment or be floating + if (si.analysis.flags != QScriptAnalysis::Object) { + if (si.ascent > 0) + base = si.ascent; + if (si.descent > 0) + descent = si.descent; + } rightToLeft = si.analysis.bidiLevel % 2; } - qreal y = position.y() + (sl.y + sl.base() + sl.descent - base - descent).toReal(); + qreal y = position.y() + (sl.y + sl.base() - base).toReal(); bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing) && (p->transform().type() > QTransform::TxTranslate); if (toggleAntialiasing) @@ -2163,11 +2166,14 @@ found: eng->maxWidth = qMax(eng->maxWidth, line.textWidth); } else { eng->minWidth = qMax(eng->minWidth, lbh.minw); - eng->maxWidth += line.textWidth; + if (qAddOverflow(eng->maxWidth, line.textWidth, &eng->maxWidth)) + eng->maxWidth = QFIXED_MAX; } - if (line.textWidth > 0 && item < eng->layoutData->items.size()) - eng->maxWidth += lbh.spaceData.textWidth; + if (line.textWidth > 0 && item < eng->layoutData->items.size()) { + if (qAddOverflow(eng->maxWidth, lbh.spaceData.textWidth, &eng->maxWidth)) + eng->maxWidth = QFIXED_MAX; + } line.textWidth += trailingSpace; if (lbh.spaceData.length) { diff --git a/qtbase/src/gui/util/qshadergenerator.cpp b/qtbase/src/gui/util/qshadergenerator.cpp index 1ec25ccd7b..20ed6abc3a 100644 --- a/qtbase/src/gui/util/qshadergenerator.cpp +++ b/qtbase/src/gui/util/qshadergenerator.cpp @@ -492,7 +492,7 @@ QByteArray QShaderGenerator::createShaderCode(const QStringList &enabledLayers) int end = begin + 1; char endChar = line.at(end); const int size = line.size(); - while (end < size && (std::isalnum(endChar) || endChar == '_')) { + while (end < size && (std::isalnum(uchar(endChar)) || endChar == '_')) { ++end; endChar = line.at(end); } diff --git a/qtbase/src/gui/util/qshaderlanguage.cpp b/qtbase/src/gui/util/qshaderlanguage.cpp index efd607ba60..9399d6efcc 100644 --- a/qtbase/src/gui/util/qshaderlanguage.cpp +++ b/qtbase/src/gui/util/qshaderlanguage.cpp @@ -52,3 +52,5 @@ void qt_register_ShaderLanguage_enums() } QT_END_NAMESPACE + +#include "moc_qshaderlanguage_p.cpp" diff --git a/qtbase/src/network/access/qhsts.cpp b/qtbase/src/network/access/qhsts.cpp index 0cef0ad3dc..be7ef7ff58 100644 --- a/qtbase/src/network/access/qhsts.cpp +++ b/qtbase/src/network/access/qhsts.cpp @@ -364,8 +364,8 @@ quoted-pair = "\" CHAR bool QHstsHeaderParser::parse(const QList> &headers) { for (const auto &h : headers) { - // We use '==' since header name was already 'trimmed' for us: - if (h.first == "Strict-Transport-Security") { + // We compare directly because header name was already 'trimmed' for us: + if (h.first.compare("Strict-Transport-Security", Qt::CaseInsensitive) == 0) { header = h.second; // RFC6797, 8.1: // diff --git a/qtbase/src/network/access/qnetworkreply.cpp b/qtbase/src/network/access/qnetworkreply.cpp index 30e4e290ea..dadea58080 100644 --- a/qtbase/src/network/access/qnetworkreply.cpp +++ b/qtbase/src/network/access/qnetworkreply.cpp @@ -76,7 +76,7 @@ QNetworkReplyPrivate::QNetworkReplyPrivate() itself. QNetworkReply is a sequential-access QIODevice, which means that - once data is read from the object, it no longer kept by the + once data is read from the object, it is no longer kept by the device. It is therefore the application's responsibility to keep this data if it needs to. Whenever more data is received from the network and processed, the readyRead() signal is emitted. @@ -344,7 +344,7 @@ QNetworkReplyPrivate::QNetworkReplyPrivate() processing. After this signal is emitted, there will be no more updates to the reply's data or metadata. - Unless close() or abort() have been called, the reply will be still be opened + Unless close() or abort() have been called, the reply will still be opened for reading, so the data can be retrieved by calls to read() or readAll(). In particular, if no calls to read() were made as a result of readyRead(), a call to readAll() will retrieve the full diff --git a/qtbase/src/network/access/qnetworkreplyfileimpl_p.h b/qtbase/src/network/access/qnetworkreplyfileimpl_p.h index 48d82abd3f..4bfbc3f7d1 100644 --- a/qtbase/src/network/access/qnetworkreplyfileimpl_p.h +++ b/qtbase/src/network/access/qnetworkreplyfileimpl_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QNETWORKREPLYFILEIMPL_H -#define QNETWORKREPLYFILEIMPL_H +#ifndef QNETWORKREPLYFILEIMPL_P_H +#define QNETWORKREPLYFILEIMPL_P_H // // W A R N I N G @@ -99,4 +99,4 @@ QT_END_NAMESPACE Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders) -#endif // QNETWORKREPLYFILEIMPL_H +#endif // QNETWORKREPLYFILEIMPL_P_H diff --git a/qtbase/src/network/configure.json b/qtbase/src/network/configure.json index 271ff164ac..ffba2d1eea 100644 --- a/qtbase/src/network/configure.json +++ b/qtbase/src/network/configure.json @@ -53,7 +53,7 @@ }, "headers": "proxy.h", "sources": [ - "-lproxy" + { "type": "pkgConfig", "args": "libproxy-1.0" } ] }, "openssl_headers": { diff --git a/qtbase/src/network/kernel/qdnslookup_unix.cpp b/qtbase/src/network/kernel/qdnslookup_unix.cpp index 12b40fc35d..99e999d436 100644 --- a/qtbase/src/network/kernel/qdnslookup_unix.cpp +++ b/qtbase/src/network/kernel/qdnslookup_unix.cpp @@ -227,7 +227,6 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN // responseLength in case of error, we still can extract the // exact error code from the response. HEADER *header = (HEADER*)response; - const int answerCount = ntohs(header->ancount); switch (header->rcode) { case NOERROR: break; @@ -260,18 +259,31 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN return; } - // Skip the query host, type (2 bytes) and class (2 bytes). char host[PACKETSZ], answer[PACKETSZ]; unsigned char *p = response + sizeof(HEADER); - int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); - if (status < 0) { + int status; + + if (ntohs(header->qdcount) == 1) { + // Skip the query host, type (2 bytes) and class (2 bytes). + status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Could not expand domain name"); + return; + } + if ((p - response) + status + 4 >= responseLength) + header->qdcount = 0xffff; // invalid reply below + else + p += status + 4; + } + if (ntohs(header->qdcount) > 1) { reply->error = QDnsLookup::InvalidReplyError; - reply->errorString = tr("Could not expand domain name"); + reply->errorString = tr("Invalid reply received"); return; } - p += status + 4; // Extract results. + const int answerCount = ntohs(header->ancount); int answerIndex = 0; while ((p < response + responseLength) && (answerIndex < answerCount)) { status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); @@ -283,6 +295,11 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN const QString name = QUrl::fromAce(host); p += status; + + if ((p - response) + 10 > responseLength) { + // probably just a truncated reply, return what we have + return; + } const quint16 type = (p[0] << 8) | p[1]; p += 2; // RR type p += 2; // RR class @@ -290,6 +307,8 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN p += 4; const quint16 size = (p[0] << 8) | p[1]; p += 2; + if ((p - response) + size > responseLength) + return; // truncated if (type == QDnsLookup::A) { if (size != 4) { diff --git a/qtbase/src/network/kernel/qhostinfo.cpp b/qtbase/src/network/kernel/qhostinfo.cpp index 33fb629899..015963686c 100644 --- a/qtbase/src/network/kernel/qhostinfo.cpp +++ b/qtbase/src/network/kernel/qhostinfo.cpp @@ -181,7 +181,6 @@ bool QHostInfoResult::event(QEvent *event) // we didn't have a context object, or it's still alive if (!withContextObject || receiver) slotObj->call(const_cast(receiver.data()), args); - slotObj->destroyIfLastRef(); deleteLater(); return true; @@ -801,6 +800,8 @@ int QHostInfoPrivate::lookupHostImpl(const QString &name, if (!QAbstractEventDispatcher::instance(QThread::currentThread())) { qWarning("QHostInfo::lookupHost() called with no event dispatcher"); + if (slotObj) + slotObj->destroyIfLastRef(); return -1; } @@ -847,6 +848,8 @@ int QHostInfoPrivate::lookupHostImpl(const QString &name, QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)), receiver, member, Qt::QueuedConnection); manager->scheduleLookup(runnable); + } else if (slotObj) { + slotObj->destroyIfLastRef(); } return id; } diff --git a/qtbase/src/network/kernel/qhostinfo_p.h b/qtbase/src/network/kernel/qhostinfo_p.h index d7875a0673..452c095b6a 100644 --- a/qtbase/src/network/kernel/qhostinfo_p.h +++ b/qtbase/src/network/kernel/qhostinfo_p.h @@ -90,6 +90,12 @@ public: moveToThread(receiver->thread()); } + ~QHostInfoResult() + { + if (slotObj) + slotObj->destroyIfLastRef(); + } + void postResultsReady(const QHostInfo &info); Q_SIGNALS: @@ -103,6 +109,8 @@ private: : receiver(other->receiver), slotObj(other->slotObj), withContextObject(other->withContextObject) { + if (slotObj) + slotObj->ref(); // cleanup if the application terminates before results are delivered connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); diff --git a/qtbase/src/network/ssl/qsslconfiguration.cpp b/qtbase/src/network/ssl/qsslconfiguration.cpp index f5ce02807f..84a9187334 100644 --- a/qtbase/src/network/ssl/qsslconfiguration.cpp +++ b/qtbase/src/network/ssl/qsslconfiguration.cpp @@ -929,7 +929,11 @@ void QSslConfiguration::setPreSharedKeyIdentityHint(const QByteArray &hint) Retrieves the current set of Diffie-Hellman parameters. If no Diffie-Hellman parameters have been set, the QSslConfiguration object - defaults to using the 1024-bit MODP group from RFC 2409. + defaults to using the 2048-bit MODP group from RFC 3526. + + \note The default parameters may change in future Qt versions. + Please check the documentation of the \e{exact Qt version} that you + are using in order to know what defaults that version uses. */ QSslDiffieHellmanParameters QSslConfiguration::diffieHellmanParameters() const { @@ -943,7 +947,11 @@ QSslDiffieHellmanParameters QSslConfiguration::diffieHellmanParameters() const a server to \a dhparams. If no Diffie-Hellman parameters have been set, the QSslConfiguration object - defaults to using the 1024-bit MODP group from RFC 2409. + defaults to using the 2048-bit MODP group from RFC 3526. + + \note The default parameters may change in future Qt versions. + Please check the documentation of the \e{exact Qt version} that you + are using in order to know what defaults that version uses. */ void QSslConfiguration::setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams) { diff --git a/qtbase/src/network/ssl/qsslcontext_openssl.cpp b/qtbase/src/network/ssl/qsslcontext_openssl.cpp index c30192a4eb..e4bb61ecb5 100644 --- a/qtbase/src/network/ssl/qsslcontext_openssl.cpp +++ b/qtbase/src/network/ssl/qsslcontext_openssl.cpp @@ -409,7 +409,7 @@ init_context: break; case QSsl::DtlsV1_0OrLater: minVersion = DTLS1_VERSION; - maxVersion = DTLS_MAX_VERSION; + maxVersion = 0; break; case QSsl::DtlsV1_2: minVersion = DTLS1_2_VERSION; @@ -417,7 +417,7 @@ init_context: break; case QSsl::DtlsV1_2OrLater: minVersion = DTLS1_2_VERSION; - maxVersion = DTLS_MAX_VERSION; + maxVersion = 0; break; case QSsl::TlsV1_3OrLater: #ifdef TLS1_3_VERSION diff --git a/qtbase/src/network/ssl/qssldiffiehellmanparameters.cpp b/qtbase/src/network/ssl/qssldiffiehellmanparameters.cpp index 7807afaa30..7c2505a0be 100644 --- a/qtbase/src/network/ssl/qssldiffiehellmanparameters.cpp +++ b/qtbase/src/network/ssl/qssldiffiehellmanparameters.cpp @@ -68,17 +68,18 @@ QT_BEGIN_NAMESPACE -// The 1024-bit MODP group from RFC 2459 (Second Oakley Group) +// The 2048-bit MODP group from RFC 3526 Q_AUTOTEST_EXPORT const char *qssl_dhparams_default_base64 = - "MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR" - "Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL" - "/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC"; + "MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO" + "NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr" + "+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc" + "YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei" + "j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg=="; /*! Returns the default QSslDiffieHellmanParameters used by QSslSocket. - This is currently the 1024-bit MODP group from RFC 2459, also - known as the Second Oakley Group. + This is currently the 2048-bit MODP group from RFC 3526. */ QSslDiffieHellmanParameters QSslDiffieHellmanParameters::defaultParameters() { diff --git a/qtbase/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp b/qtbase/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp index aaf8741130..93ad7fa728 100644 --- a/qtbase/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp +++ b/qtbase/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp @@ -59,57 +59,6 @@ QT_BEGIN_NAMESPACE -#ifdef OPENSSL_NO_DEPRECATED_3_0 - -static int q_DH_check(DH *dh, int *status) -{ - // DH_check was first deprecated in OpenSSL 3.0.0, as low-level - // API; the EVP_PKEY family of functions was advised as an alternative. - // As of now EVP_PKEY_params_check ends up calling ... DH_check, - // which is good enough. - - Q_ASSERT(dh); - Q_ASSERT(status); - - EVP_PKEY *key = q_EVP_PKEY_new(); - if (!key) { - qCWarning(lcSsl, "EVP_PKEY_new failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); - return 0; - } - const auto keyDeleter = qScopeGuard([key](){ - q_EVP_PKEY_free(key); - }); - if (!q_EVP_PKEY_set1_DH(key, dh)) { - qCWarning(lcSsl, "EVP_PKEY_set1_DH failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); - return 0; - } - - EVP_PKEY_CTX *keyCtx = q_EVP_PKEY_CTX_new(key, nullptr); - if (!keyCtx) { - qCWarning(lcSsl, "EVP_PKEY_CTX_new failed"); - QSslSocketBackendPrivate::logAndClearErrorQueue(); - return 0; - } - const auto ctxDeleter = qScopeGuard([keyCtx]{ - q_EVP_PKEY_CTX_free(keyCtx); - }); - - const int result = q_EVP_PKEY_param_check(keyCtx); - QSslSocketBackendPrivate::logAndClearErrorQueue(); - // Note: unlike DH_check, we cannot obtain the 'status', - // if the 'result' is 0 (actually the result is 1 only - // if this 'status' was 0). We could probably check the - // errors from the error queue, but it's not needed anyway - // - see the 'isSafeDH' below, how it returns immediately - // on 0. - Q_UNUSED(status) - - return result; -} -#endif // OPENSSL_NO_DEPRECATED_3_0 - static bool isSafeDH(DH *dh) { int status = 0; @@ -206,6 +155,7 @@ void QSslDiffieHellmanParametersPrivate::decodePem(const QByteArray &pem) if (isSafeDH(dh)) { char *buf = nullptr; int len = q_i2d_DHparams(dh, reinterpret_cast(&buf)); + auto freeBuf = qScopeGuard([&] { q_OPENSSL_free(buf); }); if (len > 0) derData = QByteArray(buf, len); else diff --git a/qtbase/src/network/ssl/qsslsocket.cpp b/qtbase/src/network/ssl/qsslsocket.cpp index 5bb6e7ee4a..2a0b3a4f1d 100644 --- a/qtbase/src/network/ssl/qsslsocket.cpp +++ b/qtbase/src/network/ssl/qsslsocket.cpp @@ -2221,6 +2221,10 @@ QSslSocketPrivate::QSslSocketPrivate() , flushTriggered(false) { QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration); + // If the global configuration doesn't allow root certificates to be loaded + // on demand then we have to disable it for this socket as well. + if (!configuration.allowRootCertOnDemandLoading) + allowRootCertOnDemandLoading = false; } /*! @@ -2470,6 +2474,7 @@ void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPri ptr->sessionProtocol = global->sessionProtocol; ptr->ciphers = global->ciphers; ptr->caCertificates = global->caCertificates; + ptr->allowRootCertOnDemandLoading = global->allowRootCertOnDemandLoading; ptr->protocol = global->protocol; ptr->peerVerifyMode = global->peerVerifyMode; ptr->peerVerifyDepth = global->peerVerifyDepth; diff --git a/qtbase/src/network/ssl/qsslsocket_mac.cpp b/qtbase/src/network/ssl/qsslsocket_mac.cpp index 77e847e972..e38a5e75de 100644 --- a/qtbase/src/network/ssl/qsslsocket_mac.cpp +++ b/qtbase/src/network/ssl/qsslsocket_mac.cpp @@ -468,6 +468,7 @@ void QSslSocketBackendPrivate::disconnectFromHost() if (context) { if (!shutdown) { SSLClose(context); + context.reset(nullptr); shutdown = true; } } diff --git a/qtbase/src/network/ssl/qsslsocket_openssl_symbols.cpp b/qtbase/src/network/ssl/qsslsocket_openssl_symbols.cpp index d16c40ceb5..459ccd0b19 100644 --- a/qtbase/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/qtbase/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -499,9 +499,7 @@ DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG) DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return) -#ifndef OPENSSL_NO_DEPRECATED_3_0 DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) -#endif // OPENSSL_NO_DEPRECATED_3_0 DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) #ifndef OPENSSL_NO_EC @@ -1220,9 +1218,7 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(DH_free) RESOLVEFUNC(d2i_DHparams) RESOLVEFUNC(i2d_DHparams) -#ifndef OPENSSL_NO_DEPRECATED_3_0 RESOLVEFUNC(DH_check) -#endif // OPENSSL_NO_DEPRECATED_3_0 RESOLVEFUNC(BN_bin2bn) #ifndef OPENSSL_NO_EC diff --git a/qtbase/src/network/ssl/qsslsocket_openssl_symbols_p.h b/qtbase/src/network/ssl/qsslsocket_openssl_symbols_p.h index 5e9faae291..bf165f67ad 100644 --- a/qtbase/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/qtbase/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -598,10 +598,7 @@ DH *q_DH_new(); void q_DH_free(DH *dh); DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); int q_i2d_DHparams(DH *a, unsigned char **p); - -#ifndef OPENSSL_NO_DEPRECATED_3_0 int q_DH_check(DH *dh, int *codes); -#endif // OPENSSL_NO_DEPRECATED_3_0 BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); #define q_SSL_CTX_set_tmp_dh(ctx, dh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_DH, 0, (char *)dh) diff --git a/qtbase/src/network/ssl/qsslsocket_schannel.cpp b/qtbase/src/network/ssl/qsslsocket_schannel.cpp index c956ce3c2b..d1b23af29b 100644 --- a/qtbase/src/network/ssl/qsslsocket_schannel.cpp +++ b/qtbase/src/network/ssl/qsslsocket_schannel.cpp @@ -1880,6 +1880,28 @@ bool QSslSocketBackendPrivate::verifyCertContext(CERT_CONTEXT *certContext) if (configuration.peerVerifyDepth > 0 && DWORD(configuration.peerVerifyDepth) < verifyDepth) verifyDepth = DWORD(configuration.peerVerifyDepth); + const auto &caCertificates = q->sslConfiguration().caCertificates(); + + if (!rootCertOnDemandLoadingAllowed() + && !(chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) + && (q->peerVerifyMode() == QSslSocket::VerifyPeer + || (isClient && q->peerVerifyMode() == QSslSocket::AutoVerifyPeer))) { + // When verifying a peer Windows "helpfully" builds a chain that + // may include roots from the system store. But we don't want that if + // the user has set their own CA certificates. + // Since Windows claims this is not a partial chain the root is included + // and we have to check that it is one of our configured CAs. + CERT_CHAIN_ELEMENT *element = chain->rgpElement[chain->cElement - 1]; + QSslCertificate certificate = getCertificateFromChainElement(element); + if (!caCertificates.contains(certificate)) { + auto error = QSslError(QSslError::CertificateUntrusted, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + for (DWORD i = 0; i < verifyDepth; i++) { CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; QSslCertificate certificate = getCertificateFromChainElement(element); diff --git a/qtbase/src/platformsupport/eglconvenience/qt_egl_p.h b/qtbase/src/platformsupport/eglconvenience/qt_egl_p.h index bf37d07fd8..dbd42fb799 100644 --- a/qtbase/src/platformsupport/eglconvenience/qt_egl_p.h +++ b/qtbase/src/platformsupport/eglconvenience/qt_egl_p.h @@ -61,7 +61,11 @@ # if !defined(Q_OS_INTEGRITY) # define WIN_INTERFACE_CUSTOM // NV # endif // Q_OS_INTEGRITY -#endif // QT_EGL_NO_X11 +#else // QT_EGL_NO_X11 +// If one has an eglplatform.h with https://github.com/KhronosGroup/EGL-Registry/pull/130 +// that needs USE_X11 to be defined. +# define USE_X11 +#endif #ifdef QT_EGL_WAYLAND # define WAYLAND // NV diff --git a/qtbase/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp b/qtbase/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp index 159b490ce0..00aa80cd58 100644 --- a/qtbase/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp +++ b/qtbase/src/platformsupport/fontdatabases/fontconfig/qfontconfigdatabase.cpp @@ -567,6 +567,8 @@ void QFontconfigDatabase::populateFontDatabase() fonts = FcFontList(nullptr, pattern, os); FcObjectSetDestroy(os); FcPatternDestroy(pattern); + if (!fonts) + return; } for (int i = 0; i < fonts->nfont; i++) diff --git a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp index 09d2d916fe..0e6fe5eb84 100644 --- a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp +++ b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp @@ -1471,36 +1471,70 @@ QT_WARNING_POP return fontEngine; } -static QList getTrueTypeFontOffsets(const uchar *fontData) +static QList getTrueTypeFontOffsets(const uchar *fontData, const uchar *fileEndSentinel) { QList offsets; - const quint32 headerTag = *reinterpret_cast(fontData); + if (fileEndSentinel - fontData < 12) { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; + return offsets; + } + + const quint32 headerTag = qFromUnaligned(fontData); if (headerTag != MAKE_TAG('t', 't', 'c', 'f')) { if (headerTag != MAKE_TAG(0, 1, 0, 0) && headerTag != MAKE_TAG('O', 'T', 'T', 'O') && headerTag != MAKE_TAG('t', 'r', 'u', 'e') - && headerTag != MAKE_TAG('t', 'y', 'p', '1')) + && headerTag != MAKE_TAG('t', 'y', 'p', '1')) { return offsets; + } offsets << 0; return offsets; } + + const quint32 maximumNumFonts = 0xffff; const quint32 numFonts = qFromBigEndian(fontData + 8); - for (uint i = 0; i < numFonts; ++i) { - offsets << qFromBigEndian(fontData + 12 + i * 4); + if (numFonts > maximumNumFonts) { + qCWarning(lcQpaFonts) << "Font collection of" << numFonts << "fonts is too large. Aborting."; + return offsets; } + + if (quintptr(fileEndSentinel - fontData) > 12 + (numFonts - 1) * 4) { + for (quint32 i = 0; i < numFonts; ++i) + offsets << qFromBigEndian(fontData + 12 + i * 4); + } else { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; + } + return offsets; } -static void getFontTable(const uchar *fileBegin, const uchar *data, quint32 tag, const uchar **table, quint32 *length) +static void getFontTable(const uchar *fileBegin, const uchar *fileEndSentinel, const uchar *data, quint32 tag, const uchar **table, quint32 *length) { - const quint16 numTables = qFromBigEndian(data + 4); - for (uint i = 0; i < numTables; ++i) { - const quint32 offset = 12 + 16 * i; - if (*reinterpret_cast(data + offset) == tag) { - *table = fileBegin + qFromBigEndian(data + offset + 8); - *length = qFromBigEndian(data + offset + 12); - return; + if (fileEndSentinel - data >= 6) { + const quint16 numTables = qFromBigEndian(data + 4); + if (fileEndSentinel - data >= 28 + 16 * (numTables - 1)) { + for (quint32 i = 0; i < numTables; ++i) { + const quint32 offset = 12 + 16 * i; + if (qFromUnaligned(data + offset) == tag) { + const quint32 tableOffset = qFromBigEndian(data + offset + 8); + if (quintptr(fileEndSentinel - fileBegin) <= tableOffset) { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; + break; + } + *table = fileBegin + tableOffset; + *length = qFromBigEndian(data + offset + 12); + if (quintptr(fileEndSentinel - *table) < *length) { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; + break; + } + return; + } + } + } else { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; } + } else { + qCWarning(lcQpaFonts) << "Corrupted font data detected"; } *table = 0; *length = 0; @@ -1513,8 +1547,9 @@ static void getFamiliesAndSignatures(const QByteArray &fontData, QVector *values) { const uchar *data = reinterpret_cast(fontData.constData()); + const uchar *dataEndSentinel = data + fontData.size(); - QList offsets = getTrueTypeFontOffsets(data); + QList offsets = getTrueTypeFontOffsets(data, dataEndSentinel); if (offsets.isEmpty()) return; @@ -1522,7 +1557,7 @@ static void getFamiliesAndSignatures(const QByteArray &fontData, const uchar *font = data + offsets.at(i); const uchar *table; quint32 length; - getFontTable(data, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length); + getFontTable(data, dataEndSentinel, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length); if (!table) continue; QFontNames names = qt_getCanonicalFontNames(table, length); @@ -1532,7 +1567,7 @@ static void getFamiliesAndSignatures(const QByteArray &fontData, families->append(std::move(names)); if (values || signatures) - getFontTable(data, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length); + getFontTable(data, dataEndSentinel, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length); if (values) { QFontValues fontValues; diff --git a/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon.cpp b/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon.cpp index 9aa8f10c57..ff556a0e60 100644 --- a/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon.cpp +++ b/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon.cpp @@ -95,6 +95,7 @@ static constexpr const auto KeyTbl = qMakeArray( Xkb2Qt, Xkb2Qt, Xkb2Qt, + Xkb2Qt, Xkb2Qt<0x1005FF60, Qt::Key_SysReq>, // hardcoded Sun SysReq Xkb2Qt<0x1007ff00, Qt::Key_SysReq>, // hardcoded X386 SysReq @@ -471,7 +472,7 @@ QVector QXkbCommon::toKeysym(QKeyEvent *event) } else if (event->modifiers() & Qt::KeypadModifier) { if (qtKey >= Qt::Key_0 && qtKey <= Qt::Key_9) keysyms.append(XKB_KEY_KP_0 + (qtKey - Qt::Key_0)); - } else if (isLatin(qtKey) && event->text().isUpper()) { + } else if (isLatin1(qtKey) && event->text().isUpper()) { keysyms.append(qtKey); } @@ -523,7 +524,7 @@ int QXkbCommon::keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifie // With standard shortcuts we should prefer a latin character, this is // for checks like "some qkeyevent == QKeySequence::Copy" to work even // when using for example 'russian' keyboard layout. - if (!QXkbCommon::isLatin(keysym)) { + if (!QXkbCommon::isLatin1(keysym)) { xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code); if (latinKeysym != XKB_KEY_NoSymbol) keysym = latinKeysym; @@ -546,7 +547,7 @@ static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers mod } else if (keysym >= XKB_KEY_KP_0 && keysym <= XKB_KEY_KP_9) { // numeric keypad keys qtKey = Qt::Key_0 + (keysym - XKB_KEY_KP_0); - } else if (QXkbCommon::isLatin(keysym)) { + } else if (QXkbCommon::isLatin1(keysym)) { qtKey = QXkbCommon::qxkbcommon_xkb_keysym_to_upper(keysym); } else { // check if we have a direct mapping @@ -678,7 +679,7 @@ QList QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, Qt::KeyboardModifiers neededMods = ModsTbl[i]; if ((modifiers & neededMods) == neededMods) { if (i == 8) { - if (isLatin(baseQtKey)) + if (isLatin1(baseQtKey)) continue; // add a latin key as a fall back key sym = lookupLatinKeysym(state, keycode); @@ -733,7 +734,7 @@ void QXkbCommon::verifyHasLatinLayout(xkb_keymap *keymap) for (xkb_layout_index_t layout = 0; layout < layoutCount; ++layout) { for (xkb_keycode_t code = minKeycode; code < maxKeycode; ++code) { xkb_keymap_key_get_syms_by_level(keymap, code, layout, 0, &keysyms); - if (keysyms && isLatin(keysyms[0])) + if (keysyms && isLatin1(keysyms[0])) nrLatinKeys++; if (nrLatinKeys > 10) // arbitrarily chosen threshold return; @@ -754,6 +755,8 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco { xkb_layout_index_t layout; xkb_keysym_t sym = XKB_KEY_NoSymbol; + if (!state) + return sym; xkb_keymap *keymap = xkb_state_get_keymap(state); const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(keymap, keycode); const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(state, keycode); @@ -766,7 +769,7 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco xkb_level_index_t level = xkb_state_key_get_level(state, keycode, layout); if (xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms) != 1) continue; - if (isLatin(syms[0])) { + if (isLatin1(syms[0])) { sym = syms[0]; break; } diff --git a/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon_p.h b/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon_p.h index 561eae03db..8389bd8f5a 100644 --- a/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon_p.h +++ b/qtbase/src/platformsupport/input/xkbcommon/qxkbcommon_p.h @@ -94,8 +94,8 @@ public: static void verifyHasLatinLayout(xkb_keymap *keymap); static xkb_keysym_t lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode); - static bool isLatin(xkb_keysym_t sym) { - return ((sym >= 'a' && sym <= 'z') || (sym >= 'A' && sym <= 'Z')); + static bool isLatin1(xkb_keysym_t sym) { + return sym <= 0xff; } static bool isKeypad(xkb_keysym_t sym) { return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9; diff --git a/qtbase/src/platformsupport/linuxaccessibility/atspiadaptor.cpp b/qtbase/src/platformsupport/linuxaccessibility/atspiadaptor.cpp index 9153fd20bb..26537ff892 100644 --- a/qtbase/src/platformsupport/linuxaccessibility/atspiadaptor.cpp +++ b/qtbase/src/platformsupport/linuxaccessibility/atspiadaptor.cpp @@ -194,6 +194,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const " \n" " \n" " \n" + " \n" + " \n" + " \n" " \n" ); @@ -913,8 +916,17 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) } case QAccessible::NameChanged: { if (sendObject || sendObject_property_change || sendObject_property_change_accessible_name) { - QString path = pathForInterface(event->accessibleInterface()); - QVariantList args = packDBusSignalArguments(QLatin1String("accessible-name"), 0, 0, variantForPath(path)); + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qCDebug(lcAccessibilityAtspi, + "NameChanged event from invalid accessible."); + return; + } + + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments( + QLatin1String("accessible-name"), 0, 0, + QVariant::fromValue(QDBusVariant(iface->text(QAccessible::Name)))); sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), QLatin1String("PropertyChange"), args); } @@ -922,8 +934,17 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) } case QAccessible::DescriptionChanged: { if (sendObject || sendObject_property_change || sendObject_property_change_accessible_description) { - QString path = pathForInterface(event->accessibleInterface()); - QVariantList args = packDBusSignalArguments(QLatin1String("accessible-description"), 0, 0, variantForPath(path)); + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qCDebug(lcAccessibilityAtspi, + "DescriptionChanged event from invalid accessible."); + return; + } + + QString path = pathForInterface(iface); + QVariantList args = packDBusSignalArguments( + QLatin1String("accessible-description"), 0, 0, + QVariant::fromValue(QDBusVariant(iface->text(QAccessible::Description)))); sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), QLatin1String("PropertyChange"), args); } @@ -1038,7 +1059,9 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event) // Combo Box with AT-SPI likes to be special // It requires a name-change to update caches and then selection-changed QString path = pathForInterface(iface); - QVariantList args1 = packDBusSignalArguments(QLatin1String("accessible-name"), 0, 0, variantForPath(path)); + QVariantList args1 = packDBusSignalArguments( + QLatin1String("accessible-name"), 0, 0, + QVariant::fromValue(QDBusVariant(iface->text(QAccessible::Name)))); sendDBusSignal(path, QLatin1String(ATSPI_DBUS_INTERFACE_EVENT_OBJECT), QLatin1String("PropertyChange"), args1); QVariantList args2 = packDBusSignalArguments(QString(), 0, 0, QVariant::fromValue(QDBusVariant(QVariant(0)))); @@ -1358,6 +1381,26 @@ void AtSpiAdaptor::registerApplication() delete registry; } +namespace { +QString accessibleIdForAccessible(QAccessibleInterface *accessible) +{ + QString result; + while (accessible) { + if (!result.isEmpty()) + result.prepend(QLatin1Char('.')); + if (auto obj = accessible->object()) { + const QString name = obj->objectName(); + if (!name.isEmpty()) + result.prepend(name); + else + result.prepend(QString::fromUtf8(obj->metaObject()->className())); + } + accessible = accessible->parent(); + } + return result; +} +} // namespace + // Accessible bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection) { @@ -1441,6 +1484,9 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS children << ref; } connection.send(message.createReply(QVariant::fromValue(children))); + } else if (function == QLatin1String("GetAccessibleId")) { + sendReply(connection, message, + QVariant::fromValue(QDBusVariant(accessibleIdForAccessible(interface)))); } else { qCDebug(lcAccessibilityAtspi) << "WARNING: AtSpiAdaptor::accessibleInterface does not implement " << function << message.path(); return false; @@ -1560,11 +1606,12 @@ bool AtSpiAdaptor::inheritsQAction(QObject *object) // Component static QAccessibleInterface * getWindow(QAccessibleInterface * interface) { - if (interface->role() == QAccessible::Window) + if (interface->role() == QAccessible::Dialog || interface->role() == QAccessible::Window) return interface; QAccessibleInterface * parent = interface->parent(); - while (parent && parent->role() != QAccessible::Window) + while (parent && parent->role() != QAccessible::Dialog + && parent->role() != QAccessible::Window) parent = parent->parent(); return parent; @@ -1582,7 +1629,7 @@ static QRect getRelativeRect(QAccessibleInterface *interface) wr = window->rect(); cr.setX(cr.x() - wr.x()); - cr.setY(cr.x() - wr.y()); + cr.setY(cr.y() - wr.y()); } return cr; } @@ -1836,7 +1883,7 @@ bool AtSpiAdaptor::textInterface(QAccessibleInterface *interface, const QString uint coordType = message.arguments().at(2).toUInt(); if (coordType == ATSPI_COORD_TYPE_WINDOW) { QWindow *win = interface->window(); - point -= QPoint(win->x(), win->y()); + point += QPoint(win->x(), win->y()); } int offset = interface->textInterface()->offsetAtPoint(point); sendReply(connection, message, offset); diff --git a/qtbase/src/platformsupport/linuxaccessibility/dbusconnection.cpp b/qtbase/src/platformsupport/linuxaccessibility/dbusconnection.cpp index 45ddc8e496..cc734abc63 100644 --- a/qtbase/src/platformsupport/linuxaccessibility/dbusconnection.cpp +++ b/qtbase/src/platformsupport/linuxaccessibility/dbusconnection.cpp @@ -69,6 +69,21 @@ QT_BEGIN_NAMESPACE DBusConnection::DBusConnection(QObject *parent) : QObject(parent), m_a11yConnection(QString()), m_enabled(false) { + // If the bus is explicitly set via env var it overrides everything else. + QByteArray addressEnv = qgetenv("AT_SPI_BUS_ADDRESS"); + if (!addressEnv.isEmpty()) { + // Only connect on next loop run, connections to our enabled signal are + // only established after the ctor returns. + QMetaObject::invokeMethod( + this, + [this, addressEnv] { + m_enabled = true; + connectA11yBus(QString::fromLocal8Bit(addressEnv)); + }, + Qt::QueuedConnection); + return; + } + // Start monitoring if "org.a11y.Bus" is registered as DBus service. QDBusConnection c = QDBusConnection::sessionBus(); if (!c.isConnected()) { diff --git a/qtbase/src/platformsupport/services/genericunix/qgenericunixservices.cpp b/qtbase/src/platformsupport/services/genericunix/qgenericunixservices.cpp index f0d1722c95..2abe039126 100644 --- a/qtbase/src/platformsupport/services/genericunix/qgenericunixservices.cpp +++ b/qtbase/src/platformsupport/services/genericunix/qgenericunixservices.cpp @@ -51,6 +51,9 @@ #include #include +#include +#include + #if QT_CONFIG(dbus) // These QtCore includes are needed for xdg-desktop-portal support #include @@ -58,6 +61,8 @@ #include #include +#include + #include #include #include @@ -298,8 +303,135 @@ static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url) return QDBusConnection::sessionBus().call(message); } + +namespace { +struct XDGDesktopColor +{ + double r = 0; + double g = 0; + double b = 0; + + QColor toQColor() const + { + constexpr auto rgbMax = 255; + return { static_cast(r * rgbMax), static_cast(g * rgbMax), + static_cast(b * rgbMax) }; + } +}; + +const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct) +{ + argument.beginStructure(); + argument >> myStruct.r >> myStruct.g >> myStruct.b; + argument.endStructure(); + return argument; +} + +class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker +{ + Q_OBJECT +public: + XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent) + : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId) + { + } + + void pickColor() override + { + // DBus signature: + // PickColor (IN s parent_window, + // IN a{sv} options + // OUT o handle) + // Options: + // handle_token (s) - A string that will be used as the last element of the @handle. + + QDBusMessage message = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.portal.Screenshot"), QStringLiteral("PickColor")); + message << m_parentWindowId << QVariantMap(); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); + auto watcher = new QDBusPendingCallWatcher(pendingCall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + qWarning("DBus call to pick color failed: %s", + qPrintable(reply.error().message())); + Q_EMIT colorPicked({}); + } else { + QDBusConnection::sessionBus().connect( + QStringLiteral("org.freedesktop.portal.Desktop"), reply.value().path(), + QStringLiteral("org.freedesktop.portal.Request"), QStringLiteral("Response"), this, + // clang-format off + SLOT(gotColorResponse(uint,QVariantMap)) + // clang-format on + ); + } + }); + } + +private Q_SLOTS: + void gotColorResponse(uint result, const QVariantMap &map) + { + if (result != 0) + return; + XDGDesktopColor color{}; + map.value(QStringLiteral("color")).value() >> color; + Q_EMIT colorPicked(color.toQColor()); + deleteLater(); + } + +private: + const QString m_parentWindowId; +}; +} // namespace + #endif // QT_CONFIG(dbus) +QGenericUnixServices::QGenericUnixServices() +{ +#if QT_CONFIG(dbus) + if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) { + return; + } + QDBusMessage message = QDBusMessage::createMethodCall( + QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"), + QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Get")); + message << QStringLiteral("org.freedesktop.portal.Screenshot") + << QStringLiteral("version"); + + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); + auto watcher = new QDBusPendingCallWatcher(pendingCall); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, + [this](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (!reply.isError() && reply.value().toUInt() >= 2) + m_hasScreenshotPortalWithColorPicking = true; + }); + +#endif +} + +QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent) +{ +#if QT_CONFIG(dbus) + // Make double sure that we are in a wayland environment. In particular check + // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking. + // Outside wayland we'll rather rely on other means than the XDG desktop portal. + if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY") + || QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { + return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent); + } + return nullptr; +#else + Q_UNUSED(parent); + return nullptr; +#endif +} + QByteArray QGenericUnixServices::desktopEnvironment() const { static const QByteArray result = detectDesktopEnvironment(); @@ -354,6 +486,8 @@ bool QGenericUnixServices::openDocument(const QUrl &url) } #else +QGenericUnixServices::QGenericUnixServices() = default; + QByteArray QGenericUnixServices::desktopEnvironment() const { return QByteArrayLiteral("UNKNOWN"); @@ -373,6 +507,30 @@ bool QGenericUnixServices::openDocument(const QUrl &url) return false; } +QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent) +{ + Q_UNUSED(parent); + return nullptr; +} + #endif // QT_NO_MULTIPROCESS +QString QGenericUnixServices::portalWindowIdentifier(QWindow *window) +{ + if (QGuiApplication::platformName() == QLatin1String("xcb")) + return QStringLiteral("x11:") + QString::number(window->winId(), 16); + return QString(); +} + +bool QGenericUnixServices::hasCapability(Capability capability) const +{ + switch (capability) { + case Capability::ColorPicking: + return m_hasScreenshotPortalWithColorPicking; + } + return false; +} + QT_END_NAMESPACE + +#include "qgenericunixservices.moc" diff --git a/qtbase/src/platformsupport/services/genericunix/qgenericunixservices_p.h b/qtbase/src/platformsupport/services/genericunix/qgenericunixservices_p.h index 8ac3de6f03..30924e64bd 100644 --- a/qtbase/src/platformsupport/services/genericunix/qgenericunixservices_p.h +++ b/qtbase/src/platformsupport/services/genericunix/qgenericunixservices_p.h @@ -59,16 +59,21 @@ QT_BEGIN_NAMESPACE class QGenericUnixServices : public QPlatformServices { public: - QGenericUnixServices() {} + QGenericUnixServices(); QByteArray desktopEnvironment() const override; + bool hasCapability(Capability capability) const override; bool openUrl(const QUrl &url) override; bool openDocument(const QUrl &url) override; + QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr) override; + + virtual QString portalWindowIdentifier(QWindow *window); private: QString m_webBrowser; QString m_documentLauncher; + bool m_hasScreenshotPortalWithColorPicking = false; }; QT_END_NAMESPACE diff --git a/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection.cpp b/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection.cpp index 09470bccc6..cc7c7d4d8a 100644 --- a/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection.cpp +++ b/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection.cpp @@ -69,6 +69,7 @@ const QString MenuBarPath = QLatin1String("/MenuBar"); */ QDBusMenuConnection::QDBusMenuConnection(QObject *parent, const QString &serviceName) : QObject(parent) + , m_serviceName(serviceName) , m_connection(serviceName.isNull() ? QDBusConnection::sessionBus() : QDBusConnection::connectToBus(QDBusConnection::SessionBus, serviceName)) , m_dbusWatcher(new QDBusServiceWatcher(StatusNotifierWatcherService, m_connection, QDBusServiceWatcher::WatchForRegistration, this)) @@ -83,6 +84,12 @@ QDBusMenuConnection::QDBusMenuConnection(QObject *parent, const QString &service #endif } +QDBusMenuConnection::~QDBusMenuConnection() +{ + if (!m_serviceName.isEmpty() && m_connection.isConnected()) + QDBusConnection::disconnectFromBus(m_serviceName); +} + void QDBusMenuConnection::dbusError(const QDBusError &error) { qWarning() << "QDBusTrayIcon encountered a D-Bus error:" << error; @@ -105,13 +112,7 @@ void QDBusMenuConnection::unregisterTrayIconMenu(QDBusTrayIcon *item) bool QDBusMenuConnection::registerTrayIcon(QDBusTrayIcon *item) { - bool success = connection().registerService(item->instanceId()); - if (!success) { - qWarning() << "failed to register service" << item->instanceId(); - return false; - } - - success = connection().registerObject(StatusNotifierItemPath, item); + bool success = connection().registerObject(StatusNotifierItemPath, item); if (!success) { unregisterTrayIcon(item); qWarning() << "failed to register" << item->instanceId() << StatusNotifierItemPath; @@ -126,21 +127,18 @@ bool QDBusMenuConnection::registerTrayIcon(QDBusTrayIcon *item) bool QDBusMenuConnection::registerTrayIconWithWatcher(QDBusTrayIcon *item) { + Q_UNUSED(item); QDBusMessage registerMethod = QDBusMessage::createMethodCall( StatusNotifierWatcherService, StatusNotifierWatcherPath, StatusNotifierWatcherService, QLatin1String("RegisterStatusNotifierItem")); - registerMethod.setArguments(QVariantList() << item->instanceId()); + registerMethod.setArguments(QVariantList() << m_connection.baseService()); return m_connection.callWithCallback(registerMethod, this, SIGNAL(trayIconRegistered()), SLOT(dbusError(QDBusError))); } -bool QDBusMenuConnection::unregisterTrayIcon(QDBusTrayIcon *item) +void QDBusMenuConnection::unregisterTrayIcon(QDBusTrayIcon *item) { unregisterTrayIconMenu(item); connection().unregisterObject(StatusNotifierItemPath); - bool success = connection().unregisterService(item->instanceId()); - if (!success) - qWarning() << "failed to unregister service" << item->instanceId(); - return success; } #endif // QT_NO_SYSTEMTRAYICON diff --git a/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection_p.h b/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection_p.h index f484795fbb..97bdfabb85 100644 --- a/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection_p.h +++ b/qtbase/src/platformsupport/themes/genericunix/dbusmenu/qdbusmenuconnection_p.h @@ -70,6 +70,7 @@ class QDBusMenuConnection : public QObject public: QDBusMenuConnection(QObject *parent = nullptr, const QString &serviceName = QString()); + ~QDBusMenuConnection(); QDBusConnection connection() const { return m_connection; } QDBusServiceWatcher *dbusWatcher() const { return m_dbusWatcher; } bool isStatusNotifierHostRegistered() const { return m_statusNotifierHostRegistered; } @@ -78,7 +79,7 @@ public: void unregisterTrayIconMenu(QDBusTrayIcon *item); bool registerTrayIcon(QDBusTrayIcon *item); bool registerTrayIconWithWatcher(QDBusTrayIcon *item); - bool unregisterTrayIcon(QDBusTrayIcon *item); + void unregisterTrayIcon(QDBusTrayIcon *item); #endif // QT_NO_SYSTEMTRAYICON Q_SIGNALS: @@ -90,6 +91,7 @@ private Q_SLOTS: void dbusError(const QDBusError &error); private: + QString m_serviceName; QDBusConnection m_connection; QDBusServiceWatcher *m_dbusWatcher; bool m_statusNotifierHostRegistered; diff --git a/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp b/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp index cb1b39db64..6e01af052c 100644 --- a/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp +++ b/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp @@ -755,6 +755,9 @@ QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const return QVariant(QChar(0x2022)); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::ButtonPressKeys: + return QVariant::fromValue( + QList({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select })); default: break; } diff --git a/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper.cpp b/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper.cpp index 8038e1a3c3..79541fe636 100644 --- a/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper.cpp +++ b/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper.cpp @@ -53,7 +53,6 @@ QWindowsUiaWrapper::QWindowsUiaWrapper() m_pUiaHostProviderFromHwnd = reinterpret_cast(uiaLib.resolve("UiaHostProviderFromHwnd")); m_pUiaRaiseAutomationPropertyChangedEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseAutomationPropertyChangedEvent")); m_pUiaRaiseAutomationEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseAutomationEvent")); - m_pUiaRaiseNotificationEvent = reinterpret_cast(uiaLib.resolve("UiaRaiseNotificationEvent")); m_pUiaClientsAreListening = reinterpret_cast(uiaLib.resolve("UiaClientsAreListening")); } } @@ -69,7 +68,7 @@ QWindowsUiaWrapper *QWindowsUiaWrapper::instance() return &wrapper; } -// True if most symbols resolved (UiaRaiseNotificationEvent is optional). +// True if all symbols resolved. BOOL QWindowsUiaWrapper::ready() { return m_pUiaReturnRawElementProvider @@ -114,12 +113,5 @@ HRESULT QWindowsUiaWrapper::raiseAutomationEvent(IRawElementProviderSimple *pPro return m_pUiaRaiseAutomationEvent(pProvider, id); } -HRESULT QWindowsUiaWrapper::raiseNotificationEvent(IRawElementProviderSimple *provider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId) -{ - if (!m_pUiaRaiseNotificationEvent) - return UIA_E_NOTSUPPORTED; - return m_pUiaRaiseNotificationEvent(provider, notificationKind, notificationProcessing, displayString, activityId); -} - QT_END_NAMESPACE diff --git a/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper_p.h b/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper_p.h index 9208acbc31..3ebc3008d3 100644 --- a/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper_p.h +++ b/qtbase/src/platformsupport/windowsuiautomation/qwindowsuiawrapper_p.h @@ -80,20 +80,17 @@ public: HRESULT hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider); HRESULT raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue); HRESULT raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id); - HRESULT raiseNotificationEvent(IRawElementProviderSimple *provider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId); private: typedef LRESULT (WINAPI *PtrUiaReturnRawElementProvider)(HWND, WPARAM, LPARAM, IRawElementProviderSimple *); typedef HRESULT (WINAPI *PtrUiaHostProviderFromHwnd)(HWND, IRawElementProviderSimple **); typedef HRESULT (WINAPI *PtrUiaRaiseAutomationPropertyChangedEvent)(IRawElementProviderSimple *, PROPERTYID, VARIANT, VARIANT); typedef HRESULT (WINAPI *PtrUiaRaiseAutomationEvent)(IRawElementProviderSimple *, EVENTID); - typedef HRESULT (WINAPI *PtrUiaRaiseNotificationEvent)(IRawElementProviderSimple *, NotificationKind, NotificationProcessing, BSTR, BSTR); typedef BOOL (WINAPI *PtrUiaClientsAreListening)(); PtrUiaReturnRawElementProvider m_pUiaReturnRawElementProvider = nullptr; PtrUiaHostProviderFromHwnd m_pUiaHostProviderFromHwnd = nullptr; PtrUiaRaiseAutomationPropertyChangedEvent m_pUiaRaiseAutomationPropertyChangedEvent = nullptr; PtrUiaRaiseAutomationEvent m_pUiaRaiseAutomationEvent = nullptr; - PtrUiaRaiseNotificationEvent m_pUiaRaiseNotificationEvent = nullptr; PtrUiaClientsAreListening m_pUiaClientsAreListening = nullptr; }; diff --git a/qtbase/src/platformsupport/windowsuiautomation/uiatypes_p.h b/qtbase/src/platformsupport/windowsuiautomation/uiatypes_p.h index 0d2e1161e4..afbc957094 100644 --- a/qtbase/src/platformsupport/windowsuiautomation/uiatypes_p.h +++ b/qtbase/src/platformsupport/windowsuiautomation/uiatypes_p.h @@ -162,22 +162,6 @@ enum ExpandCollapseState { ExpandCollapseState_LeafNode = 3 }; -enum NotificationKind { - NotificationKind_ItemAdded = 0, - NotificationKind_ItemRemoved = 1, - NotificationKind_ActionCompleted = 2, - NotificationKind_ActionAborted = 3, - NotificationKind_Other = 4 -}; - -enum NotificationProcessing { - NotificationProcessing_ImportantAll = 0, - NotificationProcessing_ImportantMostRecent = 1, - NotificationProcessing_All = 2, - NotificationProcessing_MostRecent = 3, - NotificationProcessing_CurrentThenMostRecent = 4 -}; - struct UiaRect { double left; double top; diff --git a/qtbase/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml b/qtbase/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml index 9c67a38c57..30c326d06f 100644 --- a/qtbase/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml +++ b/qtbase/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml @@ -14,6 +14,12 @@ + + + + + + diff --git a/qtbase/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h b/qtbase/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h index 396a213aaa..31d5a71c41 100644 --- a/qtbase/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h +++ b/qtbase/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h @@ -112,6 +112,13 @@ public Q_SLOTS: // METHODS return asyncCallWithArgumentList(QLatin1String("SetCursorLocation"), argumentList); } + inline QDBusPendingReply<> SetCursorLocationRelative(int x, int y, int w, int h) + { + QList 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 argumentList; diff --git a/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp b/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp index 645a0ae2e9..3e0e406f1a 100644 --- a/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp +++ b/qtbase/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmseventreader.cpp @@ -179,7 +179,7 @@ void QEglFSKmsEventReader::create(QEglFSKmsDevice *device) m_device = device; - qCDebug(qLcEglfsKmsDebug, "Initalizing event reader for device %p fd %d", + qCDebug(qLcEglfsKmsDebug, "Initializing event reader for device %p fd %d", m_device, m_device->fd()); m_thread = new QEglFSKmsEventReaderThread(m_device->fd()); diff --git a/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.cpp b/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.cpp index 141fb68a23..d4294d425a 100644 --- a/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.cpp +++ b/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.cpp @@ -122,11 +122,13 @@ QOffscreenIntegration::QOffscreenIntegration() #endif m_services.reset(new QPlatformServices); - QWindowSystemInterface::handleScreenAdded(new QOffscreenScreen); + m_screen = new QOffscreenScreen; + QWindowSystemInterface::handleScreenAdded(m_screen); } QOffscreenIntegration::~QOffscreenIntegration() { + QWindowSystemInterface::handleScreenRemoved(m_screen); } void QOffscreenIntegration::initialize() diff --git a/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.h b/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.h index 0ea90f6c2f..fe00fde07c 100644 --- a/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.h +++ b/qtbase/src/plugins/platforms/offscreen/qoffscreenintegration.h @@ -84,6 +84,7 @@ protected: #endif QScopedPointer m_inputContext; QScopedPointer m_services; + QPlatformScreen *m_screen; mutable QScopedPointer m_nativeInterface; }; diff --git a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 754ded14f1..059df8d991 100644 --- a/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/qtbase/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -169,27 +169,11 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve } if (event->value().type() == QVariant::String) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { - - // Tries to notify the change using UiaRaiseNotificationEvent(), which is only available on - // Windows 10 version 1709 or newer. Otherwise uses UiaRaiseAutomationPropertyChangedEvent(). - - BSTR displayString = bStrFromQString(event->value().toString()); - BSTR activityId = bStrFromQString(QString()); - - HRESULT hr = QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider, NotificationKind_Other, - NotificationProcessing_ImportantMostRecent, - displayString, activityId); - - ::SysFreeString(displayString); - ::SysFreeString(activityId); - - if (hr == static_cast(UIA_E_NOTSUPPORTED)) { - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantString(event->value().toString(), &newVal); - QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); - ::SysFreeString(newVal.bstrVal); - } + // Notifies changes in string values. + VARIANT oldVal, newVal; + clearVariant(&oldVal); + setVariantString(event->value().toString(), &newVal); + QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal); } } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) { diff --git a/qtbase/src/plugins/platforms/xcb/qxcbatom.cpp b/qtbase/src/plugins/platforms/xcb/qxcbatom.cpp index 780816605a..a769ddadbd 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbatom.cpp +++ b/qtbase/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/qtbase/src/plugins/platforms/xcb/qxcbatom.h b/qtbase/src/plugins/platforms/xcb/qxcbatom.h index 9cf93ec314..1ce6cca573 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbatom.h +++ b/qtbase/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/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp b/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp index 6457bc0551..152ab2c52d 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/qtbase/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -702,6 +702,8 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window); if (virtualDesktop) virtualDesktop->updateWorkArea(); + } else if (propertyNotify->atom == atom(QXcbAtom::_NET_SUPPORTED)) { + m_wmSupport->updateNetWMAtoms(); } else { HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent); } @@ -798,8 +800,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(); @@ -831,8 +833,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/qtbase/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/qtbase/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 1ced02f31d..5c8298a49d 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/qtbase/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -1255,16 +1255,14 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD if (Q_LIKELY(useValuators)) { const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width()); global.setX(value); - // mapFromGlobal is ok for nested/embedded windows, but works only with whole-number QPoint; - // so map it first, then add back the sub-pixel position - local.setX(window->mapFromGlobal(QPoint(int(value), 0)).x() + (value - int(value))); + local.setX(xcbWindow->mapFromGlobal(QPoint(int(value), 0)).x() + (value - int(value))); } break; case QXcbAtom::AbsY: if (Q_LIKELY(useValuators)) { qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height()); global.setY(value); - local.setY(window->mapFromGlobal(QPoint(0, int(value))).y() + (value - int(value))); + local.setY(xcbWindow->mapFromGlobal(QPoint(0, int(value))).y() + (value - int(value))); } break; case QXcbAtom::AbsPressure: diff --git a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp index 4210bf428e..0670b6ebce 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp +++ b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp @@ -300,7 +300,7 @@ QXcbCursorCacheKey::QXcbCursorCacheKey(const QCursor &c) #endif // !QT_NO_CURSOR QXcbCursor::QXcbCursor(QXcbConnection *conn, QXcbScreen *screen) - : QXcbObject(conn), m_screen(screen), m_gtkCursorThemeInitialized(false) + : QXcbObject(conn), m_screen(screen), m_gtkCursorThemeInitialized(false), m_callbackForPropertyRegistered(false) { #if QT_CONFIG(cursor) // see NUM_BITMAPS in libXcursor/src/xcursorint.h @@ -343,7 +343,7 @@ QXcbCursor::~QXcbCursor() { xcb_connection_t *conn = xcb_connection(); - if (m_gtkCursorThemeInitialized) { + if (m_callbackForPropertyRegistered) { m_screen->xSettings()->removeCallbackForHandle(this); } @@ -562,8 +562,10 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) xcb_cursor_t cursor = XCB_NONE; #if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) - if (m_screen->xSettings()->initialized()) + if (m_screen->xSettings()->initialized()) { m_screen->xSettings()->registerCallbackForProperty("Gtk/CursorThemeName",cursorThemePropertyChanged,this); + m_callbackForPropertyRegistered = true; + } // Try Xcursor first if (cshape >= 0 && cshape <= Qt::LastCursor) { diff --git a/qtbase/src/plugins/platforms/xcb/qxcbcursor.h b/qtbase/src/plugins/platforms/xcb/qxcbcursor.h index 0b238823f0..82fb47e55d 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbcursor.h +++ b/qtbase/src/plugins/platforms/xcb/qxcbcursor.h @@ -122,6 +122,7 @@ private: void *handle); #endif bool m_gtkCursorThemeInitialized; + bool m_callbackForPropertyRegistered; }; QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/xcb/qxcbintegration.cpp b/qtbase/src/plugins/platforms/xcb/qxcbintegration.cpp index 76869ced60..02d2eebb56 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/qtbase/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -274,8 +274,7 @@ QPlatformWindow *QXcbIntegration::createForeignWindow(QWindow *window, WId nativ #ifndef QT_NO_OPENGL QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { - QXcbScreen *screen = static_cast(context->screen()->handle()); - QXcbGlIntegration *glIntegration = screen->connection()->glIntegration(); + QXcbGlIntegration *glIntegration = defaultConnection()->glIntegration(); if (!glIntegration) { qWarning("QXcbIntegration: Cannot create platform OpenGL context, neither GLX nor EGL are enabled"); return nullptr; diff --git a/qtbase/src/plugins/platforms/xcb/qxcbwindow.cpp b/qtbase/src/plugins/platforms/xcb/qxcbwindow.cpp index bffad0fac5..c67ffa9dd7 100644 --- a/qtbase/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/qtbase/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -299,11 +299,6 @@ void QXcbWindow::create() return; } - QPlatformWindow::setGeometry(rect); - - if (platformScreen != currentScreen) - QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen()); - const QSize minimumSize = windowMinimumSize(); if (rect.width() > 0 || rect.height() > 0) { rect.setWidth(qBound(1, rect.width(), XCOORD_MAX)); @@ -315,6 +310,11 @@ void QXcbWindow::create() rect.setHeight(QHighDpi::toNativePixels(int(defaultWindowHeight), platformScreen->QPlatformScreen::screen())); } + QPlatformWindow::setGeometry(rect); + + if (platformScreen != currentScreen) + QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen()); + xcb_window_t xcb_parent_id = platformScreen->root(); if (parent()) { xcb_parent_id = static_cast(parent())->xcb_window(); @@ -1345,9 +1345,10 @@ void QXcbWindow::setWindowIcon(const QIcon &icon) if (!icon_data.isEmpty()) { // Ignore icon exceeding maximum xcb request length - if (size_t(icon_data.size()) > xcb_get_maximum_request_length(xcb_connection())) { - qWarning("Ignoring window icon: Size %d exceeds maximum xcb request length %u.", - icon_data.size(), xcb_get_maximum_request_length(xcb_connection())); + 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/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp index 2c72538387..8987e3efd0 100644 --- a/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp +++ b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp @@ -102,15 +102,12 @@ const QDBusArgument &operator >>(const QDBusArgument &arg, QXdgDesktopPortalFile class QXdgDesktopPortalFileDialogPrivate { public: - QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog) + QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog, uint fileChooserPortalVersion) : nativeFileDialog(nativeFileDialog) + , fileChooserPortalVersion(fileChooserPortalVersion) { } - WId winId = 0; - bool directoryMode = false; - bool modal = false; - bool multipleFiles = false; - bool saveFile = false; + QEventLoop loop; QString acceptLabel; QString directory; QString title; @@ -122,11 +119,16 @@ public: QString selectedNameFilter; QStringList selectedFiles; std::unique_ptr nativeFileDialog; + uint fileChooserPortalVersion = 0; + bool failedToOpen = false; + bool directoryMode = false; + bool multipleFiles = false; + bool saveFile = false; }; -QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog) +QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog, uint fileChooserPortalVersion) : QPlatformFileDialogHelper() - , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog)) + , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog, fileChooserPortalVersion)) { Q_D(QXdgDesktopPortalFileDialog); @@ -134,6 +136,9 @@ QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelp connect(d->nativeFileDialog.get(), SIGNAL(accept()), this, SIGNAL(accept())); connect(d->nativeFileDialog.get(), SIGNAL(reject()), this, SIGNAL(reject())); } + + d->loop.connect(this, SIGNAL(accept()), SLOT(quit())); + d->loop.connect(this, SIGNAL(reject()), SLOT(quit())); } QXdgDesktopPortalFileDialog::~QXdgDesktopPortalFileDialog() @@ -177,7 +182,7 @@ void QXdgDesktopPortalFileDialog::initializeDialog() setDirectory(options()->initialDirectory()); } -void QXdgDesktopPortalFileDialog::openPortal() +void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) { Q_D(QXdgDesktopPortalFileDialog); @@ -185,13 +190,13 @@ void QXdgDesktopPortalFileDialog::openPortal() QLatin1String("/org/freedesktop/portal/desktop"), QLatin1String("org.freedesktop.portal.FileChooser"), d->saveFile ? QLatin1String("SaveFile") : QLatin1String("OpenFile")); - QString parentWindowId = QLatin1String("x11:") + QString::number(d->winId, 16); + QString parentWindowId = QLatin1String("x11:") + QString::number(parent ? parent->winId() : 0, 16); QVariantMap options; if (!d->acceptLabel.isEmpty()) options.insert(QLatin1String("accept_label"), d->acceptLabel); - options.insert(QLatin1String("modal"), d->modal); + options.insert(QLatin1String("modal"), windowModality != Qt::NonModal); options.insert(QLatin1String("multiple"), d->multipleFiles); options.insert(QLatin1String("directory"), d->directoryMode); @@ -233,6 +238,9 @@ void QXdgDesktopPortalFileDialog::openPortal() filter.name = mimeType.comment(); filter.filterConditions = filterConditions; + if (filter.name.isEmpty()) + filter.name = mimeTypefilter; + filterList << filter; if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter) @@ -290,10 +298,18 @@ void QXdgDesktopPortalFileDialog::openPortal() QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); - connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) { + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=] (QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; - if (reply.isError()) { - Q_EMIT reject(); + // Any error means the dialog is not shown and we need to fallback + d->failedToOpen = reply.isError(); + if (d->failedToOpen) { + if (d->nativeFileDialog) { + d->nativeFileDialog->show(windowFlags, windowModality, parent); + if (d->loop.isRunning()) + d->nativeFileDialog->exec(); + } else { + Q_EMIT reject(); + } } else { QDBusConnection::sessionBus().connect(nullptr, reply.value().path(), @@ -327,7 +343,7 @@ QUrl QXdgDesktopPortalFileDialog::directory() const { Q_D(const QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog()) return d->nativeFileDialog->directory(); return d->directory; @@ -349,7 +365,7 @@ QList QXdgDesktopPortalFileDialog::selectedFiles() const { Q_D(const QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog()) return d->nativeFileDialog->selectedFiles(); QList files; @@ -404,16 +420,13 @@ void QXdgDesktopPortalFileDialog::exec() { Q_D(QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) { + if (d->nativeFileDialog && useNativeFileDialog()) { d->nativeFileDialog->exec(); return; } // HACK we have to avoid returning until we emit that the dialog was accepted or rejected - QEventLoop loop; - loop.connect(this, SIGNAL(accept()), SLOT(quit())); - loop.connect(this, SIGNAL(reject()), SLOT(quit())); - loop.exec(); + d->loop.exec(); } void QXdgDesktopPortalFileDialog::hide() @@ -430,13 +443,10 @@ bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowMo initializeDialog(); - d->modal = windowModality != Qt::NonModal; - d->winId = parent ? parent->winId() : 0; - - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog(OpenFallback)) return d->nativeFileDialog->show(windowFlags, windowModality, parent); - openPortal(); + openPortal(windowFlags, windowModality, parent); return true; } @@ -466,6 +476,23 @@ void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap & } } +bool QXdgDesktopPortalFileDialog::useNativeFileDialog(QXdgDesktopPortalFileDialog::FallbackType fallbackType) const +{ + Q_D(const QXdgDesktopPortalFileDialog); + + if (d->failedToOpen && fallbackType != OpenFallback) + return true; + + if (d->fileChooserPortalVersion < 3) { + if (options()->fileMode() == QFileDialogOptions::Directory) + return true; + else if (options()->fileMode() == QFileDialogOptions::DirectoryOnly) + return true; + } + + return false; +} + QT_END_NAMESPACE #include "moc_qxdgdesktopportalfiledialog_p.cpp" diff --git a/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h index 4f4de96ecf..65e22a5cf2 100644 --- a/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h +++ b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h @@ -51,6 +51,11 @@ class QXdgDesktopPortalFileDialog : public QPlatformFileDialogHelper Q_OBJECT Q_DECLARE_PRIVATE(QXdgDesktopPortalFileDialog) public: + enum FallbackType { + GenericFallback, + OpenFallback + }; + enum ConditionType : uint { GlobalPattern = 0, MimeType = 1 @@ -69,7 +74,7 @@ public: }; typedef QVector FilterList; - QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog = nullptr); + QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog = nullptr, uint fileChooserPortalVersion = 0); ~QXdgDesktopPortalFileDialog(); bool defaultNameFilterDisables() const override; @@ -92,7 +97,8 @@ private Q_SLOTS: private: void initializeDialog(); - void openPortal(); + void openPortal(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent); + bool useNativeFileDialog(FallbackType fallbackType = GenericFallback) const; QScopedPointer d_ptr; }; diff --git a/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp index 2fc3167fd5..b809503122 100644 --- a/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp +++ b/qtbase/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp @@ -153,11 +153,12 @@ QPlatformDialogHelper* QXdgDesktopPortalTheme::createPlatformDialogHelper(Dialog { Q_D(const QXdgDesktopPortalTheme); - if (type == FileDialog) { + if (type == FileDialog && d->fileChooserPortalVersion) { // Older versions of FileChooser portal don't support opening directories, therefore we fallback // to native file dialog opened inside the sandbox to open a directory. - if (d->fileChooserPortalVersion < 3 && d->baseTheme->usePlatformNativeDialog(type)) - return new QXdgDesktopPortalFileDialog(static_cast(d->baseTheme->createPlatformDialogHelper(type))); + if (d->baseTheme->usePlatformNativeDialog(type)) + return new QXdgDesktopPortalFileDialog(static_cast(d->baseTheme->createPlatformDialogHelper(type)), + d->fileChooserPortalVersion); return new QXdgDesktopPortalFileDialog; } diff --git a/qtbase/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/qtbase/src/plugins/sqldrivers/mysql/qsql_mysql.cpp index 4427efb770..781dad8d0b 100644 --- a/qtbase/src/plugins/sqldrivers/mysql/qsql_mysql.cpp +++ b/qtbase/src/plugins/sqldrivers/mysql/qsql_mysql.cpp @@ -1363,20 +1363,20 @@ bool QMYSQLDriver::open(const QString& db, } #if MYSQL_VERSION_ID >= 50007 - if (mysql_get_client_version() >= 50503 && mysql_get_server_version(d->mysql) >= 50503) { - // force the communication to be utf8mb4 (only utf8mb4 supports 4-byte characters) - mysql_set_character_set(d->mysql, "utf8mb4"); + // force the communication to be utf8mb4 (only utf8mb4 supports 4-byte characters) + if (mysql_set_character_set(d->mysql, "utf8mb4")) { + // this failed, try forcing it to utf (BMP only) + if (mysql_set_character_set(d->mysql, "utf8")) + qWarning() << "MySQL: Unable to set the client character set to utf8."; #if QT_CONFIG(textcodec) - d->tc = QTextCodec::codecForName("UTF-8"); + else + d->tc = codec(d->mysql); #endif - } else - { - // force the communication to be utf8 - mysql_set_character_set(d->mysql, "utf8"); + } #if QT_CONFIG(textcodec) - d->tc = codec(d->mysql); + else + d->tc = QTextCodec::codecForName("UTF-8"); #endif - } #endif // MYSQL_VERSION_ID >= 50007 d->preparedQuerysEnabled = checkPreparedQueries(d->mysql); diff --git a/qtbase/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/qtbase/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 5f51de3843..6cac60d03d 100644 --- a/qtbase/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/qtbase/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -92,23 +92,39 @@ inline static QString fromSQLTCHAR(const QVarLengthArray& input, int s return result; } +template +void toSQLTCHARImpl(QVarLengthArray &result, const QString &input); // primary template undefined + +template +void do_append(QVarLengthArray &result, const Container &c) +{ + result.append(reinterpret_cast(c.data()), c.size()); +} + +template <> +void toSQLTCHARImpl<1>(QVarLengthArray &result, const QString &input) +{ + const auto u8 = input.toUtf8(); + do_append(result, u8); +} + +template <> +void toSQLTCHARImpl<2>(QVarLengthArray &result, const QString &input) +{ + do_append(result, input); +} + +template <> +void toSQLTCHARImpl<4>(QVarLengthArray &result, const QString &input) +{ + const auto u32 = input.toUcs4(); + do_append(result, u32); +} + inline static QVarLengthArray toSQLTCHAR(const QString &input) { QVarLengthArray 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))); - } + toSQLTCHARImpl(result, input); result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't. return result; } @@ -763,6 +779,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 +822,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(), - 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 +837,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(), - 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 +1040,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)); @@ -1371,9 +1392,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", @@ -1401,7 +1425,7 @@ bool QODBCResult::exec() SQLCloseCursor(d->hStmt); QVector& values = boundValues(); - QVector tmpStorage(values.count(), QByteArray()); // holds temporary buffers + QVector tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter() QVarLengthArray indicators(values.count()); memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN)); @@ -1580,35 +1604,36 @@ bool QODBCResult::exec() case QVariant::String: if (d->unicode) { QByteArray &ba = tmpStorage[i]; - QString str = val.toString(); + { + const auto encoded = toSQLTCHAR(val.toString()); + ba = QByteArray(reinterpret_cast(encoded.data()), + encoded.size() * sizeof(SQLTCHAR)); + } + if (*ind != SQL_NULL_DATA) - *ind = str.length() * sizeof(SQLTCHAR); - int strSize = str.length() * sizeof(SQLTCHAR); + *ind = ba.size(); if (bindValueType(i) & QSql::Out) { - const QVarLengthArray a(toSQLTCHAR(str)); - ba = QByteArray((const char *)a.constData(), 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(ba.constData()), // don't detach ba.size(), ind); break; } - ba = QByteArray ((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR)); 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(ba.constData()), + const_cast(ba.constData()), // don't detach ba.size(), ind); break; @@ -1716,10 +1741,11 @@ bool QODBCResult::exec() case QVariant::String: if (d->unicode) { if (bindValueType(i) & QSql::Out) { - const QByteArray &first = tmpStorage.at(i); - QVarLengthArray 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() / int(sizeof(SQLTCHAR)); + QVarLengthArray string(strSize); + memcpy(string.data(), bytes.data(), strSize * sizeof(SQLTCHAR)); + values[i] = fromSQLTCHAR(string); } break; } @@ -1966,14 +1992,16 @@ bool QODBCDriver::open(const QString & db, SQLSMALLINT cb; QVarLengthArray 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)); @@ -2352,17 +2380,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); @@ -2436,28 +2462,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); @@ -2538,15 +2566,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/qtbase/src/printsupport/dialogs/images/print-24.png b/qtbase/src/printsupport/dialogs/images/printer-24.png similarity index 100% rename from src/printsupport/dialogs/images/print-24.png rename to src/printsupport/dialogs/images/printer-24.png diff --git a/qtbase/src/printsupport/dialogs/images/print-32.png b/qtbase/src/printsupport/dialogs/images/printer-32.png similarity index 100% rename from src/printsupport/dialogs/images/print-32.png rename to src/printsupport/dialogs/images/printer-32.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-sided-24.png b/qtbase/src/printsupport/dialogs/images/view-pages-facing-24.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-sided-24.png rename to src/printsupport/dialogs/images/view-pages-facing-24.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-sided-32.png b/qtbase/src/printsupport/dialogs/images/view-pages-facing-32.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-sided-32.png rename to src/printsupport/dialogs/images/view-pages-facing-32.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-multi-24.png b/qtbase/src/printsupport/dialogs/images/view-pages-overview-24.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-multi-24.png rename to src/printsupport/dialogs/images/view-pages-overview-24.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-multi-32.png b/qtbase/src/printsupport/dialogs/images/view-pages-overview-32.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-multi-32.png rename to src/printsupport/dialogs/images/view-pages-overview-32.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-one-24.png b/qtbase/src/printsupport/dialogs/images/view-pages-single-24.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-one-24.png rename to src/printsupport/dialogs/images/view-pages-single-24.png diff --git a/qtbase/src/printsupport/dialogs/images/view-page-one-32.png b/qtbase/src/printsupport/dialogs/images/view-pages-single-32.png similarity index 100% rename from src/printsupport/dialogs/images/view-page-one-32.png rename to src/printsupport/dialogs/images/view-pages-single-32.png diff --git a/qtbase/src/printsupport/dialogs/images/fit-page-24.png b/qtbase/src/printsupport/dialogs/images/zoom-fit-page-24.png similarity index 100% rename from src/printsupport/dialogs/images/fit-page-24.png rename to src/printsupport/dialogs/images/zoom-fit-page-24.png diff --git a/qtbase/src/printsupport/dialogs/images/fit-page-32.png b/qtbase/src/printsupport/dialogs/images/zoom-fit-page-32.png similarity index 100% rename from src/printsupport/dialogs/images/fit-page-32.png rename to src/printsupport/dialogs/images/zoom-fit-page-32.png diff --git a/qtbase/src/printsupport/dialogs/images/fit-width-24.png b/qtbase/src/printsupport/dialogs/images/zoom-fit-width-24.png similarity index 100% rename from src/printsupport/dialogs/images/fit-width-24.png rename to src/printsupport/dialogs/images/zoom-fit-width-24.png diff --git a/qtbase/src/printsupport/dialogs/images/fit-width-32.png b/qtbase/src/printsupport/dialogs/images/zoom-fit-width-32.png similarity index 100% rename from src/printsupport/dialogs/images/fit-width-32.png rename to src/printsupport/dialogs/images/zoom-fit-width-32.png diff --git a/qtbase/src/printsupport/dialogs/qprintdialog.qrc b/qtbase/src/printsupport/dialogs/qprintdialog.qrc index 5a579baa55..10b8e1d341 100644 --- a/qtbase/src/printsupport/dialogs/qprintdialog.qrc +++ b/qtbase/src/printsupport/dialogs/qprintdialog.qrc @@ -1,9 +1,9 @@ -images/fit-page-24.png -images/fit-page-32.png -images/fit-width-24.png -images/fit-width-32.png +images/zoom-fit-page-24.png +images/zoom-fit-page-32.png +images/zoom-fit-width-24.png +images/zoom-fit-width-32.png images/go-first-24.png images/go-first-32.png images/go-last-24.png @@ -18,14 +18,14 @@ images/layout-portrait-32.png images/page-setup-24.png images/page-setup-32.png -images/print-24.png -images/print-32.png -images/view-page-multi-24.png -images/view-page-multi-32.png -images/view-page-one-24.png -images/view-page-one-32.png -images/view-page-sided-24.png -images/view-page-sided-32.png +images/printer-24.png +images/printer-32.png +images/view-pages-overview-24.png +images/view-pages-overview-32.png +images/view-pages-single-24.png +images/view-pages-single-32.png +images/view-pages-facing-24.png +images/view-pages-facing-32.png images/zoom-in-24.png images/zoom-in-32.png images/zoom-out-24.png diff --git a/qtbase/src/printsupport/dialogs/qprintpreviewdialog.cpp b/qtbase/src/printsupport/dialogs/qprintpreviewdialog.cpp index 39575d5f57..23b7e89538 100644 --- a/qtbase/src/printsupport/dialogs/qprintpreviewdialog.cpp +++ b/qtbase/src/printsupport/dialogs/qprintpreviewdialog.cpp @@ -352,7 +352,7 @@ void QPrintPreviewDialogPrivate::init(QPrinter *_printer) static inline void qt_setupActionIcon(QAction *action, QLatin1String name) { QLatin1String imagePrefix(":/qt-project.org/dialogs/qprintpreviewdialog/images/"); - QIcon icon; + QIcon icon = QIcon::fromTheme(name); icon.addFile(imagePrefix + name + QLatin1String("-24.png"), QSize(24, 24)); icon.addFile(imagePrefix + name + QLatin1String("-32.png"), QSize(32, 32)); action->setIcon(icon); @@ -383,8 +383,8 @@ void QPrintPreviewDialogPrivate::setupActions() fitPageAction->setObjectName(QLatin1String("fitPageAction")); fitWidthAction->setCheckable(true); fitPageAction->setCheckable(true); - qt_setupActionIcon(fitWidthAction, QLatin1String("fit-width")); - qt_setupActionIcon(fitPageAction, QLatin1String("fit-page")); + qt_setupActionIcon(fitWidthAction, QLatin1String("zoom-fit-width")); + qt_setupActionIcon(fitPageAction, QLatin1String("zoom-fit-page")); QObject::connect(fitGroup, SIGNAL(triggered(QAction*)), q, SLOT(_q_fit(QAction*))); // Zoom @@ -410,9 +410,9 @@ void QPrintPreviewDialogPrivate::setupActions() singleModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show single page")); facingModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show facing pages")); overviewModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show overview of all pages")); - qt_setupActionIcon(singleModeAction, QLatin1String("view-page-one")); - qt_setupActionIcon(facingModeAction, QLatin1String("view-page-sided")); - qt_setupActionIcon(overviewModeAction, QLatin1String("view-page-multi")); + qt_setupActionIcon(singleModeAction, QLatin1String("view-pages-single")); + qt_setupActionIcon(facingModeAction, QLatin1String("view-pages-facing")); + qt_setupActionIcon(overviewModeAction, QLatin1String("view-pages-overview")); singleModeAction->setObjectName(QLatin1String("singleModeAction")); facingModeAction->setObjectName(QLatin1String("facingModeAction")); overviewModeAction->setObjectName(QLatin1String("overviewModeAction")); @@ -426,7 +426,7 @@ void QPrintPreviewDialogPrivate::setupActions() printerGroup = new QActionGroup(q); printAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Print")); pageSetupAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Page setup")); - qt_setupActionIcon(printAction, QLatin1String("print")); + qt_setupActionIcon(printAction, QLatin1String("printer")); qt_setupActionIcon(pageSetupAction, QLatin1String("page-setup")); QObject::connect(printAction, SIGNAL(triggered(bool)), q, SLOT(_q_print())); QObject::connect(pageSetupAction, SIGNAL(triggered(bool)), q, SLOT(_q_pageSetup())); diff --git a/qtbase/src/testlib/qabstractitemmodeltester.cpp b/qtbase/src/testlib/qabstractitemmodeltester.cpp index 1cd18b98bb..41219a7e23 100644 --- a/qtbase/src/testlib/qabstractitemmodeltester.cpp +++ b/qtbase/src/testlib/qabstractitemmodeltester.cpp @@ -454,7 +454,7 @@ void QAbstractItemModelTesterPrivate::parent() // Common error test #2, make sure that a second level index has a parent // that is the first level index. - if (model->rowCount(topIndex) > 0) { + if (model->rowCount(topIndex) > 0 && model->columnCount(topIndex) > 0) { QModelIndex childIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(childIndex.isValid()); MODELTESTER_COMPARE(model->parent(childIndex), topIndex); diff --git a/qtbase/src/testlib/qasciikey.cpp b/qtbase/src/testlib/qasciikey.cpp index 9a308da2bc..93498b256f 100644 --- a/qtbase/src/testlib/qasciikey.cpp +++ b/qtbase/src/testlib/qasciikey.cpp @@ -498,6 +498,11 @@ char QTest::keyToAscii(Qt::Key key) case Qt::Key_LaunchE : return 0; // = 0x10b0, case Qt::Key_LaunchF : return 0; // = 0x10b1, + // Keypad navigation keys + case Qt::Key_Select : return 0; // = 0x01010000 + case Qt::Key_Yes : return 0; // = 0x01010001 + case Qt::Key_No : return 0; // = 0x01010002 + default: QTEST_ASSERT(false); return 0; } } diff --git a/qtbase/src/widgets/accessible/qaccessiblewidgets.cpp b/qtbase/src/widgets/accessible/qaccessiblewidgets.cpp index 574be1f5ea..b499d5d620 100644 --- a/qtbase/src/widgets/accessible/qaccessiblewidgets.cpp +++ b/qtbase/src/widgets/accessible/qaccessiblewidgets.cpp @@ -900,7 +900,7 @@ QString QAccessibleTextWidget::attributes(int offset, int *startOffset, int *end underlineStyleValue = QStringLiteral("wave"); // this is not correct, but provides good approximation at least break; default: - qWarning() << "Unknown QTextCharFormat::​UnderlineStyle value " << underlineStyle << " could not be translated to IAccessible2 value"; + qWarning() << "Unknown QTextCharFormat::UnderlineStyle value " << underlineStyle << " could not be translated to IAccessible2 value"; break; } if (!underlineStyleValue.isNull()) { diff --git a/qtbase/src/widgets/dialogs/qcolordialog.cpp b/qtbase/src/widgets/dialogs/qcolordialog.cpp index 4247731275..30445fa069 100644 --- a/qtbase/src/widgets/dialogs/qcolordialog.cpp +++ b/qtbase/src/widgets/dialogs/qcolordialog.cpp @@ -78,7 +78,10 @@ #include "qwindow.h" #include "private/qdialog_p.h" +#include "private/qguiapplication_p.h" +#include +#include #include QT_BEGIN_NAMESPACE @@ -801,6 +804,10 @@ QColorLuminancePicker::~QColorLuminancePicker() void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) { + if (m->buttons() == Qt::NoButton) { + m->ignore(); + return; + } setVal(y2val(m->y())); } void QColorLuminancePicker::mousePressEvent(QMouseEvent *m) @@ -935,6 +942,10 @@ void QColorPicker::setCol(int h, int s) void QColorPicker::mouseMoveEvent(QMouseEvent *m) { QPoint p = m->pos() - contentsRect().topLeft(); + if (m->buttons() == Qt::NoButton) { + m->ignore(); + return; + } setCol(p); emit newCol(hue, sat); } @@ -1611,6 +1622,20 @@ void QColorDialogPrivate::_q_newStandard(int r, int c) void QColorDialogPrivate::_q_pickScreenColor() { Q_Q(QColorDialog); + + auto *platformServices = QGuiApplicationPrivate::platformIntegration()->services(); + if (platformServices->hasCapability(QPlatformServices::Capability::ColorPicking)) { + if (auto *colorPicker = platformServices->colorPicker(q->windowHandle())) { + q->connect(colorPicker, &QPlatformServiceColorPicker::colorPicked, q, + [q, colorPicker](const QColor &color) { + colorPicker->deleteLater(); + q->setCurrentColor(color); + }); + colorPicker->pickColor(); + return; + } + } + if (!colorPickingEventFilter) colorPickingEventFilter = new QColorPickingEventFilter(this, q); q->installEventFilter(colorPickingEventFilter); diff --git a/qtbase/src/widgets/itemviews/qabstractitemdelegate.cpp b/qtbase/src/widgets/itemviews/qabstractitemdelegate.cpp index e120817edc..8ea36b5427 100644 --- a/qtbase/src/widgets/itemviews/qabstractitemdelegate.cpp +++ b/qtbase/src/widgets/itemviews/qabstractitemdelegate.cpp @@ -400,12 +400,7 @@ bool QAbstractItemDelegate::helpEvent(QHelpEvent *event, const QString tooltip = index.isValid() ? d->textForRole(Qt::ToolTipRole, index.data(Qt::ToolTipRole), option.locale, precision) : QString(); - QRect rect; - if (index.isValid()) { - const QRect r = view->visualRect(index); - rect = QRect(view->mapToGlobal(r.topLeft()), r.size()); - } - QToolTip::showText(he->globalPos(), tooltip, view, rect); + QToolTip::showText(he->globalPos(), tooltip, view->viewport(), option.rect); event->setAccepted(!tooltip.isEmpty()); break; } diff --git a/qtbase/src/widgets/itemviews/qabstractitemview.cpp b/qtbase/src/widgets/itemviews/qabstractitemview.cpp index deb49ca23d..93dc5ee80c 100644 --- a/qtbase/src/widgets/itemviews/qabstractitemview.cpp +++ b/qtbase/src/widgets/itemviews/qabstractitemview.cpp @@ -2347,11 +2347,12 @@ void QAbstractItemView::keyPressEvent(QKeyEvent *event) #if !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) if (event == QKeySequence::Copy) { - QVariant variant; - if (d->model) - variant = d->model->data(currentIndex(), Qt::DisplayRole); - if (variant.canConvert()) - QGuiApplication::clipboard()->setText(variant.toString()); + const QModelIndex index = currentIndex(); + if (index.isValid() && d->model) { + const QVariant variant = d->model->data(index, Qt::DisplayRole); + if (variant.canConvert()) + QGuiApplication::clipboard()->setText(variant.toString()); + } event->accept(); } #endif diff --git a/qtbase/src/widgets/itemviews/qlistview.cpp b/qtbase/src/widgets/itemviews/qlistview.cpp index 5a88f1b1ef..63f760269c 100644 --- a/qtbase/src/widgets/itemviews/qlistview.cpp +++ b/qtbase/src/widgets/itemviews/qlistview.cpp @@ -3388,6 +3388,7 @@ void QIconModeViewBase::updateContentsSize() */ void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QAbstractItemView::currentChanged(current, previous); #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { if (current.isValid()) { @@ -3398,7 +3399,6 @@ void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &pr } } #endif - QAbstractItemView::currentChanged(current, previous); } /*! diff --git a/qtbase/src/widgets/itemviews/qtableview.cpp b/qtbase/src/widgets/itemviews/qtableview.cpp index d120c41dc9..09d34005a7 100644 --- a/qtbase/src/widgets/itemviews/qtableview.cpp +++ b/qtbase/src/widgets/itemviews/qtableview.cpp @@ -1013,6 +1013,7 @@ void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItem & int QTableViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option) const { Q_Q(const QTableView); + const int oldHint = hint; QWidget *editor = editorForIndex(index).widget.data(); if (editor && persistent.contains(editor)) { hint = qMax(hint, editor->sizeHint().width()); @@ -1021,6 +1022,17 @@ int QTableViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, con hint = qBound(min, hint, max); } hint = qMax(hint, q->itemDelegate(index)->sizeHint(option, index).width()); + + if (hasSpans()) { + auto span = spans.spanAt(index.column(), index.row()); + if (span && span->m_left == index.column() && span->m_top == index.row()) { + // spans are screwed up when sections are moved + const auto left = logicalColumn(span->m_left); + for (int i = 1; i <= span->width(); ++i) + hint -= q->columnWidth(visualColumn(left + i)); + } + hint = std::max(hint, oldHint); + } return hint; } @@ -1053,6 +1065,11 @@ int QTableViewPrivate::heightHintForIndex(const QModelIndex &index, int hint, QS option.rect.setHeight(height); option.rect.setX(q->columnViewportPosition(index.column())); option.rect.setWidth(q->columnWidth(index.column())); + if (hasSpans()) { + auto span = spans.spanAt(index.column(), index.row()); + if (span && span->m_left == index.column() && span->m_top == index.row()) + option.rect.setWidth(std::max(option.rect.width(), visualSpanRect(*span).width())); + } // 1px less space when grid is shown (see drawCell) if (showGrid) option.rect.setWidth(option.rect.width() - 1); diff --git a/qtbase/src/widgets/kernel/qaction.h b/qtbase/src/widgets/kernel/qaction.h index 258a1ea0a0..737c1e8285 100644 --- a/qtbase/src/widgets/kernel/qaction.h +++ b/qtbase/src/widgets/kernel/qaction.h @@ -81,7 +81,7 @@ class Q_WIDGETS_EXPORT QAction : public QObject Q_PROPERTY(MenuRole menuRole READ menuRole WRITE setMenuRole NOTIFY changed) Q_PROPERTY(bool iconVisibleInMenu READ isIconVisibleInMenu WRITE setIconVisibleInMenu NOTIFY changed) Q_PROPERTY(bool shortcutVisibleInContextMenu READ isShortcutVisibleInContextMenu WRITE setShortcutVisibleInContextMenu NOTIFY changed) - Q_PROPERTY(Priority priority READ priority WRITE setPriority) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY changed) public: // note this is copied into qplatformmenu.h, which must stay in sync diff --git a/qtbase/src/widgets/kernel/qwidget.cpp b/qtbase/src/widgets/kernel/qwidget.cpp index e94520021e..d661114fc7 100644 --- a/qtbase/src/widgets/kernel/qwidget.cpp +++ b/qtbase/src/widgets/kernel/qwidget.cpp @@ -1272,7 +1272,6 @@ void QWidgetPrivate::create() win->setProperty("_q_showWithoutActivating", QVariant(true)); if (q->testAttribute(Qt::WA_MacAlwaysShowToolWindow)) win->setProperty("_q_macAlwaysShowToolWindow", QVariant(true)); - setNetWmWindowTypes(true); // do nothing if none of WA_X11NetWmWindowType* is set win->setFlags(flags); fixPosIncludesFrame(); if (q->testAttribute(Qt::WA_Moved) @@ -1345,6 +1344,7 @@ void QWidgetPrivate::create() Q_ASSERT(id != WId(0)); setWinId(id); } + setNetWmWindowTypes(true); // do nothing if none of WA_X11NetWmWindowType* is set // Check children and create windows for them if necessary q_createNativeChildrenAndSetParent(q); diff --git a/qtbase/src/widgets/styles/qcommonstyle.cpp b/qtbase/src/widgets/styles/qcommonstyle.cpp index 502a527901..a79c33005c 100644 --- a/qtbase/src/widgets/styles/qcommonstyle.cpp +++ b/qtbase/src/widgets/styles/qcommonstyle.cpp @@ -1708,8 +1708,9 @@ void QCommonStyle::drawControl(ControlElement element, const QStyleOption *opt, alignment |= Qt::TextHideMnemonic; rect.translate(shiftX, shiftY); p->setFont(toolbutton->font); + const QString text = d->toolButtonElideText(toolbutton, rect, alignment); proxy()->drawItemText(p, rect, alignment, toolbutton->palette, - opt->state & State_Enabled, toolbutton->text, + opt->state & State_Enabled, text, QPalette::ButtonText); } else { QPixmap pm; diff --git a/qtbase/src/widgets/styles/qfusionstyle.cpp b/qtbase/src/widgets/styles/qfusionstyle.cpp index a225d4b563..35e2769ac4 100644 --- a/qtbase/src/widgets/styles/qfusionstyle.cpp +++ b/qtbase/src/widgets/styles/qfusionstyle.cpp @@ -1772,14 +1772,6 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio proxy()->drawControl(CE_PushButtonLabel, &subopt, painter, widget); } break; - case CE_PushButtonLabel: - if (const QStyleOptionButton *button = qstyleoption_cast(option)) { - QStyleOptionButton b(*button); - // no PM_ButtonShiftHorizontal and PM_ButtonShiftVertical for fusion style - b.state &= ~(State_On | State_Sunken); - QCommonStyle::drawControl(element, &b, painter, widget); - } - break; case CE_MenuBarEmptyArea: painter->save(); { diff --git a/qtbase/src/widgets/styles/qstylesheetstyle.cpp b/qtbase/src/widgets/styles/qstylesheetstyle.cpp index 55d95c7fb2..8e9d2abbaa 100644 --- a/qtbase/src/widgets/styles/qstylesheetstyle.cpp +++ b/qtbase/src/widgets/styles/qstylesheetstyle.cpp @@ -4453,12 +4453,15 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op case PE_PanelLineEdit: if (const QStyleOptionFrame *frm = qstyleoption_cast(opt)) { - QWidget *container = containerWidget(w); - if (container != w) { - QRenderRule containerRule = renderRule(container, opt); - if (!containerRule.hasNativeBorder() || !containerRule.baseStyleCanDraw()) - return; - rule = containerRule; + // Fall back to container widget's render rule + if (w) { + QWidget *container = containerWidget(w); + if (container != w) { + QRenderRule containerRule = renderRule(container, opt); + if (!containerRule.hasNativeBorder() || !containerRule.baseStyleCanDraw()) + return; + rule = containerRule; + } } if (rule.hasNativeBorder()) { diff --git a/qtbase/src/widgets/widgets/qabstractbutton.cpp b/qtbase/src/widgets/widgets/qabstractbutton.cpp index a128b23950..dc40bf62fb 100644 --- a/qtbase/src/widgets/widgets/qabstractbutton.cpp +++ b/qtbase/src/widgets/widgets/qabstractbutton.cpp @@ -56,6 +56,7 @@ #ifndef QT_NO_ACCESSIBILITY #include "qaccessible.h" #endif +#include #include @@ -1076,19 +1077,19 @@ void QAbstractButton::keyPressEvent(QKeyEvent *e) { Q_D(QAbstractButton); bool next = true; - switch (e->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: - e->ignore(); - break; - case Qt::Key_Select: - case Qt::Key_Space: - if (!e->isAutoRepeat()) { - setDown(true); - repaint(); - d->emitPressed(); - } - break; + + const auto key = static_cast(e->key()); + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(key) && !e->isAutoRepeat()) { + setDown(true); + repaint(); + d->emitPressed(); + return; + } + + switch (key) { case Qt::Key_Up: next = false; Q_FALLTHROUGH(); @@ -1153,15 +1154,15 @@ void QAbstractButton::keyReleaseEvent(QKeyEvent *e) if (!e->isAutoRepeat()) d->repeatTimer.stop(); - switch (e->key()) { - case Qt::Key_Select: - case Qt::Key_Space: - if (!e->isAutoRepeat() && d->down) - d->click(); - break; - default: - e->ignore(); + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(static_cast(e->key())) && !e->isAutoRepeat() && d->down) { + d->click(); + return; } + + e->ignore(); } /*!\reimp diff --git a/qtbase/src/widgets/widgets/qcombobox.cpp b/qtbase/src/widgets/widgets/qcombobox.cpp index 7a496c27e0..0a3d96647b 100644 --- a/qtbase/src/widgets/widgets/qcombobox.cpp +++ b/qtbase/src/widgets/widgets/qcombobox.cpp @@ -3352,7 +3352,23 @@ void QComboBox::keyPressEvent(QKeyEvent *e) Move move = NoMove; int newIndex = currentIndex(); - switch (e->key()) { + + bool pressLikeButton = !d->lineEdit; +#ifdef QT_KEYPAD_NAVIGATION + pressLikeButton |= QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus(); +#endif + auto key = static_cast(e->key()); + if (pressLikeButton) { + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(key)) { + showPopup(); + return; + } + } + + switch (key) { case Qt::Key_Up: if (e->modifiers() & Qt::ControlModifier) break; // pass to line edit for auto completion @@ -3394,26 +3410,11 @@ void QComboBox::keyPressEvent(QKeyEvent *e) return; } break; - case Qt::Key_Space: - if (!d->lineEdit) { - showPopup(); - return; - } - break; - case Qt::Key_Enter: - case Qt::Key_Return: case Qt::Key_Escape: if (!d->lineEdit) e->ignore(); break; #ifdef QT_KEYPAD_NAVIGATION - case Qt::Key_Select: - if (QApplicationPrivate::keypadNavigationEnabled() - && (!hasEditFocus() || !d->lineEdit)) { - showPopup(); - return; - } - break; case Qt::Key_Left: case Qt::Key_Right: if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) diff --git a/qtbase/src/widgets/widgets/qdatetimeedit_p.h b/qtbase/src/widgets/widgets/qdatetimeedit_p.h index d36b6f8f9a..e0df5b5158 100644 --- a/qtbase/src/widgets/widgets/qdatetimeedit_p.h +++ b/qtbase/src/widgets/widgets/qdatetimeedit_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWidgets module of the Qt Toolkit. diff --git a/qtbase/src/widgets/widgets/qgroupbox.cpp b/qtbase/src/widgets/widgets/qgroupbox.cpp index 02a0bed325..3f3eccc370 100644 --- a/qtbase/src/widgets/widgets/qgroupbox.cpp +++ b/qtbase/src/widgets/widgets/qgroupbox.cpp @@ -54,6 +54,8 @@ #include "qaccessible.h" #endif #include +#include +#include #include "qdebug.h" @@ -360,7 +362,10 @@ bool QGroupBox::event(QEvent *e) return true; case QEvent::KeyPress: { QKeyEvent *k = static_cast(e); - if (!k->isAutoRepeat() && (k->key() == Qt::Key_Select || k->key() == Qt::Key_Space)) { + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (!k->isAutoRepeat() && buttonPressKeys.contains(static_cast(k->key()))) { d->pressedControl = QStyle::SC_GroupBoxCheckBox; update(style()->subControlRect(QStyle::CC_GroupBox, &box, QStyle::SC_GroupBoxCheckBox, this)); return true; @@ -369,7 +374,10 @@ bool QGroupBox::event(QEvent *e) } case QEvent::KeyRelease: { QKeyEvent *k = static_cast(e); - if (!k->isAutoRepeat() && (k->key() == Qt::Key_Select || k->key() == Qt::Key_Space)) { + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (!k->isAutoRepeat() && buttonPressKeys.contains(static_cast(k->key()))) { bool toggle = (d->pressedControl == QStyle::SC_GroupBoxLabel || d->pressedControl == QStyle::SC_GroupBoxCheckBox); d->pressedControl = QStyle::SC_None; diff --git a/qtbase/src/widgets/widgets/qtoolbutton.cpp b/qtbase/src/widgets/widgets/qtoolbutton.cpp index e380cb647b..9953db73af 100644 --- a/qtbase/src/widgets/widgets/qtoolbutton.cpp +++ b/qtbase/src/widgets/widgets/qtoolbutton.cpp @@ -982,7 +982,15 @@ QAction *QToolButton::defaultAction() const return d->defaultAction; } - +/*! + \reimp + */ +void QToolButton::checkStateSet() +{ + Q_D(QToolButton); + if (d->defaultAction && d->defaultAction->isCheckable()) + d->defaultAction->setChecked(isChecked()); +} /*! \reimp diff --git a/qtbase/src/widgets/widgets/qtoolbutton.h b/qtbase/src/widgets/widgets/qtoolbutton.h index 52bd2d5f7a..82b5d7924f 100644 --- a/qtbase/src/widgets/widgets/qtoolbutton.h +++ b/qtbase/src/widgets/widgets/qtoolbutton.h @@ -118,6 +118,7 @@ protected: void changeEvent(QEvent *) override; bool hitButton(const QPoint &pos) const override; + void checkStateSet() override; void nextCheckState() override; void initStyleOption(QStyleOptionToolButton *option) const; diff --git a/qtbase/src/xml/sax/qxml.cpp b/qtbase/src/xml/sax/qxml.cpp index 7a444713c1..9076dcfd84 100644 --- a/qtbase/src/xml/sax/qxml.cpp +++ b/qtbase/src/xml/sax/qxml.cpp @@ -7464,7 +7464,12 @@ bool QXmlSimpleReaderPrivate::parseReference() case DoneD: tmp = ref().toUInt(&ok, 10); if (ok) { - stringAddC(QChar(tmp)); + if (tmp > 0xffff) { + stringAddC(QChar::highSurrogate(tmp)); + stringAddC(QChar::lowSurrogate(tmp)); + } else { + stringAddC(QChar(tmp)); + } } else { reportParseError(QLatin1String(XMLERR_ERRORPARSINGREFERENCE)); return false; @@ -7475,7 +7480,12 @@ bool QXmlSimpleReaderPrivate::parseReference() case DoneH: tmp = ref().toUInt(&ok, 16); if (ok) { - stringAddC(QChar(tmp)); + if (tmp > 0xffff) { + stringAddC(QChar::highSurrogate(tmp)); + stringAddC(QChar::lowSurrogate(tmp)); + } else { + stringAddC(QChar(tmp)); + } } else { reportParseError(QLatin1String(XMLERR_ERRORPARSINGREFERENCE)); return false; diff --git a/qtbase/sync.profile b/qtbase/sync.profile index 0292bf0dc2..7dd0177b90 100644 --- a/qtbase/sync.profile +++ b/qtbase/sync.profile @@ -77,7 +77,7 @@ "qsql.h" => "QtSql/qtsqlglobal.h" }, "QtDBus" => { - "qdbusmacros.h" => "QtDbus/qtdbusglobal.h" + "qdbusmacros.h" => "QtDBus/qtdbusglobal.h" }, "QtTest" => { "qtest_global.h" => "QtTest/qttestglobal.h" diff --git a/qtbase/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp b/qtbase/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp index e1ea7a4552..90972caa57 100644 --- a/qtbase/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp +++ b/qtbase/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp @@ -117,6 +117,7 @@ private Q_SLOTS: void shouldPropagateDropAfterLastRow_data(); void shouldPropagateDropAfterLastRow(); void qtbug91788(); + void qtbug91878(); private: QStandardItemModel mod; @@ -843,6 +844,22 @@ void tst_QConcatenateTablesProxyModel::qtbug91788() QCOMPARE(proxyConcat.columnCount(), 0); } +void tst_QConcatenateTablesProxyModel::qtbug91878() +{ + QStandardItemModel m; + m.setRowCount(4); + m.setColumnCount(4); + + QConcatenateTablesProxyModel pm; + QSortFilterProxyModel proxyFilter; + proxyFilter.setSourceModel(&pm); + proxyFilter.setFilterFixedString("something"); + pm.addSourceModel(&m); // This should not assert + + QCOMPARE(pm.columnCount(), 4); + QCOMPARE(pm.rowCount(), 4); +} + QTEST_GUILESS_MAIN(tst_QConcatenateTablesProxyModel) #include "tst_qconcatenatetablesproxymodel.moc" diff --git a/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/testdata.qrc b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/testdata.qrc index 1002d0195d..d5774a0213 100644 --- a/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/testdata.qrc +++ b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/testdata.qrc @@ -3,6 +3,7 @@ yast2-metapackage-handler-mimetypes.xml qml-again.xml text-x-objcsrc.xml + text-plain-subclass.xml test.qml invalid-magic1.xml invalid-magic2.xml diff --git a/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/text-plain-subclass.xml b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/text-plain-subclass.xml new file mode 100644 index 0000000000..7b5cb7506d --- /dev/null +++ b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/text-plain-subclass.xml @@ -0,0 +1,15 @@ + + + + MicroDVD subtitles + + + + + + + + + + + diff --git a/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp index 0ea422ecbc..4eb21b7659 100644 --- a/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp +++ b/qtbase/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp @@ -49,6 +49,7 @@ static const char *const additionalMimeFiles[] = { "yast2-metapackage-handler-mimetypes.xml", "qml-again.xml", "text-x-objcsrc.xml", + "text-plain-subclass.xml", "invalid-magic1.xml", "invalid-magic2.xml", "invalid-magic3.xml", @@ -70,15 +71,15 @@ static inline QString testSuiteWarning() QString result; QTextStream str(&result); - str << "\nCannot find the shared-mime-info test suite\nstarting from: " + str << "\nCannot find the shared-mime-info test suite\nin the parent of: " << QDir::toNativeSeparators(QDir::currentPath()) << "\n" "cd " << QDir::toNativeSeparators(QStringLiteral("tests/auto/corelib/mimetypes/qmimedatabase")) << "\n" - "wget http://cgit.freedesktop.org/xdg/shared-mime-info/snapshot/Release-1-10.zip\n" - "unzip Release-1-10.zip\n"; + "wget https://gitlab.freedesktop.org/xdg/shared-mime-info/-/archive/2.1/shared-mime-info-2.1.zip\n" + "unzip shared-mime-info-2.1.zip\n"; #ifdef Q_OS_WIN - str << "mkdir testfiles\nxcopy /s Release-1-10 s-m-i\n"; + str << "mkdir testfiles\nxcopy /s shared-mime-info-2.1 s-m-i\n"; #else - str << "ln -s Release-1-10 s-m-i\n"; + str << "ln -s shared-mime-info-2.1 s-m-i\n"; #endif return result; } @@ -154,7 +155,7 @@ void tst_QMimeDatabase::initTestCase() QVERIFY2(copyResourceFile(xmlFileName, xmlTargetFileName, &errorMessage), qPrintable(errorMessage)); #endif - m_testSuite = QFINDTESTDATA("s-m-i/tests"); + m_testSuite = QFINDTESTDATA("s-m-i/tests/mime-detection"); if (m_testSuite.isEmpty()) qWarning("%s", qPrintable(testSuiteWarning())); @@ -611,7 +612,7 @@ void tst_QMimeDatabase::allMimeTypes() QVERIFY(!lst.isEmpty()); // Hardcoding this is the only way to check both providers find the same number of mimetypes. - QCOMPARE(lst.count(), 779); + QCOMPARE(lst.count(), 811); foreach (const QMimeType &mime, lst) { const QString name = mime.name(); @@ -631,10 +632,9 @@ void tst_QMimeDatabase::suffixes_data() QTest::newRow("mimetype with a single pattern") << "application/pdf" << "*.pdf" << "pdf"; QTest::newRow("mimetype with multiple patterns") << "application/x-kpresenter" << "*.kpr;*.kpt" << "kpr"; - QTest::newRow("jpeg") << "image/jpeg" << "*.jpe;*.jpg;*.jpeg" << "jpeg"; - //if (KMimeType::sharedMimeInfoVersion() > KDE_MAKE_VERSION(0, 60, 0)) { - QTest::newRow("mimetype with many patterns") << "application/vnd.wordperfect" << "*.wp;*.wp4;*.wp5;*.wp6;*.wpd;*.wpp" << "wp"; - //} + // The preferred suffix for image/jpeg is *.jpg, as per https://bugs.kde.org/show_bug.cgi?id=176737 + QTest::newRow("jpeg") << "image/jpeg" << "*.jpe;*.jpg;*.jpeg" << "jpg"; + QTest::newRow("mimetype with many patterns") << "application/vnd.wordperfect" << "*.wp;*.wp4;*.wp5;*.wp6;*.wpd;*.wpp" << "wp"; QTest::newRow("oasis text mimetype") << "application/vnd.oasis.opendocument.text" << "*.odt" << "odt"; QTest::newRow("oasis presentation mimetype") << "application/vnd.oasis.opendocument.presentation" << "*.odp" << "odp"; QTest::newRow("mimetype with multiple patterns") << "text/plain" << "*.asc;*.txt;*,v" << "txt"; @@ -1070,6 +1070,8 @@ void tst_QMimeDatabase::installNewLocalMimeType() QVERIFY(objcsrc.isValid()); QCOMPARE(objcsrc.globPatterns(), QStringList()); } + QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.txt"), QMimeDatabase::MatchExtension).name(), + QString::fromLatin1("text/plain")); // Test that a double-definition of a mimetype doesn't lead to sniffing ("conflicting globs"). const QString qmlTestFile = QLatin1String(RESOURCE_PREFIX "test.qml"); diff --git a/qtbase/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/qtbase/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index 533fb1c8aa..63ce77d67f 100644 --- a/qtbase/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/qtbase/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -75,6 +75,7 @@ private slots: void arrayStringElements(); void arraySelfAssign_data() { basics_data(); } void arraySelfAssign(); + void arrayNested(); void mapDefaultInitialization(); void mapEmptyInitializerList(); @@ -93,6 +94,7 @@ private slots: void mapSelfAssign(); void mapComplexKeys_data() { basics_data(); } void mapComplexKeys(); + void mapNested(); void sorting(); @@ -1570,6 +1572,91 @@ void tst_QCborValue::mapComplexKeys() QVERIFY(!m.contains(tagged)); } +void tst_QCborValue::arrayNested() +{ + const QCborArray wrongArray = { false, nullptr, QCborValue() }; + { + QCborArray a1 = { 42, 47 }; + QCborArray a2 = { QCborValue(a1) }; + QCOMPARE(a2.size(), 1); + const QCborValue &first = qAsConst(a2).first(); + QVERIFY(first.isArray()); + QCOMPARE(first.toArray(wrongArray).size(), 2); + QCOMPARE(first.toArray(wrongArray).first(), 42); + QCOMPARE(first.toArray(wrongArray).last(), 47); + } + { + QCborArray a1 = { 42, 47 }; + QCborArray a2 = { QCborValue(a1) }; + QCOMPARE(a2.size(), 1); + QCborValueRef first = a2.first(); + QVERIFY(first.isArray()); + QCOMPARE(first.toArray(wrongArray).size(), 2); + QCOMPARE(first.toArray(wrongArray).first(), 42); + QCOMPARE(first.toArray(wrongArray).last(), 47); + } + + { + QCborArray a1; + a1 = { QCborValue(a1) }; // insert it into itself + QCOMPARE(a1.size(), 1); + const QCborValue &first = qAsConst(a1).first(); + QVERIFY(first.isArray()); + QCOMPARE(first, QCborArray()); + QCOMPARE(first.toArray(wrongArray), QCborArray()); + } + { + QCborArray a1; + a1 = { QCborValue(a1) }; // insert it into itself + QCborValueRef first = a1.first(); + QVERIFY(first.isArray()); + QCOMPARE(first, QCborArray()); + QCOMPARE(first.toArray(wrongArray), QCborArray()); + } + { + QCborArray a1; + a1.append(a1); // insert into itself + QCOMPARE(a1.size(), 1); + const QCborValue &first = qAsConst(a1).first(); + QVERIFY(first.isArray()); + QCOMPARE(first, QCborArray()); + QCOMPARE(first.toArray(), QCborArray()); + } + { + QCborArray a1; + a1.append(a1); // insert into itself + QCborValueRef first = a1.first(); + QVERIFY(first.isArray()); + QCOMPARE(first, QCborArray()); + QCOMPARE(first.toArray(), QCborArray()); + } +} + +void tst_QCborValue::mapNested() +{ + const QCborMap wrongMap = { { -1, false }, {-2, nullptr }, { -3, QCborValue() } }; + { + QCborMap m1 = { {1, 42}, {2, 47} }; + QCborMap m2 = { { QString(), m1 } }; + QCOMPARE(m2.size(), 1); + const QCborValue &first = m2.constBegin().value(); + QVERIFY(first.isMap()); + QCOMPARE(first.toMap(wrongMap).size(), 2); + QCOMPARE(first.toMap(wrongMap).begin().key(), 1); + QCOMPARE(first.toMap(wrongMap).begin().value(), 42); + } + + { + QCborMap m1; + m1 = { { QString(), QCborValue(m1) } }; // insert it into itself + QCOMPARE(m1.size(), 1); + const QCborValue &first = m1.constBegin().value(); + QVERIFY(first.isMap()); + QCOMPARE(first, QCborMap()); + QCOMPARE(first.toMap(wrongMap), QCborMap()); + } +} + void tst_QCborValue::sorting() { QCborValue vundef, vnull(nullptr); diff --git a/qtbase/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp b/qtbase/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp index 533fcd96f5..513ba9baa9 100644 --- a/qtbase/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +++ b/qtbase/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp @@ -42,6 +42,7 @@ #include "qc14n.h" +Q_DECLARE_METATYPE(QXmlStreamReader::Error) Q_DECLARE_METATYPE(QXmlStreamReader::ReadElementTextBehaviour) static const char *const catalogFile = "XML-Test-Suite/xmlconf/finalCatalog.xml"; @@ -587,6 +588,8 @@ private slots: void readBack() const; void roundTrip() const; void roundTrip_data() const; + void test_fastScanName_data() const; + void test_fastScanName() const; void entityExpansionLimit() const; @@ -1842,5 +1845,42 @@ void tst_QXmlStream::roundTrip() const QCOMPARE(out, in); } +void tst_QXmlStream::test_fastScanName_data() const +{ + QTest::addColumn("data"); + QTest::addColumn("errorType"); + + // 4096 is the limit in QXmlStreamReaderPrivate::fastScanName() + + QByteArray arr = "> oslo >> offset >> invalid >> minusone; + QCOMPARE(oslo, QTimeZone("Europe/Oslo")); + QCOMPARE(offset, QTimeZone(420)); + QVERIFY(!invalid.isValid()); + QCOMPARE(minusone, qint64(-1)); + parts++; +#endif + if (!parts) + QSKIP("No serialization enabled"); +} + void tst_QTimeZone::utcTest() { #ifdef QT_BUILD_INTERNAL diff --git a/qtbase/tests/auto/gui/kernel/qkeysequence/tst_qkeysequence.cpp b/qtbase/tests/auto/gui/kernel/qkeysequence/tst_qkeysequence.cpp index 874468c954..04ceb4ab65 100644 --- a/qtbase/tests/auto/gui/kernel/qkeysequence/tst_qkeysequence.cpp +++ b/qtbase/tests/auto/gui/kernel/qkeysequence/tst_qkeysequence.cpp @@ -507,6 +507,10 @@ void tst_QKeySequence::toStringFromKeycode_data() QTest::newRow("Ctrl+Alt+Num+Del") << QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::KeypadModifier | Qt::Key_Delete) << "Ctrl+Alt+Num+Del"; QTest::newRow("Ctrl+Ins") << QKeySequence(Qt::ControlModifier | Qt::Key_Insert) << "Ctrl+Ins"; QTest::newRow("Ctrl+Num+Ins(1)") << QKeySequence(Qt::Key_Insert | Qt::KeypadModifier | Qt::ControlModifier) << "Ctrl+Num+Ins"; + QTest::newRow("Ctrl") << QKeySequence(Qt::Key_Control) << "Control"; + QTest::newRow("Alt") << QKeySequence(Qt::Key_Alt) << "Alt"; + QTest::newRow("Shift") << QKeySequence(Qt::Key_Shift) << "Shift"; + QTest::newRow("Meta") << QKeySequence(Qt::Key_Meta) << "Meta"; } void tst_QKeySequence::toStringFromKeycode() diff --git a/qtbase/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp b/qtbase/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp index 15e0ecadaa..b4eca74283 100644 --- a/qtbase/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp +++ b/qtbase/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp @@ -81,6 +81,8 @@ private slots: void registerOpenTypePreferredNamesSystem(); void registerOpenTypePreferredNamesApplication(); + void stretchRespected(); + private: QString m_ledFont; QString m_testFont; @@ -355,6 +357,28 @@ static QString testString() return QStringLiteral("foo bar"); } +void tst_QFontDatabase::stretchRespected() +{ + int italicId = QFontDatabase::addApplicationFont(m_testFontItalic); + QVERIFY(italicId != -1); + + QVERIFY(!QFontDatabase::applicationFontFamilies(italicId).isEmpty()); + + QString italicFontName = QFontDatabase::applicationFontFamilies(italicId).first(); + + QFont italicFont = QFontDatabase().font(italicFontName, + QString::fromLatin1("Italic"), 14); + QVERIFY(italicFont.italic()); + + QFont italicStretchedFont = italicFont; + italicStretchedFont.setStretch( 400 ); + + QVERIFY(QFontMetricsF(italicFont).horizontalAdvance(QStringLiteral("foobar")) < + QFontMetricsF(italicStretchedFont).horizontalAdvance(QStringLiteral("foobar"))); + + QFontDatabase::removeApplicationFont(italicId); +} + void tst_QFontDatabase::condensedFontWidthNoFontMerging() { int regularFontId = QFontDatabase::addApplicationFont(m_testFont); diff --git a/qtbase/tests/auto/other/qaccessibilitylinux/tst_qaccessibilitylinux.cpp b/qtbase/tests/auto/other/qaccessibilitylinux/tst_qaccessibilitylinux.cpp index 7ba3715e13..752aa122f6 100644 --- a/qtbase/tests/auto/other/qaccessibilitylinux/tst_qaccessibilitylinux.cpp +++ b/qtbase/tests/auto/other/qaccessibilitylinux/tst_qaccessibilitylinux.cpp @@ -179,6 +179,7 @@ void tst_QAccessibilityLinux::initTestCase() QVERIFY(!address.isEmpty()); m_window = new AccessibleTestWindow(); + m_window->setObjectName("mainWindow"_L1); m_window->show(); QVERIFY(QTest::qWaitForWindowExposed(m_window)); @@ -236,8 +237,11 @@ bool hasState(QDBusInterface *interface, AtspiStateType state) void tst_QAccessibilityLinux::testLabel() { QLabel *l = new QLabel(m_window); + l->setObjectName("theObjectName"_L1); l->setText("Hello A11y"); m_window->addWidget(l); + auto a11yEmpty = new QLabel(m_window); + m_window->addWidget(l); // Application QCOMPARE(getParent(mainWindow), QLatin1String(ATSPI_DBUS_PATH_ROOT)); @@ -249,6 +253,8 @@ void tst_QAccessibilityLinux::testLabel() QCOMPARE(getChildren(labelInterface).count(), 0); QCOMPARE(labelInterface->call(QDBus::Block, "GetRoleName").arguments().first().toString(), QLatin1String("label")); QCOMPARE(labelInterface->call(QDBus::Block, "GetRole").arguments().first().toUInt(), 29u); + QCOMPARE(labelInterface->call(QDBus::Block, "GetAccessibleId").arguments().first().toString(), + QLatin1String("mainWindow.theObjectName")); QCOMPARE(getParent(labelInterface), mainWindow->path()); QVERIFY(!hasState(labelInterface, ATSPI_STATE_EDITABLE)); QVERIFY(hasState(labelInterface, ATSPI_STATE_READ_ONLY)); @@ -256,7 +262,12 @@ void tst_QAccessibilityLinux::testLabel() l->setText("New text"); QCOMPARE(labelInterface->property("Name").toString(), l->text()); + auto *a11yEmptyInterface = getInterface(children.at(1), "org.a11y.atspi.Accessible"); + QCOMPARE(a11yEmptyInterface->call(QDBus::Block, "GetAccessibleId").arguments().first().toString(), + QLatin1String("mainWindow.QLabel")); + m_window->clearChildren(); + delete a11yEmptyInterface; delete labelInterface; } diff --git a/qtbase/tests/auto/testlib/qabstractitemmodeltester/tst_qabstractitemmodeltester.cpp b/qtbase/tests/auto/testlib/qabstractitemmodeltester/tst_qabstractitemmodeltester.cpp index 4aa3f8d60b..d2050a61aa 100644 --- a/qtbase/tests/auto/testlib/qabstractitemmodeltester/tst_qabstractitemmodeltester.cpp +++ b/qtbase/tests/auto/testlib/qabstractitemmodeltester/tst_qabstractitemmodeltester.cpp @@ -115,6 +115,10 @@ void tst_QAbstractItemModelTester::standardItemModelZeroColumns() // QTBUG-92886 model.insertRows(0, 5); model.removeRows(1, 2); + + const QModelIndex parentIndex = model.index(0, 0); + model.insertRows(0, 5, parentIndex); + model.removeRows(1, 2, parentIndex); } void tst_QAbstractItemModelTester::testInsertThroughProxy() diff --git a/qtbase/tests/auto/tools/moc/allmocs_baseline_in.json b/qtbase/tests/auto/tools/moc/allmocs_baseline_in.json index 18282505e4..ce518e78fb 100644 --- a/qtbase/tests/auto/tools/moc/allmocs_baseline_in.json +++ b/qtbase/tests/auto/tools/moc/allmocs_baseline_in.json @@ -668,40 +668,6 @@ "inputFile": "task192552.h", "outputRevision": 67 }, - { - "classes": [ - { - "className": "InlineSlotsWithThrowDeclaration", - "object": true, - "qualifiedClassName": "InlineSlotsWithThrowDeclaration", - "slots": [ - { - "access": "public", - "name": "a", - "returnType": "void" - }, - { - "access": "public", - "name": "b", - "returnType": "void" - }, - { - "access": "public", - "name": "c", - "returnType": "void" - } - ], - "superClasses": [ - { - "access": "public", - "name": "QObject" - } - ] - } - ], - "inputFile": "task189996.h", - "outputRevision": 67 - }, { "classes": [ { diff --git a/qtbase/tests/auto/tools/moc/moc.pro b/qtbase/tests/auto/tools/moc/moc.pro index c324b3a8cd..4aceb78dc0 100644 --- a/qtbase/tests/auto/tools/moc/moc.pro +++ b/qtbase/tests/auto/tools/moc/moc.pro @@ -15,7 +15,7 @@ cross_compile: DEFINES += MOC_CROSS_COMPILED HEADERS += using-namespaces.h no-keywords.h task87883.h c-comments.h backslash-newlines.h oldstyle-casts.h \ slots-with-void-template.h qinvokable.h namespaced-flags.h trigraphs.h \ escapes-in-string-literals.h cstyle-enums.h qprivateslots.h gadgetwithnoenums.h \ - dir-in-include-path.h single_function_keyword.h task192552.h task189996.h \ + dir-in-include-path.h single_function_keyword.h task192552.h \ task234909.h task240368.h pure-virtual-signals.h cxx11-enums.h \ cxx11-final-classes.h \ cxx11-explicit-override-control.h \ diff --git a/qtbase/tests/auto/tools/moc/task189996.h b/qtbase/tests/auto/tools/moc/task189996.h deleted file mode 100644 index ba9450c271..0000000000 --- a/qtbase/tests/auto/tools/moc/task189996.h +++ /dev/null @@ -1,45 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -// inline functions can have throw declarations - -#ifndef TASK189996_H -#define TASK189996_H - -#include - -class InlineSlotsWithThrowDeclaration : public QObject -{ - Q_OBJECT - -public slots: - void a() noexcept { } - void b() const noexcept { } - void c() noexcept; -}; - -#endif diff --git a/qtbase/tests/auto/tools/moc/tst_moc.cpp b/qtbase/tests/auto/tools/moc/tst_moc.cpp index cc465a213a..a9ab6ec4f3 100644 --- a/qtbase/tests/auto/tools/moc/tst_moc.cpp +++ b/qtbase/tests/auto/tools/moc/tst_moc.cpp @@ -671,7 +671,6 @@ private slots: void templateGtGt(); void qprivateslots(); void qprivateproperties(); - void inlineSlotsWithThrowDeclaration(); void warnOnPropertyWithoutREAD(); void constructors(); void typenameWithUnsigned(); @@ -816,7 +815,7 @@ void tst_Moc::oldStyleCasts() QStringList args; args << "-c" << "-x" << "c++" << "-Wold-style-cast" << "-I" << "." - << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++11" << "-"; + << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++1z" << "-"; proc.start("gcc", args); QVERIFY(proc.waitForStarted()); proc.write(mocOut); @@ -886,7 +885,7 @@ void tst_Moc::inputFileNameWithDotsButNoExtension() QStringList args; args << "-c" << "-x" << "c++" << "-I" << ".." - << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++11" << "-"; + << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++1z" << "-"; proc.start("gcc", args); QVERIFY(proc.waitForStarted()); proc.write(mocOut); @@ -1166,7 +1165,7 @@ void tst_Moc::ignoreOptionClashes() QStringList gccArgs; gccArgs << "-c" << "-x" << "c++" << "-I" << ".." << "-I" << qtIncludePath << "-I" << includeDir << "-o" << "/dev/null" - << "-fPIC" << "-std=c++11" << "-"; + << "-fPIC" << "-std=c++1z" << "-"; proc.start("gcc", gccArgs); QVERIFY(proc.waitForStarted()); proc.write(mocOut); @@ -1585,19 +1584,6 @@ void tst_Moc::qprivateproperties() } -#include "task189996.h" - -void InlineSlotsWithThrowDeclaration::c() noexcept {} - -void tst_Moc::inlineSlotsWithThrowDeclaration() -{ - InlineSlotsWithThrowDeclaration tst; - const QMetaObject *mobj = tst.metaObject(); - QVERIFY(mobj->indexOfSlot("a()") != -1); - QVERIFY(mobj->indexOfSlot("b()") != -1); - QVERIFY(mobj->indexOfSlot("c()") != -1); -} - void tst_Moc::warnOnPropertyWithoutREAD() { #ifdef MOC_CROSS_COMPILED @@ -1859,7 +1845,7 @@ void tst_Moc::notifyError() QStringList args; args << "-c" << "-x" << "c++" << "-I" << "." - << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++11" << "-"; + << "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++1z" << "-"; proc.start("gcc", args); QVERIFY(proc.waitForStarted()); proc.write(mocOut); diff --git a/qtbase/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp b/qtbase/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp index 761357b252..06bb706074 100644 --- a/qtbase/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp +++ b/qtbase/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp @@ -397,6 +397,7 @@ private slots: void checkHeaderMinSize(); void resizeToContents(); + void resizeToContentsSpans(); void tabFocus(); void bigModel(); @@ -3721,6 +3722,70 @@ void tst_QTableView::resizeToContents() } + +class SpanModel : public QAbstractTableModel +{ +public: + SpanModel(bool sectionsMoved) + : _sectionsMoved(sectionsMoved) + {} + int columnCount(const QModelIndex & = {}) const override { return 2; } + int rowCount(const QModelIndex & = {}) const override { return 1; } + QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override + { + if (role != Qt::DisplayRole) + return QVariant(); + const int col = _sectionsMoved ? 1 - idx.column() : idx.column(); + if (col == 0) + return "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + return QVariant(); + } +private: + bool _sectionsMoved; +}; + + +void tst_QTableView::resizeToContentsSpans() +{ + SpanModel model1(false); + SpanModel model2(true); + QTableView view1, view2, view3; + view1.setModel(&model1); + view2.setModel(&model2); + view2.horizontalHeader()->moveSection(0, 1); + view3.setModel(&model1); + + view1.setSpan(0, 0, 1, 2); + view2.setSpan(0, 1, 1, 2); + view1.show(); + view2.show(); + view3.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view1)); + QVERIFY(QTest::qWaitForWindowExposed(&view2)); + QVERIFY(QTest::qWaitForWindowExposed(&view3)); + view1.setColumnWidth(0, 100); + view1.setColumnWidth(1, 100); + view2.setColumnWidth(0, 100); + view2.setColumnWidth(1, 100); + view3.setColumnWidth(0, 200); + + view1.resizeRowToContents(0); + view2.resizeRowToContents(0); + view3.resizeRowToContents(0); + QCOMPARE(view1.rowHeight(0), view3.rowHeight(0)); + QCOMPARE(view2.rowHeight(0), view3.rowHeight(0)); + + view3.resizeColumnToContents(0); + view3.resizeRowToContents(0); + // height should be only 1 text line for easy testing + view1.setRowHeight(0, view3.verticalHeader()->sectionSize(0)); + view2.setRowHeight(0, view3.verticalHeader()->sectionSize(0)); + view1.resizeColumnToContents(0); + view2.resizeColumnToContents(1); + QCOMPARE(view1.columnWidth(0), view3.columnWidth(0) - view1.columnWidth(1)); + QCOMPARE(view2.columnWidth(0), view3.columnWidth(0) - view2.columnWidth(1)); +} + QT_BEGIN_NAMESPACE extern bool Q_WIDGETS_EXPORT qt_tab_all_widgets(); // qapplication.cpp QT_END_NAMESPACE diff --git a/qtbase/tests/auto/widgets/widgets/qabstractbutton/tst_qabstractbutton.cpp b/qtbase/tests/auto/widgets/widgets/qabstractbutton/tst_qabstractbutton.cpp index eb108a40de..dca5528c1b 100644 --- a/qtbase/tests/auto/widgets/widgets/qabstractbutton/tst_qabstractbutton.cpp +++ b/qtbase/tests/auto/widgets/widgets/qabstractbutton/tst_qabstractbutton.cpp @@ -41,6 +41,7 @@ #include #include +#include class tst_QAbstractButton : public QObject { @@ -76,6 +77,8 @@ private slots: void keyNavigation(); #endif + void buttonPressKeys(); + protected slots: void onClicked(); void onToggled( bool on ); @@ -269,7 +272,13 @@ void tst_QAbstractButton::setAutoRepeat() QCOMPARE(press_count, click_count); QVERIFY(click_count > 1); break; - case 4: + case 4: { + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + QSKIP("platform theme has Key_Enter in ButtonPressKeys"); + } // check that pressing ENTER has no effect when autorepeat is false testWidget->setDown( false ); testWidget->setAutoRepeat( false ); @@ -286,7 +295,14 @@ void tst_QAbstractButton::setAutoRepeat() QVERIFY( click_count == 0 ); break; - case 5: + } + case 5: { + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + QSKIP("platform theme has Key_Enter in ButtonPressKeys"); + } // check that pressing ENTER has no effect when autorepeat is true testWidget->setDown( false ); testWidget->setAutoRepeat( true ); @@ -304,6 +320,7 @@ void tst_QAbstractButton::setAutoRepeat() QVERIFY( click_count == 0 ); break; + } case 6: // verify autorepeat is off by default. MyButton tmp( 0); @@ -651,5 +668,16 @@ void tst_QAbstractButton::keyNavigation() } #endif +void tst_QAbstractButton::buttonPressKeys() +{ + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + for (int i = 0; i < buttonPressKeys.length(); ++i) { + QTest::keyClick(testWidget, buttonPressKeys[i]); + QCOMPARE(click_count, i + 1); + } +} + QTEST_MAIN(tst_QAbstractButton) #include "tst_qabstractbutton.moc" diff --git a/qtbase/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/qtbase/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp index 7af60ed757..46b5af6d63 100644 --- a/qtbase/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp +++ b/qtbase/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp @@ -168,6 +168,7 @@ private slots: void checkMenuItemPosWhenStyleSheetIsSet(); void checkEmbeddedLineEditWhenStyleSheetIsSet(); void propagateStyleChanges(); + void buttonPressKeys(); private: PlatformInputContext m_platformInputContext; @@ -3642,5 +3643,24 @@ void tst_QComboBox::propagateStyleChanges() QVERIFY(frameStyle.inquired); } +void tst_QComboBox::buttonPressKeys() +{ + QComboBox comboBox; + comboBox.setEditable(false); + comboBox.addItem(QString::number(1)); + comboBox.addItem(QString::number(2)); + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + for (int i = 0; i < buttonPressKeys.length(); ++i) { + QTest::keyClick(&comboBox, buttonPressKeys[i]); + // On some platforms, a window will not be immediately visible, + // but take some event-loop iterations to complete. + // Using QTRY_VERIFY to deal with that. + QTRY_VERIFY(comboBox.view()->isVisible()); + comboBox.hidePopup(); + } +} + QTEST_MAIN(tst_QComboBox) #include "tst_qcombobox.moc" diff --git a/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/qcommandlinkbutton.pro b/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/qcommandlinkbutton.pro index be3cfcd104..c228fdfcca 100644 --- a/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/qcommandlinkbutton.pro +++ b/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/qcommandlinkbutton.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qcommandlinkbutton -QT += widgets testlib +QT += widgets testlib gui-private SOURCES += tst_qcommandlinkbutton.cpp diff --git a/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/tst_qcommandlinkbutton.cpp b/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/tst_qcommandlinkbutton.cpp index 0044d33c66..4cf06296e4 100644 --- a/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/tst_qcommandlinkbutton.cpp +++ b/qtbase/tests/auto/widgets/widgets/qcommandlinkbutton/tst_qcommandlinkbutton.cpp @@ -40,6 +40,9 @@ #include #include +#include +#include + class tst_QCommandLinkButton : public QObject { Q_OBJECT @@ -223,6 +226,13 @@ void tst_QCommandLinkButton::setAutoRepeat() // check that pressing ENTER has no effect resetCounters(); testWidget->setDown( false ); + // Skip after reset if ButtonPressKeys has Key_Enter + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + return; + } testWidget->setAutoRepeat( false ); QTest::keyPress( testWidget, Qt::Key_Enter ); @@ -255,6 +265,14 @@ void tst_QCommandLinkButton::pressed() QCOMPARE( press_count, (uint)1 ); QCOMPARE( release_count, (uint)1 ); + // Skip if ButtonPressKeys has Key_Enter + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + return; + } + QTest::keyPress( testWidget,Qt::Key_Enter ); QCOMPARE( press_count, (uint)1 ); QCOMPARE( release_count, (uint)1 ); diff --git a/qtbase/tests/auto/widgets/widgets/qgroupbox/qgroupbox.pro b/qtbase/tests/auto/widgets/widgets/qgroupbox/qgroupbox.pro index 4a5e76ff65..a235fa1fac 100644 --- a/qtbase/tests/auto/widgets/widgets/qgroupbox/qgroupbox.pro +++ b/qtbase/tests/auto/widgets/widgets/qgroupbox/qgroupbox.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qgroupbox -QT += widgets testlib +QT += widgets testlib gui-private SOURCES += tst_qgroupbox.cpp diff --git a/qtbase/tests/auto/widgets/widgets/qgroupbox/tst_qgroupbox.cpp b/qtbase/tests/auto/widgets/widgets/qgroupbox/tst_qgroupbox.cpp index 4fb5d262ca..d8d7562b73 100644 --- a/qtbase/tests/auto/widgets/widgets/qgroupbox/tst_qgroupbox.cpp +++ b/qtbase/tests/auto/widgets/widgets/qgroupbox/tst_qgroupbox.cpp @@ -35,6 +35,9 @@ #include #include +#include +#include + #include "qgroupbox.h" class tst_QGroupBox : public QObject @@ -69,6 +72,7 @@ private slots: void propagateFocus(); void task_QTBUG_19170_ignoreMouseReleaseEvent(); void task_QTBUG_15519_propagateMouseEvents(); + void buttonPressKeys(); private: bool checked; @@ -610,6 +614,20 @@ void tst_QGroupBox::task_QTBUG_15519_propagateMouseEvents() QCOMPARE(parent.mouseMoved, true); } +void tst_QGroupBox::buttonPressKeys() +{ + QGroupBox groupBox; + groupBox.setCheckable(true); + QSignalSpy clickedSpy(&groupBox, &QGroupBox::clicked); + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + for (int i = 0; i < buttonPressKeys.length(); ++i) { + QTest::keyClick(&groupBox, buttonPressKeys[i]); + QCOMPARE(clickedSpy.length(), i + 1); + } +} + void tst_QGroupBox::sendMouseMoveEvent(QWidget *widget, const QPoint &localPos) { // Send a MouseMove event without actually moving the pointer diff --git a/qtbase/tests/auto/widgets/widgets/qpushbutton/qpushbutton.pro b/qtbase/tests/auto/widgets/widgets/qpushbutton/qpushbutton.pro index 353ad06ca2..e55f6148f2 100644 --- a/qtbase/tests/auto/widgets/widgets/qpushbutton/qpushbutton.pro +++ b/qtbase/tests/auto/widgets/widgets/qpushbutton/qpushbutton.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qpushbutton -QT += widgets testlib +QT += widgets testlib gui-private SOURCES += tst_qpushbutton.cpp diff --git a/qtbase/tests/auto/widgets/widgets/qpushbutton/tst_qpushbutton.cpp b/qtbase/tests/auto/widgets/widgets/qpushbutton/tst_qpushbutton.cpp index e818514a79..4043e9326a 100644 --- a/qtbase/tests/auto/widgets/widgets/qpushbutton/tst_qpushbutton.cpp +++ b/qtbase/tests/auto/widgets/widgets/qpushbutton/tst_qpushbutton.cpp @@ -41,6 +41,9 @@ #include #include +#include +#include + class tst_QPushButton : public QObject { Q_OBJECT @@ -212,6 +215,13 @@ void tst_QPushButton::autoRepeat() // check that pressing ENTER has no effect resetCounters(); testWidget->setDown( false ); + // Skip after reset if ButtonPressKeys has Key_Enter + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + return; + } testWidget->setAutoRepeat( false ); QTest::keyPress( testWidget, Qt::Key_Enter ); @@ -247,6 +257,14 @@ void tst_QPushButton::pressed() QCOMPARE( press_count, (uint)1 ); QCOMPARE( release_count, (uint)1 ); + // Skip if ButtonPressKeys has Key_Enter + const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme() + ->themeHint(QPlatformTheme::ButtonPressKeys) + .value>(); + if (buttonPressKeys.contains(Qt::Key_Enter)) { + return; + } + QTest::keyPress( testWidget,Qt::Key_Enter ); QCOMPARE( press_count, (uint)1 ); QCOMPARE( release_count, (uint)1 ); diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/main.cpp b/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/main.cpp index b5d9fea315..c5597c6f8e 100644 --- a/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/main.cpp +++ b/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/main.cpp @@ -37,7 +37,6 @@ #include "parser.h" -static QTextStream qout(stdout, QIODevice::WriteOnly); static QTextStream qerr(stderr, QIODevice::WriteOnly); static void usage() @@ -79,19 +78,15 @@ int main(int argc, const char *argv[]) if (out_file_name.isEmpty()) out_file_name = file_name + ".ref"; - QFile _out_file; - QTextStream _out_stream; - QTextStream *out_stream; + QFile out_file; if (out_file_name == "-") { - out_stream = &qout; + out_file.open(stdout, QFile::WriteOnly); } else { - _out_file.setFileName(out_file_name); - if (!_out_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + out_file.setFileName(out_file_name); + if (!out_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qerr << "Could not open " << out_file_name << ": " << strerror(errno) << Qt::endl; return 1; } - _out_stream.setDevice(&_out_file); - out_stream = &_out_stream; } Parser parser; @@ -102,9 +97,7 @@ int main(int argc, const char *argv[]) parser.parseFile(&in_file); - out_stream->setCodec("utf8"); - - *out_stream << parser.result(); + out_file.write(parser.result().toUtf8()); return 0; } diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/parser.cpp b/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/parser.cpp index 44f8101955..24aa9376da 100644 --- a/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/parser.cpp +++ b/qtbase/tests/auto/xml/sax/qxmlsimplereader/parser/parser.cpp @@ -142,11 +142,12 @@ bool ContentHandler::startElement(const QString &namespaceURI, QString ContentHandler::escapeStr(const QString &s) { QString result = s; - result.replace(QRegularExpression("\""), "\\\""); - result.replace(QRegularExpression("\\"), "\\\\"); - result.replace(QRegularExpression("\n"), "\\n"); - result.replace(QRegularExpression("\r"), "\\r"); - result.replace(QRegularExpression("\t"), "\\t"); + result.replace(QChar(0), "\\0"); + result.replace("\\", "\\\\"); + result.replace("\"", "\\\""); + result.replace("\n", "\\n"); + result.replace("\r", "\\r"); + result.replace("\t", "\\t"); return result; } diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp b/qtbase/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp index cea4e3c8b8..df158cae0f 100644 --- a/qtbase/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp +++ b/qtbase/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp @@ -311,14 +311,12 @@ void tst_QXmlSimpleReader::testGoodXmlFile() QVERIFY(file.open(QIODevice::ReadOnly)); Parser parser; - QEXPECT_FAIL(QFINDTESTDATA("xmldocs/valid/sa/089.xml").toLocal8Bit().constData(), "a form feed character is not accepted in XML", Continue); QVERIFY(parser.parseFile(&file)); QFile ref_file(file_name + ".ref"); QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); - QTextStream ref_stream(&ref_file); - ref_stream.setCodec("UTF-8"); - QString ref_file_contents = ref_stream.readAll(); + QByteArray data = ref_file.readAll(); + QString ref_file_contents = QString::fromUtf8(data.constData(), data.size()); QCOMPARE(parser.result(), ref_file_contents); } @@ -393,9 +391,7 @@ void tst_QXmlSimpleReader::testBadXmlFile() QFile ref_file(file_name + ".ref"); QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); - QTextStream ref_stream(&ref_file); - ref_stream.setCodec("UTF-8"); - QString ref_file_contents = ref_stream.readAll(); + QString ref_file_contents = QString::fromUtf8(ref_file.readAll()); QEXPECT_FAIL(QFINDTESTDATA("xmldocs/not-wf/sa/145.xml").toLocal8Bit().constData(), "Surrogate code point 0xD800 should be rejected", Continue); @@ -469,9 +465,7 @@ void tst_QXmlSimpleReader::testIncrementalParsing() QFile ref_file(file_name + ".ref"); QVERIFY(ref_file.open(QIODevice::ReadOnly | QIODevice::Text)); - QTextStream ref_stream(&ref_file); - ref_stream.setCodec("UTF-8"); - QString ref_file_contents = ref_stream.readAll(); + QString ref_file_contents = QString::fromUtf8(ref_file.readAll()); QCOMPARE(parser.result(), ref_file_contents); } diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/not-wf/sa/142.xml.ref b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/not-wf/sa/142.xml.ref index 7ce4da6a06..0684cfa943 100644 Binary files a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/not-wf/sa/142.xml.ref and b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/not-wf/sa/142.xml.ref differ diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/064.xml.ref b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/064.xml.ref index 579aeb52f6..0b806c96a8 100644 Binary files a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/064.xml.ref and b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/064.xml.ref differ diff --git a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/089.xml.ref b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/089.xml.ref index 7c68c32286..f09bc2bd09 100644 Binary files a/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/089.xml.ref and b/qtbase/tests/auto/xml/sax/qxmlsimplereader/xmldocs/valid/sa/089.xml.ref differ diff --git a/qtbase/tests/manual/rhi/cubemap_render/buildshader.bat b/qtbase/tests/manual/rhi/cubemap_render/buildshader.bat old mode 100755 new mode 100644 Submodule qtconnectivity f0c98c39..e33b8288: diff --git a/qtconnectivity/src/bluetooth/qbluetoothsocket.cpp b/qtconnectivity/src/bluetooth/qbluetoothsocket.cpp index 9da67272..fcd0fbb2 100644 --- a/qtconnectivity/src/bluetooth/qbluetoothsocket.cpp +++ b/qtconnectivity/src/bluetooth/qbluetoothsocket.cpp @@ -797,7 +797,9 @@ void QBluetoothSocket::close() Returns true on success */ - +// ### Qt 7 consider making this function private. The qbluetoothsocket_bluez backend is the +// the only backend providing publicly accessible support for this. Other backends implement +// similarly named, but private, overload bool QBluetoothSocket::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, SocketState socketState, OpenMode openMode) { diff --git a/qtconnectivity/src/bluetooth/qbluetoothuuid.cpp b/qtconnectivity/src/bluetooth/qbluetoothuuid.cpp index bc00aa95..517465bd 100644 --- a/qtconnectivity/src/bluetooth/qbluetoothuuid.cpp +++ b/qtconnectivity/src/bluetooth/qbluetoothuuid.cpp @@ -834,8 +834,6 @@ QString QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid uu //: Connection management (Bluetooth) case QBluetoothUuid::BondManagement: return QBluetoothServiceDiscoveryAgent::tr("Bond Management"); case QBluetoothUuid::ContinuousGlucoseMonitoring: return QBluetoothServiceDiscoveryAgent::tr("Continuous Glucose Monitoring"); - default: - break; } return QString(); @@ -878,8 +876,6 @@ QString QBluetoothUuid::protocolToString(QBluetoothUuid::ProtocolUuid uuid) case QBluetoothUuid::McapControlChannel: return QBluetoothServiceDiscoveryAgent::tr("Multi-Channel Adaptation Protocol - Control"); case QBluetoothUuid::McapDataChannel: return QBluetoothServiceDiscoveryAgent::tr("Multi-Channel Adaptation Protocol - Data"); case QBluetoothUuid::L2cap: return QBluetoothServiceDiscoveryAgent::tr("Layer 2 Control Protocol"); - default: - break; } return QString(); @@ -1081,13 +1077,13 @@ QString QBluetoothUuid::characteristicToString(CharacteristicType uuid) case QBluetoothUuid::BodyCompositionFeature: return QBluetoothServiceDiscoveryAgent::tr("Body Composition Feature"); case QBluetoothUuid::BodyCompositionMeasurement: return QBluetoothServiceDiscoveryAgent::tr("Body Composition Measurement"); case QBluetoothUuid::WeightMeasurement: return QBluetoothServiceDiscoveryAgent::tr("Weight Measurement"); + case QBluetoothUuid::CharacteristicType::WeightScaleFeature: + return QBluetoothServiceDiscoveryAgent::tr("Weight Scale Feature"); case QBluetoothUuid::UserControlPoint: return QBluetoothServiceDiscoveryAgent::tr("User Control Point"); case QBluetoothUuid::MagneticFluxDensity2D: return QBluetoothServiceDiscoveryAgent::tr("Magnetic Flux Density 2D"); case QBluetoothUuid::MagneticFluxDensity3D: return QBluetoothServiceDiscoveryAgent::tr("Magnetic Flux Density 3D"); case QBluetoothUuid::Language: return QBluetoothServiceDiscoveryAgent::tr("Language"); case QBluetoothUuid::BarometricPressureTrend: return QBluetoothServiceDiscoveryAgent::tr("Barometric Pressure Trend"); - default: - break; } return QString(); @@ -1104,6 +1100,8 @@ QString QBluetoothUuid::characteristicToString(CharacteristicType uuid) QString QBluetoothUuid::descriptorToString(QBluetoothUuid::DescriptorType uuid) { switch (uuid) { + case QBluetoothUuid::UnknownDescriptorType: + break; // returns {} below case QBluetoothUuid::CharacteristicExtendedProperties: return QBluetoothServiceDiscoveryAgent::tr("Characteristic Extended Properties"); case QBluetoothUuid::CharacteristicUserDescription: @@ -1128,8 +1126,6 @@ QString QBluetoothUuid::descriptorToString(QBluetoothUuid::DescriptorType uuid) return QBluetoothServiceDiscoveryAgent::tr("Environmental Sensing Measurement"); case QBluetoothUuid::EnvironmentalSensingTriggerSetting: return QBluetoothServiceDiscoveryAgent::tr("Environmental Sensing Trigger Setting"); - default: - break; } return QString(); diff --git a/qtconnectivity/src/tools/sdpscanner/main.cpp b/qtconnectivity/src/tools/sdpscanner/main.cpp index 7e09ca6e..c39ff8f3 100644 --- a/qtconnectivity/src/tools/sdpscanner/main.cpp +++ b/qtconnectivity/src/tools/sdpscanner/main.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -159,7 +160,9 @@ static void parseAttributeValues(sdp_data_t *data, int indentation, QByteArray & break; } else if (!isprint(text[i])) { hasNonPrintableChar = true; - text.resize(text.indexOf('\0')); // cut trailing content + const auto firstNullIdx = text.indexOf('\0'); + if (firstNullIdx > 0) + text.resize(firstNullIdx); // cut trailing content break; } } @@ -211,11 +214,17 @@ static void parseAttributeValues(sdp_data_t *data, int indentation, QByteArray & case SDP_URL_STR8: case SDP_URL_STR16: case SDP_URL_STR32: - strncpy(snBuffer, data->val.str, data->unitSize - 1); + { xmlOutput.append("val.str, qstrnlen(data->val.str, data->unitSize)); + const QUrl url = QUrl::fromEncoded(urlData); + // Encoded url %-encodes all of the XML special characters except '&', + // so we need to do that manually + xmlOutput.append(url.toEncoded().replace('&', "&")); xmlOutput.append("\"/>\n"); break; + } default: fprintf(stderr, "Unknown dtd type\n"); } Submodule qtdeclarative 960a980d..1b0e3660: diff --git a/qtdeclarative/src/qml/animations/qcontinuinganimationgroupjob.cpp b/qtdeclarative/src/qml/animations/qcontinuinganimationgroupjob.cpp index 88c0e9e60e..61a9dc36f8 100644 --- a/qtdeclarative/src/qml/animations/qcontinuinganimationgroupjob.cpp +++ b/qtdeclarative/src/qml/animations/qcontinuinganimationgroupjob.cpp @@ -82,9 +82,9 @@ void QContinuingAnimationGroupJob::updateState(QAbstractAnimationJob::State newS return; } for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { - resetUncontrolledAnimationFinishTime(animation); + RETURN_IF_DELETED(resetUncontrolledAnimationFinishTime(animation)); animation->setDirection(m_direction); - animation->start(); + RETURN_IF_DELETED(animation->start()); } break; } diff --git a/qtdeclarative/src/qml/animations/qparallelanimationgroupjob.cpp b/qtdeclarative/src/qml/animations/qparallelanimationgroupjob.cpp index 420a934ba2..a828d0e234 100644 --- a/qtdeclarative/src/qml/animations/qparallelanimationgroupjob.cpp +++ b/qtdeclarative/src/qml/animations/qparallelanimationgroupjob.cpp @@ -144,10 +144,10 @@ void QParallelAnimationGroupJob::updateState(QAbstractAnimationJob::State newSta animation->stop(); m_previousLoop = m_direction == Forward ? 0 : m_loopCount - 1; } - resetUncontrolledAnimationFinishTime(animation); + RETURN_IF_DELETED(resetUncontrolledAnimationFinishTime(animation)); animation->setDirection(m_direction); if (shouldAnimationStart(animation, oldState == Stopped)) - animation->start(); + RETURN_IF_DELETED(animation->start()); } break; } diff --git a/qtdeclarative/src/qml/common/qqmljsmemorypool_p.h b/qtdeclarative/src/qml/common/qqmljsmemorypool_p.h index 0cf7ea84e6..1b81a87a2c 100644 --- a/qtdeclarative/src/qml/common/qqmljsmemorypool_p.h +++ b/qtdeclarative/src/qml/common/qqmljsmemorypool_p.h @@ -87,7 +87,7 @@ public: inline void *allocate(size_t size) { size = (size + 7) & ~size_t(7); - if (Q_LIKELY(_ptr && (_ptr + size < _end))) { + if (Q_LIKELY(_ptr && size < size_t(_end - _ptr))) { void *addr = _ptr; _ptr += size; return addr; diff --git a/qtdeclarative/src/qml/jit/qv4baselinejit.cpp b/qtdeclarative/src/qml/jit/qv4baselinejit.cpp index 45150cfffd..5ad53faf95 100644 --- a/qtdeclarative/src/qml/jit/qv4baselinejit.cpp +++ b/qtdeclarative/src/qml/jit/qv4baselinejit.cpp @@ -540,6 +540,8 @@ void BaselineJIT::generate_ThrowException() as->passEngineAsArg(0); BASELINEJIT_GENERATE_RUNTIME_CALL(ThrowException, CallResultDestination::Ignore); as->gotoCatchException(); + + // LOAD_ACC(); <- not needed here since it would be unreachable. } void BaselineJIT::generate_GetException() { as->getException(); } @@ -547,9 +549,11 @@ void BaselineJIT::generate_SetException() { as->setException(); } void BaselineJIT::generate_CreateCallContext() { + STORE_ACC(); as->prepareCallWithArgCount(1); as->passCppFrameAsArg(0); BASELINEJIT_GENERATE_RUNTIME_CALL(PushCallContext, CallResultDestination::Ignore); + LOAD_ACC(); } void BaselineJIT::generate_PushCatchContext(int index, int name) { as->pushCatchContext(index, name); } diff --git a/qtdeclarative/src/qml/jsruntime/qv4function.cpp b/qtdeclarative/src/qml/jsruntime/qv4function.cpp index cf8a53cf9f..223e64271e 100644 --- a/qtdeclarative/src/qml/jsruntime/qv4function.cpp +++ b/qtdeclarative/src/qml/jsruntime/qv4function.cpp @@ -136,7 +136,7 @@ void Function::updateInternalClass(ExecutionEngine *engine, const QList T *QRecyclePool::New() { T *rv = d->allocate(); - new (rv) T; - return rv; + return new (rv) T; } template @@ -139,8 +138,7 @@ template T *QRecyclePool::New(const T1 &a) { T *rv = d->allocate(); - new (rv) T(a); - return rv; + return new (rv) T(a); } template @@ -148,8 +146,7 @@ template T *QRecyclePool::New(T1 &a) { T *rv = d->allocate(); - new (rv) T(a); - return rv; + return new (rv) T(a); } template diff --git a/qtdeclarative/src/qml/qml/qqmldata_p.h b/qtdeclarative/src/qml/qml/qqmldata_p.h index ee31cb38d9..187339169b 100644 --- a/qtdeclarative/src/qml/qml/qqmldata_p.h +++ b/qtdeclarative/src/qml/qml/qqmldata_p.h @@ -176,24 +176,24 @@ public: }; struct NotifyList { - quint64 connectionMask; - - quint16 maximumTodoIndex; - quint16 notifiesSize; - - QQmlNotifierEndpoint *todo; - QQmlNotifierEndpoint**notifies; + QAtomicInteger connectionMask; + QQmlNotifierEndpoint *todo = nullptr; + QQmlNotifierEndpoint**notifies = nullptr; + quint16 maximumTodoIndex = 0; + quint16 notifiesSize = 0; void layout(); private: void layout(QQmlNotifierEndpoint*); }; - NotifyList *notifyList; + QAtomicPointer notifyList; - inline QQmlNotifierEndpoint *notify(int index); + inline QQmlNotifierEndpoint *notify(int index) const; void addNotify(int index, QQmlNotifierEndpoint *); int endpointCount(int index); bool signalHasEndpoint(int index) const; - void disconnectNotifiers(); + + enum class DeleteNotifyList { Yes, No }; + void disconnectNotifiers(DeleteNotifyList doDelete); // The context that created the C++ object QQmlContextData *context = nullptr; @@ -201,12 +201,12 @@ public: QQmlContextData *outerContext = nullptr; QQmlContextDataRef ownContext; - QQmlAbstractBinding *bindings; - QQmlBoundSignal *signalHandlers; + QQmlAbstractBinding *bindings = nullptr; + QQmlBoundSignal *signalHandlers = nullptr; // Linked list for QQmlContext::contextObjects - QQmlData *nextContextObject; - QQmlData**prevContextObject; + QQmlData *nextContextObject = nullptr; + QQmlData**prevContextObject = nullptr; inline bool hasBindingBit(int) const; inline void setBindingBit(QObject *obj, int); @@ -216,10 +216,10 @@ public: inline void setPendingBindingBit(QObject *obj, int); inline void clearPendingBindingBit(int); - quint16 lineNumber; - quint16 columnNumber; + quint16 lineNumber = 0; + quint16 columnNumber = 0; - quint32 jsEngineId; // id of the engine that created the jsWrapper + quint32 jsEngineId = 0; // id of the engine that created the jsWrapper struct DeferredData { DeferredData(); @@ -240,7 +240,7 @@ public: QQmlPropertyCache *propertyCache; - QQmlGuardImpl *guards; + QQmlGuardImpl *guards = nullptr; static QQmlData *get(const QObject *object, bool create = false) { QObjectPrivate *priv = QObjectPrivate::get(const_cast(object)); @@ -289,7 +289,7 @@ public: private: // For attachedProperties - mutable QQmlDataExtended *extendedData; + mutable QQmlDataExtended *extendedData = nullptr; Q_NEVER_INLINE static QQmlData *createQQmlData(QObjectPrivate *priv); Q_NEVER_INLINE static QQmlPropertyCache *createPropertyCache(QJSEngine *engine, QObject *object); @@ -342,23 +342,31 @@ bool QQmlData::wasDeleted(const QObject *object) return ddata && ddata->isQueuedForDeletion; } -QQmlNotifierEndpoint *QQmlData::notify(int index) +inline bool isIndexInConnectionMask(quint64 connectionMask, int index) +{ + return connectionMask & (1ULL << quint64(index % 64)); +} + +QQmlNotifierEndpoint *QQmlData::notify(int index) const { + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + Q_ASSERT(index <= 0xFFFF); - if (!notifyList || !(notifyList->connectionMask & (1ULL << quint64(index % 64)))) { + NotifyList *list = notifyList.loadRelaxed(); + if (!list || !isIndexInConnectionMask(list->connectionMask.loadRelaxed(), index)) return nullptr; - } else if (index < notifyList->notifiesSize) { - return notifyList->notifies[index]; - } else if (index <= notifyList->maximumTodoIndex) { - notifyList->layout(); - } - if (index < notifyList->notifiesSize) { - return notifyList->notifies[index]; - } else { - return nullptr; + if (index < list->notifiesSize) + return list->notifies[index]; + + if (index <= list->maximumTodoIndex) { + list->layout(); + if (index < list->notifiesSize) + return list->notifies[index]; } + + return nullptr; } /* @@ -367,7 +375,19 @@ QQmlNotifierEndpoint *QQmlData::notify(int index) */ inline bool QQmlData::signalHasEndpoint(int index) const { - return notifyList && (notifyList->connectionMask & (1ULL << quint64(index % 64))); + // This can be called from any thread. + // We still use relaxed semantics. If we're on a thread different from the "home" thread + // of the QQmlData, two interesting things might happen: + // + // 1. The list might go away while we hold it. In that case we are dealing with an object whose + // QObject dtor is being executed concurrently. This is UB already without the notify lists. + // Therefore, we don't need to consider it. + // 2. The connectionMask may be amended or zeroed while we are looking at it. In that case + // we "misreport" the endpoint. Since ordering of events across threads is inherently + // nondeterministic, either result is correct in that case. We can accept it. + + NotifyList *list = notifyList.loadRelaxed(); + return list && isIndexInConnectionMask(list->connectionMask.loadRelaxed(), index); } bool QQmlData::hasBindingBit(int coreIndex) const diff --git a/qtdeclarative/src/qml/qml/qqmlengine.cpp b/qtdeclarative/src/qml/qml/qqmlengine.cpp index 852a673ebd..d6b2711c2d 100644 --- a/qtdeclarative/src/qml/qml/qqmlengine.cpp +++ b/qtdeclarative/src/qml/qml/qqmlengine.cpp @@ -718,18 +718,15 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o) // Disconnect the notifiers now - during object destruction this would be too late, since // the disconnect call wouldn't be able to call disconnectNotify(), as it isn't possible to // get the metaobject anymore. - d->disconnectNotifiers(); + d->disconnectNotifiers(QQmlData::DeleteNotifyList::No); } } QQmlData::QQmlData() : ownedByQml1(false), ownMemory(true), indestructible(true), explicitIndestructibleSet(false), hasTaintedV4Object(false), isQueuedForDeletion(false), rootObjectInCreation(false), - hasInterceptorMetaObject(false), hasVMEMetaObject(false), parentFrozen(false), - bindingBitsArraySize(InlineBindingArraySize), notifyList(nullptr), - bindings(nullptr), signalHandlers(nullptr), nextContextObject(nullptr), prevContextObject(nullptr), - lineNumber(0), columnNumber(0), jsEngineId(0), - propertyCache(nullptr), guards(nullptr), extendedData(nullptr) + hasInterceptorMetaObject(false), hasVMEMetaObject(false), parentFrozen(false), dummy(0), + bindingBitsArraySize(InlineBindingArraySize), propertyCache(nullptr) { memset(bindingBitsValue, 0, sizeof(bindingBitsValue)); init(); @@ -789,7 +786,10 @@ void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int in // QQmlEngine to emit signals from a different thread. These signals are then automatically // marshalled back onto the QObject's thread and handled by QML from there. This is tested // by the qqmlecmascript::threadSignal() autotest. - if (!ddata->notifyList) + + // Relaxed semantics here. If we're on a different thread we might schedule a useless event, + // but that should be rare. + if (!ddata->notifyList.loadRelaxed()) return; auto objectThreadData = QObjectPrivate::get(object)->threadData.loadRelaxed(); @@ -1835,49 +1835,73 @@ void QQmlData::releaseDeferredData() void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint) { - if (!notifyList) { - notifyList = (NotifyList *)malloc(sizeof(NotifyList)); - notifyList->connectionMask = 0; - notifyList->maximumTodoIndex = 0; - notifyList->notifiesSize = 0; - notifyList->todo = nullptr; - notifyList->notifies = nullptr; + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + + NotifyList *list = notifyList.loadRelaxed(); + + if (!list) { + list = new NotifyList; + // We don't really care when this change takes effect on other threads. The notifyList can + // only become non-null once in the life time of a QQmlData. It becomes null again when the + // underlying QObject is deleted. At that point any interaction with the QQmlData is UB + // anyway. So, for all intents and purposese, the list becomes non-null once and then stays + // non-null "forever". We can apply relaxed semantics. + notifyList.storeRelaxed(list); } Q_ASSERT(!endpoint->isConnected()); index = qMin(index, 0xFFFF - 1); - notifyList->connectionMask |= (1ULL << quint64(index % 64)); - if (index < notifyList->notifiesSize) { + // Likewise, we don't really care _when_ the change in the connectionMask is propagated to other + // threads. Cross-thread event ordering is inherently nondeterministic. Therefore, when querying + // the conenctionMask in the presence of concurrent modification, any result is correct. + list->connectionMask.storeRelaxed( + list->connectionMask.loadRelaxed() | (1ULL << quint64(index % 64))); - endpoint->next = notifyList->notifies[index]; + if (index < list->notifiesSize) { + endpoint->next = list->notifies[index]; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->notifies[index]; - notifyList->notifies[index] = endpoint; - + endpoint->prev = &list->notifies[index]; + list->notifies[index] = endpoint; } else { - notifyList->maximumTodoIndex = qMax(int(notifyList->maximumTodoIndex), index); + list->maximumTodoIndex = qMax(int(list->maximumTodoIndex), index); - endpoint->next = notifyList->todo; + endpoint->next = list->todo; if (endpoint->next) endpoint->next->prev = &endpoint->next; - endpoint->prev = ¬ifyList->todo; - notifyList->todo = endpoint; + endpoint->prev = &list->todo; + list->todo = endpoint; } } -void QQmlData::disconnectNotifiers() +void QQmlData::disconnectNotifiers(QQmlData::DeleteNotifyList doDelete) { - if (notifyList) { - while (notifyList->todo) - notifyList->todo->disconnect(); - for (int ii = 0; ii < notifyList->notifiesSize; ++ii) { - while (QQmlNotifierEndpoint *ep = notifyList->notifies[ii]) + // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics. + if (NotifyList *list = notifyList.loadRelaxed()) { + while (QQmlNotifierEndpoint *todo = list->todo) + todo->disconnect(); + for (int ii = 0; ii < list->notifiesSize; ++ii) { + while (QQmlNotifierEndpoint *ep = list->notifies[ii]) ep->disconnect(); } - free(notifyList->notifies); - free(notifyList); - notifyList = nullptr; + free(list->notifies); + + if (doDelete == DeleteNotifyList::Yes) { + // We can only get here from QQmlData::destroyed(), and that can only come from the + // the QObject dtor. If you're still sending signals at that point you have UB already + // without any threads. Therefore, it's enough to apply relaxed semantics. + notifyList.storeRelaxed(nullptr); + delete list; + } else { + // We can use relaxed semantics here. The worst thing that can happen is that some + // signal is falsely reported as connected. Signal connectedness across threads + // is not quite deterministic anyway. + list->connectionMask.storeRelaxed(0); + list->maximumTodoIndex = 0; + list->notifiesSize = 0; + list->notifies = nullptr; + + } } } @@ -1961,7 +1985,7 @@ void QQmlData::destroyed(QObject *object) guard->objectDestroyed(object); } - disconnectNotifiers(); + disconnectNotifiers(DeleteNotifyList::Yes); if (extendedData) delete extendedData; diff --git a/qtdeclarative/src/qml/qml/qqmlimport.cpp b/qtdeclarative/src/qml/qml/qqmlimport.cpp index e7263d1850..289f11d006 100644 --- a/qtdeclarative/src/qml/qml/qqmlimport.cpp +++ b/qtdeclarative/src/qml/qml/qqmlimport.cpp @@ -2119,9 +2119,12 @@ void QQmlImportDatabase::addImportPath(const QString& path) cPath.replace(Backslash, Slash); } - if (!cPath.isEmpty() - && !fileImportPath.contains(cPath)) - fileImportPath.prepend(cPath); + if (!cPath.isEmpty()) { + if (fileImportPath.contains(cPath)) + fileImportPath.move(fileImportPath.indexOf(cPath), 0); + else + fileImportPath.prepend(cPath); + } } /*! diff --git a/qtdeclarative/src/qml/qml/qqmltypewrapper.cpp b/qtdeclarative/src/qml/qml/qqmltypewrapper.cpp index 175de8b936..a6ba4b8cb3 100644 --- a/qtdeclarative/src/qml/qml/qqmltypewrapper.cpp +++ b/qtdeclarative/src/qml/qml/qqmltypewrapper.cpp @@ -419,8 +419,10 @@ ReturnedValue QQmlTypeWrapper::virtualInstanceOf(const Object *typeObject, const return Encode(false); QQmlRefPointer td = qenginepriv->typeLoader.getType(typeWrapper->d()->type().sourceUrl()); - ExecutableCompilationUnit *cu = td->compilationUnit(); - myQmlType = qenginepriv->metaObjectForType(cu->metaTypeId); + if (ExecutableCompilationUnit *cu = td->compilationUnit()) + myQmlType = qenginepriv->metaObjectForType(cu->metaTypeId); + else + return Encode(false); // It seems myQmlType has some errors, so we could not compile it. } else { myQmlType = qenginepriv->metaObjectForType(myTypeId); } diff --git a/qtdeclarative/src/qml/qml/qqmlvmemetaobject.cpp b/qtdeclarative/src/qml/qml/qqmlvmemetaobject.cpp index 1e0e4e419f..a0532d1794 100644 --- a/qtdeclarative/src/qml/qml/qqmlvmemetaobject.cpp +++ b/qtdeclarative/src/qml/qml/qqmlvmemetaobject.cpp @@ -251,7 +251,7 @@ void QQmlVMEMetaObjectEndpoint::tryConnect() if (!pd) return; - if (pd->notifyIndex() != -1) + if (pd->notifyIndex() != -1 && ctxt->engine) connect(target, pd->notifyIndex(), ctxt->engine); } diff --git a/qtdeclarative/src/qml/types/qqmlconnections.cpp b/qtdeclarative/src/qml/types/qqmlconnections.cpp index 4a4e6ce12c..a5889b7396 100644 --- a/qtdeclarative/src/qml/types/qqmlconnections.cpp +++ b/qtdeclarative/src/qml/types/qqmlconnections.cpp @@ -341,7 +341,7 @@ void QQmlConnections::connectSignalsToMethods() && propName.at(2).isUpper()) { qmlWarning(this) << tr("Detected function \"%1\" in Connections element. " "This is probably intended to be a signal handler but no " - "signal of the target matches the name.").arg(propName); + "signal of the \"%2\" target matches the name.").arg(propName).arg(target->metaObject()->className()); } } } diff --git a/qtdeclarative/src/qmlmodels/qqmldelegatemodel.cpp b/qtdeclarative/src/qmlmodels/qqmldelegatemodel.cpp index 4fcff70de6..5b7e767ae2 100644 --- a/qtdeclarative/src/qmlmodels/qqmldelegatemodel.cpp +++ b/qtdeclarative/src/qmlmodels/qqmldelegatemodel.cpp @@ -389,6 +389,12 @@ void QQmlDelegateModelPrivate::connectToAbstractItemModel() q, QQmlDelegateModel, SLOT(_q_rowsRemoved(QModelIndex,int,int))); qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, QQmlDelegateModel, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsInserted(QModelIndex,int,int)), + q, QQmlDelegateModel, SLOT(_q_columnsInserted(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsRemoved(QModelIndex,int,int)), + q, QQmlDelegateModel, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + qmlobject_connect(aim, QAbstractItemModel, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), + q, QQmlDelegateModel, SLOT(_q_columnsMoved(QModelIndex,int,int,QModelIndex,int))); qmlobject_connect(aim, QAbstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), q, QQmlDelegateModel, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); qmlobject_connect(aim, QAbstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), @@ -413,6 +419,12 @@ void QQmlDelegateModelPrivate::disconnectFromAbstractItemModel() q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); QObject::disconnect(aim, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(columnsInserted(QModelIndex,int,int)), q, + SLOT(_q_columnsInserted(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(columnsRemoved(QModelIndex,int,int)), q, + SLOT(_q_columnsRemoved(QModelIndex,int,int))); + QObject::disconnect(aim, SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), q, + SLOT(_q_columnsMoved(QModelIndex,int,int,QModelIndex,int))); QObject::disconnect(aim, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), q, SLOT(_q_dataChanged(QModelIndex,QModelIndex,QVector))); QObject::disconnect(aim, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), @@ -1871,10 +1883,15 @@ void QQmlDelegateModelPrivate::emitChanges() for (int i = 1; i < m_groupCount; ++i) QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); - auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache - for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) { - if (cacheItem->attached) - cacheItem->attached->emitChanges(); + // emitChanges may alter m_cache and delete items + QVarLengthArray> attachedObjects; + attachedObjects.reserve(m_cache.length()); + for (const QQmlDelegateModelItem *cacheItem : qAsConst(m_cache)) + attachedObjects.append(cacheItem->attached); + + for (const QPointer &attached : qAsConst(attachedObjects)) { + if (attached && attached->m_cacheItem) + attached->emitChanges(); } } @@ -1974,6 +1991,38 @@ void QQmlDelegateModel::_q_rowsMoved( } } +void QQmlDelegateModel::_q_columnsInserted(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + Q_UNUSED(end); + if (parent == d->m_adaptorModel.rootIndex && begin == 0) { + // mark all items as changed + _q_itemsChanged(0, d->m_count, QVector()); + } +} + +void QQmlDelegateModel::_q_columnsRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QQmlDelegateModel); + Q_UNUSED(end); + if (parent == d->m_adaptorModel.rootIndex && begin == 0) { + // mark all items as changed + _q_itemsChanged(0, d->m_count, QVector()); + } +} + +void QQmlDelegateModel::_q_columnsMoved(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int column) +{ + Q_D(QQmlDelegateModel); + Q_UNUSED(end); + if ((parent == d->m_adaptorModel.rootIndex && start == 0) + || (destination == d->m_adaptorModel.rootIndex && column == 0)) { + // mark all items as changed + _q_itemsChanged(0, d->m_count, QVector()); + } +} + void QQmlDelegateModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end, const QVector &roles) { Q_D(QQmlDelegateModel); @@ -2663,20 +2712,24 @@ void QQmlDelegateModelAttached::emitChanges() m_previousGroups = m_cacheItem->groups; int indexChanges = 0; - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + const int groupCount = m_cacheItem->metaType->groupCount; + for (int i = 1; i < groupCount; ++i) { if (m_previousIndex[i] != m_currentIndex[i]) { m_previousIndex[i] = m_currentIndex[i]; indexChanges |= (1 << i); } } + // Don't access m_cacheItem anymore once we've started sending signals. + // We don't own it and someone might delete it. + int notifierId = 0; const QMetaObject *meta = metaObject(); - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + for (int i = 1; i < groupCount; ++i, ++notifierId) { if (groupChanges & (1 << i)) QMetaObject::activate(this, meta, notifierId, nullptr); } - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + for (int i = 1; i < groupCount; ++i, ++notifierId) { if (indexChanges & (1 << i)) QMetaObject::activate(this, meta, notifierId, nullptr); } diff --git a/qtdeclarative/src/qmlmodels/qqmldelegatemodel_p.h b/qtdeclarative/src/qmlmodels/qqmldelegatemodel_p.h index 8aab4badca..d140bfbaaf 100644 --- a/qtdeclarative/src/qmlmodels/qqmldelegatemodel_p.h +++ b/qtdeclarative/src/qmlmodels/qqmldelegatemodel_p.h @@ -152,6 +152,9 @@ private Q_SLOTS: void _q_itemsMoved(int from, int to, int count); void _q_modelReset(); void _q_rowsInserted(const QModelIndex &,int,int); + void _q_columnsInserted(const QModelIndex &, int, int); + void _q_columnsRemoved(const QModelIndex &, int, int); + void _q_columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int); void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); void _q_rowsRemoved(const QModelIndex &,int,int); void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); diff --git a/qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp b/qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp index ae1954ae8d..99e6eff7c3 100644 --- a/qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp +++ b/qtdeclarative/src/quick/accessible/qaccessiblequickitem.cpp @@ -46,6 +46,7 @@ #include "QtQuick/private/qquicktextinput_p.h" #include "QtQuick/private/qquickaccessibleattached_p.h" #include "QtQuick/qquicktextdocument.h" +#include "QtQuick/qquickrendercontrol.h" QT_BEGIN_NAMESPACE #if QT_CONFIG(accessibility) @@ -57,7 +58,19 @@ QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item) QWindow *QAccessibleQuickItem::window() const { - return item()->window(); + QQuickWindow *window = item()->window(); + + // For QQuickWidget the above window will be the offscreen QQuickWindow, + // which is not a part of the accessibility tree. Detect this case and + // return the window for the QQuickWidget instead. + if (window && !window->handle()) { + if (QQuickRenderControl *renderControl = QQuickWindowPrivate::get(window)->renderControl) { + if (QWindow *renderWindow = renderControl->renderWindow(nullptr)) + return renderWindow; + } + } + + return window; } int QAccessibleQuickItem::childCount() const @@ -113,19 +126,15 @@ QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const QAccessibleInterface *QAccessibleQuickItem::parent() const { QQuickItem *parent = item()->parentItem(); - QQuickWindow *window = item()->window(); - QQuickItem *ci = window ? window->contentItem() : nullptr; + QQuickWindow *itemWindow = item()->window(); + QQuickItem *ci = itemWindow ? itemWindow->contentItem() : nullptr; while (parent && !QQuickItemPrivate::get(parent)->isAccessible && parent != ci) parent = parent->parentItem(); if (parent) { if (parent == ci) { - // Jump out to the scene widget if the parent is the root item. - // There are two root items, QQuickWindow::rootItem and - // QQuickView::declarativeRoot. The former is the true root item, - // but is not a part of the accessibility tree. Check if we hit - // it here and return an interface for the scene instead. - return QAccessible::queryAccessibleInterface(window); + // Jump out to the window if the parent is the root item + return QAccessible::queryAccessibleInterface(window()); } else { while (parent && !parent->d_func()->isAccessible) parent = parent->parentItem(); @@ -193,7 +202,7 @@ QAccessible::State QAccessibleQuickItem::state() const QRect viewRect_ = viewRect(); QRect itemRect = rect(); - if (viewRect_.isNull() || itemRect.isNull() || !item()->window() || !item()->window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity())) + if (viewRect_.isNull() || itemRect.isNull() || !window() || !window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity())) state.invisible = true; if (!viewRect_.intersects(itemRect)) state.offscreen = true; @@ -206,6 +215,10 @@ QAccessible::State QAccessibleQuickItem::state() const if (role() == QAccessible::EditableText) if (auto ti = qobject_cast(item())) state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal; + if (!item()->isEnabled()) { + state.focusable = false; + state.disabled = true; + } return state; } @@ -217,7 +230,7 @@ QAccessible::Role QAccessibleQuickItem::role() const QAccessible::Role role = QAccessible::NoRole; if (item()) - role = QQuickItemPrivate::get(item())->accessibleRole(); + role = QQuickItemPrivate::get(item())->effectiveAccessibleRole(); if (role == QAccessible::NoRole) { if (qobject_cast(const_cast(item()))) role = QAccessible::StaticText; diff --git a/qtdeclarative/src/quick/accessible/qaccessiblequickview_p.h b/qtdeclarative/src/quick/accessible/qaccessiblequickview_p.h index 39ffcaf39c..8baa01330c 100644 --- a/qtdeclarative/src/quick/accessible/qaccessiblequickview_p.h +++ b/qtdeclarative/src/quick/accessible/qaccessiblequickview_p.h @@ -58,7 +58,7 @@ QT_BEGIN_NAMESPACE #if QT_CONFIG(accessibility) -class QAccessibleQuickWindow : public QAccessibleObject +class Q_QUICK_EXPORT QAccessibleQuickWindow : public QAccessibleObject { public: QAccessibleQuickWindow(QQuickWindow *object); diff --git a/qtdeclarative/src/quick/items/qquickdrag.cpp b/qtdeclarative/src/quick/items/qquickdrag.cpp index 8321fcfeed..383078b3b9 100644 --- a/qtdeclarative/src/quick/items/qquickdrag.cpp +++ b/qtdeclarative/src/quick/items/qquickdrag.cpp @@ -481,7 +481,9 @@ void QQuickDragAttached::setKeys(const QStringList &keys) \qmlattachedproperty stringlist QtQuick::Drag::mimeData \since 5.2 - This property holds a map of mimeData that is used during startDrag. + This property holds a map from mime type to data that is used during startDrag. + The mime data needs to be a \c string, or an \c ArrayBuffer with the data encoded + according to the mime type. */ QVariantMap QQuickDragAttached::mimeData() const @@ -766,8 +768,12 @@ Qt::DropAction QQuickDragAttachedPrivate::startDrag(Qt::DropActions supportedAct QDrag *drag = new QDrag(source ? source : q); QMimeData *mimeData = new QMimeData(); - for (auto it = externalMimeData.cbegin(), end = externalMimeData.cend(); it != end; ++it) - mimeData->setData(it.key(), it.value().toString().toUtf8()); + for (auto it = externalMimeData.cbegin(), end = externalMimeData.cend(); it != end; ++it) { + if (static_cast(it.value().type()) == QMetaType::QByteArray) + mimeData->setData(it.key(), it.value().toByteArray()); + else + mimeData->setData(it.key(), it.value().toString().toUtf8()); + } drag->setMimeData(mimeData); if (pixmapLoader.isReady()) { diff --git a/qtdeclarative/src/quick/items/qquickflickable.cpp b/qtdeclarative/src/quick/items/qquickflickable.cpp index ea357d819d..2634b68248 100644 --- a/qtdeclarative/src/quick/items/qquickflickable.cpp +++ b/qtdeclarative/src/quick/items/qquickflickable.cpp @@ -2120,11 +2120,9 @@ void QQuickFlickable::setContentWidth(qreal w) d->contentItem->setWidth(w); d->hData.markExtentsDirty(); // Make sure that we're entirely in view. - if ((!d->pressed && !d->hData.moving && !d->vData.moving) || d->hData.dragging) { - d->hData.contentPositionChangedExternallyDuringDrag = d->hData.dragging; + if (!d->pressed && !d->hData.moving && !d->vData.moving) { d->fixupMode = QQuickFlickablePrivate::Immediate; d->fixupX(); - d->hData.contentPositionChangedExternallyDuringDrag = false; } else if (!d->pressed && d->hData.fixingUp) { d->fixupMode = QQuickFlickablePrivate::ExtentChanged; d->fixupX(); @@ -2151,11 +2149,9 @@ void QQuickFlickable::setContentHeight(qreal h) d->contentItem->setHeight(h); d->vData.markExtentsDirty(); // Make sure that we're entirely in view. - if ((!d->pressed && !d->hData.moving && !d->vData.moving) || d->vData.dragging) { - d->vData.contentPositionChangedExternallyDuringDrag = d->vData.dragging; + if (!d->pressed && !d->hData.moving && !d->vData.moving) { d->fixupMode = QQuickFlickablePrivate::Immediate; d->fixupY(); - d->vData.contentPositionChangedExternallyDuringDrag = false; } else if (!d->pressed && d->vData.fixingUp) { d->fixupMode = QQuickFlickablePrivate::ExtentChanged; d->fixupY(); diff --git a/qtdeclarative/src/quick/items/qquickflickable_p_p.h b/qtdeclarative/src/quick/items/qquickflickable_p_p.h index d5d838eaea..aef15e150a 100644 --- a/qtdeclarative/src/quick/items/qquickflickable_p_p.h +++ b/qtdeclarative/src/quick/items/qquickflickable_p_p.h @@ -120,6 +120,7 @@ public: dragStartOffset = 0; fixingUp = false; inOvershoot = false; + contentPositionChangedExternallyDuringDrag = false; } void markExtentsDirty() { diff --git a/qtdeclarative/src/quick/items/qquickitem.cpp b/qtdeclarative/src/quick/items/qquickitem.cpp index 33da9762d3..9e8b289376 100644 --- a/qtdeclarative/src/quick/items/qquickitem.cpp +++ b/qtdeclarative/src/quick/items/qquickitem.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include @@ -2326,6 +2327,7 @@ QQuickItem::QQuickItem(QQuickItemPrivate &dd, QQuickItem *parent) QQuickItem::~QQuickItem() { Q_D(QQuickItem); + d->inDestructor = true; if (d->windowRefCount > 1) d->windowRefCount = 1; // Make sure window is set to null in next call to derefWindow(). @@ -2398,7 +2400,7 @@ bool QQuickItemPrivate::canAcceptTabFocus(QQuickItem *item) return true; #if QT_CONFIG(accessibility) - QAccessible::Role role = QQuickItemPrivate::get(item)->accessibleRole(); + QAccessible::Role role = QQuickItemPrivate::get(item)->effectiveAccessibleRole(); if (role == QAccessible::EditableText || role == QAccessible::Table || role == QAccessible::List) { return true; } else if (role == QAccessible::ComboBox || role == QAccessible::SpinBox) { @@ -2526,6 +2528,7 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo QQuickItem *current = item; qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: startItem:" << startItem; qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: firstFromItem:" << firstFromItem; + QDuplicateTracker cycleDetector; do { qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: current:" << current; qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: from:" << from; @@ -2592,7 +2595,10 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo // traversed all of the chain (by compare the [current] item with [startItem]) // Since the [startItem] might be promoted to its parent if it is invisible, // we still have to check [current] item with original start item - if ((current == startItem || current == originalStartItem) && from == firstFromItem) { + // We might also run into a cycle before we reach firstFromItem again + // but note that we have to ignore current if we are meant to skip it + if (((current == startItem || current == originalStartItem) && from == firstFromItem) || + (!skip && cycleDetector.hasSeen(current))) { // wrapped around, avoid endless loops if (item == contentItem) { qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: looped, return contentItem"; @@ -2689,9 +2695,8 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) const bool wasVisible = isVisible(); op->removeChild(this); - if (wasVisible) { + if (wasVisible && !op->inDestructor) emit oldParentItem->visibleChildrenChanged(); - } } else if (d->window) { QQuickWindowPrivate::get(d->window)->parentlessItems.remove(this); } @@ -2768,8 +2773,9 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) d->itemChange(ItemParentHasChanged, d->parentItem); - emit parentChanged(d->parentItem); - if (isVisible() && d->parentItem) + if (!d->inDestructor) + emit parentChanged(d->parentItem); + if (isVisible() && d->parentItem && !QQuickItemPrivate::get(d->parentItem)->inDestructor) emit d->parentItem->visibleChildrenChanged(); } @@ -2965,7 +2971,8 @@ void QQuickItemPrivate::removeChild(QQuickItem *child) itemChange(QQuickItem::ItemChildRemovedChange, child); - emit q->childrenChanged(); + if (!inDestructor) + emit q->childrenChanged(); } void QQuickItemPrivate::refWindow(QQuickWindow *c) @@ -3194,6 +3201,7 @@ QQuickItemPrivate::QQuickItemPrivate() , touchEnabled(false) #endif , hasCursorHandler(false) + , inDestructor(false) , dirtyAttributes(0) , nextDirtyItem(nullptr) , prevDirtyItem(nullptr) @@ -5120,6 +5128,13 @@ void QQuickItem::componentComplete() d->addToDirtyList(); QQuickWindowPrivate::get(d->window)->dirtyItem(this); } + +#if QT_CONFIG(accessibility) + if (d->isAccessible && d->effectiveVisible) { + QAccessibleEvent ev(this, QAccessible::ObjectShow); + QAccessible::updateAccessibility(&ev); + } +#endif } QQuickStateGroup *QQuickItemPrivate::_states() @@ -6106,9 +6121,11 @@ bool QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) QAccessible::updateAccessibility(&ev); } #endif - emit q->visibleChanged(); - if (childVisibilityChanged) - emit q->visibleChildrenChanged(); + if (!inDestructor) { + emit q->visibleChanged(); + if (childVisibilityChanged) + emit q->visibleChildrenChanged(); + } return true; // effective visibility DID change } @@ -6157,6 +6174,15 @@ void QQuickItemPrivate::setEffectiveEnableRecur(QQuickItem *scope, bool newEffec } itemChange(QQuickItem::ItemEnabledHasChanged, effectiveEnable); +#if QT_CONFIG(accessibility) + if (isAccessible) { + QAccessible::State changedState; + changedState.disabled = true; + changedState.focusable = true; + QAccessibleStateChangeEvent ev(q, changedState); + QAccessible::updateAccessibility(&ev); + } +#endif emit q->enabledChanged(); } @@ -8974,13 +9000,20 @@ QQuickItemPrivate::ExtraData::ExtraData() #if QT_CONFIG(accessibility) -QAccessible::Role QQuickItemPrivate::accessibleRole() const +QAccessible::Role QQuickItemPrivate::effectiveAccessibleRole() const { Q_Q(const QQuickItem); - QQuickAccessibleAttached *accessibleAttached = qobject_cast(qmlAttachedPropertiesObject(q, false)); - if (accessibleAttached) - return accessibleAttached->role(); + auto *attached = qmlAttachedPropertiesObject(q, false); + auto role = QAccessible::NoRole; + if (auto *accessibleAttached = qobject_cast(attached)) + role = accessibleAttached->role(); + if (role == QAccessible::NoRole) + role = accessibleRole(); + return role; +} +QAccessible::Role QQuickItemPrivate::accessibleRole() const +{ return QAccessible::NoRole; } #endif diff --git a/qtdeclarative/src/quick/items/qquickitem_p.h b/qtdeclarative/src/quick/items/qquickitem_p.h index 841d91bb40..6f329bd119 100644 --- a/qtdeclarative/src/quick/items/qquickitem_p.h +++ b/qtdeclarative/src/quick/items/qquickitem_p.h @@ -472,6 +472,7 @@ public: bool replayingPressEvent:1; bool touchEnabled:1; bool hasCursorHandler:1; + quint32 inDestructor:1; // has entered ~QQuickItem enum DirtyType { TransformOrigin = 0x00000001, @@ -574,7 +575,10 @@ public: virtual void implicitHeightChanged(); #if QT_CONFIG(accessibility) + QAccessible::Role effectiveAccessibleRole() const; +private: virtual QAccessible::Role accessibleRole() const; +public: #endif void setImplicitAntialiasing(bool antialiasing); diff --git a/qtdeclarative/src/quick/items/qquickmousearea_p_p.h b/qtdeclarative/src/quick/items/qquickmousearea_p_p.h index fba383e268..0d63618622 100644 --- a/qtdeclarative/src/quick/items/qquickmousearea_p_p.h +++ b/qtdeclarative/src/quick/items/qquickmousearea_p_p.h @@ -61,7 +61,6 @@ QT_BEGIN_NAMESPACE class QQuickMouseEvent; class QQuickMouseArea; -class QQuickPointerMask; class QQuickMouseAreaPrivate : public QQuickItemPrivate { Q_DECLARE_PUBLIC(QQuickMouseArea) @@ -100,7 +99,6 @@ public: #if QT_CONFIG(quick_draganddrop) QQuickDrag *drag; #endif - QPointer mask; QPointF startScene; QPointF targetStartPos; QPointF lastPos; diff --git a/qtdeclarative/src/quick/util/qquickstategroup.cpp b/qtdeclarative/src/quick/util/qquickstategroup.cpp index 7cb3138618..f732b1eb4a 100644 --- a/qtdeclarative/src/quick/util/qquickstategroup.cpp +++ b/qtdeclarative/src/quick/util/qquickstategroup.cpp @@ -381,8 +381,14 @@ bool QQuickStateGroupPrivate::updateAutoState() const auto potentialWhenBinding = QQmlPropertyPrivate::binding(whenProp); // if there is a binding, the value in when might not be up-to-date at this point // so we manually reevaluate the binding - if (auto abstractBinding = dynamic_cast(potentialWhenBinding)) - whenValue = abstractBinding->evaluate().toBool(); + if (auto abstractBinding = dynamic_cast(potentialWhenBinding)) { + QVariant evalResult = abstractBinding->evaluate(); + if (evalResult.userType() == qMetaTypeId()) + whenValue = evalResult.value().toBool(); + else + whenValue = evalResult.toBool(); + } + if (whenValue) { if (stateChangeDebug()) qWarning() << "Setting auto state due to expression"; diff --git a/qtdeclarative/src/quickwidgets/qaccessiblequickwidget.cpp b/qtdeclarative/src/quickwidgets/qaccessiblequickwidget.cpp new file mode 100644 index 0000000000..8a1c901880 --- /dev/null +++ b/qtdeclarative/src/quickwidgets/qaccessiblequickwidget.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblequickwidget_p.h" + +#include "qquickwidget_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +QAccessibleQuickWidget::QAccessibleQuickWidget(QQuickWidget* widget) +: QAccessibleWidget(widget) +, m_accessibleWindow(QQuickWidgetPrivate::get(widget)->offscreenWindow) +{ + // NOTE: m_accessibleWindow is a QAccessibleQuickWindow, and not a + // QAccessibleQuickWidgetOffscreenWindow (defined below). This means + // it will return the Quick item child interfaces, which is what's needed here + // (unlike QAccessibleQuickWidgetOffscreenWindow, which will report 0 children). +} + +QAccessibleInterface *QAccessibleQuickWidget::child(int index) const +{ + return m_accessibleWindow.child(index); +} + +int QAccessibleQuickWidget::childCount() const +{ + return m_accessibleWindow.childCount(); +} + +int QAccessibleQuickWidget::indexOfChild(const QAccessibleInterface *iface) const +{ + return m_accessibleWindow.indexOfChild(iface); +} + +QAccessibleInterface *QAccessibleQuickWidget::childAt(int x, int y) const +{ + return m_accessibleWindow.childAt(x, y); +} + +QAccessibleQuickWidgetOffscreenWindow::QAccessibleQuickWidgetOffscreenWindow(QQuickWindow *window) +:QAccessibleQuickWindow(window) +{ + +} + +QAccessibleInterface *QAccessibleQuickWidgetOffscreenWindow::child(int index) const +{ + Q_UNUSED(index); + return nullptr; +} + +int QAccessibleQuickWidgetOffscreenWindow::childCount() const +{ + return 0; +} + +int QAccessibleQuickWidgetOffscreenWindow::indexOfChild(const QAccessibleInterface *iface) const +{ + Q_UNUSED(iface); + return -1; +} + +QAccessibleInterface *QAccessibleQuickWidgetOffscreenWindow::QAccessibleQuickWidgetOffscreenWindow::childAt(int x, int y) const +{ + Q_UNUSED(x); + Q_UNUSED(y); + return nullptr; +} + +#endif // accessibility + +QT_END_NAMESPACE diff --git a/qtdeclarative/src/quickwidgets/qaccessiblequickwidget_p.h b/qtdeclarative/src/quickwidgets/qaccessiblequickwidget_p.h new file mode 100644 index 0000000000..7c2ab930e0 --- /dev/null +++ b/qtdeclarative/src/quickwidgets/qaccessiblequickwidget_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEQUICKWIDGET_H +#define QACCESSIBLEQUICKWIDGET_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickwidget.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +// These classes implement the QQuickWiget accessibility switcharoo, +// where the child items of the QQuickWidgetOffscreenWindow are reported +// as child accessible interfaces of the QAccessibleQuickWidget. +class QAccessibleQuickWidget: public QAccessibleWidget +{ +public: + QAccessibleQuickWidget(QQuickWidget* widget); + + QAccessibleInterface *child(int index) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *iface) const override; + QAccessibleInterface *childAt(int x, int y) const override; + +private: + QAccessibleQuickWindow m_accessibleWindow; + Q_DISABLE_COPY(QAccessibleQuickWidget) +}; + +class QAccessibleQuickWidgetOffscreenWindow: public QAccessibleQuickWindow +{ +public: + QAccessibleQuickWidgetOffscreenWindow(QQuickWindow *window); + QAccessibleInterface *child(int index) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *iface) const override; + QAccessibleInterface *childAt(int x, int y) const override; +}; + +#endif // accessibility + +QT_END_NAMESPACE + +#endif diff --git a/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory.cpp b/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory.cpp new file mode 100644 index 0000000000..7ba88a1769 --- /dev/null +++ b/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblequickwidgetfactory_p.h" +#include "qaccessiblequickwidget_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +QAccessibleInterface *qAccessibleQuickWidgetFactory(const QString &classname, QObject *object) +{ + if (classname == QLatin1String("QQuickWidget")) { + return new QAccessibleQuickWidget(qobject_cast(object)); + } else if (classname == QLatin1String("QQuickWidgetOffscreenWindow")) { + return new QAccessibleQuickWidgetOffscreenWindow(qobject_cast(object)); + } + return 0; +} + +#endif // accessibility + +QT_END_NAMESPACE + diff --git a/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory_p.h b/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory_p.h new file mode 100644 index 0000000000..8c63b09f81 --- /dev/null +++ b/qtdeclarative/src/quickwidgets/qaccessiblequickwidgetfactory_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QACCESSIBLEQUICKWIDGETFACTORY_H +#define QACCESSIBLEQUICKWIDGETFACTORY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(accessibility) + +QAccessibleInterface *qAccessibleQuickWidgetFactory(const QString &classname, QObject *object); + +#endif + +QT_END_NAMESPACE + +#endif diff --git a/qtdeclarative/src/quickwidgets/qquickwidget.cpp b/qtdeclarative/src/quickwidgets/qquickwidget.cpp index cf021d9a7c..b0117683f7 100644 --- a/qtdeclarative/src/quickwidgets/qquickwidget.cpp +++ b/qtdeclarative/src/quickwidgets/qquickwidget.cpp @@ -39,6 +39,7 @@ #include "qquickwidget.h" #include "qquickwidget_p.h" +#include "qaccessiblequickwidgetfactory_p.h" #include "private/qquickwindow_p.h" #include "private/qquickitem_p.h" @@ -75,9 +76,16 @@ QT_BEGIN_NAMESPACE +QQuickWidgetOffscreenWindow::QQuickWidgetOffscreenWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control) +:QQuickWindow(dd, control) +{ + setTitle(QString::fromLatin1("Offscreen")); + setObjectName(QString::fromLatin1("QQuickOffScreenWindow")); +} + // override setVisble to prevent accidental offscreen window being created // by base class. -class QQuickOffcreenWindowPrivate: public QQuickWindowPrivate { +class QQuickWidgetOffscreenWindowPrivate: public QQuickWindowPrivate { public: void setVisible(bool visible) override { Q_Q(QWindow); @@ -105,9 +113,8 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) Q_Q(QQuickWidget); renderControl = new QQuickWidgetRenderControl(q); - offscreenWindow = new QQuickWindow(*new QQuickOffcreenWindowPrivate(),renderControl); - offscreenWindow->setTitle(QString::fromLatin1("Offscreen")); - offscreenWindow->setObjectName(QString::fromLatin1("QQuickOffScreenWindow")); + offscreenWindow = new QQuickWidgetOffscreenWindow(*new QQuickWidgetOffscreenWindowPrivate(), renderControl); + offscreenWindow->setScreen(q->screen()); // Do not call create() on offscreenWindow. // Check if the Software Adaptation is being used @@ -138,6 +145,10 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) QWidget::connect(offscreenWindow, &QQuickWindow::focusObjectChanged, q, &QQuickWidget::propagateFocusObjectChanged); QObject::connect(renderControl, SIGNAL(renderRequested()), q, SLOT(triggerUpdate())); QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); + +#if QT_CONFIG(accessibility) + QAccessible::installFactory(&qAccessibleQuickWidgetFactory); +#endif } void QQuickWidgetPrivate::ensureEngine() const @@ -901,9 +912,7 @@ void QQuickWidgetPrivate::createContext() context = new QOpenGLContext; context->setFormat(offscreenWindow->requestedFormat()); - const QWindow *win = q->window()->windowHandle(); - if (win && win->screen()) - context->setScreen(win->screen()); + context->setScreen(q->screen()); QOpenGLContext *shareContext = qt_gl_global_share_context(); if (!shareContext) shareContext = QWidgetPrivate::get(q->window())->shareContext(); @@ -1527,19 +1536,16 @@ bool QQuickWidget::event(QEvent *e) d->handleWindowChange(); break; - case QEvent::ScreenChangeInternal: - if (QWindow *window = this->window()->windowHandle()) { - QScreen *newScreen = window->screen(); - - if (d->offscreenWindow) - d->offscreenWindow->setScreen(newScreen); - if (d->offscreenSurface) - d->offscreenSurface->setScreen(newScreen); + case QEvent::ScreenChangeInternal: { + QScreen *newScreen = screen(); + if (d->offscreenWindow) + d->offscreenWindow->setScreen(newScreen); + if (d->offscreenSurface) + d->offscreenSurface->setScreen(newScreen); #if QT_CONFIG(opengl) - if (d->context) - d->context->setScreen(newScreen); + if (d->context) + d->context->setScreen(newScreen); #endif - } if (d->useSoftwareRenderer #if QT_CONFIG(opengl) @@ -1552,7 +1558,7 @@ bool QQuickWidget::event(QEvent *e) d->render(true); } break; - + } case QEvent::Show: case QEvent::Move: d->updatePosition(); diff --git a/qtdeclarative/src/quickwidgets/qquickwidget_p.h b/qtdeclarative/src/quickwidgets/qquickwidget_p.h index 881f7f9220..1a946bcc71 100644 --- a/qtdeclarative/src/quickwidgets/qquickwidget_p.h +++ b/qtdeclarative/src/quickwidgets/qquickwidget_p.h @@ -148,6 +148,14 @@ public: bool forceFullUpdate; }; +class QQuickWidgetOffscreenWindow: public QQuickWindow +{ + Q_OBJECT + +public: + QQuickWidgetOffscreenWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control); +}; + QT_END_NAMESPACE #endif // QQuickWidget_P_H diff --git a/qtdeclarative/src/quickwidgets/quickwidgets.pro b/qtdeclarative/src/quickwidgets/quickwidgets.pro index 2438e577ae..85d156b8a3 100644 --- a/qtdeclarative/src/quickwidgets/quickwidgets.pro +++ b/qtdeclarative/src/quickwidgets/quickwidgets.pro @@ -7,9 +7,13 @@ DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_INTEGER_EVENT_COORDINATES QT_NO_FO HEADERS += \ qquickwidget.h \ qquickwidget_p.h \ - qtquickwidgetsglobal.h + qtquickwidgetsglobal.h \ + qaccessiblequickwidget_p.h \ + qaccessiblequickwidgetfactory_p.h SOURCES += \ - qquickwidget.cpp + qquickwidget.cpp \ + qaccessiblequickwidget.cpp \ + qaccessiblequickwidgetfactory.cpp load(qt_module) diff --git a/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml new file mode 100644 index 0000000000..23874970e7 --- /dev/null +++ b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml @@ -0,0 +1,50 @@ +import QtQuick 2.15 +import QtQml.Models 2.15 + +Item { + DelegateModel { + id: delegateModel + model: ListModel { + id: sourceModel + + ListElement { title: "foo" } + ListElement { title: "bar" } + + function clear() { + if (count > 0) + remove(0, count); + } + } + + groups: [ + DelegateModelGroup { name: "selectedItems" } + ] + + delegate: Text { + height: DelegateModel.inSelectedItems ? implicitHeight * 2 : implicitHeight + Component.onCompleted: { + if (index === 0) + DelegateModel.inSelectedItems = true; + } + } + + Component.onCompleted: { + items.create(0) + items.create(1) + } + } + + ListView { + anchors.fill: parent + model: delegateModel + } + + Timer { + running: true + interval: 10 + onTriggered: sourceModel.clear() + } + + property int count: delegateModel.items.count +} + diff --git a/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/redrawUponColumnChange.qml b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/redrawUponColumnChange.qml new file mode 100644 index 0000000000..206133bb39 --- /dev/null +++ b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/data/redrawUponColumnChange.qml @@ -0,0 +1,11 @@ +import QtQuick 2.8 + +ListView { + id: root + width: 200 + height: 200 + + delegate: Text { + text: display + } +} diff --git a/qtdeclarative/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 35f1e2c94d..f473cff75f 100644 --- a/qtdeclarative/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/qtdeclarative/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -27,6 +27,8 @@ ****************************************************************************/ #include +#include +#include #include #include #include @@ -47,6 +49,8 @@ private slots: void filterOnGroup_removeWhenCompleted(); void qtbug_86017(); void contextAccessedByHandler(); + void redrawUponColumnChange(); + void deleteRace(); }; class AbstractItemModel : public QAbstractItemModel @@ -186,6 +190,41 @@ void tst_QQmlDelegateModel::contextAccessedByHandler() QVERIFY(root->property("works").toBool()); } +void tst_QQmlDelegateModel::redrawUponColumnChange() +{ + QStandardItemModel m1; + m1.appendRow({ + new QStandardItem("Banana"), + new QStandardItem("Coconut"), + }); + + QQuickView view(testFileUrl("redrawUponColumnChange.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QQuickItem *root = view.rootObject(); + root->setProperty("model", QVariant::fromValue(&m1)); + + QObject *item = root->property("currentItem").value(); + QVERIFY(item); + QCOMPARE(item->property("text").toString(), "Banana"); + + QVERIFY(root); + m1.removeColumn(0); + + QCOMPARE(item->property("text").toString(), "Coconut"); +} + +void tst_QQmlDelegateModel::deleteRace() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("deleteRace.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("count").toInt(), 2); + QTRY_COMPARE(o->property("count").toInt(), 0); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" diff --git a/qtdeclarative/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/qtdeclarative/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index 9c865b3f73..1f788f7a7f 100644 --- a/qtdeclarative/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/qtdeclarative/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -154,6 +154,11 @@ void tst_QQmlImport::importPathOrder() engine.addImportPath(QT_QMLTEST_DATADIR); expectedImportPaths.prepend(QT_QMLTEST_DATADIR); QCOMPARE(expectedImportPaths, engine.importPathList()); + + // Add qml2Imports again to make it the first of the list + engine.addImportPath(qml2Imports); + expectedImportPaths.move(expectedImportPaths.indexOf(qml2Imports), 0); + QCOMPARE(expectedImportPaths, engine.importPathList()); } Q_DECLARE_METATYPE(QQmlImports::ImportVersion) diff --git a/qtdeclarative/tests/auto/qml/qqmllanguage/data/Broken.qml b/qtdeclarative/tests/auto/qml/qqmllanguage/data/Broken.qml new file mode 100644 index 0000000000..e24d9112a8 --- /dev/null +++ b/qtdeclarative/tests/auto/qml/qqmllanguage/data/Broken.qml @@ -0,0 +1,5 @@ +import QtQml 2.15 + +QtObject { + notThere: 5 +} diff --git a/qtdeclarative/tests/auto/qml/qqmllanguage/data/asBroken.qml b/qtdeclarative/tests/auto/qml/qqmllanguage/data/asBroken.qml new file mode 100644 index 0000000000..bd88d14c76 --- /dev/null +++ b/qtdeclarative/tests/auto/qml/qqmllanguage/data/asBroken.qml @@ -0,0 +1,6 @@ +import QtQml 2.15 + +QtObject { + id: self + property var selfAsBroken: self as Broken +} diff --git a/qtdeclarative/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/qtdeclarative/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 1be1533b63..dfc82011c6 100644 --- a/qtdeclarative/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/qtdeclarative/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -337,6 +337,7 @@ private slots: void bareInlineComponent(); void hangOnWarning(); + void objectAsBroken(); void ambiguousContainingType(); void staticConstexprMembers(); @@ -5939,6 +5940,21 @@ void tst_qqmllanguage::bindingAliasToComponentUrl() } } +void tst_qqmllanguage::objectAsBroken() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("asBroken.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + QVariant selfAsBroken = o->property("selfAsBroken"); + QVERIFY(selfAsBroken.isValid()); + // QCOMPARE(selfAsBroken.metaType(), QMetaType::fromType()); + + QQmlComponent b(&engine, testFileUrl("Broken.qml")); + QVERIFY(b.isError()); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/qtdeclarative/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/qtdeclarative/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index d092cd0170..62f7c67dd4 100644 --- a/qtdeclarative/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/qtdeclarative/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -2642,7 +2642,12 @@ void tst_qquickflickable::setContentPositionWhileDragging() // QTBUG-104966 } else if (newExtent >= 0) { // ...or reduce the content size be be less than current (contentX, contentY) position // This forces the content item to move. - expectedContentPos = moveDelta; + // contentY: 150 + // 320 - 150 = 170 pixels down to bottom + // Now reduce contentHeight to 200 + // since we are at the bottom, and the flickable is 100 pixels tall, contentY must land + // at newExtent - 100. + if (isHorizontal) { flickable->setContentWidth(newExtent); } else { @@ -2652,6 +2657,7 @@ void tst_qquickflickable::setContentPositionWhileDragging() // QTBUG-104966 // We therefore cannot scroll/flick it further down. Drag it up towards the top instead // (by moving mouse down). pos += moveDelta; + expectedContentPos = unitDelta * (newExtent - (isHorizontal ? flickable->width() : flickable->height())); } QTest::mouseMove(window.data(), pos); diff --git a/qtdeclarative/tests/auto/quick/qquickgridview/data/qtbug86255.qml b/qtdeclarative/tests/auto/quick/qquickgridview/data/qtbug86255.qml new file mode 100644 index 0000000000..20688b1967 --- /dev/null +++ b/qtdeclarative/tests/auto/quick/qquickgridview/data/qtbug86255.qml @@ -0,0 +1,55 @@ +import QtQuick 2.15 + +Item { + width: 240 + height: 320 + + GridView { + id: grid + objectName: "view" + anchors.fill: parent + cellWidth: 64 + cellHeight: 64 + model: ListModel { + id: listModel + + Component.onCompleted: reload() + + function reload() { + clear(); + for (let i = 0; i < 1000; i++) { + let magic = Math.random(); + append( { magic } ); + } + } + } + clip: true + delegate: Item { + id: d + property string val: magic + Loader { + property alias value: d.val + asynchronous: true + sourceComponent: cmp + } + } + } + + Timer { + running: true + interval: 1000 + onTriggered: listModel.reload() + } + Timer { + running: true + interval: 500 + onTriggered: grid.flick(0, -4000) + } + + Component { + id: cmp + Text { + text: value + } + } +} diff --git a/qtdeclarative/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/qtdeclarative/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index 94ec4f44d5..7d0d9fa7a7 100644 --- a/qtdeclarative/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/qtdeclarative/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -213,6 +213,7 @@ private slots: void QTBUG_45640(); void QTBUG_49218(); void QTBUG_48870_fastModelUpdates(); + void QTBUG_86255(); void keyNavigationEnabled(); void resizeDynamicCellWidthRtL(); @@ -6814,6 +6815,18 @@ void tst_QQuickGridView::resizeDynamicCellWidthRtL() QTRY_COMPARE(gridview->contentX(), 0.f); } +void tst_QQuickGridView::QTBUG_86255() +{ + QScopedPointer window(createView()); + window->setSource(testFileUrl("qtbug86255.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + QQuickGridView *view = findItem(window->rootObject(), "view"); + QVERIFY(view != nullptr); + QTRY_COMPARE(view->isFlicking(), true); + QTRY_COMPARE(view->isFlicking(), false); +} + void tst_QQuickGridView::releaseItems() { QScopedPointer view(createView()); diff --git a/qtdeclarative/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml b/qtdeclarative/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml new file mode 100644 index 0000000000..889e480f3b --- /dev/null +++ b/qtdeclarative/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml @@ -0,0 +1,13 @@ +import QtQuick 2.6 + +Item { + visible: true + Item { + visible: false + Item { + objectName: "hiddenChild" + activeFocusOnTab: true + focus: true + } + } +} diff --git a/qtdeclarative/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/qtdeclarative/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index c8f251dbe1..c8ef36ee68 100644 --- a/qtdeclarative/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/qtdeclarative/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -67,6 +67,7 @@ private slots: void activeFocusOnTab10(); void activeFocusOnTab_infiniteLoop_data(); void activeFocusOnTab_infiniteLoop(); + void activeFocusOnTab_infiniteLoopControls(); void nextItemInFocusChain(); void nextItemInFocusChain2(); @@ -1057,6 +1058,17 @@ void tst_QQuickItem::activeFocusOnTab_infiniteLoop() QCOMPARE(item, window->rootObject()); } + +void tst_QQuickItem::activeFocusOnTab_infiniteLoopControls() +{ + auto source = testFileUrl("activeFocusOnTab_infiniteLoop3.qml"); + QScopedPointerwindow(new QQuickView()); + window->setSource(source); + window->show(); + QVERIFY(window->errors().isEmpty()); + QTest::keyClick(window.get(), Qt::Key_Tab); // should not hang +} + void tst_QQuickItem::nextItemInFocusChain() { if (!qt_tab_all_widgets()) diff --git a/qtdeclarative/tests/auto/quick/qquickstates/data/jsValueWhen.qml b/qtdeclarative/tests/auto/quick/qquickstates/data/jsValueWhen.qml new file mode 100644 index 0000000000..6d5eb1600c --- /dev/null +++ b/qtdeclarative/tests/auto/quick/qquickstates/data/jsValueWhen.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 + +Item { + id: root + property var prop: null + property bool works: false + states: [ + State { + name: "mystate" + when: root.prop + PropertyChanges { + target: root + works: "works" + } + } + ] + Component.onCompleted: root.prop = new Object +} diff --git a/qtdeclarative/tests/auto/quick/qquickstates/tst_qquickstates.cpp b/qtdeclarative/tests/auto/quick/qquickstates/tst_qquickstates.cpp index aa55b42935..26e86672b0 100644 --- a/qtdeclarative/tests/auto/quick/qquickstates/tst_qquickstates.cpp +++ b/qtdeclarative/tests/auto/quick/qquickstates/tst_qquickstates.cpp @@ -188,6 +188,7 @@ private slots: void revertListMemoryLeak(); void duplicateStateName(); void trivialWhen(); + void jsValueWhen(); void noStateOsciallation(); void parentChangeCorrectReversal(); void revertNullObjectBinding(); @@ -1734,6 +1735,16 @@ void tst_qquickstates::trivialWhen() QVERIFY(c.create()); } +void tst_qquickstates::jsValueWhen() +{ + QQmlEngine engine; + + QQmlComponent c(&engine, testFileUrl("jsValueWhen.qml")); + QScopedPointer root(c.create()); + QVERIFY(root); + QVERIFY(root->property("works").toBool()); +} + void tst_qquickstates::noStateOsciallation() { QQmlEngine engine; diff --git a/qtdeclarative/tests/manual/quickcontrols2/swipedelegate/CloseOnCompletedWorks.qml b/qtdeclarative/tests/manual/quickcontrols2/swipedelegate/CloseOnCompletedWorks.qml new file mode 100644 index 0000000000..38dfde41c3 --- /dev/null +++ b/qtdeclarative/tests/manual/quickcontrols2/swipedelegate/CloseOnCompletedWorks.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2 +import QtQuick.Controls 2 +ApplicationWindow { + visible: true + width: 640 + height: 480 + + ListView { + anchors.fill: parent + model: 2 + + delegate: SwipeDelegate { + text: "Swipe me left (should not crash)" + + swipe.right: Label { + text: "Release (should not crash)" + } + + swipe.onCompleted: { + swipe.close() + } + } + } +} diff --git a/qtdeclarative/tools/qml/main.cpp b/qtdeclarative/tools/qml/main.cpp index beeec88f07..2cb7653d65 100644 --- a/qtdeclarative/tools/qml/main.cpp +++ b/qtdeclarative/tools/qml/main.cpp @@ -446,8 +446,8 @@ int main(int argc, char *argv[]) QCommandLineParser parser; parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); - const QCommandLineOption helpOption = parser.addHelpOption(); - const QCommandLineOption versionOption = parser.addVersionOption(); + parser.addHelpOption(); + parser.addVersionOption(); #ifdef QT_GUI_LIB QCommandLineOption apptypeOption(QStringList() << QStringLiteral("a") << QStringLiteral("apptype"), QCoreApplication::translate("main", "Select which application class to use. Default is gui."), @@ -522,14 +522,7 @@ int main(int argc, char *argv[]) parser.addPositionalArgument("args", QCoreApplication::translate("main", "Arguments after '--' are ignored, but passed through to the application.arguments variable in QML."), "[-- args...]"); - if (!parser.parse(QCoreApplication::arguments())) { - qWarning() << parser.errorText(); - exit(1); - } - if (parser.isSet(versionOption)) - parser.showVersion(); - if (parser.isSet(helpOption)) - parser.showHelp(); + parser.process(*app); if (parser.isSet(listConfOption)) listConfFiles(); if (applicationType == QmlApplicationTypeUnknown) { Submodule qtdoc 362b09c8...c8af0c56: Submodule qtimageformats 6a1af670..b22bf4d0: diff --git a/qtimageformats/src/plugins/imageformats/tga/qtgafile.cpp b/qtimageformats/src/plugins/imageformats/tga/qtgafile.cpp index 5d086c6..3961c16 100644 --- a/qtimageformats/src/plugins/imageformats/tga/qtgafile.cpp +++ b/qtimageformats/src/plugins/imageformats/tga/qtgafile.cpp @@ -220,9 +220,18 @@ QImage QTgaFile::readImage() int offset = mHeader[IdLength]; // Mostly always zero - // Even in TrueColor files a color pallette may be present - if (mHeader[ColorMapType] == 1) - offset += littleEndianInt(&mHeader[CMapLength]) * littleEndianInt(&mHeader[CMapDepth]); + // Even in TrueColor files a color palette may be present so we have to check it here + // even we only support image type 2 (= uncompressed true-color image) + if (mHeader[ColorMapType] == 1) { + int cmapDepth = mHeader[CMapDepth]; + if (cmapDepth == 15) // 15 bit is stored as 16 bit + ignoring the highest bit (no alpha) + cmapDepth = 16; + if (cmapDepth != 16 && cmapDepth != 24 && cmapDepth != 32) { + mErrorMessage = tr("Invalid color map depth (%1)").arg(cmapDepth); + return {}; + } + offset += littleEndianInt(&mHeader[CMapLength]) * cmapDepth / 8; + } mDevice->seek(HeaderSize + offset); diff --git a/qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp b/qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp index 1386750..79be154 100644 --- a/qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp +++ b/qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp @@ -38,11 +38,15 @@ ****************************************************************************/ #include "qtiffhandler_p.h" -#include #include #include #include #include +#include +#include +#include +#include + extern "C" { #include "tiffio.h" } @@ -90,13 +94,33 @@ toff_t qtiffSizeProc(thandle_t fd) return static_cast(fd)->size(); } -int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/) +int qtiffMapProc(thandle_t fd, void **base, toff_t *size) { + QIODevice *device = static_cast(fd); + + QFileDevice *file = qobject_cast(device); + if (file) { + *base = file->map(0, file->size()); + if (*base != nullptr) { + *size = file->size(); + return 1; + } + } else { + QBuffer *buf = qobject_cast(device); + if (buf) { + *base = const_cast(buf->data().constData()); + *size = buf->size(); + return 1; + } + } return 0; } -void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/) +void qtiffUnmapProc(thandle_t fd, void *base, toff_t /*size*/) { + QFileDevice *file = qobject_cast(static_cast(fd)); + if (file && base) + file->unmap(static_cast(base)); } @@ -361,6 +385,8 @@ bool QTiffHandler::read(QImage *image) } TIFF *const tiff = d->tiff; + if (TIFFIsTiled(tiff) && TIFFTileSize64(tiff) > uint64_t(image->sizeInBytes())) // Corrupt image + return false; const quint32 width = d->size.width(); const quint32 height = d->size.height(); diff --git a/qtimageformats/src/plugins/imageformats/webp/CMakeLists.txt b/qtimageformats/src/plugins/imageformats/webp/CMakeLists.txt index 25aa0c9..fbbcc1c 100644 --- a/qtimageformats/src/plugins/imageformats/webp/CMakeLists.txt +++ b/qtimageformats/src/plugins/imageformats/webp/CMakeLists.txt @@ -30,6 +30,7 @@ qt_internal_extend_target(QWebpPlugin CONDITION QT_FEATURE_system_webp qt_internal_extend_target(QWebpPlugin CONDITION NOT QT_FEATURE_system_webp SOURCES ../../../3rdparty/libwebp/sharpyuv/sharpyuv.c + ../../../3rdparty/libwebp/sharpyuv/sharpyuv_cpu.c ../../../3rdparty/libwebp/sharpyuv/sharpyuv_csp.c ../../../3rdparty/libwebp/sharpyuv/sharpyuv_dsp.c ../../../3rdparty/libwebp/sharpyuv/sharpyuv_gamma.c diff --git a/qtimageformats/src/plugins/imageformats/webp/qwebphandler.cpp b/qtimageformats/src/plugins/imageformats/webp/qwebphandler.cpp index 82d38cb..d02eb05 100644 --- a/qtimageformats/src/plugins/imageformats/webp/qwebphandler.cpp +++ b/qtimageformats/src/plugins/imageformats/webp/qwebphandler.cpp @@ -45,6 +45,7 @@ #include #include #include +#include static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h @@ -102,21 +103,23 @@ bool QWebpHandler::ensureScanned() const m_scanState = ScanError; - if (device()->isSequential()) { - qWarning() << "Sequential devices are not supported"; + QWebpHandler *that = const_cast(this); + const int headerBytesNeeded = sizeof(WebPBitstreamFeatures); + QByteArray header = device()->peek(headerBytesNeeded); + if (header.size() < headerBytesNeeded) return false; - } - qint64 oldPos = device()->pos(); - device()->seek(0); - - QWebpHandler *that = const_cast(this); - QByteArray header = device()->peek(sizeof(WebPBitstreamFeatures)); + // We do no random access during decoding, just a readAll() of the whole image file. So if + // if it is all available already, we can accept a sequential device. The riff header contains + // the file size minus 8 bytes header + qint64 byteSize = qFromLittleEndian(header.constData() + 4); + if (device()->isSequential() && device()->bytesAvailable() < byteSize + 8) { + qWarning() << "QWebpHandler: Insufficient data available in sequential device"; + return false; + } if (WebPGetFeatures((const uint8_t*)header.constData(), header.size(), &(that->m_features)) == VP8_STATUS_OK) { if (m_features.has_animation) { // For animation, we have to read and scan whole file to determine loop count and images count - device()->seek(oldPos); - if (that->ensureDemuxer()) { that->m_loop = WebPDemuxGetI(m_demuxer, WEBP_FF_LOOP_COUNT); that->m_frameCount = WebPDemuxGetI(m_demuxer, WEBP_FF_FRAME_COUNT); @@ -126,17 +129,13 @@ bool QWebpHandler::ensureScanned() const if (that->m_features.has_alpha) that->m_composited->fill(Qt::transparent); - // We do not reset device position since we have read in all data m_scanState = ScanSuccess; - return true; } } else { m_scanState = ScanSuccess; } } - device()->seek(oldPos); - return m_scanState == ScanSuccess; } @@ -159,7 +158,7 @@ bool QWebpHandler::ensureDemuxer() bool QWebpHandler::read(QImage *image) { - if (!ensureScanned() || device()->isSequential() || !ensureDemuxer()) + if (!ensureScanned() || !ensureDemuxer()) return false; QRect prevFrameRect; Submodule qtlocation 0ec8f5e8..b4c42e25: Submodule src/3rdparty/mapbox-gl-native d3101bbc...4c88f2c0 (commits not present) diff --git a/qtlocation/src/location/labs/qsg/qgeomapobjectqsgsupport.cpp b/qtlocation/src/location/labs/qsg/qgeomapobjectqsgsupport.cpp index a978573d..11e1466f 100644 --- a/qtlocation/src/location/labs/qsg/qgeomapobjectqsgsupport.cpp +++ b/qtlocation/src/location/labs/qsg/qgeomapobjectqsgsupport.cpp @@ -158,7 +158,7 @@ void QGeoMapObjectQSGSupport::updateMapObjects(QSGNode *root, QQuickWindow *wind if (!root) return; - if (m_mapObjectsRootNode && m_mapObjectsRootNode->parent()) + if (m_mapObjectsRootNode && !m_mapObjectsRootNode->parent()) root->appendChildNode(m_mapObjectsRootNode.get()); if (!m_mapObjectsRootNode) { Submodule qtmultimedia b4d58d89..f587b18d: diff --git a/qtmultimedia/src/gsttools/qgstvideorenderersink.cpp b/qtmultimedia/src/gsttools/qgstvideorenderersink.cpp index 4000f2178..a446d93fe 100644 --- a/qtmultimedia/src/gsttools/qgstvideorenderersink.cpp +++ b/qtmultimedia/src/gsttools/qgstvideorenderersink.cpp @@ -368,7 +368,8 @@ static GstGLContext *gstGLDisplayContext(QAbstractVideoSurface *surface) if (!nativeContext) qWarning() << "Could not find resource for" << contextName; - GstGLContext *appContext = gst_gl_context_new_wrapped(display, (guintptr)nativeContext, glPlatform, GST_GL_API_ANY); + GstGLAPI glApi = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? GST_GL_API_OPENGL : GST_GL_API_GLES2; + GstGLContext *appContext = gst_gl_context_new_wrapped(display, (guintptr)nativeContext, glPlatform, glApi); if (!appContext) qWarning() << "Could not create wrappped context for platform:" << glPlatform; diff --git a/qtmultimedia/src/multimediawidgets/multimediawidgets.pro b/qtmultimedia/src/multimediawidgets/multimediawidgets.pro index 1919e8107..4c30d8fbf 100644 --- a/qtmultimedia/src/multimediawidgets/multimediawidgets.pro +++ b/qtmultimedia/src/multimediawidgets/multimediawidgets.pro @@ -2,8 +2,6 @@ TARGET = QtMultimediaWidgets QT = core gui multimedia widgets-private QT_PRIVATE += multimedia-private -qtHaveModule(opengl): \ - QT_PRIVATE += opengl PRIVATE_HEADERS += \ qvideowidget_p.h \ Submodule qtpim 02efef5e..f9a8f0fc: (diff failed) Submodule qtquick3d 18711bd8..f3c3c204: diff --git a/qtquick3d/src/plugins/assetimporters/assimp/assimp.pro b/qtquick3d/src/plugins/assetimporters/assimp/assimp.pro index ca5c499e..174a075b 100644 --- a/qtquick3d/src/plugins/assetimporters/assimp/assimp.pro +++ b/qtquick3d/src/plugins/assetimporters/assimp/assimp.pro @@ -10,7 +10,7 @@ QT_FOR_CONFIG += assetimporters-private include($$OUT_PWD/../qtassetimporters-config.pri) qtConfig(system-assimp):!if(cross_compile:host_build) { - QMAKE_USE_PRIVATE += assimp + QMAKE_USE_PRIVATE += quick3d-assimp } else { include(../../../3rdparty/assimp/assimp.pri) } Submodule qtquickcontrols2 1ce461bd..a2d56960: diff --git a/qtquickcontrols2/src/imports/platform/widgets/qwidgetplatformmenu.cpp b/qtquickcontrols2/src/imports/platform/widgets/qwidgetplatformmenu.cpp index e5fe734f7..e36922775 100644 --- a/qtquickcontrols2/src/imports/platform/widgets/qwidgetplatformmenu.cpp +++ b/qtquickcontrols2/src/imports/platform/widgets/qwidgetplatformmenu.cpp @@ -38,6 +38,7 @@ #include "qwidgetplatformmenuitem_p.h" #include +#include #include #include @@ -145,7 +146,7 @@ void QWidgetPlatformMenu::showPopup(const QWindow *window, const QRect &targetRe QPoint targetPos = targetRect.bottomLeft(); if (window) - targetPos = window->mapToGlobal(targetPos); + targetPos = window->mapToGlobal(QHighDpi::fromNativeLocalPosition(targetPos, window)); const QWidgetPlatformMenuItem *widgetItem = qobject_cast(item); m_menu->popup(targetPos, widgetItem ? widgetItem->action() : nullptr); diff --git a/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton.cpp b/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton.cpp index 20cf59c1a..43af47a94 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton.cpp @@ -1201,6 +1201,12 @@ QAccessible::Role QQuickAbstractButton::accessibleRole() const } return QAccessible::Button; } + +void QQuickAbstractButton::accessiblePressAction() +{ + Q_D(QQuickAbstractButton); + d->trigger(); +} #endif QT_END_NAMESPACE diff --git a/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton_p.h b/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton_p.h index 0fa48980e..ab66220d0 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton_p.h +++ b/qtquickcontrols2/src/quicktemplates2/qquickabstractbutton_p.h @@ -209,6 +209,7 @@ protected: #if QT_CONFIG(accessibility) void accessibilityActiveChanged(bool active) override; QAccessible::Role accessibleRole() const override; + Q_INVOKABLE void accessiblePressAction(); #endif private: diff --git a/qtquickcontrols2/src/quicktemplates2/qquickcontainer.cpp b/qtquickcontrols2/src/quicktemplates2/qquickcontainer.cpp index f38c2b09c..6eed2a024 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickcontainer.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickcontainer.cpp @@ -225,6 +225,7 @@ void QQuickContainerPrivate::cleanup() QObject::disconnect(contentModel, &QQmlObjectModel::countChanged, q, &QQuickContainer::countChanged); QObject::disconnect(contentModel, &QQmlObjectModel::childrenChanged, q, &QQuickContainer::contentChildrenChanged); delete contentModel; + contentModel = nullptr; } QQuickItem *QQuickContainerPrivate::itemAt(int index) const @@ -436,7 +437,7 @@ void QQuickContainerPrivate::contentChildren_clear(QQmlListProperty void QQuickContainerPrivate::updateContentWidth() { Q_Q(QQuickContainer); - if (hasContentWidth || qFuzzyCompare(contentWidth, implicitContentWidth)) + if (hasContentWidth || qFuzzyCompare(contentWidth, implicitContentWidth) || !contentModel) return; contentWidth = implicitContentWidth; @@ -446,7 +447,7 @@ void QQuickContainerPrivate::updateContentWidth() void QQuickContainerPrivate::updateContentHeight() { Q_Q(QQuickContainer); - if (hasContentHeight || qFuzzyCompare(contentHeight, implicitContentHeight)) + if (hasContentHeight || qFuzzyCompare(contentHeight, implicitContentHeight) || !contentModel) return; contentHeight = implicitContentHeight; diff --git a/qtquickcontrols2/src/quicktemplates2/qquickcontrol.cpp b/qtquickcontrols2/src/quicktemplates2/qquickcontrol.cpp index a719efd34..768691dac 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickcontrol.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickcontrol.cpp @@ -2334,12 +2334,13 @@ QAccessible::Role QQuickControl::accessibleRole() const void QQuickControl::accessibilityActiveChanged(bool active) { + Q_D(QQuickControl); if (!active) return; QQuickAccessibleAttached *accessibleAttached = qobject_cast(qmlAttachedPropertiesObject(this, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(d->effectiveAccessibleRole()); } #endif diff --git a/qtquickcontrols2/src/quicktemplates2/qquickdialogbuttonbox.cpp b/qtquickcontrols2/src/quicktemplates2/qquickdialogbuttonbox.cpp index e6db14eb5..6197d1547 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickdialogbuttonbox.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickdialogbuttonbox.cpp @@ -237,7 +237,7 @@ static QRectF alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment void QQuickDialogButtonBoxPrivate::resizeContent() { Q_Q(QQuickDialogButtonBox); - if (!contentItem) + if (!contentItem || !contentModel) return; QRectF geometry = q->boundingRect().adjusted(q->leftPadding(), q->topPadding(), -q->rightPadding(), -q->bottomPadding()); @@ -322,6 +322,9 @@ void QQuickDialogButtonBoxPrivate::updateLayout() qreal QQuickDialogButtonBoxPrivate::getContentWidth() const { Q_Q(const QQuickDialogButtonBox); + if (!contentModel) + return 0; + const int count = contentModel->count(); const qreal totalSpacing = qMax(0, count - 1) * spacing; qreal totalWidth = totalSpacing; @@ -341,6 +344,9 @@ qreal QQuickDialogButtonBoxPrivate::getContentWidth() const qreal QQuickDialogButtonBoxPrivate::getContentHeight() const { Q_Q(const QQuickDialogButtonBox); + if (!contentModel) + return 0; + const int count = contentModel->count(); qreal maxHeight = 0; for (int i = 0; i < count; ++i) { diff --git a/qtquickcontrols2/src/quicktemplates2/qquicklabel.cpp b/qtquickcontrols2/src/quicktemplates2/qquicklabel.cpp index 71b60a2bc..2bc621674 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquicklabel.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquicklabel.cpp @@ -263,7 +263,7 @@ void QQuickLabelPrivate::accessibilityActiveChanged(bool active) Q_Q(QQuickLabel); QQuickAccessibleAttached *accessibleAttached = qobject_cast(qmlAttachedPropertiesObject(q, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(effectiveAccessibleRole()); maybeSetAccessibleName(text); } diff --git a/qtquickcontrols2/src/quicktemplates2/qquickoverlay.cpp b/qtquickcontrols2/src/quicktemplates2/qquickoverlay.cpp index 91bd59184..0ce518f84 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickoverlay.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickoverlay.cpp @@ -399,8 +399,11 @@ void QQuickOverlay::itemChange(ItemChange change, const ItemChangeData &data) Q_D(QQuickOverlay); QQuickItem::itemChange(change, data); - if (change == ItemChildAddedChange || change == ItemChildRemovedChange) + if (change == ItemChildAddedChange || change == ItemChildRemovedChange) { setVisible(!d->allDrawers.isEmpty() || !childItems().isEmpty()); + if (data.item->parent() == d->mouseGrabberPopup) + d->setMouseGrabberPopup(nullptr); + } } void QQuickOverlay::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) diff --git a/qtquickcontrols2/src/quicktemplates2/qquickpopup.cpp b/qtquickcontrols2/src/quicktemplates2/qquickpopup.cpp index 7df80a047..bfaa84e30 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickpopup.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickpopup.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include @@ -2720,6 +2721,19 @@ QPalette QQuickPopup::defaultPalette() const } #if QT_CONFIG(accessibility) +QAccessible::Role QQuickPopup::effectiveAccessibleRole() const +{ + auto *attached = qmlAttachedPropertiesObject(this, false); + + auto role = QAccessible::NoRole; + if (auto *accessibleAttached = qobject_cast(attached)) + role = accessibleAttached->role(); + if (role == QAccessible::NoRole) + role = accessibleRole(); + + return role; +} + QAccessible::Role QQuickPopup::accessibleRole() const { return QAccessible::Dialog; diff --git a/qtquickcontrols2/src/quicktemplates2/qquickpopup_p.h b/qtquickcontrols2/src/quicktemplates2/qquickpopup_p.h index dc3ebf6f8..a3773be3e 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickpopup_p.h +++ b/qtquickcontrols2/src/quicktemplates2/qquickpopup_p.h @@ -454,7 +454,10 @@ protected: virtual QPalette defaultPalette() const; #if QT_CONFIG(accessibility) + QAccessible::Role effectiveAccessibleRole() const; +private: virtual QAccessible::Role accessibleRole() const; +protected: virtual void accessibilityActiveChanged(bool active); #endif diff --git a/qtquickcontrols2/src/quicktemplates2/qquickpopupitem.cpp b/qtquickcontrols2/src/quicktemplates2/qquickpopupitem.cpp index 0069b9fc1..143c37fc3 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquickpopupitem.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquickpopupitem.cpp @@ -404,7 +404,7 @@ QPalette QQuickPopupItem::defaultPalette() const QAccessible::Role QQuickPopupItem::accessibleRole() const { Q_D(const QQuickPopupItem); - return d->popup->accessibleRole(); + return d->popup->effectiveAccessibleRole(); } void QQuickPopupItem::accessibilityActiveChanged(bool active) diff --git a/qtquickcontrols2/src/quicktemplates2/qquicktextarea.cpp b/qtquickcontrols2/src/quicktemplates2/qquicktextarea.cpp index 64fc631dd..fba3f6b70 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquicktextarea.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquicktextarea.cpp @@ -512,7 +512,7 @@ void QQuickTextAreaPrivate::accessibilityActiveChanged(bool active) Q_Q(QQuickTextArea); QQuickAccessibleAttached *accessibleAttached = qobject_cast(qmlAttachedPropertiesObject(q, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(effectiveAccessibleRole()); accessibleAttached->set_readOnly(q->isReadOnly()); accessibleAttached->setDescription(placeholder); } diff --git a/qtquickcontrols2/src/quicktemplates2/qquicktextfield.cpp b/qtquickcontrols2/src/quicktemplates2/qquicktextfield.cpp index 8fa04bd3a..e83346cbd 100644 --- a/qtquickcontrols2/src/quicktemplates2/qquicktextfield.cpp +++ b/qtquickcontrols2/src/quicktemplates2/qquicktextfield.cpp @@ -359,7 +359,7 @@ void QQuickTextFieldPrivate::accessibilityActiveChanged(bool active) Q_Q(QQuickTextField); QQuickAccessibleAttached *accessibleAttached = qobject_cast(qmlAttachedPropertiesObject(q, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(effectiveAccessibleRole()); accessibleAttached->set_readOnly(m_readOnly); accessibleAttached->set_passwordEdit((m_echoMode == QQuickTextField::Password || m_echoMode == QQuickTextField::PasswordEchoOnEdit) ? true : false); accessibleAttached->setDescription(placeholder); diff --git a/qtquickcontrols2/tests/auto/qquickpopup/data/releaseAfterExitTransition.qml b/qtquickcontrols2/tests/auto/qquickpopup/data/releaseAfterExitTransition.qml new file mode 100644 index 000000000..9e4598b9f --- /dev/null +++ b/qtquickcontrols2/tests/auto/qquickpopup/data/releaseAfterExitTransition.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +ApplicationWindow { + id: window + width: 400 + height: 400 + title: "releaseAfterExitTransition" + + property alias popup: popup + property alias modalPopup: modalPopup + + Popup { + id: popup + y: parent.height - height + width: 50 + height: 50 + } + + Popup { + id: modalPopup + modal: true + y: parent.height - height + width: 50 + height: 50 + exit: Transition { PauseAnimation { duration: 100 } } + } +} diff --git a/qtquickcontrols2/tests/auto/qquickpopup/tst_qquickpopup.cpp b/qtquickcontrols2/tests/auto/qquickpopup/tst_qquickpopup.cpp index 54952d128..3d50e2dd4 100644 --- a/qtquickcontrols2/tests/auto/qquickpopup/tst_qquickpopup.cpp +++ b/qtquickcontrols2/tests/auto/qquickpopup/tst_qquickpopup.cpp @@ -100,6 +100,7 @@ private slots: void invisibleToolTipOpen(); void centerInOverlayWithinStackViewItem(); void destroyDuringExitTransition(); + void releaseAfterExitTransition(); }; void tst_QQuickPopup::initTestCase() @@ -1575,6 +1576,34 @@ void tst_QQuickPopup::destroyDuringExitTransition() QVERIFY(!button->isDown()); } +void tst_QQuickPopup::releaseAfterExitTransition() +{ + QQuickApplicationHelper helper(this, "releaseAfterExitTransition.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + QQuickPopup *modalPopup = window->property("modalPopup").value(); + QQuickPopup *popup = window->property("popup").value(); + + modalPopup->open(); + QTRY_VERIFY(modalPopup->isOpened()); + + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); + // wait until the transition is finished and the overlay hides itself + QTRY_VERIFY(!overlay->isVisible()); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); + + popup->open(); + QTRY_VERIFY(popup->isOpened()); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1)); + QTRY_VERIFY(!popup->isOpened()); +} + + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" Submodule qtscript 5be95f96..0e91c4ab: (diff failed) Submodule qtspeech 214dcefc..3b163bfd: diff --git a/qtspeech/src/plugins/tts/speechdispatcher/qtexttospeech_speechd.cpp b/qtspeech/src/plugins/tts/speechdispatcher/qtexttospeech_speechd.cpp index 6eb74b8..bcc7dd1 100644 --- a/qtspeech/src/plugins/tts/speechdispatcher/qtexttospeech_speechd.cpp +++ b/qtspeech/src/plugins/tts/speechdispatcher/qtexttospeech_speechd.cpp @@ -357,7 +357,9 @@ QVector QTextToSpeechEngineSpeechd::availableLocales() const QVector QTextToSpeechEngineSpeechd::availableVoices() const { - return m_voices.values(m_currentLocale.name()).toVector(); + QList resultList = m_voices.values(m_currentLocale.name()); + std::reverse(resultList.begin(), resultList.end()); + return resultList.toVector(); } // We have no way of knowing our own client_id since speech-dispatcher seems to be incomplete Submodule qtsvg 3385b64d..7d6e373c: diff --git a/qtsvg/src/plugins/imageformats/svg/qsvgiohandler.cpp b/qtsvg/src/plugins/imageformats/svg/qsvgiohandler.cpp index 561e77e..12e0574 100644 --- a/qtsvg/src/plugins/imageformats/svg/qsvgiohandler.cpp +++ b/qtsvg/src/plugins/imageformats/svg/qsvgiohandler.cpp @@ -191,6 +191,8 @@ bool QSvgIOHandler::read(QImage *image) } } if (!finalSize.isEmpty()) { + if (qMax(finalSize.width(), finalSize.height()) > 0xffff) + return false; // Assume corrupted file image->fill(d->backColor.rgba()); QPainter p(image); d->r.render(&p, bounds); diff --git a/qtsvg/src/svg/qsvgfont_p.h b/qtsvg/src/svg/qsvgfont_p.h index fd0a3fa..fcffbe8 100644 --- a/qtsvg/src/svg/qsvgfont_p.h +++ b/qtsvg/src/svg/qsvgfont_p.h @@ -74,6 +74,7 @@ public: class Q_SVG_PRIVATE_EXPORT QSvgFont : public QSvgRefCounted { public: + static constexpr qreal DEFAULT_UNITS_PER_EM = 1000; QSvgFont(qreal horizAdvX); void setFamilyName(const QString &name); @@ -86,9 +87,7 @@ public: void draw(QPainter *p, const QPointF &point, const QString &str, qreal pixelSize, Qt::Alignment alignment) const; public: QString m_familyName; - qreal m_unitsPerEm; - qreal m_ascent; - qreal m_descent; + qreal m_unitsPerEm = DEFAULT_UNITS_PER_EM; qreal m_horizAdvX; QHash m_glyphs; }; diff --git a/qtsvg/src/svg/qsvghandler.cpp b/qtsvg/src/svg/qsvghandler.cpp index c229c3b..222b6d8 100644 --- a/qtsvg/src/svg/qsvghandler.cpp +++ b/qtsvg/src/svg/qsvghandler.cpp @@ -1393,9 +1393,10 @@ static void parseFont(QSvgNode *node, case FontSizeNone: break; case FontSizeValue: { - QSvgHandler::LengthType dummy; // should always be pixel size - fontStyle->setSize(qMin(parseLength(attributes.fontSize, dummy, handler), - qreal(0xffff))); + QSvgHandler::LengthType type; + qreal fs = parseLength(attributes.fontSize, type, handler); + fs = convertToPixels(fs, true, type); + fontStyle->setSize(qMin(fs, qreal(0xffff))); } break; default: @@ -2578,6 +2579,8 @@ static QSvgNode *createCircleNode(QSvgNode *parent, qreal ncx = toDouble(cx); qreal ncy = toDouble(cy); qreal nr = toDouble(r); + if (nr < 0.0) + return nullptr; QRectF rect(ncx-nr, ncy-nr, nr*2, nr*2); QSvgNode *circle = new QSvgCircle(parent, rect); @@ -2668,7 +2671,7 @@ static bool parseFontFaceNode(QSvgStyleProperty *parent, qreal unitsPerEm = toDouble(unitsPerEmStr); if (!unitsPerEm) - unitsPerEm = 1000; + unitsPerEm = QSvgFont::DEFAULT_UNITS_PER_EM; if (!name.isEmpty()) font->setFamilyName(name); @@ -3048,15 +3051,16 @@ static QSvgStyleProperty *createRadialGradientNode(QSvgNode *node, qreal ncx = 0.5; qreal ncy = 0.5; - qreal nr = 0.5; if (!cx.isEmpty()) ncx = toDouble(cx); if (!cy.isEmpty()) ncy = toDouble(cy); + + qreal nr = 0.0; if (!r.isEmpty()) nr = toDouble(r); - if (nr < 0.5) - nr = 0.5; + if (nr <= 0.0) + return nullptr; qreal nfx = ncx; if (!fx.isEmpty()) @@ -3352,7 +3356,9 @@ static QSvgNode *createTextNode(QSvgNode *parent, //### editable and rotate not handled QSvgHandler::LengthType type; qreal nx = parseLength(x, type, handler); + nx = convertToPixels(nx, true, type); qreal ny = parseLength(y, type, handler); + ny = convertToPixels(ny, true, type); QSvgNode *text = new QSvgText(parent, QPointF(nx, ny)); return text; diff --git a/qtsvg/src/svg/qsvgstructure.cpp b/qtsvg/src/svg/qsvgstructure.cpp index b89608b..89c9e4e 100644 --- a/qtsvg/src/svg/qsvgstructure.cpp +++ b/qtsvg/src/svg/qsvgstructure.cpp @@ -255,9 +255,13 @@ inline static bool isSupportedSvgFeature(const QString &str) }; if (str.length() <= MAX_WORD_LENGTH && str.length() >= MIN_WORD_LENGTH) { + const char16_t unicode44 = str.at(44).unicode(); + const char16_t unicode45 = str.at(45).unicode(); + if (unicode44 >= sizeof(asso_values) || unicode45 >= sizeof(asso_values)) + return false; const int key = str.length() - + asso_values[str.at(45).unicode()] - + asso_values[str.at(44).unicode()]; + + asso_values[unicode45] + + asso_values[unicode44]; if (key <= MAX_HASH_VALUE && key >= 0) return str == QLatin1String(wordlist[key]); } Submodule qttools ddf6686e..38ae810b: diff --git a/qttools/src/assistant/help/help.pro b/qttools/src/assistant/help/help.pro index 800c4a38d..7556f451b 100644 --- a/qttools/src/assistant/help/help.pro +++ b/qttools/src/assistant/help/help.pro @@ -1,7 +1,6 @@ TARGET = QtHelp QT = core-private gui widgets sql -QT_PRIVATE = network DEFINES += QHELP_LIB diff --git a/qttools/src/assistant/qhelpgenerator/helpgenerator.cpp b/qttools/src/assistant/qhelpgenerator/helpgenerator.cpp index feab1e2d5..cbfb82507 100644 --- a/qttools/src/assistant/qhelpgenerator/helpgenerator.cpp +++ b/qttools/src/assistant/qhelpgenerator/helpgenerator.cpp @@ -445,7 +445,9 @@ bool HelpGeneratorPrivate::insertFiles(const QStringList &files, const QString & if (filterSetId < 0) return false; ++filterSetId; - for (int attId : qAsConst(filterAtts)) { + QList attValues = filterAtts.values(); + std::sort(attValues.begin(), attValues.end()); + for (int attId : qAsConst(attValues)) { m_query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable " "VALUES(?, ?)")); m_query->bindValue(0, filterSetId); diff --git a/qttools/src/assistant/qhelpgenerator/qhelpgenerator.pro b/qttools/src/assistant/qhelpgenerator/qhelpgenerator.pro index bb22000c8..415347a00 100644 --- a/qttools/src/assistant/qhelpgenerator/qhelpgenerator.pro +++ b/qttools/src/assistant/qhelpgenerator/qhelpgenerator.pro @@ -1,4 +1,4 @@ -QT += network help-private +QT += help-private QTPLUGIN.platforms = qminimal QTPLUGIN.sqldrivers = qsqlite diff --git a/qttools/src/linguist/Qt5LinguistToolsMacros.cmake b/qttools/src/linguist/Qt5LinguistToolsMacros.cmake index 6a45e57be..0ccf59f3c 100644 --- a/qttools/src/linguist/Qt5LinguistToolsMacros.cmake +++ b/qttools/src/linguist/Qt5LinguistToolsMacros.cmake @@ -68,6 +68,7 @@ function(QT5_CREATE_TRANSLATION _qm_files) if(NOT EXISTS "${stamp_file_dir}") file(MAKE_DIRECTORY "${stamp_file_dir}") endif() + set(stamp_files "") foreach(_ts_file ${_my_tsfiles}) get_filename_component(_ts_name ${_ts_file} NAME) if(_my_sources) @@ -95,7 +96,14 @@ function(QT5_CREATE_TRANSLATION _qm_files) file(WRITE ${_ts_lst_file} "${_lst_file_srcs}") endif() - set(stamp_file "${stamp_file_dir}/${_ts_name}.stamp") + file(RELATIVE_PATH _ts_relative_path ${CMAKE_CURRENT_SOURCE_DIR} ${_ts_file}) + string(REPLACE "../" "__/" _ts_relative_path "${_ts_relative_path}") + set(stamp_file "${stamp_file_dir}/${_ts_relative_path}.stamp") + list(APPEND stamp_files ${stamp_file}) + get_filename_component(full_stamp_file_dir "${stamp_file}" DIRECTORY) + if(NOT EXISTS "${full_stamp_file_dir}") + file(MAKE_DIRECTORY "${full_stamp_file_dir}") + endif() add_custom_command(OUTPUT ${stamp_file} COMMAND ${Qt5_LUPDATE_EXECUTABLE} ARGS ${_lupdate_options} "@${_ts_lst_file}" -ts ${_ts_file} @@ -103,7 +111,7 @@ function(QT5_CREATE_TRANSLATION _qm_files) DEPENDS ${_dependencies} VERBATIM) endforeach() - qt5_add_translation(${_qm_files} __QT_INTERNAL_DEPEND_ON_TIMESTAMP_FILE ${_my_tsfiles}) + qt5_add_translation(${_qm_files} ${_my_tsfiles} __QT_INTERNAL_TIMESTAMP_FILES ${stamp_files}) set(${_qm_files} ${${_qm_files}} PARENT_SCOPE) endfunction() @@ -120,17 +128,17 @@ endif() function(QT5_ADD_TRANSLATION _qm_files) - set(options __QT_INTERNAL_DEPEND_ON_TIMESTAMP_FILE) + set(options) set(oneValueArgs) - set(multiValueArgs OPTIONS) + set(multiValueArgs OPTIONS __QT_INTERNAL_TIMESTAMP_FILES) cmake_parse_arguments(_LRELEASE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_lrelease_files ${_LRELEASE_UNPARSED_ARGUMENTS}) + set(idx 0) foreach(_current_FILE ${_lrelease_files}) get_filename_component(_abs_FILE ${_current_FILE} ABSOLUTE) get_filename_component(qm ${_abs_FILE} NAME) - set(ts_stamp_file "${CMAKE_CURRENT_BINARY_DIR}/.lupdate/${qm}.stamp") # everything before the last dot has to be considered the file name (including other dots) string(REGEX REPLACE "\\.[^.]*$" "" FILE_NAME ${qm}) get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION) @@ -141,7 +149,9 @@ function(QT5_ADD_TRANSLATION _qm_files) set(qm "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.qm") endif() - if(_LRELEASE___QT_INTERNAL_DEPEND_ON_TIMESTAMP_FILE) + if(_LRELEASE___QT_INTERNAL_TIMESTAMP_FILES) + list(GET _LRELEASE___QT_INTERNAL_TIMESTAMP_FILES ${idx} ts_stamp_file) + math(EXPR idx "${idx} + 1") set(qm_dep "${ts_stamp_file}") else() set(qm_dep "${_abs_FILE}") Submodule qtwayland b8f1882d..4de268cb: diff --git a/qtwayland/src/client/configure.json b/qtwayland/src/client/configure.json index 2f424580..29222357 100644 --- a/qtwayland/src/client/configure.json +++ b/qtwayland/src/client/configure.json @@ -149,8 +149,7 @@ "#endif" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "vulkan-server-buffer": { "label": "Vulkan Buffer Sharing", @@ -168,7 +167,8 @@ "exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;", "return 0;" ] - } + }, + "use": "wayland-client" }, "egl_1_5-wayland": { "label": "EGL 1.5 with Wayland Platform", @@ -183,7 +183,7 @@ "eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_EXT, (struct wl_display *)(nullptr), nullptr);" ] }, - "use": "egl" + "use": "egl wayland-client" } }, diff --git a/qtwayland/src/client/global/qwaylandclientextension.cpp b/qtwayland/src/client/global/qwaylandclientextension.cpp index 966096a8..36609c08 100644 --- a/qtwayland/src/client/global/qwaylandclientextension.cpp +++ b/qtwayland/src/client/global/qwaylandclientextension.cpp @@ -74,7 +74,10 @@ void QWaylandClientExtensionPrivate::handleRegistryGlobal(void *data, ::wl_regis void QWaylandClientExtension::addRegistryListener() { Q_D(QWaylandClientExtension); - d->waylandIntegration->display()->addRegistryListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); + if (!d->registered) { + d->waylandIntegration->display()->addRegistryListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); + d->registered = true; + } } QWaylandClientExtension::QWaylandClientExtension(const int ver) @@ -88,6 +91,13 @@ QWaylandClientExtension::QWaylandClientExtension(const int ver) QMetaObject::invokeMethod(this, "addRegistryListener", Qt::QueuedConnection); } +QWaylandClientExtension::~QWaylandClientExtension() +{ + Q_D(QWaylandClientExtension); + if (d->registered && !QCoreApplication::closingDown()) + d->waylandIntegration->display()->removeListener(&QWaylandClientExtensionPrivate::handleRegistryGlobal, this); +} + QtWaylandClient::QWaylandIntegration *QWaylandClientExtension::integration() const { Q_D(const QWaylandClientExtension); diff --git a/qtwayland/src/client/global/qwaylandclientextension.h b/qtwayland/src/client/global/qwaylandclientextension.h index 98272e57..5bd28398 100644 --- a/qtwayland/src/client/global/qwaylandclientextension.h +++ b/qtwayland/src/client/global/qwaylandclientextension.h @@ -63,6 +63,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandClientExtension : public QObject Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) public: QWaylandClientExtension(const int version); + ~QWaylandClientExtension(); QtWaylandClient::QWaylandIntegration *integration() const; int version() const; diff --git a/qtwayland/src/client/global/qwaylandclientextension_p.h b/qtwayland/src/client/global/qwaylandclientextension_p.h index 69cc46a0..9091efbe 100644 --- a/qtwayland/src/client/global/qwaylandclientextension_p.h +++ b/qtwayland/src/client/global/qwaylandclientextension_p.h @@ -68,6 +68,7 @@ public: QtWaylandClient::QWaylandIntegration *waylandIntegration = nullptr; int version = -1; bool active = false; + bool registered = false; }; class Q_WAYLAND_CLIENT_EXPORT QWaylandClientExtensionTemplatePrivate : public QWaylandClientExtensionPrivate diff --git a/qtwayland/src/client/qwaylandabstractdecoration.cpp b/qtwayland/src/client/qwaylandabstractdecoration.cpp index b628930d..d15a7f9f 100644 --- a/qtwayland/src/client/qwaylandabstractdecoration.cpp +++ b/qtwayland/src/client/qwaylandabstractdecoration.cpp @@ -122,7 +122,7 @@ const QImage &QWaylandAbstractDecoration::contentImage() if (d->m_isDirty) { // Update the decoration backingstore - const int bufferScale = waylandWindow()->scale(); + const qreal bufferScale = waylandWindow()->scale(); const QSize imageSize = waylandWindow()->surfaceSize() * bufferScale; d->m_decorationContentImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); // Only scale by buffer scale, not QT_SCALE_FACTOR etc. diff --git a/qtwayland/src/client/qwaylandclipboard.cpp b/qtwayland/src/client/qwaylandclipboard.cpp index 81f48e05..14561c77 100644 --- a/qtwayland/src/client/qwaylandclipboard.cpp +++ b/qtwayland/src/client/qwaylandclipboard.cpp @@ -54,10 +54,15 @@ namespace QtWaylandClient { QWaylandClipboard::QWaylandClipboard(QWaylandDisplay *display) : mDisplay(display) { + m_clientClipboard[QClipboard::Clipboard] = nullptr; + m_clientClipboard[QClipboard::Selection] = nullptr; } QWaylandClipboard::~QWaylandClipboard() { + if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection]) + delete m_clientClipboard[QClipboard::Clipboard]; + delete m_clientClipboard[QClipboard::Selection]; } QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) @@ -69,8 +74,8 @@ QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) switch (mode) { case QClipboard::Clipboard: if (auto *dataDevice = seat->dataDevice()) { - if (auto *source = dataDevice->selectionSource()) - return source->mimeData(); + if (dataDevice->selectionSource()) + return m_clientClipboard[QClipboard::Clipboard]; if (auto *offer = dataDevice->selectionOffer()) return offer->mimeData(); } @@ -78,8 +83,8 @@ QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode) case QClipboard::Selection: #if QT_CONFIG(wayland_client_primary_selection) if (auto *selectionDevice = seat->primarySelectionDevice()) { - if (auto *source = selectionDevice->selectionSource()) - return source->mimeData(); + if (selectionDevice->selectionSource()) + return m_clientClipboard[QClipboard::Selection]; if (auto *offer = selectionDevice->selectionOffer()) return offer->mimeData(); } @@ -104,17 +109,27 @@ void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) if (data && data->hasFormat(plain) && !data->hasFormat(utf8)) data->setData(utf8, data->data(plain)); + if (m_clientClipboard[mode]) { + if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection]) + delete m_clientClipboard[mode]; + m_clientClipboard[mode] = nullptr; + } + + m_clientClipboard[mode] = data; + switch (mode) { case QClipboard::Clipboard: if (auto *dataDevice = seat->dataDevice()) { - dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr); + dataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), + m_clientClipboard[QClipboard::Clipboard]) : nullptr); emitChanged(mode); } break; case QClipboard::Selection: #if QT_CONFIG(wayland_client_primary_selection) if (auto *selectionDevice = seat->primarySelectionDevice()) { - selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), data) : nullptr); + selectionDevice->setSelectionSource(data ? new QWaylandPrimarySelectionSourceV1(mDisplay->primarySelectionManager(), + m_clientClipboard[QClipboard::Selection]) : nullptr); emitChanged(mode); } #endif diff --git a/qtwayland/src/client/qwaylandclipboard_p.h b/qtwayland/src/client/qwaylandclipboard_p.h index ce14e124..bb52683d 100644 --- a/qtwayland/src/client/qwaylandclipboard_p.h +++ b/qtwayland/src/client/qwaylandclipboard_p.h @@ -80,6 +80,7 @@ public: private: QWaylandDisplay *mDisplay = nullptr; QMimeData m_emptyData; + QMimeData *m_clientClipboard[2]; }; } diff --git a/qtwayland/src/client/qwaylandcursor.cpp b/qtwayland/src/client/qwaylandcursor.cpp index e4eca9d4..ba76ba2d 100644 --- a/qtwayland/src/client/qwaylandcursor.cpp +++ b/qtwayland/src/client/qwaylandcursor.cpp @@ -44,6 +44,7 @@ #include "qwaylandshmbackingstore_p.h" #include +#include #include #include @@ -250,7 +251,27 @@ QWaylandCursor::QWaylandCursor(QWaylandDisplay *display) QSharedPointer QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor) { Q_ASSERT(cursor->shape() == Qt::BitmapCursor); - const QImage &img = cursor->pixmap().toImage(); + + const QBitmap mask = cursor->mask(Qt::ReturnByValue); + QImage img; + if (cursor->pixmap().isNull()) + img = cursor->bitmap(Qt::ReturnByValue).toImage(); + else + img = cursor->pixmap().toImage(); + + // convert to supported format if necessary + if (!display->shm()->formatSupported(img.format())) { + if (mask.isNull()) { + img.convertTo(QImage::Format_RGB32); + } else { + // preserve mask + img.convertTo(QImage::Format_ARGB32); + QPixmap pixmap = QPixmap::fromImage(img); + pixmap.setMask(mask); + img = pixmap.toImage(); + } + } + QSharedPointer buffer(new QWaylandShmBuffer(display, img.size(), img.format())); memcpy(buffer->image()->bits(), img.bits(), size_t(img.sizeInBytes())); return buffer; diff --git a/qtwayland/src/client/qwaylanddatadevice.cpp b/qtwayland/src/client/qwaylanddatadevice.cpp index 1e2db786..9c3308d5 100644 --- a/qtwayland/src/client/qwaylanddatadevice.cpp +++ b/qtwayland/src/client/qwaylanddatadevice.cpp @@ -72,6 +72,8 @@ QWaylandDataDevice::QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWayl QWaylandDataDevice::~QWaylandDataDevice() { + if (wl_data_device_get_version(object()) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) + release(); } QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const @@ -110,7 +112,7 @@ QWaylandDataOffer *QWaylandDataDevice::dragOffer() const return m_dragOffer.data(); } -bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) +bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon) { auto *seat = m_display->currentInputDevice(); auto *origin = seat->pointerFocus(); @@ -123,7 +125,31 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) } m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); + + if (wl_data_device_get_version(object()) >= 3) + m_dragSource->set_actions(dropActionsToWl(supportedActions)); + connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); + connect(m_dragSource.data(), &QWaylandDataSource::dndResponseUpdated, this, [this](bool accepted, Qt::DropAction action) { + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + if (!drag->currentDrag()) { + return; + } + // in old versions drop action is not set, so we guess + if (wl_data_source_get_version(m_dragSource->object()) < 3) { + drag->setResponse(accepted); + } else { + QPlatformDropQtResponse response(accepted, action); + drag->setResponse(response); + } + }); + connect(m_dragSource.data(), &QWaylandDataSource::dndDropped, this, [](bool accepted, Qt::DropAction action) { + QPlatformDropQtResponse response(accepted, action); + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setDropResponse(response); + }); + connect(m_dragSource.data(), &QWaylandDataSource::finished, this, []() { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); + }); start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial()); return true; @@ -152,7 +178,7 @@ void QWaylandDataDevice::data_device_drop() supportedActions = drag->supportedActions(); } else if (m_dragOffer) { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } else { return; } @@ -162,7 +188,11 @@ void QWaylandDataDevice::data_device_drop() QGuiApplication::keyboardModifiers()); if (drag) { - static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(response); + auto drag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); + drag->setDropResponse(response); + drag->finishDrag(); + } else if (m_dragOffer) { + m_dragOffer->finish(); } } @@ -186,7 +216,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, supportedActions = drag->supportedActions(); } else if (m_dragOffer) { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, @@ -197,11 +227,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); } - if (response.isAccepted()) { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData()); - } else { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr); - } + sendResponse(supportedActions, response); } void QWaylandDataDevice::data_device_leave() @@ -235,10 +261,10 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe supportedActions = drag->supportedActions(); } else { dragData = m_dragOffer->mimeData(); - supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; + supportedActions = m_dragOffer->supportedActions(); } - QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, + const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers()); @@ -246,11 +272,7 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); } - if (response.isAccepted()) { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData()); - } else { - wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr); - } + sendResponse(supportedActions, response); } #endif // QT_CONFIG(draganddrop) @@ -277,14 +299,10 @@ void QWaylandDataDevice::selectionSourceCancelled() #if QT_CONFIG(draganddrop) void QWaylandDataDevice::dragSourceCancelled() { + static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(); m_dragSource.reset(); } -void QWaylandDataDevice::dragSourceTargetChanged(const QString &mimeType) -{ - static_cast(QGuiApplicationPrivate::platformIntegration()->drag())->updateTarget(mimeType); -} - QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const { QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y)); @@ -297,6 +315,33 @@ QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) con } return pnt; } + +void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response) +{ + if (response.isAccepted()) { + if (wl_data_device_get_version(object()) >= 3) + m_dragOffer->set_actions(dropActionsToWl(supportedActions), dropActionsToWl(response.acceptedAction())); + + m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat()); + } else { + m_dragOffer->accept(m_enterSerial, QString()); + } +} + +int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions) +{ + + int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (actions & Qt::CopyAction) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (actions & (Qt::MoveAction | Qt::TargetMoveAction)) + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + // wayland does not support LinkAction at the time of writing + return wlActions; +} + + #endif // QT_CONFIG(draganddrop) } diff --git a/qtwayland/src/client/qwaylanddatadevice_p.h b/qtwayland/src/client/qwaylanddatadevice_p.h index 16c3ad28..801dcc2c 100644 --- a/qtwayland/src/client/qwaylanddatadevice_p.h +++ b/qtwayland/src/client/qwaylanddatadevice_p.h @@ -64,6 +64,7 @@ QT_REQUIRE_CONFIG(wayland_datadevice); QT_BEGIN_NAMESPACE class QMimeData; +class QPlatformDragQtResponse; class QWindow; namespace QtWaylandClient { @@ -89,7 +90,7 @@ public: #if QT_CONFIG(draganddrop) QWaylandDataOffer *dragOffer() const; - bool startDrag(QMimeData *mimeData, QWaylandWindow *icon); + bool startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon); void cancelDrag(); #endif @@ -109,13 +110,16 @@ private Q_SLOTS: #if QT_CONFIG(draganddrop) void dragSourceCancelled(); - void dragSourceTargetChanged(const QString &mimeType); #endif private: #if QT_CONFIG(draganddrop) QPoint calculateDragPosition(int x, int y, QWindow *wnd) const; #endif + void sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response); + + static int dropActionsToWl(Qt::DropActions dropActions); + QWaylandDisplay *m_display = nullptr; QWaylandInputDevice *m_inputDevice = nullptr; diff --git a/qtwayland/src/client/qwaylanddatadevicemanager.cpp b/qtwayland/src/client/qwaylanddatadevicemanager.cpp index 35d67307..6dc4f77f 100644 --- a/qtwayland/src/client/qwaylanddatadevicemanager.cpp +++ b/qtwayland/src/client/qwaylanddatadevicemanager.cpp @@ -50,8 +50,8 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { -QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id) - : wl_data_device_manager(display->wl_registry(), id, 1) +QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id) + : wl_data_device_manager(display->wl_registry(), id, qMin(version, 3)) , m_display(display) { // Create transfer devices for all input devices. diff --git a/qtwayland/src/client/qwaylanddatadevicemanager_p.h b/qtwayland/src/client/qwaylanddatadevicemanager_p.h index bd05c0fb..510d9be4 100644 --- a/qtwayland/src/client/qwaylanddatadevicemanager_p.h +++ b/qtwayland/src/client/qwaylanddatadevicemanager_p.h @@ -68,7 +68,7 @@ class QWaylandInputDevice; class Q_WAYLAND_CLIENT_EXPORT QWaylandDataDeviceManager : public QtWayland::wl_data_device_manager { public: - QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id); + QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id); ~QWaylandDataDeviceManager() override; QWaylandDataDevice *getDataDevice(QWaylandInputDevice *inputDevice); diff --git a/qtwayland/src/client/qwaylanddataoffer.cpp b/qtwayland/src/client/qwaylanddataoffer.cpp index 2297e8a1..fe0ea8c9 100644 --- a/qtwayland/src/client/qwaylanddataoffer.cpp +++ b/qtwayland/src/client/qwaylanddataoffer.cpp @@ -82,6 +82,15 @@ QMimeData *QWaylandDataOffer::mimeData() return m_mimeData.data(); } +Qt::DropActions QWaylandDataOffer::supportedActions() const +{ + if (wl_data_offer_get_version(const_cast<::wl_data_offer*>(object())) < 3) { + return Qt::MoveAction | Qt::CopyAction; + } + + return m_supportedActions; +} + void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) { receive(mimeType, fd); @@ -93,6 +102,22 @@ void QWaylandDataOffer::data_offer_offer(const QString &mime_type) m_mimeData->appendFormat(mime_type); } +void QWaylandDataOffer::data_offer_action(uint32_t dnd_action) +{ + Q_UNUSED(dnd_action); + // This is the compositor telling the drag target what action it should perform + // It does not map nicely into Qt final drop semantics, other than pretending there is only one supported action? +} + +void QWaylandDataOffer::data_offer_source_actions(uint32_t source_actions) +{ + m_supportedActions = Qt::DropActions(); + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + m_supportedActions |= Qt::MoveAction; + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + m_supportedActions |= Qt::CopyAction; +} + QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) : m_dataOffer(dataOffer) { @@ -163,17 +188,18 @@ QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::T int QWaylandMimeData::readData(int fd, QByteArray &data) const { - fd_set readset; - FD_ZERO(&readset); - FD_SET(fd, &readset); - struct timeval timeout; + struct pollfd readset; + readset.fd = fd; + readset.events = POLLIN; + struct timespec timeout; timeout.tv_sec = 1; - timeout.tv_usec = 0; + timeout.tv_nsec = 0; + Q_FOREVER { - int ready = select(FD_SETSIZE, &readset, nullptr, nullptr, &timeout); + int ready = qt_safe_poll(&readset, 1, &timeout); if (ready < 0) { - qWarning() << "QWaylandDataOffer: select() failed"; + qWarning() << "QWaylandDataOffer: qt_safe_poll() failed"; return -1; } else if (ready == 0) { qWarning("QWaylandDataOffer: timeout reading from pipe"); diff --git a/qtwayland/src/client/qwaylanddataoffer_p.h b/qtwayland/src/client/qwaylanddataoffer_p.h index 9cf1483c..6f667398 100644 --- a/qtwayland/src/client/qwaylanddataoffer_p.h +++ b/qtwayland/src/client/qwaylanddataoffer_p.h @@ -82,6 +82,7 @@ public: explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); ~QWaylandDataOffer() override; QMimeData *mimeData() override; + Qt::DropActions supportedActions() const; QString firstFormat() const; @@ -89,10 +90,13 @@ public: protected: void data_offer_offer(const QString &mime_type) override; + void data_offer_source_actions(uint32_t source_actions) override; + void data_offer_action(uint32_t dnd_action) override; private: QWaylandDisplay *m_display = nullptr; QScopedPointer m_mimeData; + Qt::DropActions m_supportedActions; }; diff --git a/qtwayland/src/client/qwaylanddatasource.cpp b/qtwayland/src/client/qwaylanddatasource.cpp index 9b17cdfa..1a6b9835 100644 --- a/qtwayland/src/client/qwaylanddatasource.cpp +++ b/qtwayland/src/client/qwaylanddatasource.cpp @@ -71,11 +71,6 @@ QWaylandDataSource::~QWaylandDataSource() destroy(); } -QMimeData * QWaylandDataSource::mimeData() const -{ - return m_mime_data; -} - void QWaylandDataSource::data_source_cancelled() { Q_EMIT cancelled(); @@ -101,7 +96,32 @@ void QWaylandDataSource::data_source_send(const QString &mime_type, int32_t fd) void QWaylandDataSource::data_source_target(const QString &mime_type) { - Q_EMIT targetChanged(mime_type); + m_accepted = !mime_type.isEmpty(); + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_action(uint32_t action) +{ + Qt::DropAction qtAction = Qt::IgnoreAction; + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + qtAction = Qt::MoveAction; + else if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + qtAction = Qt::CopyAction; + + m_dropAction = qtAction; + Q_EMIT dndResponseUpdated(m_accepted, m_dropAction); +} + +void QWaylandDataSource::data_source_dnd_finished() +{ + Q_EMIT finished(); +} + +void QWaylandDataSource::data_source_dnd_drop_performed() +{ + + Q_EMIT dndDropped(m_accepted, m_dropAction); } } diff --git a/qtwayland/src/client/qwaylanddatasource_p.h b/qtwayland/src/client/qwaylanddatasource_p.h index 3003da1b..089c5485 100644 --- a/qtwayland/src/client/qwaylanddatasource_p.h +++ b/qtwayland/src/client/qwaylanddatasource_p.h @@ -74,19 +74,25 @@ public: QWaylandDataSource(QWaylandDataDeviceManager *dataDeviceManager, QMimeData *mimeData); ~QWaylandDataSource() override; - QMimeData *mimeData() const; - Q_SIGNALS: - void targetChanged(const QString &mime_type); void cancelled(); + void finished(); + + void dndResponseUpdated(bool accepted, Qt::DropAction action); + void dndDropped(bool accepted, Qt::DropAction action); protected: void data_source_cancelled() override; void data_source_send(const QString &mime_type, int32_t fd) override; void data_source_target(const QString &mime_type) override; + void data_source_dnd_drop_performed() override; + void data_source_dnd_finished() override; + void data_source_action(uint32_t action) override; private: QMimeData *m_mime_data = nullptr; + bool m_accepted = false; + Qt::DropAction m_dropAction = Qt::IgnoreAction; }; } diff --git a/qtwayland/src/client/qwaylanddisplay.cpp b/qtwayland/src/client/qwaylanddisplay.cpp index 8a6d5db1..737b539d 100644 --- a/qtwayland/src/client/qwaylanddisplay.cpp +++ b/qtwayland/src/client/qwaylanddisplay.cpp @@ -87,10 +87,203 @@ #include +#include // for std::tie + +static void checkWaylandError(struct wl_display *display) +{ + int ecode = wl_display_get_error(display); + if ((ecode == EPIPE || ecode == ECONNRESET)) { + // special case this to provide a nicer error + qWarning("The Wayland connection broke. Did the Wayland compositor die?"); + } else { + qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); + } + _exit(1); +} + QT_BEGIN_NAMESPACE namespace QtWaylandClient { +class EventThread : public QThread +{ + Q_OBJECT +public: + enum OperatingMode { + EmitToDispatch, // Emit the signal, allow dispatching in a differnt thread. + SelfDispatch, // Dispatch the events inside this thread. + }; + + EventThread(struct wl_display * wl, struct wl_event_queue * ev_queue, + OperatingMode mode) + : m_fd(wl_display_get_fd(wl)) + , m_pipefd{ -1, -1 } + , m_wldisplay(wl) + , m_wlevqueue(ev_queue) + , m_mode(mode) + , m_reading(true) + , m_quitting(false) + { + setObjectName(QStringLiteral("WaylandEventThread")); + } + + void readAndDispatchEvents() + { + /* + * Dispatch pending events and flush the requests at least once. If the event thread + * is not reading, try to call _prepare_read() to allow the event thread to poll(). + * If that fails, re-try dispatch & flush again until _prepare_read() is successful. + * + * This allow any call to readAndDispatchEvents() to start event thread's polling, + * not only the one issued from event thread's waitForReading(), which means functions + * called from dispatch_pending() can safely spin an event loop. + */ + for (;;) { + if (dispatchQueuePending() < 0) { + checkWaylandError(m_wldisplay); + return; + } + + wl_display_flush(m_wldisplay); + + // We have to check if event thread is reading every time we dispatch + // something, as that may recursively call this function. + if (m_reading.loadAcquire()) + break; + + if (prepareReadQueue() == 0) { + QMutexLocker l(&m_mutex); + m_reading.storeRelease(true); + m_cond.wakeOne(); + break; + } + } + } + + void stop() + { + // We have to both write to the pipe and set the flag, as the thread may be + // either in the poll() or waiting for _prepare_read(). + if (m_pipefd[1] != -1 && write(m_pipefd[1], "\0", 1) == -1) + qWarning("Failed to write to the pipe: %s.", strerror(errno)); + + { + QMutexLocker l(&m_mutex); + m_quitting = true; + m_cond.wakeOne(); + } + + wait(); + } + +Q_SIGNALS: + void needReadAndDispatch(); + +protected: + void run() override + { + // we use this pipe to make the loop exit otherwise if we simply used a flag on the loop condition, if stop() gets + // called while poll() is blocking the thread will never quit since there are no wayland messages coming anymore. + struct Pipe + { + Pipe(int *fds) + : fds(fds) + { + if (qt_safe_pipe(fds) != 0) + qWarning("Pipe creation failed. Quitting may hang."); + } + ~Pipe() + { + if (fds[0] != -1) { + close(fds[0]); + close(fds[1]); + } + } + + int *fds; + } pipe(m_pipefd); + + // Make the main thread call wl_prepare_read(), dispatch the pending messages and flush the + // outbound ones. Wait until it's done before proceeding, unless we're told to quit. + while (waitForReading()) { + pollfd fds[2] = { { m_fd, POLLIN, 0 }, { m_pipefd[0], POLLIN, 0 } }; + poll(fds, 2, -1); + + if (fds[1].revents & POLLIN) { + // we don't really care to read the byte that was written here since we're closing down + wl_display_cancel_read(m_wldisplay); + break; + } + + if (fds[0].revents & POLLIN) + wl_display_read_events(m_wldisplay); + // The polll was succesfull and the event thread did the wl_display_read_events(). On the next iteration of the loop + // the event sent to the main thread will cause it to dispatch the messages just read, unless the loop exits in which + // case we don't care anymore about them. + else + wl_display_cancel_read(m_wldisplay); + } + } + +private: + bool waitForReading() + { + Q_ASSERT(QThread::currentThread() == this); + + m_reading.storeRelease(false); + + if (m_mode == SelfDispatch) { + readAndDispatchEvents(); + } else { + Q_EMIT needReadAndDispatch(); + + QMutexLocker lock(&m_mutex); + // m_reading might be set from our emit or some other invocation of + // readAndDispatchEvents(). + while (!m_reading.loadRelaxed() && !m_quitting) + m_cond.wait(&m_mutex); + } + + return !m_quitting; + } + + int dispatchQueuePending() + { + if (m_wlevqueue) + return wl_display_dispatch_queue_pending(m_wldisplay, m_wlevqueue); + else + return wl_display_dispatch_pending(m_wldisplay); + } + + int prepareReadQueue() + { + if (m_wlevqueue) + return wl_display_prepare_read_queue(m_wldisplay, m_wlevqueue); + else + return wl_display_prepare_read(m_wldisplay); + } + + int m_fd; + int m_pipefd[2]; + wl_display *m_wldisplay; + wl_event_queue *m_wlevqueue; + OperatingMode m_mode; + + /* Concurrency note when operating in EmitToDispatch mode: + * m_reading is set to false inside event thread's waitForReading(), and is + * set to true inside main thread's readAndDispatchEvents(). + * The lock is not taken when setting m_reading to false, as the main thread + * is not actively waiting for it to turn false. However, the lock is taken + * inside readAndDispatchEvents() before setting m_reading to true, + * as the event thread is actively waiting for it under the wait condition. + */ + + QAtomicInteger m_reading; + bool m_quitting; + QMutex m_mutex; + QWaitCondition m_cond; +}; + Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland"); // for general (uncategorized) Wayland platform logging struct wl_surface *QWaylandDisplay::createSurface(void *handle) @@ -160,17 +353,16 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) if (!mXkbContext) qCWarning(lcQpaWayland, "failed to create xkb context"); #endif - - forceRoundTrip(); - - if (!mWaitingScreens.isEmpty()) { - // Give wl_output.done and zxdg_output_v1.done events a chance to arrive - forceRoundTrip(); - } } QWaylandDisplay::~QWaylandDisplay(void) { + if (m_eventThread) + m_eventThread->stop(); + + if (m_frameEventQueueThread) + m_frameEventQueueThread->stop(); + if (mSyncCallback) wl_callback_destroy(mSyncCallback); @@ -187,10 +379,26 @@ QWaylandDisplay::~QWaylandDisplay(void) #if QT_CONFIG(cursor) qDeleteAll(mCursorThemes); #endif + + if (m_frameEventQueue) + wl_event_queue_destroy(m_frameEventQueue); + if (mDisplay) wl_display_disconnect(mDisplay); } +// Steps which is called just after constructor. This separates registry_global() out of the constructor +// so that factory functions in integration can be overridden. +void QWaylandDisplay::initialize() +{ + forceRoundTrip(); + + if (!mWaitingScreens.isEmpty()) { + // Give wl_output.done and zxdg_output_v1.done events a chance to arrive + forceRoundTrip(); + } +} + void QWaylandDisplay::ensureScreen() { if (!mScreens.empty() || mPlaceholderScreen) @@ -205,98 +413,37 @@ void QWaylandDisplay::ensureScreen() void QWaylandDisplay::checkError() const { - int ecode = wl_display_get_error(mDisplay); - if ((ecode == EPIPE || ecode == ECONNRESET)) { - // special case this to provide a nicer error - qWarning("The Wayland connection broke. Did the Wayland compositor die?"); - } else { - qWarning("The Wayland connection experienced a fatal error: %s", strerror(ecode)); - } - _exit(1); + checkWaylandError(mDisplay); } +// Called in main thread, either from queued signal or directly. void QWaylandDisplay::flushRequests() { - if (wl_display_prepare_read(mDisplay) == 0) { - wl_display_read_events(mDisplay); - } - - if (wl_display_dispatch_pending(mDisplay) < 0) - checkError(); - - { - QReadLocker locker(&m_frameQueueLock); - for (const FrameQueue &q : mExternalQueues) { - QMutexLocker locker(q.mutex); - while (wl_display_prepare_read_queue(mDisplay, q.queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, q.queue); - wl_display_read_events(mDisplay); - wl_display_dispatch_queue_pending(mDisplay, q.queue); - } - } - - wl_display_flush(mDisplay); -} - -void QWaylandDisplay::blockingReadEvents() -{ - if (wl_display_dispatch(mDisplay) < 0) - checkError(); -} - -void QWaylandDisplay::destroyFrameQueue(const QWaylandDisplay::FrameQueue &q) -{ - QWriteLocker locker(&m_frameQueueLock); - auto it = std::find_if(mExternalQueues.begin(), - mExternalQueues.end(), - [&q] (const QWaylandDisplay::FrameQueue &other){ return other.queue == q.queue; }); - Q_ASSERT(it != mExternalQueues.end()); - mExternalQueues.erase(it); - if (q.queue != nullptr) - wl_event_queue_destroy(q.queue); - delete q.mutex; + m_eventThread->readAndDispatchEvents(); } -QWaylandDisplay::FrameQueue QWaylandDisplay::createFrameQueue() +// We have to wait until we have an eventDispatcher before creating the eventThread, +// otherwise forceRoundTrip() may block inside _events_read() because eventThread is +// polling. +void QWaylandDisplay::initEventThread() { - QWriteLocker locker(&m_frameQueueLock); - FrameQueue q{createEventQueue()}; - mExternalQueues.append(q); - return q; -} + m_eventThread.reset( + new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); + connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, + &QWaylandDisplay::flushRequests, Qt::QueuedConnection); + m_eventThread->start(); -wl_event_queue *QWaylandDisplay::createEventQueue() -{ - return wl_display_create_queue(mDisplay); + // wl_display_disconnect() free this. + m_frameEventQueue = wl_display_create_queue(mDisplay); + m_frameEventQueueThread.reset( + new EventThread(mDisplay, m_frameEventQueue, EventThread::SelfDispatch)); + m_frameEventQueueThread->start(); } -void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout) +void QWaylandDisplay::blockingReadEvents() { - if (!condition()) - return; - - QElapsedTimer timer; - timer.start(); - struct pollfd pFd = qt_make_pollfd(wl_display_get_fd(mDisplay), POLLIN); - while (timeout == -1 || timer.elapsed() < timeout) { - while (wl_display_prepare_read_queue(mDisplay, queue) != 0) - wl_display_dispatch_queue_pending(mDisplay, queue); - - wl_display_flush(mDisplay); - - const int remaining = qMax(timeout - timer.elapsed(), 0ll); - const int pollTimeout = timeout == -1 ? -1 : remaining; - if (qt_poll_msecs(&pFd, 1, pollTimeout) > 0) - wl_display_read_events(mDisplay); - else - wl_display_cancel_read(mDisplay); - - if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0) - checkError(); - - if (!condition()) - break; - } + if (wl_display_dispatch(mDisplay) < 0) + checkWaylandError(mDisplay); } QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const @@ -347,7 +494,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin if (interface == QStringLiteral("wl_output")) { mWaitingScreens << new QWaylandScreen(this, version, id); } else if (interface == QStringLiteral("wl_compositor")) { - mCompositorVersion = qMin((int)version, 3); + mCompositorVersion = qMin((int)version, 4); mCompositor.init(registry, id, mCompositorVersion); } else if (interface == QStringLiteral("wl_shm")) { mShm.reset(new QWaylandShm(this, version, id)); @@ -356,7 +503,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mInputDevices.append(inputDevice); #if QT_CONFIG(wayland_datadevice) } else if (interface == QStringLiteral("wl_data_device_manager")) { - mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, id)); + mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, version, id)); #endif } else if (interface == QStringLiteral("qt_surface_extension")) { mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1)); @@ -373,6 +520,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin #if QT_CONFIG(wayland_client_primary_selection) } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setPrimarySelectionDevice(mPrimarySelectionManager->createDevice(inputDevice)); #endif } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); @@ -431,6 +580,13 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } +#if QT_CONFIG(wayland_client_primary_selection) + if (global.interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { + mPrimarySelectionManager.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setPrimarySelectionDevice(nullptr); + } +#endif mGlobals.removeAt(i); break; } @@ -456,9 +612,10 @@ void QWaylandDisplay::addRegistryListener(RegistryListener listener, void *data) void QWaylandDisplay::removeListener(RegistryListener listener, void *data) { - std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ + auto iter = std::remove_if(mRegistryListeners.begin(), mRegistryListeners.end(), [=](Listener l){ return (l.listener == listener && l.data == data); }); + mRegistryListeners.erase(iter, mRegistryListeners.end()); } uint32_t QWaylandDisplay::currentTimeMillisec() @@ -471,50 +628,9 @@ uint32_t QWaylandDisplay::currentTimeMillisec() return 0; } -static void -sync_callback(void *data, struct wl_callback *callback, uint32_t serial) -{ - Q_UNUSED(serial) - bool *done = static_cast(data); - - *done = true; - - // If the wl_callback done event is received after the condition check in the while loop in - // forceRoundTrip(), but before the call to processEvents, the call to processEvents may block - // forever if no more events are posted (eventhough the callback is handled in response to the - // aboutToBlock signal). Hence, we wake up the event dispatcher so forceRoundTrip may return. - // (QTBUG-64696) - if (auto *dispatcher = QThread::currentThread()->eventDispatcher()) - dispatcher->wakeUp(); - - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener sync_listener = { - sync_callback -}; - void QWaylandDisplay::forceRoundTrip() { - // wl_display_roundtrip() works on the main queue only, - // but we use a separate one, so basically reimplement it here - int ret = 0; - bool done = false; - wl_callback *callback = wl_display_sync(mDisplay); - wl_callback_add_listener(callback, &sync_listener, &done); - flushRequests(); - if (QThread::currentThread()->eventDispatcher()) { - while (!done && ret >= 0) { - QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::WaitForMoreEvents); - ret = wl_display_dispatch_pending(mDisplay); - } - } else { - while (!done && ret >= 0) - ret = wl_display_dispatch(mDisplay); - } - - if (ret == -1 && !done) - wl_callback_destroy(callback); + wl_display_roundtrip(mDisplay); } bool QWaylandDisplay::supportsWindowDecoration() const @@ -578,14 +694,10 @@ void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevic if (mLastKeyboardFocus == keyboardFocus) return; - if (mWaylandIntegration->mShellIntegration) { - mWaylandIntegration->mShellIntegration->handleKeyboardFocusChanged(keyboardFocus, mLastKeyboardFocus); - } else { - if (keyboardFocus) - handleWindowActivated(keyboardFocus); - if (mLastKeyboardFocus) - handleWindowDeactivated(mLastKeyboardFocus); - } + if (keyboardFocus) + handleWindowActivated(keyboardFocus); + if (mLastKeyboardFocus) + handleWindowDeactivated(mLastKeyboardFocus); mLastKeyboardFocus = keyboardFocus; } @@ -604,6 +716,19 @@ void QWaylandDisplay::handleWaylandSync() QWindow *activeWindow = mActiveWindows.empty() ? nullptr : mActiveWindows.last()->window(); if (activeWindow != QGuiApplication::focusWindow()) QWindowSystemInterface::handleWindowActivated(activeWindow); + + if (!activeWindow) { + if (lastInputDevice()) { +#if QT_CONFIG(clipboard) + if (auto *dataDevice = lastInputDevice()->dataDevice()) + dataDevice->invalidateSelectionOffer(); +#endif +#if QT_CONFIG(wayland_client_primary_selection) + if (auto *device = lastInputDevice()->primarySelectionDevice()) + device->invalidateSelectionOffer(); +#endif + } + } } const wl_callback_listener QWaylandDisplay::syncCallbackListener = { @@ -630,6 +755,13 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const return mInputDevices.isEmpty() ? 0 : mInputDevices.first(); } +bool QWaylandDisplay::isKeyboardAvailable() const +{ + return std::any_of( + mInputDevices.constBegin(), mInputDevices.constEnd(), + [this](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; }); +} + #if QT_CONFIG(cursor) QWaylandCursor *QWaylandDisplay::waylandCursor() @@ -656,6 +788,8 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int p } // namespace QtWaylandClient +#include "qwaylanddisplay.moc" + QT_END_NAMESPACE #include "moc_qwaylanddisplay_p.cpp" diff --git a/qtwayland/src/client/qwaylanddisplay_p.h b/qtwayland/src/client/qwaylanddisplay_p.h index 1bad8b67..cf91b924 100644 --- a/qtwayland/src/client/qwaylanddisplay_p.h +++ b/qtwayland/src/client/qwaylanddisplay_p.h @@ -111,6 +111,7 @@ class QWaylandSurface; class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; +class EventThread; typedef void (*RegistryListener)(void *data, struct wl_registry *registry, @@ -122,15 +123,11 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandDisplay : public QObject, public QtWayland Q_OBJECT public: - struct FrameQueue { - FrameQueue(wl_event_queue *q = nullptr) : queue(q), mutex(new QMutex) {} - wl_event_queue *queue; - QMutex *mutex; - }; - QWaylandDisplay(QWaylandIntegration *waylandIntegration); ~QWaylandDisplay(void) override; + void initialize(); + #if QT_CONFIG(xkbcommon) struct xkb_context *xkbContext() const { return mXkbContext.get(); } #endif @@ -214,11 +211,11 @@ public: void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); void handleWindowDestroyed(QWaylandWindow *window); - wl_event_queue *createEventQueue(); - FrameQueue createFrameQueue(); - void destroyFrameQueue(const FrameQueue &q); - void dispatchQueueWhile(wl_event_queue *queue, std::function condition, int timeout = -1); + wl_event_queue *frameEventQueue() { return m_frameEventQueue; }; + + bool isKeyboardAvailable() const; + void initEventThread(); public slots: void blockingReadEvents(); void flushRequests(); @@ -241,6 +238,9 @@ private: }; struct wl_display *mDisplay = nullptr; + QScopedPointer m_eventThread; + wl_event_queue *m_frameEventQueue = nullptr; + QScopedPointer m_frameEventQueueThread; QtWayland::wl_compositor mCompositor; QScopedPointer mShm; QList mWaitingScreens; @@ -279,11 +279,9 @@ private: QWaylandInputDevice *mLastInputDevice = nullptr; QPointer mLastInputWindow; QPointer mLastKeyboardFocus; - QVector mActiveWindows; - QVector mExternalQueues; + QList mActiveWindows; struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; - QReadWriteLock m_frameQueueLock; bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); diff --git a/qtwayland/src/client/qwaylanddnd.cpp b/qtwayland/src/client/qwaylanddnd.cpp index 6535aa16..7c53f5fa 100644 --- a/qtwayland/src/client/qwaylanddnd.cpp +++ b/qtwayland/src/client/qwaylanddnd.cpp @@ -66,7 +66,7 @@ void QWaylandDrag::startDrag() { QBasicDrag::startDrag(); QWaylandWindow *icon = static_cast(shapedPixmapWindow()->handle()); - if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), icon)) { + if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), drag()->supportedActions(), icon)) { icon->addAttachOffset(-drag()->hotSpot()); } else { // Cancelling immediately does not work, since the event loop for QDrag::exec is started @@ -80,6 +80,9 @@ void QWaylandDrag::cancel() QBasicDrag::cancel(); m_display->currentInputDevice()->dataDevice()->cancelDrag(); + + if (drag()) + drag()->deleteLater(); } void QWaylandDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) @@ -103,33 +106,41 @@ void QWaylandDrag::endDrag() m_display->currentInputDevice()->handleEndDrag(); } -void QWaylandDrag::updateTarget(const QString &mimeType) +void QWaylandDrag::setResponse(bool accepted) { - setCanDrop(!mimeType.isEmpty()); - - if (canDrop()) { - updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers())); - } else { - updateCursor(Qt::IgnoreAction); - } + // This method is used for old DataDevices where the drag action is not communicated + Qt::DropAction action = defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers()); + setResponse(QPlatformDropQtResponse(accepted, action)); } -void QWaylandDrag::setResponse(const QPlatformDragQtResponse &response) +void QWaylandDrag::setResponse(const QPlatformDropQtResponse &response) { setCanDrop(response.isAccepted()); if (canDrop()) { - updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers())); + updateCursor(response.acceptedAction()); } else { updateCursor(Qt::IgnoreAction); } } -void QWaylandDrag::finishDrag(const QPlatformDropQtResponse &response) +void QWaylandDrag::setDropResponse(const QPlatformDropQtResponse &response) { setExecutedDropAction(response.acceptedAction()); +} + +void QWaylandDrag::finishDrag() +{ QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); eventFilter(shapedPixmapWindow(), &event); + + if (drag()) + drag()->deleteLater(); +} + +bool QWaylandDrag::ownsDragObject() const +{ + return true; } } diff --git a/qtwayland/src/client/qwaylanddnd_p.h b/qtwayland/src/client/qwaylanddnd_p.h index 474fe2ab..46f629ac 100644 --- a/qtwayland/src/client/qwaylanddnd_p.h +++ b/qtwayland/src/client/qwaylanddnd_p.h @@ -71,9 +71,10 @@ public: QWaylandDrag(QWaylandDisplay *display); ~QWaylandDrag() override; - void updateTarget(const QString &mimeType); - void setResponse(const QPlatformDragQtResponse &response); - void finishDrag(const QPlatformDropQtResponse &response); + void setResponse(bool accepted); + void setResponse(const QPlatformDropQtResponse &response); + void setDropResponse(const QPlatformDropQtResponse &response); + void finishDrag(); protected: void startDrag() override; @@ -82,6 +83,7 @@ protected: void drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; void endDrag() override; + bool ownsDragObject() const override; private: QWaylandDisplay *m_display = nullptr; diff --git a/qtwayland/src/client/qwaylandinputcontext.cpp b/qtwayland/src/client/qwaylandinputcontext.cpp index 47696a6a..9435e961 100644 --- a/qtwayland/src/client/qwaylandinputcontext.cpp +++ b/qtwayland/src/client/qwaylandinputcontext.cpp @@ -93,9 +93,14 @@ void QWaylandTextInput::reset() void QWaylandTextInput::commit() { if (QObject *o = QGuiApplication::focusObject()) { - QInputMethodEvent event; - event.setCommitString(m_preeditCommit); - QCoreApplication::sendEvent(o, &event); + if (!m_preeditCommit.isEmpty()) { + + QInputMethodEvent event; + event.setCommitString(m_preeditCommit); + m_preeditCommit = QString(); + + QCoreApplication::sendEvent(o, &event); + } } reset(); diff --git a/qtwayland/src/client/qwaylandinputdevice.cpp b/qtwayland/src/client/qwaylandinputdevice.cpp index 08e8adb8..34c185a3 100644 --- a/qtwayland/src/client/qwaylandinputdevice.cpp +++ b/qtwayland/src/client/qwaylandinputdevice.cpp @@ -310,8 +310,7 @@ void QWaylandInputDevice::Pointer::updateCursor() auto shape = seat()->mCursor.shape; if (shape == Qt::BlankCursor) { - if (mCursor.surface) - mCursor.surface->hide(); + getOrCreateCursorSurface()->hide(); return; } @@ -846,7 +845,7 @@ void QWaylandInputDevice::Pointer::releaseButtons() mButtons = Qt::NoButton; if (auto *window = focusWindow()) { - MotionEvent e(focusWindow(), mParent->mTime, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers()); + ReleaseEvent e(focusWindow(), mParent->mTime, mSurfacePos, mGlobalPos, mButtons, Qt::NoButton, mParent->modifiers()); window->handleMouse(mParent, e); } } @@ -1304,14 +1303,6 @@ void QWaylandInputDevice::Keyboard::handleFocusDestroyed() void QWaylandInputDevice::Keyboard::handleFocusLost() { mFocus = nullptr; -#if QT_CONFIG(clipboard) - if (auto *dataDevice = mParent->dataDevice()) - dataDevice->invalidateSelectionOffer(); -#endif -#if QT_CONFIG(wayland_client_primary_selection) - if (auto *device = mParent->primarySelectionDevice()) - device->invalidateSelectionOffer(); -#endif mParent->mQDisplay->handleKeyboardFocusChanged(mParent); mRepeatTimer.stop(); } @@ -1400,6 +1391,7 @@ void QWaylandInputDevice::Touch::touch_cancel() if (touchExt) touchExt->touchCanceled(); + mFocus = nullptr; QWindowSystemInterface::handleTouchCancelEvent(nullptr, mParent->mTouchDevice); } diff --git a/qtwayland/src/client/qwaylandintegration.cpp b/qtwayland/src/client/qwaylandintegration.cpp index d257e2e3..54861600 100644 --- a/qtwayland/src/client/qwaylandintegration.cpp +++ b/qtwayland/src/client/qwaylandintegration.cpp @@ -125,6 +125,9 @@ QWaylandIntegration::QWaylandIntegration() #endif reconfigureInputContext(); + + QWaylandWindow::fixedToplevelPositions = + !qEnvironmentVariableIsSet("QT_WAYLAND_DISABLE_FIXED_POSITIONS"); } QWaylandIntegration::~QWaylandIntegration() @@ -192,14 +195,18 @@ QAbstractEventDispatcher *QWaylandIntegration::createEventDispatcher() const void QWaylandIntegration::initialize() { + mDisplay->initEventThread(); + + // Call after eventDispatcher is fully connected, for QWaylandDisplay::forceRoundTrip() + mDisplay->initialize(); + + // But the aboutToBlock() and awake() should be connected after initializePlatform(). + // Otherwise the connected flushRequests() may consumes up all events before processEvents starts to wait, + // so that processEvents(QEventLoop::WaitForMoreEvents) may be blocked in the forceRoundTrip(). QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::eventDispatcher; QObject::connect(dispatcher, SIGNAL(aboutToBlock()), mDisplay.data(), SLOT(flushRequests())); QObject::connect(dispatcher, SIGNAL(awake()), mDisplay.data(), SLOT(flushRequests())); - int fd = wl_display_get_fd(mDisplay->wl_display()); - QSocketNotifier *sn = new QSocketNotifier(fd, QSocketNotifier::Read, mDisplay.data()); - QObject::connect(sn, SIGNAL(activated(QSocketDescriptor)), mDisplay.data(), SLOT(flushRequests())); - // Qt does not support running with no screens mDisplay->ensureScreen(); } @@ -262,6 +269,14 @@ QWaylandDisplay *QWaylandIntegration::display() const return mDisplay.data(); } +Qt::KeyboardModifiers QWaylandIntegration::queryKeyboardModifiers() const +{ + if (auto *seat = mDisplay->currentInputDevice()) { + return seat->modifiers(); + } + return Qt::NoModifier; +} + QList QWaylandIntegration::possibleKeys(const QKeyEvent *event) const { if (auto *seat = mDisplay->currentInputDevice()) diff --git a/qtwayland/src/client/qwaylandintegration_p.h b/qtwayland/src/client/qwaylandintegration_p.h index ff70ae25..73b80658 100644 --- a/qtwayland/src/client/qwaylandintegration_p.h +++ b/qtwayland/src/client/qwaylandintegration_p.h @@ -107,6 +107,8 @@ public: QWaylandDisplay *display() const; + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + QList possibleKeys(const QKeyEvent *event) const override; QStringList themeNames() const override; diff --git a/qtwayland/src/client/qwaylandnativeinterface.cpp b/qtwayland/src/client/qwaylandnativeinterface.cpp index bf54a1a0..9763c312 100644 --- a/qtwayland/src/client/qwaylandnativeinterface.cpp +++ b/qtwayland/src/client/qwaylandnativeinterface.cpp @@ -139,7 +139,7 @@ void *QWaylandNativeInterface::nativeResourceForScreen(const QByteArray &resourc { QByteArray lowerCaseResource = resourceString.toLower(); - if (lowerCaseResource == "output") + if (lowerCaseResource == "output" && !screen->handle()->isPlaceholder()) return ((QWaylandScreen *) screen->handle())->output(); return nullptr; diff --git a/qtwayland/src/client/qwaylandprimaryselectionv1.cpp b/qtwayland/src/client/qwaylandprimaryselectionv1.cpp index 78b1de13..cf3b4b22 100644 --- a/qtwayland/src/client/qwaylandprimaryselectionv1.cpp +++ b/qtwayland/src/client/qwaylandprimaryselectionv1.cpp @@ -54,11 +54,6 @@ QWaylandPrimarySelectionDeviceManagerV1::QWaylandPrimarySelectionDeviceManagerV1 : zwp_primary_selection_device_manager_v1(display->wl_registry(), id, qMin(version, uint(1))) , m_display(display) { - // Create devices for all seats. - // This only works if we get the global before all devices - const auto seats = m_display->inputDevices(); - for (auto *seat : seats) - seat->setPrimarySelectionDevice(createDevice(seat)); } QWaylandPrimarySelectionDeviceV1 *QWaylandPrimarySelectionDeviceManagerV1::createDevice(QWaylandInputDevice *seat) diff --git a/qtwayland/src/client/qwaylandscreen.cpp b/qtwayland/src/client/qwaylandscreen.cpp index 6cb337de..5537dafd 100644 --- a/qtwayland/src/client/qwaylandscreen.cpp +++ b/qtwayland/src/client/qwaylandscreen.cpp @@ -60,7 +60,7 @@ QWaylandXdgOutputManagerV1::QWaylandXdgOutputManagerV1(QWaylandDisplay* display, } QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id) - : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 2)) + : QtWayland::wl_output(waylandDisplay->wl_registry(), id, qMin(version, 3)) , m_outputId(id) , mWaylandDisplay(waylandDisplay) , mOutputName(QStringLiteral("Screen%1").arg(id)) @@ -72,7 +72,7 @@ QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uin qCWarning(lcQpaWayland) << "wl_output done event not supported by compositor," << "QScreen may not work correctly"; mWaylandDisplay->forceRoundTrip(); // Give the compositor a chance to send geometry etc. - mOutputDone = true; // Fake the done event + mProcessedEvents |= OutputDoneEvent; // Fake the done event maybeInitialize(); } } @@ -81,16 +81,29 @@ QWaylandScreen::~QWaylandScreen() { if (zxdg_output_v1::isInitialized()) zxdg_output_v1::destroy(); + if (wl_output::isInitialized() && wl_output_get_version(wl_output::object()) >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output::release(); +} + +uint QWaylandScreen::requiredEvents() const +{ + uint ret = OutputDoneEvent; + + if (mWaylandDisplay->xdgOutputManager()) { + ret |= XdgOutputNameEvent; + + if (mWaylandDisplay->xdgOutputManager()->version() < 3) + ret |= XdgOutputDoneEvent; + } + return ret; } void QWaylandScreen::maybeInitialize() { Q_ASSERT(!mInitialized); - if (!mOutputDone) - return; - - if (mWaylandDisplay->xdgOutputManager() && !mXdgOutputDone) + const uint requiredEvents = this->requiredEvents(); + if ((mProcessedEvents & requiredEvents) != requiredEvents) return; mInitialized = true; @@ -276,9 +289,8 @@ void QWaylandScreen::output_scale(int32_t factor) void QWaylandScreen::output_done() { - mOutputDone = true; - if (zxdg_output_v1::isInitialized() && mWaylandDisplay->xdgOutputManager()->version() >= 3) - mXdgOutputDone = true; + mProcessedEvents |= OutputDoneEvent; + if (mInitialized) { updateOutputProperties(); if (zxdg_output_v1::isInitialized()) @@ -339,7 +351,7 @@ void QWaylandScreen::zxdg_output_v1_done() if (Q_UNLIKELY(mWaylandDisplay->xdgOutputManager()->version() >= 3)) qWarning(lcQpaWayland) << "zxdg_output_v1.done received on version 3 or newer, this is most likely a bug in the compositor"; - mXdgOutputDone = true; + mProcessedEvents |= XdgOutputDoneEvent; if (mInitialized) updateXdgOutputProperties(); else @@ -348,7 +360,11 @@ void QWaylandScreen::zxdg_output_v1_done() void QWaylandScreen::zxdg_output_v1_name(const QString &name) { + if (Q_UNLIKELY(mInitialized)) + qWarning(lcQpaWayland) << "zxdg_output_v1.name received after output has been initialized, this is most likely a bug in the compositor"; + mOutputName = name; + mProcessedEvents |= XdgOutputNameEvent; } void QWaylandScreen::updateXdgOutputProperties() diff --git a/qtwayland/src/client/qwaylandscreen_p.h b/qtwayland/src/client/qwaylandscreen_p.h index df1c94f2..050cfdc0 100644 --- a/qtwayland/src/client/qwaylandscreen_p.h +++ b/qtwayland/src/client/qwaylandscreen_p.h @@ -116,6 +116,13 @@ public: static QWaylandScreen *fromWlOutput(::wl_output *output); private: + enum Event : uint { + XdgOutputDoneEvent = 0x1, + OutputDoneEvent = 0x2, + XdgOutputNameEvent = 0x4, + }; + uint requiredEvents() const; + void output_mode(uint32_t flags, int width, int height, int refresh) override; void output_geometry(int32_t x, int32_t y, int32_t width, int32_t height, @@ -148,8 +155,7 @@ private: QSize mPhysicalSize; QString mOutputName; Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; - bool mOutputDone = false; - bool mXdgOutputDone = false; + uint mProcessedEvents = 0; bool mInitialized = false; #if QT_CONFIG(cursor) diff --git a/qtwayland/src/client/qwaylandshmbackingstore.cpp b/qtwayland/src/client/qwaylandshmbackingstore.cpp index dc7ff670..145f933b 100644 --- a/qtwayland/src/client/qwaylandshmbackingstore.cpp +++ b/qtwayland/src/client/qwaylandshmbackingstore.cpp @@ -52,6 +52,7 @@ #include +#include #include #include @@ -61,6 +62,9 @@ # ifndef MFD_CLOEXEC # define MFD_CLOEXEC 0x0001U # endif +# ifndef MFD_ALLOW_SEALING +# define MFD_ALLOW_SEALING 0x0002U +# endif #endif QT_BEGIN_NAMESPACE @@ -68,14 +72,16 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, - const QSize &size, QImage::Format format, int scale) + const QSize &size, QImage::Format format, qreal scale) { int stride = size.width() * 4; int alloc = stride * size.height(); int fd = -1; -#ifdef SYS_memfd_create - fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC); +#if defined(SYS_memfd_create) && defined(F_SEAL_SEAL) + fd = syscall(SYS_memfd_create, "wayland-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); #endif QScopedPointer filePointer; @@ -108,7 +114,7 @@ QWaylandShmBuffer::QWaylandShmBuffer(QWaylandDisplay *display, QWaylandShm* shm = display->shm(); wl_shm_format wl_format = shm->formatFrom(format); mImage = QImage(data, size.width(), size.height(), stride, format); - mImage.setDevicePixelRatio(qreal(scale)); + mImage.setDevicePixelRatio(scale); mShmPool = wl_shm_create_pool(shm->object(), fd, alloc); init(wl_shm_pool_create_buffer(mShmPool,0, size.width(), size.height(), @@ -180,8 +186,6 @@ void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) mPainting = true; ensureSize(); - waylandWindow()->setCanResize(false); - if (mBackBuffer->image()->hasAlphaChannel()) { QPainter p(paintDevice()); p.setCompositionMode(QPainter::CompositionMode_Source); @@ -196,7 +200,6 @@ void QWaylandShmBackingStore::endPaint() mPainting = false; if (mPendingFlush) flush(window(), mPendingRegion, QPoint()); - waylandWindow()->setCanResize(true); } void QWaylandShmBackingStore::ensureSize() @@ -271,7 +274,7 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size) void QWaylandShmBackingStore::resize(const QSize &size) { QMargins margins = windowDecorationMargins(); - int scale = waylandWindow()->scale(); + qreal scale = waylandWindow()->scale(); QSize sizeWithMargins = (size + QSize(margins.left()+margins.right(),margins.top()+margins.bottom())) * scale; // We look for a free buffer to draw into. If the buffer is not the last buffer we used, diff --git a/qtwayland/src/client/qwaylandshmbackingstore_p.h b/qtwayland/src/client/qwaylandshmbackingstore_p.h index e01632da..f3fae438 100644 --- a/qtwayland/src/client/qwaylandshmbackingstore_p.h +++ b/qtwayland/src/client/qwaylandshmbackingstore_p.h @@ -71,7 +71,7 @@ class QWaylandWindow; class Q_WAYLAND_CLIENT_EXPORT QWaylandShmBuffer : public QWaylandBuffer { public: QWaylandShmBuffer(QWaylandDisplay *display, - const QSize &size, QImage::Format format, int scale = 1); + const QSize &size, QImage::Format format, qreal scale = 1); ~QWaylandShmBuffer() override; QSize size() const override { return mImage.size(); } int scale() const override { return int(mImage.devicePixelRatio()); } diff --git a/qtwayland/src/client/qwaylandwindow.cpp b/qtwayland/src/client/qwaylandwindow.cpp index 5a390434..d3958d46 100644 --- a/qtwayland/src/client/qwaylandwindow.cpp +++ b/qtwayland/src/client/qwaylandwindow.cpp @@ -76,7 +76,6 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) : QPlatformWindow(window) , mDisplay(display) - , mFrameQueue(mDisplay->createFrameQueue()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { { @@ -95,9 +94,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) QWaylandWindow::~QWaylandWindow() { - mDisplay->destroyFrameQueue(mFrameQueue); - mDisplay->handleWindowDestroyed(this); - delete mWindowDecoration; if (mSurface) @@ -189,7 +185,7 @@ void QWaylandWindow::initWindow() // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() // to inform the compositor that high-resolution buffers will be provided. if (mDisplay->compositorVersion() >= 3) - mSurface->set_buffer_scale(scale()); + mSurface->set_buffer_scale(mScale); if (QScreen *s = window()->screen()) setOrientationMask(s->orientationUpdateMask()); @@ -204,6 +200,8 @@ void QWaylandWindow::initWindow() mShellSurface->requestWindowStates(window()->windowStates()); handleContentOrientationChange(window()->contentOrientation()); mFlags = window()->flags(); + + mSurface->commit(); } void QWaylandWindow::initializeWlSurface() @@ -243,6 +241,7 @@ bool QWaylandWindow::shouldCreateSubSurface() const void QWaylandWindow::reset() { + closeChildPopups(); delete mShellSurface; mShellSurface = nullptr; delete mSubSurfaceWindow; @@ -255,17 +254,22 @@ void QWaylandWindow::reset() mSurface.reset(); } - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } + { + QMutexLocker lock(&mFrameSyncMutex); + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } - mFrameCallbackElapsedTimer.invalidate(); - mWaitingForFrameCallback = false; + mFrameCallbackElapsedTimer.invalidate(); + mWaitingForFrameCallback = false; + } mFrameCallbackTimedOut = false; mMask = QRegion(); mQueuedBuffer = nullptr; + + mDisplay->handleWindowDestroyed(this); } QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) @@ -351,19 +355,25 @@ void QWaylandWindow::setGeometry_helper(const QRect &rect) } } -void QWaylandWindow::setGeometry(const QRect &rect) +void QWaylandWindow::setGeometry(const QRect &r) { + auto rect = r; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip) { + rect.moveTo(screen()->geometry().topLeft()); + } setGeometry_helper(rect); if (window()->isVisible() && rect.isValid()) { if (mWindowDecoration) mWindowDecoration->update(); - if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) + if (mResizeAfterSwap && windowType() == Egl && mSentInitialResize) { + QMutexLocker lock(&mResizeLock); mResizeDirty = true; - else + } else { QWindowSystemInterface::handleGeometryChange(window(), geometry()); - + } mSentInitialResize = true; } QRect exposeGeometry(QPoint(), geometry().size()); @@ -374,7 +384,7 @@ void QWaylandWindow::setGeometry(const QRect &rect) mShellSurface->setWindowGeometry(windowContentGeometry()); if (isOpaque() && mMask.isEmpty()) - setOpaqueArea(rect); + setOpaqueArea(QRect(QPoint(0, 0), rect.size())); } void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) @@ -399,21 +409,6 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) mLastExposeGeometry = rect; } - -static QVector> activePopups; - -void QWaylandWindow::closePopups(QWaylandWindow *parent) -{ - while (!activePopups.isEmpty()) { - auto popup = activePopups.takeLast(); - if (popup.isNull()) - continue; - if (popup.data() == parent) - return; - popup->reset(); - } -} - QPlatformScreen *QWaylandWindow::calculateScreenFromSurfaceEvents() const { QReadLocker lock(&mSurfaceLock); @@ -433,10 +428,7 @@ void QWaylandWindow::setVisible(bool visible) lastVisible = visible; if (visible) { - if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) - activePopups << this; initWindow(); - mDisplay->flushRequests(); setGeometry(windowGeometry()); // Don't flush the events here, or else the newly visible window may start drawing, but since @@ -444,7 +436,6 @@ void QWaylandWindow::setVisible(bool visible) // QWaylandShmBackingStore::beginPaint(). } else { sendExposeEvent(QRect()); - closePopups(this); reset(); } } @@ -487,8 +478,6 @@ void QWaylandWindow::setMask(const QRegion &mask) if (isOpaque()) setOpaqueArea(mMask); } - - mSurface->commit(); } void QWaylandWindow::applyConfigureWhenPossible() @@ -556,12 +545,12 @@ void QWaylandWindow::sendRecursiveExposeEvent() void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { - Q_ASSERT(!buffer->committed()); QReadLocker locker(&mSurfaceLock); if (mSurface == nullptr) return; if (buffer) { + Q_ASSERT(!buffer->committed()); handleUpdate(); buffer->setBusy(); @@ -583,7 +572,16 @@ void QWaylandWindow::damage(const QRect &rect) if (mSurface == nullptr) return; - mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + const qreal s = scale(); + if (mDisplay->compositorVersion() >= 4) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } else { + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } } void QWaylandWindow::safeCommit(QWaylandBuffer *buffer, const QRegion &damage) @@ -619,8 +617,19 @@ void QWaylandWindow::commit(QWaylandBuffer *buffer, const QRegion &damage) return; attachOffset(buffer); - for (const QRect &rect: damage) - mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + if (mDisplay->compositorVersion() >= 4) { + const qreal s = scale(); + for (const QRect &rect : damage) { + const QRect bufferRect = + QRectF(s * rect.x(), s * rect.y(), s * rect.width(), s * rect.height()) + .toAlignedRect(); + mSurface->damage_buffer(bufferRect.x(), bufferRect.y(), bufferRect.width(), + bufferRect.height()); + } + } else { + for (const QRect &rect: damage) + mSurface->damage(rect.x(), rect.y(), rect.width(), rect.height()); + } Q_ASSERT(!buffer->committed()); buffer->setCommitted(); mSurface->commit(); @@ -635,42 +644,53 @@ void QWaylandWindow::commit() const wl_callback_listener QWaylandWindow::callbackListener = { [](void *data, wl_callback *callback, uint32_t time) { - Q_UNUSED(callback); Q_UNUSED(time); auto *window = static_cast(data); - window->handleFrameCallback(); + window->handleFrameCallback(callback); } }; -void QWaylandWindow::handleFrameCallback() +void QWaylandWindow::handleFrameCallback(wl_callback* callback) { + QMutexLocker locker(&mFrameSyncMutex); + if (!mFrameCallback) { + // This means the callback is already unset by QWaylandWindow::reset. + // The wl_callback object will be destroyed there too. + return; + } + Q_ASSERT(callback == mFrameCallback); + wl_callback_destroy(callback); + mFrameCallback = nullptr; + mWaitingForFrameCallback = false; mFrameCallbackElapsedTimer.invalidate(); // The rest can wait until we can run it on the correct thread - if (!mWaitingForUpdateDelivery) { - auto doHandleExpose = [this]() { - bool wasExposed = isExposed(); - mFrameCallbackTimedOut = false; - if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? - sendExposeEvent(QRect(QPoint(), geometry().size())); - if (wasExposed && hasPendingUpdateRequest()) - deliverUpdateRequest(); - - mWaitingForUpdateDelivery = false; - }; + auto doHandleExpose = [this]() { + mWaitingForUpdateDelivery.storeRelease(false); + bool wasExposed = isExposed(); + mFrameCallbackTimedOut = false; + if (!wasExposed && isExposed()) // Did setting mFrameCallbackTimedOut make the window exposed? + sendExposeEvent(QRect(QPoint(), geometry().size())); + if (wasExposed && hasPendingUpdateRequest()) + deliverUpdateRequest(); + }; + if (mWaitingForUpdateDelivery.testAndSetAcquire(false, true)) { // Queued connection, to make sure we don't call handleUpdate() from inside waitForFrameSync() // in the single-threaded case. - mWaitingForUpdateDelivery = true; QMetaObject::invokeMethod(this, doHandleExpose, Qt::QueuedConnection); } + + mFrameSyncWait.notify_all(); } bool QWaylandWindow::waitForFrameSync(int timeout) { - QMutexLocker locker(mFrameQueue.mutex); - mDisplay->dispatchQueueWhile(mFrameQueue.queue, [&]() { return mWaitingForFrameCallback; }, timeout); + QMutexLocker locker(&mFrameSyncMutex); + + QDeadlineTimer deadline(timeout); + while (mWaitingForFrameCallback && mFrameSyncWait.wait(&mFrameSyncMutex, deadline)) { } if (mWaitingForFrameCallback) { qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; @@ -772,8 +792,6 @@ void QWaylandWindow::handleContentOrientationChange(Qt::ScreenOrientation orient Q_UNREACHABLE(); } mSurface->set_buffer_transform(transform); - // set_buffer_transform is double buffered, we need to commit. - mSurface->commit(); } void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask) @@ -1032,8 +1050,17 @@ void QWaylandWindow::handleScreensChanged() if (newScreen == mLastReportedScreen) return; + if (!newScreen->isPlaceholder() && !newScreen->QPlatformScreen::screen()) + mDisplay->forceRoundTrip(); QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); mLastReportedScreen = newScreen; + if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup + && window()->type() != Qt::ToolTip + && geometry().topLeft() != newScreen->geometry().topLeft()) { + auto geometry = this->geometry(); + geometry.moveTo(newScreen->geometry().topLeft()); + setGeometry(geometry); + } int scale = newScreen->isPlaceholder() ? 1 : static_cast(newScreen)->scale(); if (scale != mScale) { @@ -1084,14 +1111,14 @@ bool QWaylandWindow::isActive() const return mDisplay->isWindowActivated(this); } -int QWaylandWindow::scale() const +qreal QWaylandWindow::scale() const { - return mScale; + return devicePixelRatio(); } qreal QWaylandWindow::devicePixelRatio() const { - return mScale; + return qreal(mScale); } bool QWaylandWindow::setMouseGrabEnabled(bool grab) @@ -1105,10 +1132,18 @@ bool QWaylandWindow::setMouseGrabEnabled(bool grab) return true; } +Qt::WindowStates QWaylandWindow::windowStates() const +{ + return mLastReportedWindowStates; +} + void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states) { createDecoration(); - QWindowSystemInterface::handleWindowStateChanged(window(), states, mLastReportedWindowStates); + Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive; + Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive; + QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive, + lastStatesWithoutActive); mLastReportedWindowStates = states; } @@ -1150,19 +1185,24 @@ void QWaylandWindow::timerEvent(QTimerEvent *event) if (event->timerId() != mFrameCallbackCheckIntervalTimerId) return; - bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); - if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { - killTimer(mFrameCallbackCheckIntervalTimerId); - mFrameCallbackCheckIntervalTimerId = -1; - } - if (mFrameCallbackElapsedTimer.isValid() && callbackTimerExpired) { - mFrameCallbackElapsedTimer.invalidate(); + { + QMutexLocker lock(&mFrameSyncMutex); - qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; - mFrameCallbackTimedOut = true; - mWaitingForUpdate = false; - sendExposeEvent(QRect()); + bool callbackTimerExpired = mFrameCallbackElapsedTimer.hasExpired(mFrameCallbackTimeout); + if (!mFrameCallbackElapsedTimer.isValid() || callbackTimerExpired ) { + killTimer(mFrameCallbackCheckIntervalTimerId); + mFrameCallbackCheckIntervalTimerId = -1; + } + if (!mFrameCallbackElapsedTimer.isValid() || !callbackTimerExpired) { + return; + } + mFrameCallbackElapsedTimer.invalidate(); } + + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); } void QWaylandWindow::requestUpdate() @@ -1171,8 +1211,11 @@ void QWaylandWindow::requestUpdate() Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA // If we have a frame callback all is good and will be taken care of there - if (mWaitingForFrameCallback) - return; + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet // This is a somewhat redundant behavior and might indicate a bug in the calling code, so log @@ -1185,7 +1228,12 @@ void QWaylandWindow::requestUpdate() // so use invokeMethod to delay the delivery a bit. QMetaObject::invokeMethod(this, [this] { // Things might have changed in the meantime - if (hasPendingUpdateRequest() && !mWaitingForFrameCallback) + { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; + } + if (hasPendingUpdateRequest()) deliverUpdateRequest(); }, Qt::QueuedConnection); } @@ -1196,19 +1244,18 @@ void QWaylandWindow::requestUpdate() void QWaylandWindow::handleUpdate() { qCDebug(lcWaylandBackingstore) << "handleUpdate" << QThread::currentThread(); + // TODO: Should sync subsurfaces avoid requesting frame callbacks? QReadLocker lock(&mSurfaceLock); if (!mSurface) return; - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) + return; - QMutexLocker locker(mFrameQueue.mutex); struct ::wl_surface *wrappedSurface = reinterpret_cast(wl_proxy_create_wrapper(mSurface->object())); - wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mFrameQueue.queue); + wl_proxy_set_queue(reinterpret_cast(wrappedSurface), mDisplay->frameEventQueue()); mFrameCallback = wl_surface_frame(wrappedSurface); wl_proxy_wrapper_destroy(wrappedSurface); wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); @@ -1218,6 +1265,8 @@ void QWaylandWindow::handleUpdate() // Start a timer for handling the case when the compositor stops sending frame callbacks. if (mFrameCallbackTimeout > 0) { QMetaObject::invokeMethod(this, [this] { + QMutexLocker locker(&mFrameSyncMutex); + if (mWaitingForFrameCallback) { if (mFrameCallbackCheckIntervalTimerId < 0) mFrameCallbackCheckIntervalTimerId = startTimer(mFrameCallbackTimeout); @@ -1278,6 +1327,20 @@ void QWaylandWindow::setOpaqueArea(const QRegion &opaqueArea) wl_region_destroy(region); } +void QWaylandWindow::addChildPopup(QWaylandWindow *surface) { + mChildPopups.append(surface); +} + +void QWaylandWindow::removeChildPopup(QWaylandWindow *surface) { + mChildPopups.removeAll(surface); +} + +void QWaylandWindow::closeChildPopups() { + while (!mChildPopups.isEmpty()) { + auto popup = mChildPopups.takeLast(); + popup->reset(); + } +} } QT_END_NAMESPACE diff --git a/qtwayland/src/client/qwaylandwindow_p.h b/qtwayland/src/client/qwaylandwindow_p.h index 01337cff..741f9e5c 100644 --- a/qtwayland/src/client/qwaylandwindow_p.h +++ b/qtwayland/src/client/qwaylandwindow_p.h @@ -98,6 +98,9 @@ public: QWaylandWindow(QWindow *window, QWaylandDisplay *display); ~QWaylandWindow() override; + // Keep Toplevels position on the top left corner of their screen + static inline bool fixedToplevelPositions = true; + virtual WindowType windowType() const = 0; virtual void ensureSize(); WId winId() const override; @@ -148,13 +151,14 @@ public: void setWindowState(Qt::WindowStates states) override; void setWindowFlags(Qt::WindowFlags flags) override; void handleWindowStatesChanged(Qt::WindowStates states); + Qt::WindowStates windowStates() const; void raise() override; void lower() override; void setMask(const QRegion ®ion) override; - int scale() const; + qreal scale() const; qreal devicePixelRatio() const override; void requestActivateWindow() override; @@ -206,6 +210,10 @@ public: void handleUpdate(); void deliverUpdateRequest() override; + void addChildPopup(QWaylandWindow* child); + void removeChildPopup(QWaylandWindow* child); + void closeChildPopups(); + public slots: void applyConfigure(); @@ -215,7 +223,11 @@ signals: protected: QWaylandDisplay *mDisplay = nullptr; + + // mSurface can be written by the main thread. Other threads should claim a read lock for access + mutable QReadWriteLock mSurfaceLock; QScopedPointer mSurface; + QWaylandShellSurface *mShellSurface = nullptr; QWaylandSubSurface *mSubSurfaceWindow = nullptr; QVector mChildren; @@ -225,13 +237,14 @@ protected: Qt::MouseButtons mMousePressedInContentArea = Qt::NoButton; WId mWindowId; - bool mWaitingForFrameCallback = false; bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out - bool mWaitingForUpdateDelivery = false; int mFrameCallbackCheckIntervalTimerId = -1; - QElapsedTimer mFrameCallbackElapsedTimer; - struct ::wl_callback *mFrameCallback = nullptr; - QWaylandDisplay::FrameQueue mFrameQueue; + QAtomicInt mWaitingForUpdateDelivery = false; + + bool mWaitingForFrameCallback = false; // Protected by mFrameSyncMutex + QElapsedTimer mFrameCallbackElapsedTimer; // Protected by mFrameSyncMutex + struct ::wl_callback *mFrameCallback = nullptr; // Protected by mFrameSyncMutex + QMutex mFrameSyncMutex; QWaitCondition mFrameSyncWait; // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer @@ -261,6 +274,8 @@ protected: QWaylandBuffer *mQueuedBuffer = nullptr; QRegion mQueuedBufferDamage; + QList> mChildPopups; + private: void setGeometry_helper(const QRect &rect); void initWindow(); @@ -283,12 +298,10 @@ private: QRect mLastExposeGeometry; static const wl_callback_listener callbackListener; - void handleFrameCallback(); + void handleFrameCallback(struct ::wl_callback* callback); static QWaylandWindow *mMouseGrab; - mutable QReadWriteLock mSurfaceLock; - friend class QWaylandSubSurface; }; diff --git a/qtwayland/src/client/shellintegration/qwaylandshellintegration_p.h b/qtwayland/src/client/shellintegration/qwaylandshellintegration_p.h index ccad0048..4cc9b3b8 100644 --- a/qtwayland/src/client/shellintegration/qwaylandshellintegration_p.h +++ b/qtwayland/src/client/shellintegration/qwaylandshellintegration_p.h @@ -73,11 +73,10 @@ public: return true; } virtual QWaylandShellSurface *createShellSurface(QWaylandWindow *window) = 0; + // kept for binary compat with layer-shell-qt virtual void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) { - if (newFocus) - m_display->handleWindowActivated(newFocus); - if (oldFocus) - m_display->handleWindowDeactivated(oldFocus); + Q_UNUSED(newFocus); + Q_UNUSED(oldFocus); } virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) { Q_UNUSED(resource); diff --git a/qtwayland/src/compositor/configure.json b/qtwayland/src/compositor/configure.json index bcfd5215..da95d07b 100644 --- a/qtwayland/src/compositor/configure.json +++ b/qtwayland/src/compositor/configure.json @@ -7,6 +7,31 @@ "testDir": "../../config.tests", "libraries": { + "wayland-client": { + "label": "Wayland client library", + "headers": "wayland-version.h", + "test": { + "main": [ + "#if WAYLAND_VERSION_MAJOR < 1", + "# error Wayland 1.8.0 or higher required", + "#endif", + "#if WAYLAND_VERSION_MAJOR == 1", + "# if WAYLAND_VERSION_MINOR < 8", + "# error Wayland 1.8.0 or higher required", + "# endif", + "# if WAYLAND_VERSION_MINOR == 8", + "# if WAYLAND_VERSION_MICRO < 0", + "# error Wayland 1.8.0 or higher required", + "# endif", + "# endif", + "#endif" + ] + }, + "sources": [ + { "type": "pkgConfig", "args": "wayland-client" }, + "-lwayland-client" + ] + }, "wayland-server": { "label": "wayland-server", "headers": "wayland-version.h", @@ -151,8 +176,7 @@ "#endif" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "dmabuf-client-buffer": { "label": "Linux Client dma-buf Buffer Sharing", @@ -176,8 +200,7 @@ "return 0;" ] }, - "libs": "-ldrm", - "use": "egl" + "use": "drm egl" }, "vulkan-server-buffer": { "label": "Vulkan Buffer Sharing", @@ -195,7 +218,8 @@ "exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;", "return 0;" ] - } + }, + "use": "wayland-client" } }, diff --git a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp index e00c28c3..dbe2845a 100644 --- a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp +++ b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.cpp @@ -40,6 +40,7 @@ #include "qwaylandeglwindow.h" #include +#include #include "qwaylandglcontext.h" #include @@ -115,6 +116,7 @@ void QWaylandEglWindow::updateSurface(bool create) } mOffset = QPoint(); } else { + QReadLocker locker(&mSurfaceLock); if (m_waylandEglWindow) { int current_width, current_height; static bool disableResizeCheck = qgetenv("QT_WAYLAND_DISABLE_RESIZECHECK").toInt(); @@ -122,14 +124,16 @@ void QWaylandEglWindow::updateSurface(bool create) if (!disableResizeCheck) { wl_egl_window_get_attached_size(m_waylandEglWindow, ¤t_width, ¤t_height); } - if (disableResizeCheck || (current_width != sizeWithMargins.width() || current_height != sizeWithMargins.height())) { + if (disableResizeCheck || (current_width != sizeWithMargins.width() || current_height != sizeWithMargins.height()) || m_requestedSize != sizeWithMargins) { wl_egl_window_resize(m_waylandEglWindow, sizeWithMargins.width(), sizeWithMargins.height(), mOffset.x(), mOffset.y()); + m_requestedSize = sizeWithMargins; mOffset = QPoint(); m_resize = true; } - } else if (create && wlSurface()) { - m_waylandEglWindow = wl_egl_window_create(wlSurface(), sizeWithMargins.width(), sizeWithMargins.height()); + } else if (create && mSurface) { + m_waylandEglWindow = wl_egl_window_create(mSurface->object(), sizeWithMargins.width(), sizeWithMargins.height()); + m_requestedSize = sizeWithMargins; } if (!m_eglSurface && m_waylandEglWindow && create) { diff --git a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h index 2fccbcea..ad1e5ee9 100644 --- a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h +++ b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandeglwindow.h @@ -85,6 +85,7 @@ private: mutable QOpenGLFramebufferObject *m_contentFBO = nullptr; QSurfaceFormat m_format; + QSize m_requestedSize; }; } diff --git a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp index c1f45fa6..bbc63444 100644 --- a/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp +++ b/qtwayland/src/hardwareintegration/client/wayland-egl/qwaylandglcontext.cpp @@ -195,7 +195,7 @@ public: QOpenGLTextureCache *cache = QOpenGLTextureCache::cacheForContext(m_context->context()); QSize surfaceSize = window->surfaceSize(); - int scale = window->scale() ; + qreal scale = window->scale() ; glViewport(0, 0, surfaceSize.width() * scale, surfaceSize.height() * scale); //Draw Decoration diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp index 85d25e3c..60bdd491 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5.cpp @@ -47,18 +47,21 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { -QWaylandXdgPopupV5::QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow *window) +QWaylandXdgPopupV5::QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow* parent, QWaylandWindow *window) : QWaylandShellSurface(window) , QtWayland::xdg_popup_v5(popup) + , m_parent(parent) , m_window(window) { if (window->display()->windowExtension()) m_extendedWindow = new QWaylandExtendedSurface(window); + m_parent->addChildPopup(m_window); } QWaylandXdgPopupV5::~QWaylandXdgPopupV5() { xdg_popup_destroy(object()); + m_parent->removeChildPopup(m_window); delete m_extendedWindow; } diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h index 7494f6a6..d85f130b 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgpopupv5_p.h @@ -70,7 +70,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandXdgPopupV5 : public QWaylandShellSurface { Q_OBJECT public: - QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow *window); + QWaylandXdgPopupV5(struct ::xdg_popup_v5 *popup, QWaylandWindow* parent, QWaylandWindow *window); ~QWaylandXdgPopupV5() override; protected: @@ -78,6 +78,7 @@ protected: private: QWaylandExtendedSurface *m_extendedWindow = nullptr; + QWaylandWindow *m_parent = nullptr; QWaylandWindow *m_window = nullptr; }; diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp index 7e242c4a..def8452a 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5.cpp @@ -84,7 +84,7 @@ QWaylandXdgPopupV5 *QWaylandXdgShellV5::createXdgPopup(QWaylandWindow *window, Q int x = position.x() + parentWindow->frameMargins().left(); int y = position.y() + parentWindow->frameMargins().top(); - auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->wlSurface(), parentSurface, seat, m_popupSerial, x, y), window); + auto popup = new QWaylandXdgPopupV5(get_xdg_popup(window->wlSurface(), parentSurface, seat, m_popupSerial, x, y), parentWindow, window); m_popups.append(window); QObject::connect(popup, &QWaylandXdgPopupV5::destroyed, [this, window](){ m_popups.removeOne(window); diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp index 4e25949f..cfc60939 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration.cpp @@ -85,13 +85,6 @@ QWaylandShellSurface *QWaylandXdgShellV5Integration::createShellSurface(QWayland return m_xdgShell->createXdgSurface(window); } -void QWaylandXdgShellV5Integration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) { - if (newFocus && qobject_cast(newFocus->shellSurface())) - m_display->handleWindowActivated(newFocus); - if (oldFocus && qobject_cast(oldFocus->shellSurface())) - m_display->handleWindowDeactivated(oldFocus); -} - } QT_END_NAMESPACE diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h index ce6bdb9e..aed88670 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v5/qwaylandxdgshellv5integration_p.h @@ -67,7 +67,6 @@ public: QWaylandXdgShellV5Integration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp index 8c371661..151c78e3 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6.cpp @@ -174,6 +174,7 @@ QWaylandXdgSurfaceV6::Popup::Popup(QWaylandXdgSurfaceV6 *xdgSurface, QWaylandXdg , m_xdgSurface(xdgSurface) , m_parent(parent) { + m_parent->window()->addChildPopup(m_xdgSurface->window()); } QWaylandXdgSurfaceV6::Popup::~Popup() @@ -181,6 +182,8 @@ QWaylandXdgSurfaceV6::Popup::~Popup() if (isInitialized()) destroy(); + m_parent->window()->removeChildPopup(m_xdgSurface->window()); + if (m_grabbing) { auto *shell = m_xdgSurface->m_shell; Q_ASSERT(shell->m_topmostGrabbingPopup == this); diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp index 03164316..e8da8ba1 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration.cpp @@ -68,20 +68,6 @@ QWaylandShellSurface *QWaylandXdgShellV6Integration::createShellSurface(QWayland return m_xdgShell->getXdgSurface(window); } -void QWaylandXdgShellV6Integration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) -{ - if (newFocus) { - auto *xdgSurface = qobject_cast(newFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowActivated(newFocus); - } - if (oldFocus && qobject_cast(oldFocus->shellSurface())) { - auto *xdgSurface = qobject_cast(oldFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowDeactivated(oldFocus); - } -} - } QT_END_NAMESPACE diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h index 261f8cbb..c1bcd5c6 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h +++ b/qtwayland/src/plugins/shellintegration/xdg-shell-v6/qwaylandxdgshellv6integration_p.h @@ -65,7 +65,6 @@ public: QWaylandXdgShellV6Integration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp index 94ea573e..535c3398 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp @@ -67,11 +67,6 @@ QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface) QWaylandXdgSurface::Toplevel::~Toplevel() { - if (m_applied.states & Qt::WindowActive) { - QWaylandWindow *window = m_xdgSurface->window(); - window->display()->handleWindowDeactivated(window); - } - // The protocol spec requires that the decoration object is deleted before xdg_toplevel. delete m_decoration; m_decoration = nullptr; @@ -85,16 +80,15 @@ void QWaylandXdgSurface::Toplevel::applyConfigure() if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) m_normalSize = m_xdgSurface->m_window->windowFrameGeometry().size(); - if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)) + if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window); - if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)) + if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive) + && !m_xdgSurface->m_window->display()->isKeyboardAvailable()) m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window); - // TODO: none of the other plugins send WindowActive either, but is it on purpose? - Qt::WindowStates statesWithoutActive = m_pending.states & ~Qt::WindowActive; - - m_xdgSurface->m_window->handleWindowStatesChanged(statesWithoutActive); + m_xdgSurface->m_window->handleWindowStatesChanged(m_pending.states); if (m_pending.size.isEmpty()) { // An empty size in the configure means it's up to the client to choose the size @@ -105,8 +99,6 @@ void QWaylandXdgSurface::Toplevel::applyConfigure() m_xdgSurface->m_window->resizeFromApplyConfigure(m_pending.size); } - m_xdgSurface->setSizeHints(); - m_applied = m_pending; qCDebug(lcQpaWayland) << "Applied pending xdg_toplevel configure event:" << m_applied.size << m_applied.states; } @@ -203,12 +195,17 @@ QtWayland::xdg_toplevel::resize_edge QWaylandXdgSurface::Toplevel::convertToResi | ((edges & Qt::RightEdge) ? resize_edge_right : 0)); } -QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parent, +QWaylandXdgSurface::Popup::Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, QtWayland::xdg_positioner *positioner) - : xdg_popup(xdgSurface->get_popup(parent->object(), positioner->object())) - , m_xdgSurface(xdgSurface) + : m_xdgSurface(xdgSurface) + , m_parentXdgSurface(qobject_cast(parent->shellSurface())) , m_parent(parent) { + + init(xdgSurface->get_popup(m_parentXdgSurface ? m_parentXdgSurface->object() : nullptr, positioner->object())); + if (m_parent) { + m_parent->addChildPopup(m_xdgSurface->window()); + } } QWaylandXdgSurface::Popup::~Popup() @@ -216,10 +213,14 @@ QWaylandXdgSurface::Popup::~Popup() if (isInitialized()) destroy(); + if (m_parent) { + m_parent->removeChildPopup(m_xdgSurface->window()); + } + if (m_grabbing) { auto *shell = m_xdgSurface->m_shell; Q_ASSERT(shell->m_topmostGrabbingPopup == this); - shell->m_topmostGrabbingPopup = m_parent->m_popup; + shell->m_topmostGrabbingPopup = m_parentXdgSurface ? m_parentXdgSurface->m_popup : nullptr; m_grabbing = false; // Synthesize Qt enter/leave events for popup @@ -267,6 +268,7 @@ QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, ::xdg_surface *s m_toplevel->set_parent(parentXdgSurface->m_toplevel->object()); } } + setSizeHints(); } QWaylandXdgSurface::~QWaylandXdgSurface() @@ -365,9 +367,6 @@ bool QWaylandXdgSurface::wantsDecorations() const void QWaylandXdgSurface::propagateSizeHints() { setSizeHints(); - - if (m_toplevel && m_window) - m_window->commit(); } void QWaylandXdgSurface::setWindowGeometry(const QRect &rect) @@ -382,10 +381,10 @@ void QWaylandXdgSurface::setSizeHints() const int minHeight = qMax(0, m_window->windowMinimumSize().height()); m_toplevel->set_min_size(minWidth, minHeight); - int maxWidth = qMax(0, m_window->windowMaximumSize().width()); + int maxWidth = qMax(minWidth, m_window->windowMaximumSize().width()); if (maxWidth == QWINDOWSIZE_MAX) maxWidth = 0; - int maxHeight = qMax(0, m_window->windowMaximumSize().height()); + int maxHeight = qMax(minHeight, m_window->windowMaximumSize().height()); if (maxHeight == QWINDOWSIZE_MAX) maxHeight = 0; m_toplevel->set_max_size(maxWidth, maxHeight); @@ -410,8 +409,6 @@ void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) { Q_ASSERT(!m_toplevel && !m_popup); - auto parentXdgSurface = static_cast(parent->shellSurface()); - auto positioner = new QtWayland::xdg_positioner(m_shell->create_positioner()); // set_popup expects a position relative to the parent QPoint transientPos = m_window->geometry().topLeft(); // this is absolute @@ -425,11 +422,10 @@ void QWaylandXdgSurface::setPopup(QWaylandWindow *parent) positioner->set_gravity(QtWayland::xdg_positioner::gravity_bottom_right); positioner->set_size(m_window->geometry().width(), m_window->geometry().height()); positioner->set_constraint_adjustment(QtWayland::xdg_positioner::constraint_adjustment_slide_x - | QtWayland::xdg_positioner::constraint_adjustment_slide_y - | QtWayland::xdg_positioner::constraint_adjustment_flip_x - | QtWayland::xdg_positioner::constraint_adjustment_flip_y); - m_popup = new Popup(this, parentXdgSurface, positioner); + | QtWayland::xdg_positioner::constraint_adjustment_slide_y); + m_popup = new Popup(this, parent, positioner); positioner->destroy(); + delete positioner; } diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h index 96785205..4b518f0a 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h +++ b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshell_p.h @@ -131,14 +131,15 @@ private: class Popup : public QtWayland::xdg_popup { public: - Popup(QWaylandXdgSurface *xdgSurface, QWaylandXdgSurface *parent, QtWayland::xdg_positioner *positioner); + Popup(QWaylandXdgSurface *xdgSurface, QWaylandWindow *parent, QtWayland::xdg_positioner *positioner); ~Popup() override; void grab(QWaylandInputDevice *seat, uint serial); void xdg_popup_popup_done() override; QWaylandXdgSurface *m_xdgSurface = nullptr; - QWaylandXdgSurface *m_parent = nullptr; + QWaylandXdgSurface *m_parentXdgSurface = nullptr; + QWaylandWindow *m_parent = nullptr; bool m_grabbing = false; }; diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp index 8769d971..da0dd6a7 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp +++ b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration.cpp @@ -69,20 +69,6 @@ QWaylandShellSurface *QWaylandXdgShellIntegration::createShellSurface(QWaylandWi return m_xdgShell->getXdgSurface(window); } -void QWaylandXdgShellIntegration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) -{ - if (newFocus) { - auto *xdgSurface = qobject_cast(newFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowActivated(newFocus); - } - if (oldFocus && qobject_cast(oldFocus->shellSurface())) { - auto *xdgSurface = qobject_cast(oldFocus->shellSurface()); - if (xdgSurface && !xdgSurface->handlesActiveState()) - m_display->handleWindowDeactivated(oldFocus); - } -} - } QT_END_NAMESPACE diff --git a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h index b6caa6c9..2f929f98 100644 --- a/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h +++ b/qtwayland/src/plugins/shellintegration/xdg-shell/qwaylandxdgshellintegration_p.h @@ -65,7 +65,6 @@ public: QWaylandXdgShellIntegration() {} bool initialize(QWaylandDisplay *display) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; - void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override; private: QScopedPointer m_xdgShell; diff --git a/qtwayland/src/shared/qwaylandmimehelper.cpp b/qtwayland/src/shared/qwaylandmimehelper.cpp index c5266ab3..e2fe1928 100644 --- a/qtwayland/src/shared/qwaylandmimehelper.cpp +++ b/qtwayland/src/shared/qwaylandmimehelper.cpp @@ -60,7 +60,7 @@ QByteArray QWaylandMimeHelper::getByteArray(QMimeData *mimeData, const QString & buf.open(QIODevice::ReadWrite); QByteArray fmt = "BMP"; if (mimeType.startsWith(QLatin1String("image/"))) { - QByteArray imgFmt = mimeType.mid(6).toUpper().toLatin1(); + QByteArray imgFmt = mimeType.mid(6).toLower().toLatin1(); if (QImageWriter::supportedImageFormats().contains(imgFmt)) fmt = imgFmt; } diff --git a/qtwayland/tests/auto/client/datadevicev1/tst_datadevicev1.cpp b/qtwayland/tests/auto/client/datadevicev1/tst_datadevicev1.cpp index 1568b3b9..067410d0 100644 --- a/qtwayland/tests/auto/client/datadevicev1/tst_datadevicev1.cpp +++ b/qtwayland/tests/auto/client/datadevicev1/tst_datadevicev1.cpp @@ -35,7 +35,7 @@ using namespace MockCompositor; -constexpr int dataDeviceVersion = 1; +constexpr int dataDeviceVersion = 3; class DataDeviceCompositor : public DefaultCompositor { public: diff --git a/qtwayland/tests/auto/client/seatv5/tst_seatv5.cpp b/qtwayland/tests/auto/client/seatv5/tst_seatv5.cpp index 9312c2e5..2ea382f1 100644 --- a/qtwayland/tests/auto/client/seatv5/tst_seatv5.cpp +++ b/qtwayland/tests/auto/client/seatv5/tst_seatv5.cpp @@ -73,6 +73,7 @@ private slots: void multiTouch(); void multiTouchUpAndMotionFrame(); void tapAndMoveInSameFrame(); + void cancelTouch(); }; void tst_seatv5::bindsToSeat() @@ -646,5 +647,34 @@ void tst_seatv5::tapAndMoveInSameFrame() QTRY_COMPARE(window.m_events.last().touchPoints.first().state(), Qt::TouchPointState::TouchPointReleased); } +void tst_seatv5::cancelTouch() +{ + TouchWindow window; + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + + exec([=] { + auto *t = touch(); + auto *c = client(); + t->sendDown(xdgToplevel()->surface(), {32, 32}, 1); + t->sendFrame(c); + t->sendCancel(c); + t->sendFrame(c); + }); + + QTRY_VERIFY(!window.m_events.empty()); + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchBegin); + QCOMPARE(e.touchPointStates, Qt::TouchPointPressed); + QCOMPARE(e.touchPoints.length(), 1); + QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top())); + } + { + auto e = window.m_events.takeFirst(); + QCOMPARE(e.type, QEvent::TouchCancel); + QCOMPARE(e.touchPoints.length(), 0); + } +} + QCOMPOSITOR_TEST_MAIN(tst_seatv5) #include "tst_seatv5.moc" diff --git a/qtwayland/tests/auto/client/shared/coreprotocol.cpp b/qtwayland/tests/auto/client/shared/coreprotocol.cpp index 0d988521..53e12291 100644 --- a/qtwayland/tests/auto/client/shared/coreprotocol.cpp +++ b/qtwayland/tests/auto/client/shared/coreprotocol.cpp @@ -185,6 +185,8 @@ void Output::output_bind_resource(QtWaylandServer::wl_output::Resource *resource if (m_version >= WL_OUTPUT_DONE_SINCE_VERSION) wl_output::send_done(resource->handle); + + Q_EMIT outputBound(resource); } // Seat stuff @@ -451,6 +453,13 @@ void Touch::sendFrame(wl_client *client) send_frame(r->handle); } +void Touch::sendCancel(wl_client *client) +{ + const auto touchResources = resourceMap().values(client); + for (auto *r : touchResources) + send_cancel(r->handle); +} + uint Keyboard::sendEnter(Surface *surface) { auto serial = m_seat->m_compositor->nextSerial(); diff --git a/qtwayland/tests/auto/client/shared/coreprotocol.h b/qtwayland/tests/auto/client/shared/coreprotocol.h index a1af137a..00c439e1 100644 --- a/qtwayland/tests/auto/client/shared/coreprotocol.h +++ b/qtwayland/tests/auto/client/shared/coreprotocol.h @@ -158,7 +158,7 @@ class WlCompositor : public Global, public QtWaylandServer::wl_compositor { Q_OBJECT public: - explicit WlCompositor(CoreCompositor *compositor, int version = 3) + explicit WlCompositor(CoreCompositor *compositor, int version = 4) : QtWaylandServer::wl_compositor(compositor->m_display, version) , m_compositor(compositor) {} @@ -273,6 +273,9 @@ public: OutputData m_data; int m_version = 1; // TODO: remove on libwayland upgrade +Q_SIGNALS: + void outputBound(Resource *resource); + protected: void output_bind_resource(Resource *resource) override; }; @@ -364,6 +367,7 @@ public: uint sendUp(wl_client *client, int id); void sendMotion(wl_client *client, const QPointF &position, int id); void sendFrame(wl_client *client); + void sendCancel(wl_client *client); Seat *m_seat = nullptr; }; diff --git a/qtwayland/tests/auto/client/shared_old/mockcompositor.cpp b/qtwayland/tests/auto/client/shared_old/mockcompositor.cpp index a415cbf5..b1d3d07d 100644 --- a/qtwayland/tests/auto/client/shared_old/mockcompositor.cpp +++ b/qtwayland/tests/auto/client/shared_old/mockcompositor.cpp @@ -342,7 +342,7 @@ Compositor::Compositor(MockCompositor *mockCompositor) exit(EXIT_FAILURE); } - wl_global_create(m_display, &wl_compositor_interface, 1, this, bindCompositor); + wl_global_create(m_display, &wl_compositor_interface, 4, this, bindCompositor); m_data_device_manager.reset(new DataDeviceManager(this, m_display)); diff --git a/qtwayland/tests/auto/client/shared_old/mocksurface.cpp b/qtwayland/tests/auto/client/shared_old/mocksurface.cpp index e9df5f90..c3246e4a 100644 --- a/qtwayland/tests/auto/client/shared_old/mocksurface.cpp +++ b/qtwayland/tests/auto/client/shared_old/mocksurface.cpp @@ -125,6 +125,16 @@ void Surface::surface_damage(Resource *resource, Q_UNUSED(height); } +void Surface::surface_damage_buffer(Resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(resource); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(width); + Q_UNUSED(height); +} + void Surface::surface_frame(Resource *resource, uint32_t callback) { diff --git a/qtwayland/tests/auto/client/shared_old/mocksurface.h b/qtwayland/tests/auto/client/shared_old/mocksurface.h index 949dc23d..d176837e 100644 --- a/qtwayland/tests/auto/client/shared_old/mocksurface.h +++ b/qtwayland/tests/auto/client/shared_old/mocksurface.h @@ -65,6 +65,8 @@ protected: struct wl_resource *buffer, int x, int y) override; void surface_damage(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) override; + void surface_damage_buffer(Resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) override; void surface_frame(Resource *resource, uint32_t callback) override; void surface_commit(Resource *resource) override; diff --git a/qtwayland/tests/auto/client/xdgoutput/tst_xdgoutput.cpp b/qtwayland/tests/auto/client/xdgoutput/tst_xdgoutput.cpp index 80429608..68e8d77a 100644 --- a/qtwayland/tests/auto/client/xdgoutput/tst_xdgoutput.cpp +++ b/qtwayland/tests/auto/client/xdgoutput/tst_xdgoutput.cpp @@ -55,6 +55,7 @@ private slots: void primaryScreen(); void overrideGeometry(); void changeGeometry(); + void outputCreateEnterRace(); }; void tst_xdgoutput::cleanup() @@ -134,5 +135,39 @@ void tst_xdgoutput::changeGeometry() exec([=] { remove(output(1)); }); } +void tst_xdgoutput::outputCreateEnterRace() +{ + m_config.autoConfigure = true; + m_config.autoEnter = false; + QRasterWindow window; + QSignalSpy screenChanged(&window, &QWindow::screenChanged); + window.resize(400, 320); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial); + exec([=] { xdgToplevel()->surface()->sendEnter(output(0));}); + + QTRY_COMPARE(QGuiApplication::screens().size(), 1); + QScreen *primaryScreen = QGuiApplication::screens().first(); + QCOMPARE(window.screen(), primaryScreen); + + auto *out = exec([=] { + return add(); + }); + + // In Compositor Thread + connect(out, &Output::outputBound, this, [this](QtWaylandServer::wl_output::Resource *resource){ + auto surface = xdgToplevel()->surface(); + surface->sendLeave(output(0)); + surface->QtWaylandServer::wl_surface::send_enter(surface->resource()->handle, resource->handle); + }, Qt::DirectConnection); + + QTRY_COMPARE(QGuiApplication::screens().size(), 2); + QTRY_COMPARE(window.screen(), QGuiApplication::screens()[1]); + + exec([=] { remove(out); }); + m_config.autoConfigure = false; + m_config.autoEnter = true; +} + QCOMPOSITOR_TEST_MAIN(tst_xdgoutput) #include "tst_xdgoutput.moc" diff --git a/qtwayland/tests/auto/client/xdgshell/tst_xdgshell.cpp b/qtwayland/tests/auto/client/xdgshell/tst_xdgshell.cpp index 2277bbb8..afbeef53 100644 --- a/qtwayland/tests/auto/client/xdgshell/tst_xdgshell.cpp +++ b/qtwayland/tests/auto/client/xdgshell/tst_xdgshell.cpp @@ -31,6 +31,7 @@ #include #include #include +#include using namespace MockCompositor; @@ -45,6 +46,7 @@ private slots: void configureStates(); void popup(); void tooltipOnPopup(); + void tooltipAndSiblingPopup(); void switchPopups(); void hidePopupParent(); void pongs(); @@ -138,6 +140,7 @@ void tst_xdgshell::configureSize() void tst_xdgshell::configureStates() { + QVERIFY(qputenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT", "0")); QRasterWindow window; window.resize(64, 48); window.show(); @@ -154,9 +157,12 @@ void tst_xdgshell::configureStates() // Toplevel windows don't know their position on xdg-shell // QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled -// QEXPECT_FAIL("", "configure has already been acked, we shouldn't have to wait for isActive", Continue); -// QVERIFY(window.isActive()); - QTRY_VERIFY(window.isActive()); // Just make sure it eventually get's set correctly + // window.windowstate() is driven by keyboard focus, however for decorations we want to follow + // XDGShell this is internal to QtWayland so it is queried directly + auto waylandWindow = static_cast(window.handle()); + Q_ASSERT(waylandWindow); + QTRY_VERIFY(waylandWindow->windowStates().testFlag( + Qt::WindowActive)); // Just make sure it eventually get's set correctly const QSize screenSize(640, 480); const uint maximizedSerial = exec([=] { @@ -186,6 +192,7 @@ void tst_xdgshell::configureStates() QCOMPARE(window.windowStates(), Qt::WindowNoState); QCOMPARE(window.frameGeometry().size(), windowedSize); // QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled + QVERIFY(qunsetenv("QT_WAYLAND_FRAME_CALLBACK_TIMEOUT")); } void tst_xdgshell::popup() @@ -340,6 +347,92 @@ void tst_xdgshell::tooltipOnPopup() QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); } +void tst_xdgshell::tooltipAndSiblingPopup() +{ + class ToolTip : public QRasterWindow { + public: + explicit ToolTip(QWindow *parent) { + setTransientParent(parent); + setFlags(Qt::ToolTip); + resize(100, 100); + show(); + } + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_popup = new QRasterWindow; + m_popup->setTransientParent(transientParent()); + m_popup->setFlags(Qt::Popup); + m_popup->resize(100, 100); + m_popup->show(); + } + + QRasterWindow *m_popup = nullptr; + }; + + class Window : public QRasterWindow { + public: + void mousePressEvent(QMouseEvent *event) override { + QRasterWindow::mousePressEvent(event); + m_tooltip = new ToolTip(this); + } + ToolTip *m_tooltip = nullptr; + }; + + Window window; + window.resize(200, 200); + window.show(); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial); + + exec([=] { + auto *surface = xdgToplevel()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + p->sendLeave(surface); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup()); + exec([=] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(!xdgPopup()->m_grabbed); + + exec([=] { + auto *surface = xdgPopup()->surface(); + auto *p = pointer(); + auto *c = client(); + p->sendEnter(surface, {100, 100}); + p->sendFrame(c); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); + p->sendButton(client(), BTN_LEFT, Pointer::button_state_released); + p->sendFrame(c); + }); + + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)); + exec([=] { xdgPopup(1)->sendCompleteConfigure(QRect(100, 100, 100, 100)); }); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_xdgSurface->m_committedConfigureSerial); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(1)->m_grabbed); + + // Close the middle tooltip (it should not close the sibling popup) + window.m_tooltip->close(); + + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + // Verify the remaining xdg surface is a grab popup.. + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)); + QCOMPOSITOR_TRY_VERIFY(xdgPopup(0)->m_grabbed); + + window.m_tooltip->m_popup->close(); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(1), nullptr); + QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr); +} + // QTBUG-65680 void tst_xdgshell::switchPopups() { @@ -505,15 +598,17 @@ void tst_xdgshell::minMaxSize() window.show(); QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); - exec([=] { xdgToplevel()->sendCompleteConfigure(); }); + // we don't roundtrip with our configuration the initial commit should be correct QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, QSize(100, 100)); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, QSize(1000, 1000)); window.setMaximumSize(QSize(500, 400)); + window.update(); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.maxSize, QSize(500, 400)); window.setMinimumSize(QSize(50, 40)); + window.update(); QCOMPOSITOR_TRY_COMPARE(xdgToplevel()->m_committed.minSize, QSize(50, 40)); } Submodule qtwebchannel 48a520c1..f84887c1: diff --git a/qtwebchannel/src/webchannel/qmetaobjectpublisher.cpp b/qtwebchannel/src/webchannel/qmetaobjectpublisher.cpp index 536eb5c..898d769 100644 --- a/qtwebchannel/src/webchannel/qmetaobjectpublisher.cpp +++ b/qtwebchannel/src/webchannel/qmetaobjectpublisher.cpp @@ -186,8 +186,6 @@ Q_DECLARE_TYPEINFO(OverloadResolutionCandidate, Q_MOVABLE_TYPE); QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) : QObject(webChannel) , webChannel(webChannel) - , signalHandler(this) - , clientIsIdle(false) , blockUpdates(false) , propertyUpdatesInitialized(false) { @@ -301,17 +299,17 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWeb return data; } -void QMetaObjectPublisher::setClientIsIdle(bool isIdle) +void QMetaObjectPublisher::setClientIsIdle(bool isIdle, QWebChannelAbstractTransport *transport) { - if (clientIsIdle == isIdle) { - return; - } - clientIsIdle = isIdle; - if (!isIdle && timer.isActive()) { - timer.stop(); - } else if (isIdle && !timer.isActive()) { - timer.start(PROPERTY_UPDATE_INTERVAL, this); - } + transportState[transport].clientIsIdle = isIdle; + if (isIdle) + sendEnqueuedPropertyUpdates(transport); +} + +bool QMetaObjectPublisher::isClientIdle(QWebChannelAbstractTransport *transport) +{ + auto found = transportState.find(transport); + return found != transportState.end() && found.value().clientIsIdle; } QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport *transport) @@ -333,6 +331,7 @@ QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) { + auto *signalHandler = signalHandlerFor(object); foreach (const QJsonValue &propertyInfoVar, objectInfo[KEY_PROPERTIES].toArray()) { const QJsonArray &propertyInfo = propertyInfoVar.toArray(); if (propertyInfo.size() < 2) { @@ -353,19 +352,19 @@ void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object // Only connect for a property update once if (connectedProperties.isEmpty()) { - signalHandler.connectTo(object, signalIndex); + signalHandler->connectTo(object, signalIndex); } connectedProperties.insert(propertyIndex); } // also always connect to destroyed signal - signalHandler.connectTo(object, s_destroyedSignalIndex); + signalHandler->connectTo(object, s_destroyedSignalIndex); } void QMetaObjectPublisher::sendPendingPropertyUpdates() { - if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) { + if (blockUpdates) { return; } @@ -415,18 +414,19 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates() // data does not contain specific updates if (!data.isEmpty()) { - setClientIsIdle(false); - message[KEY_DATA] = data; - broadcastMessage(message); + enqueueBroadcastMessage(message); } // send every property update which is not supposed to be broadcasted const QHash::const_iterator suend = specificUpdates.constEnd(); for (QHash::const_iterator it = specificUpdates.constBegin(); it != suend; ++it) { message[KEY_DATA] = it.value(); - it.key()->sendMessage(message); + enqueueMessage(message, it.key()); } + + for (auto state = transportState.begin(); state != transportState.end(); ++state) + sendEnqueuedPropertyUpdates(state.key()); } QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QMetaMethod &method, @@ -572,7 +572,7 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal } } else { pendingPropertyUpdates[object][signalIndex] = arguments; - if (clientIsIdle && !blockUpdates && !timer.isActive()) { + if (!blockUpdates && !timer.isActive()) { timer.start(PROPERTY_UPDATE_INTERVAL, this); } } @@ -590,7 +590,7 @@ void QMetaObjectPublisher::objectDestroyed(const QObject *object) // only remove from handler when we initialized the property updates // cf: https://bugreports.qt.io/browse/QTBUG-60250 if (propertyUpdatesInitialized) { - signalHandler.remove(object); + signalHandlerFor(object)->remove(object); signalToPropertyMap.remove(object); } pendingPropertyUpdates.remove(object); @@ -852,6 +852,52 @@ void QMetaObjectPublisher::broadcastMessage(const QJsonObject &message) const } } +void QMetaObjectPublisher::enqueueBroadcastMessage(const QJsonObject &message) +{ + if (webChannel->d_func()->transports.isEmpty()) { + qWarning("QWebChannel is not connected to any transports, cannot send message: %s", + QJsonDocument(message).toJson().constData()); + return; + } + + for (auto *transport : webChannel->d_func()->transports) { + auto &state = transportState[transport]; + state.queuedMessages.append(message); + } +} + +void QMetaObjectPublisher::enqueueMessage(const QJsonObject &message, + QWebChannelAbstractTransport *transport) +{ + auto &state = transportState[transport]; + state.queuedMessages.append(message); +} + +void QMetaObjectPublisher::sendEnqueuedPropertyUpdates(QWebChannelAbstractTransport *transport) +{ + auto found = transportState.find(transport); + if (found != transportState.end() && found.value().clientIsIdle + && !found.value().queuedMessages.isEmpty()) { + + // If the client is connected with an in-process transport, it can + // happen that a message triggers a subsequent property change. In + // that case, we need to ensure that the queued messages have already + // been cleared; otherwise the recursive call will send everythig again. + // Case in point: The qmlwebchannel tests fail if we don't clear the + // queued messages before sending them out. + // For that same reason set the client to "busy" (aka non-idle) just + // right before sending out the messages; otherwise a potential + // "Idle" type message will not correctly restore the Idle state. + const auto messages = std::move(found.value().queuedMessages); + Q_ASSERT(found.value().queuedMessages.isEmpty()); + found.value().clientIsIdle = false; + + for (const auto &message : messages) { + transport->sendMessage(message); + } + } +} + void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport) { if (!webChannel->d_func()->transports.contains(transport)) { @@ -866,7 +912,7 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel const MessageType type = toType(message.value(KEY_TYPE)); if (type == TypeIdle) { - setClientIsIdle(true); + setClientIsIdle(true, transport); } else if (type == TypeInit) { if (!message.contains(KEY_ID)) { qWarning("JSON message object is missing the id property: %s", @@ -913,9 +959,9 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel return; transport->sendMessage(createResponse(message.value(KEY_ID), wrapResult(result, transport))); } else if (type == TypeConnectToSignal) { - signalHandler.connectTo(object, message.value(KEY_SIGNAL).toInt(-1)); + signalHandlerFor(object)->connectTo(object, message.value(KEY_SIGNAL).toInt(-1)); } else if (type == TypeDisconnectFromSignal) { - signalHandler.disconnectFrom(object, message.value(KEY_SIGNAL).toInt(-1)); + signalHandlerFor(object)->disconnectFrom(object, message.value(KEY_SIGNAL).toInt(-1)); } else if (type == TypeSetProperty) { setProperty(object, message.value(KEY_PROPERTY).toInt(-1), message.value(KEY_VALUE)); @@ -931,6 +977,7 @@ void QMetaObjectPublisher::setBlockUpdates(bool block) blockUpdates = block; if (!blockUpdates) { + timer.start(PROPERTY_UPDATE_INTERVAL, this); sendPendingPropertyUpdates(); } else if (timer.isActive()) { timer.stop(); @@ -948,4 +995,15 @@ void QMetaObjectPublisher::timerEvent(QTimerEvent *event) } } +SignalHandler *QMetaObjectPublisher::signalHandlerFor(const QObject *object) +{ + auto thread = object->thread(); + auto it = signalHandlers.find(thread); + if (it == signalHandlers.end()) { + it = signalHandlers.emplace(thread, this).first; + it->second.moveToThread(thread); + } + return &it->second; +} + QT_END_NAMESPACE diff --git a/qtwebchannel/src/webchannel/qmetaobjectpublisher_p.h b/qtwebchannel/src/webchannel/qmetaobjectpublisher_p.h index bbd9875..4713ef1 100644 --- a/qtwebchannel/src/webchannel/qmetaobjectpublisher_p.h +++ b/qtwebchannel/src/webchannel/qmetaobjectpublisher_p.h @@ -59,6 +59,9 @@ #include #include #include +#include + +#include #include "qwebchannelglobal.h" @@ -109,17 +112,36 @@ public: */ void broadcastMessage(const QJsonObject &message) const; + /** + * Enqueue the given @p message to all known transports. + */ + void enqueueBroadcastMessage(const QJsonObject &message); + + /** + * Enqueue the given @p message to @p transport. + */ + void enqueueMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport); + + /** + * If client for given @p transport is idle, send queued messaged to @p transport and then mark + * the client as not idle. + */ + void sendEnqueuedPropertyUpdates(QWebChannelAbstractTransport *transport); + /** * Serialize the QMetaObject of @p object and return it in JSON form. */ QJsonObject classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport); /** - * Set the client to idle or busy, based on the value of @p isIdle. - * - * When the value changed, start/stop the property update timer accordingly. + * Set the client to idle or busy for a single @p transport, based on the value of @p isIdle. + */ + void setClientIsIdle(bool isIdle, QWebChannelAbstractTransport *transport); + + /** + * Check that client is idle for @p transport. */ - void setClientIsIdle(bool isIdle); + bool isClientIdle(QWebChannelAbstractTransport *transport); /** * Initialize clients by sending them the class information of the registered objects. @@ -272,10 +294,18 @@ private: friend class TestWebChannel; QWebChannel *webChannel; - SignalHandler signalHandler; + std::unordered_map> signalHandlers; + SignalHandler *signalHandlerFor(const QObject *object); - // true when the client is idle, false otherwise - bool clientIsIdle; + struct TransportState + { + TransportState() : clientIsIdle(false) { } + // true when the client is idle, false otherwise + bool clientIsIdle; + // messages to send + QQueue queuedMessages; + }; + QHash transportState; // true when no property updates should be sent, false otherwise bool blockUpdates; diff --git a/qtwebchannel/src/webchannel/signalhandler_p.h b/qtwebchannel/src/webchannel/signalhandler_p.h index 27afadb..d77373c 100644 --- a/qtwebchannel/src/webchannel/signalhandler_p.h +++ b/qtwebchannel/src/webchannel/signalhandler_p.h @@ -56,6 +56,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -71,6 +72,7 @@ static const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMetho template class SignalHandler : public QObject { + Q_DISABLE_COPY(SignalHandler) public: SignalHandler(Receiver *receiver, QObject *parent = 0); @@ -268,6 +270,7 @@ int SignalHandler::qt_metacall(QMetaObject::Call call, int methodId, v if (call == QMetaObject::InvokeMetaMethod) { const QObject *object = sender(); Q_ASSERT(object); + Q_ASSERT(QThread::currentThread() == object->thread()); Q_ASSERT(senderSignalIndex() == methodId); Q_ASSERT(m_connectionsCounter.contains(object)); Q_ASSERT(m_connectionsCounter.value(object).contains(methodId)); diff --git a/qtwebchannel/tests/auto/qml/testwebchannel.cpp b/qtwebchannel/tests/auto/qml/testwebchannel.cpp index 9891687..3ca81c2 100644 --- a/qtwebchannel/tests/auto/qml/testwebchannel.cpp +++ b/qtwebchannel/tests/auto/qml/testwebchannel.cpp @@ -46,7 +46,11 @@ TestWebChannel::~TestWebChannel() bool TestWebChannel::clientIsIdle() const { - return QWebChannel::d_func()->publisher->clientIsIdle; + for (auto *transport : QWebChannel::d_func()->transports) { + if (QWebChannel::d_func()->publisher->isClientIdle(transport)) + return true; + } + return false; } QT_END_NAMESPACE diff --git a/qtwebchannel/tests/auto/webchannel/tst_webchannel.cpp b/qtwebchannel/tests/auto/webchannel/tst_webchannel.cpp index 181da9e..37f989a 100644 --- a/qtwebchannel/tests/auto/webchannel/tst_webchannel.cpp +++ b/qtwebchannel/tests/auto/webchannel/tst_webchannel.cpp @@ -785,7 +785,7 @@ void TestWebChannel::testTransportWrapObjectProperties() DummyTransport *dummyTransport = new DummyTransport(this); channel.connectTo(dummyTransport); channel.d_func()->publisher->initializeClient(dummyTransport); - channel.d_func()->publisher->setClientIsIdle(true); + channel.d_func()->publisher->setClientIsIdle(true, dummyTransport); QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.size(), 0); @@ -943,8 +943,6 @@ void TestWebChannel::testInfiniteRecursion() void TestWebChannel::testAsyncObject() { - QSKIP("This test is broken. See QTBUG-80729"); - QWebChannel channel; channel.connectTo(m_dummyTransport); @@ -990,6 +988,50 @@ void TestWebChannel::testAsyncObject() thread.wait(); } +void TestWebChannel::testPropertyMultipleTransports() +{ + DummyTransport transport1; + DummyTransport transport2; + + QWebChannel channel; + QMetaObjectPublisher *publisher = channel.d_func()->publisher; + + TestObject testObj; + testObj.setObjectName("testObject"); + channel.registerObject(testObj.objectName(), &testObj); + channel.connectTo(&transport1); + channel.connectTo(&transport2); + + testObj.setProp("Hello"); + + publisher->initializeClient(&transport1); + publisher->initializeClient(&transport2); + publisher->setClientIsIdle(true, &transport1); + QCOMPARE(publisher->isClientIdle(&transport1), true); + QCOMPARE(publisher->isClientIdle(&transport2), false); + QVERIFY(transport1.messagesSent().isEmpty()); + QVERIFY(transport2.messagesSent().isEmpty()); + + testObj.setProp("World"); + QTRY_COMPARE_WITH_TIMEOUT(transport1.messagesSent().size(), 1u, 2000); + QCOMPARE(transport2.messagesSent().size(), 0u); + publisher->setClientIsIdle(true, &transport2); + QTRY_COMPARE_WITH_TIMEOUT(transport2.messagesSent().size(), 1u, 2000); + QCOMPARE(publisher->isClientIdle(&transport1), false); + QCOMPARE(publisher->isClientIdle(&transport2), false); + + testObj.setProp("!!!"); + publisher->setClientIsIdle(true, &transport2); + QCOMPARE(publisher->isClientIdle(&transport2), true); + QCOMPARE(publisher->isClientIdle(&transport1), false); + QTRY_COMPARE_WITH_TIMEOUT(transport2.messagesSent().size(), 2u, 2000); + QCOMPARE(transport1.messagesSent().size(), 1u); + publisher->setClientIsIdle(true, &transport1); + QTRY_COMPARE_WITH_TIMEOUT(transport1.messagesSent().size(), 2u, 2000); + QCOMPARE(publisher->isClientIdle(&transport1), false); + QCOMPARE(publisher->isClientIdle(&transport2), false); +} + class FunctionWrapper : public QObject { Q_OBJECT @@ -1082,7 +1124,7 @@ void TestWebChannel::benchInitializeClients() publisher->propertyUpdatesInitialized = false; publisher->signalToPropertyMap.clear(); - publisher->signalHandler.clear(); + publisher->signalHandlers.clear(); } } @@ -1107,7 +1149,7 @@ void TestWebChannel::benchPropertyUpdates() obj->change(); } - channel.d_func()->publisher->clientIsIdle = true; + channel.d_func()->publisher->setClientIsIdle(true, m_dummyTransport); channel.d_func()->publisher->sendPendingPropertyUpdates(); } } diff --git a/qtwebchannel/tests/auto/webchannel/tst_webchannel.h b/qtwebchannel/tests/auto/webchannel/tst_webchannel.h index eae21f4..dd4e690 100644 --- a/qtwebchannel/tests/auto/webchannel/tst_webchannel.h +++ b/qtwebchannel/tests/auto/webchannel/tst_webchannel.h @@ -348,6 +348,7 @@ private slots: void testJsonToVariant(); void testInfiniteRecursion(); void testAsyncObject(); + void testPropertyMultipleTransports(); void testDeletionDuringMethodInvocation_data(); void testDeletionDuringMethodInvocation(); Submodule qtwebsockets 50f3853a..d41bb9f4: diff --git a/qtwebsockets/src/websockets/qwebsocket_p.cpp b/qtwebsockets/src/websockets/qwebsocket_p.cpp index cf3087f..0dd0fa6 100644 --- a/qtwebsockets/src/websockets/qwebsocket_p.cpp +++ b/qtwebsockets/src/websockets/qwebsocket_p.cpp @@ -1100,6 +1100,8 @@ void QWebSocketPrivate::processHandshake(QTcpSocket *pSocket) m_handshakeState = AllDoneState; setErrorString(errorDescription); Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); + if (m_pSocket->state() != QAbstractSocket::UnconnectedState) + m_pSocket->disconnectFromHost(); } } diff --git a/qtwebsockets/src/websockets/qwebsocketdataprocessor.cpp b/qtwebsockets/src/websockets/qwebsocketdataprocessor.cpp index 2affdd5..95f1194 100644 --- a/qtwebsockets/src/websockets/qwebsocketdataprocessor.cpp +++ b/qtwebsockets/src/websockets/qwebsocketdataprocessor.cpp @@ -273,6 +273,7 @@ void QWebSocketDataProcessor::clear() if (!m_pConverterState) m_pConverterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull | QTextCodec::IgnoreHeader); + frame.clear(); } /*!