summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/jar/build.gradle2
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/Qt6AndroidGradleHelpers.cmake2
-rw-r--r--src/corelib/doc/images/modelindex-no-parent.svg99
-rw-r--r--src/corelib/doc/src/cmake/cmake-properties.qdoc4
-rw-r--r--src/corelib/itemmodels/qabstractitemmodel.cpp2
-rw-r--r--src/corelib/kernel/qeventdispatcher_wasm.cpp12
-rw-r--r--src/corelib/kernel/qeventdispatcher_wasm_p.h2
-rw-r--r--src/corelib/platform/wasm/qwasmlocalfileengine.cpp422
-rw-r--r--src/corelib/platform/wasm/qwasmlocalfileengine_p.h102
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp7
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h10
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess.cpp38
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess_p.h7
-rw-r--r--src/network/android/jar/build.gradle2
-rw-r--r--src/plugins/networkinformation/android/jar/build.gradle2
-rw-r--r--src/plugins/platforms/cocoa/qnsview.mm17
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.cpp79
-rw-r--r--src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp157
-rw-r--r--src/plugins/platforms/wasm/qwasmfiledialoghelper.h49
-rw-r--r--src/plugins/platforms/wasm/qwasmtheme.cpp13
-rw-r--r--src/plugins/platforms/wasm/qwasmtheme.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp2
-rw-r--r--src/tools/androiddeployqt/main.cpp2
-rw-r--r--src/tools/bootstrap/CMakeLists.txt4
-rw-r--r--src/tools/rcc/rcc.cpp93
-rw-r--r--src/widgets/kernel/qapplication.cpp13
-rw-r--r--src/widgets/styles/qfusionstyle_p_p.h3
30 files changed, 1087 insertions, 65 deletions
diff --git a/src/android/jar/build.gradle b/src/android/jar/build.gradle
index 74ecff6b75f..54512f6f4b3 100644
--- a/src/android/jar/build.gradle
+++ b/src/android/jar/build.gradle
@@ -23,7 +23,7 @@ repositories {
}
android {
- compileSdk 35
+ compileSdk 36
namespace = "org.qtproject.qt.android"
defaultConfig {
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 539ad753ca6..e50838730cb 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1533,6 +1533,7 @@ qt_internal_extend_target(Core CONDITION WASM
platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h
platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h
platform/wasm/qwasmsuspendresumecontrol.cpp platform/wasm/qwasmsuspendresumecontrol_p.h
+ platform/wasm/qwasmlocalfileengine.cpp platform/wasm/qwasmlocalfileengine_p.h
kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h
)
diff --git a/src/corelib/Qt6AndroidGradleHelpers.cmake b/src/corelib/Qt6AndroidGradleHelpers.cmake
index f71bac4e08a..fc8e009b9da 100644
--- a/src/corelib/Qt6AndroidGradleHelpers.cmake
+++ b/src/corelib/Qt6AndroidGradleHelpers.cmake
@@ -193,7 +193,7 @@ function(_qt_internal_android_generate_target_build_gradle target)
QT_ANDROID_MIN_SDK_VERSION "28")
_qt_internal_android_get_gradle_property(target_sdk_version ${target}
- QT_ANDROID_TARGET_SDK_VERSION "34")
+ QT_ANDROID_TARGET_SDK_VERSION "36")
set(target_abis "$<TARGET_PROPERTY:${target},_qt_android_abis>")
set(target_abi_list "$<JOIN:${target_abis};${CMAKE_ANDROID_ARCH_ABI},'$<COMMA> '>")
diff --git a/src/corelib/doc/images/modelindex-no-parent.svg b/src/corelib/doc/images/modelindex-no-parent.svg
new file mode 100644
index 00000000000..4ce4b86b1f2
--- /dev/null
+++ b/src/corelib/doc/images/modelindex-no-parent.svg
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ width="300"
+ height="160"
+ version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+
+<style>
+ svg .line-style { stroke: black; fill: none }
+ svg .fill-style { stroke: black; fill: #c0c0c0 }
+ svg .text-style { font: 12px arial; fill: black }
+ svg .italic-style { font: 12px arial; fill: black; font-style: italic }
+ svg .bold-style { font: 12px arial; fill: black; font-weight: bold }
+
+ svg.dark .line-style { stroke: #f2f2f2; fill: none }
+ svg.dark .fill-style { stroke: #f2f2f2; fill: #606060 }
+ svg.dark .text-style { font: 12px arial; fill: #f2f2f2 }
+ svg.dark .italic-style { font: 12px arial; fill: #f2f2f2; font-style: italic }
+ svg.dark .bold-style { font: 12px arial; fill: #f2f2f2; font-weight: bold }
+
+ [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none }
+ [data-theme="dark"] svg .fill-style { stroke: #f2f2f2; fill: #606060 }
+ [data-theme="dark"] svg .text-style { font: 12px arial; fill: #f2f2f2 }
+ [data-theme="dark"] svg .italic-style { font: 12px arial; fill: #f2f2f2; font-style: italic }
+ [data-theme="dark"] svg .bold-style { font: 12px arial; fill: #f2f2f2; font-weight: bold }
+
+ [data-theme="light"] svg .line-style { stroke: black; fill: none }
+ [data-theme="light"] svg .fill-style { stroke: black; fill: #c0c0c0 }
+ [data-theme="light"] svg .text-style { font: 12px arial; fill: black }
+ [data-theme="light"] svg .italic-style { font: 12px arial; fill: black; font-style: italic }
+ [data-theme="light"] svg .bold-style { font: 12px arial; fill: black; font-weight: bold }
+</style>
+
+<text x="25" y="49" font-family="arial" font-size="12px"
+ class="text-style">0</text>
+<text x="25" y="79" font-family="arial" font-size="12px"
+ class="text-style">1</text>
+<text x="25" y="109" font-family="arial" font-size="12px"
+ class="text-style">2</text>
+<g transform="translate(30,139)">
+<text x="0" y="0" font-family="arial" font-size="12px"
+ class="text-style" transform="rotate(270)">...</text>
+</g>
+
+<path d="m 40.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 70.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 100.5,30.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 130.5,30.5 h 30 v 30" stroke="black" fill="none"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 40.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 70.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 100.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="#c0c0c0"
+ class="fill-style" />
+<path d="m 130.5,60.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 40.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 70.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 100.5,90.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" />
+<path d="m 70.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 40.5,120.5 v 30 h 30" stroke="black" fill="none"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 100.5,150.5 h 30" stroke="black"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 130.5,120.5 h 30 v 30 h -30 z" stroke="black" fill="none"
+ class="line-style" stroke-dasharray="3, 3" />
+<path d="m 160.5,90.5 v 30" stroke="black"
+ class="line-style" stroke-dasharray="3, 3" />
+
+<text x="50" y="20" font-family="arial" font-size="12px"
+ class="text-style">0</text>
+<text x="80" y="20" font-family="arial" font-size="12px"
+ class="text-style">1</text>
+<text x="110" y="20" font-family="arial" font-size="12px"
+ class="text-style">2</text>
+<text x="140" y="20" font-family="arial" font-size="12px"
+ class="text-style">...</text>
+
+<text x="190" y="55" font-family="arial" font-size="12px" font-weight="bold"
+ class="bold-style">Model index</text>
+<rect x="185.5" y="60.5" width="77" height="37" stroke="black" fill="none"
+ class="line-style" />
+<text x="190" y="75" font-family="arial" font-size="12px"
+ class="text-style">row = 1</text>
+<text x="190" y="92" font-family="arial" font-size="12px"
+ class="text-style">column = 2</text>
+
+<path d="m 185.5,85.5 c -35,0 -35,-10 -70,-10" stroke="black" fill="none"
+ stroke-dasharray="2, 2" class="line-style" />
+</svg>
diff --git a/src/corelib/doc/src/cmake/cmake-properties.qdoc b/src/corelib/doc/src/cmake/cmake-properties.qdoc
index c7b1a27a4b4..310debf7edc 100644
--- a/src/corelib/doc/src/cmake/cmake-properties.qdoc
+++ b/src/corelib/doc/src/cmake/cmake-properties.qdoc
@@ -239,7 +239,7 @@ precedence over this CMake property.
\badcode
set_target_properties(${target} PROPERTIES
- QT_ANDROID_COMPILE_SDK_VERSION 35
+ QT_ANDROID_COMPILE_SDK_VERSION 36
)
\endcode
@@ -247,7 +247,7 @@ The following format also works:
\badcode
set_target_properties(${target} PROPERTIES
- QT_ANDROID_COMPILE_SDK_VERSION "android-35"
+ QT_ANDROID_COMPILE_SDK_VERSION "android-36"
)
\endcode
diff --git a/src/corelib/itemmodels/qabstractitemmodel.cpp b/src/corelib/itemmodels/qabstractitemmodel.cpp
index 58a057009ff..8b0a05ee6ed 100644
--- a/src/corelib/itemmodels/qabstractitemmodel.cpp
+++ b/src/corelib/itemmodels/qabstractitemmodel.cpp
@@ -1390,7 +1390,7 @@ void QAbstractItemModel::resetInternalData()
simple table of rows and columns. Each item has a unique index specified by
a QModelIndex.
- \image modelindex-no-parent.png {Screenshot showing a 3x3 grid with numbered
+ \image modelindex-no-parent.svg {Diagram showing a 3x3 grid with numbered
rows and columns that shows the cell at row 1, column 2 highlighted.}
Every item of data that can be accessed via a model has an associated model
diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp
index f7edf3285f8..25a27a0c22b 100644
--- a/src/corelib/kernel/qeventdispatcher_wasm.cpp
+++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp
@@ -150,6 +150,18 @@ bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *e
bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
{
+ // Accept the current event if event handling recurses / event loop is reentered,
+ // to prevent the browser from propagating it to other event handlers.
+ if (useAsyncify() && isMainThreadEventDispatcher()) {
+ auto control = QWasmSuspendResumeControl::get();
+ auto currentEvent = control->currentEvent();
+ if (!currentEvent.isUndefined() && currentEvent.instanceof(emscripten::val::global("Event"))) {
+ currentEvent.call<void>("preventDefault");
+ currentEvent.call<void>("stopPropagation");
+ control->setCurrentEvent(emscripten::val::undefined());
+ }
+ }
+
emit awake();
if (!useAsyncify() && isMainThreadEventDispatcher())
diff --git a/src/corelib/kernel/qeventdispatcher_wasm_p.h b/src/corelib/kernel/qeventdispatcher_wasm_p.h
index 674af85dac2..1946bf63ede 100644
--- a/src/corelib/kernel/qeventdispatcher_wasm_p.h
+++ b/src/corelib/kernel/qeventdispatcher_wasm_p.h
@@ -40,7 +40,6 @@ public:
~QEventDispatcherWasm();
bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
- bool sendAllEvents(QEventLoop::ProcessEventsFlags flag);
void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType,
QObject *object) override final;
@@ -69,6 +68,7 @@ protected:
virtual bool sendPostedEvents();
private:
+ bool sendAllEvents(QEventLoop::ProcessEventsFlags flag);
bool isMainThreadEventDispatcher();
bool isSecondaryThreadEventDispatcher();
bool isValidEventDispatcher();
diff --git a/src/corelib/platform/wasm/qwasmlocalfileengine.cpp b/src/corelib/platform/wasm/qwasmlocalfileengine.cpp
new file mode 100644
index 00000000000..58e662d5fde
--- /dev/null
+++ b/src/corelib/platform/wasm/qwasmlocalfileengine.cpp
@@ -0,0 +1,422 @@
+// Copyright (C) 2025 Qt Group
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qwasmlocalfileengine_p.h"
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+// Custom URL scheme for files handled by this file engine. A complete file URL can
+// look like
+//
+// "weblocalfile:/n/file.ext"
+//
+// where n is a counter to ensure uniqueness, needed since web platform gives us the file name only
+// and does not provide the file path.
+//
+// The scheme may be visible to end users if the application displays it, so
+// we avoid using "wasm" and instead say "web", which should be more recognizable.
+static constexpr QLatin1StringView wasmlocalfileScheme = "weblocalfile"_L1;
+
+// Instantiate the engine, the system will pick it up automatically
+// Never destroy it to avoid problems with static destruction.
+// The OS will reclaim the memory anyway,
+// TODO: cleanup on QApplication destruction and re-init on QApplication re-creation.
+static QWasmFileEngineHandler *singleton = new QWasmFileEngineHandler();
+
+QWasmFileEngineHandler::QWasmFileEngineHandler()
+{
+}
+
+QWasmFileEngineHandler::~QWasmFileEngineHandler()
+{
+}
+
+std::unique_ptr<QAbstractFileEngine> QWasmFileEngineHandler::create(const QString &fileName) const
+{
+ if (!QWasmFileEngineHandler::isWasmFileName(fileName))
+ return {};
+
+ // Check if it's a File or FileSystemFileHandle
+ if (singleton->m_files.contains(fileName)) {
+ qstdweb::File file = singleton->m_files.value(fileName);
+ return std::make_unique<QWasmFileEngine>(fileName, file);
+ } else if (singleton->m_fileSystemFiles.contains(fileName)) {
+ qstdweb::FileSystemFileHandle file = singleton->m_fileSystemFiles.value(fileName);
+ return std::make_unique<QWasmFileEngine>(fileName, file);
+ }
+
+ // Not an error, this function will be called with partial paths like "weblocalfile:/1/"
+ return {};
+}
+
+// Check if this is a wasm filename by checking the URL scheme.
+bool QWasmFileEngineHandler::isWasmFileName(const QString& fileName)
+{
+ return QUrl(fileName).scheme() == wasmlocalfileScheme;
+}
+
+// Creates a wasm filename using the custom URL scheme and a counter.
+QString QWasmFileEngineHandler::makeWasmFileName(const QString &nativeFileName)
+{
+ static std::atomic<uint64_t> sid = 0;
+ const uint64_t id = ++sid;
+ return wasmlocalfileScheme + QStringLiteral(":/%1/%2").arg(id).arg(nativeFileName);
+}
+
+// Extracts the native filename from the custom URL (removes scheme and counter).
+QString QWasmFileEngineHandler::nativeFileName(const QString &wasmFileName)
+{
+ QUrl url(wasmFileName);
+ if (url.scheme() == wasmlocalfileScheme) {
+ QString path = url.path();
+ // Path is "/n/filename", find the second '/' and extract the filename
+ const qsizetype idx = path.indexOf(u'/', 1);
+ if (idx != -1)
+ return path.mid(idx + 1);
+ }
+ return wasmFileName;
+}
+
+// Adds a File to the set of open files. Returns a prefixed wasm file name.
+QString QWasmFileEngineHandler::addFile(qstdweb::File file)
+{
+ QString nativeFileName = QString::fromStdString(file.name());
+ QString wasmFileName = makeWasmFileName(nativeFileName);
+ singleton->m_files.insert(wasmFileName, file);
+ return wasmFileName;
+}
+
+// Adds a FileSystemFileHandle to the set of open files. Returns a prefixed wasm file name.
+QString QWasmFileEngineHandler::addFile(qstdweb::FileSystemFileHandle file)
+{
+ QString nativeFileName = QString::fromStdString(file.name());
+ QString wasmFileName = makeWasmFileName(nativeFileName);
+ singleton->m_fileSystemFiles.insert(wasmFileName, file);
+ return wasmFileName;
+}
+
+// Removes a File or FileSystemFileHandle from the set of open file handlers
+void QWasmFileEngineHandler::removeFile(const QString fileName)
+{
+ singleton->m_files.remove(fileName);
+ singleton->m_fileSystemFiles.remove(fileName);
+}
+
+qstdweb::File QWasmFileEngineHandler::getFile(const QString fileName)
+{
+ return singleton->m_files.value(fileName);
+}
+
+qstdweb::FileSystemFileHandle QWasmFileEngineHandler::getFileSystemFile(const QString fileName)
+{
+ return singleton->m_fileSystemFiles.value(fileName);
+}
+
+/*!
+ \class QWasmFileEngine
+ \brief The QWasmFileEngine class provides a QAbstractFileEngine
+ for files that has the prefix ':weblocalfile/'.
+*/
+
+// Constructs a QWasmFileEngine with a File for read-only access
+QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::File file)
+ : m_fileName(fileName),
+ m_blobDevice(std::make_unique<qstdweb::BlobIODevice>(file.slice(0, file.size())))
+{
+ // TODO use m_blobDevice in unbuffered mode? if there is already a buffer higher up.
+}
+
+// Constructs a QWasmFileEngine with a FileSystemFileHandle for read-write access
+QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::FileSystemFileHandle file)
+ : m_fileName(fileName),
+ m_fileDevice(std::make_unique<qstdweb::FileSystemFileIODevice>(file))
+{
+
+}
+
+QWasmFileEngine::~QWasmFileEngine()
+{
+ close();
+}
+
+bool QWasmFileEngine::open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions)
+{
+ Q_UNUSED(permissions);
+ m_openMode = openMode;
+
+ if (m_fileDevice)
+ return m_fileDevice->open(openMode);
+ else if (m_blobDevice)
+ return m_blobDevice->open(openMode);
+
+ return false;
+}
+
+bool QWasmFileEngine::close()
+{
+ if (m_openMode == QIODevice::NotOpen)
+ return false;
+
+ bool success = true;
+ if (m_fileDevice) {
+ m_fileDevice->close();
+ }
+ if (m_blobDevice) {
+ m_blobDevice->close();
+ }
+
+ m_openMode = QIODevice::NotOpen;
+ return success;
+}
+
+bool QWasmFileEngine::flush()
+{
+ return true;
+}
+
+bool QWasmFileEngine::syncToDisk()
+{
+ return true;
+}
+
+qint64 QWasmFileEngine::size() const
+{
+ if (m_fileDevice)
+ return m_fileDevice->size();
+ if (m_blobDevice)
+ return m_blobDevice->size();
+ return 0;
+}
+
+qint64 QWasmFileEngine::pos() const
+{
+ if (m_fileDevice)
+ return m_fileDevice->pos();
+ if (m_blobDevice)
+ return m_blobDevice->pos();
+ return 0;
+}
+
+bool QWasmFileEngine::seek(qint64 pos)
+{
+ if (m_fileDevice)
+ return m_fileDevice->seek(pos);
+ if (m_blobDevice)
+ return m_blobDevice->seek(pos);
+ return false;
+}
+
+bool QWasmFileEngine::isSequential() const
+{
+ return false;
+}
+
+bool QWasmFileEngine::remove()
+{
+ return false;
+}
+
+bool QWasmFileEngine::copy(const QString &newName)
+{
+ Q_UNUSED(newName);
+ return false;
+}
+
+bool QWasmFileEngine::rename(const QString &newName)
+{
+ Q_UNUSED(newName);
+ return false;
+}
+
+bool QWasmFileEngine::renameOverwrite(const QString &newName)
+{
+ Q_UNUSED(newName);
+ return false;
+}
+
+bool QWasmFileEngine::link(const QString &newName)
+{
+ Q_UNUSED(newName);
+ return false;
+}
+
+bool QWasmFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
+ std::optional<QFile::Permissions> permissions) const
+{
+ Q_UNUSED(dirName);
+ Q_UNUSED(createParentDirectories);
+ Q_UNUSED(permissions);
+ return false;
+}
+
+bool QWasmFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
+{
+ Q_UNUSED(dirName);
+ Q_UNUSED(recurseParentDirectories);
+ return false;
+}
+
+bool QWasmFileEngine::setSize(qint64 size)
+{
+ Q_UNUSED(size);
+ return false;
+}
+
+bool QWasmFileEngine::caseSensitive() const
+{
+ return true;
+}
+
+bool QWasmFileEngine::isRelativePath() const
+{
+ return false;
+}
+
+
+QAbstractFileEngine::FileFlags QWasmFileEngine::fileFlags(FileFlags type) const
+{
+ return type & (QAbstractFileEngine::FileFlag::ExistsFlag |
+ QAbstractFileEngine::FileFlag::FileType |
+ QAbstractFileEngine::FileFlag::ReadOwnerPerm |
+ QAbstractFileEngine::FileFlag::WriteOwnerPerm);
+}
+
+bool QWasmFileEngine::setPermissions(uint perms)
+{
+ Q_UNUSED(perms);
+ return false;
+}
+
+QByteArray QWasmFileEngine::id() const
+{
+ return {};
+}
+
+QString QWasmFileEngine::fileName(FileName file) const
+{
+ switch (file) {
+ case DefaultName:
+ case AbsoluteName:
+ case CanonicalName:
+ return m_fileName;
+ case BaseName: {
+ QString native = QWasmFileEngineHandler::nativeFileName(m_fileName);
+ QFileInfo info(native);
+ return info.fileName();
+ }
+ case PathName:
+ case AbsolutePathName:
+ case CanonicalPathName: {
+ QString native = QWasmFileEngineHandler::nativeFileName(m_fileName);
+ QFileInfo info(native);
+ QString path = info.path();
+ return path.isEmpty() ? "."_L1 : path;
+ }
+ default:
+ return QString();
+ }
+}
+
+uint QWasmFileEngine::ownerId(FileOwner) const
+{
+ return 0;
+}
+
+QString QWasmFileEngine::owner(FileOwner) const
+{
+ return {};
+}
+
+bool QWasmFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
+{
+ Q_UNUSED(newDate);
+ Q_UNUSED(time);
+ return false;
+}
+
+QDateTime QWasmFileEngine::fileTime(QFile::FileTime time) const
+{
+ Q_UNUSED(time);
+ return {};
+}
+
+void QWasmFileEngine::setFileName(const QString &file)
+{
+ if (m_fileName == file)
+ return;
+ close();
+ m_fileName = file;
+}
+
+int QWasmFileEngine::handle() const
+{
+ return -1;
+}
+
+QAbstractFileEngine::TriStateResult QWasmFileEngine::cloneTo(QAbstractFileEngine *target)
+{
+ Q_UNUSED(target);
+ return QAbstractFileEngine::TriStateResult::NotSupported;
+}
+
+QAbstractFileEngine::IteratorUniquePtr QWasmFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
+ const QStringList &filterNames)
+{
+ Q_UNUSED(path);
+ Q_UNUSED(filters);
+ Q_UNUSED(filterNames);
+ return nullptr;
+}
+
+qint64 QWasmFileEngine::read(char *data, qint64 maxlen)
+{
+ if (!(m_openMode & QIODevice::ReadOnly))
+ return -1;
+
+ if (m_fileDevice)
+ return m_fileDevice->read(data, maxlen);
+ if (m_blobDevice)
+ return m_blobDevice->read(data, maxlen);
+ return -1;
+}
+
+qint64 QWasmFileEngine::readLine(char *data, qint64 maxlen)
+{
+ if (!(m_openMode & QIODevice::ReadOnly))
+ return -1;
+
+ if (m_fileDevice)
+ return m_fileDevice->readLine(data, maxlen);
+ if (m_blobDevice)
+ return m_blobDevice->readLine(data, maxlen);
+ return -1;
+}
+
+qint64 QWasmFileEngine::write(const char *data, qint64 len)
+{
+ if (!(m_openMode & QIODevice::WriteOnly))
+ return -1;
+
+ if (m_fileDevice)
+ return m_fileDevice->write(data, len);
+ return -1;
+}
+
+bool QWasmFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
+{
+ Q_UNUSED(extension);
+ Q_UNUSED(option);
+ Q_UNUSED(output);
+ return false;
+}
+
+bool QWasmFileEngine::supportsExtension(Extension extension) const
+{
+ Q_UNUSED(extension);
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/platform/wasm/qwasmlocalfileengine_p.h b/src/corelib/platform/wasm/qwasmlocalfileengine_p.h
new file mode 100644
index 00000000000..dd82788dd90
--- /dev/null
+++ b/src/corelib/platform/wasm/qwasmlocalfileengine_p.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2024 Qt Group
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QWASMLOCALFILEENGINE_P_H
+#define QWASMLOCALFILEENGINE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qabstractfileengine_p.h>
+#include <QtCore/private/qstdweb_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmFileEngine;
+
+class QWasmFileEngineHandler: public QAbstractFileEngineHandler
+{
+public:
+ Q_DISABLE_COPY_MOVE(QWasmFileEngineHandler)
+
+ QWasmFileEngineHandler();
+ virtual ~QWasmFileEngineHandler() override;
+ virtual std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
+
+ static bool isWasmFileName(const QString& fileName);
+ static QString makeWasmFileName(const QString &fileName);
+ static QString nativeFileName(const QString &wasmFileName);
+
+ static QString addFile(qstdweb::File file);
+ static QString addFile(qstdweb::FileSystemFileHandle file);
+ static void removeFile(const QString fileName);
+ qstdweb::File getFile(const QString fileName);
+ qstdweb::FileSystemFileHandle getFileSystemFile(const QString fileName);
+
+private:
+ QHash<QString, qstdweb::File> m_files;
+ QHash<QString, qstdweb::FileSystemFileHandle> m_fileSystemFiles;
+};
+
+class QWasmFileEngine: public QAbstractFileEngine
+{
+public:
+ explicit QWasmFileEngine(const QString &fileName, qstdweb::File file);
+ explicit QWasmFileEngine(const QString &fileName, qstdweb::FileSystemFileHandle file);
+ ~QWasmFileEngine() override;
+
+ virtual bool open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) override;
+ virtual bool close() override;
+ virtual bool flush() override;
+ virtual bool syncToDisk() override;
+ virtual qint64 size() const override;
+ virtual qint64 pos() const override;
+ virtual bool seek(qint64 pos) override;
+ virtual bool isSequential() const override;
+ virtual bool remove() override;
+ virtual bool copy(const QString &newName) override;
+ virtual bool rename(const QString &newName) override;
+ virtual bool renameOverwrite(const QString &newName) override;
+ virtual bool link(const QString &newName) override;
+ virtual bool mkdir(const QString &dirName, bool createParentDirectories,
+ std::optional<QFile::Permissions> permissions = std::nullopt) const override;
+ virtual bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
+ virtual bool setSize(qint64 size) override;
+ virtual bool caseSensitive() const override;
+ virtual bool isRelativePath() const override;
+ virtual FileFlags fileFlags(FileFlags type=FileInfoAll) const override;
+ virtual bool setPermissions(uint perms) override;
+ virtual QByteArray id() const override;
+ virtual QString fileName(FileName file = DefaultName) const override;
+ virtual uint ownerId(FileOwner) const override;
+ virtual QString owner(FileOwner) const override;
+ virtual bool setFileTime(const QDateTime &newDate, QFile::FileTime time) override;
+ virtual QDateTime fileTime(QFile::FileTime time) const override;
+ virtual void setFileName(const QString &file) override;
+ virtual int handle() const override;
+ virtual TriStateResult cloneTo(QAbstractFileEngine *target) override;
+ virtual IteratorUniquePtr beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
+ const QStringList &filterNames) override;
+ virtual qint64 read(char *data, qint64 maxlen) override;
+ virtual qint64 readLine(char *data, qint64 maxlen) override;
+ virtual qint64 write(const char *data, qint64 len) override;
+ virtual bool extension(Extension extension, const ExtensionOption *option = nullptr,
+ ExtensionReturn *output = nullptr) override;
+ virtual bool supportsExtension(Extension extension) const override;
+
+private:
+ QString m_fileName;
+ QIODevice::OpenMode m_openMode = QIODevice::NotOpen;
+ std::unique_ptr<qstdweb::BlobIODevice> m_blobDevice;
+ std::unique_ptr<qstdweb::FileSystemFileIODevice> m_fileDevice;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMFILEENGINEHANDLER_H
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
index a4bc7843380..85358d1daf4 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
@@ -225,8 +225,11 @@ int QWasmSuspendResumeControl::sendPendingEvents()
pendingEvents.call<void>("splice", i, 1);
auto it = m_eventHandlers.find(event["index"].as<int>());
- if (it != m_eventHandlers.end())
- it->second(event["arg"]);
+ if (it != m_eventHandlers.end()) {
+ setCurrentEvent(event["arg"]);
+ it->second(currentEvent());
+ setCurrentEvent(emscripten::val::undefined());
+ }
++count;
}
}
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
index ff97ff3d7ea..4e75d5674be 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
@@ -42,12 +42,22 @@ public:
void suspendExclusive(QList<uint32_t> eventHandlerIndices);
int sendPendingEvents();
+ emscripten::val currentEvent() const
+ {
+ return m_currentEvent;
+ }
+ void setCurrentEvent(emscripten::val currentEvent)
+ {
+ m_currentEvent = currentEvent;
+ }
+
private:
friend void qtSendPendingEvents();
static QWasmSuspendResumeControl *s_suspendResumeControl;
std::map<int, std::function<void(emscripten::val)>> m_eventHandlers;
std::function<bool(int)> m_eventFilter = [](int) { return true; };
+ emscripten::val m_currentEvent = emscripten::val::undefined();
};
class Q_CORE_EXPORT QWasmEventHandler
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
index 051e2b1a04e..8d06821d4c1 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
@@ -285,6 +285,44 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint)
});
}
+void showOpenFileDialog(const std::string &accept,
+ const std::function<void (bool accepted, std::vector<qstdweb::File> files)> fileDialogClosed)
+{
+ FileDialog::showOpen(makeFilterList(accept), FileSelectMode::MultipleFiles, {
+ .thenFunc = [=](emscripten::val result) {
+ if (result.isUndefined() || result.isNull()) {
+ fileDialogClosed(false, std::vector<qstdweb::File>());
+ } else {
+ std::vector<qstdweb::File> files;
+ int length = result["length"].as<int>();
+ files.reserve(length);
+ for (int i = 0; i < length; ++i) {
+ emscripten::val fileVal = result[i];
+ if (!fileVal.isUndefined() && !fileVal.isNull()) {
+ files.push_back(qstdweb::File(fileVal));
+ }
+ }
+ fileDialogClosed(true, files);
+ }
+ },
+ .catchFunc = [=](emscripten::val) {
+ fileDialogClosed(false, std::vector<qstdweb::File>());
+ }
+ });
+}
+
+void showSaveFileDialog(const std::string &fileNameHint, const std::function<void (bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed)
+{
+ FileDialog::showSave(fileNameHint, {
+ .thenFunc = [=](emscripten::val result) {
+ fileDialogClosed(true, qstdweb::FileSystemFileHandle(result));
+ },
+ .catchFunc = [=](emscripten::val) {
+ fileDialogClosed(false, qstdweb::FileSystemFileHandle());
+ }
+ });
+}
+
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
index 77b14577f7e..5597746a5f3 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
@@ -16,6 +16,7 @@
//
#include <private/qglobal_p.h>
+#include <private/qstdweb_p.h>
#include <cstdint>
#include <functional>
@@ -38,6 +39,12 @@ Q_CORE_EXPORT void openFile(const std::string &accept,
Q_CORE_EXPORT void saveFile(const QByteArray &data, const std::string &fileNameHint);
Q_CORE_EXPORT void saveFile(const char *content, size_t size, const std::string &fileNameHint);
+Q_CORE_EXPORT void showOpenFileDialog(const std::string &accept,
+ const std::function<void (bool accepted, std::vector<qstdweb::File> files)> fileDialogClosed);
+Q_CORE_EXPORT void showSaveFileDialog(const std::string &fileNameHint,
+ const std::function<void (bool accepted, qstdweb::FileSystemFileHandle fileHandle)> fileDialogClosed);
+
+
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE
diff --git a/src/network/android/jar/build.gradle b/src/network/android/jar/build.gradle
index f1f470b6635..580e15b92f6 100644
--- a/src/network/android/jar/build.gradle
+++ b/src/network/android/jar/build.gradle
@@ -23,7 +23,7 @@ repositories {
}
android {
- compileSdk 35
+ compileSdk 36
defaultConfig {
minSdkVersion 28
diff --git a/src/plugins/networkinformation/android/jar/build.gradle b/src/plugins/networkinformation/android/jar/build.gradle
index f1f470b6635..580e15b92f6 100644
--- a/src/plugins/networkinformation/android/jar/build.gradle
+++ b/src/plugins/networkinformation/android/jar/build.gradle
@@ -23,7 +23,7 @@ repositories {
}
android {
- compileSdk 35
+ compileSdk 36
defaultConfig {
minSdkVersion 28
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index a6d96df0fbb..b00e6375e1a 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -269,6 +269,23 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper);
qCDebug(lcQpaWindow) << "Done moving" << self << "to" << self.window;
}
+// QWindow::setParent() promises that the child window will be clipped
+// to its parent, which we rely on in e.g. Qt Widgets when a native window
+// is added to a scroll area. We try to be smart and only enable clipping
+// if we have potential child QWindows that rely on this behavior.
+// FIXME: Be even smarter, and only consider QWindow based subviews,
+// in a way that also includes foreign windows.
+
+- (void)didAddSubview:(NSView *)subview
+{
+ self.clipsToBounds = YES;
+}
+
+- (void)willRemoveSubview:(NSView *)subview
+{
+ self.clipsToBounds = self.subviews.count > 1;
+}
+
// ----------------------------------------------------------------------------
- (QWindow *)topLevelWindow
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt
index 03cb0d52ca1..7e9beb7e832 100644
--- a/src/plugins/platforms/wasm/CMakeLists.txt
+++ b/src/plugins/platforms/wasm/CMakeLists.txt
@@ -33,6 +33,7 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
qwasminputcontext.cpp qwasminputcontext.h
qwasmwindowstack.h
+ qwasmfiledialoghelper.cpp qwasmfiledialoghelper.h
DEFINES
QT_EGL_NO_X11
QT_NO_FOREACH
diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp
index 71efcee7887..eefd18154aa 100644
--- a/src/plugins/platforms/wasm/qwasmdom.cpp
+++ b/src/plugins/platforms/wasm/qwasmdom.cpp
@@ -10,6 +10,7 @@
#include <QtCore/qrect.h>
#include <QtGui/qimage.h>
#include <private/qstdweb_p.h>
+#include <private/qwasmlocalfileengine_p.h>
#include <QtCore/qurl.h>
#include <utility>
@@ -112,11 +113,14 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
m_callback(mimeData);
- // Delete files; we expect that the user callback reads/copies
- // file content before returning.
- // Fixme: tie file lifetime to lifetime of the QMimeData?
- for (QUrl fileUrl: fileUrls)
- QFile(fileUrl.toLocalFile()).remove();
+ // Delete temporary files; we expect that the user callback reads/copies
+ // file content before returning.// Fixme: tie file lifetime to lifetime of the QMimeData?
+ // Note: QWasmFileEngine files (weblocalfile://) are managed by QWasmFileEngine
+ // and are not deleted here
+ for (QUrl fileUrl: fileUrls) {
+ if (!QWasmFileEngineHandler::isWasmFileName(fileUrl.toString()))
+ QFile(fileUrl.toLocalFile()).remove();
+ }
delete this;
}
@@ -144,49 +148,56 @@ void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
case ItemKind::File: {
qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
- if (webfile.size() > 1e+9) { // limit file size to 1 GB
- qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
+ // Add a file access url for the local file. If asyncify is available,
+ // add a QWasmFileEngine managed url. Else fall back to placing a copy
+ // of the file at /tmp on Emsripten's in-memory file system.
+ if (qstdweb::haveAsyncify()) {
+ QUrl fileUrl(QWasmFileEngineHandler::addFile(webfile));
+ mimeContext->fileUrls.append(fileUrl);
mimeContext->deref();
- continue;
- }
+ } else {
+ // Limit in-memory file size to 1 GB
+ if (webfile.size() > 1e+9) {
+ qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
+ mimeContext->deref();
+ continue;
+ }
- QString mimeFormat = QString::fromStdString(webfile.type());
- QString fileName = QString::fromStdString(webfile.name());
+ // Read file content
+ QByteArray fileContent(webfile.size(), Qt::Uninitialized);
+ webfile.stream(fileContent.data(), [=]() {
+ QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
+ qtTmpDir.mkpath(qtTmpDir.path());
+
+ QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
+ mimeContext->fileUrls.append(fileUrl);
+
+ QFile file(fileUrl.toLocalFile());
+ if (!file.open(QFile::WriteOnly)) {
+ qWarning() << "File was not opened";
+ mimeContext->deref();
+ return;
+ }
+ if (file.write(fileContent) < 0)
+ qWarning() << "Write failed";
+ file.close();
+ mimeContext->deref();
+ });
- // there's a file, now read it
- QByteArray fileContent(webfile.size(), Qt::Uninitialized);
- webfile.stream(fileContent.data(), [=]() {
-
// If we get a single file, and that file is an image, then
// try to decode the image data. This handles the case where
// image data (i.e. not an image file) is pasted. The browsers
// will then create a fake "image.png" file which has the image
- // data. As a side effect Qt will also decode the image for
+ // data. As a side effect Qt will also decode the image for
// single-image-file drops, since there is no way to differentiate
// the fake "image.png" from a real one.
+ QString mimeFormat = QString::fromStdString(webfile.type());
if (fileCount == 1 && mimeFormat.contains("image/")) {
QImage image;
if (image.loadFromData(fileContent))
mimeContext->mimeData->setImageData(image);
}
-
- QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
- qtTmpDir.mkpath(qtTmpDir.path());
-
- QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
- mimeContext->fileUrls.append(fileUrl);
-
- QFile file(fileUrl.toLocalFile());
- if (!file.open(QFile::WriteOnly)) {
- qWarning() << "File was not opened";
- mimeContext->deref();
- return;
- }
- if (file.write(fileContent) < 0)
- qWarning() << "Write failed";
- file.close();
- mimeContext->deref();
- });
+ }
break;
}
case ItemKind::String:
diff --git a/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp b/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp
new file mode 100644
index 00000000000..1e6e2b1f644
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmfiledialoghelper.cpp
@@ -0,0 +1,157 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmfiledialoghelper.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+#include <QtGui/private/qwasmlocalfileaccess_p.h>
+#include <QtCore/private/qwasmlocalfileengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QWasmFileDialogHelper::QWasmFileDialogHelper()
+ : m_eventLoop(nullptr)
+{
+
+}
+
+QWasmFileDialogHelper::~QWasmFileDialogHelper()
+{
+
+}
+
+bool QWasmFileDialogHelper::defaultNameFilterDisables() const
+{
+ return false;
+}
+
+void QWasmFileDialogHelper::setDirectory(const QUrl &directory)
+{
+ Q_UNUSED(directory)
+}
+
+QUrl QWasmFileDialogHelper::directory() const
+{
+ return QUrl();
+}
+
+void QWasmFileDialogHelper::selectFile(const QUrl &file)
+{
+ m_selectedFiles.clear();
+ m_selectedFiles.append(file);
+}
+
+QList<QUrl> QWasmFileDialogHelper::selectedFiles() const
+{
+ return m_selectedFiles;
+}
+
+void QWasmFileDialogHelper::setFilter()
+{
+
+}
+
+void QWasmFileDialogHelper::selectNameFilter(const QString &filter)
+{
+ Q_UNUSED(filter);
+ // TODO
+}
+
+QString QWasmFileDialogHelper::selectedNameFilter() const
+{
+ return QString();
+}
+
+void QWasmFileDialogHelper::exec()
+{
+ QEventLoop eventLoop;
+ m_eventLoop = &eventLoop;
+ eventLoop.exec();
+ m_eventLoop = nullptr;
+}
+
+bool QWasmFileDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent)
+{
+ Q_UNUSED(flags)
+ Q_UNUSED(modality)
+ Q_UNUSED(parent)
+ showFileDialog();
+ return true;
+}
+
+void QWasmFileDialogHelper::hide()
+{
+
+}
+
+void QWasmFileDialogHelper::showFileDialog()
+{
+ if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) {
+ // Use name filters from options
+ QString nameFilter = options()->nameFilters().join(";;");
+ if (nameFilter.isEmpty())
+ nameFilter = "*";
+
+ QWasmLocalFileAccess::showOpenFileDialog(nameFilter.toStdString(), [this](bool accepted, std::vector<qstdweb::File> files) {
+ onOpenDialogClosed(accepted, files);
+ });
+ } else if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
+ QString suggestion = m_selectedFiles.isEmpty() ? QString() : QUrl(m_selectedFiles.first()).fileName();
+ m_selectedFiles.clear();
+
+ QWasmLocalFileAccess::showSaveFileDialog(suggestion.toStdString(), [this](bool accepted, qstdweb::FileSystemFileHandle file){
+ onSaveDialogClosed(accepted, file);
+ });
+ }
+}
+
+void QWasmFileDialogHelper::onOpenDialogClosed(bool accepted, std::vector<qstdweb::File> files)
+{
+ m_selectedFiles.clear();
+
+ if (!accepted) {
+ emit reject();
+ return;
+ }
+
+ // Track opened files
+ for (const auto &file : files) {
+ QString wasmFileName = QWasmFileEngineHandler::addFile(file);
+ QUrl fileUrl(wasmFileName);
+ m_selectedFiles.append(fileUrl);
+ }
+
+ // Emit signals
+ if (m_selectedFiles.size() > 0) {
+ emit fileSelected(m_selectedFiles.first());
+ emit filesSelected(m_selectedFiles);
+ }
+ emit accept();
+
+ // exit exec() if in exec()
+ if (m_eventLoop)
+ m_eventLoop->quit();
+}
+
+void QWasmFileDialogHelper::onSaveDialogClosed(bool accepted, qstdweb::FileSystemFileHandle file)
+{
+ if (!accepted) {
+ emit reject();
+ return;
+ }
+
+ // Track save file
+ QString wasmFileName = QWasmFileEngineHandler::addFile(file);
+ QUrl fileUrl(wasmFileName);
+ m_selectedFiles.append(fileUrl);
+
+ // Emit signals
+ emit fileSelected(m_selectedFiles.first());
+ emit accept();
+
+ if (m_eventLoop)
+ m_eventLoop->quit();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmfiledialoghelper.h b/src/plugins/platforms/wasm/qwasmfiledialoghelper.h
new file mode 100644
index 00000000000..c5a5b57e518
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmfiledialoghelper.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMFILEDIALOGHELPER_H
+#define QWASMFILEDIALOGHELPER_H
+
+#include <QtCore/QObject>
+#include <QtCore/QUrl>
+#include <QtCore/QEventLoop>
+#include <QtGui/qpa/qplatformdialoghelper.h>
+#include <QtGui/private/qwasmlocalfileaccess_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmFileDialogHelper : public QPlatformFileDialogHelper
+{
+ Q_OBJECT
+public:
+ QWasmFileDialogHelper();
+ ~QWasmFileDialogHelper();
+public:
+ virtual void exec() override;
+ virtual bool show(Qt::WindowFlags windowFlags,
+ Qt::WindowModality windowModality,
+ QWindow *parent) override;
+ virtual void hide() override;
+ virtual bool defaultNameFilterDisables() const override;
+ virtual void setDirectory(const QUrl &directory) override;
+ virtual QUrl directory() const override;
+ virtual void selectFile(const QUrl &filename) override;
+ virtual QList<QUrl> selectedFiles() const override;
+ virtual void setFilter() override;
+ virtual void selectNameFilter(const QString &filter) override;
+ virtual QString selectedNameFilter() const override;
+ static QStringList cleanFilterList(const QString &filter);
+signals:
+ void fileDone(const QUrl &);
+private:
+ void showFileDialog();
+ void onOpenDialogClosed(bool accepted, std::vector<qstdweb::File> files);
+ void onSaveDialogClosed(bool accepted, qstdweb::FileSystemFileHandle file);
+
+ QList<QUrl> m_selectedFiles;
+ QEventLoop *m_eventLoop;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASMFILEDIALOGHELPER_H
diff --git a/src/plugins/platforms/wasm/qwasmtheme.cpp b/src/plugins/platforms/wasm/qwasmtheme.cpp
index b9340f31275..b1e5c208c6c 100644
--- a/src/plugins/platforms/wasm/qwasmtheme.cpp
+++ b/src/plugins/platforms/wasm/qwasmtheme.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmtheme.h"
+#include "qwasmfiledialoghelper.h"
#include <QtCore/qvariant.h>
#include <QFontDatabase>
#include <QList>
@@ -127,6 +128,18 @@ const QFont *QWasmTheme::font(Font type) const
return nullptr;
}
+bool QWasmTheme::usePlatformNativeDialog(DialogType type) const
+{
+ return (type == DialogType::FileDialog);
+}
+
+QPlatformDialogHelper *QWasmTheme::createPlatformDialogHelper(DialogType type) const
+{
+ if (type == DialogType::FileDialog)
+ return new QWasmFileDialogHelper();
+ return nullptr;
+}
+
void QWasmTheme::onColorSchemeChange()
{
auto colorScheme = getColorSchemeFromMedia();
diff --git a/src/plugins/platforms/wasm/qwasmtheme.h b/src/plugins/platforms/wasm/qwasmtheme.h
index 4eaad874c76..8b8dd6ebd97 100644
--- a/src/plugins/platforms/wasm/qwasmtheme.h
+++ b/src/plugins/platforms/wasm/qwasmtheme.h
@@ -57,6 +57,9 @@ public:
Qt::ContrastPreference contrastPreference() const override;
QVariant themeHint(ThemeHint hint) const override;
const QFont *font(Font type) const override;
+ bool usePlatformNativeDialog(DialogType type) const override;
+ QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
+
QFont *fixedFont = nullptr;
void onColorSchemeChange();
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 6e8bd46ca58..7f1dd7eb34c 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -925,7 +925,7 @@ bool QWasmWindow::deliverPointerEvent(const PointerEvent &event)
std::back_inserter(touchPointList),
[](const QWindowSystemInterface::TouchPoint &val) { return val; });
- if (event.type == EventType::PointerUp)
+ if (event.type == EventType::PointerUp || event.type == EventType::PointerCancel)
m_pointerIdToTouchPoints.remove(event.pointerId);
return event.type == EventType::PointerCancel
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index 2816982b1a8..9459e8bdfd3 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -848,7 +848,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
// make mouse events fall through this window
// NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window
if (flagsIn & Qt::WindowTransparentForInput)
- exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+ exStyle |= WS_EX_TRANSPARENT;
// Currently only compatible with D3D surfaces, use it with care.
if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE"))
diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp
index 7f24df7eac2..c4e7101b372 100644
--- a/src/tools/androiddeployqt/main.cpp
+++ b/src/tools/androiddeployqt/main.cpp
@@ -173,7 +173,7 @@ struct Options
QString versionName;
QString versionCode;
QByteArray minSdkVersion{"28"};
- QByteArray targetSdkVersion{"35"};
+ QByteArray targetSdkVersion{"36"};
// lib c++ path
QString stdCppPath;
diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt
index e6f920dcf32..c12475d69af 100644
--- a/src/tools/bootstrap/CMakeLists.txt
+++ b/src/tools/bootstrap/CMakeLists.txt
@@ -220,6 +220,10 @@ target_link_libraries(Bootstrap PRIVATE PlatformCommonInternal)
qt_internal_apply_gc_binaries(Bootstrap PUBLIC)
set_target_properties(Bootstrap PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
qt_internal_add_target_aliases(Bootstrap)
+qt_internal_add_target_optimized_flags_for_debug_config_in_current_scope(Bootstrap)
+qt_internal_enable_optimized_tools_lto(Bootstrap)
+qt_internal_workaround_static_lib_gcc_lto_issue(Bootstrap)
+
qt_set_msvc_cplusplus_options(Bootstrap PUBLIC)
qt_set_common_target_properties(Bootstrap)
qt_internal_apply_intel_cet(Bootstrap PUBLIC)
diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp
index 40bfa9c2863..6a6027d44f2 100644
--- a/src/tools/rcc/rcc.cpp
+++ b/src/tools/rcc/rcc.cpp
@@ -1,10 +1,12 @@
// Copyright (C) 2018 The Qt Company Ltd.
// Copyright (C) 2018 Intel Corporation.
+// Copyright (C) 2024 Christoph Cullmann <christoph@cullmann.io>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "rcc.h"
#include <qbytearray.h>
+#include <qcryptographichash.h>
#include <qdatetime.h>
#include <qdebug.h>
#include <qdir.h>
@@ -90,8 +92,28 @@ public:
QString resourceName() const;
+ struct DeduplicationKey {
+ RCCResourceLibrary::CompressionAlgorithm compressAlgo;
+ int compressLevel;
+ int compressThreshold;
+ QByteArray hash;
+
+ bool operator==(const DeduplicationKey &other) const
+ {
+ return compressAlgo == other.compressAlgo &&
+ compressLevel == other.compressLevel &&
+ compressThreshold == other.compressThreshold &&
+ hash == other.hash;
+ }
+ };
+
+ typedef QMultiHash<DeduplicationKey, RCCFileInfo*> DeduplicationMultiHash;
+
public:
- qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage);
+ qint64 writeDataBlob(RCCResourceLibrary &lib,
+ qint64 offset,
+ DeduplicationMultiHash &dedupByContent,
+ QString *errorMessage);
qint64 writeDataName(RCCResourceLibrary &, qint64 offset);
void writeDataInfo(RCCResourceLibrary &lib);
@@ -114,6 +136,11 @@ public:
qint64 m_childOffset = 0;
};
+static size_t qHash(const RCCFileInfo::DeduplicationKey &key, size_t seed) noexcept
+{
+ return qHashMulti(seed, key.compressAlgo, key.compressLevel, key.compressThreshold, key.hash);
+}
+
RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language,
QLocale::Territory territory, uint flags,
RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel,
@@ -217,8 +244,10 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
}
}
-qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
- QString *errorMessage)
+qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib,
+ qint64 offset,
+ DeduplicationMultiHash &dedupByContent,
+ QString *errorMessage)
{
const bool text = lib.m_format == RCCResourceLibrary::C_Code;
const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
@@ -230,24 +259,58 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
m_dataOffset = offset;
QByteArray data;
+ // determine compession algorithm & level early as used in de-duplication keys
+ // this avoid corruption for the two pass variants (QTBUG-137546)
+#if QT_CONFIG(zstd)
+ if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) {
+ m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd;
+ m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental
+ }
+#endif
+#ifndef QT_NO_COMPRESS
+ if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) {
+ m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib;
+ m_compressLevel = 9;
+ }
+#endif
+
if (!m_isEmpty) {
- //find the data to be written
- QFile file(m_fileInfo.absoluteFilePath());
+ // find the data to be written
+ const QString absoluteFilePath = m_fileInfo.absoluteFilePath();
+ QFile file(absoluteFilePath);
if (!file.open(QFile::ReadOnly)) {
- *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
+ *errorMessage = msgOpenReadFailed(absoluteFilePath, file.errorString());
return 0;
}
-
data = file.readAll();
+
+ // de-duplicate the same file content, we can re-use already written data
+ // we only do that if we have the same compression settings
+ const QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
+ const DeduplicationKey key{m_compressAlgo, m_compressLevel, m_compressThreshold, hash};
+ const QList<RCCFileInfo *> potentialCandidates = dedupByContent.values(key);
+ for (const RCCFileInfo *candidate : potentialCandidates) {
+ // check real content, we can have collisions
+ QFile candidateFile(candidate->m_fileInfo.absoluteFilePath());
+ if (!candidateFile.open(QFile::ReadOnly)) {
+ *errorMessage = msgOpenReadFailed(candidate->m_fileInfo.absoluteFilePath(),
+ candidateFile.errorString());
+ return 0;
+ }
+ if (data != candidateFile.readAll())
+ continue;
+ // just remember the offset & flags with final compression state
+ // of the already written data and be done
+ m_dataOffset = candidate->m_dataOffset;
+ m_flags = candidate->m_flags;
+ return offset;
+ }
+ dedupByContent.insert(key, this);
}
// Check if compression is useful for this file
if (data.size() != 0) {
#if QT_CONFIG(zstd)
- if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) {
- m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd;
- m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental
- }
if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) {
if (lib.m_zstdCCtx == nullptr)
lib.m_zstdCCtx = ZSTD_createCCtx();
@@ -292,10 +355,6 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
}
#endif
#ifndef QT_NO_COMPRESS
- if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) {
- m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib;
- m_compressLevel = 9;
- }
if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) {
QByteArray compressed =
qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel);
@@ -1168,6 +1227,7 @@ bool RCCResourceLibrary::writeDataBlobs()
QStack<RCCFileInfo*> pending;
pending.push(m_root);
qint64 offset = 0;
+ RCCFileInfo::DeduplicationMultiHash dedupByContent;
QString errorMessage;
while (!pending.isEmpty()) {
RCCFileInfo *file = pending.pop();
@@ -1176,7 +1236,8 @@ bool RCCResourceLibrary::writeDataBlobs()
if (child->m_flags & RCCFileInfo::Directory)
pending.push(child);
else {
- offset = child->writeDataBlob(*this, offset, &errorMessage);
+ offset = child->writeDataBlob(*this, offset,
+ dedupByContent, &errorMessage);
if (offset == 0) {
m_errorDevice->write(errorMessage.toUtf8());
return false;
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp
index d2b0fb724ac..a735696adcb 100644
--- a/src/widgets/kernel/qapplication.cpp
+++ b/src/widgets/kernel/qapplication.cpp
@@ -3741,6 +3741,14 @@ void QApplicationPrivate::cleanupMultitouch_sys()
{
}
+/*! \internal
+ Check the target widgets of the active touchpoints of the given \a device,
+ and choose the widget that is closest to any of the points. This widget
+ will then get all the touchpoints, even if it would not otherwise be the
+ target for some of them.
+
+ \sa translateRawTouchEvent()
+*/
QWidget *QApplicationPrivate::findClosestTouchPointTarget(const QPointingDevice *device, const QEventPoint &touchPoint)
{
const QPointF globalPos = touchPoint.globalPosition();
@@ -3754,7 +3762,10 @@ QWidget *QApplicationPrivate::findClosestTouchPointTarget(const QPointingDevice
qreal dx = globalPos.x() - pt.globalPosition().x();
qreal dy = globalPos.y() - pt.globalPosition().y();
qreal distance = dx * dx + dy * dy;
- if (closestTouchPointId == -1 || distance < closestDistance) {
+ // closestTouchPointId is -1 at the beginning.
+ // closestTouchPointId may be 0 if
+ // a synth-mouse eventPoint was found in activePoints: that's not relevant here.
+ if (closestTouchPointId <= 0 || distance < closestDistance) {
closestTouchPointId = pt.id();
closestDistance = distance;
closestTarget = QMutableEventPoint::target(pt);
diff --git a/src/widgets/styles/qfusionstyle_p_p.h b/src/widgets/styles/qfusionstyle_p_p.h
index dcb79f9e93c..821be49b2fa 100644
--- a/src/widgets/styles/qfusionstyle_p_p.h
+++ b/src/widgets/styles/qfusionstyle_p_p.h
@@ -94,7 +94,8 @@ public:
QColor buttonColor = pal.button().color();
int val = qGray(buttonColor.rgb());
buttonColor = buttonColor.lighter(100 + qMax(1, (180 - val)/6));
- buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, buttonColor.value());
+ buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75,
+ buttonColor.value(), buttonColor.alpha());
return buttonColor;
}