Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qwasmclipboard.h"
5#include "qwasmdom.h"
6#include "qwasmevent.h"
7#include "qwasmwindow.h"
8
9#include <private/qstdweb_p.h>
10
11#include <QCoreApplication>
12#include <qpa/qwindowsysteminterface.h>
13#include <QBuffer>
14#include <QString>
15
16#include <emscripten/val.h>
17
19using namespace emscripten;
20
22{
23 QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
24 if (!_mimes)
25 return;
26
27 // doing it this way seems to sanitize the text better that calling data() like down below
28 if (_mimes->hasText()) {
29 event["clipboardData"].call<void>("setData", val("text/plain"),
30 _mimes->text().toEcmaString());
31 }
32 if (_mimes->hasHtml()) {
33 event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
34 }
35
36 for (auto mimetype : _mimes->formats()) {
37 if (mimetype.contains("text/"))
38 continue;
39 QByteArray ba = _mimes->data(mimetype);
40 if (!ba.isEmpty())
41 event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
42 val(ba.constData()));
43 }
44
45 event.call<void>("preventDefault");
46}
47
49{
50 if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
51 // Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
54 }
55
57}
58
60{
61 if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
62 // Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
65 }
67}
68
70{
71 event.call<void>("preventDefault"); // prevent browser from handling drop event
72
73 QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
74}
75
76EMSCRIPTEN_BINDINGS(qtClipboardModule) {
77 function("qtClipboardCutTo", &qClipboardCutTo);
78 function("qtClipboardCopyTo", &qClipboardCopyTo);
79 function("qtClipboardPasteTo", &qClipboardPasteTo);
80}
81
83{
84 val clipboard = val::global("navigator")["clipboard"];
85
86 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
87 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
88
89 if (m_hasClipboardApi && hasPermissionsApi)
90 initClipboardPermissions();
91}
92
96
104
106{
107 // handle setText/ setData programmatically
109 if (m_hasClipboardApi)
110 writeToClipboardApi();
111 else
112 writeToClipboard();
113}
114
116{
117 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
119
120 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
122
123 const bool isPaste = event.key == Qt::Key_V;
124
125 return m_hasClipboardApi && !isPaste
128}
129
134
136{
137 Q_UNUSED(mode);
138 return false;
139}
140
141void QWasmClipboard::initClipboardPermissions()
142{
143 val permissions = val::global("navigator")["permissions"];
144
145 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
146 val readPermissionsMap = val::object();
147 readPermissionsMap.set("name", val("clipboard-read"));
148 return readPermissionsMap;
149 })());
150 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
151 val readPermissionsMap = val::object();
152 readPermissionsMap.set("name", val("clipboard-write"));
153 return readPermissionsMap;
154 })());
155}
156
158{
159 emscripten::val cContext = val::undefined();
160 emscripten::val isChromium = val::global("window")["chrome"];
161 if (!isChromium.isUndefined()) {
162 cContext = val::global("document");
163 } else {
164 cContext = target;
165 }
166 // Fallback path for browsers which do not support direct clipboard access
167 cContext.call<void>("addEventListener", val("cut"),
168 val::module_property("qtClipboardCutTo"), true);
169 cContext.call<void>("addEventListener", val("copy"),
170 val::module_property("qtClipboardCopyTo"), true);
171 cContext.call<void>("addEventListener", val("paste"),
172 val::module_property("qtClipboardPasteTo"), true);
173}
174
176{
177 return m_hasClipboardApi;
178}
179
180void QWasmClipboard::writeToClipboardApi()
181{
182 Q_ASSERT(m_hasClipboardApi);
183
184 // copy event
185 // browser event handler detected ctrl c if clipboard API
186 // or Qt call from keyboard event handler
187
189 if (!_mimes)
190 return;
191
192 emscripten::val clipboardWriteArray = emscripten::val::array();
194
195 for (auto mimetype : _mimes->formats()) {
196 // we need to treat binary and text differently, as the blob method below
197 // fails for text mimetypes
198 // ignore text types
199
200 if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
201 continue;
202
203 if (_mimes->hasHtml()) { // prefer html over text
204 ba = _mimes->html().toLocal8Bit();
205 // force this mime
206 mimetype = "text/html";
207 } else if (mimetype.contains("text/plain")) {
208 ba = _mimes->text().toLocal8Bit();
209 } else if (mimetype.contains("image")) {
210 QImage img = qvariant_cast<QImage>( _mimes->imageData());
211 QBuffer buffer(&ba);
213 img.save(&buffer, "PNG");
214 mimetype = "image/png"; // chrome only allows png
215 // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
216 // safari silently fails
217 // so we use png internally for now
218 } else {
219 // DATA
220 ba = _mimes->data(mimetype);
221 }
222 // Create file data Blob
223
224 const char *content = ba.data();
225 int dataLength = ba.length();
226 if (dataLength < 1) {
227 qDebug() << "no content found";
228 return;
229 }
230
231 emscripten::val document = emscripten::val::global("document");
232 emscripten::val window = emscripten::val::global("window");
233
234 emscripten::val fileContentView =
235 emscripten::val(emscripten::typed_memory_view(dataLength, content));
236 emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
237 emscripten::val fileContentCopyView =
238 emscripten::val::global("Uint8Array").new_(fileContentCopy);
239 fileContentCopyView.call<void>("set", fileContentView);
240
241 emscripten::val contentArray = emscripten::val::array();
242 contentArray.call<void>("push", fileContentCopyView);
243
244 // we have a blob, now create a ClipboardItem
245 emscripten::val type = emscripten::val::array();
246 type.set("type", mimetype.toEcmaString());
247
248 emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
249
250 emscripten::val clipboardItemObject = emscripten::val::object();
251 clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
252
253 val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
254
255 clipboardWriteArray.call<void>("push", clipboardItemData);
256
257 // Clipboard write is only supported with one ClipboardItem at the moment
258 // but somehow this still works?
259 // break;
260 }
261
262 val navigator = val::global("navigator");
263
265 navigator["clipboard"], "write",
266 {
267 .catchFunc = [](emscripten::val error) {
268 qWarning() << "clipboard error"
269 << QString::fromStdString(error["name"].as<std::string>())
270 << QString::fromStdString(error["message"].as<std::string>());
271 }
272 },
273 clipboardWriteArray);
274}
275
276void QWasmClipboard::writeToClipboard()
277{
278 // this works for firefox, chrome by generating
279 // copy event, but not safari
280 // execCommand has been deemed deprecated in the docs, but browsers do not seem
281 // interested in removing it. There is no replacement, so we use it here.
282 val document = val::global("document");
283 document.call<val>("execCommand", val("copy"));
284}
285
287{
288 qDebug() << "sendClipboardData";
289
290 dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
291 const auto mimeCallback = std::function([transfer](QMimeData *data) {
292
293 // Persist clipboard data so that the app can read it when handling the CTRL+V
294 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
299 delete transfer;
300 });
301
302 transfer->toMimeDataWithFile(mimeCallback);
303}
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:499
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
Mode
\keyword clipboard mode
Definition qclipboard.h:27
@ KeyRelease
Definition qcoreevent.h:65
@ KeyPress
Definition qcoreevent.h:64
\inmodule QtGui
Definition qimage.h:37
\inmodule QtCore
Definition qmimedata.h:16
virtual QMimeData * mimeData(QClipboard::Mode mode=QClipboard::Clipboard)
virtual void setMimeData(QMimeData *data, QClipboard::Mode mode=QClipboard::Clipboard)
static QString fromStdString(const std::string &s)
Definition qstring.h:1447
virtual ~QWasmClipboard()
void sendClipboardData(emscripten::val event)
static void installEventHandlers(const emscripten::val &target)
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()
static bool handleKeyEvent(QWindow *window, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString &text=QString(), bool autorep=false, ushort count=1)
EGLint EGLint * formats
Combined button and popup list for selecting options.
@ Key_C
Definition qnamespace.h:549
@ Key_X
Definition qnamespace.h:570
@ Key_V
Definition qnamespace.h:568
@ ControlModifier
@ CaseSensitive
emscripten::val document()
Definition qwasmdom.h:49
void make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args)
Definition qstdweb_p.h:221
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction function
DBusConnection const char DBusError * error
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
GLenum mode
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum type
GLenum target
struct _cl_event * event
GLuint GLfloat * val
GLint void * img
Definition qopenglext.h:233
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_UNUSED(x)
static void qClipboardCutTo(val event)
static void qClipboardPasteTo(val event)
static void commonCopyEvent(val event)
static void qClipboardCopyTo(val event)
EMSCRIPTEN_BINDINGS(qtClipboardModule)
QByteArray ba
[0]
QMimeData * mimeData
aWidget window() -> setWindowTitle("New Window Title")
[2]