diff options
Diffstat (limited to 'src/plugins/platforms/wasm')
19 files changed, 1308 insertions, 477 deletions
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js index 37a5308034..2db7723ae2 100644 --- a/src/plugins/platforms/wasm/qtloader.js +++ b/src/plugins/platforms/wasm/qtloader.js @@ -50,6 +50,7 @@ // External mode.usage: // // var config = { +// canvasElements : [$("canvas-id")], // showLoader: function() { // loader.style.display = 'block' // canvas.style.display = 'hidden' @@ -69,6 +70,8 @@ // One or more HTML elements. QtLoader will display loader elements // on these while loading the applicaton, and replace the loader with a // canvas on load complete. +// canvasElements : [canvas-element, ...] +// One or more canvas elements. // showLoader : function(status, containerElement) // Optional loading element constructor function. Implement to create // a custom loading screen. This function may be called multiple times, @@ -115,6 +118,12 @@ // "Exited", iff crashed is false. // exitText // Abort/exit message. +// addCanvasElement +// Add canvas at run-time. Adds a corresponding QScreen, +// removeCanvasElement +// Remove canvas at run-time. Removes the corresponding QScreen. +// resizeCanvasElement +// Signals to the application that a canvas has been resized. var Module = {} @@ -146,8 +155,26 @@ function QtLoader(config) while (element.firstChild) element.removeChild(element.firstChild); } - // Set default state handler functions if needed + function createCanvas() { + var canvas = document.createElement("canvas"); + canvas.className = "QtCanvas"; + 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.caretColor = "transparent"; + canvas.style.cursor = "default"; + + return canvas; + } + + // Set default state handler functions and create canvases if needed if (config.containerElements !== undefined) { + + config.canvasElements = config.containerElements.map(createCanvas); + config.showError = config.showError || function(errorText, container) { removeChildren(container); var errorTextElement = document.createElement("text"); @@ -164,12 +191,8 @@ function QtLoader(config) return loadingText; }; - config.showCanvas = config.showCanvas || function(container) { + config.showCanvas = config.showCanvas || function(canvas, container) { removeChildren(container); - var canvas = document.createElement("canvas"); - canvas.className = "QtCanvas" - canvas.style = "height: 100%; width: 100%;" - return canvas; } config.showExit = config.showExit || function(crashed, exitCode, container) { @@ -211,6 +234,9 @@ function QtLoader(config) publicAPI.canLoadApplication = canLoadQt(); publicAPI.status = undefined; publicAPI.loadEmscriptenModule = loadEmscriptenModule; + publicAPI.addCanvasElement = addCanvasElement; + publicAPI.removeCanvasElement = removeCanvasElement; + publicAPI.resizeCanvasElement = resizeCanvasElement; restartCount = 0; @@ -312,13 +338,13 @@ function QtLoader(config) // and is ready to be instantiated. Define the instantiateWasm callback which // emscripten will call to create the instance. Module.instantiateWasm = function(imports, successCallback) { - return WebAssembly.instantiate(wasmModule, imports).then(function(instance) { - successCallback(instance); - return instance; + WebAssembly.instantiate(wasmModule, imports).then(function(instance) { + successCallback(instance, wasmModule); }, function(error) { self.error = error; setStatus("Error"); }); + return {}; }; Module.locateFile = Module.locateFile || function(filename) { @@ -382,6 +408,10 @@ function QtLoader(config) } }); + Module.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'}); + + Module.qtCanvasElements = config.canvasElements; + config.restart = function() { // Restart by reloading the page. This will wipe all state which means @@ -436,19 +466,17 @@ function QtLoader(config) } function setCanvasContent() { - var firstCanvas; if (config.containerElements === undefined) { - firstCanvas = config.showCanvas(); - } else { - for (container of config.containerElements) { - var canvasElement = config.showCanvas(container); - container.appendChild(canvasElement); - } - firstCanvas = config.containerElements[0].firstChild; + if (config.showCanvas !== undefined) + config.showCanvas(); + return; } - if (Module.canvas === undefined) { - Module.canvas = firstCanvas; + for (var i = 0; i < config.containerElements.length; ++i) { + var container = config.containerElements[i]; + var canvas = config.canvasElements[i]; + config.showCanvas(canvas, container); + container.appendChild(canvas); } } @@ -510,6 +538,25 @@ function QtLoader(config) window.setTimeout(function() { handleStatusChange(); }, 0); } + function addCanvasElement(element) { + if (publicAPI.status == "Running") + Module.qtAddCanvasElement(element); + else + console.log("Error: addCanvasElement can only be called in the Running state"); + } + + function removeCanvasElement(element) { + if (publicAPI.status == "Running") + Module.qtRemoveCanvasElement(element); + else + console.log("Error: removeCanvasElement can only be called in the Running state"); + } + + function resizeCanvasElement(element) { + if (publicAPI.status == "Running") + Module.qtResizeCanvasElement(element); + } + setStatus("Created"); return publicAPI; diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp new file mode 100644 index 0000000000..d4a1e4dd50 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** 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 qClipboardCutTo(val event) +{ + 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>("qtGetClipboardData"); + val clipFormat = module.call<val>("qtGetClipboardFormat"); + 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>("qtGetClipboardData"); + val clipFormat = module.call<val>("qtGetClipboardFormat"); + event["clipboardData"].call<void>("setData", clipFormat, clipdata); + event.call<void>("preventDefault"); +} + +static void qClipboardPasteTo(val event) +{ + bool hasClipboardApi = QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi; + val clipdata = hasClipboardApi ? + val::global("Module").call<val>("qtGetClipboardData") : + event["clipboardData"].call<val>("getData", std::string("text")); + + 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(qtClipboardModule) { + function("qtGetClipboardData", &getClipboardData); + function("qtGetClipboardFormat", &getClipboardFormat); + function("qtPasteClipboardData", &pasteClipboardData); + function("qtClipboardPromiseResolve", &qClipboardPromiseResolve); + function("qtClipboardCutTo", &qClipboardCutTo); + function("qtClipboardCopyTo", &qClipboardCopyTo); + function("qtClipboardPasteTo", &qClipboardPasteTo); +} + +QWasmClipboard::QWasmClipboard() +{ + val clipboard = val::global("navigator")["clipboard"]; + hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined()); + + 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() +{ + if (!hasClipboardApi) + return; + + val permissions = val::global("navigator")["permissions"]; + 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); +} + +void QWasmClipboard::installEventHandlers(const QString &canvasId) +{ + if (hasClipboardApi) + return; + + // Fallback path for browsers which do not support direct clipboard access + val canvas = val::global(canvasId.toUtf8().constData()); + canvas.call<void>("addEventListener", std::string("cut"), + val::module_property("qtClipboardCutTo")); + canvas.call<void>("addEventListener", std::string("copy"), + val::module_property("qtClipboardCopyTo")); + canvas.call<void>("addEventListener", std::string("paste"), + val::module_property("qtClipboardPasteTo")); +} + +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")["qtClipboardPromiseResolve"]; + textPromise.call<val>("then", readTextResolve); + } +} + +void QWasmClipboard::writeTextToClipboard() +{ + if (QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) { + val module = val::global("Module"); + val txt = module.call<val>("qtGetClipboardData"); + val format = module.call<val>("qtGetClipboardFormat"); + val navigator = val::global("navigator"); + navigator["clipboard"].call<void>("writeText", txt.as<std::string>()); + } +} diff --git a/src/plugins/platforms/wasm/qwasmclipboard.h b/src/plugins/platforms/wasm/qwasmclipboard.h new file mode 100644 index 0000000000..00aae8fead --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmclipboard.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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(); + void installEventHandlers(const QString &canvasId); + bool hasClipboardApi; + void readTextFromClipboard(); + void writeTextToClipboard(); +}; + +#endif // QWASMCLIPBOARD_H diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp index 3dc6b7d2f3..e6a69c4814 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.cpp +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -56,8 +56,9 @@ QWasmCompositedWindow::QWasmCompositedWindow() { } -QWasmCompositor::QWasmCompositor() - : m_frameBuffer(nullptr) +QWasmCompositor::QWasmCompositor(QWasmScreen *screen) + :QObject(screen) + , m_frameBuffer(nullptr) , m_blitter(new QOpenGLTextureBlitter) , m_needComposit(false) , m_inFlush(false) @@ -107,11 +108,6 @@ void QWasmCompositor::removeWindow(QWasmWindow *window) notifyTopWindowChanged(window); } -void QWasmCompositor::setScreen(QWasmScreen *screen) -{ - m_screen = screen; -} - void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) { QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; @@ -197,7 +193,7 @@ void QWasmCompositor::requestRedraw() QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); } -QWindow *QWasmCompositor::windowAt(QPoint p, int padding) const +QWindow *QWasmCompositor::windowAt(QPoint globalPoint, int padding) const { int index = m_windowStack.count() - 1; // qDebug() << "window at" << "point" << p << "window count" << index; @@ -209,7 +205,7 @@ QWindow *QWasmCompositor::windowAt(QPoint p, int padding) const QRect geometry = compositedWindow.window->windowFrameGeometry() .adjusted(-padding, -padding, padding, padding); - if (compositedWindow.visible && geometry.contains(p)) + if (compositedWindow.visible && geometry.contains(globalPoint)) return m_windowStack.at(index)->window(); --index; } @@ -654,7 +650,7 @@ void QWasmCompositor::frame() m_needComposit = false; - if (m_windowStack.empty() || !m_screen) + if (m_windowStack.empty() || !screen()) return; QWasmWindow *someWindow = nullptr; @@ -673,7 +669,7 @@ void QWasmCompositor::frame() if (m_context.isNull()) { m_context.reset(new QOpenGLContext()); //mContext->setFormat(mScreen->format()); - m_context->setScreen(m_screen->screen()); + m_context->setScreen(screen()->screen()); m_context->create(); } @@ -682,8 +678,8 @@ void QWasmCompositor::frame() if (!m_blitter->isCreated()) m_blitter->create(); - qreal dpr = m_screen->devicePixelRatio(); - glViewport(0, 0, m_screen->geometry().width() * dpr, m_screen->geometry().height() * dpr); + qreal dpr = screen()->devicePixelRatio(); + glViewport(0, 0, screen()->geometry().width() * dpr, screen()->geometry().height() * dpr); m_context->functions()->glClearColor(0.2, 0.2, 0.2, 1.0); m_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -697,7 +693,7 @@ void QWasmCompositor::frame() if (!compositedWindow.visible) continue; - drawWindow(m_blitter.data(), m_screen, window); + drawWindow(m_blitter.data(), screen(), window); } m_blitter->release(); @@ -719,3 +715,8 @@ void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window) requestRedraw(); QWindowSystemInterface::handleWindowActivated(window->window()); } + +QWasmScreen *QWasmCompositor::screen() +{ + return static_cast<QWasmScreen *>(parent()); +} diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h index 4e5ed46cec..3104573073 100644 --- a/src/plugins/platforms/wasm/qwasmcompositor.h +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -62,7 +62,7 @@ class QWasmCompositor : public QObject { Q_OBJECT public: - QWasmCompositor(); + QWasmCompositor(QWasmScreen *screen); ~QWasmCompositor(); enum QWasmSubControl { @@ -103,7 +103,6 @@ public: void addWindow(QWasmWindow *window, QWasmWindow *parentWindow = nullptr); void removeWindow(QWasmWindow *window); - void setScreen(QWasmScreen *screen); void setVisible(QWasmWindow *window, bool visible); void raise(QWasmWindow *window); @@ -117,7 +116,7 @@ public: void redrawWindowContent(); void requestRedraw(); - QWindow *windowAt(QPoint p, int padding = 0) const; + QWindow *windowAt(QPoint globalPoint, int padding = 0) const; QWindow *keyWindow() const; bool event(QEvent *event); @@ -129,8 +128,7 @@ private slots: void frame(); private: - void createFrameBuffer(); - void flushCompletedCallback(int32_t); + QWasmScreen *screen(); void notifyTopWindowChanged(QWasmWindow *window); void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window); @@ -142,7 +140,6 @@ private: QImage *m_frameBuffer; QScopedPointer<QOpenGLContext> m_context; QScopedPointer<QOpenGLTextureBlitter> m_blitter; - QWasmScreen *m_screen; QHash<QWasmWindow *, QWasmCompositedWindow> m_compositedWindows; QList<QWasmWindow *> m_windowStack; diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp index 54804a55b3..2b3f37300d 100644 --- a/src/plugins/platforms/wasm/qwasmcursor.cpp +++ b/src/plugins/platforms/wasm/qwasmcursor.cpp @@ -28,19 +28,21 @@ ****************************************************************************/ #include "qwasmcursor.h" +#include "qwasmscreen.h" #include <QtCore/qdebug.h> +#include <QtGui/qwindow.h> #include <emscripten/emscripten.h> +#include <emscripten/bind.h> void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window) { - if (windowCursor == nullptr) + if (!windowCursor || !window) + return; + QScreen *screen = window->screen(); + if (!screen) return; - - // FIXME: The HTML5 plugin sets the cursor on the native canvas; when using multiple windows - // multiple cursors need to be managed taking mouse postion and stacking into account. - Q_UNUSED(window); // Bitmap and custom cursors are not implemented (will fall back to "auto") if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor) @@ -51,12 +53,10 @@ void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window) if (htmlCursorName.isEmpty()) htmlCursorName = "auto"; - // Set cursor on the main canvas - EM_ASM_ARGS({ - if (Module['canvas']) { - Module['canvas'].style['cursor'] = Pointer_stringify($0); - } - }, htmlCursorName.constData()); + // Set cursor on the canvas + QString canvasId = QWasmScreen::get(screen)->canvasId(); + emscripten::val canvasStyle = emscripten::val::global(canvasId.toUtf8().constData())["style"]; + canvasStyle.set("cursor", emscripten::val(htmlCursorName.constData())); } QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape) diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp index 8ab109f03c..f4ca49997a 100644 --- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp @@ -31,6 +31,7 @@ #include "qwasmeventdispatcher.h" #include "qwasmcompositor.h" #include "qwasmintegration.h" +#include "qwasmclipboard.h" #include <QtGui/qevent.h> #include <qpa/qwindowsysteminterface.h> @@ -39,6 +40,9 @@ #include <QtCore/qobject.h> #include <QtCore/qdeadlinetimer.h> +#include <private/qmakearray_p.h> +#include <QtCore/qnamespace.h> + #include <emscripten/bind.h> #include <iostream> @@ -46,71 +50,336 @@ QT_BEGIN_NAMESPACE using namespace emscripten; +typedef struct emkb2qt { + const char *em; + unsigned int qt; + + constexpr bool operator <=(const emkb2qt &that) const noexcept + { + return !(strcmp(that) > 0); + } + + bool operator <(const emkb2qt &that) const noexcept + { + return ::strcmp(em, that.em) < 0; + } + constexpr int strcmp(const emkb2qt &that, const int i = 0) const + { + return em[i] == 0 && that.em[i] == 0 ? 0 + : em[i] == 0 ? -1 + : that.em[i] == 0 ? 1 + : em[i] < that.em[i] ? -1 + : em[i] > that.em[i] ? 1 + : strcmp(that, i + 1); + } +} emkb2qt_t; + +template<unsigned int Qt, char ... EmChar> +struct Emkb2Qt +{ + static constexpr const char storage[sizeof ... (EmChar) + 1] = {EmChar..., '\0'}; + using Type = emkb2qt_t; + static constexpr Type data() noexcept { return Type{storage, Qt}; } +}; + +template<unsigned int Qt, char ... EmChar> constexpr char Emkb2Qt<Qt, EmChar...>::storage[]; + +static constexpr const auto KeyTbl = qMakeArray( + QSortedData< + Emkb2Qt< Qt::Key_Escape, 'E','s','c','a','p','e' >, + Emkb2Qt< Qt::Key_Tab, 'T','a','b' >, + Emkb2Qt< Qt::Key_Backspace, 'B','a','c','k','s','p','a','c','e' >, + Emkb2Qt< Qt::Key_Return, 'E','n','t','e','r' >, + Emkb2Qt< Qt::Key_Insert, 'I','n','s','e','r','t' >, + Emkb2Qt< Qt::Key_Delete, 'D','e','l','e','t','e' >, + Emkb2Qt< Qt::Key_Pause, 'P','a','u','s','e' >, + Emkb2Qt< Qt::Key_Pause, 'C','l','e','a','r' >, + Emkb2Qt< Qt::Key_Home, 'H','o','m','e' >, + Emkb2Qt< Qt::Key_End, 'E','n','d' >, + Emkb2Qt< Qt::Key_Left, 'A','r','r','o','w','L','e','f','t' >, + Emkb2Qt< Qt::Key_Up, 'A','r','r','o','w','U','p' >, + Emkb2Qt< Qt::Key_Right, 'A','r','r','o','w','R','i','g','h','t' >, + Emkb2Qt< Qt::Key_Down, 'A','r','r','o','w','D','o','w','n' >, + Emkb2Qt< Qt::Key_PageUp, 'P','a','g','e','U','p' >, + Emkb2Qt< Qt::Key_PageDown, 'P','a','g','e','D','o','w','n' >, + Emkb2Qt< Qt::Key_Shift, 'S','h','i','f','t' >, + Emkb2Qt< Qt::Key_Control, 'C','o','n','t','r','o','l' >, + Emkb2Qt< Qt::Key_Meta, 'O','S'>, + Emkb2Qt< Qt::Key_Alt, 'A','l','t','L','e','f','t' >, + Emkb2Qt< Qt::Key_Alt, 'A','l','t' >, + Emkb2Qt< Qt::Key_CapsLock, 'C','a','p','s','L','o','c','k' >, + Emkb2Qt< Qt::Key_NumLock, 'N','u','m','L','o','c','k' >, + Emkb2Qt< Qt::Key_ScrollLock, 'S','c','r','o','l','l','L','o','c','k' >, + Emkb2Qt< Qt::Key_F1, 'F','1' >, + Emkb2Qt< Qt::Key_F2, 'F','2' >, + Emkb2Qt< Qt::Key_F3, 'F','3' >, + Emkb2Qt< Qt::Key_F4, 'F','4' >, + Emkb2Qt< Qt::Key_F5, 'F','5' >, + Emkb2Qt< Qt::Key_F6, 'F','6' >, + Emkb2Qt< Qt::Key_F7, 'F','7' >, + Emkb2Qt< Qt::Key_F8, 'F','8' >, + Emkb2Qt< Qt::Key_F9, 'F','9' >, + Emkb2Qt< Qt::Key_F10, 'F','1','0' >, + Emkb2Qt< Qt::Key_F11, 'F','1','1' >, + Emkb2Qt< Qt::Key_F12, 'F','1','2' >, + Emkb2Qt< Qt::Key_F13, 'F','1','3' >, + Emkb2Qt< Qt::Key_F14, 'F','1','4' >, + Emkb2Qt< Qt::Key_F15, 'F','1','5' >, + Emkb2Qt< Qt::Key_F16, 'F','1','6' >, + Emkb2Qt< Qt::Key_F17, 'F','1','7' >, + Emkb2Qt< Qt::Key_F18, 'F','1','8' >, + Emkb2Qt< Qt::Key_F19, 'F','1','9' >, + Emkb2Qt< Qt::Key_F20, 'F','2','0' >, + Emkb2Qt< Qt::Key_F21, 'F','2','1' >, + Emkb2Qt< Qt::Key_F22, 'F','2','2' >, + Emkb2Qt< Qt::Key_F23, 'F','2','3' >, + Emkb2Qt< Qt::Key_Space, ' ' >, + Emkb2Qt< Qt::Key_Comma, ',' >, + Emkb2Qt< Qt::Key_Minus, '-' >, + Emkb2Qt< Qt::Key_Period, '.' >, + Emkb2Qt< Qt::Key_Slash, '/' >, + Emkb2Qt< Qt::Key_0, '0' >, + Emkb2Qt< Qt::Key_1, '1' >, + Emkb2Qt< Qt::Key_2, '2' >, + Emkb2Qt< Qt::Key_3, '3' >, + Emkb2Qt< Qt::Key_4, '4' >, + Emkb2Qt< Qt::Key_5, '5' >, + Emkb2Qt< Qt::Key_6, '6' >, + Emkb2Qt< Qt::Key_7, '7' >, + Emkb2Qt< Qt::Key_8, '8' >, + Emkb2Qt< Qt::Key_9, '9' >, + Emkb2Qt< Qt::Key_Semicolon, ';' >, + Emkb2Qt< Qt::Key_Equal, '=' >, + Emkb2Qt< Qt::Key_A, 'K','e','y','A' >, + Emkb2Qt< Qt::Key_B, 'K','e','y','B' >, + Emkb2Qt< Qt::Key_C, 'K','e','y','C' >, + Emkb2Qt< Qt::Key_D, 'K','e','y','D' >, + Emkb2Qt< Qt::Key_E, 'K','e','y','E' >, + Emkb2Qt< Qt::Key_F, 'K','e','y','F' >, + Emkb2Qt< Qt::Key_G, 'K','e','y','G' >, + Emkb2Qt< Qt::Key_H, 'K','e','y','H' >, + Emkb2Qt< Qt::Key_I, 'K','e','y','I' >, + Emkb2Qt< Qt::Key_J, 'K','e','y','J' >, + Emkb2Qt< Qt::Key_K, 'K','e','y','K' >, + Emkb2Qt< Qt::Key_L, 'K','e','y','L' >, + Emkb2Qt< Qt::Key_M, 'K','e','y','M' >, + Emkb2Qt< Qt::Key_N, 'K','e','y','N' >, + Emkb2Qt< Qt::Key_O, 'K','e','y','O' >, + Emkb2Qt< Qt::Key_P, 'K','e','y','P' >, + Emkb2Qt< Qt::Key_Q, 'K','e','y','Q' >, + Emkb2Qt< Qt::Key_R, 'K','e','y','R' >, + Emkb2Qt< Qt::Key_S, 'K','e','y','S' >, + Emkb2Qt< Qt::Key_T, 'K','e','y','T' >, + Emkb2Qt< Qt::Key_U, 'K','e','y','U' >, + Emkb2Qt< Qt::Key_V, 'K','e','y','V' >, + Emkb2Qt< Qt::Key_W, 'K','e','y','W' >, + Emkb2Qt< Qt::Key_X, 'K','e','y','X' >, + Emkb2Qt< Qt::Key_Y, 'K','e','y','Y' >, + Emkb2Qt< Qt::Key_Z, 'K','e','y','Z' >, + Emkb2Qt< Qt::Key_BracketLeft, '[' >, + Emkb2Qt< Qt::Key_Backslash, '\\' >, + Emkb2Qt< Qt::Key_BracketRight, ']' >, + Emkb2Qt< Qt::Key_Apostrophe, '\'' >, + Emkb2Qt< Qt::Key_QuoteLeft, 'B','a','c','k','q','u','o','t','e' >, + Emkb2Qt< Qt::Key_multiply, 'N','u','m','p','a','d','M','u','l','t','i','p','l','y' >, + Emkb2Qt< Qt::Key_Minus, 'N','u','m','p','a','d','S','u','b','t','r','a','c','t' >, + Emkb2Qt< Qt::Key_Period, 'N','u','m','p','a','d','D','e','c','i','m','a','l' >, + Emkb2Qt< Qt::Key_Plus, 'N','u','m','p','a','d','A','d','d' >, + Emkb2Qt< Qt::Key_division, 'N','u','m','p','a','d','D','i','v','i','d','e' >, + Emkb2Qt< Qt::Key_Equal, 'N','u','m','p','a','d','E','q','u','a','l' >, + Emkb2Qt< Qt::Key_0, 'N','u','m','p','a','d','0' >, + Emkb2Qt< Qt::Key_1, 'N','u','m','p','a','d','1' >, + Emkb2Qt< Qt::Key_2, 'N','u','m','p','a','d','2' >, + Emkb2Qt< Qt::Key_3, 'N','u','m','p','a','d','3' >, + Emkb2Qt< Qt::Key_4, 'N','u','m','p','a','d','4' >, + Emkb2Qt< Qt::Key_5, 'N','u','m','p','a','d','5' >, + Emkb2Qt< Qt::Key_6, 'N','u','m','p','a','d','6' >, + Emkb2Qt< Qt::Key_7, 'N','u','m','p','a','d','7' >, + Emkb2Qt< Qt::Key_8, 'N','u','m','p','a','d','8' >, + Emkb2Qt< Qt::Key_9, 'N','u','m','p','a','d','9' >, + Emkb2Qt< Qt::Key_Comma, 'N','u','m','p','a','d','C','o','m','m','a' >, + Emkb2Qt< Qt::Key_Enter, 'N','u','m','p','a','d','E','n','t','e','r' >, + Emkb2Qt< Qt::Key_Paste, 'P','a','s','t','e' >, + Emkb2Qt< Qt::Key_AltGr, 'A','l','t','R','i','g','h','t' >, + Emkb2Qt< Qt::Key_Help, 'H','e','l','p' >, + Emkb2Qt< Qt::Key_Equal, '=' >, + Emkb2Qt< Qt::Key_yen, 'I','n','t','l','Y','e','n' >, + + Emkb2Qt< Qt::Key_Exclam, '\x21' >, + Emkb2Qt< Qt::Key_QuoteDbl, '\x22' >, + Emkb2Qt< Qt::Key_NumberSign, '\x23' >, + Emkb2Qt< Qt::Key_Dollar, '\x24' >, + Emkb2Qt< Qt::Key_Percent, '\x25' >, + Emkb2Qt< Qt::Key_Ampersand, '\x26' >, + Emkb2Qt< Qt::Key_ParenLeft, '\x28' >, + Emkb2Qt< Qt::Key_ParenRight, '\x29' >, + Emkb2Qt< Qt::Key_Asterisk, '\x2a' >, + Emkb2Qt< Qt::Key_Plus, '\x2b' >, + Emkb2Qt< Qt::Key_Colon, '\x3a' >, + Emkb2Qt< Qt::Key_Semicolon, '\x3b' >, + Emkb2Qt< Qt::Key_Less, '\x3c' >, + Emkb2Qt< Qt::Key_Equal, '\x3d' >, + Emkb2Qt< Qt::Key_Greater, '\x3e' >, + Emkb2Qt< Qt::Key_Question, '\x3f' >, + Emkb2Qt< Qt::Key_At, '\x40' >, + Emkb2Qt< Qt::Key_BracketLeft, '\x5b' >, + Emkb2Qt< Qt::Key_Backslash, '\x5c' >, + Emkb2Qt< Qt::Key_BracketRight, '\x5d' >, + Emkb2Qt< Qt::Key_AsciiCircum, '\x5e' >, + Emkb2Qt< Qt::Key_Underscore, '\x5f' >, + Emkb2Qt< Qt::Key_QuoteLeft, '\x60'>, + Emkb2Qt< Qt::Key_BraceLeft, '\x7b'>, + Emkb2Qt< Qt::Key_Bar, '\x7c'>, + Emkb2Qt< Qt::Key_BraceRight, '\x7d'>, + Emkb2Qt< Qt::Key_AsciiTilde, '\x7e'>, + Emkb2Qt< Qt::Key_Space, '\x20' >, + Emkb2Qt< Qt::Key_Comma, '\x2c' >, + Emkb2Qt< Qt::Key_Minus, '\x2d' >, + Emkb2Qt< Qt::Key_Period, '\x2e' >, + Emkb2Qt< Qt::Key_Slash, '\x2f' >, + Emkb2Qt< Qt::Key_Apostrophe, '\x27' >, + Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >, + + Emkb2Qt< Qt::Key_Agrave, '\xc3','\xa0' >, + Emkb2Qt< Qt::Key_Aacute, '\xc3','\xa1' >, + Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\xa2' >, + Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\xa4' >, + Emkb2Qt< Qt::Key_AE, '\xc3','\xa6' >, + Emkb2Qt< Qt::Key_Atilde, '\xc3','\xa3' >, + Emkb2Qt< Qt::Key_Aring, '\xc3','\xa5' >, + Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\xa7' >, + Emkb2Qt< Qt::Key_Egrave, '\xc3','\xa8' >, + Emkb2Qt< Qt::Key_Eacute, '\xc3','\xa9' >, + Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\xaa' >, + Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\xab' >, + Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\xae' >, + Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\xaf' >, + Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\xb4' >, + Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\xb6' >, + Emkb2Qt< Qt::Key_Ograve, '\xc3','\xb2' >, + Emkb2Qt< Qt::Key_Oacute, '\xc3','\xb3' >, + Emkb2Qt< Qt::Key_Ooblique, '\xc3','\xb8' >, + Emkb2Qt< Qt::Key_Otilde, '\xc3','\xb5' >, + Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\xbb' >, + Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\xbc' >, + Emkb2Qt< Qt::Key_Ugrave, '\xc3','\xb9' >, + Emkb2Qt< Qt::Key_Uacute, '\xc3','\xba' >, + Emkb2Qt< Qt::Key_Ntilde, '\xc3','\xb1' >, + Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\xbf' > + >::Data{} + ); + +static constexpr const auto DeadKeyShiftTbl = qMakeArray( + QSortedData< + // shifted + Emkb2Qt< Qt::Key_Agrave, '\xc3','\x80' >, + Emkb2Qt< Qt::Key_Aacute, '\xc3','\x81' >, + Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\x82' >, + Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\x84' >, + Emkb2Qt< Qt::Key_AE, '\xc3','\x86' >, + Emkb2Qt< Qt::Key_Atilde, '\xc3','\x83' >, + Emkb2Qt< Qt::Key_Aring, '\xc3','\x85' >, + Emkb2Qt< Qt::Key_Egrave, '\xc3','\x88' >, + Emkb2Qt< Qt::Key_Eacute, '\xc3','\x89' >, + Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\x8a' >, + Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\x8b' >, + Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\x8e' >, + Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\x8f' >, + Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\x94' >, + Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\x96' >, + Emkb2Qt< Qt::Key_Ograve, '\xc3','\x92' >, + Emkb2Qt< Qt::Key_Oacute, '\xc3','\x93' >, + Emkb2Qt< Qt::Key_Ooblique, '\xc3','\x98' >, + Emkb2Qt< Qt::Key_Otilde, '\xc3','\x95' >, + Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\x9b' >, + Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\x9c' >, + Emkb2Qt< Qt::Key_Ugrave, '\xc3','\x99' >, + Emkb2Qt< Qt::Key_Uacute, '\xc3','\x9a' >, + Emkb2Qt< Qt::Key_Ntilde, '\xc3','\x91' >, + Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\x87' >, + Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\x8f' > + >::Data{} +); + // macOS CTRL <-> META switching. We most likely want to enable // the existing switching code in QtGui, but for now do it here. static bool g_usePlatformMacCtrlMetaSwitching = false; bool g_useNaturalScrolling = true; // natural scrolling is default on linux/windows -void setNaturalScrolling(bool use) { - g_useNaturalScrolling = use; +static void mouseWheelEvent(emscripten::val event) { + + emscripten::val wheelInterted = event["webkitDirectionInvertedFromDevice"]; + + if (wheelInterted.as<bool>()) { + g_useNaturalScrolling = true; + } } -EMSCRIPTEN_BINDINGS(mouse_module) { - function("setNaturalScrolling", &setNaturalScrolling); +EMSCRIPTEN_BINDINGS(qtMouseModule) { + function("qtMouseWheelEvent", &mouseWheelEvent); } -QWasmEventTranslator::QWasmEventTranslator(QObject *parent) - : QObject(parent) +QWasmEventTranslator::QWasmEventTranslator(QWasmScreen *screen) + : QObject(screen) , draggedWindow(nullptr) , lastWindow(nullptr) , pressedButtons(Qt::NoButton) , resizeMode(QWasmWindow::ResizeNone) { - emscripten_set_keydown_callback(0, (void *)this, 1, &keyboard_cb); - emscripten_set_keyup_callback(0, (void *)this, 1, &keyboard_cb); - - emscripten_set_mousedown_callback(0, (void *)this, 1, &mouse_cb); - emscripten_set_mouseup_callback(0, (void *)this, 1, &mouse_cb); - emscripten_set_mousemove_callback(0, (void *)this, 1, &mouse_cb); - - emscripten_set_focus_callback(0, (void *)this, 1, &focus_cb); - - emscripten_set_wheel_callback(0, (void *)this, 1, &wheel_cb); - touchDevice = new QTouchDevice; touchDevice->setType(QTouchDevice::TouchScreen); touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); QWindowSystemInterface::registerTouchDevice(touchDevice); - emscripten_set_touchstart_callback("#canvas", (void *)this, 1, &touchCallback); - emscripten_set_touchend_callback("#canvas", (void *)this, 1, &touchCallback); - emscripten_set_touchmove_callback("#canvas", (void *)this, 1, &touchCallback); - emscripten_set_touchcancel_callback("#canvas", (void *)this, 1, &touchCallback); + initEventHandlers(); +} + +void QWasmEventTranslator::initEventHandlers() +{ + qDebug() << "QWasmEventTranslator::initEventHandlers"; + + QByteArray _canvasId = screen()->canvasId().toUtf8(); + const char *canvasId = _canvasId.constData(); // The Platform Detect: expand coverage and move as needed enum Platform { GenericPlatform, MacOSPlatform }; - Platform platform = - Platform(EM_ASM_INT("if (navigator.platform.includes(\"Mac\")) return 1; return 0;")); - + Platform platform = Platform(emscripten::val::global("navigator")["platform"] + .call<bool>("includes", emscripten::val("Mac"))); g_usePlatformMacCtrlMetaSwitching = (platform == MacOSPlatform); if (platform == MacOSPlatform) { g_useNaturalScrolling = false; // make this !default on macOS - EM_ASM( - if (window.safari !== undefined) {//this only works on safari - Module["canvas"].addEventListener('wheel', mouseWheelEvent); - function mouseWheelEvent(e) { - if (event.webkitDirectionInvertedFromDevice) { - Module.setNaturalScrolling(event.webkitDirectionInvertedFromDevice); - } - } - } - ); + + if (emscripten::val::global("window")["safari"].isUndefined()) { + + emscripten::val::global(canvasId).call<void>("addEventListener", + std::string("wheel"), + val::module_property("qtMouseWheelEvent")); + } } + + emscripten_set_keydown_callback(canvasId, (void *)this, 1, &keyboard_cb); + emscripten_set_keyup_callback(canvasId, (void *)this, 1, &keyboard_cb); + + emscripten_set_mousedown_callback(canvasId, (void *)this, 1, &mouse_cb); + emscripten_set_mouseup_callback(canvasId, (void *)this, 1, &mouse_cb); + emscripten_set_mousemove_callback(canvasId, (void *)this, 1, &mouse_cb); + + emscripten_set_focus_callback(canvasId, (void *)this, 1, &focus_cb); + + emscripten_set_wheel_callback(canvasId, (void *)this, 1, &wheel_cb); + + emscripten_set_touchstart_callback(canvasId, (void *)this, 1, &touchCallback); + emscripten_set_touchend_callback(canvasId, (void *)this, 1, &touchCallback); + emscripten_set_touchmove_callback(canvasId, (void *)this, 1, &touchCallback); + emscripten_set_touchcancel_callback(canvasId, (void *)this, 1, &touchCallback); + + emscripten_set_resize_callback(nullptr, (void *)this, 1, uiEvent_cb); // Note: handles browser window resize + } template <typename Event> @@ -153,132 +422,49 @@ QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateMouseEventModifier(c int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) { - Q_UNUSED(userData) - - bool alphanumeric; - Qt::Key qtKey = translateEmscriptKey(keyEvent, &alphanumeric); + QWasmEventTranslator *wasmTranslator = reinterpret_cast<QWasmEventTranslator *>(userData); + bool accepted = wasmTranslator->processKeyboard(eventType, keyEvent); - QEvent::Type keyType = QEvent::None; - switch (eventType) { - case EMSCRIPTEN_EVENT_KEYPRESS: - case EMSCRIPTEN_EVENT_KEYDOWN: //down - keyType = QEvent::KeyPress; - break; - case EMSCRIPTEN_EVENT_KEYUP: //up - keyType = QEvent::KeyRelease; - break; - default: - break; - }; - - 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); - QWasmEventDispatcher::maintainTimers(); return accepted ? 1 : 0; } -Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumeric) +QWasmScreen *QWasmEventTranslator::screen() { - Qt::Key qtKey; - if (outAlphanumeric) - *outAlphanumeric = false; - - switch (emscriptKey->keyCode) { - case KeyMultiply: qtKey = Qt::Key_Asterisk; *outAlphanumeric = true; break; - case KeyAdd: qtKey = Qt::Key_Plus; *outAlphanumeric = true; break; - case KeyMinus: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; - case KeySubtract: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; - case KeyDecimal: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; - case KeyDivide: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; - case KeyNumPad0: qtKey = Qt::Key_0; *outAlphanumeric = true; break; - case KeyNumPad1: qtKey = Qt::Key_1; *outAlphanumeric = true; break; - case KeyNumPad2: qtKey = Qt::Key_2; *outAlphanumeric = true; break; - case KeyNumPad3: qtKey = Qt::Key_3; *outAlphanumeric = true; break; - case KeyNumPad4: qtKey = Qt::Key_4; *outAlphanumeric = true; break; - case KeyNumPad5: qtKey = Qt::Key_5; *outAlphanumeric = true; break; - case KeyNumPad6: qtKey = Qt::Key_6; *outAlphanumeric = true; break; - case KeyNumPad7: qtKey = Qt::Key_7; *outAlphanumeric = true; break; - case KeyNumPad8: qtKey = Qt::Key_8; *outAlphanumeric = true; break; - case KeyNumPad9: qtKey = Qt::Key_9; *outAlphanumeric = true; break; - case KeyComma: qtKey = Qt::Key_Comma; *outAlphanumeric = true; break; - case KeyPeriod: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; - case KeySlash: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; - case KeySemiColon: qtKey = Qt::Key_Semicolon; *outAlphanumeric = true; break; - case KeyEquals: qtKey = Qt::Key_Equal; *outAlphanumeric = true; break; - case KeyOpenBracket: qtKey = Qt::Key_BracketLeft; *outAlphanumeric = true; break; - case KeyCloseBracket: qtKey = Qt::Key_BracketRight; *outAlphanumeric = true; break; - case KeyBackSlash: qtKey = Qt::Key_Backslash; *outAlphanumeric = true; break; - case KeyMeta: - Q_FALLTHROUGH(); - case KeyMetaRight: - qtKey = Qt::Key_Meta; - break; - case KeyTab: qtKey = Qt::Key_Tab; break; - case KeyClear: qtKey = Qt::Key_Clear; break; - case KeyBackSpace: qtKey = Qt::Key_Backspace; break; - case KeyEnter: qtKey = Qt::Key_Return; break; - case KeyShift: qtKey = Qt::Key_Shift; break; - case KeyControl: qtKey = Qt::Key_Control; break; - case KeyAlt: qtKey = Qt::Key_Alt; break; - case KeyCapsLock: qtKey = Qt::Key_CapsLock; break; - case KeyEscape: qtKey = Qt::Key_Escape; break; - case KeyPageUp: qtKey = Qt::Key_PageUp; break; - case KeyPageDown: qtKey = Qt::Key_PageDown; break; - case KeyEnd: qtKey = Qt::Key_End; break; - case KeyHome: qtKey = Qt::Key_Home; break; - case KeyLeft: qtKey = Qt::Key_Left; break; - case KeyUp: qtKey = Qt::Key_Up; break; - case KeyRight: qtKey = Qt::Key_Right; break; - case KeyDown: qtKey = Qt::Key_Down; break; - case KeyBrightnessDown: qtKey = Qt::Key_MonBrightnessDown; break; - case KeyBrightnessUp: qtKey = Qt::Key_MonBrightnessUp; break; - case KeyMediaTrackPrevious: qtKey = Qt::Key_MediaPrevious; break; - case KeyMediaPlayPause: qtKey = Qt::Key_MediaTogglePlayPause; break; - case KeyMediaTrackNext: qtKey = Qt::Key_MediaNext; break; - case KeyAudioVolumeMute: qtKey = Qt::Key_VolumeMute; break; - case KeyAudioVolumeDown: qtKey = Qt::Key_VolumeDown; break; - case KeyAudioVolumeUp: qtKey = Qt::Key_VolumeUp; break; - case KeyDelete: qtKey = Qt::Key_Delete; break; - - case KeyF1: qtKey = Qt::Key_F1; break; - case KeyF2: qtKey = Qt::Key_F2; break; - case KeyF3: qtKey = Qt::Key_F3; break; - case KeyF4: qtKey = Qt::Key_F4; break; - case KeyF5: qtKey = Qt::Key_F5; break; - case KeyF6: qtKey = Qt::Key_F6; break; - case KeyF7: qtKey = Qt::Key_F7; break; - case KeyF8: qtKey = Qt::Key_F8; break; - case KeyF9: qtKey = Qt::Key_F9; break; - case KeyF10: qtKey = Qt::Key_F10; break; - case KeyF11: qtKey = Qt::Key_F11; break; - case KeyF12: qtKey = Qt::Key_F12; break; - case 124: qtKey = Qt::Key_F13; break; - case 125: qtKey = Qt::Key_F14; break; - - case KeySpace: - default: - if (outAlphanumeric) - *outAlphanumeric = true; - qtKey = static_cast<Qt::Key>(emscriptKey->keyCode); - break; - } + return static_cast<QWasmScreen *>(parent()); +} + +Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey) +{ + Qt::Key qtKey = Qt::Key_unknown; - // Handle Mac command key. Using event->keyCode as above is - // no reliable since the codes differ between browsers. - if (qstrncmp(emscriptKey->key, "Meta", 4) == 0) { - qtKey = Qt::Key_Meta; - *outAlphanumeric = false; + if (qstrncmp(emscriptKey->code, "Key", 3) == 0 || qstrncmp(emscriptKey->code, "Numpad", 6) == 0) { + + emkb2qt_t searchKey{emscriptKey->code, 0}; // search emcsripten code + auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey); + if (it1 != KeyTbl.end() && !(searchKey < *it1)) { + qtKey = static_cast<Qt::Key>(it1->qt); + } + } else if (qstrncmp(emscriptKey->key, "Dead", 4) == 0 ) { + emkb2qt_t searchKey1{emscriptKey->code, 0}; + for (auto it1 = KeyTbl.cbegin(); it1 != KeyTbl.end(); ++it1) + if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) { + qtKey = static_cast<Qt::Key>(it1->qt); + } } - if (g_usePlatformMacCtrlMetaSwitching) { - if (qtKey == Qt::Key_Meta) - qtKey = Qt::Key_Control; - else if (qtKey == Qt::Key_Control) - qtKey = Qt::Key_Meta; + if (qtKey == Qt::Key_unknown) { + emkb2qt_t searchKey{emscriptKey->key, 0}; // search unicode key + auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey); + if (it1 != KeyTbl.end() && !(searchKey < *it1)) { + qtKey = static_cast<Qt::Key>(it1->qt); + } + } + if (qtKey == Qt::Key_unknown) {//try harder with shifted number keys + emkb2qt_t searchKey1{emscriptKey->key, 0}; + for (auto it1 = KeyTbl.cbegin(); it1 != KeyTbl.end(); ++it1) + if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) { + qtKey = static_cast<Qt::Key>(it1->qt); + } } return qtKey; @@ -363,27 +549,30 @@ void resizeWindow(QWindow *window, QWasmWindow::ResizeMode mode, void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent) { auto timestamp = mouseEvent->timestamp; - QPoint point(mouseEvent->canvasX, mouseEvent->canvasY); + QPoint targetPoint(mouseEvent->targetX, mouseEvent->targetY); + QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; QEvent::Type buttonEventType = QEvent::None; - Qt::MouseButton button = translateMouseButton(mouseEvent->button); Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent); - QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); - if (window2 != nullptr) - lastWindow = window2; - - QWasmWindow *htmlWindow = static_cast<QWasmWindow*>(window2->handle()); + QWindow *window2 = screen()->compositor()->windowAt(globalPoint, 5); + if (window2 == nullptr) + return; + lastWindow = window2; - bool interior = window2 && window2->geometry().contains(point); + QPoint localPoint = window2->mapFromGlobal(globalPoint); + bool interior = window2->geometry().contains(globalPoint); - QPoint localPoint(point.x() - window2->geometry().x(), point.y() - window2->geometry().y()); + QWasmWindow *htmlWindow = static_cast<QWasmWindow*>(window2->handle()); switch (eventType) { case EMSCRIPTEN_EVENT_MOUSEDOWN: { - if (window2) + if (window2) { window2->raise(); + if (!window2->isActive()) + window2->requestActivate(); + } pressedButtons.setFlag(button); @@ -391,18 +580,18 @@ void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEven pressedWindow = window2; buttonEventType = QEvent::MouseButtonPress; if (!(htmlWindow->m_windowState & Qt::WindowFullScreen) && !(htmlWindow->m_windowState & Qt::WindowMaximized)) { - if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) && htmlWindow->isPointOnTitle(point)) + if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) && htmlWindow->isPointOnTitle(globalPoint)) draggedWindow = window2; - else if (htmlWindow && htmlWindow->isPointOnResizeRegion(point)) { + else if (htmlWindow && htmlWindow->isPointOnResizeRegion(globalPoint)) { draggedWindow = window2; - resizeMode = htmlWindow->resizeModeAtPoint(point); - resizePoint = point; + resizeMode = htmlWindow->resizeModeAtPoint(globalPoint); + resizePoint = globalPoint; resizeStartRect = window2->geometry(); } } } - htmlWindow->injectMousePressed(localPoint, point, button, modifiers); + htmlWindow->injectMousePressed(localPoint, globalPoint, button, modifiers); break; } case EMSCRIPTEN_EVENT_MOUSEUP: @@ -422,7 +611,7 @@ void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEven } if (oldWindow) - oldWindow->injectMouseReleased(localPoint, point, button, modifiers); + oldWindow->injectMouseReleased(localPoint, globalPoint, button, modifiers); break; } case EMSCRIPTEN_EVENT_MOUSEMOVE: // drag event @@ -451,7 +640,7 @@ void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEven } if (window2 && interior) { QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>( - window2, timestamp, localPoint, point, pressedButtons, button, buttonEventType, modifiers); + window2, timestamp, localPoint, globalPoint, pressedButtons, button, buttonEventType, modifiers); } } @@ -463,8 +652,8 @@ int QWasmEventTranslator::focus_cb(int /*eventType*/, const EmscriptenFocusEvent int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) { Q_UNUSED(eventType) - Q_UNUSED(userData) + QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData); EmscriptenMouseEvent mouseEvent = wheelEvent->mouse; int scrollFactor = 0; @@ -483,21 +672,24 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh if (g_useNaturalScrolling) //macOS platform has document oriented scrolling scrollFactor = -scrollFactor; - Qt::KeyboardModifiers modifiers = translateMouseEventModifier(&mouseEvent); + QWasmEventTranslator *translator = (QWasmEventTranslator*)userData; + Qt::KeyboardModifiers modifiers = translator->translateMouseEventModifier(&mouseEvent); auto timestamp = mouseEvent.timestamp; - QPoint globalPoint(mouseEvent.canvasX, mouseEvent.canvasY); + QPoint targetPoint(mouseEvent.targetX, mouseEvent.targetY); + QPoint globalPoint = eventTranslator->screen()->geometry().topLeft() + targetPoint; - QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(globalPoint, 5); - - QPoint localPoint(globalPoint.x() - window2->geometry().x(), globalPoint.y() - window2->geometry().y()); + QWindow *window2 = eventTranslator->screen()->compositor()->windowAt(globalPoint, 5); + if (!window2) + return 0; + QPoint localPoint = window2->mapFromGlobal(globalPoint); QPoint pixelDelta; if (wheelEvent->deltaY != 0) pixelDelta.setY(wheelEvent->deltaY * scrollFactor); if (wheelEvent->deltaX != 0) pixelDelta.setX(wheelEvent->deltaX * scrollFactor); - QWindowSystemInterface::handleWheelEvent(window2, timestamp, localPoint, globalPoint, QPoint(), pixelDelta, modifiers); - + QWindowSystemInterface::handleWheelEvent(window2, timestamp, localPoint, + globalPoint, QPoint(), pixelDelta, modifiers); QWasmEventDispatcher::maintainTimers(); return 1; @@ -505,6 +697,12 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { + auto translator = reinterpret_cast<QWasmEventTranslator*>(userData); + return translator->handleTouch(eventType, touchEvent); +} + +int QWasmEventTranslator::handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent) +{ QList<QWindowSystemInterface::TouchPoint> touchPointList; touchPointList.reserve(touchEvent->numTouches); QWindow *window2; @@ -513,46 +711,72 @@ int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEven const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; - QPoint point(touches->canvasX, touches->canvasY); - window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); + QPoint targetPoint(touches->targetX, touches->targetY); + QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; + + window2 = this->screen()->compositor()->windowAt(globalPoint, 5); + if (window2 == nullptr) + continue; QWindowSystemInterface::TouchPoint touchPoint; - auto cX = point.x(); - auto cY = point.y(); touchPoint.area = QRect(0, 0, 8, 8); - touchPoint.area.moveCenter(QPointF(cX,cY)); // simulate area - touchPoint.id = touches->identifier; - touchPoint.normalPosition = QPointF(cX / window2->width(), cY / window2->height()); + touchPoint.pressure = 1.0; + + touchPoint.area.moveCenter(globalPoint); + + const auto tp = pressedTouchIds.constFind(touchPoint.id); + if (tp != pressedTouchIds.constEnd()) + touchPoint.normalPosition = tp.value(); + + QPointF localPoint = QPointF(window2->mapFromGlobal(globalPoint)); + QPointF normalPosition(localPoint.x() / window2->width(), + localPoint.y() / window2->height()); + + const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition); + touchPoint.normalPosition = normalPosition; switch (eventType) { case EMSCRIPTEN_EVENT_TOUCHSTART: - touchPoint.state = Qt::TouchPointPressed; + if (tp != pressedTouchIds.constEnd()) { + touchPoint.state = (stationaryTouchPoint + ? Qt::TouchPointStationary + : Qt::TouchPointMoved); + } else { + touchPoint.state = Qt::TouchPointPressed; + } + pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); + break; case EMSCRIPTEN_EVENT_TOUCHEND: touchPoint.state = Qt::TouchPointReleased; + pressedTouchIds.remove(touchPoint.id); break; case EMSCRIPTEN_EVENT_TOUCHMOVE: - touchPoint.state = Qt::TouchPointMoved; + touchPoint.state = (stationaryTouchPoint + ? Qt::TouchPointStationary + : Qt::TouchPointMoved); + + pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); break; default: - Q_UNREACHABLE(); + break; } touchPointList.append(touchPoint); } - QWasmEventTranslator *wasmEventTranslator = (QWasmEventTranslator*)userData; QFlags<Qt::KeyboardModifier> keyModifier = translatKeyModifier(touchEvent); - if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) - QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, touchPointList, keyModifier); - else - QWindowSystemInterface::handleTouchCancelEvent(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, keyModifier); + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + window2, getTimestamp(), touchDevice, touchPointList, keyModifier); + + if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) + QWindowSystemInterface::handleTouchCancelEvent(window2, getTimestamp(), touchDevice, keyModifier); QWasmEventDispatcher::maintainTimers(); - return 1; + return 0; } quint64 QWasmEventTranslator::getTimestamp() @@ -560,4 +784,149 @@ quint64 QWasmEventTranslator::getTimestamp() return QDeadlineTimer::current().deadlineNSecs() / 1000; } +Qt::Key QWasmEventTranslator::translateDeadKey(Qt::Key deadKey, Qt::Key accentBaseKey) +{ + Qt::Key wasmKey = Qt::Key_unknown; + switch (deadKey) { +#ifdef Q_OS_MACOS + case Qt::Key_QuoteLeft: // ` macOS: Key_Dead_Grave +#else + case Qt::Key_O: // ´ Key_Dead_Grave +#endif + wasmKey = graveKeyTable.value(accentBaseKey); + break; + case Qt::Key_E: // ´ Key_Dead_Acute + wasmKey = acuteKeyTable.value(accentBaseKey); + break; + case Qt::Key_AsciiTilde: + case Qt::Key_N:// Key_Dead_Tilde + wasmKey = tildeKeyTable.value(accentBaseKey); + break; +#ifndef Q_OS_MACOS + case Qt::Key_QuoteLeft: +#endif + case Qt::Key_U:// ¨ Key_Dead_Diaeresis + wasmKey = diaeresisKeyTable.value(accentBaseKey); + break; + case Qt::Key_I:// macOS Key_Dead_Circumflex + case Qt::Key_6:// linux + case Qt::Key_Apostrophe:// linux + wasmKey = circumflexKeyTable.value(accentBaseKey); + break; + break; + default: + break; + }; + + return wasmKey; +} + +bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent) +{ + Qt::Key qtKey = translateEmscriptKey(keyEvent); + + Qt::KeyboardModifiers modifiers = translateKeyboardEventModifier(keyEvent); + + QString keyText; + QEvent::Type keyType = QEvent::None; + switch (eventType) { + case EMSCRIPTEN_EVENT_KEYPRESS: + case EMSCRIPTEN_EVENT_KEYDOWN: // down + keyType = QEvent::KeyPress; + + if (m_emDeadKey != Qt::Key_unknown) { + + Qt::Key transformedKey = translateDeadKey(m_emDeadKey, qtKey); + + if (transformedKey != Qt::Key_unknown) + qtKey = transformedKey; + + if (keyEvent->shiftKey == 0) { + for (auto it = KeyTbl.cbegin(); it != KeyTbl.end(); ++it) { + if (it != KeyTbl.end() && (qtKey == static_cast<Qt::Key>(it->qt))) { + keyText = it->em; + m_emDeadKey = Qt::Key_unknown; + break; + } + } + } else { + for (auto it = DeadKeyShiftTbl.cbegin(); it != DeadKeyShiftTbl.end(); ++it) { + if (it != DeadKeyShiftTbl.end() && (qtKey == static_cast<Qt::Key>(it->qt))) { + keyText = it->em; + m_emDeadKey = Qt::Key_unknown; + break; + } + } + } + } + if (qstrncmp(keyEvent->key, "Dead", 4) == 0 || qtKey == Qt::Key_AltGr) { + qtKey = translateEmscriptKey(keyEvent); + m_emStickyDeadKey = true; + if (keyEvent->shiftKey == 1 && qtKey == Qt::Key_QuoteLeft) + qtKey = Qt::Key_AsciiTilde; + m_emDeadKey = qtKey; + } + break; + case EMSCRIPTEN_EVENT_KEYUP: // up + keyType = QEvent::KeyRelease; + if (m_emStickyDeadKey && qtKey != Qt::Key_Alt) { + m_emStickyDeadKey = false; + } + break; + default: + break; + }; + + if (keyType == QEvent::None) + 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 && + mods.testFlag(Qt::ControlModifier) + && qtKey == Qt::Key_V) { + QWasmIntegration::get()->getWasmClipboard()->readTextFromClipboard(); + } else { + if (keyText.isEmpty()) + keyText = QString(keyEvent->key); + if (keyText.size() > 1) + keyText.clear(); + accepted = QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>( + 0, keyType, qtKey, modifiers, keyText); + } + if (keyType == QEvent::KeyPress && + mods.testFlag(Qt::ControlModifier) + && qtKey == Qt::Key_C) { + QWasmIntegration::get()->getWasmClipboard()->writeTextToClipboard(); + } + + QWasmEventDispatcher::maintainTimers(); + + return accepted; +} + +int QWasmEventTranslator::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData) +{ + Q_UNUSED(e) + QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData); + + if (eventType == EMSCRIPTEN_EVENT_RESIZE) { + // This resize event is called when the HTML window is resized. Depending + // on the page layout the the canvas might also have been resized, so we + // update the Qt screen size (and canvas render size). + eventTranslator->screen()->updateQScreenAndCanvasRenderSize(); + } + + return 0; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h index f3dff8e48c..d6043072ba 100644 --- a/src/plugins/platforms/wasm/qwasmeventtranslator.h +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.h @@ -36,6 +36,7 @@ #include <emscripten/html5.h> #include "qwasmwindow.h" #include <QtGui/qtouchdevice.h> +#include <QHash> QT_BEGIN_NAMESPACE @@ -45,133 +46,9 @@ class QWasmEventTranslator : public QObject { Q_OBJECT - enum KeyCode { - // numpad - KeyNumPad0 = 0x60, - KeyNumPad1 = 0x61, - KeyNumPad2 = 0x62, - KeyNumPad3 = 0x63, - KeyNumPad4 = 0x64, - KeyNumPad5 = 0x65, - KeyNumPad6 = 0x66, - KeyNumPad7 = 0x67, - KeyNumPad8 = 0x68, - KeyNumPad9 = 0x69, - KeyMultiply = 0x6A, - KeyAdd = 0x6B, - KeySeparator = 0x6C, - KeySubtract = 0x6D, - KeyDecimal = 0x6E, - KeyDivide = 0x6F, - KeyMeta = 0x5B, - KeyMetaRight = 0x5C, - //////// - KeyClear = 0x90, - KeyEnter = 0xD, - KeyBackSpace = 0x08, - KeyCancel = 0x03, - KeyTab = 0x09, - KeyShift = 0x10, - KeyControl = 0x11, - KeyAlt = 0x12, - KeyPause = 0x13, - KeyCapsLock = 0x14, - KeyEscape = 0x1B, - KeySpace = 0x20, - KeyPageUp = 0x21, - KeyPageDown = 0x22, - KeyEnd = 0x23, - KeyHome = 0x24, - KeyLeft = 0x25, - KeyUp = 0x26, - KeyRight = 0x27, - KeyDown = 0x28, - KeyComma = 0xBC, - KeyPeriod = 0xBE, - KeySlash = 0xBF, - KeyZero = 0x30, - KeyOne = 0x31, - KeyTwo = 0x32, - KeyThree = 0x33, - KeyFour = 0x34, - KeyFive = 0x35, - KeySix = 0x36, - KeySeven = 0x37, - KeyEight = 0x38, - KeyNine = 0x39, - KeyBrightnessDown = 0xD8, - KeyBrightnessUp = 0xD9, - KeyMediaTrackPrevious = 0xB1, - KeyMediaPlayPause = 0xB3, - KeyMediaTrackNext = 0xB0, - KeyAudioVolumeMute = 0xAD, - KeyAudioVolumeDown = 0xAE, - KeyAudioVolumeUp = 0xAF, - KeySemiColon = 0xBA, - KeyEquals = 0xBB, - KeyMinus = 0xBD, - KeyA = 0x41, - KeyB = 0x42, - KeyC = 0x43, - KeyD = 0x44, - KeyE = 0x45, - KeyF = 0x46, - KeyG = 0x47, - KeyH = 0x48, - KeyI = 0x49, - KeyJ = 0x4A, - KeyK = 0x4B, - KeyL = 0x4C, - KeyM = 0x4D, - KeyN = 0x4E, - KeyO = 0x4F, - KeyP = 0x50, - KeyQ = 0x51, - KeyR = 0x52, - KeyS = 0x53, - KeyT = 0x54, - KeyU = 0x55, - KeyV = 0x56, - KeyW = 0x57, - KeyX = 0x58, - KeyY = 0x59, - KeyZ = 0x5A, - KeyOpenBracket = 0xDB, - KeyBackSlash = 0xDC, - KeyCloseBracket = 0xDD, - KeyF1 = 0x70, - KeyF2 = 0x71, - KeyF3 = 0x72, - KeyF4 = 0x73, - KeyF5 = 0x74, - KeyF6 = 0x75, - KeyF7 = 0x76, - KeyF8 = 0x77, - KeyF9 = 0x78, - KeyF10 = 0x79, - KeyF11 = 0x7A, - KeyF12 = 0x7B, - KeyDelete = 0x2E, - KeyNumLock = 0x90, - KeyScrollLock = 0x91, - KeyPrintScreen = 0x9A, - KeyInsert = 0x9B, - KeyHelp = 0x9C, - KeyBackQuote = 0xC0, - KeyQuote = 0xDE, - KeyFinal = 0x18, - KeyConvert = 0x1C, - KeyNonConvert = 0x1D, - KeyAccept = 0x1E, - KeyModeChange = 0x1F, - KeyKana = 0x15, - KeyKanji = 0x19, - KeyUndefined = 0x0 - }; - public: - explicit QWasmEventTranslator(QObject *parent = 0); + explicit QWasmEventTranslator(QWasmScreen *screen); static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); static int mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); @@ -180,19 +57,65 @@ public: static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData); + static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData); + void processEvents(); + void initEventHandlers(); + int handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent); Q_SIGNALS: void getWindowAt(const QPoint &point, QWindow **window); private: - static Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumretic); + QWasmScreen *screen(); + Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey); template <typename Event> - static QFlags<Qt::KeyboardModifier> translatKeyModifier(const Event *event); - static QFlags<Qt::KeyboardModifier> translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent); - static QFlags<Qt::KeyboardModifier> translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent); - static Qt::MouseButton translateMouseButton(unsigned short button); + QFlags<Qt::KeyboardModifier> translatKeyModifier(const Event *event); + QFlags<Qt::KeyboardModifier> translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent); + QFlags<Qt::KeyboardModifier> translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent); + Qt::MouseButton translateMouseButton(unsigned short button); void processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent); + bool processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent); + + Qt::Key translateDeadKey(Qt::Key deadKey, Qt::Key accentBaseKey); + + QHash<Qt::Key , Qt::Key> tildeKeyTable { // ~ + { Qt::Key_A, Qt::Key_Atilde}, + { Qt::Key_N, Qt::Key_Ntilde}, + { Qt::Key_O, Qt::Key_Otilde} + }; + QHash<Qt::Key , Qt::Key> graveKeyTable { // ` + { Qt::Key_A, Qt::Key_Agrave}, + { Qt::Key_E, Qt::Key_Egrave}, + { Qt::Key_I, Qt::Key_Igrave}, + { Qt::Key_O, Qt::Key_Ograve}, + { Qt::Key_U, Qt::Key_Ugrave} + }; + QHash<Qt::Key , Qt::Key> acuteKeyTable { // ' + { Qt::Key_A, Qt::Key_Aacute}, + { Qt::Key_E, Qt::Key_Eacute}, + { Qt::Key_I, Qt::Key_Iacute}, + { Qt::Key_O, Qt::Key_Oacute}, + { Qt::Key_U, Qt::Key_Uacute}, + { Qt::Key_Y, Qt::Key_Yacute} + }; + QHash<Qt::Key , Qt::Key> diaeresisKeyTable { // umlaut ¨ + { Qt::Key_A, Qt::Key_Adiaeresis}, + { Qt::Key_E, Qt::Key_Ediaeresis}, + { Qt::Key_I, Qt::Key_Idiaeresis}, + { Qt::Key_O, Qt::Key_Odiaeresis}, + { Qt::Key_U, Qt::Key_Udiaeresis}, + { Qt::Key_Y, Qt::Key_ydiaeresis} + }; + QHash<Qt::Key , Qt::Key> circumflexKeyTable { // ^ + { Qt::Key_A, Qt::Key_Acircumflex}, + { Qt::Key_E, Qt::Key_Ecircumflex}, + { Qt::Key_I, Qt::Key_Icircumflex}, + { Qt::Key_O, Qt::Key_Ocircumflex}, + { Qt::Key_U, Qt::Key_Ucircumflex} + }; + + QMap <int, QPointF> pressedTouchIds; private: QWindow *draggedWindow; @@ -205,6 +128,9 @@ private: QRect resizeStartRect; QTouchDevice *touchDevice; quint64 getTimestamp(); + + Qt::Key m_emDeadKey = Qt::Key_unknown; + bool m_emStickyDeadKey = false; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp index 3829043d07..e601d553f2 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.cpp +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -33,6 +33,8 @@ #include "qwasmcompositor.h" #include "qwasmopenglcontext.h" #include "qwasmtheme.h" +#include "qwasmclipboard.h" +#include "qwasmservices.h" #include "qwasmwindow.h" #ifndef QT_NO_OPENGL @@ -55,47 +57,76 @@ using namespace emscripten; QT_BEGIN_NAMESPACE -void browserBeforeUnload() +static void browserBeforeUnload(emscripten::val) { QWasmIntegration::QWasmBrowserExit(); } -EMSCRIPTEN_BINDINGS(my_module) +static void addCanvasElement(emscripten::val canvas) { - function("browserBeforeUnload", &browserBeforeUnload); + QString canvasId = QString::fromStdString(canvas["id"].as<std::string>()); + QWasmIntegration::get()->addScreen(canvasId); } -static QWasmIntegration *globalHtml5Integration; -QWasmIntegration *QWasmIntegration::get() { return globalHtml5Integration; } +static void removeCanvasElement(emscripten::val canvas) +{ + QString canvasId = QString::fromStdString(canvas["id"].as<std::string>()); + QWasmIntegration::get()->removeScreen(canvasId); +} -QWasmIntegration::QWasmIntegration() - : m_fontDb(nullptr), - m_compositor(new QWasmCompositor), - m_screen(new QWasmScreen(m_compositor)), - m_eventDispatcher(nullptr) +static void resizeCanvasElement(emscripten::val canvas) { + QString canvasId = QString::fromStdString(canvas["id"].as<std::string>()); + QWasmIntegration::get()->resizeScreen(canvasId); +} - globalHtml5Integration = this; +EMSCRIPTEN_BINDINGS(qtQWasmIntegraton) +{ + function("qtBrowserBeforeUnload", &browserBeforeUnload); + function("qtAddCanvasElement", &addCanvasElement); + function("qtRemoveCanvasElement", &removeCanvasElement); + function("qtResizeCanvasElement", &resizeCanvasElement); +} - updateQScreenAndCanvasRenderSize(); - QWindowSystemInterface::handleScreenAdded(m_screen); - emscripten_set_resize_callback(0, (void *)this, 1, uiEvent_cb); +QWasmIntegration *QWasmIntegration::s_instance; - m_eventTranslator = new QWasmEventTranslator; +QWasmIntegration::QWasmIntegration() + : m_fontDb(nullptr), + m_desktopServices(nullptr), + m_clipboard(new QWasmClipboard) +{ + s_instance = this; + + // We expect that qtloader.js has populated Module.qtCanvasElements with one or more canvases. + // Also check Module.canvas, which may be set if the emscripen or a custom loader is used. + emscripten::val qtCanvaseElements = val::module_property("qtCanvasElements"); + emscripten::val canvas = val::module_property("canvas"); + + if (!qtCanvaseElements.isUndefined()) { + int screenCount = qtCanvaseElements["length"].as<int>(); + for (int i = 0; i < screenCount; ++i) { + emscripten::val canvas = qtCanvaseElements[i].as<emscripten::val>(); + QString canvasId = QString::fromStdString(canvas["id"].as<std::string>()); + addScreen(canvasId); + } + } else if (!canvas.isUndefined()){ + QString canvasId = QString::fromStdString(canvas["id"].as<std::string>()); + addScreen(canvasId); + } - EM_ASM(// exit app if browser closes - window.onbeforeunload = function () { - Module.browserBeforeUnload(); - }; - ); + emscripten::val::global("window").set("onbeforeunload", val::module_property("qtBrowserBeforeUnload")); } QWasmIntegration::~QWasmIntegration() { - delete m_compositor; - QWindowSystemInterface::handleScreenRemoved(m_screen); delete m_fontDb; - delete m_eventTranslator; + delete m_desktopServices; + + for (auto it = m_screens.constBegin(); it != m_screens.constEnd(); ++it) + QWindowSystemInterface::handleScreenRemoved(*it); + m_screens.clear(); + + s_instance = nullptr; } void QWasmIntegration::QWasmBrowserExit() @@ -109,7 +140,7 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; - case ThreadedOpenGL: return true; + case ThreadedOpenGL: return false; case RasterGLSurface: return false; // to enable this you need to fix qopenglwidget and quickwidget for wasm case MultipleWindows: return true; case WindowManagement: return true; @@ -119,13 +150,15 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const { - return new QWasmWindow(window, m_compositor, m_backingStores.value(window)); + QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor(); + return new QWasmWindow(window, compositor, m_backingStores.value(window)); } QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const { #ifndef QT_NO_OPENGL - QWasmBackingStore *backingStore = new QWasmBackingStore(m_compositor, window); + QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor(); + QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window); m_backingStores.insert(window, backingStore); return backingStore; #else @@ -155,9 +188,21 @@ QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const { + if (hint == ShowIsFullScreen) + return true; + return QPlatformIntegration::styleHint(hint); } +Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const +{ + // Don't maximize dialogs + if (flags & Qt::Dialog & ~Qt::Window) + return Qt::WindowNoState; + + return QPlatformIntegration::defaultWindowState(flags); +} + QStringList QWasmIntegration::themeNames() const { return QStringList() << QLatin1String("webassembly"); @@ -170,50 +215,34 @@ QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const return QPlatformIntegration::createPlatformTheme(name); } -int QWasmIntegration::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData) +QPlatformServices *QWasmIntegration::services() const { - Q_UNUSED(e) - Q_UNUSED(userData) - - if (eventType == EMSCRIPTEN_EVENT_RESIZE) { - // This resize event is called when the HTML window is resized. Depending - // on the page layout the the canvas might also have been resized, so we - // update the Qt screen size (and canvas render size). - updateQScreenAndCanvasRenderSize(); - } - - return 0; + if (m_desktopServices == nullptr) + m_desktopServices = new QWasmServices(); + return m_desktopServices; } -static void set_canvas_size(double width, double height) +QPlatformClipboard* QWasmIntegration::clipboard() const { - EM_ASM_({ - var canvas = Module.canvas; - canvas.width = $0; - canvas.height = $1; - }, width, height); + return m_clipboard; } -void QWasmIntegration::updateQScreenAndCanvasRenderSize() +void QWasmIntegration::addScreen(const QString &canvasId) { - // The HTML canvas has two sizes: the CSS size and the canvas render size. - // The CSS size is determined according to standard CSS rules, while the - // render size is set using the "width" and "height" attributes. The render - // size must be set manually and is not auto-updated on CSS size change. - // Setting the render size to a value larger than the CSS size enables high-dpi - // rendering. - - double css_width; - double css_height; - emscripten_get_element_css_size(0, &css_width, &css_height); - QSizeF cssSize(css_width, css_height); + QWasmScreen *screen = new QWasmScreen(canvasId); + m_clipboard->installEventHandlers(canvasId); + m_screens.insert(canvasId, screen); + QWindowSystemInterface::handleScreenAdded(screen); +} - QWasmScreen *screen = QWasmIntegration::get()->m_screen; - QSizeF canvasSize = cssSize * screen->devicePixelRatio(); +void QWasmIntegration::removeScreen(const QString &canvasId) +{ + QWindowSystemInterface::handleScreenRemoved(m_screens.take(canvasId)); +} - set_canvas_size(canvasSize.width(), canvasSize.height()); - screen->setGeometry(QRect(QPoint(0, 0), cssSize.toSize())); - QWasmIntegration::get()->m_compositor->redrawWindowContent(); +void QWasmIntegration::resizeScreen(const QString &canvasId) +{ + m_screens.value(canvasId)->updateQScreenAndCanvasRenderSize(); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h index ebc3d9d431..11d8d0f7f5 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.h +++ b/src/plugins/platforms/wasm/qwasmintegration.h @@ -49,6 +49,8 @@ class QWasmEventDispatcher; class QWasmScreen; class QWasmCompositor; class QWasmBackingStore; +class QWasmClipboard; +class QWasmServices; class QWasmIntegration : public QObject, public QPlatformIntegration { @@ -66,25 +68,28 @@ public: QPlatformFontDatabase *fontDatabase() const override; QAbstractEventDispatcher *createEventDispatcher() const override; QVariant styleHint(QPlatformIntegration::StyleHint hint) const override; + Qt::WindowState defaultWindowState(Qt::WindowFlags flags) const override; QStringList themeNames() const override; QPlatformTheme *createPlatformTheme(const QString &name) const override; + QPlatformServices *services() const override; + QPlatformClipboard *clipboard() const override; + QWasmClipboard *getWasmClipboard() { return m_clipboard; } - static QWasmIntegration *get(); - QWasmScreen *screen() { return m_screen; } - QWasmCompositor *compositor() { return m_compositor; } - QWasmEventTranslator *eventTranslator() { return m_eventTranslator; } - + static QWasmIntegration *get() { return s_instance; } static void QWasmBrowserExit(); - static void updateQScreenAndCanvasRenderSize(); + + void addScreen(const QString &canvasId); + void removeScreen(const QString &canvasId); + void resizeScreen(const QString &canvasId); private: mutable QWasmFontDatabase *m_fontDb; - QWasmCompositor *m_compositor; - mutable QWasmScreen *m_screen; - mutable QWasmEventTranslator *m_eventTranslator; - mutable QWasmEventDispatcher *m_eventDispatcher; - static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData); + mutable QWasmServices *m_desktopServices; mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores; + + QHash<QString, QWasmScreen *> m_screens; + mutable QWasmClipboard *m_clipboard; + static QWasmIntegration *s_instance; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp index 73af3d1878..ae43e2ebf0 100644 --- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp @@ -28,7 +28,7 @@ ****************************************************************************/ #include "qwasmopenglcontext.h" - +#include "qwasmintegration.h" #include <EGL/egl.h> QT_BEGIN_NAMESPACE @@ -57,7 +57,7 @@ void QWasmOpenGLContext::maybeRecreateEmscriptenContext(QPlatformSurface *surfac emscripten_webgl_destroy_context(m_context); // Create new context - const char *canvasId = 0; // (use default canvas) FIXME: get the actual canvas from the surface. + const QString canvasId = QWasmScreen::get(surface->screen())->canvasId(); m_context = createEmscriptenContext(canvasId, m_requestedFormat); // Register context-lost callback. @@ -73,11 +73,11 @@ void QWasmOpenGLContext::maybeRecreateEmscriptenContext(QPlatformSurface *surfac return true; }; bool capture = true; - emscripten_set_webglcontextlost_callback(canvasId, this, capture, callback); + emscripten_set_webglcontextlost_callback(canvasId.toLocal8Bit().constData(), this, capture, callback); } } -EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const char *canvasId, QSurfaceFormat format) +EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasId, QSurfaceFormat format) { EmscriptenWebGLContextAttributes attributes; emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes @@ -96,7 +96,7 @@ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(cons attributes.depth = format.depthBufferSize() > 0; attributes.stencil = format.stencilBufferSize() > 0; - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(canvasId, &attributes); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(canvasId.toLocal8Bit().constData(), &attributes); return context; } diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h index 9123100479..126b596a7e 100644 --- a/src/plugins/platforms/wasm/qwasmopenglcontext.h +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h @@ -51,7 +51,7 @@ public: private: void maybeRecreateEmscriptenContext(QPlatformSurface *surface); - static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const char *canvasId, QSurfaceFormat format); + static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const QString &canvasId, QSurfaceFormat format); bool m_contextLost = false; QSurfaceFormat m_requestedFormat; diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp index 93e9906ffc..a26cafa900 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.cpp +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -29,7 +29,10 @@ #include "qwasmscreen.h" #include "qwasmwindow.h" +#include "qwasmeventtranslator.h" #include "qwasmcompositor.h" +#include <emscripten/bind.h> +#include <emscripten/val.h> #include <QtEglSupport/private/qeglconvenience_p.h> #ifndef QT_NO_OPENGL @@ -43,12 +46,13 @@ QT_BEGIN_NAMESPACE -QWasmScreen::QWasmScreen(QWasmCompositor *compositor) - : m_compositor(compositor) - , m_depth(32) - , m_format(QImage::Format_RGB32) +QWasmScreen::QWasmScreen(const QString &canvasId) + : m_canvasId(canvasId) + { - m_compositor->setScreen(this); + m_compositor = new QWasmCompositor(this); + m_eventTranslator = new QWasmEventTranslator(this); + updateQScreenAndCanvasRenderSize(); } QWasmScreen::~QWasmScreen() @@ -56,6 +60,31 @@ QWasmScreen::~QWasmScreen() } +QWasmScreen *QWasmScreen::get(QPlatformScreen *screen) +{ + return static_cast<QWasmScreen *>(screen); +} + +QWasmScreen *QWasmScreen::get(QScreen *screen) +{ + return get(screen->handle()); +} + +QWasmCompositor *QWasmScreen::compositor() +{ + return m_compositor; +} + +QWasmEventTranslator *QWasmScreen::eventTranslator() +{ + return m_eventTranslator; +} + +QString QWasmScreen::canvasId() const +{ + return m_canvasId; +} + QRect QWasmScreen::geometry() const { return m_geometry; @@ -77,12 +106,15 @@ qreal QWasmScreen::devicePixelRatio() const // HTML window dpr if the OpenGL driver/GPU allocates a less than // full resolution surface. Use emscripten_webgl_get_drawing_buffer_size() // and compute the dpr instead. - double htmlWindowDpr = EM_ASM_DOUBLE({ - return window.devicePixelRatio; - }); + double htmlWindowDpr = emscripten::val::global("window")["devicePixelRatio"].as<double>(); return qreal(htmlWindowDpr); } +QString QWasmScreen::name() const +{ + return m_canvasId; +} + QPlatformCursor *QWasmScreen::cursor() const { return const_cast<QWasmCursor *>(&m_cursor); @@ -115,4 +147,31 @@ void QWasmScreen::setGeometry(const QRect &rect) resizeMaximizedWindows(); } +void QWasmScreen::updateQScreenAndCanvasRenderSize() +{ + // The HTML canvas has two sizes: the CSS size and the canvas render size. + // The CSS size is determined according to standard CSS rules, while the + // render size is set using the "width" and "height" attributes. The render + // size must be set manually and is not auto-updated on CSS size change. + // Setting the render size to a value larger than the CSS size enables high-dpi + // rendering. + + QByteArray canvasId = m_canvasId.toUtf8(); + double css_width; + double css_height; + emscripten_get_element_css_size(canvasId.constData(), &css_width, &css_height); + QSizeF cssSize(css_width, css_height); + + QSizeF canvasSize = cssSize * devicePixelRatio(); + emscripten::val canvas = emscripten::val::global(canvasId.constData()); + canvas.set("width", canvasSize.width()); + canvas.set("height", canvasSize.height()); + + emscripten::val rect = canvas.call<emscripten::val>("getBoundingClientRect"); + QPoint position(rect["left"].as<int>(), rect["top"].as<int>()); + + setGeometry(QRect(position, cssSize.toSize())); + m_compositor->redrawWindowContent(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h index 3891db77bb..82d2a83edb 100644 --- a/src/plugins/platforms/wasm/qwasmscreen.h +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -43,20 +43,28 @@ class QPlatformOpenGLContext; class QWasmWindow; class QWasmBackingStore; class QWasmCompositor; +class QWasmEventTranslator; class QOpenGLContext; class QWasmScreen : public QObject, public QPlatformScreen { Q_OBJECT public: - - QWasmScreen(QWasmCompositor *compositor); + QWasmScreen(const QString &canvasId); ~QWasmScreen(); + static QWasmScreen *get(QPlatformScreen *screen); + static QWasmScreen *get(QScreen *screen); + QString canvasId() const; + + QWasmCompositor *compositor(); + QWasmEventTranslator *eventTranslator(); + QRect geometry() const override; int depth() const override; QImage::Format format() const override; qreal devicePixelRatio() const override; + QString name() const override; QPlatformCursor *cursor() const override; void resizeMaximizedWindows(); @@ -64,17 +72,18 @@ public: QWindow *topLevelAt(const QPoint &p) const override; void invalidateSize(); + void updateQScreenAndCanvasRenderSize(); public slots: void setGeometry(const QRect &rect); -protected: private: - QWasmCompositor *m_compositor; - + QString m_canvasId; + QWasmCompositor *m_compositor = nullptr; + QWasmEventTranslator *m_eventTranslator = nullptr; QRect m_geometry = QRect(0, 0, 100, 100); - int m_depth; - QImage::Format m_format; + int m_depth = 32; + QImage::Format m_format = QImage::Format_RGB32; QWasmCursor m_cursor; }; diff --git a/src/plugins/platforms/wasm/qwasmservices.cpp b/src/plugins/platforms/wasm/qwasmservices.cpp new file mode 100644 index 0000000000..9328b8c065 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmservices.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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 "qwasmservices.h" +#include <QtCore/QUrl> +#include <QtCore/QDebug> + +#include <emscripten/val.h> + +QT_BEGIN_NAMESPACE + +bool QWasmServices::openUrl(const QUrl &url) +{ + QByteArray utf8Url = url.toString().toUtf8(); + emscripten::val::global("window").call<void>("open", emscripten::val(utf8Url.constData()), emscripten::val("_blank")); + return true; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmservices.h b/src/plugins/platforms/wasm/qwasmservices.h new file mode 100644 index 0000000000..3b37f21f82 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmservices.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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 QWASMDESKTOPSERVICES_H +#define QWASMDESKTOPSERVICES_H + +#include <qpa/qplatformservices.h> + +QT_BEGIN_NAMESPACE + +class QWasmServices : public QPlatformServices +{ +public: + bool openUrl(const QUrl &url) override; +}; + +QT_END_NAMESPACE + +#endif // QWASMDESKTOPSERVICES_H diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 25a0190053..39797cb09d 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -198,8 +198,10 @@ void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global, if (!hasTitleBar() || button != Qt::LeftButton) return; - if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) + if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) { window()->close(); + return; + } if (maxButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarMaxButton) { window()->setWindowState(Qt::WindowMaximized); diff --git a/src/plugins/platforms/wasm/wasm.pro b/src/plugins/platforms/wasm/wasm.pro index eaaba53aa2..9b98445c68 100644 --- a/src/plugins/platforms/wasm/wasm.pro +++ b/src/plugins/platforms/wasm/wasm.pro @@ -18,7 +18,9 @@ SOURCES = \ qwasmcompositor.cpp \ qwasmcursor.cpp \ qwasmopenglcontext.cpp \ - qwasmtheme.cpp + qwasmtheme.cpp \ + qwasmclipboard.cpp \ + qwasmservices.cpp HEADERS = \ qwasmintegration.h \ @@ -31,7 +33,9 @@ HEADERS = \ qwasmstylepixmaps_p.h \ qwasmcursor.h \ qwasmopenglcontext.h \ - qwasmtheme.h + qwasmtheme.h \ + qwasmclipboard.h \ + qwasmservices.h wasmfonts.files = \ ../../../3rdparty/wasm/Vera.ttf \ diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html index 67bfcdfbdc..f7c856d63d 100644 --- a/src/plugins/platforms/wasm/wasm_shell.html +++ b/src/plugins/platforms/wasm/wasm_shell.html @@ -7,27 +7,32 @@ <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; caret-color: transparent; cursor:default } </style> </head> <body onload="init()"> - <figure style="overflow:visible;" id="spinner"> + <figure style="overflow:visible;" id="qtspinner"> <center style="margin-top:1.5em; line-height:150%"> <img src="qtlogo.svg"; width=320; height=200; style="display:block"> </img> <strong>Qt for WebAssembly: APPNAME</strong> - <div id="status"></div> + <div id="qtstatus"></div> <noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript> </center> </figure> - <canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas> + <canvas id="qtcanvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas> <script type='text/javascript'> function init() { - var spinner = document.getElementById('spinner'); - var canvas = document.getElementById('canvas'); - var status = document.getElementById('status') + var spinner = document.querySelector('#qtspinner'); + var canvas = document.querySelector('#qtcanvas'); + var status = document.querySelector('#qtstatus') var qtLoader = QtLoader({ + canvasElements : [canvas], showLoader: function(loaderStatus) { spinner.style.display = 'block'; canvas.style.display = 'none'; @@ -50,7 +55,6 @@ showCanvas: function() { spinner.style.display = 'none'; canvas.style.display = 'block'; - return canvas; }, }); qtLoader.loadEmscriptenModule("APPNAME"); |