summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm/qwasmscreen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmscreen.cpp')
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp309
1 files changed, 165 insertions, 144 deletions
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index 4930868944..ddf8140c48 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -2,123 +2,124 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmscreen.h"
-#include "qwasmwindow.h"
-#include "qwasmeventtranslator.h"
+
#include "qwasmcompositor.h"
+#include "qwasmcssstyle.h"
#include "qwasmintegration.h"
-#include "qwasmstring.h"
+#include "qwasmkeytranslator.h"
+#include "qwasmwindow.h"
#include <emscripten/bind.h>
#include <emscripten/val.h>
-#include <QtGui/private/qeglconvenience_p.h>
-#ifndef QT_NO_OPENGL
-# include <QtGui/private/qeglplatformcontext_p.h>
-#endif
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qguiapplication.h>
#include <private/qhighdpiscaling_p.h>
+#include <tuple>
+
QT_BEGIN_NAMESPACE
using namespace emscripten;
-const char * QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = "data-qtCanvasResizeObserverCallbackContext";
+const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName =
+ "data-qtCanvasResizeObserverCallbackContext";
QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
- : m_container(containerOrCanvas)
- , m_canvas(emscripten::val::undefined())
- , m_compositor(new QWasmCompositor(this))
- , m_eventTranslator(new QWasmEventTranslator())
+ : m_container(containerOrCanvas),
+ m_intermediateContainer(emscripten::val::undefined()),
+ m_shadowContainer(emscripten::val::undefined()),
+ m_compositor(new QWasmCompositor(this)),
+ m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
{
- // Each screen is backed by a html canvas element. Use either
- // a user-supplied canvas or create one as a child of the user-
- // supplied root element.
- std::string tagName = containerOrCanvas["tagName"].as<std::string>();
- if (tagName == "CANVAS" || tagName == "canvas") {
- m_canvas = containerOrCanvas;
- } else {
- // Create the canvas (for the correct document) as a child of the container
- m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas"));
- containerOrCanvas.call<void>("appendChild", m_canvas);
- std::string screenId = std::string("qtcanvas_") + std::to_string(uint32_t(this));
- m_canvas.set("id", screenId);
-
- // Make the canvas occupy 100% of parent
- emscripten::val style = m_canvas["style"];
- style.set("width", std::string("100%"));
- style.set("height", std::string("100%"));
+ auto document = m_container["ownerDocument"];
+ // Each screen is represented by a div container. All of the windows exist therein as
+ // its children. Qt versions < 6.5 used to represent screens as canvas. Support that by
+ // transforming the canvas into a div.
+ if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas") {
+ qWarning() << "Support for canvas elements as an element backing screen is deprecated. The "
+ "canvas provided for the screen will be transformed into a div.";
+ auto container = document.call<emscripten::val>("createElement", emscripten::val("div"));
+ m_container["parentNode"].call<void>("replaceChild", container, m_container);
+ m_container = container;
}
- emscripten::val style = m_canvas["style"];
+ // Create an intermediate container which we can remove during cleanup in ~QWasmScreen().
+ // This is required due to the attachShadow() call below; there is no corresponding
+ // "detachShadow()" API to return the container to its previous state.
+ m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
+ m_intermediateContainer.set("id", std::string("qt-shadow-container"));
+ emscripten::val intermediateContainerStyle = m_intermediateContainer["style"];
+ intermediateContainerStyle.set("width", std::string("100%"));
+ intermediateContainerStyle.set("height", std::string("100%"));
+ m_container.call<void>("appendChild", m_intermediateContainer);
+
+ auto shadowOptions = emscripten::val::object();
+ shadowOptions.set("mode", "open");
+ auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions);
+
+ m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
- // Configure container and canvas for accessibility support: set "position: relative"
- // so that a11y child elements can be positioned with "position: absolute", and hide
- // the canvas from screen readers.
- m_container["style"].set("position", std::string("relative"));
- m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); // FIXME make the canvas non-focusable, as required by the aria-hidden role
- style.set("z-index", std::string("1")); // a11y elements are at 0
+ shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer));
- style.set("border", std::string("0px none"));
- style.set("background-color", std::string("white"));
+ shadow.call<void>("appendChild", m_shadowContainer);
- // Set contenteditable so that the canvas gets clipboard events,
- // then hide the resulting focus frame, and reset the cursor.
- m_canvas.set("contentEditable", std::string("true"));
- // set inputmode to none to stop mobile keyboard opening
- // when user clicks anywhere on the canvas.
- m_canvas.set("inputmode", std::string("none"));
- style.set("outline", std::string("0px solid transparent"));
- style.set("caret-color", std::string("transparent"));
- style.set("cursor", std::string("default"));
+ m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this)));
+
+ m_shadowContainer["classList"].call<void>("add", std::string("qt-screen"));
// Disable the default context menu; Qt applications typically
// provide custom right-click behavior.
- m_onContextMenu = std::make_unique<qstdweb::EventCallback>(m_canvas, "contextmenu", [](emscripten::val event){
- event.call<void>("preventDefault");
- });
-
- // Create "specialHTMLTargets" mapping for the canvas. Normally, Emscripten
- // uses the html element id when targeting elements, for example when registering
- // event callbacks. However, this approach is limited to supporting single-document
- // apps/ages only, since Emscripten uses the main document to look up the element.
- // As a workaround for this, Emscripten supports registering custom mappings in the
- // "specialHTMLTargets" object. Add a mapping for the canvas for this screen.
- //
- // This functionality is gated on "specialHTMLTargets" being available as a module
- // property. One way to ensure this is the case is to add it to EXPORTED_RUNTIME_METHODS.
- // Qt does not currently do this by default since if added it _must_ be used in order
- // to avoid an undefined reference error at startup, and there are cases when Qt won't use
- // it, for example if QGuiApplication is not usded.
- if (hasSpecialHtmlTargets())
- emscripten::val::module_property("specialHTMLTargets").set(canvasSpecialHtmlTargetId(), m_canvas);
-
- // Install event handlers on the container/canvas. This must be
- // done after the canvas has been created above.
- m_compositor->initEventHandlers();
+ m_onContextMenu = std::make_unique<qstdweb::EventCallback>(
+ m_shadowContainer, "contextmenu",
+ [](emscripten::val event) { event.call<void>("preventDefault"); });
+ // Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
+ // on its id only under some conditions, like the target being embedded in a shadow DOM or a
+ // subframe.
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(eventTargetId().toStdString(), m_shadowContainer);
+
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(outerScreenId().toStdString(), m_container);
updateQScreenAndCanvasRenderSize();
- m_canvas.call<void>("focus");
+ m_shadowContainer.call<void>("focus");
+
+ m_touchDevice = std::make_unique<QPointingDevice>(
+ "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
+ QPointingDevice::PointerType::Finger,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
+ | QPointingDevice::Capability::NormalizedPosition,
+ 10, 0);
+ m_tabletDevice = std::make_unique<QPointingDevice>(
+ "stylus", 2, QInputDevice::DeviceType::Stylus,
+ QPointingDevice::PointerType::Pen,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure
+ | QPointingDevice::Capability::NormalizedPosition
+ | QInputDevice::Capability::MouseEmulation
+ | QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation
+ | QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt
+ | QInputDevice::Capability::TangentialPressure,
+ 0, 0);
+
+ QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
}
QWasmScreen::~QWasmScreen()
{
- // Delete the compositor before removing the screen from specialHTMLTargets,
- // since its destructor needs to look up the target when deregistering
- // event handlers.
- m_compositor = nullptr;
+ m_intermediateContainer.call<void>("remove");
- if (hasSpecialHtmlTargets())
- emscripten::val::module_property("specialHTMLTargets")
- .set(canvasSpecialHtmlTargetId(), emscripten::val::undefined());
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(eventTargetId().toStdString(), emscripten::val::undefined());
- m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0)));
+ m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(0)));
}
void QWasmScreen::deleteScreen()
{
- m_compositor->destroy();
+ // Deletes |this|!
QWindowSystemInterface::handleScreenRemoved(this);
}
@@ -139,58 +140,21 @@ QWasmCompositor *QWasmScreen::compositor()
return m_compositor.get();
}
-QWasmEventTranslator *QWasmScreen::eventTranslator()
-{
- return m_eventTranslator.get();
-}
-
-emscripten::val QWasmScreen::container() const
+emscripten::val QWasmScreen::element() const
{
- return m_container;
+ return m_shadowContainer;
}
-emscripten::val QWasmScreen::canvas() const
-{
- return m_canvas;
-}
-
-// Returns the html element id for the screen's canvas.
-QString QWasmScreen::canvasId() const
-{
- return QWasmString::toQString(m_canvas["id"]);
-}
-
-// Returns the canvas _target_ id, for use with Emscripten's event registration
-// functions. This either based on the id registered in specialHtmlTargets, or
-// on the canvas id.
-QString QWasmScreen::canvasTargetId() const
-{
- if (hasSpecialHtmlTargets())
- return QString::fromStdString(canvasSpecialHtmlTargetId());
- else
- return QStringLiteral("#") + canvasId();
-}
-
-std::string QWasmScreen::canvasSpecialHtmlTargetId() const
+QString QWasmScreen::eventTargetId() const
{
// Return a globally unique id for the canvas. We can choose any string,
// as long as it starts with a "!".
- return std::string("!qtcanvas_") + std::to_string(uint32_t(this));
+ return QString("!qtcanvas_%1").arg(uintptr_t(this));
}
-bool QWasmScreen::hasSpecialHtmlTargets() const
+QString QWasmScreen::outerScreenId() const
{
- static bool gotIt = []{
- // Enable use of specialHTMLTargets, if available
- emscripten::val htmlTargets = emscripten::val::module_property("specialHTMLTargets");
- if (htmlTargets.isUndefined())
- return false;
-
- // Check that the object has the expected type - it can also be
- // defined as an abort() function which prints an error on usage.
- return htmlTargets["constructor"]["name"].as<std::string>() == std::string("Array");
- }();
- return gotIt;
+ return QString("!outerscreen_%1").arg(uintptr_t(this));
}
QRect QWasmScreen::geometry() const
@@ -242,7 +206,7 @@ qreal QWasmScreen::devicePixelRatio() const
QString QWasmScreen::name() const
{
- return canvasId();
+ return QString::fromEcmaString(m_shadowContainer["id"]);
}
QPlatformCursor *QWasmScreen::cursor() const
@@ -259,12 +223,30 @@ void QWasmScreen::resizeMaximizedWindows()
QWindow *QWasmScreen::topWindow() const
{
- return m_compositor->keyWindow();
+ return activeChild() ? activeChild()->window() : nullptr;
}
QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
{
- return m_compositor->windowAt(p);
+ const auto found =
+ std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
+ const QRect geometry = window->windowFrameGeometry();
+
+ return window->isVisible() && geometry.contains(p);
+ });
+ return found != childStack().end() ? (*found)->window() : nullptr;
+}
+
+QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
+{
+ return geometry().topLeft() + p;
+}
+
+QPointF QWasmScreen::clipPoint(const QPointF &p) const
+{
+ const auto geometryF = screen()->geometry().toRectF();
+ return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()),
+ qBound(geometryF.top(), p.y(), geometryF.bottom()));
}
void QWasmScreen::invalidateSize()
@@ -275,10 +257,23 @@ void QWasmScreen::invalidateSize()
void QWasmScreen::setGeometry(const QRect &rect)
{
m_geometry = rect;
- QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry());
+ QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(),
+ availableGeometry());
resizeMaximizedWindows();
}
+void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ Q_UNUSED(parent);
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && childStack().size() == 1) {
+ child->window()->setFlag(Qt::WindowStaysOnBottomHint);
+ }
+ QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child);
+ m_compositor->onWindowTreeChanged(changeType, child);
+}
+
void QWasmScreen::updateQScreenAndCanvasRenderSize()
{
// The HTML canvas has two sizes: the CSS size and the canvas render size.
@@ -287,28 +282,26 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize()
// 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 canvasSelector = canvasTargetId().toUtf8();
double css_width;
double css_height;
- emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height);
+ emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height);
QSizeF cssSize(css_width, css_height);
QSizeF canvasSize = cssSize * devicePixelRatio();
- m_canvas.set("width", canvasSize.width());
- m_canvas.set("height", canvasSize.height());
+ m_shadowContainer.set("width", canvasSize.width());
+ m_shadowContainer.set("height", canvasSize.height());
// Returns the html elements document/body position
auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint {
- emscripten::val bodyRect = element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
+ emscripten::val bodyRect =
+ element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect");
- return QPoint (canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
- canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
+ return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
+ canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
};
- setGeometry(QRect(getElementBodyPosition(m_canvas), cssSize.toSize()));
- m_compositor->requestUpdateAllWindows();
+ setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
}
void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val)
@@ -317,20 +310,23 @@ void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscript
if (count == 0)
return;
emscripten::val entry = entries[0];
- QWasmScreen *screen =
- reinterpret_cast<QWasmScreen *>(entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
+ QWasmScreen *screen = reinterpret_cast<QWasmScreen *>(
+ entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
if (!screen) {
qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer";
return;
}
// We could access contentBoxSize|contentRect|devicePixelContentBoxSize on the entry here, but
- // these are not universally supported across all browsers. Get the sizes from the canvas instead.
+ // these are not universally supported across all browsers. Get the sizes from the canvas
+ // instead.
screen->updateQScreenAndCanvasRenderSize();
}
-EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) {
- emscripten::function("qtCanvasResizeObserverCallback", &QWasmScreen::canvasResizeObserverCallback);
+EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback)
+{
+ emscripten::function("qtCanvasResizeObserverCallback",
+ &QWasmScreen::canvasResizeObserverCallback);
}
void QWasmScreen::installCanvasResizeObserver()
@@ -338,15 +334,40 @@ void QWasmScreen::installCanvasResizeObserver()
emscripten::val ResizeObserver = emscripten::val::global("ResizeObserver");
if (ResizeObserver == emscripten::val::undefined())
return; // ResizeObserver API is not available
- emscripten::val resizeObserver = ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback"));
+ emscripten::val resizeObserver =
+ ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback"));
if (resizeObserver == emscripten::val::undefined())
return; // Something went horribly wrong
// We need to get back to this instance from the (static) resize callback;
// set a "data-" property on the canvas element.
- m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(this)));
+ m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(this)));
- resizeObserver.call<void>("observe", m_canvas);
+ resizeObserver.call<void>("observe", m_shadowContainer);
+}
+
+emscripten::val QWasmScreen::containerElement()
+{
+ return m_shadowContainer;
+}
+
+QWasmWindowTreeNode *QWasmScreen::parentNode()
+{
+ return nullptr;
+}
+
+QList<QWasmWindow *> QWasmScreen::allWindows()
+{
+ QList<QWasmWindow *> windows;
+ for (auto *child : childStack()) {
+ QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively);
+ std::transform(
+ list.begin(), list.end(), std::back_inserter(windows),
+ [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); });
+ windows.push_back(child);
+ }
+ return windows;
}
QT_END_NAMESPACE