Qt 6.x
The Qt SDK
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{
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().toJsString());
31 }
32 if (_mimes->hasHtml()) {
33 event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toJsString());
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.toJsString(),
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 static std::shared_ptr<qstdweb::CancellationFlag> readDataCancellation = nullptr;
74 readDataCancellation = qstdweb::readDataTransfer(
75 event["clipboardData"],
76 [](QByteArray fileContent) {
78 image.loadFromData(fileContent, nullptr);
79 return image;
80 },
81 [event](std::unique_ptr<QMimeData> data) {
82 if (data->formats().isEmpty())
83 return;
84
85 // Persist clipboard data so that the app can read it when handling the CTRL+V
86 QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(
87 data.release(), QClipboard::Clipboard);
88
91 });
92}
93
94EMSCRIPTEN_BINDINGS(qtClipboardModule) {
95 function("qtClipboardCutTo", &qClipboardCutTo);
96 function("qtClipboardCopyTo", &qClipboardCopyTo);
97 function("qtClipboardPasteTo", &qClipboardPasteTo);
98}
99
101{
102 val clipboard = val::global("navigator")["clipboard"];
103
104 const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
105 m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
106
107 if (m_hasClipboardApi && hasPermissionsApi)
108 initClipboardPermissions();
109}
110
112{
113}
114
116{
118 return nullptr;
119
121}
122
124{
125 // handle setText/ setData programmatically
127 if (m_hasClipboardApi)
128 writeToClipboardApi();
129 else
130 writeToClipboard();
131}
132
134{
135 if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
137
138 if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
140
141 const bool isPaste = event.key == Qt::Key_V;
142
143 return m_hasClipboardApi && !isPaste
146}
147
149{
150 return mode == QClipboard::Clipboard;
151}
152
154{
155 Q_UNUSED(mode);
156 return false;
157}
158
159void QWasmClipboard::initClipboardPermissions()
160{
161 val permissions = val::global("navigator")["permissions"];
162
163 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
164 val readPermissionsMap = val::object();
165 readPermissionsMap.set("name", val("clipboard-read"));
166 return readPermissionsMap;
167 })());
168 qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
169 val readPermissionsMap = val::object();
170 readPermissionsMap.set("name", val("clipboard-write"));
171 return readPermissionsMap;
172 })());
173}
174
176{
177 emscripten::val cContext = val::undefined();
178 emscripten::val isChromium = val::global("window")["chrome"];
179 if (!isChromium.isUndefined()) {
180 cContext = val::global("document");
181 } else {
182 cContext = target;
183 }
184 // Fallback path for browsers which do not support direct clipboard access
185 cContext.call<void>("addEventListener", val("cut"),
186 val::module_property("qtClipboardCutTo"), true);
187 cContext.call<void>("addEventListener", val("copy"),
188 val::module_property("qtClipboardCopyTo"), true);
189 cContext.call<void>("addEventListener", val("paste"),
190 val::module_property("qtClipboardPasteTo"), true);
191}
192
194{
195 return m_hasClipboardApi;
196}
197
198void QWasmClipboard::writeToClipboardApi()
199{
200 Q_ASSERT(m_hasClipboardApi);
201
202 // copy event
203 // browser event handler detected ctrl c if clipboard API
204 // or Qt call from keyboard event handler
205
207 if (!_mimes)
208 return;
209
210 emscripten::val clipboardWriteArray = emscripten::val::array();
212
213 for (auto mimetype : _mimes->formats()) {
214 // we need to treat binary and text differently, as the blob method below
215 // fails for text mimetypes
216 // ignore text types
217
218 if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
219 continue;
220
221 if (_mimes->hasHtml()) { // prefer html over text
222 ba = _mimes->html().toLocal8Bit();
223 // force this mime
224 mimetype = "text/html";
225 } else if (mimetype.contains("text/plain")) {
226 ba = _mimes->text().toLocal8Bit();
227 } else if (mimetype.contains("image")) {
228 QImage img = qvariant_cast<QImage>( _mimes->imageData());
229 QBuffer buffer(&ba);
231 img.save(&buffer, "PNG");
232 mimetype = "image/png"; // chrome only allows png
233 // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
234 // safari silently fails
235 // so we use png internally for now
236 } else {
237 // DATA
238 ba = _mimes->data(mimetype);
239 }
240 // Create file data Blob
241
242 const char *content = ba.data();
243 int dataLength = ba.length();
244 if (dataLength < 1) {
245 qDebug() << "no content found";
246 return;
247 }
248
249 emscripten::val document = emscripten::val::global("document");
250 emscripten::val window = emscripten::val::global("window");
251
252 emscripten::val fileContentView =
253 emscripten::val(emscripten::typed_memory_view(dataLength, content));
254 emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
255 emscripten::val fileContentCopyView =
256 emscripten::val::global("Uint8Array").new_(fileContentCopy);
257 fileContentCopyView.call<void>("set", fileContentView);
258
259 emscripten::val contentArray = emscripten::val::array();
260 contentArray.call<void>("push", fileContentCopyView);
261
262 // we have a blob, now create a ClipboardItem
263 emscripten::val type = emscripten::val::array();
264 type.set("type", mimetype.toJsString());
265
266 emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
267
268 emscripten::val clipboardItemObject = emscripten::val::object();
269 clipboardItemObject.set(mimetype.toJsString(), contentBlob);
270
271 val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
272
273 clipboardWriteArray.call<void>("push", clipboardItemData);
274
275 // Clipboard write is only supported with one ClipboardItem at the moment
276 // but somehow this still works?
277 // break;
278 }
279
280 val navigator = val::global("navigator");
281
283 navigator["clipboard"], "write",
284 {
285 .catchFunc = [](emscripten::val error) {
286 qWarning() << "clipboard error"
287 << QString::fromStdString(error["name"].as<std::string>())
288 << QString::fromStdString(error["message"].as<std::string>());
289 }
290 },
291 clipboardWriteArray);
292}
293
294void QWasmClipboard::writeToClipboard()
295{
296 // this works for firefox, chrome by generating
297 // copy event, but not safari
298 // execCommand has been deemed deprecated in the docs, but browsers do not seem
299 // interested in removing it. There is no replacement, so we use it here.
300 val document = val::global("document");
301 document.call<val>("execCommand", val("copy"));
302}
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:479
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
Mode
\keyword clipboard mode
Definition qclipboard.h:27
@ KeyPress
Definition qcoreevent.h:64
\inmodule QtGui
Definition qimage.h:37
\inmodule QtCore
Definition qmimedata.h:16
QVariant imageData() const
Returns a QVariant storing a QImage if the object can return an image; otherwise returns a null varia...
bool hasHtml() const
Returns true if the object can return HTML (MIME type text/html); otherwise returns false.
bool hasText() const
Returns true if the object can return plain text (MIME type text/plain); otherwise returns false.
QString html() const
Returns a string if the data stored in the object is HTML (MIME type text/html); otherwise returns an...
QByteArray data(const QString &mimetype) const
Returns the data stored in the object in the format described by the MIME type specified by mimeType.
virtual QStringList formats() const
Returns a list of formats supported by the object.
QString text() const
Returns a plain text (MIME type text/plain) representation of the data.
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:1322
QByteArray toLocal8Bit() const &
Definition qstring.h:567
virtual ~QWasmClipboard()
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()
QWasmClipboard * getWasmClipboard()
QPlatformClipboard * clipboard() const override
Accessor for the platform integration's clipboard.
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:548
@ Key_X
Definition qnamespace.h:569
@ Key_V
Definition qnamespace.h:567
@ ControlModifier
@ CaseSensitive
emscripten::val document()
Definition qwasmdom.h:20
Definition image.cpp:4
void make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args)
Definition qstdweb_p.h:182
std::shared_ptr< CancellationFlag > readDataTransfer(emscripten::val webDataTransfer, std::function< QVariant(QByteArray)> imageReader, std::function< void(std::unique_ptr< QMimeData >)> onDone)
Definition qstdweb.cpp:868
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction function
DBusConnection const char DBusError * error
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
GLenum mode
GLenum GLuint buffer
GLenum type
GLenum target
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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]