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// Qt-Security score:significant reason:default
4
7#include "qwasmdom.h"
8#include "qwasmevent.h"
9#include "qwasmwindow.h"
10
11#include <private/qstdweb_p.h>
12
13#include <QCoreApplication>
14#include <qpa/qwindowsysteminterface.h>
15#include <QBuffer>
16#include <QString>
17
18#include <emscripten/val.h>
19
21using namespace emscripten;
22
23static void commonCopyEvent(val event)
24{
25 QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
26 if (!_mimes)
27 return;
28
29 // doing it this way seems to sanitize the text better that calling data() like down below
30 if (_mimes->hasText()) {
31 event["clipboardData"].call<void>("setData", val("text/plain"),
32 _mimes->text().toEcmaString());
33 }
34 if (_mimes->hasHtml()) {
35 event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
36 }
37
38 for (auto mimetype : _mimes->formats()) {
39 if (mimetype.contains("text/"))
40 continue;
41 QByteArray ba = _mimes->data(mimetype);
42 if (!ba.isEmpty())
43 event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
44 val(ba.constData()));
45 }
46
47 event.call<void>("preventDefault");
48}
49
50void QWasmClipboard::cut(val event)
51{
53 // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
54 QWindowSystemInterface::handleKeyEvent(
55 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
56 }
57
58 commonCopyEvent(event);
59}
60
61void QWasmClipboard::copy(val event)
62{
64 // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
65 QWindowSystemInterface::handleKeyEvent(
66 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
67 }
68 commonCopyEvent(event);
69}
70
71// Returns true if the event targets an element in a Qt window.
72static bool eventTargetsQtWindow(val event)
73{
74 val target = event["target"];
75 if (target.isNull() || target.isUndefined() || target["closest"].isUndefined())
76 return false;
77 return !target.call<val>("closest", val(".qt-window")).isNull();
78}
79
80void QWasmClipboard::paste(val event)
81{
82 // The document-level handler sees paste events for the whole page. Leave
83 // events for non-Qt elements to the browser.
84 if (!eventTargetsQtWindow(event))
85 return;
86
87 // sendClipboardData() delivers the data via a synthetic Ctrl+V. Cancel the default
88 // action, which would insert it again as an "insertFromPaste" input event.
89 event.call<void>("preventDefault");
90
91 QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
92}
93
95{
96 val clipboard = val::global("navigator")["clipboard"];
97
98 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
99 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
100
101 if (m_hasClipboardApi && hasPermissionsApi)
102 initClipboardPermissions();
103
105 val document = val::global("document");
106 m_documentCut = QWasmEventHandler(document, "cut", QWasmClipboard::cut);
107 m_documentCopy = QWasmEventHandler(document, "copy", QWasmClipboard::copy);
108 m_documentPaste = QWasmEventHandler(document, "paste", QWasmClipboard::paste);
109 }
110}
111
115
116QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
117{
118 if (mode != QClipboard::Clipboard)
119 return nullptr;
120
121 return QPlatformClipboard::mimeData(mode);
122}
123
124void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
125{
126 // handle setText/ setData programmatically
127 QPlatformClipboard::setMimeData(mimeData, mode);
128 if (m_hasClipboardApi)
129 writeToClipboardApi();
130 else
131 writeToClipboard();
132}
133
135{
136 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
138
139 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
141
142 const bool isPaste = event.key == Qt::Key_V;
143
144 return m_hasClipboardApi && !isPaste
147}
148
149bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
150{
151 return mode == QClipboard::Clipboard;
152}
153
154bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
155{
156 Q_UNUSED(mode);
157 return false;
158}
159
160void QWasmClipboard::initClipboardPermissions()
161{
162 val permissions = val::global("navigator")["permissions"];
163
164 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
165 val readPermissionsMap = val::object();
166 readPermissionsMap.set("name", val("clipboard-read"));
167 return readPermissionsMap;
168 })());
169 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
170 val readPermissionsMap = val::object();
171 readPermissionsMap.set("name", val("clipboard-write"));
172 return readPermissionsMap;
173 })());
174}
175
177{
178 return m_hasClipboardApi;
179}
180
182{
183 // Chrome uses global handlers
184 return val::global("window")["chrome"].isUndefined();
185}
186
187void QWasmClipboard::writeToClipboardApi()
188{
189 Q_ASSERT(m_hasClipboardApi);
190
191 QMimeData *mimeData = this->mimeData(QClipboard::Clipboard);
192 if (!mimeData)
193 return;
194
195 // Support for plain text, html and images (png) are standardized,
196 // copy those to the clipboard data object.
197 emscripten::val clipboardData = emscripten::val::object();
198 for (const QString &mimetype: mimeData->formats()) {
199 if (mimetype == QLatin1String("text/plain")) {
200 emscripten::val text = mimeData->text().toEcmaString();
201 clipboardData.set(mimetype.toEcmaString(), text);
202 } else if (mimetype == QLatin1String("text/html")) {
203 emscripten::val html = mimeData->html().toEcmaString();
204 clipboardData.set(mimetype.toEcmaString(), html);
205 } else if (mimetype.contains(QLatin1String("image"))) {
206 // Serialize the Qt image data to browser supported png
207 QImage img = qvariant_cast<QImage>(mimeData->imageData());
208 QByteArray ba;
209 QBuffer buffer(&ba);
210 buffer.open(QIODevice::WriteOnly);
211 img.save(&buffer, "PNG");
212
213 qstdweb::Blob blob = qstdweb::Blob::fromArrayBuffer(qstdweb::Uint8Array::copyFrom(ba).buffer());
214 clipboardData.set(std::string("image/png"), blob.val());
215 }
216 }
217
218 // Return if there is no data (creating an empty ClipboardItem is an error)
219 if (val::global("Object").call<val>("keys", clipboardData)["length"].as<int>() == 0)
220 return;
221
222 // Write a single clipboard item containing the data formats to the clipboard
223 emscripten::val clipboardItem = val::global("ClipboardItem").new_(clipboardData);
224 emscripten::val clipboardItemArray = emscripten::val::array();
225 clipboardItemArray.call<void>("push", clipboardItem);
226 val navigator = val::global("navigator");
227 qstdweb::Promise::make(
228 navigator["clipboard"], "write",
229 {
230 .catchFunc = [](emscripten::val error) {
231 qWarning() << "clipboard error"
232 << QString::fromStdString(error["name"].as<std::string>())
233 << QString::fromStdString(error["message"].as<std::string>());
234 }
235 },
236 clipboardItemArray);
237}
238
239void QWasmClipboard::writeToClipboard()
240{
241 // this works for firefox, chrome by generating
242 // copy event, but not safari
243 // execCommand has been deemed deprecated in the docs, but browsers do not seem
244 // interested in removing it. There is no replacement, so we use it here.
245 val document = val::global("document");
246 document.call<val>("execCommand", val("copy"));
247}
248
249void QWasmClipboard::sendClipboardData(emscripten::val event)
250{
251 dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
252 const auto mimeCallback = std::function([transfer](QMimeData *data) {
253
254 // Persist clipboard data so that the app can read it when handling the CTRL+V
255 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
256 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
257 Qt::ControlModifier, "V");
258 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
259 Qt::ControlModifier, "V");
260 delete transfer;
261 });
262
263 transfer->toMimeDataWithFile(mimeCallback);
264}
265QT_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()
Combined button and popup list for selecting options.
Definition qwasmdom.h:30
static bool eventTargetsQtWindow(val event)
static void commonCopyEvent(val event)
EventType
Definition qwasmevent.h:23