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
49static void qClipboardCutTo(val event)
50{
51 QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
52 if (wasmInput && wasmInput->usingTextInput())
53 return;
54
56 // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
57 QWindowSystemInterface::handleKeyEvent(
58 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
59 }
60
61 commonCopyEvent(event);
62}
63
64static void qClipboardCopyTo(val event)
65{
66 QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
67 if (wasmInput && wasmInput->usingTextInput())
68 return;
69
71 // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
72 QWindowSystemInterface::handleKeyEvent(
73 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
74 }
75 commonCopyEvent(event);
76}
77
78static void qClipboardPasteTo(val event)
79{
80 QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
81 if (wasmInput && wasmInput->usingTextInput())
82 return;
83
84 event.call<void>("preventDefault"); // prevent browser from handling drop event
85
86 QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
87}
88
90 function("qtClipboardCutTo", &qClipboardCutTo);
91 function("qtClipboardCopyTo", &qClipboardCopyTo);
92 function("qtClipboardPasteTo", &qClipboardPasteTo);
93}
94
96{
97 val clipboard = val::global("navigator")["clipboard"];
98
99 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
100 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
101
102 if (m_hasClipboardApi && hasPermissionsApi)
103 initClipboardPermissions();
104}
105
109
110QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
111{
112 if (mode != QClipboard::Clipboard)
113 return nullptr;
114
115 return QPlatformClipboard::mimeData(mode);
116}
117
118void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
119{
120 // handle setText/ setData programmatically
121 QPlatformClipboard::setMimeData(mimeData, mode);
122 if (m_hasClipboardApi)
123 writeToClipboardApi();
124 else
125 writeToClipboard();
126}
127
129{
130 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
132
133 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
135
136 const bool isPaste = event.key == Qt::Key_V;
137
138 return m_hasClipboardApi && !isPaste
141}
142
143bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
144{
145 return mode == QClipboard::Clipboard;
146}
147
148bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
149{
150 Q_UNUSED(mode);
151 return false;
152}
153
154void QWasmClipboard::initClipboardPermissions()
155{
156 val permissions = val::global("navigator")["permissions"];
157
158 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
159 val readPermissionsMap = val::object();
160 readPermissionsMap.set("name", val("clipboard-read"));
161 return readPermissionsMap;
162 })());
163 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
164 val readPermissionsMap = val::object();
165 readPermissionsMap.set("name", val("clipboard-write"));
166 return readPermissionsMap;
167 })());
168}
169
170void QWasmClipboard::installEventHandlers(const emscripten::val &target)
171{
172 emscripten::val cContext = val::undefined();
173 emscripten::val isChromium = val::global("window")["chrome"];
174 if (!isChromium.isUndefined()) {
175 cContext = val::global("document");
176 } else {
177 cContext = target;
178 }
179 // Fallback path for browsers which do not support direct clipboard access
180 cContext.call<void>("addEventListener", val("cut"),
181 val::module_property("qtClipboardCutTo"), true);
182 cContext.call<void>("addEventListener", val("copy"),
183 val::module_property("qtClipboardCopyTo"), true);
184 cContext.call<void>("addEventListener", val("paste"),
185 val::module_property("qtClipboardPasteTo"), true);
186}
187
189{
190 return m_hasClipboardApi;
191}
192
193void QWasmClipboard::writeToClipboardApi()
194{
195 Q_ASSERT(m_hasClipboardApi);
196
197 // copy event
198 // browser event handler detected ctrl c if clipboard API
199 // or Qt call from keyboard event handler
200
201 QMimeData *_mimes = mimeData(QClipboard::Clipboard);
202 if (!_mimes)
203 return;
204
205 emscripten::val clipboardWriteArray = emscripten::val::array();
206 QByteArray ba;
207
208 for (auto mimetype : _mimes->formats()) {
209 // we need to treat binary and text differently, as the blob method below
210 // fails for text mimetypes
211 // ignore text types
212
213 if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
214 continue;
215
216 if (_mimes->hasHtml()) { // prefer html over text
217 ba = _mimes->html().toLocal8Bit();
218 // force this mime
219 mimetype = "text/html";
220 } else if (mimetype.contains("text/plain")) {
221 ba = _mimes->text().toLocal8Bit();
222 } else if (mimetype.contains("image")) {
223 QImage img = qvariant_cast<QImage>( _mimes->imageData());
224 QBuffer buffer(&ba);
225 buffer.open(QIODevice::WriteOnly);
226 img.save(&buffer, "PNG");
227 mimetype = "image/png"; // chrome only allows png
228 // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
229 // safari silently fails
230 // so we use png internally for now
231 } else {
232 // DATA
233 ba = _mimes->data(mimetype);
234 }
235 // Create file data Blob
236
237 const char *content = ba.data();
238 int dataLength = ba.length();
239 if (dataLength < 1) {
240 qDebug() << "no content found";
241 return;
242 }
243
244 emscripten::val document = emscripten::val::global("document");
245 emscripten::val window = emscripten::val::global("window");
246
247 emscripten::val fileContentView =
248 emscripten::val(emscripten::typed_memory_view(dataLength, content));
249 emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
250 emscripten::val fileContentCopyView =
251 emscripten::val::global("Uint8Array").new_(fileContentCopy);
252 fileContentCopyView.call<void>("set", fileContentView);
253
254 emscripten::val contentArray = emscripten::val::array();
255 contentArray.call<void>("push", fileContentCopyView);
256
257 // we have a blob, now create a ClipboardItem
258 emscripten::val type = emscripten::val::array();
259 type.set("type", mimetype.toEcmaString());
260
261 emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
262
263 emscripten::val clipboardItemObject = emscripten::val::object();
264 clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
265
266 val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
267
268 clipboardWriteArray.call<void>("push", clipboardItemData);
269
270 // Clipboard write is only supported with one ClipboardItem at the moment
271 // but somehow this still works?
272 // break;
273 }
274
275 val navigator = val::global("navigator");
276
277 qstdweb::Promise::make(
278 navigator["clipboard"], "write",
279 {
280 .catchFunc = [](emscripten::val error) {
281 qWarning() << "clipboard error"
282 << QString::fromStdString(error["name"].as<std::string>())
283 << QString::fromStdString(error["message"].as<std::string>());
284 }
285 },
286 clipboardWriteArray);
287}
288
289void QWasmClipboard::writeToClipboard()
290{
291 // this works for firefox, chrome by generating
292 // copy event, but not safari
293 // execCommand has been deemed deprecated in the docs, but browsers do not seem
294 // interested in removing it. There is no replacement, so we use it here.
295 val document = val::global("document");
296 document.call<val>("execCommand", val("copy"));
297}
298
299void QWasmClipboard::sendClipboardData(emscripten::val event)
300{
301 qDebug() << "sendClipboardData";
302
303 dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
304 const auto mimeCallback = std::function([transfer](QMimeData *data) {
305
306 // Persist clipboard data so that the app can read it when handling the CTRL+V
307 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
308 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
309 Qt::ControlModifier, "V");
310 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
311 Qt::ControlModifier, "V");
312 delete transfer;
313 });
314
315 transfer->toMimeDataWithFile(mimeCallback);
316}
317QT_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()
QJSValue function
Definition qjsengine.cpp:9
Combined button and popup list for selecting options.
Definition qwasmdom.h:29
static void qClipboardCutTo(val event)
static void qClipboardPasteTo(val event)
static void commonCopyEvent(val event)
static void qClipboardCopyTo(val event)
EMSCRIPTEN_BINDINGS(qtClipboardModule)