summaryrefslogtreecommitdiffstats
path: root/src/gui/platform/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/platform/wasm')
-rw-r--r--src/gui/platform/wasm/qlocalfileapi.cpp108
-rw-r--r--src/gui/platform/wasm/qlocalfileapi_p.h31
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess.cpp265
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess_p.h7
-rw-r--r--src/gui/platform/wasm/qwasmnativeinterface.cpp17
5 files changed, 319 insertions, 109 deletions
diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp
index ec7ae40391..76b99361c4 100644
--- a/src/gui/platform/wasm/qlocalfileapi.cpp
+++ b/src/gui/platform/wasm/qlocalfileapi.cpp
@@ -8,29 +8,62 @@
QT_BEGIN_NAMESPACE
namespace LocalFileApi {
namespace {
+std::string qtFilterListToFileInputAccept(const QStringList &filterList)
+{
+ QStringList transformed;
+ for (const auto &filter : filterList) {
+ const auto type = Type::fromQt(filter);
+ if (type && type->accept()) {
+ const auto &extensions = type->accept()->mimeType().extensions();
+ std::transform(extensions.begin(), extensions.end(), std::back_inserter(transformed),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return extension.value().toString();
+ });
+ }
+ }
+ return transformed.join(QStringLiteral(",")).toStdString();
+}
+
std::optional<emscripten::val> qtFilterListToTypes(const QStringList &filterList)
{
using namespace qstdweb;
using namespace emscripten;
-
auto types = emscripten::val::array();
for (const auto &fileFilter : filterList) {
auto type = Type::fromQt(fileFilter);
- if (type)
- types.call<void>("push", type->asVal());
+ if (type) {
+ auto jsType = emscripten::val::object();
+ jsType.set("description", type->description().toString().toStdString());
+ if (type->accept()) {
+ jsType.set("accept", ([&mimeType = type->accept()->mimeType()]() {
+ val acceptDict = val::object();
+
+ QList<emscripten::val> extensions;
+ extensions.reserve(mimeType.extensions().size());
+ std::transform(
+ mimeType.extensions().begin(), mimeType.extensions().end(),
+ std::back_inserter(extensions),
+ [](const Type::Accept::MimeType::Extension &extension) {
+ return val(extension.value().toString().toStdString());
+ });
+ acceptDict.set("application/octet-stream",
+ emscripten::val::array(extensions.begin(),
+ extensions.end()));
+ return acceptDict;
+ })());
+ }
+ types.call<void>("push", std::move(jsType));
+ }
}
return types["length"].as<int>() == 0 ? std::optional<emscripten::val>() : types;
}
-}
+} // namespace
Type::Type(QStringView description, std::optional<Accept> accept)
- : m_storage(emscripten::val::object())
+ : m_description(description.trimmed()), m_accept(std::move(accept))
{
- m_storage.set("description", description.trimmed().toString().toStdString());
- if (accept)
- m_storage.set("accept", accept->asVal());
}
Type::~Type() = default;
@@ -46,7 +79,7 @@ std::optional<Type> Type::fromQt(QStringView type)
// Group 1 is treated as the description, whereas group 2 or 3 are treated as the filter list.
static QRegularExpression regex(
QString(QStringLiteral("(?:(?:([^(]*)\\(([^()]+)\\)[^)]*)|([^()]+))")));
- const auto match = regex.match(type);
+ const auto match = regex.matchView(type);
if (!match.hasMatch())
return std::nullopt;
@@ -69,12 +102,7 @@ std::optional<Type> Type::fromQt(QStringView type)
return Type(description, std::move(*accept));
}
-emscripten::val Type::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::Accept() : m_storage(emscripten::val::object()) { }
+Type::Accept::Accept() = default;
Type::Accept::~Accept() = default;
@@ -86,7 +114,7 @@ std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation)
// The next group of non-empty characters.
static QRegularExpression internalRegex(QString(QStringLiteral("([^\\s]+)\\s*")));
int offset = 0;
- auto internalMatch = internalRegex.match(qtRepresentation, offset);
+ auto internalMatch = internalRegex.matchView(qtRepresentation, offset);
MimeType mimeType;
while (internalMatch.hasMatch()) {
@@ -97,42 +125,28 @@ std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation)
mimeType.addExtension(*webExtension);
- internalMatch = internalRegex.match(qtRepresentation, internalMatch.capturedEnd());
+ internalMatch = internalRegex.matchView(qtRepresentation, internalMatch.capturedEnd());
}
- accept.addMimeType(mimeType);
+ accept.setMimeType(mimeType);
return accept;
}
-void Type::Accept::addMimeType(MimeType mimeType)
-{
- // The mime type provided here does not seem to have any effect at the result at all.
- m_storage.set("application/octet-stream", mimeType.asVal());
-}
-
-emscripten::val Type::Accept::asVal() const
+void Type::Accept::setMimeType(MimeType mimeType)
{
- return m_storage;
+ m_mimeType = std::move(mimeType);
}
-Type::Accept::MimeType::MimeType() : m_storage(emscripten::val::array()) { }
+Type::Accept::MimeType::MimeType() = default;
Type::Accept::MimeType::~MimeType() = default;
void Type::Accept::MimeType::addExtension(Extension extension)
{
- m_storage.call<void>("push", extension.asVal());
+ m_extensions.push_back(std::move(extension));
}
-emscripten::val Type::Accept::MimeType::asVal() const
-{
- return m_storage;
-}
-
-Type::Accept::MimeType::Extension::Extension(QStringView extension)
- : m_storage(extension.toString().toStdString())
-{
-}
+Type::Accept::MimeType::Extension::Extension(QStringView extension) : m_value(extension) { }
Type::Accept::MimeType::Extension::~Extension() = default;
@@ -144,7 +158,7 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
// The web filter does not support wildcards.
static QRegularExpression qtAcceptAllRegex(
QRegularExpression::anchoredPattern(QString(QStringLiteral("[*]+|[*]+\\.[*]+"))));
- if (qtAcceptAllRegex.match(qtRepresentation).hasMatch())
+ if (qtAcceptAllRegex.matchView(qtRepresentation).hasMatch())
return std::nullopt;
// Checks for correctness. The web filter only allows filename extensions and does not filter
@@ -153,7 +167,7 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
static QRegularExpression qtFilenameMatcherRegex(
QRegularExpression::anchoredPattern(QString(QStringLiteral("(\\*?)(\\.[^*]+)"))));
- auto extensionMatch = qtFilenameMatcherRegex.match(qtRepresentation);
+ auto extensionMatch = qtFilenameMatcherRegex.matchView(qtRepresentation);
if (extensionMatch.hasMatch())
return Extension(extensionMatch.capturedView(2));
@@ -161,15 +175,10 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
return std::nullopt;
}
-emscripten::val Type::Accept::MimeType::Extension::asVal() const
-{
- return m_storage;
-}
-
emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple)
{
auto options = emscripten::val::object();
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList)) {
+ if (auto typeList = qtFilterListToTypes(filterList); typeList) {
options.set("types", std::move(*typeList));
options.set("excludeAcceptAllOption", true);
}
@@ -186,12 +195,17 @@ emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::st
if (!suggestedName.empty())
options.set("suggestedName", emscripten::val(suggestedName));
- if (auto typeList = LocalFileApi::qtFilterListToTypes(filterList))
+ if (auto typeList = qtFilterListToTypes(filterList))
options.set("types", emscripten::val(std::move(*typeList)));
return options;
}
-} // namespace LocalFileApi
+std::string makeFileInputAccept(const QStringList &filterList)
+{
+ return qtFilterListToFileInputAccept(filterList);
+}
+
+} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qlocalfileapi_p.h b/src/gui/platform/wasm/qlocalfileapi_p.h
index a8e7f666f9..1398d674d8 100644
--- a/src/gui/platform/wasm/qlocalfileapi_p.h
+++ b/src/gui/platform/wasm/qlocalfileapi_p.h
@@ -24,7 +24,8 @@
QT_BEGIN_NAMESPACE
namespace LocalFileApi {
-class Q_CORE_EXPORT Type {
+class Q_AUTOTEST_EXPORT Type
+{
public:
class Accept {
public:
@@ -36,12 +37,12 @@ public:
~Extension();
- emscripten::val asVal() const;
+ const QStringView &value() const { return m_value; }
private:
explicit Extension(QStringView extension);
- emscripten::val m_storage;
+ QStringView m_value;
};
MimeType();
@@ -49,37 +50,43 @@ public:
void addExtension(Extension type);
- emscripten::val asVal() const;
+ const std::vector<Extension> &extensions() const { return m_extensions; }
private:
- emscripten::val m_storage;
+ std::vector<Extension> m_extensions;
};
static std::optional<Accept> fromQt(QStringView type);
~Accept();
- void addMimeType(MimeType mimeType);
+ void setMimeType(MimeType mimeType);
- emscripten::val asVal() const;
+ const MimeType &mimeType() const { return m_mimeType; }
private:
Accept();
- emscripten::val m_storage;
+ MimeType m_mimeType;
};
Type(QStringView description, std::optional<Accept> accept);
~Type();
static std::optional<Type> fromQt(QStringView type);
- emscripten::val asVal() const;
+ const QStringView &description() const { return m_description; }
+ const std::optional<Accept> &accept() const { return m_accept; }
private:
- emscripten::val m_storage;
+ QStringView m_description;
+ std::optional<Accept> m_accept;
};
-Q_CORE_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple);
-Q_CORE_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName);
+Q_AUTOTEST_EXPORT emscripten::val makeOpenFileOptions(const QStringList &filterList,
+ bool acceptMultiple);
+Q_AUTOTEST_EXPORT emscripten::val makeSaveFileOptions(const QStringList &filterList,
+ const std::string &suggestedName);
+
+Q_AUTOTEST_EXPORT std::string makeFileInputAccept(const QStringList &filterList);
} // namespace LocalFileApi
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
index 172c8f6814..a946cda043 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
@@ -2,19 +2,110 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwasmlocalfileaccess_p.h"
+#include "qlocalfileapi_p.h"
#include <private/qstdweb_p.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>
+#include <QtCore/qregularexpression.h>
+
QT_BEGIN_NAMESPACE
namespace QWasmLocalFileAccess {
+namespace FileDialog {
+namespace {
+bool hasLocalFilesApi()
+{
+ return !qstdweb::window()["showOpenFilePicker"].isUndefined();
+}
+
+void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks onFilesSelected)
+{
+ // Create file input html element which will display a native file dialog
+ // and call back to our onchange handler once the user has selected
+ // one or more files.
+ emscripten::val document = emscripten::val::global("document");
+ emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
+ input.set("type", "file");
+ input.set("style", "display:none");
+ input.set("accept", LocalFileApi::makeFileInputAccept(accept));
+ Q_UNUSED(accept);
+ input.set("multiple", emscripten::val(fileSelectMode == FileSelectMode::MultipleFiles));
+
+ // Note: there is no event in case the user cancels the file dialog.
+ static std::unique_ptr<qstdweb::EventCallback> changeEvent;
+ auto callback = [=](emscripten::val) { onFilesSelected.thenFunc(input["files"]); };
+ changeEvent = std::make_unique<qstdweb::EventCallback>(input, "change", callback);
+
+ // Activate file input
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", input);
+ input.call<void>("click");
+ body.call<void>("removeChild", input);
+}
+
+void showOpenViaLocalFileApi(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks callbacks)
+{
+ using namespace qstdweb;
+
+ auto options = LocalFileApi::makeOpenFileOptions(accept, fileSelectMode == FileSelectMode::MultipleFiles);
+
+ Promise::make(
+ window(), QStringLiteral("showOpenFilePicker"),
+ {
+ .thenFunc = [=](emscripten::val fileHandles) mutable {
+ std::vector<emscripten::val> filePromises;
+ filePromises.reserve(fileHandles["length"].as<int>());
+ for (int i = 0; i < fileHandles["length"].as<int>(); ++i)
+ filePromises.push_back(fileHandles[i].call<emscripten::val>("getFile"));
+ Promise::all(std::move(filePromises), callbacks);
+ },
+ .catchFunc = callbacks.catchFunc,
+ .finallyFunc = callbacks.finallyFunc,
+ }, std::move(options));
+}
+void showSaveViaLocalFileApi(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
+{
+ using namespace qstdweb;
+ using namespace emscripten;
+
+ auto options = LocalFileApi::makeSaveFileOptions(QStringList(), fileNameHint);
+
+ Promise::make(
+ window(), QStringLiteral("showSaveFilePicker"),
+ std::move(callbacks), std::move(options));
+}
+} // namespace
+
+void showOpen(const QStringList &accept, FileSelectMode fileSelectMode,
+ qstdweb::PromiseCallbacks callbacks)
+{
+ hasLocalFilesApi() ?
+ showOpenViaLocalFileApi(accept, fileSelectMode, std::move(callbacks)) :
+ showOpenViaHTMLPolyfill(accept, fileSelectMode, std::move(callbacks));
+}
+
+bool canShowSave()
+{
+ return hasLocalFilesApi();
+}
+
+void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks)
+{
+ Q_ASSERT(canShowSave());
+ showSaveViaLocalFileApi(fileNameHint, std::move(callbacks));
+}
+} // namespace FileDialog
+
+namespace {
void readFiles(const qstdweb::FileList &fileList,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void ()> &fileDataReady)
+ const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<void ()> &fileDataReady)
{
auto readFile = std::make_shared<std::function<void(int)>>();
@@ -25,7 +116,7 @@ void readFiles(const qstdweb::FileList &fileList,
return;
}
- const qstdweb::File file = fileList[fileIndex];
+ const qstdweb::File file = qstdweb::File(fileList[fileIndex]);
// Ask caller if the file should be accepted
char *buffer = acceptFile(file.size(), file.name());
@@ -35,7 +126,7 @@ void readFiles(const qstdweb::FileList &fileList,
}
// Read file data into caller-provided buffer
- file.stream(buffer, [=]() {
+ file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() {
fileDataReady();
(*readFile)(fileIndex + 1);
});
@@ -44,53 +135,21 @@ void readFiles(const qstdweb::FileList &fileList,
(*readFile)(0);
}
-typedef std::function<void (const qstdweb::FileList &fileList)> OpenFileDialogCallback;
-void openFileDialog(const std::string &accept, FileSelectMode fileSelectMode,
- const OpenFileDialogCallback &filesSelected)
+QStringList makeFilterList(const std::string &qtAcceptList)
{
- // Create file input html element which will display a native file dialog
- // and call back to our onchange handler once the user has selected
- // one or more files.
- emscripten::val document = emscripten::val::global("document");
- emscripten::val input = document.call<emscripten::val>("createElement", std::string("input"));
- input.set("type", "file");
- input.set("style", "display:none");
- input.set("accept", emscripten::val(accept));
- input.set("multiple", emscripten::val(fileSelectMode == MultipleFiles));
-
- // Note: there is no event in case the user cancels the file dialog.
- static std::unique_ptr<qstdweb::EventCallback> changeEvent;
- auto callback = [=](emscripten::val) { filesSelected(qstdweb::FileList(input["files"])); };
- changeEvent.reset(new qstdweb::EventCallback(input, "change", callback));
-
- // Activate file input
- emscripten::val body = document["body"];
- body.call<void>("appendChild", input);
- input.call<void>("click");
- body.call<void>("removeChild", input);
-}
+ // copy of qt_make_filter_list() from qfiledialog.cpp
+ auto filter = QString::fromStdString(qtAcceptList);
+ if (filter.isEmpty())
+ return QStringList();
+ QString sep(";;");
+ if (!filter.contains(sep) && filter.contains(u'\n'))
+ sep = u'\n';
-void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
- const std::function<void (int fileCount)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void()> &fileDataReady)
-{
- openFileDialog(accept, fileSelectMode, [=](const qstdweb::FileList &files) {
- fileDialogClosed(files.length());
- readFiles(files, acceptFile, fileDataReady);
- });
+ return filter.split(sep);
}
-
-void openFile(const std::string &accept,
- const std::function<void (bool fileSelected)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
- const std::function<void()> &fileDataReady)
-{
- auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
- openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
}
-void saveFile(const char *content, size_t size, const std::string &fileNameHint)
+void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
{
// Save a file by creating programmatically clicking a download
// link to an object url to a Blob containing a copy of the file
@@ -98,7 +157,7 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint)
// can be released as soon as this function returns.
qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content, size);
emscripten::val document = emscripten::val::global("document");
- emscripten::val window = emscripten::val::global("window");
+ emscripten::val window = qstdweb::window();
emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a"));
contentLink.set("href", contentUrl);
@@ -113,6 +172,118 @@ void saveFile(const char *content, size_t size, const std::string &fileNameHint)
window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl);
}
+void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
+ const std::function<void (int fileCount)> &fileDialogClosed,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
+ const std::function<void()> &fileDataReady)
+{
+ FileDialog::showOpen(makeFilterList(accept), fileSelectMode, {
+ .thenFunc = [=](emscripten::val result) {
+ auto files = qstdweb::FileList(result);
+ fileDialogClosed(files.length());
+ readFiles(files, acceptFile, fileDataReady);
+ },
+ .catchFunc = [=](emscripten::val) {
+ fileDialogClosed(0);
+ }
+ });
+}
+
+void openFile(const std::string &accept,
+ const std::function<void (bool fileSelected)> &fileDialogClosed,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
+ const std::function<void()> &fileDataReady)
+{
+ auto fileDialogClosedWithInt = [=](int fileCount) { fileDialogClosed(fileCount != 0); };
+ openFiles(accept, FileSelectMode::SingleFile, fileDialogClosedWithInt, acceptFile, fileDataReady);
+}
+
+void saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data)
+{
+ using namespace emscripten;
+ using namespace qstdweb;
+
+ Promise::make(fileHandle, QStringLiteral("createWritable"), {
+ .thenFunc = [=](val writable) {
+ struct State {
+ size_t written;
+ std::function<void(val result)> continuation;
+ };
+
+ static constexpr size_t desiredChunkSize = 1024u;
+#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
+ qstdweb::Uint8Array chunkArray(desiredChunkSize);
+#endif
+
+ auto state = std::make_shared<State>();
+ state->written = 0u;
+ state->continuation = [=](val) mutable {
+ const size_t remaining = data.size() - state->written;
+ if (remaining == 0) {
+ Promise::make(writable, QStringLiteral("close"), { .thenFunc = [=](val) {} });
+ state.reset();
+ return;
+ }
+
+ const auto currentChunkSize = std::min(remaining, desiredChunkSize);
+
+#if defined(__EMSCRIPTEN_SHARED_MEMORY__)
+ // If shared memory is used, WebAssembly.Memory is instantiated with the 'shared'
+ // option on. Passing a typed_memory_view to SharedArrayBuffer to
+ // FileSystemWritableFileStream.write is disallowed by security policies, so we
+ // need to make a copy of the data to a chunk array buffer.
+ Promise::make(
+ writable, QStringLiteral("write"),
+ {
+ .thenFunc = state->continuation,
+ },
+ chunkArray.copyFrom(data.constData() + state->written, currentChunkSize)
+ .val()
+ .call<emscripten::val>("subarray", emscripten::val(0),
+ emscripten::val(currentChunkSize)));
+#else
+ Promise::make(writable, QStringLiteral("write"),
+ {
+ .thenFunc = state->continuation,
+ },
+ val(typed_memory_view(currentChunkSize, data.constData() + state->written)));
+#endif
+ state->written += currentChunkSize;
+ };
+
+ state->continuation(val::undefined());
+ },
+ });
+}
+
+void saveFile(const QByteArray &data, const std::string &fileNameHint)
+{
+ if (!FileDialog::canShowSave()) {
+ downloadDataAsFile(data.constData(), data.size(), fileNameHint);
+ return;
+ }
+
+ FileDialog::showSave(fileNameHint, {
+ .thenFunc = [=](emscripten::val result) {
+ saveDataToFileInChunks(result, data);
+ },
+ });
+}
+
+void saveFile(const char *content, size_t size, const std::string &fileNameHint)
+{
+ if (!FileDialog::canShowSave()) {
+ downloadDataAsFile(content, size, fileNameHint);
+ return;
+ }
+
+ FileDialog::showSave(fileNameHint, {
+ .thenFunc = [=](emscripten::val result) {
+ saveDataToFileInChunks(result, QByteArray(content, size));
+ },
+ });
+}
+
} // namespace QWasmLocalFileAccess
QT_END_NAMESPACE
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
index eb73463759..77b14577f7 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h
@@ -23,18 +23,19 @@ QT_BEGIN_NAMESPACE
namespace QWasmLocalFileAccess {
-enum FileSelectMode { SingleFile, MultipleFiles };
+enum class FileSelectMode { SingleFile, MultipleFiles };
Q_CORE_EXPORT void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
const std::function<void (int fileCount)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
const std::function<void()> &fileDataReady);
Q_CORE_EXPORT void openFile(const std::string &accept,
const std::function<void (bool fileSelected)> &fileDialogClosed,
- const std::function<char *(uint64_t size, const std::string name)> &acceptFile,
+ const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
const std::function<void()> &fileDataReady);
+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);
} // namespace QWasmLocalFileAccess
diff --git a/src/gui/platform/wasm/qwasmnativeinterface.cpp b/src/gui/platform/wasm/qwasmnativeinterface.cpp
new file mode 100644
index 0000000000..7313629a8d
--- /dev/null
+++ b/src/gui/platform/wasm/qwasmnativeinterface.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformwindow_p.h>
+
+
+QT_BEGIN_NAMESPACE
+
+using namespace QNativeInterface::Private;
+
+QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWasmWindow);
+
+QT_END_NAMESPACE