wasm: add clipboard support

This feature uses the js Clipboard API and is supported only in Chrome,
as Firefox only supports reading the clipboard in browser extensions.

It also requires https or localhost access, otherwise access to the
clipboard is blocked by chrome.

Chrome users will be able to copy/paste text to and from the system
clipbaord.
Other browsers will be able to use the clipboard from within the same
application.

Currently only supports text and html.

Task-number: QTBUG-64638
Change-Id: Ie6de9d10812b776519bd6115593b433fe77059fe
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Lorn Potter 2018-02-22 04:35:25 +10:00 committed by Morten Johan Sørvig
parent ef2ddcf551
commit 2c6c724949
6 changed files with 307 additions and 11 deletions

View File

@ -0,0 +1,204 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qwasmclipboard.h"
#include "qwasmwindow.h"
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/bind.h>
#include <QCoreApplication>
#include <qpa/qwindowsysteminterface.h>
using namespace emscripten;
// there has got to be a better way...
static QByteArray g_clipboardArray;
static QByteArray g_clipboardFormat;
static val getClipboardData()
{
return val(g_clipboardArray.constData());
}
static val getClipboardFormat()
{
return val(g_clipboardFormat.constData());
}
static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
{
QString formatString = QString::fromStdString(format.as<std::string>());
QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
QMimeData *mMimeData = new QMimeData;
mMimeData->setData(formatString, dataArray);
QWasmClipboard::qWasmClipboardPaste(mMimeData);
}
static void qClipboardPromiseResolve(emscripten::val something)
{
pasteClipboardData(emscripten::val("text/plain"), something);
}
static void qClipboardCopyTo(val event)
{
val target = event["target"];
val clipboard = event["clipboardData"];
val module = val::global("Module");
val clipdata = module.call<val>("getClipboardData");
val clipFormat = module.call<val>("getClipboardFormat");
clipboard.call<void>("setData", clipFormat, clipdata);
target.call<void>("preventDefault");
}
static void qClipboardPasteTo(val event)
{
val target = event["clipboardData"];
val module = val::global("Module");
val clipdata = module.call<val>("getClipboardData");
const std::string data = clipdata.as<std::string>();
if (data.length() > 0) {
QString qstr = QString::fromStdString(data);
QMimeData *mMimeData = new QMimeData;
mMimeData->setText(qstr);
QWasmClipboard::qWasmClipboardPaste(mMimeData);
}
}
EMSCRIPTEN_BINDINGS(clipboard_module) {
function("getClipboardData", &getClipboardData);
function("getClipboardFormat", &getClipboardFormat);
function("pasteClipboardData", &pasteClipboardData);
function("qClipboardPromiseResolve", &qClipboardPromiseResolve);
function("qClipboardCopyTo", &qClipboardCopyTo);
function("qClipboardPasteTo", &qClipboardPasteTo);
}
QWasmClipboard::QWasmClipboard() :
hasClipboardApi(false)
{
initClipboardEvents();
}
QWasmClipboard::~QWasmClipboard()
{
g_clipboardArray.clear();
g_clipboardFormat.clear();
}
QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode)
{
if (mode != QClipboard::Clipboard)
return nullptr;
return QPlatformClipboard::mimeData(mode);
}
void QWasmClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
{
if (mimeData->hasText()) {
g_clipboardFormat = mimeData->formats().at(0).toUtf8();
g_clipboardArray = mimeData->text().toUtf8();
} else if (mimeData->hasHtml()) {
g_clipboardFormat =mimeData->formats().at(0).toUtf8();
g_clipboardArray = mimeData->html().toUtf8();
}
QPlatformClipboard::setMimeData(mimeData, mode);
}
bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
{
return mode == QClipboard::Clipboard;
}
bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
{
Q_UNUSED(mode);
return false;
}
void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData)
{
QWasmIntegration::get()->clipboard()->setMimeData(mData, QClipboard::Clipboard);
QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
}
void QWasmClipboard::initClipboardEvents()
{
val navigator = val::global("navigator");
val permissions = navigator["permissions"];
val clipboard = navigator["clipboard"];
hasClipboardApi = (!clipboard.isUndefined());
if (hasClipboardApi) {
val readPermissionsMap = val::object();
readPermissionsMap.set("name", val("clipboard-read"));
permissions.call<val>("query", readPermissionsMap);
val writePermissionsMap = val::object();
writePermissionsMap.set("name", val("clipboard-write"));
permissions.call<val>("query", writePermissionsMap);
} else {
val window = val::global("window");
window.call<void>("addEventListener", std::string("paste"),
val::module_property("qClipboardPasteTo"));
window.call<void>("addEventListener", std::string("copy"),
val::module_property("qClipboardCopyTo"));
}
}
void QWasmClipboard::readTextFromClipboard()
{
if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
val navigator = val::global("navigator");
val textPromise = navigator["clipboard"].call<val>("readText");
val readTextResolve = val::global("Module")["qClipboardPromiseResolve"];
textPromise.call<val>("then", readTextResolve);
}
}
void QWasmClipboard::writeTextToClipboard()
{
if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
val module = val::global("Module");
val txt = module.call<val>("getClipboardData");
val format = module.call<val>("getClipboardFormat");
val navigator = val::global("navigator");
navigator["clipboard"].call<void>("writeText", txt.as<std::string>());
}
}

View File

