Revision control

Copy as Markdown

// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qwasmintegration.h"
#include "qwasminputcontext.h"
#include "qwasmeventdispatcher.h"
#include "qwasmcompositor.h"
#include "qwasmopenglcontext.h"
#include "qwasmtheme.h"
#if QT_CONFIG(clipboard)
#include "qwasmclipboard.h"
#endif
#include "qwasmaccessibility.h"
#include "qwasmservices.h"
#include "qwasmoffscreensurface.h"
#include "qwasmplatform.h"
#include "qwasmwindow.h"
#include "qwasmbackingstore.h"
#include "qwasmfontdatabase.h"
#if QT_CONFIG(draganddrop)
#include "qwasmdrag.h"
#endif
#include <qpa/qplatformwindow.h>
#include <QtGui/qscreen.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <qpa/qplatforminputcontextfactory_p.h>
#include <qpa/qwindowsysteminterface_p.h>
#include "private/qwasmsuspendresumecontrol_p.h"
#include <emscripten/bind.h>
#include <emscripten/val.h>
// this is where EGL headers are pulled in, make sure it is last
#include "qwasmscreen.h"
#if QT_CONFIG(draganddrop)
#include <private/qsimpledrag_p.h>
#endif
QT_BEGIN_NAMESPACE
using namespace emscripten;
using namespace Qt::StringLiterals;
static void setContainerElements(emscripten::val elementArray)
{
QWasmIntegration::get()->setContainerElements(elementArray);
}
static void addContainerElement(emscripten::val element)
{
QWasmIntegration::get()->addContainerElement(element);
}
static void removeContainerElement(emscripten::val element)
{
QWasmIntegration::get()->removeContainerElement(element);
}
static void resizeContainerElement(emscripten::val element)
{
QWasmIntegration::get()->resizeScreen(element);
}
static void qtUpdateDpi()
{
QWasmIntegration::get()->updateDpi();
}
static void resizeAllScreens(emscripten::val event)
{
Q_UNUSED(event);
QWasmIntegration::get()->resizeAllScreens();
}
static void loadLocalFontFamilies(emscripten::val event)
{
QWasmIntegration::get()->loadLocalFontFamilies(event);
}
EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)
{
function("qtSetContainerElements", &setContainerElements);
function("qtAddContainerElement", &addContainerElement);
function("qtRemoveContainerElement", &removeContainerElement);
function("qtResizeContainerElement", &resizeContainerElement);
function("qtUpdateDpi", &qtUpdateDpi);
function("qtResizeAllScreens", &resizeAllScreens);
function("qtLoadLocalFontFamilies", &loadLocalFontFamilies);
}
QWasmIntegration *QWasmIntegration::s_instance;
QWasmIntegration::QWasmIntegration()
: m_fontDb(nullptr)
, m_desktopServices(nullptr)
#if QT_CONFIG(clipboard)
, m_clipboard(new QWasmClipboard)
#endif
#if QT_CONFIG(accessibility)
, m_accessibility(new QWasmAccessibility)
#endif
{
s_instance = this;
touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
// Create screens for container elements. Each container element will ultimately become a
// div element. Qt historically supported supplying canvas for screen elements - these elements
// will be transformed into divs and warnings about deprecation will be printed. See
// QWasmScreen ctor.
emscripten::val filtered = emscripten::val::array();
emscripten::val qtContainerElements = val::module_property("qtContainerElements");
if (qtContainerElements.isArray()) {
for (int i = 0; i < qtContainerElements["length"].as<int>(); ++i) {
emscripten::val element = qtContainerElements[i].as<emscripten::val>();
if (element.isNull() || element.isUndefined())
qWarning() << "Skipping null or undefined element in qtContainerElements";
else
filtered.call<void>("push", element);
}
} else {
// No screens, which may or may not be intended
qWarning() << "The qtContainerElements module property was not set or is invalid. "
"Proceeding with no screens.";
}
setContainerElements(filtered);
// install browser window resize handler
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE,
[](int, const EmscriptenUiEvent *, void *) -> EM_BOOL {
// This resize event is called when the HTML window is
// resized. Depending on the page layout the elements might
// also have been resized, so we update the Qt screen sizes
// (and canvas render sizes).
if (QWasmIntegration *integration = QWasmIntegration::get())
integration->resizeAllScreens();
return EM_FALSE;
});
// install visualViewport resize handler which picks up size and scale change on mobile.
emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
if (!visualViewport.isUndefined()) {
visualViewport.call<void>("addEventListener", val("resize"),
val::module_property("qtResizeAllScreens"));
}
#if QT_CONFIG(draganddrop)
m_drag = std::make_unique<QWasmDrag>();
#endif
}
QWasmIntegration::~QWasmIntegration()
{
// Remove event listener
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, nullptr);
emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
if (!visualViewport.isUndefined()) {
visualViewport.call<void>("removeEventListener", val("resize"),
val::module_property("qtResizeAllScreens"));
}
delete m_fontDb;
delete m_desktopServices;
#if QT_CONFIG(accessibility)
delete m_accessibility;
#endif
for (const auto &elementAndScreen : m_screens)
elementAndScreen.wasmScreen->deleteScreen();
m_screens.clear();
delete m_clipboard;
s_instance = nullptr;
}
bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
switch (cap) {
case ThreadedPixmaps: return true;
case OpenGL: return true;
case ThreadedOpenGL: return false;
case MultipleWindows: return true;
case WindowManagement: return true;
case ForeignWindows: return true;
case ChildWindows: return true;
case OpenGLOnRasterSurface: return true;
case OffscreenSurface: return true;
case ApplicationState: return true;
default: return QPlatformIntegration::hasCapability(cap);
}
}
QWasmWindow *QWasmIntegration::createWindow(QWindow *window, WId nativeHandle) const
{
auto *wasmScreen = QWasmScreen::get(window->screen());
QWasmCompositor *compositor = wasmScreen->compositor();
return new QWasmWindow(window, compositor,
m_backingStores.value(window), nativeHandle);
}
QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
{
return createWindow(window, 0);
}
QPlatformWindow *QWasmIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
{
return createWindow(window, nativeHandle);
}
QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
{
QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window);
m_backingStores.insert(window, backingStore);
return backingStore;
}
void QWasmIntegration::removeBackingStore(QWindow* window)
{
m_backingStores.remove(window);
}
#ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
return new QWasmOpenGLContext(context);
}
#endif
void QWasmIntegration::initialize()
{
auto icStrs = QPlatformInputContextFactory::requested();
if (!icStrs.isEmpty()) {
m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
m_wasmInputContext = nullptr;
} else {
m_inputContext.reset(new QWasmInputContext());
m_wasmInputContext = static_cast<QWasmInputContext *>(m_inputContext.data());
}
}
QPlatformInputContext *QWasmIntegration::inputContext() const
{
return m_inputContext.data();
}
QPlatformOffscreenSurface *QWasmIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
return new QWasmOffscreenSurface(surface);
}
QPlatformFontDatabase *QWasmIntegration::fontDatabase() const
{
if (m_fontDb == nullptr)
m_fontDb = new QWasmFontDatabase;
return m_fontDb;
}
QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const
{
return new QWasmEventDispatcher();
}
QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
{
switch (hint) {
case ShowIsFullScreen:
return true;
case UnderlineShortcut:
return platform() != Platform::MacOS;
default:
return QPlatformIntegration::styleHint(hint);
}
}
Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const
{
// Don't maximize dialogs or popups
if (flags.testFlag(Qt::Dialog) || flags.testFlag(Qt::Popup))
return Qt::WindowNoState;
return QPlatformIntegration::defaultWindowState(flags);
}
QStringList QWasmIntegration::themeNames() const
{
return QStringList() << "webassembly"_L1;
}
QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const
{
if (name == "webassembly"_L1)
return new QWasmTheme;
return QPlatformIntegration::createPlatformTheme(name);
}
QPlatformServices *QWasmIntegration::services() const
{
if (m_desktopServices == nullptr)
m_desktopServices = new QWasmServices();
return m_desktopServices;
}
#if QT_CONFIG(clipboard)
QPlatformClipboard* QWasmIntegration::clipboard() const
{
return m_clipboard;
}
#endif
#ifndef QT_NO_ACCESSIBILITY
QPlatformAccessibility *QWasmIntegration::accessibility() const
{
return m_accessibility;
}
#endif
void QWasmIntegration::setContainerElements(emscripten::val elementArray)
{
const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
QList<ScreenMapping> newScreens;
QList<QWasmScreen *> screensToDelete;
std::transform(m_screens.begin(), m_screens.end(), std::back_inserter(screensToDelete),
[](const ScreenMapping &mapping) { return mapping.wasmScreen; });
for (int i = 0; i < elementArray["length"].as<int>(); ++i) {
const auto element = elementArray[i];
const auto it = std::find_if(
m_screens.begin(), m_screens.end(),
[&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
QWasmScreen *screen;
if (it != m_screens.end()) {
screen = it->wasmScreen;
screensToDelete.erase(std::remove_if(screensToDelete.begin(), screensToDelete.end(),
[screen](const QWasmScreen *removedScreen) {
return removedScreen == screen;
}),
screensToDelete.end());
} else {
screen = new QWasmScreen(element);
QWindowSystemInterface::handleScreenAdded(screen);
}
newScreens.push_back({element, screen});
}
std::for_each(screensToDelete.begin(), screensToDelete.end(),
[](QWasmScreen *removed) { removed->deleteScreen(); });
m_screens = newScreens;
auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
}
void QWasmIntegration::addContainerElement(emscripten::val element)
{
Q_ASSERT_X(m_screens.end()
== std::find_if(m_screens.begin(), m_screens.end(),
[&element](const ScreenMapping &screen) {
return screen.emscriptenVal == element;
}),
Q_FUNC_INFO, "Double-add of an element");
QWasmScreen *screen = new QWasmScreen(element);
QWindowSystemInterface::handleScreenAdded(screen);
m_screens.push_back({element, screen});
}
void QWasmIntegration::removeContainerElement(emscripten::val element)
{
const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
const auto it =
std::find_if(m_screens.begin(), m_screens.end(),
[&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
if (it == m_screens.end()) {
qWarning() << "Attempt to remove a nonexistent screen.";
return;
}
QWasmScreen *removedScreen = it->wasmScreen;
removedScreen->deleteScreen();
m_screens.erase(std::remove_if(m_screens.begin(), m_screens.end(),
[removedScreen](const ScreenMapping &mapping) {
return removedScreen == mapping.wasmScreen;
}),
m_screens.end());
auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
}
void QWasmIntegration::resizeScreen(const emscripten::val &element)
{
auto it = std::find_if(m_screens.begin(), m_screens.end(),
[&] (const ScreenMapping &candidate) { return candidate.emscriptenVal.equals(element); });
if (it == m_screens.end()) {
qWarning() << "Attempting to resize non-existing screen for element"
<< QString::fromEcmaString(element["id"]);
return;
}
it->wasmScreen->updateQScreenSize();
}
void QWasmIntegration::updateDpi()
{
emscripten::val dpi = emscripten::val::module_property("qtFontDpi");
if (dpi.isUndefined())
return;
qreal dpiValue = dpi.as<qreal>();
for (const auto &elementAndScreen : m_screens)
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.wasmScreen->screen(), dpiValue, dpiValue);
}
void QWasmIntegration::resizeAllScreens()
{
for (const auto &elementAndScreen : m_screens)
elementAndScreen.wasmScreen->updateQScreenSize();
}
void QWasmIntegration::loadLocalFontFamilies(emscripten::val families)
{
m_fontDb->populateLocalFontFamilies(families);
}
quint64 QWasmIntegration::getTimestamp()
{
return emscripten_performance_now();
}
#if QT_CONFIG(draganddrop)
QPlatformDrag *QWasmIntegration::drag() const
{
return m_drag.get();
}
#endif // QT_CONFIG(draganddrop)
QT_END_NAMESPACE