wasm: improve clipboard fallback path

This improves handling of cut/copy/paste clipboard events,
ands allows clipboard access via the common keyboard
shortcuts.

Make the canvas be eligible for clipboard events by
setting the contenteditable attribute. Install clipboard
event handlers directly on the canvas.

Suppress Ctrl+X/C/V key event handling in the keyboard
event handler in order to make the browser generate
clipboard events. Send synthetic key events from the
clipboard event handlers to make the app copy/paste
to Qt’s clipboard at the correct time.

Access the system clipboard data using event.clipboardData.

Task-number: QTBUG-64638
Change-Id: I584b78ffa2b755b1b76e477b970255c6e5522f6a
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Morten Johan Sørvig 2019-02-23 09:40:30 +01:00
parent 607338f98f
commit 070f75d3b0
4 changed files with 57 additions and 18 deletions

View File

@ -168,7 +168,14 @@ function QtLoader(config)
removeChildren(container);
var canvas = document.createElement("canvas");
canvas.className = "QtCanvas"
canvas.style = "height: 100%; width: 100%;"
canvas.style.height = "100%"
canvas.style.width = "100%"
// Set contentEditable in order to enable clipboard events; hide the resulting focus frame.
canvas.contentEditable = true;
canvas.style.outline = "0px solid transparent";
canvas.style.cursor = "default";
return canvas;
}

View File

@ -67,23 +67,42 @@ static void qClipboardPromiseResolve(emscripten::val something)
pasteClipboardData(emscripten::val("text/plain"), something);
}
static void qClipboardCopyTo(val event)
static void qClipboardCutTo(val event)
{
val target = event["target"];
val clipboard = event["clipboardData"];
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
}
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");
event["clipboardData"].call<void>("setData", clipFormat, clipdata);
event.call<void>("preventDefault");
}
static void qClipboardCopyTo(val event)
{
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
}
val module = val::global("Module");
val clipdata = module.call<val>("getClipboardData");
val clipFormat = module.call<val>("getClipboardFormat");
event["clipboardData"].call<void>("setData", clipFormat, clipdata);
event.call<void>("preventDefault");
}
static void qClipboardPasteTo(val event)
{
val target = event["clipboardData"];
val module = val::global("Module");
val clipdata = module.call<val>("getClipboardData");
bool hasClipboardApi = QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi;
val clipdata = hasClipboardApi ?
val::global("Module").call<val>("getClipboardData") :
event["clipboardData"].call<val>("getData", std::string("text"));
const std::string data = clipdata.as<std::string>();
if (data.length() > 0) {
@ -99,6 +118,7 @@ EMSCRIPTEN_BINDINGS(clipboard_module) {
function("getClipboardFormat", &getClipboardFormat);
function("pasteClipboardData", &pasteClipboardData);
function("qClipboardPromiseResolve", &qClipboardPromiseResolve);
function("qClipboardCutTo", &qClipboardCutTo);
function("qClipboardCopyTo", &qClipboardCopyTo);
function("qClipboardPasteTo", &qClipboardPasteTo);
}
@ -161,7 +181,7 @@ void QWasmClipboard::initClipboardEvents()
val permissions = navigator["permissions"];
val clipboard = navigator["clipboard"];
hasClipboardApi = (!clipboard.isUndefined());
hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
if (hasClipboardApi) {
val readPermissionsMap = val::object();
readPermissionsMap.set("name", val("clipboard-read"));
@ -172,13 +192,13 @@ void QWasmClipboard::initClipboardEvents()
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 canvas = val::module_property("canvas");
canvas.call<void>("addEventListener", std::string("cut"),
val::module_property("qClipboardCutTo"));
canvas.call<void>("addEventListener", std::string("copy"),
val::module_property("qClipboardCopyTo"));
canvas.call<void>("addEventListener", std::string("paste"),
val::module_property("qClipboardPasteTo"));
}
}

View File

@ -832,6 +832,14 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
return 0;
QFlags<Qt::KeyboardModifier> mods = translateKeyboardEventModifier(keyEvent);
// Clipboard fallback path: cut/copy/paste are handled by clipboard event
// handlers if direct clipboard access is not available.
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi && modifiers & Qt::ControlModifier &&
(qtKey == Qt::Key_X || qtKey == Qt::Key_C || qtKey == Qt::Key_V)) {
return 0;
}
bool accepted = false;
if (keyType == QEvent::KeyPress &&

View File

@ -7,7 +7,11 @@
<style>
html, body { padding: 0; margin : 0; overflow:hidden; height: 100% }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas { border: 0px none; background-color: white; height:100%; width:100%; }
canvas { border: 0px none; background-color: white; height:100%; width:100%; }
/* The contenteditable property is set to true for the canvas in order to support
clipboard events. Hide the resulting focus frame and set the cursor back to
the default cursor. */
canvas { outline: 0px solid transparent; cursor:default }
</style>
</head>
<body onload="init()">
@ -19,7 +23,7 @@
<noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript>
</center>
</figure>
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<canvas id="canvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas>
<script type='text/javascript'>
function init() {