@ -0,0 +1,59 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QWasmClipboard_H
#define QWasmClipboard_H
#include <QObject>
#include <qpa/qplatformclipboard.h>
#include <QMimeData>
#include <emscripten/bind.h>
class QWasmClipboard : public QObject, public QPlatformClipboard
{
public:
QWasmClipboard();
virtual ~QWasmClipboard();
// QPlatformClipboard methods.
QMimeData* mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData* data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
static void qWasmClipboardPaste(QMimeData *mData);
void initClipboardEvents();
bool hasClipboardApi;
void readTextFromClipboard();
void writeTextToClipboard();
};
#endif // QWASMCLIPBOARD_H

View File

@ -31,6 +31,7 @@
#include "qwasmeventdispatcher.h"
#include "qwasmcompositor.h"
#include "qwasmintegration.h"
#include "qwasmclipboard.h"
#include <QtGui/qevent.h>
#include <qpa/qwindowsysteminterface.h>
@ -175,10 +176,26 @@ int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEve
if (keyType == QEvent::None)
return 0;
QString keyText = alphanumeric ? QString(keyEvent->key) : QString();
bool accepted = QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
0, keyType, qtKey, translateKeyboardEventModifier(keyEvent), keyText);
QFlags<Qt::KeyboardModifier> mods = translateKeyboardEventModifier(keyEvent);
bool accepted = false;
if (keyType == QEvent::KeyPress &&
mods.testFlag(Qt::ControlModifier)
&& qtKey == Qt::Key_V) {
QWasmIntegration::get()->getWasmClipboard()->readTextFromClipboard();
} else {
QString keyText = alphanumeric ? QString(keyEvent->key) : QString();
accepted = QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
0, keyType, qtKey, translateKeyboardEventModifier(keyEvent), keyText);
}
if (keyType == QEvent::KeyPress &&
mods.testFlag(Qt::ControlModifier)
&& qtKey == Qt::Key_C) {
QWasmIntegration::get()->getWasmClipboard()->writeTextToClipboard();
}
QWasmEventDispatcher::maintainTimers();
return accepted ? 1 : 0;
}

View File

@ -33,6 +33,7 @@
#include "qwasmcompositor.h"
#include "qwasmopenglcontext.h"
#include "qwasmtheme.h"
#include "qwasmclipboard.h"
#include "qwasmwindow.h"
#ifndef QT_NO_OPENGL
@ -65,17 +66,16 @@ EMSCRIPTEN_BINDINGS(my_module)
function("browserBeforeUnload", &browserBeforeUnload);
}
static QWasmIntegration *globalHtml5Integration;
QWasmIntegration *QWasmIntegration::get() { return globalHtml5Integration; }
QWasmIntegration *QWasmIntegration::s_instance;
QWasmIntegration::QWasmIntegration()
: m_fontDb(nullptr),
m_compositor(new QWasmCompositor),
m_screen(new QWasmScreen(m_compositor)),
m_eventDispatcher(nullptr)
m_eventDispatcher(nullptr),
m_clipboard(new QWasmClipboard)
{
globalHtml5Integration = this;
s_instance = this;
updateQScreenAndCanvasRenderSize();
screenAdded(m_screen);
@ -93,6 +93,7 @@ QWasmIntegration::~QWasmIntegration()
destroyScreen(m_screen);
delete m_fontDb;
delete m_eventTranslator;
s_instance = nullptr;
}
void QWasmIntegration::QWasmBrowserExit()
@ -211,4 +212,11 @@ void QWasmIntegration::updateQScreenAndCanvasRenderSize()
QWasmIntegration::get()->m_compositor->redrawWindowContent();
}
QPlatformClipboard* QWasmIntegration::clipboard() const
{
if (!m_clipboard)
m_clipboard = new QWasmClipboard;
return m_clipboard;
}
QT_END_NAMESPACE

View File

@ -49,6 +49,7 @@ class QWasmEventDispatcher;
class QWasmScreen;
class QWasmCompositor;
class QWasmBackingStore;
class QWasmClipboard;
class QWasmIntegration : public QObject, public QPlatformIntegration
{
@ -68,12 +69,14 @@ public:
QVariant styleHint(QPlatformIntegration::StyleHint hint) const override;
QStringList themeNames() const override;
QPlatformTheme *createPlatformTheme(const QString &name) const override;
QPlatformClipboard *clipboard() const override;
static QWasmIntegration *get();
QWasmScreen *screen() { return m_screen; }
QWasmCompositor *compositor() { return m_compositor; }
QWasmEventTranslator *eventTranslator() { return m_eventTranslator; }
QWasmClipboard *getWasmClipboard() { return m_clipboard; }
static QWasmIntegration *get() { return s_instance; }
static void QWasmBrowserExit();
static void updateQScreenAndCanvasRenderSize();
@ -85,6 +88,9 @@ private:
mutable QWasmEventDispatcher *m_eventDispatcher;
static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData);
mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores;
mutable QWasmClipboard *m_clipboard;
static QWasmIntegration *s_instance;
};
QT_END_NAMESPACE

View File

@ -18,7 +18,8 @@ SOURCES = \
qwasmcompositor.cpp \
qwasmcursor.cpp \
qwasmopenglcontext.cpp \
qwasmtheme.cpp
qwasmtheme.cpp \
qwasmclipboard.cpp
HEADERS = \
qwasmintegration.h \
@ -31,7 +32,8 @@ HEADERS = \
qwasmstylepixmaps_p.h \
qwasmcursor.h \
qwasmopenglcontext.h \
qwasmtheme.h
qwasmtheme.h \
qwasmclipboard.h
wasmfonts.files = \
../../../3rdparty/wasm/Vera.ttf \