summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmclipboard.cpp
blob: ff681398315820a74acb971fdedbd55cfa4f6e8d (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
// Copyright (C) 2018 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only#include"qwasmclipboard.h"#include"qwasminputcontext.h"#include"qwasmdom.h"#include"qwasmevent.h"#include"qwasmwindow.h"#include <private/qstdweb_p.h>#include <QCoreApplication>#include <qpa/qwindowsysteminterface.h>#include <QBuffer>#include <QString>#include <emscripten/val.h> QT_BEGIN_NAMESPACE using namespace emscripten;static voidcommonCopyEvent(val event){ QMimeData *_mimes =QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);if(!_mimes)return;// doing it this way seems to sanitize the text better that calling data() like down belowif(_mimes->hasText()) { event["clipboardData"].call<void>("setData",val("text/plain"), _mimes->text().toEcmaString());}if(_mimes->hasHtml()) { event["clipboardData"].call<void>("setData",val("text/html"), _mimes->html().toEcmaString());}for(auto mimetype : _mimes->formats()) {if(mimetype.contains("text/"))continue; QByteArray ba = _mimes->data(mimetype);if(!ba.isEmpty()) event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),val(ba.constData()));} event.call<void>("preventDefault");}voidQWasmClipboard::cut(val event){ QWasmInputContext *wasmInput =QWasmIntegration::get()->wasmInputContext();if(wasmInput && wasmInput->usingTextInput())return;if(!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {// Send synthetic Ctrl+X to make the app cut data to Qt's clipboardQWindowSystemInterface::handleKeyEvent(0,QEvent::KeyPress,Qt::Key_X,Qt::ControlModifier,"X");}commonCopyEvent(event);}voidQWasmClipboard::copy(val event){ QWasmInputContext *wasmInput =QWasmIntegration::get()->wasmInputContext();if(wasmInput && wasmInput->usingTextInput())return;if(!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {// Send synthetic Ctrl+C to make the app copy data to Qt's clipboardQWindowSystemInterface::handleKeyEvent(0,QEvent::KeyPress,Qt::Key_C,Qt::ControlModifier,"C");}commonCopyEvent(event);}voidQWasmClipboard::paste(val event){ QWasmInputContext *wasmInput =QWasmIntegration::get()->wasmInputContext();if(wasmInput && wasmInput->usingTextInput())return; event.call<void>("preventDefault");// prevent browser from handling drop eventQWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);}voidQWasmClipboard::beforeInput(emscripten::val event){ event.call<void>("preventDefault"); event.call<void>("stopPropagation");}voidQWasmClipboard::input(emscripten::val event){ event.call<void>("preventDefault"); event.call<void>("stopPropagation"); event["target"].set("innerHTML",std::string()); event["target"].set("value",std::string());}QWasmClipboard::QWasmClipboard(){ val clipboard =val::global("navigator")["clipboard"];const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined(); m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();if(m_hasClipboardApi && hasPermissionsApi)initClipboardPermissions();if(!shouldInstallWindowEventHandlers()) { val document =val::global("document"); m_documentCut =QWasmEventHandler(document,"cut",QWasmClipboard::cut); m_documentCopy =QWasmEventHandler(document,"copy",QWasmClipboard::copy); m_documentPaste =QWasmEventHandler(document,"paste",QWasmClipboard::paste);}}QWasmClipboard::~QWasmClipboard(){} QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode){if(mode !=QClipboard::Clipboard)returnnullptr;returnQPlatformClipboard::mimeData(mode);}voidQWasmClipboard::setMimeData(QMimeData *mimeData,QClipboard::Mode mode){// handle setText/ setData programmaticallyQPlatformClipboard::setMimeData(mimeData, mode);if(m_hasClipboardApi)writeToClipboardApi();elsewriteToClipboard();}QWasmClipboard::ProcessKeyboardResult QWasmClipboard::processKeyboard(const KeyEvent &event){if(event.type !=EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))returnProcessKeyboardResult::Ignored;if(event.key !=Qt::Key_C && event.key !=Qt::Key_V && event.key !=Qt::Key_X)returnProcessKeyboardResult::Ignored;const bool isPaste = event.key ==Qt::Key_V;return m_hasClipboardApi && !isPaste ?ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded :ProcessKeyboardResult::NativeClipboardEventNeeded;}boolQWasmClipboard::supportsMode(QClipboard::Mode mode)const{return mode ==QClipboard::Clipboard;}boolQWasmClipboard::ownsMode(QClipboard::Mode mode)const{Q_UNUSED(mode);return false;}voidQWasmClipboard::initClipboardPermissions(){ val permissions =val::global("navigator")["permissions"];qstdweb::Promise::make(permissions,"query", { .catchFunc = [](emscripten::val) {} }, ([]() { val readPermissionsMap =val::object(); readPermissionsMap.set("name",val("clipboard-read"));return readPermissionsMap;})());qstdweb::Promise::make(permissions,"query", { .catchFunc = [](emscripten::val) {} }, ([]() { val readPermissionsMap =val::object(); readPermissionsMap.set("name",val("clipboard-write"));return readPermissionsMap;})());}boolQWasmClipboard::hasClipboardApi(){return m_hasClipboardApi;}boolQWasmClipboard::shouldInstallWindowEventHandlers(){// Chrome uses global handlersreturnval::global("window")["chrome"].isUndefined();}voidQWasmClipboard::writeToClipboardApi(){Q_ASSERT(m_hasClipboardApi);// copy event// browser event handler detected ctrl c if clipboard API// or Qt call from keyboard event handler QMimeData *_mimes =mimeData(QClipboard::Clipboard);if(!_mimes)return;emscripten::val clipboardWriteArray =emscripten::val::array(); QByteArray ba;for(auto mimetype : _mimes->formats()) {// we need to treat binary and text differently, as the blob method below// fails for text mimetypes// ignore text typesif(mimetype.contains("STRING",Qt::CaseSensitive) || mimetype.contains("TEXT",Qt::CaseSensitive))continue;if(_mimes->hasHtml()) {// prefer html over text ba = _mimes->html().toLocal8Bit();// force this mime mimetype ="text/html";}else if(mimetype.contains("text/plain")) { ba = _mimes->text().toLocal8Bit();}else if(mimetype.contains("image")) { QImage img = qvariant_cast<QImage>( _mimes->imageData()); QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); img.save(&buffer,"PNG"); mimetype ="image/png";// chrome only allows png// clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."// safari silently fails// so we use png internally for now}else{// DATA ba = _mimes->data(mimetype);}// Create file data Blobconst char*content = ba.data();int dataLength = ba.length();if(dataLength <1) {qDebug() <<"no content found";return;}emscripten::val document =emscripten::val::global("document");emscripten::val window =emscripten::val::global("window");emscripten::val fileContentView =emscripten::val(emscripten::typed_memory_view(dataLength, content));emscripten::val fileContentCopy =emscripten::val::global("ArrayBuffer").new_(dataLength);emscripten::val fileContentCopyView =emscripten::val::global("Uint8Array").new_(fileContentCopy); fileContentCopyView.call<void>("set", fileContentView);emscripten::val contentArray =emscripten::val::array(); contentArray.call<void>("push", fileContentCopyView);// we have a blob, now create a ClipboardItememscripten::val type =emscripten::val::array(); type.set("type", mimetype.toEcmaString());emscripten::val contentBlob =emscripten::val::global("Blob").new_(contentArray, type);emscripten::val clipboardItemObject =emscripten::val::object(); clipboardItemObject.set(mimetype.toEcmaString(), contentBlob); val clipboardItemData =val::global("ClipboardItem").new_(clipboardItemObject); clipboardWriteArray.call<void>("push", clipboardItemData);// Clipboard write is only supported with one ClipboardItem at the moment// but somehow this still works?// break;} val navigator =val::global("navigator");qstdweb::Promise::make( navigator["clipboard"],"write",{.catchFunc = [](emscripten::val error) {qWarning() <<"clipboard error"<<QString::fromStdString(error["name"].as<std::string>())<<QString::fromStdString(error["message"].as<std::string>());}}, clipboardWriteArray);}voidQWasmClipboard::writeToClipboard(){// this works for firefox, chrome by generating// copy event, but not safari// execCommand has been deemed deprecated in the docs, but browsers do not seem// interested in removing it. There is no replacement, so we use it here. val document =val::global("document"); document.call<val>("execCommand",val("copy"));}voidQWasmClipboard::sendClipboardData(emscripten::val event){dom::DataTransfer *transfer =newdom::DataTransfer(event["clipboardData"]);constauto mimeCallback =std::function([transfer](QMimeData *data) {// Persist clipboard data so that the app can read it when handling the CTRL+VQWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data,QClipboard::Clipboard);QWindowSystemInterface::handleKeyEvent(0,QEvent::KeyPress,Qt::Key_V,Qt::ControlModifier,"V");QWindowSystemInterface::handleKeyEvent(0,QEvent::KeyRelease,Qt::Key_V,Qt::ControlModifier,"V");delete transfer;}); transfer->toMimeDataWithFile(mimeCallback);} QT_END_NAMESPACE 
close