Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmclipboard.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
6#include "qwasmdom.h"
7#include "qwasmevent.h"
8#include "qwasmwindow.h"
9
10#include <private/qstdweb_p.h>
11
12#include <QCoreApplication>
13#include <qpa/qwindowsysteminterface.h>
14#include <QBuffer>
15#include <QString>
16
17#include <emscripten/val.h>
18
20using namespace emscripten;
21
22static void commonCopyEvent(val event)
23{
24 QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
25 if (!_mimes)
26 return;
27
28 // doing it this way seems to sanitize the text better that calling data() like down below
29 if (_mimes->hasText()) {
30 event["clipboardData"].call<void>("setData", val("text/plain"),
31 _mimes->text().toEcmaString());
32 }
33 if (_mimes->hasHtml()) {
34 event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
35 }
36
37 for (auto mimetype : _mimes->formats()) {
38 if (mimetype.contains("text/"))
39 continue;
40 QByteArray ba = _mimes->data(mimetype);
41 if (!ba.isEmpty())
42 event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
43 val(ba.constData()));
44 }
45
46 event.call<void>("preventDefault");
47}
48
49void QWasmClipboard::cut(val event)
50{
52 // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
53 QWindowSystemInterface::handleKeyEvent(
54 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
55 }
56
57 commonCopyEvent(event);
58}
59
60void QWasmClipboard::copy(val event)
61{
63 // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
64 QWindowSystemInterface::handleKeyEvent(
65 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
66 }
67 commonCopyEvent(event);
68}
69
70void QWasmClipboard::paste(val event)
71{
72 event.call<void>("preventDefault"); // prevent browser from handling drop event
73
74 QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
75}
76
78{
79 val clipboard = val::global("navigator")["clipboard"];
80
81 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
82 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
83
84 if (m_hasClipboardApi && hasPermissionsApi)
85 initClipboardPermissions();
86
88 val document = val::global("document");
89 m_documentCut = QWasmEventHandler(document, "cut", QWasmClipboard::cut);
90 m_documentCopy = QWasmEventHandler(document, "copy", QWasmClipboard::copy);
91 m_documentPaste = QWasmEventHandler(document, "paste", QWasmClipboard::paste);
92 }
93}
94
98
99QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
100{
101 if (mode != QClipboard::Clipboard)
102 return nullptr;
103
104 return QPlatformClipboard::mimeData(mode);
105}
106
107void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
108{
109 // handle setText/ setData programmatically
110 QPlatformClipboard::setMimeData(mimeData, mode);
111 if (m_hasClipboardApi)
112 writeToClipboardApi();
113 else
114 writeToClipboard();
115}
116
118{
119 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
121
122 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
124
125 const bool isPaste = event.key == Qt::Key_V;
126
127 return m_hasClipboardApi && !isPaste
130}
131
132bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
133{
134 return mode == QClipboard::Clipboard;
135}
136
137bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
138{
139 Q_UNUSED(mode);
140 return false;
141}
142
143void QWasmClipboard::initClipboardPermissions()
144{
145 val permissions = val::global("navigator")["permissions"];
146
147 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
148 val readPermissionsMap = val::object();
149 readPermissionsMap.set("name", val("clipboard-read"));
150 return readPermissionsMap;
151 })());
152 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
153 val readPermissionsMap = val::object();
154 readPermissionsMap.set("name", val("clipboard-write"));
155 return readPermissionsMap;
156 })());
157}
158
160{
161 return m_hasClipboardApi;
162}
163
165{
166 // Chrome uses global handlers
167 return val::global("window")["chrome"].isUndefined();
168}
169
170void QWasmClipboard::writeToClipboardApi()
171{
172 Q_ASSERT(m_hasClipboardApi);
173
174 QMimeData *mimeData = this->mimeData(QClipboard::Clipboard);
175 if (!mimeData)
176 return;
177
178 // Support for plain text, html and images (png) are standardized,
179 // copy those to the clipboard data object.
180 emscripten::val clipboardData = emscripten::val::object();
181 for (const QString &mimetype: mimeData->formats()) {
182 if (mimetype == QLatin1String("text/plain")) {
183 emscripten::val text = mimeData->text().toEcmaString();
184 clipboardData.set(mimetype.toEcmaString(), text);
185 } else if (mimetype == QLatin1String("text/html")) {
186 emscripten::val html = mimeData->html().toEcmaString();
187 clipboardData.set(mimetype.toEcmaString(), html);
188 } else if (mimetype.contains(QLatin1String("image"))) {
189 // Serialize the Qt image data to browser supported png
190 QImage img = qvariant_cast<QImage>(mimeData->imageData());
191 QByteArray ba;
192 QBuffer buffer(&ba);
193 buffer.open(QIODevice::WriteOnly);
194 img.save(&buffer, "PNG");
195
196 qstdweb::Blob blob = qstdweb::Blob::fromArrayBuffer(qstdweb::Uint8Array::copyFrom(ba).buffer());
197 clipboardData.set(std::string("image/png"), blob.val());
198 }
199 }
200
201 // Return if there is no data (creating an empty ClipboardItem is an error)
202 if (val::global("Object").call<val>("keys", clipboardData)["length"].as<int>() == 0)
203 return;
204
205 // Write a single clipboard item containing the data formats to the clipboard
206 emscripten::val clipboardItem = val::global("ClipboardItem").new_(clipboardData);
207 emscripten::val clipboardItemArray = emscripten::val::array();
208 clipboardItemArray.call<void>("push", clipboardItem);
209 val navigator = val::global("navigator");
210 qstdweb::Promise::make(
211 navigator["clipboard"], "write",
212 {
213 .catchFunc = [](emscripten::val error) {
214 qWarning() << "clipboard error"
215 << QString::fromStdString(error["name"].as<std::string>())
216 << QString::fromStdString(error["message"].as<std::string>());
217 }
218 },
219 clipboardItemArray);
220}
221
222void QWasmClipboard::writeToClipboard()
223{
224 // this works for firefox, chrome by generating
225 // copy event, but not safari
226 // execCommand has been deemed deprecated in the docs, but browsers do not seem
227 // interested in removing it. There is no replacement, so we use it here.
228 val document = val::global("document");
229 document.call<val>("execCommand", val("copy"));
230}
231
232void QWasmClipboard::sendClipboardData(emscripten::val event)
233{
234 dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
235 const auto mimeCallback = std::function([transfer](QMimeData *data) {
236
237 // Persist clipboard data so that the app can read it when handling the CTRL+V
238 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
239 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
240 Qt::ControlModifier, "V");
241 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
242 Qt::ControlModifier, "V");
243 delete transfer;
244 });
245
246 transfer->toMimeDataWithFile(mimeCallback);
247}
248QT_END_NAMESPACE
virtual ~QWasmClipboard()
static bool shouldInstallWindowEventHandlers()
void setMimeData(QMimeData *data, QClipboard::Mode mode=QClipboard::Clipboard) override
bool supportsMode(QClipboard::Mode mode) const override
QMimeData * mimeData(QClipboard::Mode mode=QClipboard::Clipboard) override
ProcessKeyboardResult processKeyboard(const KeyEvent &event)
bool ownsMode(QClipboard::Mode mode) const override
static QWasmIntegration * get()
QWasmClipboard * getWasmClipboard()
Definition qwasmdom.h:29
static void commonCopyEvent(val event)