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
50// Returns the Qt window element the event originated in, found through the
51// composed path and .qt-window, or null if it did not originate in a Qt window.
52static val eventQtWindow(val event)
53{
54 const val target = Event::composedTarget(event);
55 if (target.isNull() || target.isUndefined() || target["closest"].isUndefined())
56 return val::null();
57 return target.call<val>("closest", val(".qt-window"));
58}
59
60// A window handler also sees child window events, which the child services itself.
61static bool isEventForReceivingHandler(val event)
62{
63 return eventQtWindow(event).strictlyEquals(event["currentTarget"]);
64}
65
66void QWasmClipboard::cut(val event)
67{
68 if (!isEventForReceivingHandler(event))
69 return;
70
72 // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
73 QWindowSystemInterface::handleKeyEvent(
74 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
75 }
76
77 commonCopyEvent(event);
78}
79
80void QWasmClipboard::copy(val event)
81{
82 if (!isEventForReceivingHandler(event))
83 return;
84
86 // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
87 QWindowSystemInterface::handleKeyEvent(
88 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
89 }
90 commonCopyEvent(event);
91}
92
93void QWasmClipboard::paste(val event)
94{
95 if (!isEventForReceivingHandler(event))
96 return;
97
98 // sendClipboardData() delivers the data via a synthetic Ctrl+V. Cancel the default
99 // action, which would insert it again as an "insertFromPaste" input event.
100 event.call<void>("preventDefault");
101
102 QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
103}
104
106{
107 val clipboard = val::global("navigator")["clipboard"];
108
109 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
110 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
111
112 if (m_hasClipboardApi && hasPermissionsApi)
113 initClipboardPermissions();
114}
115
119
120QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
121{
122 if (mode != QClipboard::Clipboard)
123 return nullptr;
124
125 return QPlatformClipboard::mimeData(mode);
126}
127
128void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
129{
130 // handle setText/ setData programmatically
131 QPlatformClipboard::setMimeData(mimeData, mode);
132 if (m_hasClipboardApi)
133 writeToClipboardApi();
134 else
135 writeToClipboard();
136}
137
139{
140 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
142
143 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
145
146 const bool isPaste = event.key == Qt::Key_V;
147
148 return m_hasClipboardApi && !isPaste
151}
152
153bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
154{
155 return mode == QClipboard::Clipboard;
156}
157
158bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
159{
160 Q_UNUSED(mode);
161 return false;
162}
163
164void QWasmClipboard::initClipboardPermissions()
165{
166 val permissions = val::global("navigator")["permissions"];
167
168 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
169 val readPermissionsMap = val::object();
170 readPermissionsMap.set("name", val("clipboard-read"));
171 return readPermissionsMap;
172 })());
173 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
174 val readPermissionsMap = val::object();
175 readPermissionsMap.set("name", val("clipboard-write"));
176 return readPermissionsMap;
177 })());
178}
179
181{
182 return m_hasClipboardApi;
183}
184
185void QWasmClipboard::writeToClipboardApi()
186{
187 Q_ASSERT(m_hasClipboardApi);
188
189 QMimeData *mimeData = this->mimeData(QClipboard::Clipboard);
190 if (!mimeData)
191 return;
192
193 // Support for plain text, html and images (png) are standardized,
194 // copy those to the clipboard data object.
195 emscripten::val clipboardData = emscripten::val::object();
196 for (const QString &mimetype: mimeData->formats()) {
197 if (mimetype == QLatin1String("text/plain")) {
198 emscripten::val text = mimeData->text().toEcmaString();
199 clipboardData.set(mimetype.toEcmaString(), text);
200 } else if (mimetype == QLatin1String("text/html")) {
201 emscripten::val html = mimeData->html().toEcmaString();
202 clipboardData.set(mimetype.toEcmaString(), html);
203 } else if (mimetype.contains(QLatin1String("image"))) {
204 // Serialize the Qt image data to browser supported png
205 QImage img = qvariant_cast<QImage>(mimeData->imageData());
206 QByteArray ba;
207 QBuffer buffer(&ba);
208 buffer.open(QIODevice::WriteOnly);
209 img.save(&buffer, "PNG");
210
211 qstdweb::Blob blob = qstdweb::Blob::fromArrayBuffer(qstdweb::Uint8Array::copyFrom(ba).buffer());
212 clipboardData.set(std::string("image/png"), blob.val());
213 }
214 }
215
216 // Return if there is no data (creating an empty ClipboardItem is an error)
217 if (val::global("Object").call<val>("keys", clipboardData)["length"].as<int>() == 0)
218 return;
219
220 // Write a single clipboard item containing the data formats to the clipboard
221 emscripten::val clipboardItem = val::global("ClipboardItem").new_(clipboardData);
222 emscripten::val clipboardItemArray = emscripten::val::array();
223 clipboardItemArray.call<void>("push", clipboardItem);
224 val navigator = val::global("navigator");
225 qstdweb::Promise::make(
226 navigator["clipboard"], "write",
227 {
228 .catchFunc = [](emscripten::val error) {
229 qWarning() << "clipboard error"
230 << QString::fromStdString(error["name"].as<std::string>())
231 << QString::fromStdString(error["message"].as<std::string>());
232 }
233 },
234 clipboardItemArray);
235}
236
237void QWasmClipboard::writeToClipboard()
238{
239 // this works for firefox, chrome by generating
240 // copy event, but not safari
241 // execCommand has been deemed deprecated in the docs, but browsers do not seem
242 // interested in removing it. There is no replacement, so we use it here.
243 val document = val::global("document");
244 document.call<val>("execCommand", val("copy"));
245}
246
247void QWasmClipboard::sendClipboardData(emscripten::val event)
248{
249 dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
250 const auto mimeCallback = std::function([transfer](QMimeData *data) {
251
252 // Persist clipboard data so that the app can read it when handling the CTRL+V
253 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
254 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
255 Qt::ControlModifier, "V");
256 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
257 Qt::ControlModifier, "V");
258 delete transfer;
259 });
260
261 transfer->toMimeDataWithFile(mimeCallback);
262}
263QT_END_NAMESPACE
virtual ~QWasmClipboard()
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 val eventQtWindow(val event)
static bool isEventForReceivingHandler(val event)
static void commonCopyEvent(val event)
EventType
Definition qwasmevent.h:23
static emscripten::val composedTarget(emscripten::val webEvent)