diff options
Diffstat (limited to 'src/gui/platform/wasm')
-rw-r--r-- | src/gui/platform/wasm/qlocalfileapi.cpp | 211 | ||||
-rw-r--r-- | src/gui/platform/wasm/qlocalfileapi_p.h | 94 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess.cpp | 335 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmlocalfileaccess_p.h | 55 | ||||
-rw-r--r-- | src/gui/platform/wasm/qwasmnativeinterface.cpp | 17 |
5 files changed, 541 insertions, 171 deletions
diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp new file mode 100644 index 0000000000..76b99361c4 --- /dev/null +++ b/src/gui/platform/wasm/qlocalfileapi.cpp @@ -0,0 +1,211 @@ +// Copyright (C) 2022 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 "qlocalfileapi_p.h" +#include <private/qstdweb_p.h> +#include <QtCore/QRegularExpression> + +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) { + 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_description(description.trimmed()), m_accept(std::move(accept)) +{ +} + +Type::~Type() = default; + +std::optional<Type> Type::fromQt(QStringView type) +{ + using namespace emscripten; + + // Accepts either a string in format: + // GROUP3 + // or in this format: + // GROUP1 (GROUP2) + // 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.matchView(type); + + if (!match.hasMatch()) + return std::nullopt; + + constexpr size_t DescriptionIndex = 1; + constexpr size_t FilterListFromParensIndex = 2; + constexpr size_t PlainFilterListIndex = 3; + + const auto description = match.hasCaptured(DescriptionIndex) + ? match.capturedView(DescriptionIndex) + : QStringView(); + const auto filterList = match.capturedView(match.hasCaptured(FilterListFromParensIndex) + ? FilterListFromParensIndex + : PlainFilterListIndex); + + auto accept = Type::Accept::fromQt(filterList); + if (!accept) + return std::nullopt; + + return Type(description, std::move(*accept)); +} + +Type::Accept::Accept() = default; + +Type::Accept::~Accept() = default; + +std::optional<Type::Accept> Type::Accept::fromQt(QStringView qtRepresentation) +{ + Accept accept; + + // Used for accepting multiple extension specifications on a filter list. + // The next group of non-empty characters. + static QRegularExpression internalRegex(QString(QStringLiteral("([^\\s]+)\\s*"))); + int offset = 0; + auto internalMatch = internalRegex.matchView(qtRepresentation, offset); + MimeType mimeType; + + while (internalMatch.hasMatch()) { + auto webExtension = MimeType::Extension::fromQt(internalMatch.capturedView(1)); + + if (!webExtension) + return std::nullopt; + + mimeType.addExtension(*webExtension); + + internalMatch = internalRegex.matchView(qtRepresentation, internalMatch.capturedEnd()); + } + + accept.setMimeType(mimeType); + return accept; +} + +void Type::Accept::setMimeType(MimeType mimeType) +{ + m_mimeType = std::move(mimeType); +} + +Type::Accept::MimeType::MimeType() = default; + +Type::Accept::MimeType::~MimeType() = default; + +void Type::Accept::MimeType::addExtension(Extension extension) +{ + m_extensions.push_back(std::move(extension)); +} + +Type::Accept::MimeType::Extension::Extension(QStringView extension) : m_value(extension) { } + +Type::Accept::MimeType::Extension::~Extension() = default; + +std::optional<Type::Accept::MimeType::Extension> +Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation) +{ + // Checks for a filter that matches everything: + // Any number of asterisks or any number of asterisks with a '.' between them. + // The web filter does not support wildcards. + static QRegularExpression qtAcceptAllRegex( + QRegularExpression::anchoredPattern(QString(QStringLiteral("[*]+|[*]+\\.[*]+")))); + if (qtAcceptAllRegex.matchView(qtRepresentation).hasMatch()) + return std::nullopt; + + // Checks for correctness. The web filter only allows filename extensions and does not filter + // the actual filenames, therefore we check whether the filter provided only filters for the + // extension. + static QRegularExpression qtFilenameMatcherRegex( + QRegularExpression::anchoredPattern(QString(QStringLiteral("(\\*?)(\\.[^*]+)")))); + + auto extensionMatch = qtFilenameMatcherRegex.matchView(qtRepresentation); + if (extensionMatch.hasMatch()) + return Extension(extensionMatch.capturedView(2)); + + // Mapping impossible. + return std::nullopt; +} + +emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple) +{ + auto options = emscripten::val::object(); + if (auto typeList = qtFilterListToTypes(filterList); typeList) { + options.set("types", std::move(*typeList)); + options.set("excludeAcceptAllOption", true); + } + + options.set("multiple", acceptMultiple); + + return options; +} + +emscripten::val makeSaveFileOptions(const QStringList &filterList, const std::string& suggestedName) +{ + auto options = emscripten::val::object(); + + if (!suggestedName.empty()) + options.set("suggestedName", emscripten::val(suggestedName)); + + if (auto typeList = qtFilterListToTypes(filterList)) + options.set("types", emscripten::val(std::move(*typeList))); + + return options; +} + +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 new file mode 100644 index 0000000000..1398d674d8 --- /dev/null +++ b/src/gui/platform/wasm/qlocalfileapi_p.h @@ -0,0 +1,94 @@ +// Copyright (C) 2022 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 + +#ifndef QLOCALFILEAPI_P_H +#define QLOCALFILEAPI_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qglobal_p.h> +#include <qstringview.h> +#include <emscripten/val.h> +#include <cstdint> +#include <functional> + +QT_BEGIN_NAMESPACE + +namespace LocalFileApi { +class Q_AUTOTEST_EXPORT Type +{ +public: + class Accept { + public: + class MimeType { + public: + class Extension { + public: + static std::optional<Extension> fromQt(QStringView extension); + + ~Extension(); + + const QStringView &value() const { return m_value; } + + private: + explicit Extension(QStringView extension); + + QStringView m_value; + }; + + MimeType(); + ~MimeType(); + + void addExtension(Extension type); + + const std::vector<Extension> &extensions() const { return m_extensions; } + + private: + std::vector<Extension> m_extensions; + }; + + static std::optional<Accept> fromQt(QStringView type); + + ~Accept(); + + void setMimeType(MimeType mimeType); + + const MimeType &mimeType() const { return m_mimeType; } + + private: + Accept(); + MimeType m_mimeType; + }; + + Type(QStringView description, std::optional<Accept> accept); + ~Type(); + + static std::optional<Type> fromQt(QStringView type); + const QStringView &description() const { return m_description; } + const std::optional<Accept> &accept() const { return m_accept; } + +private: + QStringView m_description; + std::optional<Accept> m_accept; +}; + +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 + +#endif // QLOCALFILEAPI_P_H diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp index 85c894a74a..a946cda043 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp +++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp @@ -1,94 +1,111 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 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 "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 streamFile(const qstdweb::File &file, uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) +void showOpenViaHTMLPolyfill(const QStringList &accept, FileSelectMode fileSelectMode, + qstdweb::PromiseCallbacks onFilesSelected) { - // Read file in chunks in order to avoid holding two copies in memory at the same time - const uint32_t chunkSize = 256 * 1024; - const uint32_t end = offset + length; - // assert end < file.size - auto fileReader = std::make_shared<qstdweb::FileReader>(); - - auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>(); - *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable { - - // Copy current chunk from JS memory to Wasm memory - qstdweb::ArrayBuffer result = fileReader->result(); - qstdweb::Uint8Array(result).copyTo(chunkBuffer); - - // Read next chunk if not at buffer end - uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end); - uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end); - if (nextChunkBegin == end) { - completed(); - chunkCompleted.reset(); - return; - } - char *nextChunkBuffer = chunkBuffer + result.byteLength(); - fileReader->onLoad([=]() { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); }); - qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd); - fileReader->readAsArrayBuffer(blob); - }; + // 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 - // Read first chunk. First iteration is a dummy iteration with no available data. - (*chunkCompleted)(offset, buffer); +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 streamFile(const qstdweb::File &file, char *buffer, const std::function<void ()> &completed) +void showSave(const std::string &fileNameHint, qstdweb::PromiseCallbacks callbacks) { - streamFile(file, 0, file.size(), buffer, completed); + 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)>>(); @@ -99,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()); @@ -109,7 +126,7 @@ void readFiles(const qstdweb::FileList &fileList, } // Read file data into caller-provided buffer - streamFile(file, buffer, [=]() { + file.stream(buffer, [readFile = readFile.get(), fileIndex, fileDataReady]() { fileDataReady(); (*readFile)(fileIndex + 1); }); @@ -118,87 +135,153 @@ 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)); + // 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'; - // Note: there is no event in case the user cancels the file dialog. - static std::unique_ptr<qstdweb::EventCallback> changeEvent; - auto callback = [=]() { filesSelected(qstdweb::FileList(input["files"])); }; - changeEvent.reset(new qstdweb::EventCallback(input, "change", callback)); + return filter.split(sep); +} +} + +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 + // content. The copy is made so that the passed in content buffer + // 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 = 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); + contentLink.set("download", fileNameHint); + contentLink.set("style", "display:none"); - // Activate file input emscripten::val body = document["body"]; - body.call<void>("appendChild", input); - input.call<void>("click"); - body.call<void>("removeChild", input); + body.call<void>("appendChild", contentLink); + contentLink.call<void>("click"); + body.call<void>("removeChild", contentLink); + + 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<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); + 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<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 saveDataToFileInChunks(emscripten::val fileHandle, const QByteArray &data) { - // Save a file by creating programatically clicking a download - // link to an object url to a Blob containing the file content. - // File content is copied once, so that the passed in content - // buffer can be released as soon as this function returns - we - // don't know for how long the browser will retain the TypedArray - // view used to create the Blob. + using namespace emscripten; + using namespace qstdweb; - emscripten::val document = emscripten::val::global("document"); - emscripten::val window = emscripten::val::global("window"); + Promise::make(fileHandle, QStringLiteral("createWritable"), { + .thenFunc = [=](val writable) { + struct State { + size_t written; + std::function<void(val result)> continuation; + }; - emscripten::val fileContentView = emscripten::val(emscripten::typed_memory_view(size, content)); - emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(size); - emscripten::val fileContentCopyView = emscripten::val::global("Uint8Array").new_(fileContentCopy); - fileContentCopyView.call<void>("set", fileContentView); + static constexpr size_t desiredChunkSize = 1024u; +#if defined(__EMSCRIPTEN_SHARED_MEMORY__) + qstdweb::Uint8Array chunkArray(desiredChunkSize); +#endif - emscripten::val contentArray = emscripten::val::array(); - contentArray.call<void>("push", fileContentCopyView); - emscripten::val type = emscripten::val::object(); - type.set("type","application/octet-stream"); - emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type); + 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; + } - emscripten::val contentUrl = window["URL"].call<emscripten::val>("createObjectURL", contentBlob); - emscripten::val contentLink = document.call<emscripten::val>("createElement", std::string("a")); - contentLink.set("href", contentUrl); - contentLink.set("download", fileNameHint); - contentLink.set("style", "display:none"); + const auto currentChunkSize = std::min(remaining, desiredChunkSize); - emscripten::val body = document["body"]; - body.call<void>("appendChild", contentLink); - contentLink.call<void>("click"); - body.call<void>("removeChild", contentLink); +#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; + }; - window["URL"].call<emscripten::val>("revokeObjectURL", contentUrl); + 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 diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h index ccd88570c8..77b14577f7 100644 --- a/src/gui/platform/wasm/qwasmlocalfileaccess_p.h +++ b/src/gui/platform/wasm/qwasmlocalfileaccess_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 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 #ifndef QWASMLOCALFILEACCESS_P_H #define QWASMLOCALFILEACCESS_P_H @@ -51,7 +15,7 @@ // We mean it. // -#include <qglobal.h> +#include <private/qglobal_p.h> #include <cstdint> #include <functional> @@ -59,19 +23,20 @@ QT_BEGIN_NAMESPACE namespace QWasmLocalFileAccess { -enum FileSelectMode { SingleFile, MultipleFiles }; +enum class FileSelectMode { SingleFile, MultipleFiles }; -void openFiles(const std::string &accept, FileSelectMode fileSelectMode, +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); -void openFile(const std::string &accept, +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); -void saveFile(const char *content, size_t size, const std::string &fileNameHint); +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 |