summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmclipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmclipboard.cpp')
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp344
1 files changed, 217 insertions, 127 deletions
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 222dcff7fa..1aa3ffa5b3 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -1,111 +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;
+#include <emscripten/val.h>
-// there has got to be a better way...
-static QString g_clipboardText;
-static QString g_clipboardFormat;
+QT_BEGIN_NAMESPACE
+using namespace emscripten;
-static val getClipboardData()
+static void commonCopyEvent(val event)
{
- return QWasmString::fromQString(g_clipboardText);
-}
+ QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
+ if (!_mimes)
+ return;
-static val getClipboardFormat()
-{
- return QWasmString::fromQString(g_clipboardFormat);
-}
+ // 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");
- }
- event["clipboardData"].call<void>("setData", getClipboardFormat(), getClipboardData());
- event.call<void>("preventDefault");
+ QWindowSystemInterface::handleKeyEvent(
+ 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
+ }
+
+ 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");
}
- event["clipboardData"].call<void>("setData", getClipboardFormat(), getClipboardData());
- event.call<void>("preventDefault");
+ commonCopyEvent(event);
}
static void qClipboardPasteTo(val event)
{
- bool hasClipboardApi = QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi;
- val clipdata = hasClipboardApi ? getClipboardData() :
- 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("qtClipboardPromiseResolve", &qClipboardPromiseResolve);
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
@@ -114,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;
@@ -134,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
@@ -158,57 +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::module_property("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 navigator = val::global("navigator");
- navigator["clipboard"].call<void>("writeText", getClipboardData());
- }
+ 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