diff options
Diffstat (limited to 'src/gui/platform/wasm')
-rw-r--r-- | src/gui/platform/wasm/qlocalfileapi.cpp | 108 | ||||
-rw-r--r-- | src/gui/platform/wasm/qlocalfileapi_p.h | 31 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 265 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 7 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmnativeinterface.cpp | 17 |
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 |