diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmclipboard.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmclipboard.cpp | 359 |
1 files changed, 217 insertions, 142 deletions
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp index 713adee8f9..1aa3ffa5b3 100644 --- a/src/plugins/platforms/wasm/qwasmclipboard.cpp +++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp @@ -1,123 +1,79 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 or (at your option) 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.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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmclipboard.h" +#include "qwasmdom.h" +#include "qwasmevent.h" #include "qwasmwindow.h" -#include "qwasmstring.h" -#include <emscripten.h> -#include <emscripten/html5.h> -#include <emscripten/bind.h> +#include <private/qstdweb_p.h> #include <QCoreApplication> #include <qpa/qwindowsysteminterface.h> +#include <QBuffer> +#include <QString> -using namespace emscripten; - -// there has got to be a better way... -static QString g_clipboardText; -static QString g_clipboardFormat; +#include <emscripten/val.h> -static val getClipboardData() -{ - return QWasmString::fromQString(g_clipboardText); -} +QT_BEGIN_NAMESPACE +using namespace emscripten; -static val getClipboardFormat() +static void commonCopyEvent(val event) { - return QWasmString::fromQString(g_clipboardFormat); -} + QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard); + if (!_mimes) + return; + + // doing it this way seems to sanitize the text better that calling data() like down below + if (_mimes->hasText()) { + event["clipboardData"].call<void>("setData", val("text/plain"), + _mimes->text().toEcmaString()); + } + if (_mimes->hasHtml()) { + event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString()); + } -static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr) -{ - QString formatString = QWasmString::toQString(format); - QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>()); - QMimeData *mMimeData = new QMimeData; - mMimeData->setData(formatString, dataArray); - QWasmClipboard::qWasmClipboardPaste(mMimeData); -} + for (auto mimetype : _mimes->formats()) { + if (mimetype.contains("text/")) + continue; + QByteArray ba = _mimes->data(mimetype); + if (!ba.isEmpty()) + event["clipboardData"].call<void>("setData", mimetype.toEcmaString(), + val(ba.constData())); + } -static void qClipboardPromiseResolve(emscripten::val something) -{ - pasteClipboardData(emscripten::val("text/plain"), something); + event.call<void>("preventDefault"); } static void qClipboardCutTo(val event) { - if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) { + if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) { // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard - QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( - 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X"); - } + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X"); + } - val module = val::global("Module"); - val clipdata = module.call<val>("qtGetClipboardData"); - val clipFormat = module.call<val>("qtGetClipboardFormat"); - event["clipboardData"].call<void>("setData", clipFormat, clipdata); - event.call<void>("preventDefault"); + commonCopyEvent(event); } static void qClipboardCopyTo(val event) { - if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) { + if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) { // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard - QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( - 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C"); + QWindowSystemInterface::handleKeyEvent( + 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C"); } - - val module = val::global("Module"); - val clipdata = module.call<val>("qtGetClipboardData"); - val clipFormat = module.call<val>("qtGetClipboardFormat"); - event["clipboardData"].call<void>("setData", clipFormat, clipdata); - event.call<void>("preventDefault"); + commonCopyEvent(event); } static void qClipboardPasteTo(val event) { - bool hasClipboardApi = QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi; - val clipdata = hasClipboardApi ? - val::global("Module").call<val>("qtGetClipboardData") : - event["clipboardData"].call<val>("getData", val("text")); + event.call<void>("preventDefault"); // prevent browser from handling drop event - const QString qstr = QWasmString::toQString(clipdata); - if (qstr.length() > 0) { - QMimeData *mMimeData = new QMimeData; - mMimeData->setText(qstr); - QWasmClipboard::qWasmClipboardPaste(mMimeData); - } + QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event); } EMSCRIPTEN_BINDINGS(qtClipboardModule) { - function("qtGetClipboardData", &getClipboardData); - function("qtGetClipboardFormat", &getClipboardFormat); - function("qtPasteClipboardData", &pasteClipboardData); - function("qtClipboardPromiseResolve", &qClipboardPromiseResolve); function("qtClipboardCutTo", &qClipboardCutTo); function("qtClipboardCopyTo", &qClipboardCopyTo); function("qtClipboardPasteTo", &qClipboardPasteTo); @@ -126,19 +82,19 @@ EMSCRIPTEN_BINDINGS(qtClipboardModule) { QWasmClipboard::QWasmClipboard() { val clipboard = val::global("navigator")["clipboard"]; - val permissions = val::global("navigator")["permissions"]; - hasClipboardApi = (!clipboard.isUndefined() && !permissions.isUndefined() && !clipboard["readText"].isUndefined()); - if (hasClipboardApi) - initClipboardEvents(); + + const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined(); + m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined(); + + if (m_hasClipboardApi && hasPermissionsApi) + initClipboardPermissions(); } QWasmClipboard::~QWasmClipboard() { - g_clipboardText.clear(); - g_clipboardFormat.clear(); } -QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode) +QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode) { if (mode != QClipboard::Clipboard) return nullptr; @@ -146,17 +102,29 @@ QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode) return QPlatformClipboard::mimeData(mode); } -void QWasmClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode) +void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode) { - if (mimeData->hasText()) { - g_clipboardFormat = mimeData->formats().at(0); - g_clipboardText = mimeData->text(); - } else if (mimeData->hasHtml()) { - g_clipboardFormat = mimeData->formats().at(0); - g_clipboardText = mimeData->html(); - } - + // handle setText/ setData programmatically QPlatformClipboard::setMimeData(mimeData, mode); + if (m_hasClipboardApi) + writeToClipboardApi(); + else + writeToClipboard(); +} + +QWasmClipboard::ProcessKeyboardResult QWasmClipboard::processKeyboard(const KeyEvent &event) +{ + if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier)) + return ProcessKeyboardResult::Ignored; + + if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X) + return ProcessKeyboardResult::Ignored; + + const bool isPaste = event.key == Qt::Key_V; + + return m_hasClipboardApi && !isPaste + ? ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded + : ProcessKeyboardResult::NativeClipboardEventNeeded; } bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const @@ -170,60 +138,167 @@ bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const return false; } -void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData) +void QWasmClipboard::initClipboardPermissions() { - QWasmIntegration::get()->clipboard()->setMimeData(mData, QClipboard::Clipboard); + val permissions = val::global("navigator")["permissions"]; - QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( - 0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V"); + qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() { + val readPermissionsMap = val::object(); + readPermissionsMap.set("name", val("clipboard-read")); + return readPermissionsMap; + })()); + qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() { + val readPermissionsMap = val::object(); + readPermissionsMap.set("name", val("clipboard-write")); + return readPermissionsMap; + })()); } -void QWasmClipboard::initClipboardEvents() +void QWasmClipboard::installEventHandlers(const emscripten::val &target) { - if (!hasClipboardApi) - return; - - val permissions = val::global("navigator")["permissions"]; - val readPermissionsMap = val::object(); - readPermissionsMap.set("name", val("clipboard-read")); - permissions.call<val>("query", readPermissionsMap); + emscripten::val cContext = val::undefined(); + emscripten::val isChromium = val::global("window")["chrome"]; + if (!isChromium.isUndefined()) { + cContext = val::global("document"); + } else { + cContext = target; + } + // Fallback path for browsers which do not support direct clipboard access + cContext.call<void>("addEventListener", val("cut"), + val::module_property("qtClipboardCutTo"), true); + cContext.call<void>("addEventListener", val("copy"), + val::module_property("qtClipboardCopyTo"), true); + cContext.call<void>("addEventListener", val("paste"), + val::module_property("qtClipboardPasteTo"), true); +} - val writePermissionsMap = val::object(); - writePermissionsMap.set("name", val("clipboard-write")); - permissions.call<val>("query", writePermissionsMap); +bool QWasmClipboard::hasClipboardApi() +{ + return m_hasClipboardApi; } -void QWasmClipboard::installEventHandlers(const emscripten::val &canvas) +void QWasmClipboard::writeToClipboardApi() { - if (hasClipboardApi) + Q_ASSERT(m_hasClipboardApi); + + // copy event + // browser event handler detected ctrl c if clipboard API + // or Qt call from keyboard event handler + + QMimeData *_mimes = mimeData(QClipboard::Clipboard); + if (!_mimes) return; - // Fallback path for browsers which do not support direct clipboard access - canvas.call<void>("addEventListener", val("cut"), - val::module_property("qtClipboardCutTo")); - canvas.call<void>("addEventListener", val("copy"), - val::module_property("qtClipboardCopyTo")); - canvas.call<void>("addEventListener", val("paste"), - val::module_property("qtClipboardPasteTo")); + emscripten::val clipboardWriteArray = emscripten::val::array(); + QByteArray ba; + + for (auto mimetype : _mimes->formats()) { + // we need to treat binary and text differently, as the blob method below + // fails for text mimetypes + // ignore text types + + if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive)) + continue; + + if (_mimes->hasHtml()) { // prefer html over text + ba = _mimes->html().toLocal8Bit(); + // force this mime + mimetype = "text/html"; + } else if (mimetype.contains("text/plain")) { + ba = _mimes->text().toLocal8Bit(); + } else if (mimetype.contains("image")) { + QImage img = qvariant_cast<QImage>( _mimes->imageData()); + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + mimetype = "image/png"; // chrome only allows png + // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write." + // safari silently fails + // so we use png internally for now + } else { + // DATA + ba = _mimes->data(mimetype); + } + // Create file data Blob + + const char *content = ba.data(); + int dataLength = ba.length(); + if (dataLength < 1) { + qDebug() << "no content found"; + return; + } + + emscripten::val document = emscripten::val::global("document"); + emscripten::val window = emscripten::val::global("window"); + + emscripten::val fileContentView = + emscripten::val(emscripten::typed_memory_view(dataLength, content)); + emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength); + emscripten::val fileContentCopyView = + emscripten::val::global("Uint8Array").new_(fileContentCopy); + fileContentCopyView.call<void>("set", fileContentView); + + emscripten::val contentArray = emscripten::val::array(); + contentArray.call<void>("push", fileContentCopyView); + + // we have a blob, now create a ClipboardItem + emscripten::val type = emscripten::val::array(); + type.set("type", mimetype.toEcmaString()); + + emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type); + + emscripten::val clipboardItemObject = emscripten::val::object(); + clipboardItemObject.set(mimetype.toEcmaString(), contentBlob); + + val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject); + + clipboardWriteArray.call<void>("push", clipboardItemData); + + // Clipboard write is only supported with one ClipboardItem at the moment + // but somehow this still works? + // break; + } + + val navigator = val::global("navigator"); + + qstdweb::Promise::make( + navigator["clipboard"], "write", + { + .catchFunc = [](emscripten::val error) { + qWarning() << "clipboard error" + << QString::fromStdString(error["name"].as<std::string>()) + << QString::fromStdString(error["message"].as<std::string>()); + } + }, + clipboardWriteArray); } -void QWasmClipboard::readTextFromClipboard() +void QWasmClipboard::writeToClipboard() { - if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) { - val navigator = val::global("navigator"); - val textPromise = navigator["clipboard"].call<val>("readText"); - val readTextResolve = val::global("Module")["qtClipboardPromiseResolve"]; - textPromise.call<val>("then", readTextResolve); - } + // this works for firefox, chrome by generating + // copy event, but not safari + // execCommand has been deemed deprecated in the docs, but browsers do not seem + // interested in removing it. There is no replacement, so we use it here. + val document = val::global("document"); + document.call<val>("execCommand", val("copy")); } -void QWasmClipboard::writeTextToClipboard() +void QWasmClipboard::sendClipboardData(emscripten::val event) { - if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) { - val module = val::global("Module"); - val txt = module.call<val>("qtGetClipboardData"); - val format = module.call<val>("qtGetClipboardFormat"); - val navigator = val::global("navigator"); - navigator["clipboard"].call<void>("writeText", txt); - } + qDebug() << "sendClipboardData"; + + dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]); + const auto mimeCallback = std::function([transfer](QMimeData *data) { + + // Persist clipboard data so that the app can read it when handling the CTRL+V + QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard); + QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V, + Qt::ControlModifier, "V"); + QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V, + Qt::ControlModifier, "V"); + delete transfer; + }); + + transfer->toMimeDataWithFile(mimeCallback); } +QT_END_NAMESPACE |