summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/wasm')
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt108
-rw-r--r--src/plugins/platforms/wasm/main.cpp34
-rw-r--r--src/plugins/platforms/wasm/qtloader.js806
-rw-r--r--src/plugins/platforms/wasm/qtlogo.svg40
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp789
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.h92
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.cpp129
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.h41
-rw-r--r--src/plugins/platforms/wasm/qwasmbase64iconstore.cpp40
-rw-r--r--src/plugins/platforms/wasm/qwasmbase64iconstore.h37
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp359
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.h60
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp778
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.h161
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.cpp248
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.h18
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.cpp162
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.h36
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.cpp303
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.h63
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.cpp291
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.h47
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.cpp338
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.h271
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.cpp216
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.h58
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.cpp971
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.h99
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.cpp395
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.h56
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp161
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h48
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp354
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.h74
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.cpp295
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.h34
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.cpp56
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.h47
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.cpp189
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.h58
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.cpp32
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.h29
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp288
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h77
-rw-r--r--src/plugins/platforms/wasm/qwasmservices.cpp35
-rw-r--r--src/plugins/platforms/wasm/qwasmservices.h30
-rw-r--r--src/plugins/platforms/wasm/qwasmstring.cpp62
-rw-r--r--src/plugins/platforms/wasm/qwasmstring.h45
-rw-r--r--src/plugins/platforms/wasm/qwasmstylepixmaps_p.h183
-rw-r--r--src/plugins/platforms/wasm/qwasmtheme.cpp42
-rw-r--r--src/plugins/platforms/wasm/qwasmtheme.h30
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp774
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h203
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.cpp195
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.h52
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp460
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.h227
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.cpp203
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.h75
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.cpp108
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.h53
-rw-r--r--src/plugins/platforms/wasm/resources/maximize.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/qtlogo.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/restore.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/x.svg1
-rw-r--r--src/plugins/platforms/wasm/wasm.pro78
-rw-r--r--src/plugins/platforms/wasm/wasm_shell.html84
67 files changed, 7192 insertions, 4539 deletions
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt
new file mode 100644
index 0000000000..185b921a4f
--- /dev/null
+++ b/src/plugins/platforms/wasm/CMakeLists.txt
@@ -0,0 +1,108 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## QWasmIntegrationPlugin Plugin:
+#####################################################################
+
+qt_internal_add_plugin(QWasmIntegrationPlugin
+ OUTPUT_NAME qwasm
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm
+ PLUGIN_TYPE platforms
+ STATIC
+ SOURCES
+ main.cpp
+ qwasmaccessibility.cpp qwasmaccessibility.h
+ qwasmbase64iconstore.cpp qwasmbase64iconstore.h
+ qwasmclipboard.cpp qwasmclipboard.h
+ qwasmcompositor.cpp qwasmcompositor.h
+ qwasmcssstyle.cpp qwasmcssstyle.h
+ qwasmcursor.cpp qwasmcursor.h
+ qwasmdom.cpp qwasmdom.h
+ qwasmevent.cpp qwasmevent.h
+ qwasmeventdispatcher.cpp qwasmeventdispatcher.h
+ qwasmfontdatabase.cpp qwasmfontdatabase.h
+ qwasmintegration.cpp qwasmintegration.h
+ qwasmkeytranslator.cpp qwasmkeytranslator.h
+ qwasmoffscreensurface.cpp qwasmoffscreensurface.h
+ qwasmopenglcontext.cpp qwasmopenglcontext.h
+ qwasmplatform.cpp qwasmplatform.h
+ qwasmscreen.cpp qwasmscreen.h
+ qwasmservices.cpp qwasmservices.h
+ qwasmtheme.cpp qwasmtheme.h
+ qwasmwindow.cpp qwasmwindow.h
+ qwasmwindowclientarea.cpp qwasmwindowclientarea.h
+ qwasmwindowtreenode.cpp qwasmwindowtreenode.h
+ qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
+ qwasminputcontext.cpp qwasminputcontext.h
+ qwasmwindowstack.cpp qwasmwindowstack.h
+ qwasmdrag.cpp qwasmdrag.h
+ DEFINES
+ QT_EGL_NO_X11
+ QT_NO_FOREACH
+ LIBRARIES
+ Qt::Core
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+)
+
+# Resources:
+set(wasmfonts_resource_files
+ "${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSans.ttf"
+ "${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSansMono.ttf"
+)
+
+qt_internal_add_resource(QWasmIntegrationPlugin "wasmfonts"
+ PREFIX
+ "/fonts"
+ BASE
+ "${QtBase_SOURCE_DIR}/src/3rdparty/wasm"
+ FILES
+ ${wasmfonts_resource_files}
+)
+
+qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl
+ SOURCES
+ qwasmbackingstore.cpp qwasmbackingstore.h
+ LIBRARIES
+ Qt::OpenGL
+ Qt::OpenGLPrivate
+)
+
+# PLUGIN_EXTENDS = "-"
+
+set(wasm_support_files
+ wasm_shell.html
+ qtloader.js
+ resources/qtlogo.svg
+)
+
+set(wasmwindow_resource_files
+ "resources/maximize.svg"
+ "resources/qtlogo.svg"
+ "resources/restore.svg"
+ "resources/x.svg"
+)
+
+qt_internal_add_resource(QWasmIntegrationPlugin "wasmwindow"
+ PREFIX
+ "/wasm-window"
+ BASE
+ "resources"
+ FILES
+ ${wasmwindow_resource_files}
+)
+
+qt_path_join(destination ${QT_INSTALL_DIR} "plugins/platforms")
+qt_copy_or_install(FILES
+ ${wasm_support_files}
+ DESTINATION "${destination}"
+)
+# Need to copy the support files to the build dir in a top-level prefix build
+# So _qt_internal_wasm_add_target_helpers finds them.
+if(QT_WILL_INSTALL)
+ foreach(wasm_support_file ${wasm_support_files})
+ file(COPY "${wasm_support_file}" DESTINATION "${QT_BUILD_DIR}/plugins/platforms")
+ endforeach()
+endif()
diff --git a/src/plugins/platforms/wasm/main.cpp b/src/plugins/platforms/wasm/main.cpp
index a4d23b4e0d..f32ef5aab8 100644
--- a/src/plugins/platforms/wasm/main.cpp
+++ b/src/plugins/platforms/wasm/main.cpp
@@ -1,37 +1,13 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qpa/qplatformintegrationplugin.h>
#include "qwasmintegration.h"
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
class QWasmIntegrationPlugin : public QPlatformIntegrationPlugin
{
Q_OBJECT
@@ -43,7 +19,7 @@ public:
QPlatformIntegration *QWasmIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
Q_UNUSED(paramList);
- if (!system.compare(QStringLiteral("wasm"), Qt::CaseInsensitive))
+ if (!system.compare("wasm"_L1, Qt::CaseInsensitive))
return new QWasmIntegration;
return nullptr;
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index ef4a6ec2b9..8027dd8fa9 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -1,577 +1,301 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
-
-// QtLoader provides javascript API for managing Qt application modules.
-//
-// QtLoader provides API on top of Emscripten which supports common lifecycle
-// tasks such as displaying placeholder content while the module downloads,
-// handing application exits, and checking for browser wasm support.
-//
-// There are two usage modes:
-// * Managed: QtLoader owns and manages the HTML display elements like
-// the loader and canvas.
-// * External: The embedding HTML page owns the display elements. QtLoader
-// provides event callbacks which the page reacts to.
-//
-// Managed mode usage:
-//
-// var config = {
-// containerElements : [$("container-id")];
-// }
-// var qtLoader = QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// External mode.usage:
-//
-// var config = {
-// canvasElements : [$("canvas-id")],
-// showLoader: function() {
-// loader.style.display = 'block'
-// canvas.style.display = 'hidden'
-// },
-// showCanvas: function() {
-// loader.style.display = 'hidden'
-// canvas.style.display = 'block'
-// return canvas;
-// }
-// }
-// var qtLoader = QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// Config keys
-//
-// containerElements : [container-element, ...]
-// 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,
-// while preparing the application binary. "status" is a string
-// containing the loading sub-status, and may be either "Downloading",
-// or "Compiling". The browser may be using streaming compilation, in
-// which case the wasm module is compiled during downloading and the
-// there is no separate compile step.
-// showCanvas : function(containerElement)
-// Optional canvas constructor function. Implement to create custom
-// canvas elements.
-// showExit : function(crashed, exitCode, containerElement)
-// Optional exited element constructor function.
-// showError : function(crashed, exitCode, containerElement)
-// Optional error element constructor function.
-//
-// path : <string>
-// Prefix path for wasm file, realative to the loading HMTL file.
-// restartMode : "DoNotRestart", "RestartOnExit", "RestartOnCrash"
-// Controls whether the application should be reloaded on exits. The default is "DoNotRestart"
-// restartType : "RestartModule", "ReloadPage"
-// restartLimit : <int>
-// Restart attempts limit. The default is 10.
-// stdoutEnabled : <bool>
-// stderrEnabled : <bool>
-// environment : <object>
-// key-value environment variable pairs.
-//
-// QtLoader object API
-//
-// webAssemblySupported : bool
-// webGLSupported : bool
-// canLoadQt : bool
-// Reports if WebAssembly and WebGL are supported. These are requirements for
-// running Qt applications.
-// loadEmscriptenModule(applicationName)
-// Loads the application from the given emscripten javascript module file and wasm file
-// status
-// One of "Created", "Loading", "Running", "Exited".
-// crashed
-// Set to true if there was an unclean exit.
-// exitCode
-// main()/emscripten_force_exit() return code. Valid on status change to
-// "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.
-// setFontDpi
-// Sets the logical font dpi for the application.
-
-
-var Module = {}
-
-function QtLoader(config)
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/**
+ * Loads the instance of a WASM module.
+ *
+ * @param config May contain any key normally accepted by emscripten and the 'qt' extra key, with
+ * the following sub-keys:
+ * - environment: { [name:string] : string }
+ * environment variables set on the instance
+ * - onExit: (exitStatus: { text: string, code?: number, crashed: bool }) => void
+ * called when the application has exited for any reason. There are two cases:
+ * aborted: crashed is true, text contains an error message.
+ * exited: crashed is false, code contians the exit code.
+ *
+ * Note that by default Emscripten does not exit when main() returns. This behavior
+ * is controlled by the EXIT_RUNTIME linker flag; set "-s EXIT_RUNTIME=1" to make
+ * Emscripten tear down the runtime and exit when main() returns.
+ *
+ * - containerElements: HTMLDivElement[]
+ * Array of host elements for Qt screens. Each of these elements is mapped to a QScreen on
+ * launch.
+ * - fontDpi: number
+ * Specifies font DPI for the instance
+ * - onLoaded: () => void
+ * Called when the module has loaded, at the point in time where any loading placeholder
+ * should be hidden and the application window should be shown.
+ * - entryFunction: (emscriptenConfig: object) => Promise<EmscriptenModule>
+ * Qt always uses emscripten's MODULARIZE option. This is the MODULARIZE entry function.
+ * - module: Promise<WebAssembly.Module>
+ * The module to create the instance from (optional). Specifying the module allows optimizing
+ * use cases where several instances are created from a single WebAssembly source.
+ * - qtdir: string
+ * Path to Qt installation. This path will be used for loading Qt shared libraries and plugins.
+ * The path is set to 'qt' by default, and is relative to the path of the web page's html file.
+ * This property is not in use when static linking is used, since this build mode includes all
+ * libraries and plugins in the wasm file.
+ * - preload: [string]: Array of file paths to json-encoded files which specifying which files to preload.
+ * The preloaded files will be downloaded at application startup and copied to the in-memory file
+ * system provided by Emscripten.
+ *
+ * Each json file must contain an array of source, destination objects:
+ * [
+ * {
+ * "source": "path/to/source",
+ * "destination": "/path/to/destination"
+ * },
+ * ...
+ * ]
+ * The source path is relative to the html file path. The destination path must be
+ * an absolute path.
+ *
+ * $QTDIR may be used as a placeholder for the "qtdir" configuration property (see @qtdir), for instance:
+ * "source": "$QTDIR/plugins/imageformats/libqjpeg.so"
+ * - localFonts.requestPermission: bool
+ * Whether Qt should request for local fonts access permission on startup (default false).
+ * - localFonts.familiesCollection string
+ * Specifies a collection of local fonts to load. Possible values are:
+ * "NoFontFamilies" : Don't load any font families
+ * "DefaultFontFamilies" : A subset of available font families; currently the "web-safe" fonts (default).
+ * "AllFontFamilies" : All local font families (not reccomended)
+ * - localFonts.extraFamilies: [string]
+ * Adds additional font families to be loaded at startup.
+ *
+ * @return Promise<instance: EmscriptenModule>
+ * The promise is resolved when the module has been instantiated and its main function has been
+ * called.
+ *
+ * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten for
+ * EmscriptenModule
+ */
+async function qtLoad(config)
{
- function webAssemblySupported() {
- return typeof WebAssembly !== "undefined"
- }
-
- function webGLSupported() {
- // We expect that WebGL is supported if WebAssembly is; however
- // the GPU may be blacklisted.
- try {
- var canvas = document.createElement("canvas");
- return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")));
- } catch (e) {
- return false;
+ const throwIfEnvUsedButNotExported = (instance, config) =>
+ {
+ const environment = config.environment;
+ if (!environment || Object.keys(environment).length === 0)
+ return;
+ const isEnvExported = typeof instance.ENV === 'object';
+ if (!isEnvExported)
+ throw new Error('ENV must be exported if environment variables are passed');
+ };
+
+ if (typeof config !== 'object')
+ throw new Error('config is required, expected an object');
+ if (typeof config.qt !== 'object')
+ throw new Error('config.qt is required, expected an object');
+ if (typeof config.qt.entryFunction !== 'function')
+ throw new Error('config.qt.entryFunction is required, expected a function');
+
+ config.qt.qtdir ??= 'qt';
+ config.qt.preload ??= [];
+
+ config.qtContainerElements = config.qt.containerElements;
+ delete config.qt.containerElements;
+ config.qtFontDpi = config.qt.fontDpi;
+ delete config.qt.fontDpi;
+
+ // Make Emscripten not call main(); this gives us more control over
+ // the startup sequence.
+ const originalNoInitialRun = config.noInitialRun;
+ const originalArguments = config.arguments;
+ config.noInitialRun = true;
+
+ // Used for rejecting a failed load's promise where emscripten itself does not allow it,
+ // like in instantiateWasm below. This allows us to throw in case of a load error instead of
+ // hanging on a promise to entry function, which emscripten unfortunately does.
+ let circuitBreakerReject;
+ const circuitBreaker = new Promise((_, reject) => { circuitBreakerReject = reject; });
+
+ // If module async getter is present, use it so that module reuse is possible.
+ if (config.qt.module) {
+ config.instantiateWasm = async (imports, successCallback) =>
+ {
+ try {
+ const module = await config.qt.module;
+ successCallback(
+ await WebAssembly.instantiate(module, imports), module);
+ } catch (e) {
+ circuitBreakerReject(e);
+ }
}
}
-
- function canLoadQt() {
- // The current Qt implementation requires WebAssembly (asm.js is not in use),
- // and also WebGL (there is no raster fallback).
- return webAssemblySupported() && webGLSupported();
- }
-
- function removeChildren(element) {
- while (element.firstChild) element.removeChild(element.firstChild);
- }
-
- 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");
- errorTextElement.className = "QtError"
- errorTextElement.innerHTML = errorText;
- return errorTextElement;
+ const fetchJsonHelper = async path => (await fetch(path)).json();
+ const filesToPreload = (await Promise.all(config.qt.preload.map(fetchJsonHelper))).flat();
+ const qtPreRun = (instance) => {
+ // Copy qt.environment to instance.ENV
+ throwIfEnvUsedButNotExported(instance, config);
+ for (const [name, value] of Object.entries(config.qt.environment ?? {}))
+ instance.ENV[name] = value;
+
+ // Preload files from qt.preload
+ const makeDirs = (FS, filePath) => {
+ const parts = filePath.split("/");
+ let path = "/";
+ for (let i = 0; i < parts.length - 1; ++i) {
+ const part = parts[i];
+ if (part == "")
+ continue;
+ path += part + "/";
+ try {
+ FS.mkdir(path);
+ } catch (error) {
+ const EEXIST = 20;
+ if (error.errno != EEXIST)
+ throw error;
+ }
+ }
}
- config.showLoader = config.showLoader || function(loadingState, container) {
- removeChildren(container);
- var loadingText = document.createElement("text");
- loadingText.className = "QtLoading"
- loadingText.innerHTML = '<p><center> ${loadingState}...</center><p>';
- return loadingText;
- };
-
- config.showCanvas = config.showCanvas || function(canvas, container) {
- removeChildren(container);
+ const extractFilenameAndDir = (path) => {
+ const parts = path.split('/');
+ const filename = parts.pop();
+ const dir = parts.join('/');
+ return {
+ filename: filename,
+ dir: dir
+ };
}
-
- config.showExit = config.showExit || function(crashed, exitCode, container) {
- if (!crashed)
- return undefined;
-
- removeChildren(container);
- var fontSize = 54;
- var crashSymbols = ["\u{1F615}", "\u{1F614}", "\u{1F644}", "\u{1F928}", "\u{1F62C}",
- "\u{1F915}", "\u{2639}", "\u{1F62E}", "\u{1F61E}", "\u{1F633}"];
- var symbolIndex = Math.floor(Math.random() * crashSymbols.length);
- var errorHtml = `<font size='${fontSize}'> ${crashSymbols[symbolIndex]} </font>`
- var errorElement = document.createElement("text");
- errorElement.className = "QtExit"
- errorElement.innerHTML = errorHtml;
- return errorElement;
+ const preloadFile = (file) => {
+ makeDirs(instance.FS, file.destination);
+ const source = file.source.replace('$QTDIR', config.qt.qtdir);
+ const filenameAndDir = extractFilenameAndDir(file.destination);
+ instance.FS.createPreloadedFile(filenameAndDir.dir, filenameAndDir.filename, source, true, true);
}
+ const isFsExported = typeof instance.FS === 'object';
+ if (!isFsExported)
+ throw new Error('FS must be exported if preload is used');
+ filesToPreload.forEach(preloadFile);
}
- config.restartMode = config.restartMode || "DoNotRestart";
- config.restartLimit = config.restartLimit || 10;
-
- if (config.stdoutEnabled === undefined) config.stdoutEnabled = true;
- if (config.stderrEnabled === undefined) config.stderrEnabled = true;
-
- // Make sure config.path is defined and ends with "/" if needed
- if (config.path === undefined)
- config.path = "";
- if (config.path.length > 0 && !config.path.endsWith("/"))
- config.path = config.path.concat("/");
-
- if (config.environment === undefined)
- config.environment = {};
+ if (!config.preRun)
+ config.preRun = [];
+ config.preRun.push(qtPreRun);
- var publicAPI = {};
- publicAPI.webAssemblySupported = webAssemblySupported();
- publicAPI.webGLSupported = webGLSupported();
- publicAPI.canLoadQt = canLoadQt();
- publicAPI.canLoadApplication = canLoadQt();
- publicAPI.status = undefined;
- publicAPI.loadEmscriptenModule = loadEmscriptenModule;
- publicAPI.addCanvasElement = addCanvasElement;
- publicAPI.removeCanvasElement = removeCanvasElement;
- publicAPI.resizeCanvasElement = resizeCanvasElement;
- publicAPI.setFontDpi = setFontDpi;
- publicAPI.fontDpi = fontDpi;
-
- restartCount = 0;
-
- function fetchResource(filePath) {
- var fullPath = config.path + filePath;
- return fetch(fullPath).then(function(response) {
- if (!response.ok) {
- self.error = response.status + " " + response.statusText + " " + response.url;
- setStatus("Error");
- return Promise.reject(self.error)
- } else {
- return response;
- }
- });
+ const originalOnRuntimeInitialized = config.onRuntimeInitialized;
+ config.onRuntimeInitialized = () => {
+ originalOnRuntimeInitialized?.();
+ config.qt.onLoaded?.();
}
- function fetchText(filePath) {
- return fetchResource(filePath).then(function(response) {
- return response.text();
- });
+ const originalLocateFile = config.locateFile;
+ config.locateFile = filename => {
+ const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename;
+ if (originalLocatedFilename.startsWith('libQt6'))
+ return `${config.qt.qtdir}/lib/${originalLocatedFilename}`;
+ return originalLocatedFilename;
}
- function fetchThenCompileWasm(response) {
- return response.arrayBuffer().then(function(data) {
- self.loaderSubState = "Compiling";
- setStatus("Loading") // trigger loaderSubState udpate
- return WebAssembly.compile(data);
- });
- }
-
- function fetchCompileWasm(filePath) {
- return fetchResource(filePath).then(function(response) {
- if (typeof WebAssembly.compileStreaming !== "undefined") {
- self.loaderSubState = "Downloading/Compiling";
- setStatus("Loading");
- return WebAssembly.compileStreaming(response).catch(function(error) {
- // compileStreaming may/will fail if the server does not set the correct
- // mime type (application/wasm) for the wasm file. Fall back to fetch,
- // then compile in this case.
- return fetchThenCompileWasm(response);
- });
- } else {
- // Fall back to fetch, then compile if compileStreaming is not supported
- return fetchThenCompileWasm(response);
- }
- });
- }
+ let onExitCalled = false;
+ const originalOnExit = config.onExit;
+ config.onExit = code => {
+ originalOnExit?.();
- function loadEmscriptenModule(applicationName) {
-
- // Loading in qtloader.js goes through four steps:
- // 1) Check prerequisites
- // 2) Download resources
- // 3) Configure the emscripten Module object
- // 4) Start the emcripten runtime, after which emscripten takes over
-
- // Check for Wasm & WebGL support; set error and return before downloading resources if missing
- if (!webAssemblySupported()) {
- self.error = "Error: WebAssembly is not supported"
- setStatus("Error");
- return;
- }
- if (!webGLSupported()) {
- self.error = "Error: WebGL is not supported"
- setStatus("Error");
- return;
- }
-
- // Continue waiting if loadEmscriptenModule() is called again
- if (publicAPI.status == "Loading")
- return;
- self.loaderSubState = "Downloading";
- setStatus("Loading");
-
- // Fetch emscripten generated javascript runtime
- var emscriptenModuleSource = undefined
- var emscriptenModuleSourcePromise = fetchText(applicationName + ".js").then(function(source) {
- emscriptenModuleSource = source
- });
-
- // Fetch and compile wasm module
- var wasmModule = undefined;
- var wasmModulePromise = fetchCompileWasm(applicationName + ".wasm").then(function (module) {
- wasmModule = module;
- });
-
- // Wait for all resources ready
- Promise.all([emscriptenModuleSourcePromise, wasmModulePromise]).then(function(){
- completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule);
- }).catch(function(error) {
- self.error = error;
- setStatus("Error");
- });
- }
-
- function completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule) {
-
- // The wasm binary has been compiled into a module during resource download,
- // and is ready to be instantiated. Define the instantiateWasm callback which
- // emscripten will call to create the instance.
- Module.instantiateWasm = function(imports, successCallback) {
- WebAssembly.instantiate(wasmModule, imports).then(function(instance) {
- successCallback(instance, wasmModule);
- }, function(error) {
- self.error = error;
- setStatus("Error");
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ code,
+ crashed: false
});
- return {};
- };
-
- Module.locateFile = Module.locateFile || function(filename) {
- return config.path + filename;
- };
-
- // Attach status callbacks
- Module.setStatus = Module.setStatus || function(text) {
- // Currently the only usable status update from this function
- // is "Running..."
- if (text.startsWith("Running"))
- setStatus("Running");
- };
- Module.monitorRunDependencies = Module.monitorRunDependencies || function(left) {
- // console.log("monitorRunDependencies " + left)
- };
-
- // Attach standard out/err callbacks.
- Module.print = Module.print || function(text) {
- if (config.stdoutEnabled)
- console.log(text)
- };
- Module.printErr = Module.printErr || function(text) {
- // Filter out OpenGL getProcAddress warnings. Qt tries to resolve
- // all possible function/extension names at startup which causes
- // emscripten to spam the console log with warnings.
- if (text.startsWith !== undefined && text.startsWith("bad name in getProcAddress:"))
- return;
-
- if (config.stderrEnabled)
- console.log(text)
- };
-
- // Error handling: set status to "Exited", update crashed and
- // exitCode according to exit type.
- // Emscripten will typically call printErr with the error text
- // as well. Note that emscripten may also throw exceptions from
- // async callbacks. These should be handled in window.onerror by user code.
- Module.onAbort = Module.onAbort || function(text) {
- publicAPI.crashed = true;
- publicAPI.exitText = text;
- setStatus("Exited");
- };
- Module.quit = Module.quit || function(code, exception) {
- if (exception.name == "ExitStatus") {
- // Clean exit with code
- publicAPI.exitText = undefined
- publicAPI.exitCode = code;
- } else {
- publicAPI.exitText = exception.toString();
- publicAPI.crashed = true;
- }
- setStatus("Exited");
- };
-
- // Set environment variables
- Module.preRun = Module.preRun || []
- Module.preRun.push(function() {
- for (var [key, value] of Object.entries(config.environment)) {
- ENV[key.toUpperCase()] = value;
- }
- });
-
- 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
- // reload loops can't be prevented.
- if (config.restartType == "ReloadPage") {
- location.reload();
- }
-
- // Restart by readling the emscripten app module.
- ++self.restartCount;
- if (self.restartCount > config.restartLimit) {
- self.error = "Error: This application has crashed too many times and has been disabled. Reload the page to try again."
- setStatus("Error");
- return;
- }
- loadEmscriptenModule(applicationName);
- };
-
- publicAPI.exitCode = undefined;
- publicAPI.exitText = undefined;
- publicAPI.crashed = false;
-
- // Finally evaluate the emscripten application script, which will
- // reference the global Module object created above.
- self.eval(emscriptenModuleSource); // ES5 indirect global scope eval
- }
-
- function setErrorContent() {
- if (config.containerElements === undefined) {
- if (config.showError !== undefined)
- config.showError(self.error);
- return;
- }
-
- for (container of config.containerElements) {
- var errorElement = config.showError(self.error, container);
- container.appendChild(errorElement);
}
}
- function setLoaderContent() {
- if (config.containerElements === undefined) {
- if (config.showLoader !== undefined)
- config.showLoader(self.loaderSubState);
- return;
- }
-
- for (container of config.containerElements) {
- var loaderElement = config.showLoader(self.loaderSubState, container);
- container.appendChild(loaderElement);
+ const originalOnAbort = config.onAbort;
+ config.onAbort = text =>
+ {
+ originalOnAbort?.();
+
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text,
+ crashed: true
+ });
}
- }
-
- function setCanvasContent() {
- if (config.containerElements === undefined) {
- if (config.showCanvas !== undefined)
- config.showCanvas();
+ };
+
+ // Call app/emscripten module entry function. It may either come from the emscripten
+ // runtime script or be customized as needed.
+ let instance;
+ try {
+ instance = await Promise.race(
+ [circuitBreaker, config.qt.entryFunction(config)]);
+
+ // Call main after creating the instance. We've opted into manually
+ // calling main() by setting noInitialRun in the config. Thie Works around
+ // issue where Emscripten suppresses all exceptions thrown during main.
+ if (!originalNoInitialRun)
+ instance.callMain(originalArguments);
+ } catch (e) {
+ // If this is the exception thrown by app.exec() then that is a normal
+ // case and we suppress it.
+ if (e == "unwind") // not much to go on
return;
- }
- 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);
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text: e.message,
+ crashed: true
+ });
}
+ throw e;
}
- function setExitContent() {
-
- // publicAPI.crashed = true;
-
- if (publicAPI.status != "Exited")
- return;
-
- if (config.containerElements === undefined) {
- if (config.showExit !== undefined)
- config.showExit(publicAPI.crashed, publicAPI.exitCode);
- return;
- }
-
- if (!publicAPI.crashed)
- return;
+ return instance;
+}
- for (container of config.containerElements) {
- var loaderElement = config.showExit(publicAPI.crashed, publicAPI.exitCode, container);
- if (loaderElement !== undefined)
- container.appendChild(loaderElement);
- }
+// Compatibility API. This API is deprecated,
+// and will be removed in a future version of Qt.
+function QtLoader(qtConfig) {
+
+ const warning = 'Warning: The QtLoader API is deprecated and will be removed in ' +
+ 'a future version of Qt. Please port to the new qtLoad() API.';
+ console.warn(warning);
+
+ let emscriptenConfig = qtConfig.moduleConfig || {}
+ qtConfig.moduleConfig = undefined;
+ const showLoader = qtConfig.showLoader;
+ qtConfig.showLoader = undefined;
+ const showError = qtConfig.showError;
+ qtConfig.showError = undefined;
+ const showExit = qtConfig.showExit;
+ qtConfig.showExit = undefined;
+ const showCanvas = qtConfig.showCanvas;
+ qtConfig.showCanvas = undefined;
+ if (qtConfig.canvasElements) {
+ qtConfig.containerElements = qtConfig.canvasElements
+ qtConfig.canvasElements = undefined;
+ } else {
+ qtConfig.containerElements = qtConfig.containerElements;
+ qtConfig.containerElements = undefined;
}
-
- var committedStatus = undefined;
- function handleStatusChange() {
- if (publicAPI.status != "Loading" && committedStatus == publicAPI.status)
- return;
- committedStatus = publicAPI.status;
-
- if (publicAPI.status == "Error") {
- setErrorContent();
- } else if (publicAPI.status == "Loading") {
- setLoaderContent();
- } else if (publicAPI.status == "Running") {
- setCanvasContent();
- } else if (publicAPI.status == "Exited") {
- if (config.restartMode == "RestartOnExit" ||
- config.restartMode == "RestartOnCrash" && publicAPI.crashed) {
- committedStatus = undefined;
- config.restart();
- } else {
- setExitContent();
+ emscriptenConfig.qt = qtConfig;
+
+ let qtloader = {
+ exitCode: undefined,
+ exitText: "",
+ loadEmscriptenModule: _name => {
+ try {
+ qtLoad(emscriptenConfig);
+ } catch (e) {
+ showError?.(e.message);
}
}
-
- // Send status change notification
- if (config.statusChanged)
- config.statusChanged(publicAPI.status);
- }
-
- function setStatus(status) {
- if (status != "Loading" && publicAPI.status == status)
- return;
- publicAPI.status = status;
-
- 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);
+ qtConfig.onLoaded = () => {
+ showCanvas?.();
}
- function setFontDpi(dpi) {
- Module.qtFontDpi = dpi;
- if (publicAPI.status == "Running")
- Module.qtSetFontDpi(dpi);
+ qtConfig.onExit = exit => {
+ qtloader.exitCode = exit.code
+ qtloader.exitText = exit.text;
+ showExit?.();
}
- function fontDpi() {
- return Module.qtFontDpi;
- }
-
- setStatus("Created");
+ showLoader?.("Loading");
- return publicAPI;
-}
+ return qtloader;
+};
diff --git a/src/plugins/platforms/wasm/qtlogo.svg b/src/plugins/platforms/wasm/qtlogo.svg
deleted file mode 100644
index ad7c7776bf..0000000000
--- a/src/plugins/platforms/wasm/qtlogo.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- width="462pt"
- height="339pt"
- viewBox="0 0 462 339"
- version="1.1">
- <metadata
- id="metadata20">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <path
- fill="#41cd52"
- d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z"
- id="path6" />
- <path
- d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z"
- id="path8"
- fill="#ffffff" />
- <path
- d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z"
- id="path10"
- fill="#ffffff" />
- <path
- fill="#41cd52"
- d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z"
- id="path12" />
-</svg>
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
new file mode 100644
index 0000000000..4c3cb46ba3
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
@@ -0,0 +1,789 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmaccessibility.h"
+#include "qwasmscreen.h"
+#include "qwasmwindow.h"
+#include "qwasmintegration.h"
+#include <QtGui/qwindow.h>
+
+#if QT_CONFIG(accessibility)
+
+#include <QtGui/private/qaccessiblebridgeutils_p.h>
+
+Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
+
+// Qt WebAssembly a11y backend
+//
+// This backend implements accessibility support by creating "shadowing" html
+// elements for each Qt UI element. We access the DOM by using Emscripten's
+// val.h API.
+//
+// Currently, html elements are created in response to notifyAccessibilityUpdate
+// events. In addition or alternatively, we could also walk the accessibility tree
+// from setRootObject().
+
+QWasmAccessibility::QWasmAccessibility()
+{
+
+ s_instance = this;
+}
+
+QWasmAccessibility::~QWasmAccessibility()
+{
+ s_instance = nullptr;
+}
+
+QWasmAccessibility *QWasmAccessibility::s_instance = nullptr;
+
+QWasmAccessibility* QWasmAccessibility::get()
+{
+ return s_instance;
+}
+
+void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
+{
+ get()->addAccessibilityEnableButtonImpl(window);
+}
+
+void QWasmAccessibility::removeAccessibilityEnableButton(QWindow *window)
+{
+ get()->removeAccessibilityEnableButtonImpl(window);
+}
+
+void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
+{
+ if (m_accessibilityEnabled)
+ return;
+
+ emscripten::val container = getContainer(window);
+ emscripten::val document = getDocument(container);
+ emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
+ button.set("innerText", std::string("Enable Screen Reader"));
+ button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader"));
+ container.call<void>("appendChild", button);
+
+ auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback>
+ (button, std::string("click"), [this](emscripten::val) { enableAccessibility(); }));
+ m_enableButtons.insert(std::make_pair(window, std::move(enableContext)));
+}
+
+void QWasmAccessibility::removeAccessibilityEnableButtonImpl(QWindow *window)
+{
+ auto it = m_enableButtons.find(window);
+ if (it == m_enableButtons.end())
+ return;
+
+ // Remove button
+ auto [element, callback] = it->second;
+ Q_UNUSED(callback);
+ element["parentElement"].call<void>("removeChild", element);
+ m_enableButtons.erase(it);
+}
+
+void QWasmAccessibility::enableAccessibility()
+{
+ // Enable accessibility globally for the applicaton. Remove all "enable"
+ // buttons and populate the accessibility tree, starting from the root object.
+
+ Q_ASSERT(!m_accessibilityEnabled);
+ m_accessibilityEnabled = true;
+ for (const auto& [key, value] : m_enableButtons) {
+ const auto &[element, callback] = value;
+ Q_UNUSED(key);
+ Q_UNUSED(callback);
+ element["parentElement"].call<void>("removeChild", element);
+ }
+ m_enableButtons.clear();
+ populateAccessibilityTree(QAccessible::queryAccessibleInterface(m_rootObject));
+}
+
+emscripten::val QWasmAccessibility::getContainer(QWindow *window)
+{
+ return window ? static_cast<QWasmWindow *>(window->handle())->a11yContainer()
+ : emscripten::val::undefined();
+}
+
+emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface)
+{
+ if (!iface)
+ return emscripten::val::undefined();
+ return getContainer(getWindow(iface));
+}
+
+QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
+{
+ QWindow *window = iface->window();
+ // this is needed to add tabs as the window is not available
+ if (!window && iface->parent())
+ window = iface->parent()->window();
+ return window;
+}
+
+emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
+{
+ if (container.isUndefined())
+ return emscripten::val::global("document");
+ return container["ownerDocument"];
+}
+
+emscripten::val QWasmAccessibility::getDocument(QAccessibleInterface *iface)
+{
+ return getDocument(getContainer(iface));
+}
+
+emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface)
+{
+ // Get the html container element for the interface; this depends on which
+ // QScreen it is on. If the interface is not on a screen yet we get an undefined
+ // container, and the code below handles that case as well.
+ emscripten::val container = getContainer(iface);
+
+ // Get the correct html document for the container, or fall back
+ // to the global document. TODO: Does using the correct document actually matter?
+ emscripten::val document = getDocument(container);
+
+ // Translate the Qt a11y elemen role into html element type + ARIA role.
+ // Here we can either create <div> elements with a spesific ARIA role,
+ // or create e.g. <button> elements which should have built-in accessibility.
+ emscripten::val element = [this, iface, document] {
+
+ emscripten::val element = emscripten::val::undefined();
+
+ switch (iface->role()) {
+
+ case QAccessible::Button: {
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+ case QAccessible::CheckBox: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("checkbox"));
+ if (iface->state().checked) {
+ element.call<void>("setAttribute", std::string("checked"), std::string("true"));
+ }
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+
+ } break;
+
+ case QAccessible::RadioButton: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("radio"));
+ if (iface->state().checked) {
+ element.call<void>("setAttribute", std::string("checked"), std::string("true"));
+ }
+ element.set(std::string("name"), std::string("buttonGroup"));
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::SpinBox: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("number"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::Slider: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("range"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::PageTabList:{
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ element.call<void>("setAttribute", std::string("role"), std::string("tablist"));
+ QString idName = iface->text(QAccessible::Name).replace(" ", "_");
+ idName += "_tabList";
+ element.call<void>("setAttribute", std::string("id"), idName.toStdString());
+
+ for (int i = 0; i < iface->childCount(); ++i) {
+ if (iface->child(i)->role() == QAccessible::PageTab){
+ emscripten::val elementTab = emscripten::val::undefined();
+ elementTab = ensureHtmlElement(iface->child(i));
+ elementTab.call<void>("setAttribute", std::string("aria-owns"), idName.toStdString());
+ setHtmlElementGeometry(iface->child(i));
+ }
+ }
+ } break;
+
+ case QAccessible::PageTab:{
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ element.call<void>("setAttribute", std::string("role"), std::string("tab"));
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::ScrollBar: {
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ element.call<void>("setAttribute", std::string("role"), std::string("scrollbar"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("aria-valuenow"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::StaticText: {
+ element = document.call<emscripten::val>("createElement", std::string("textarea"));
+ element.call<void>("setAttribute", std::string("readonly"), std::string("true"));
+
+ } break;
+ case QAccessible::Dialog: {
+ element = document.call<emscripten::val>("createElement", std::string("dialog"));
+ }break;
+ case QAccessible::ToolBar:{
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ QString text = iface->text(QAccessible::Name);
+
+ element.call<void>("setAttribute", std::string("role"), std::string("toolbar"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ }break;
+ case QAccessible::MenuItem:
+ case QAccessible::ButtonMenu: {
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ QString text = iface->text(QAccessible::Name);
+
+ element.call<void>("setAttribute", std::string("role"), std::string("menuitem"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ }break;
+ case QAccessible::MenuBar:
+ case QAccessible::PopupMenu: {
+ element = document.call<emscripten::val>("createElement",std::string("div"));
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("role"), std::string("menubar"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ for (int i = 0; i < iface->childCount(); ++i) {
+ emscripten::val childElement = emscripten::val::undefined();
+ childElement= ensureHtmlElement(iface->child(i));
+ childElement.call<void>("setAttribute", std::string("aria-owns"), text.toStdString());
+ setHtmlElementTextName(iface->child(i));
+ setHtmlElementGeometry(iface->child(i));
+ }
+ }break;
+ case QAccessible::EditableText: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"),std::string("text"));
+ element.call<void>("addEventListener", emscripten::val("input"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ }
+
+ return element;
+
+ }();
+
+ // Add the html element to the container if we have one. If not there
+ // is a second chance when handling the ObjectShow event.
+ if (!container.isUndefined())
+ container.call<void>("appendChild", element);
+
+ return element;
+}
+
+void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
+{
+ Q_UNUSED(iface);
+ qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement";
+}
+
+emscripten::val QWasmAccessibility::ensureHtmlElement(QAccessibleInterface *iface)
+{
+ auto it = m_elements.find(iface);
+ if (it != m_elements.end())
+ return it.value();
+
+ emscripten::val element = createHtmlElement(iface);
+ m_elements.insert(iface, element);
+
+ return element;
+}
+
+void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible)
+{
+ emscripten::val element = ensureHtmlElement(iface);
+ emscripten::val container = getContainer(iface);
+
+ if (container.isUndefined()) {
+ qCDebug(lcQpaAccessibility) << "TODO: setHtmlElementVisibility: unable to find html container for element" << iface;
+ return;
+ }
+
+ container.call<void>("appendChild", element);
+
+ element.set("ariaHidden", !visible); // ariaHidden mean completely hidden; maybe some sort of soft-hidden should be used.
+}
+
+void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
+{
+ emscripten::val element = ensureHtmlElement(iface);
+
+ // QAccessibleInterface gives us the geometry in global (screen) coordinates. Translate that
+ // to window geometry in order to position elements relative to window origin.
+ QWindow *window = getWindow(iface);
+ if (!window)
+ qCWarning(lcQpaAccessibility) << "Unable to find window for" << iface << "setting null geometry";
+ QRect screenGeometry = iface->rect();
+ QPoint windowPos = window ? window->mapFromGlobal(screenGeometry.topLeft()) : QPoint();
+ QRect windowGeometry(windowPos, screenGeometry.size());
+
+ setHtmlElementGeometry(element, windowGeometry);
+}
+
+void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
+{
+ // Position the element using "position: absolute" in order to place
+ // it under the corresponding Qt element in the screen.
+ emscripten::val style = element["style"];
+ style.set("position", std::string("absolute"));
+ style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
+ // screen element, but isn't
+ style.set("left", std::to_string(geometry.x()) + "px");
+ style.set("top", std::to_string(geometry.y()) + "px");
+ style.set("width", std::to_string(geometry.width()) + "px");
+ style.set("height", std::to_string(geometry.height()) + "px");
+}
+
+void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface)
+{
+ emscripten::val element = ensureHtmlElement(iface);
+ QString text = iface->text(QAccessible::Name);
+ element.set("innerHTML", text.toStdString()); // FIXME: use something else than innerHTML
+}
+
+void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) {
+ emscripten::val element = ensureHtmlElement(iface);
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("name"), text.toStdString());
+ QString value = iface->text(QAccessible::Value);
+ element.set("innerHTML", value.toStdString());
+}
+
+void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) {
+ emscripten::val element = ensureHtmlElement(iface);
+ QString desc = iface->text(QAccessible::Description);
+ element.call<void>("setAttribute", std::string("aria-description"), desc.toStdString());
+}
+
+void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) {
+
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus:
+ case QAccessible::TextRemoved:
+ case QAccessible::TextInserted:
+ case QAccessible::TextCaretMoved: {
+ setHtmlElementTextNameLE(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
+{
+
+ QAccessibleInterface *iface = m_elements.key(event["target"]);
+ if (iface == nullptr) {
+ return;
+ } else {
+ QString eventType = QString::fromStdString(event["type"].as<std::string>());
+ const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
+ if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
+
+ iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
+
+ } else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
+
+ iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
+
+ } else if (actionNames.contains(QAccessibleActionInterface::increaseAction()) ||
+ actionNames.contains(QAccessibleActionInterface::decreaseAction())) {
+
+ QString val = QString::fromStdString(event["target"]["value"].as<std::string>());
+
+ iface->valueInterface()->setCurrentValue(val.toInt());
+
+ } else if (eventType == "input") {
+
+ // as EditableTextInterface is not implemented in qml accessibility
+ // so we need to check the role for text to update in the textbox during accessibility
+
+ if (iface->editableTextInterface() || iface->role() == QAccessible::EditableText) {
+ std::string insertText = event["target"]["value"].as<std::string>();
+ iface->setText(QAccessible::Value, QString::fromStdString(insertText));
+ }
+ }
+ }
+}
+
+void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
+{
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleButtonUpdate for event" << event->type();
+}
+
+void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::StateChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ bool checkedString = accessible->state().checked ? true : false;
+ element.call<void>("setAttribute", std::string("checked"), checkedString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ QString text = iface->text(QAccessible::Name);
+ QString desc = iface->text(QAccessible::Description);
+ switch (event->type()) {
+ case QAccessible::NameChanged:
+ case QAccessible::StateChanged:{
+ emscripten::val element = ensureHtmlElement(iface);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ QString text = iface->text(QAccessible::Name);
+ QString desc = iface->text(QAccessible::Description);
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged:
+ case QAccessible::MenuStart ://"TODO: To implement later
+ case QAccessible::PopupMenuStart://"TODO: To implement later
+ case QAccessible::StateChanged:{
+ emscripten::val element = ensureHtmlElement(iface);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
+
+ switch (event->type()) {
+ case QAccessible::NameChanged:
+ case QAccessible::Focus:
+ case QAccessible::DialogStart:
+ case QAccessible::StateChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
+{
+ if (!iface)
+ return;
+
+ // Create html element for the interface, sync up properties.
+ ensureHtmlElement(iface);
+ const bool visible = !iface->state().invisible;
+ setHtmlElementVisibility(iface, visible);
+ setHtmlElementGeometry(iface);
+ setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
+
+ for (int i = 0; i < iface->childCount(); ++i)
+ populateAccessibilityTree(iface->child(i));
+}
+
+void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::StateChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string checkedString = accessible->state().checked ? "true" : "false";
+ element.call<void>("setAttribute", std::string("checked"), checkedString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("aria-valuenow"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
+ break;
+ }
+
+}
+
+void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
+{
+ if (!m_accessibilityEnabled)
+ return;
+
+ QAccessibleInterface *iface = event->accessibleInterface();
+ if (!iface) {
+ qWarning() << "notifyAccessibilityUpdate with null a11y interface" ;
+ return;
+ }
+
+ // Handle some common event types. See
+ // https://doc.qt.io/qt-5/qaccessible.html#Event-enum
+ switch (event->type()) {
+ case QAccessible::ObjectShow:
+ setHtmlElementVisibility(iface, true);
+
+ // Sync up properties on show;
+ setHtmlElementGeometry(iface);
+ setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
+
+ return;
+ break;
+ case QAccessible::ObjectHide:
+ setHtmlElementVisibility(iface, false);
+ return;
+ break;
+ // TODO: maybe handle more types here
+ default:
+ break;
+ };
+
+ // Switch on interface role, see
+ // https://doc.qt.io/qt-5/qaccessibleinterface.html#role
+ switch (iface->role()) {
+ case QAccessible::StaticText:
+ handleStaticTextUpdate(event);
+ break;
+ case QAccessible::Button:
+ handleStaticTextUpdate(event);
+ break;
+ case QAccessible::CheckBox:
+ handleCheckBoxUpdate(event);
+ break;
+ case QAccessible::EditableText:
+ handleLineEditUpdate(event);
+ break;
+ case QAccessible::Dialog:
+ handleDialogUpdate(event);
+ break;
+ case QAccessible::MenuItem:
+ case QAccessible::MenuBar:
+ case QAccessible::PopupMenu:
+ handleMenuUpdate(event);
+ break;
+ case QAccessible::ToolBar:
+ case QAccessible::ButtonMenu:
+ handleToolUpdate(event);
+ case QAccessible::RadioButton:
+ handleRadioButtonUpdate(event);
+ break;
+ case QAccessible::SpinBox:
+ handleSpinBoxUpdate(event);
+ break;
+ case QAccessible::Slider:
+ handleSliderUpdate(event);
+ break;
+ case QAccessible::PageTab:
+ handlePageTabUpdate(event);
+ break;
+ case QAccessible::PageTabList:
+ handlePageTabListUpdate(event);
+ break;
+ case QAccessible::ScrollBar:
+ handleScrollBarUpdate(event);
+ break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
+ };
+}
+
+void QWasmAccessibility::setRootObject(QObject *root)
+{
+ m_rootObject = root;
+}
+
+void QWasmAccessibility::initialize()
+{
+
+}
+
+void QWasmAccessibility::cleanup()
+{
+
+}
+
+void QWasmAccessibility::onHtmlEventReceived(emscripten::val event)
+{
+ static_cast<QWasmAccessibility *>(QWasmIntegration::get()->accessibility())->handleEventFromHtmlElement(event);
+}
+
+EMSCRIPTEN_BINDINGS(qtButtonEvent) {
+ function("qtEventReceived", &QWasmAccessibility::onHtmlEventReceived);
+}
+
+#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.h b/src/plugins/platforms/wasm/qwasmaccessibility.h
new file mode 100644
index 0000000000..c4be7f0d72
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMACCESIBILITY_H
+#define QWASMACCESIBILITY_H
+
+#include <QtCore/qtconfigmacros.h>
+#include <QtGui/qtguiglobal.h>
+
+#if QT_CONFIG(accessibility)
+
+#include <QtCore/qhash.h>
+#include <private/qstdweb_p.h>
+#include <qpa/qplatformaccessibility.h>
+
+#include <emscripten/val.h>
+#include <QLoggingCategory>
+
+#include <map>
+#include <emscripten/bind.h>
+
+Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
+
+class QWasmAccessibility : public QPlatformAccessibility
+{
+public:
+ QWasmAccessibility();
+ ~QWasmAccessibility();
+
+ static QWasmAccessibility* get();
+
+ static void addAccessibilityEnableButton(QWindow *window);
+ static void removeAccessibilityEnableButton(QWindow *window);
+
+private:
+ void addAccessibilityEnableButtonImpl(QWindow *window);
+ void removeAccessibilityEnableButtonImpl(QWindow *window);
+ void enableAccessibility();
+
+ static emscripten::val getContainer(QWindow *window);
+ static emscripten::val getContainer(QAccessibleInterface *iface);
+ static emscripten::val getDocument(const emscripten::val &container);
+ static emscripten::val getDocument(QAccessibleInterface *iface);
+ static QWindow *getWindow(QAccessibleInterface *iface);
+
+ emscripten::val createHtmlElement(QAccessibleInterface *iface);
+ void destroyHtmlElement(QAccessibleInterface *iface);
+ emscripten::val ensureHtmlElement(QAccessibleInterface *iface);
+ void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible);
+ void setHtmlElementGeometry(QAccessibleInterface *iface);
+ void setHtmlElementGeometry(emscripten::val element, QRect geometry);
+ void setHtmlElementTextName(QAccessibleInterface *iface);
+ void setHtmlElementTextNameLE(QAccessibleInterface *iface);
+ void setHtmlElementDescription(QAccessibleInterface *iface);
+
+ void handleStaticTextUpdate(QAccessibleEvent *event);
+ void handleButtonUpdate(QAccessibleEvent *event);
+ void handleCheckBoxUpdate(QAccessibleEvent *event);
+ void handleDialogUpdate(QAccessibleEvent *event);
+ void handleMenuUpdate(QAccessibleEvent *event);
+ void handleToolUpdate(QAccessibleEvent *event);
+ void handleLineEditUpdate(QAccessibleEvent *event);
+ void handleRadioButtonUpdate(QAccessibleEvent *event);
+ void handleSpinBoxUpdate(QAccessibleEvent *event);
+ void handlePageTabUpdate(QAccessibleEvent *event);
+ void handleSliderUpdate(QAccessibleEvent *event);
+ void handleScrollBarUpdate(QAccessibleEvent *event);
+ void handlePageTabListUpdate(QAccessibleEvent *event);
+
+ void handleEventFromHtmlElement(const emscripten::val event);
+
+ void populateAccessibilityTree(QAccessibleInterface *iface);
+ void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
+ void setRootObject(QObject *o) override;
+ void initialize() override;
+ void cleanup() override;
+
+public: // public for EMSCRIPTEN_BINDINGS
+ static void onHtmlEventReceived(emscripten::val event);
+
+private:
+ static QWasmAccessibility *s_instance;
+ QObject *m_rootObject = nullptr;
+ bool m_accessibilityEnabled = false;
+ std::map<QWindow *, std::tuple<emscripten::val, std::shared_ptr<qstdweb::EventCallback>>> m_enableButtons;
+ QHash<QAccessibleInterface *, emscripten::val> m_elements;
+
+};
+
+#endif // QT_CONFIG(accessibility)
+
+#endif
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
index 3d667ccf97..a3c1ae8a50 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.cpp
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
@@ -1,50 +1,21 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmbackingstore.h"
#include "qwasmwindow.h"
#include "qwasmcompositor.h"
+#include "qwasmdom.h"
-#include <QtOpenGL/qopengltexture.h>
-#include <QtGui/qmatrix4x4.h>
#include <QtGui/qpainter.h>
-#include <private/qguiapplication_p.h>
-#include <qpa/qplatformscreen.h>
-#include <QtGui/qoffscreensurface.h>
#include <QtGui/qbackingstore.h>
+#include <emscripten.h>
+#include <emscripten/wire.h>
+
QT_BEGIN_NAMESPACE
QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window)
- : QPlatformBackingStore(window)
- , m_compositor(compositor)
- , m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D))
+ : QPlatformBackingStore(window), m_compositor(compositor)
{
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
if (wasmWindow)
@@ -55,29 +26,11 @@ QWasmBackingStore::~QWasmBackingStore()
{
auto window = this->window();
QWasmIntegration::get()->removeBackingStore(window);
- destroy();
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
if (wasmWindow)
wasmWindow->setBackingStore(nullptr);
}
-void QWasmBackingStore::destroy()
-{
- if (m_texture->isCreated()) {
- auto context = m_compositor->context();
- auto currentContext = QOpenGLContext::currentContext();
- if (!currentContext || !QOpenGLContext::areSharing(context, currentContext)) {
- QOffscreenSurface offScreenSurface(m_compositor->screen()->screen());
- offScreenSurface.setFormat(context->format());
- offScreenSurface.create();
- context->makeCurrent(&offScreenSurface);
- m_texture->destroy();
- } else {
- m_texture->destroy();
- }
- }
-}
-
QPaintDevice *QWasmBackingStore::paintDevice()
{
return &m_image;
@@ -90,33 +43,24 @@ void QWasmBackingStore::flush(QWindow *window, const QRegion &region, const QPoi
Q_UNUSED(offset);
m_dirty |= region;
- m_compositor->requestRedraw();
+ m_compositor->handleBackingStoreFlush(window);
}
-void QWasmBackingStore::updateTexture()
+void QWasmBackingStore::updateTexture(QWasmWindow *window)
{
if (m_dirty.isNull())
return;
- if (m_recreateTexture) {
- m_recreateTexture = false;
- destroy();
+ if (m_webImageDataArray.isUndefined()) {
+ m_webImageDataArray = window->context2d().call<emscripten::val>(
+ "createImageData", emscripten::val(m_image.width()),
+ emscripten::val(m_image.height()));
}
- if (!m_texture->isCreated()) {
- m_texture->setMinificationFilter(QOpenGLTexture::Nearest);
- m_texture->setMagnificationFilter(QOpenGLTexture::Nearest);
- m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
- m_texture->setData(m_image, QOpenGLTexture::DontGenerateMipMaps);
- m_texture->create();
- }
- m_texture->bind();
-
- QRegion fixed;
+ QRegion clippedDpiScaledRegion;
QRect imageRect = m_image.rect();
for (const QRect &rect : m_dirty) {
-
// Convert device-independent dirty region to device region
qreal dpr = m_image.devicePixelRatio();
QRect deviceRect = QRect(rect.topLeft() * dpr, rect.size() * dpr);
@@ -129,21 +73,11 @@ void QWasmBackingStore::updateTexture()
r.setWidth(imageRect.width());
}
- fixed |= r;
+ clippedDpiScaledRegion |= r;
}
- for (const QRect &rect : fixed) {
- // if the sub-rect is full-width we can pass the image data directly to
- // OpenGL instead of copying, since there is no gap between scanlines
- if (rect.width() == imageRect.width()) {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- m_image.constScanLine(rect.y()));
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- m_image.copy(rect).constBits());
- }
- }
- /* End of code taken from QEGLPlatformBackingStore */
+ for (const QRect &dirtyRect : clippedDpiScaledRegion)
+ dom::drawImageToWebImageDataArray(m_image, m_webImageDataArray, dirtyRect);
m_dirty = QRegion();
}
@@ -152,28 +86,33 @@ void QWasmBackingStore::beginPaint(const QRegion &region)
{
m_dirty |= region;
// Keep backing store device pixel ratio in sync with window
- if (m_image.devicePixelRatio() != window()->devicePixelRatio())
+ if (m_image.devicePixelRatio() != window()->handle()->devicePixelRatio())
resize(backingStore()->size(), backingStore()->staticContents());
QPainter painter(&m_image);
- painter.setCompositionMode(QPainter::CompositionMode_Source);
- const QColor blank = Qt::transparent;
- for (const QRect &rect : region)
- painter.fillRect(rect, blank);
+
+ if (m_image.hasAlphaChannel()) {
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ const QColor blank = Qt::transparent;
+ for (const QRect &rect : region)
+ painter.fillRect(rect, blank);
+ }
}
void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
Q_UNUSED(staticContents);
- m_image = QImage(size * window()->devicePixelRatio(), QImage::Format_RGB32);
- m_image.setDevicePixelRatio(window()->devicePixelRatio());
- m_recreateTexture = true;
+ QImage::Format format = QImage::Format_RGBA8888;
+ const auto platformScreenDPR = window()->handle()->devicePixelRatio();
+ m_image = QImage(size * platformScreenDPR, format);
+ m_image.setDevicePixelRatio(platformScreenDPR);
+ m_webImageDataArray = emscripten::val::undefined();
}
QImage QWasmBackingStore::toImage() const
{
- // used by QPlatformBackingStore::composeAndFlush
+ // used by QPlatformBackingStore::rhiFlush
return m_image;
}
@@ -182,10 +121,10 @@ const QImage &QWasmBackingStore::getImageRef() const
return m_image;
}
-const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture()
+emscripten::val QWasmBackingStore::getUpdatedWebImage(QWasmWindow *window)
{
- updateTexture();
- return m_texture.data();
+ updateTexture(window);
+ return m_webImageDataArray;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.h b/src/plugins/platforms/wasm/qwasmbackingstore.h
index b93c96b483..54e9fe4cb3 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.h
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.h
@@ -1,31 +1,5 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMBACKINGSTORE_H
#define QWASMBACKINGSTORE_H
@@ -33,18 +7,20 @@
#include <qpa/qplatformbackingstore.h>
#include <QtGui/qimage.h>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
class QOpenGLTexture;
class QRegion;
class QWasmCompositor;
+class QWasmWindow;
class QWasmBackingStore : public QPlatformBackingStore
{
public:
QWasmBackingStore(QWasmCompositor *compositor, QWindow *window);
~QWasmBackingStore();
- void destroy();
QPaintDevice *paintDevice() override;
@@ -54,17 +30,16 @@ public:
QImage toImage() const override;
const QImage &getImageRef() const;
- const QOpenGLTexture *getUpdatedTexture();
+ emscripten::val getUpdatedWebImage(QWasmWindow *window);
protected:
- void updateTexture();
+ void updateTexture(QWasmWindow *window);
private:
QWasmCompositor *m_compositor;
QImage m_image;
- QScopedPointer<QOpenGLTexture> m_texture;
QRegion m_dirty;
- bool m_recreateTexture = false;
+ emscripten::val m_webImageDataArray = emscripten::val::undefined();
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
new file mode 100644
index 0000000000..8f05f082ea
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmbase64iconstore.h"
+
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_GLOBAL_STATIC(Base64IconStore, globalWasmWindowIconStore);
+
+Base64IconStore::Base64IconStore()
+{
+ QString iconSources[static_cast<size_t>(IconType::Size)] = {
+ QStringLiteral(":/wasm-window/maximize.svg"), QStringLiteral(":/wasm-window/qtlogo.svg"),
+ QStringLiteral(":/wasm-window/restore.svg"), QStringLiteral(":/wasm-window/x.svg")
+ };
+
+ for (size_t iconType = static_cast<size_t>(IconType::First);
+ iconType < static_cast<size_t>(IconType::Size); ++iconType) {
+ QFile svgFile(iconSources[static_cast<size_t>(iconType)]);
+ if (!svgFile.open(QIODevice::ReadOnly))
+ Q_ASSERT(false); // A resource should always be opened.
+ m_storage[static_cast<size_t>(iconType)] = svgFile.readAll().toBase64();
+ }
+}
+
+Base64IconStore::~Base64IconStore() = default;
+
+Base64IconStore *Base64IconStore::get()
+{
+ return globalWasmWindowIconStore();
+}
+
+std::string_view Base64IconStore::getIcon(IconType type) const
+{
+ return m_storage[static_cast<size_t>(type)];
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.h b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
new file mode 100644
index 0000000000..89704f2d2c
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMBASE64IMAGESTORE_H
+#define QWASMBASE64IMAGESTORE_H
+
+#include <string>
+#include <string_view>
+
+#include <QtCore/qtconfigmacros.h>
+
+QT_BEGIN_NAMESPACE
+class Base64IconStore
+{
+public:
+ enum class IconType {
+ Maximize,
+ First = Maximize,
+ QtLogo,
+ Restore,
+ X,
+ Size,
+ };
+
+ Base64IconStore();
+ ~Base64IconStore();
+
+ static Base64IconStore *get();
+
+ std::string_view getIcon(IconType type) const;
+
+private:
+ std::string m_storage[static_cast<size_t>(IconType::Size)];
+};
+
+QT_END_NAMESPACE
+#endif // QWASMBASE64IMAGESTORE_H
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 713adee8f9..1aa3ffa5b3 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -1,123 +1,79 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmclipboard.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
#include "qwasmwindow.h"
-#include "qwasmstring.h"
-#include <emscripten.h>
-#include <emscripten/html5.h>
-#include <emscripten/bind.h>
+#include <private/qstdweb_p.h>
#include <QCoreApplication>
#include <qpa/qwindowsysteminterface.h>
+#include <QBuffer>
+#include <QString>
-using namespace emscripten;
-
-// there has got to be a better way...
-static QString g_clipboardText;
-static QString g_clipboardFormat;
+#include <emscripten/val.h>
-static val getClipboardData()
-{
- return QWasmString::fromQString(g_clipboardText);
-}
+QT_BEGIN_NAMESPACE
+using namespace emscripten;
-static val getClipboardFormat()
+static void commonCopyEvent(val event)
{
- return QWasmString::fromQString(g_clipboardFormat);
-}
+ QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
+ if (!_mimes)
+ return;
+
+ // doing it this way seems to sanitize the text better that calling data() like down below
+ if (_mimes->hasText()) {
+ event["clipboardData"].call<void>("setData", val("text/plain"),
+ _mimes->text().toEcmaString());
+ }
+ if (_mimes->hasHtml()) {
+ event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
+ }
-static void pasteClipboardData(emscripten::val format, emscripten::val dataPtr)
-{
- QString formatString = QWasmString::toQString(format);
- QByteArray dataArray = QByteArray::fromStdString(dataPtr.as<std::string>());
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setData(formatString, dataArray);
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
-}
+ for (auto mimetype : _mimes->formats()) {
+ if (mimetype.contains("text/"))
+ continue;
+ QByteArray ba = _mimes->data(mimetype);
+ if (!ba.isEmpty())
+ event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
+ val(ba.constData()));
+ }
-static void qClipboardPromiseResolve(emscripten::val something)
-{
- pasteClipboardData(emscripten::val("text/plain"), something);
+ event.call<void>("preventDefault");
}
static void qClipboardCutTo(val event)
{
- if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
+ 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");
- }
+ QWindowSystemInterface::handleKeyEvent(
+ 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");
+ commonCopyEvent(event);
}
static void qClipboardCopyTo(val event)
{
- if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
+ 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");
+ QWindowSystemInterface::handleKeyEvent(
+ 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");
+ commonCopyEvent(event);
}
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", val("text"));
+ event.call<void>("preventDefault"); // prevent browser from handling drop event
- const QString qstr = QWasmString::toQString(clipdata);
- if (qstr.length() > 0) {
- QMimeData *mMimeData = new QMimeData;
- mMimeData->setText(qstr);
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
- }
+ QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
- function("qtGetClipboardData", &getClipboardData);
- function("qtGetClipboardFormat", &getClipboardFormat);
- function("qtPasteClipboardData", &pasteClipboardData);
- function("qtClipboardPromiseResolve", &qClipboardPromiseResolve);
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
@@ -126,19 +82,19 @@ EMSCRIPTEN_BINDINGS(qtClipboardModule) {
QWasmClipboard::QWasmClipboard()
{
val clipboard = val::global("navigator")["clipboard"];
- val permissions = val::global("navigator")["permissions"];
- hasClipboardApi = (!clipboard.isUndefined() && !permissions.isUndefined() && !clipboard["readText"].isUndefined());
- if (hasClipboardApi)
- initClipboardEvents();
+
+ const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
+ m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
+
+ if (m_hasClipboardApi && hasPermissionsApi)
+ initClipboardPermissions();
}
QWasmClipboard::~QWasmClipboard()
{
- g_clipboardText.clear();
- g_clipboardFormat.clear();
}
-QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode)
+QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
{
if (mode != QClipboard::Clipboard)
return nullptr;
@@ -146,17 +102,29 @@ QMimeData* QWasmClipboard::mimeData(QClipboard::Mode mode)
return QPlatformClipboard::mimeData(mode);
}
-void QWasmClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode)
+void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
{
- if (mimeData->hasText()) {
- g_clipboardFormat = mimeData->formats().at(0);
- g_clipboardText = mimeData->text();
- } else if (mimeData->hasHtml()) {
- g_clipboardFormat = mimeData->formats().at(0);
- g_clipboardText = mimeData->html();
- }
-
+ // handle setText/ setData programmatically
QPlatformClipboard::setMimeData(mimeData, mode);
+ if (m_hasClipboardApi)
+ writeToClipboardApi();
+ else
+ writeToClipboard();
+}
+
+QWasmClipboard::ProcessKeyboardResult QWasmClipboard::processKeyboard(const KeyEvent &event)
+{
+ if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
+ return ProcessKeyboardResult::Ignored;
+
+ if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
+ return ProcessKeyboardResult::Ignored;
+
+ const bool isPaste = event.key == Qt::Key_V;
+
+ return m_hasClipboardApi && !isPaste
+ ? ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
+ : ProcessKeyboardResult::NativeClipboardEventNeeded;
}
bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
@@ -170,60 +138,167 @@ bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
return false;
}
-void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData)
+void QWasmClipboard::initClipboardPermissions()
{
- QWasmIntegration::get()->clipboard()->setMimeData(mData, QClipboard::Clipboard);
+ val permissions = val::global("navigator")["permissions"];
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
+ qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
+ val readPermissionsMap = val::object();
+ readPermissionsMap.set("name", val("clipboard-read"));
+ return readPermissionsMap;
+ })());
+ qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
+ val readPermissionsMap = val::object();
+ readPermissionsMap.set("name", val("clipboard-write"));
+ return readPermissionsMap;
+ })());
}
-void QWasmClipboard::initClipboardEvents()
+void QWasmClipboard::installEventHandlers(const emscripten::val &target)
{
- if (!hasClipboardApi)
- return;
-
- val permissions = val::global("navigator")["permissions"];
- val readPermissionsMap = val::object();
- readPermissionsMap.set("name", val("clipboard-read"));
- permissions.call<val>("query", readPermissionsMap);
+ emscripten::val cContext = val::undefined();
+ emscripten::val isChromium = val::global("window")["chrome"];
+ if (!isChromium.isUndefined()) {
+ cContext = val::global("document");
+ } else {
+ cContext = target;
+ }
+ // Fallback path for browsers which do not support direct clipboard access
+ cContext.call<void>("addEventListener", val("cut"),
+ val::module_property("qtClipboardCutTo"), true);
+ cContext.call<void>("addEventListener", val("copy"),
+ val::module_property("qtClipboardCopyTo"), true);
+ cContext.call<void>("addEventListener", val("paste"),
+ val::module_property("qtClipboardPasteTo"), true);
+}
- val writePermissionsMap = val::object();
- writePermissionsMap.set("name", val("clipboard-write"));
- permissions.call<val>("query", writePermissionsMap);
+bool QWasmClipboard::hasClipboardApi()
+{
+ return m_hasClipboardApi;
}
-void QWasmClipboard::installEventHandlers(const emscripten::val &canvas)
+void QWasmClipboard::writeToClipboardApi()
{
- if (hasClipboardApi)
+ Q_ASSERT(m_hasClipboardApi);
+
+ // copy event
+ // browser event handler detected ctrl c if clipboard API
+ // or Qt call from keyboard event handler
+
+ QMimeData *_mimes = mimeData(QClipboard::Clipboard);
+ if (!_mimes)
return;
- // Fallback path for browsers which do not support direct clipboard access
- canvas.call<void>("addEventListener", val("cut"),
- val::module_property("qtClipboardCutTo"));
- canvas.call<void>("addEventListener", val("copy"),
- val::module_property("qtClipboardCopyTo"));
- canvas.call<void>("addEventListener", val("paste"),
- val::module_property("qtClipboardPasteTo"));
+ emscripten::val clipboardWriteArray = emscripten::val::array();
+ QByteArray ba;
+
+ for (auto mimetype : _mimes->formats()) {
+ // we need to treat binary and text differently, as the blob method below
+ // fails for text mimetypes
+ // ignore text types
+
+ if (mimetype.contains("STRING", Qt::CaseSensitive) || mimetype.contains("TEXT", Qt::CaseSensitive))
+ continue;
+
+ if (_mimes->hasHtml()) { // prefer html over text
+ ba = _mimes->html().toLocal8Bit();
+ // force this mime
+ mimetype = "text/html";
+ } else if (mimetype.contains("text/plain")) {
+ ba = _mimes->text().toLocal8Bit();
+ } else if (mimetype.contains("image")) {
+ QImage img = qvariant_cast<QImage>( _mimes->imageData());
+ QBuffer buffer(&ba);
+ buffer.open(QIODevice::WriteOnly);
+ img.save(&buffer, "PNG");
+ mimetype = "image/png"; // chrome only allows png
+ // clipboard error "NotAllowedError" "Type application/x-qt-image not supported on write."
+ // safari silently fails
+ // so we use png internally for now
+ } else {
+ // DATA
+ ba = _mimes->data(mimetype);
+ }
+ // Create file data Blob
+
+ const char *content = ba.data();
+ int dataLength = ba.length();
+ if (dataLength < 1) {
+ qDebug() << "no content found";
+ return;
+ }
+
+ emscripten::val document = emscripten::val::global("document");
+ emscripten::val window = emscripten::val::global("window");
+
+ emscripten::val fileContentView =
+ emscripten::val(emscripten::typed_memory_view(dataLength, content));
+ emscripten::val fileContentCopy = emscripten::val::global("ArrayBuffer").new_(dataLength);
+ emscripten::val fileContentCopyView =
+ emscripten::val::global("Uint8Array").new_(fileContentCopy);
+ fileContentCopyView.call<void>("set", fileContentView);
+
+ emscripten::val contentArray = emscripten::val::array();
+ contentArray.call<void>("push", fileContentCopyView);
+
+ // we have a blob, now create a ClipboardItem
+ emscripten::val type = emscripten::val::array();
+ type.set("type", mimetype.toEcmaString());
+
+ emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
+
+ emscripten::val clipboardItemObject = emscripten::val::object();
+ clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
+
+ val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
+
+ clipboardWriteArray.call<void>("push", clipboardItemData);
+
+ // Clipboard write is only supported with one ClipboardItem at the moment
+ // but somehow this still works?
+ // break;
+ }
+
+ val navigator = val::global("navigator");
+
+ qstdweb::Promise::make(
+ navigator["clipboard"], "write",
+ {
+ .catchFunc = [](emscripten::val error) {
+ qWarning() << "clipboard error"
+ << QString::fromStdString(error["name"].as<std::string>())
+ << QString::fromStdString(error["message"].as<std::string>());
+ }
+ },
+ clipboardWriteArray);
}
-void QWasmClipboard::readTextFromClipboard()
+void QWasmClipboard::writeToClipboard()
{
- 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);
- }
+ // this works for firefox, chrome by generating
+ // copy event, but not safari
+ // execCommand has been deemed deprecated in the docs, but browsers do not seem
+ // interested in removing it. There is no replacement, so we use it here.
+ val document = val::global("document");
+ document.call<val>("execCommand", val("copy"));
}
-void QWasmClipboard::writeTextToClipboard()
+void QWasmClipboard::sendClipboardData(emscripten::val event)
{
- 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);
- }
+ qDebug() << "sendClipboardData";
+
+ dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
+ const auto mimeCallback = std::function([transfer](QMimeData *data) {
+
+ // Persist clipboard data so that the app can read it when handling the CTRL+V
+ QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ delete transfer;
+ });
+
+ transfer->toMimeDataWithFile(mimeCallback);
}
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.h b/src/plugins/platforms/wasm/qwasmclipboard.h
index 3b28e2c381..86618dd560 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.h
+++ b/src/plugins/platforms/wasm/qwasmclipboard.h
@@ -1,31 +1,5 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWasmClipboard_H
#define QWasmClipboard_H
@@ -33,14 +7,25 @@
#include <QObject>
#include <qpa/qplatformclipboard.h>
+#include <private/qstdweb_p.h>
#include <QMimeData>
#include <emscripten/bind.h>
#include <emscripten/val.h>
+QT_BEGIN_NAMESPACE
+
+struct KeyEvent;
+
class QWasmClipboard : public QObject, public QPlatformClipboard
{
public:
+ enum class ProcessKeyboardResult {
+ Ignored,
+ NativeClipboardEventNeeded,
+ NativeClipboardEventAndCopiedDataNeeded,
+ };
+
QWasmClipboard();
virtual ~QWasmClipboard();
@@ -50,12 +35,19 @@ public:
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
- static void qWasmClipboardPaste(QMimeData *mData);
- void initClipboardEvents();
- void installEventHandlers(const emscripten::val &canvas);
- bool hasClipboardApi;
- void readTextFromClipboard();
- void writeTextToClipboard();
+ ProcessKeyboardResult processKeyboard(const KeyEvent &event);
+ static void installEventHandlers(const emscripten::val &target);
+ bool hasClipboardApi();
+ void sendClipboardData(emscripten::val event);
+
+private:
+ void initClipboardPermissions();
+ void writeToClipboardApi();
+ void writeToClipboard();
+
+ bool m_hasClipboardApi = false;
};
+QT_END_NAMESPACE
+
#endif // QWASMCLIPBOARD_H
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp
index 4ea69a3e78..ef460f666f 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp
@@ -1,96 +1,43 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmcompositor.h"
#include "qwasmwindow.h"
-#include "qwasmstylepixmaps_p.h"
-#include <QtOpenGL/qopengltexture.h>
-
-#include <QtGui/private/qwindow_p.h>
-#include <QtGui/qopenglcontext.h>
-#include <QtGui/qopenglfunctions.h>
-#include <QtGui/qoffscreensurface.h>
-#include <QtGui/qpainter.h>
-#include <private/qpixmapcache_p.h>
-
-#include <private/qguiapplication_p.h>
+#include <private/qeventdispatcher_wasm_p.h>
#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtGui/qguiapplication.h>
-Q_GUI_EXPORT int qt_defaultDpiX();
+#include <emscripten/html5.h>
-QWasmCompositedWindow::QWasmCompositedWindow()
- : window(nullptr)
- , parentWindow(nullptr)
- , flushPending(false)
- , visible(false)
-{
-}
+using namespace emscripten;
+
+bool QWasmCompositor::m_requestUpdateHoldEnabled = true;
-QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
- :QObject(screen)
- , m_blitter(new QOpenGLTextureBlitter)
- , m_needComposit(false)
- , m_inFlush(false)
- , m_inResize(false)
- , m_isEnabled(true)
- , m_targetDevicePixelRatio(1)
+QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen)
{
+ QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
}
QWasmCompositor::~QWasmCompositor()
{
- destroy();
+ if (m_requestAnimationFrameId != -1)
+ emscripten_cancel_animation_frame(m_requestAnimationFrameId);
+
+ // TODO(mikolaj.boc): Investigate if m_isEnabled is needed at all. It seems like a frame should
+ // not be generated after this instead.
+ m_isEnabled = false; // prevent frame() from creating a new m_context
}
-void QWasmCompositor::destroy()
+void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindow *window)
{
- // Destroy OpenGL resources. This is done here in a separate function
- // which can be called while screen() still returns a valid screen
- // (which it might not, during destruction). A valid QScreen is
- // a requirement for QOffscreenSurface on Wasm since the native
- // context is tied to a single canvas.
- if (m_context) {
- QOffscreenSurface offScreenSurface(screen()->screen());
- offScreenSurface.setFormat(m_context->format());
- offScreenSurface.create();
- m_context->makeCurrent(&offScreenSurface);
- for (QWasmWindow *window : m_windowStack)
- window->destroy();
- m_blitter.reset(nullptr);
- m_context.reset(nullptr);
- }
-
- m_isEnabled = false; // prevent frame() from creating a new m_context
+ auto allWindows = screen()->allWindows();
+ setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) {
+ return !element->context2d().isUndefined();
+ }));
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval)
+ m_requestUpdateWindows.remove(window);
}
void QWasmCompositor::setEnabled(bool enabled)
@@ -98,656 +45,115 @@ void QWasmCompositor::setEnabled(bool enabled)
m_isEnabled = enabled;
}
-void QWasmCompositor::addWindow(QWasmWindow *window, QWasmWindow *parentWindow)
+// requestUpdate delivery is initially disabled at startup, while Qt completes
+// startup tasks such as font loading. This function enables requestUpdate delivery
+// again.
+bool QWasmCompositor::releaseRequestUpdateHold()
{
- QWasmCompositedWindow compositedWindow;
- compositedWindow.window = window;
- compositedWindow.parentWindow = parentWindow;
- m_compositedWindows.insert(window, compositedWindow);
-
- if (parentWindow == 0)
- m_windowStack.append(window);
- else
- m_compositedWindows[parentWindow].childWindows.append(window);
-
- notifyTopWindowChanged(window);
+ const bool wasEnabled = m_requestUpdateHoldEnabled;
+ m_requestUpdateHoldEnabled = false;
+ return wasEnabled;
}
-void QWasmCompositor::removeWindow(QWasmWindow *window)
+void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
- QWasmWindow *platformWindow = m_compositedWindows[window].parentWindow;
-
- if (platformWindow) {
- QWasmWindow *parentWindow = window;
- m_compositedWindows[parentWindow].childWindows.removeAll(window);
+ auto it = m_requestUpdateWindows.find(window);
+ if (it == m_requestUpdateWindows.end()) {
+ m_requestUpdateWindows.insert(window, updateType);
+ } else {
+ // Already registered, but upgrade ExposeEventDeliveryType to UpdateRequestDeliveryType.
+ // if needed, to make sure QWindow::updateRequest's are matched.
+ if (it.value() == ExposeEventDelivery && updateType == UpdateRequestDelivery)
+ it.value() = UpdateRequestDelivery;
}
- m_windowStack.removeAll(window);
- m_compositedWindows.remove(window);
-
- notifyTopWindowChanged(window);
+ requestUpdate();
}
-void QWasmCompositor::setVisible(QWasmWindow *window, bool visible)
+// Requests an update/new frame using RequestAnimationFrame
+void QWasmCompositor::requestUpdate()
{
- QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
- if (compositedWindow.visible == visible)
+ if (m_requestAnimationFrameId != -1)
return;
- compositedWindow.visible = visible;
- compositedWindow.flushPending = true;
- if (visible)
- compositedWindow.damage = compositedWindow.window->geometry();
- else
- m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area.
-
- requestRedraw();
-}
-
-void QWasmCompositor::raise(QWasmWindow *window)
-{
- if (m_compositedWindows.size() <= 1)
- return;
-
- QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
- compositedWindow.damage = compositedWindow.window->geometry();
- m_windowStack.removeAll(window);
- m_windowStack.append(window);
-
- notifyTopWindowChanged(window);
-}
-
-void QWasmCompositor::lower(QWasmWindow *window)
-{
- if (m_compositedWindows.size() <= 1)
+ if (m_requestUpdateHoldEnabled)
return;
- m_windowStack.removeAll(window);
- m_windowStack.prepend(window);
- QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
- m_globalDamage = compositedWindow.window->geometry(); // repaint previosly covered area.
-
- notifyTopWindowChanged(window);
-}
-
-void QWasmCompositor::setParent(QWasmWindow *window, QWasmWindow *parent)
-{
- m_compositedWindows[window].parentWindow = parent;
-
- requestRedraw();
-}
-
-void QWasmCompositor::flush(QWasmWindow *window, const QRegion &region)
-{
- QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
- compositedWindow.flushPending = true;
- compositedWindow.damage = region;
+ static auto frame = [](double frameTime, void *context) -> int {
+ Q_UNUSED(frameTime);
- requestRedraw();
-}
+ QWasmCompositor *compositor = reinterpret_cast<QWasmCompositor *>(context);
-int QWasmCompositor::windowCount() const
-{
- return m_windowStack.count();
-}
+ compositor->m_requestAnimationFrameId = -1;
+ compositor->deliverUpdateRequests();
-
-void QWasmCompositor::redrawWindowContent()
-{
- // Redraw window content by sending expose events. This redraw
- // will cause a backing store flush, which will call requestRedraw()
- // to composit.
- for (QWasmWindow *platformWindow : m_windowStack) {
- QWindow *window = platformWindow->window();
- QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(
- window, QRect(QPoint(0, 0), window->geometry().size()));
- }
-}
-
-void QWasmCompositor::requestRedraw()
-{
- if (m_needComposit)
- return;
-
- m_needComposit = true;
- QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
-}
-
-QWindow *QWasmCompositor::windowAt(QPoint globalPoint, int padding) const
-{
- int index = m_windowStack.count() - 1;
- // qDebug() << "window at" << "point" << p << "window count" << index;
-
- while (index >= 0) {
- const QWasmCompositedWindow &compositedWindow = m_compositedWindows[m_windowStack.at(index)];
- //qDebug() << "windwAt testing" << compositedWindow.window <<
-
- QRect geometry = compositedWindow.window->windowFrameGeometry()
- .adjusted(-padding, -padding, padding, padding);
-
- if (compositedWindow.visible && geometry.contains(globalPoint))
- return m_windowStack.at(index)->window();
- --index;
- }
-
- return 0;
-}
-
-QWindow *QWasmCompositor::keyWindow() const
-{
- return m_windowStack.at(m_windowStack.count() - 1)->window();
-}
-
-bool QWasmCompositor::event(QEvent *ev)
-{
- if (ev->type() == QEvent::UpdateRequest) {
- if (m_isEnabled)
- frame();
- return true;
- }
-
- return QObject::event(ev);
-}
-
-void QWasmCompositor::blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry)
-{
- QMatrix4x4 m;
- m.translate(-1.0f, -1.0f);
-
- m.scale(2.0f / (float)screen->geometry().width(),
- 2.0f / (float)screen->geometry().height());
-
- m.translate((float)targetGeometry.width() / 2.0f,
- (float)-targetGeometry.height() / 2.0f);
-
- m.translate(targetGeometry.x(), screen->geometry().height() - targetGeometry.y());
-
- m.scale(0.5f * (float)targetGeometry.width(),
- 0.5f * (float)targetGeometry.height());
-
- blitter->blit(texture->textureId(), m, QOpenGLTextureBlitter::OriginTopLeft);
-}
-
-void QWasmCompositor::drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
-{
- QWasmBackingStore *backingStore = window->backingStore();
- if (!backingStore)
- return;
-
- QOpenGLTexture const *texture = backingStore->getUpdatedTexture();
- QPoint windowCanvasPosition = window->geometry().topLeft() - screen->geometry().topLeft();
- QRect windowCanvasGeometry = QRect(windowCanvasPosition, window->geometry().size());
- blit(blitter, screen, texture, windowCanvasGeometry);
-}
-
-QPalette QWasmCompositor::makeWindowPalette()
-{
- QPalette palette;
- palette.setColor(QPalette::Active, QPalette::Highlight,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Active, QPalette::Base,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Inactive, QPalette::Highlight,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::Base,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::HighlightedText,
- palette.color(QPalette::Inactive, QPalette::Window));
-
- return palette;
-}
-
-QRect QWasmCompositor::titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol)
-{
- QRect ret;
- const int controlMargin = 2;
- const int controlHeight = tb.rect.height() - controlMargin *2;
- const int delta = controlHeight + controlMargin;
- int offset = 0;
-
- bool isMinimized = tb.state & Qt::WindowMinimized;
- bool isMaximized = tb.state & Qt::WindowMaximized;
-
- ret = tb.rect;
- switch (subcontrol) {
- case SC_TitleBarLabel:
- if (tb.flags & Qt::WindowSystemMenuHint)
- ret.adjust(delta, 0, -delta, 0);
- break;
- case SC_TitleBarCloseButton:
- if (tb.flags & Qt::WindowSystemMenuHint) {
- ret.adjust(0, 0, -delta, 0);
- offset += delta;
- }
- break;
- case SC_TitleBarMaxButton:
- if (!isMaximized && tb.flags & Qt::WindowMaximizeButtonHint) {
- ret.adjust(0, 0, -delta*2, 0);
- offset += (delta +delta);
- }
- break;
- case SC_TitleBarNormalButton:
- if (isMinimized && (tb.flags & Qt::WindowMinimizeButtonHint)) {
- offset += delta;
- } else if (isMaximized && (tb.flags & Qt::WindowMaximizeButtonHint)) {
- ret.adjust(0, 0, -delta*2, 0);
- offset += (delta +delta);
- }
- break;
- case SC_TitleBarSysMenu:
- if (tb.flags & Qt::WindowSystemMenuHint) {
- ret.setRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin,
- controlHeight, controlHeight);
- }
- break;
- default:
- break;
+ return 0;
};
-
- if (subcontrol != SC_TitleBarLabel && subcontrol != SC_TitleBarSysMenu) {
- ret.setRect(tb.rect.right() - offset, tb.rect.top() + controlMargin,
- controlHeight, controlHeight);
- }
-
- if (qApp->layoutDirection() == Qt::LeftToRight)
- return ret;
-
- QRect rect = ret;
- rect.translate(2 * (tb.rect.right() - ret.right()) +
- ret.width() - tb.rect.width(), 0);
-
- return rect;
+ m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this);
}
-int dpiScaled(qreal value)
+void QWasmCompositor::deliverUpdateRequests()
{
- return value * (qreal(qt_defaultDpiX()) / 96.0);
-}
-
-QWasmCompositor::QWasmTitleBarOptions QWasmCompositor::makeTitleBarOptions(const QWasmWindow *window)
-{
- int width = window->windowFrameGeometry().width();
- int border = window->borderWidth();
-
- QWasmTitleBarOptions titleBarOptions;
-
- titleBarOptions.rect = QRect(border, border, width - 2 * border, window->titleHeight());
- titleBarOptions.flags = window->window()->flags();
- titleBarOptions.state = window->window()->windowState();
+ // We may get new update requests during the window content update below:
+ // prepare for recording the new update set by setting aside the current
+ // update set.
+ auto requestUpdateWindows = m_requestUpdateWindows;
+ m_requestUpdateWindows.clear();
- bool isMaximized = titleBarOptions.state & Qt::WindowMaximized; // this gets reset when maximized
-
- if (titleBarOptions.flags & (Qt::WindowTitleHint))
- titleBarOptions.subControls |= SC_TitleBarLabel;
- if (titleBarOptions.flags & Qt::WindowMaximizeButtonHint) {
- if (isMaximized)
- titleBarOptions.subControls |= SC_TitleBarNormalButton;
- else
- titleBarOptions.subControls |= SC_TitleBarMaxButton;
- }
- if (titleBarOptions.flags & Qt::WindowSystemMenuHint) {
- titleBarOptions.subControls |= SC_TitleBarCloseButton;
- titleBarOptions.subControls |= SC_TitleBarSysMenu;
+ // Update window content, either all windows or a spesific set of windows. Use the correct
+ // update type: QWindow subclasses expect that requested and delivered updateRequests matches
+ // exactly.
+ m_inDeliverUpdateRequest = true;
+ for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
+ auto *window = it.key();
+ UpdateRequestDeliveryType updateType = it.value();
+ deliverUpdateRequest(window, updateType);
}
-
- titleBarOptions.palette = QWasmCompositor::makeWindowPalette();
-
- if (window->window()->isActive())
- titleBarOptions.palette.setCurrentColorGroup(QPalette::Active);
- else
- titleBarOptions.palette.setCurrentColorGroup(QPalette::Inactive);
-
- if (window->activeSubControl() != QWasmCompositor::SC_None)
- titleBarOptions.subControls = window->activeSubControl();
-
- if (!window->window()->title().isEmpty())
- titleBarOptions.titleBarOptionsString = window->window()->title();
-
- return titleBarOptions;
-}
-
-void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
-{
- int width = window->windowFrameGeometry().width();
- int height = window->windowFrameGeometry().height();
- qreal dpr = window->devicePixelRatio();
-
- QImage image(QSize(width * dpr, height * dpr), QImage::Format_RGB32);
- image.setDevicePixelRatio(dpr);
- QPainter painter(&image);
- painter.fillRect(QRect(0, 0, width, height), painter.background());
-
- QWasmTitleBarOptions titleBarOptions = makeTitleBarOptions(window);
-
- drawTitlebarWindow(titleBarOptions, &painter);
-
- QWasmFrameOptions frameOptions;
- frameOptions.rect = QRect(0, 0, width, height);
- frameOptions.lineWidth = dpiScaled(4.);
-
- drawFrameWindow(frameOptions, &painter);
-
- painter.end();
-
- QOpenGLTexture texture(QOpenGLTexture::Target2D);
- texture.setMinificationFilter(QOpenGLTexture::Nearest);
- texture.setMagnificationFilter(QOpenGLTexture::Nearest);
- texture.setWrapMode(QOpenGLTexture::ClampToEdge);
- texture.setData(image, QOpenGLTexture::DontGenerateMipMaps);
- texture.create();
- texture.bind();
-
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- image.constScanLine(0));
-
- blit(blitter, screen, &texture, QRect(window->windowFrameGeometry().topLeft(), QSize(width, height)));
-}
-
-void QWasmCompositor::drawFrameWindow(QWasmFrameOptions options, QPainter *painter)
-{
- int x = options.rect.x();
- int y = options.rect.y();
- int w = options.rect.width();
- int h = options.rect.height();
- const QColor &c1 = options.palette.light().color();
- const QColor &c2 = options.palette.shadow().color();
- const QColor &c3 = options.palette.midlight().color();
- const QColor &c4 = options.palette.dark().color();
- const QBrush *fill = 0;
-
- const qreal devicePixelRatio = painter->device()->devicePixelRatioF();
- if (!qFuzzyCompare(devicePixelRatio, qreal(1))) {
- const qreal inverseScale = qreal(1) / devicePixelRatio;
- painter->scale(inverseScale, inverseScale);
- x = qRound(devicePixelRatio * x);
- y = qRound(devicePixelRatio * y);
- w = qRound(devicePixelRatio * w);
- h = qRound(devicePixelRatio * h);
- }
-
- QPen oldPen = painter->pen();
- QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) };
- painter->setPen(c1);
- painter->drawPolyline(a, 3);
- QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) };
- painter->setPen(c2);
- painter->drawPolyline(b, 3);
- if (w > 4 && h > 4) {
- QPoint c[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) };
- painter->setPen(c3);
- painter->drawPolyline(c, 3);
- QPoint d[3] = { QPoint(x+1, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y+1) };
- painter->setPen(c4);
- painter->drawPolyline(d, 3);
- if (fill)
- painter->fillRect(QRect(x+2, y+2, w-4, h-4), *fill);
- }
- painter->setPen(oldPen);
-}
-
-//from commonstyle.cpp
-static QPixmap cachedPixmapFromXPM(const char * const *xpm)
-{
- QPixmap result;
- const QString tag = QString::asprintf("xpm:0x%p", static_cast<const void*>(xpm));
- if (!QPixmapCache::find(tag, &result)) {
- result = QPixmap(xpm);
- QPixmapCache::insert(tag, result);
- }
- return result;
-}
-
-void QWasmCompositor::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
- const QPixmap &pixmap) const
-{
- qreal scale = pixmap.devicePixelRatio();
- QSize size = pixmap.size() / scale;
- int x = rect.x();
- int y = rect.y();
- int w = size.width();
- int h = size.height();
- if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter)
- y += rect.size().height()/2 - h/2;
- else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom)
- y += rect.size().height() - h;
- if ((alignment & Qt::AlignRight) == Qt::AlignRight)
- x += rect.size().width() - w;
- else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter)
- x += rect.size().width()/2 - w/2;
-
- QRect aligned = QRect(x, y, w, h);
- QRect inter = aligned.intersected(rect);
-
- painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width() * scale, inter.height() *scale);
-}
-
-
-void QWasmCompositor::drawTitlebarWindow(QWasmTitleBarOptions tb, QPainter *painter)
-{
- QRect ir;
- if (tb.subControls.testFlag(SC_TitleBarLabel)) {
- QColor left = tb.palette.highlight().color();
- QColor right = tb.palette.base().color();
-
- QBrush fillBrush(left);
- if (left != right) {
- QPoint p1(tb.rect.x(), tb.rect.top() + tb.rect.height()/2);
- QPoint p2(tb.rect.right(), tb.rect.top() + tb.rect.height()/2);
- QLinearGradient lg(p1, p2);
- lg.setColorAt(0, left);
- lg.setColorAt(1, right);
- fillBrush = lg;
- }
-
- painter->fillRect(tb.rect, fillBrush);
- ir = titlebarRect(tb, SC_TitleBarLabel);
- painter->setPen(tb.palette.highlightedText().color());
- painter->drawText(ir.x() + 2, ir.y(), ir.width() - 2, ir.height(),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb.titleBarOptionsString);
- } // SC_TitleBarLabel
-
- bool down = false;
- QPixmap pixmap;
-
- if (tb.subControls.testFlag(SC_TitleBarCloseButton)
- && tb.flags & Qt::WindowSystemMenuHint) {
- ir = titlebarRect(tb, SC_TitleBarCloseButton);
- down = tb.subControls & SC_TitleBarCloseButton && (tb.state & State_Sunken);
- pixmap = cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } //SC_TitleBarCloseButton
-
- if (tb.subControls.testFlag(SC_TitleBarMaxButton)
- && tb.flags & Qt::WindowMaximizeButtonHint
- && !(tb.state & Qt::WindowMaximized)) {
- ir = titlebarRect(tb, SC_TitleBarMaxButton);
- down = tb.subControls & SC_TitleBarMaxButton && (tb.state & State_Sunken);
- pixmap = cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } //SC_TitleBarMaxButton
-
- bool drawNormalButton = (tb.subControls & SC_TitleBarNormalButton)
- && (((tb.flags & Qt::WindowMinimizeButtonHint)
- && (tb.flags & Qt::WindowMinimized))
- || ((tb.flags & Qt::WindowMaximizeButtonHint)
- && (tb.flags & Qt::WindowMaximized)));
-
- if (drawNormalButton) {
- ir = titlebarRect(tb, SC_TitleBarNormalButton);
- down = tb.subControls & SC_TitleBarNormalButton && (tb.state & State_Sunken);
- pixmap = cachedPixmapFromXPM(qt_normalizeup_xpm).scaled( QSize(10, 10));
-
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } // SC_TitleBarNormalButton
-
- if (tb.subControls & SC_TitleBarSysMenu && tb.flags & Qt::WindowSystemMenuHint) {
- ir = titlebarRect(tb, SC_TitleBarSysMenu);
- pixmap = cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- }
+ m_inDeliverUpdateRequest = false;
+ frame(requestUpdateWindows.keys());
}
-void QWasmCompositor::drawShadePanel(QWasmTitleBarOptions options, QPainter *painter)
+void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
- int lineWidth = 1;
- QPalette palette = options.palette;
- const QBrush *fill = &options.palette.brush(QPalette::Button);
-
- int x = options.rect.x();
- int y = options.rect.y();
- int w = options.rect.width();
- int h = options.rect.height();
-
- const qreal devicePixelRatio = painter->device()->devicePixelRatioF();
- if (!qFuzzyCompare(devicePixelRatio, qreal(1))) {
- const qreal inverseScale = qreal(1) / devicePixelRatio;
- painter->scale(inverseScale, inverseScale);
-
- x = qRound(devicePixelRatio * x);
- y = qRound(devicePixelRatio * y);
- w = qRound(devicePixelRatio * w);
- h = qRound(devicePixelRatio * h);
- lineWidth = qRound(devicePixelRatio * lineWidth);
- }
-
- QColor shade = palette.dark().color();
- QColor light = palette.light().color();
-
- if (fill) {
- if (fill->color() == shade)
- shade = palette.shadow().color();
- if (fill->color() == light)
- light = palette.midlight().color();
- }
- QPen oldPen = painter->pen();
- QList<QLineF> lines;
- lines.reserve(2*lineWidth);
-
- painter->setPen(light);
- int x1, y1, x2, y2;
- int i;
- x1 = x;
- y1 = y2 = y;
- x2 = x + w - 2;
- for (i = 0; i < lineWidth; i++) // top shadow
- lines << QLineF(x1, y1++, x2--, y2++);
+ QWindow *qwindow = window->window();
- x2 = x1;
- y1 = y + h - 2;
- for (i = 0; i < lineWidth; i++) // left shado
- lines << QLineF(x1++, y1, x2++, y2--);
+ // Make sure the DPR value for the window is up to date on expose/repaint.
+ // FIXME: listen to native DPR change events instead, if/when available.
+ QWindowSystemInterface::handleWindowDevicePixelRatioChanged(qwindow);
- painter->drawLines(lines);
- lines.clear();
- painter->setPen(shade);
- x1 = x;
- y1 = y2 = y+h-1;
- x2 = x+w-1;
- for (i=0; i<lineWidth; i++) { // bottom shadow
- lines << QLineF(x1++, y1--, x2, y2--);
+ // Update by deliverUpdateRequest and expose event according to requested update
+ // type. If the window has not yet been exposed then we must expose it first regardless
+ // of update type. The deliverUpdateRequest must still be sent in this case in order
+ // to maintain correct window update state.
+ QRect updateRect(QPoint(0, 0), qwindow->geometry().size());
+ if (updateType == UpdateRequestDelivery) {
+ if (qwindow->isExposed() == false)
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
+ window->deliverUpdateRequest();
+ } else {
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
}
- x1 = x2;
- y1 = y;
- y2 = y + h - lineWidth - 1;
- for (i = 0; i < lineWidth; i++) // right shadow
- lines << QLineF(x1--, y1++, x2--, y2);
-
- painter->drawLines(lines);
- if (fill) // fill with fill color
- painter->fillRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2, *fill);
- painter->setPen(oldPen); // restore pen
-
}
-void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
+void QWasmCompositor::handleBackingStoreFlush(QWindow *window)
{
- if (window->window()->type() != Qt::Popup && !(window->m_windowState & Qt::WindowFullScreen))
- drawWindowDecorations(blitter, screen, window);
- drawWindowContent(blitter, screen, window);
+ // Request update to flush the updated backing store content, unless we are currently
+ // processing an update, in which case the new content will flushed as a part of that update.
+ if (!m_inDeliverUpdateRequest)
+ requestUpdateWindow(static_cast<QWasmWindow *>(window->handle()));
}
-void QWasmCompositor::frame()
+void QWasmCompositor::frame(const QList<QWasmWindow *> &windows)
{
- if (!m_needComposit)
- return;
-
- m_needComposit = false;
-
- if (!m_isEnabled || m_windowStack.empty() || !screen())
+ if (!m_isEnabled || !screen())
return;
- QWasmWindow *someWindow = nullptr;
-
- for (QWasmWindow *window : qAsConst(m_windowStack)) {
- if (window->window()->surfaceClass() == QSurface::Window
- && qt_window_private(static_cast<QWindow *>(window->window()))->receivedExpose) {
- someWindow = window;
- break;
- }
- }
-
- if (!someWindow)
- return;
-
- if (m_context.isNull()) {
- m_context.reset(new QOpenGLContext());
- m_context->setFormat(someWindow->window()->requestedFormat());
- m_context->setScreen(screen()->screen());
- m_context->create();
- }
-
- bool ok = m_context->makeCurrent(someWindow->window());
- if (!ok)
- return;
-
- if (!m_blitter->isCreated())
- m_blitter->create();
-
- 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);
-
- m_blitter->bind();
- m_blitter->setRedBlueSwizzle(true);
-
- for (QWasmWindow *window : qAsConst(m_windowStack)) {
- QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
-
- if (!compositedWindow.visible)
- continue;
-
- drawWindow(m_blitter.data(), screen(), window);
- }
-
- m_blitter->release();
-
- if (someWindow && someWindow->window()->surfaceType() == QSurface::OpenGLSurface)
- m_context->swapBuffers(someWindow->window());
-}
-
-void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window)
-{
- QWindow *modalWindow;
- bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow);
-
- if (blocked) {
- raise(static_cast<QWasmWindow*>(modalWindow->handle()));
- return;
- }
-
- requestRedraw();
- QWindowSystemInterface::handleWindowActivated(window->window());
+ for (QWasmWindow *window : windows)
+ window->paint();
}
QWasmScreen *QWasmCompositor::screen()
{
return static_cast<QWasmScreen *>(parent());
}
-
-QOpenGLContext *QWasmCompositor::context()
-{
- return m_context.data();
-}
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h
index a07c747a98..4953d65233 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.h
+++ b/src/plugins/platforms/wasm/qwasmcompositor.h
@@ -1,166 +1,59 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 or (at your option) any later version
-** approved by the KDE Free Qt Foundation. The licenses are as published by
-** the Free Software Foundation and appearing in the file LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMCOMPOSITOR_H
#define QWASMCOMPOSITOR_H
-#include <QtGui/qregion.h>
+#include "qwasmwindowstack.h"
+
#include <qpa/qplatformwindow.h>
-#include <QtOpenGL/qopengltextureblitter.h>
-#include <QtGui/qpalette.h>
-#include <QtGui/qpainter.h>
+#include <QMap>
QT_BEGIN_NAMESPACE
class QWasmWindow;
class QWasmScreen;
-class QOpenGLContext;
-class QOpenGLTexture;
-class QWasmCompositedWindow
-{
-public:
- QWasmCompositedWindow();
-
- QWasmWindow *window;
- QWasmWindow *parentWindow;
- QRegion damage;
- bool flushPending;
- bool visible;
- QList<QWasmWindow *> childWindows;
-};
+enum class QWasmWindowTreeNodeChangeType;
-class QWasmCompositor : public QObject
+class QWasmCompositor final : public QObject
{
Q_OBJECT
public:
QWasmCompositor(QWasmScreen *screen);
- ~QWasmCompositor();
- void destroy();
-
- enum QWasmSubControl {
- SC_None = 0x00000000,
- SC_TitleBarSysMenu = 0x00000001,
- SC_TitleBarMinButton = 0x00000002,
- SC_TitleBarMaxButton = 0x00000004,
- SC_TitleBarCloseButton = 0x00000008,
- SC_TitleBarNormalButton = 0x00000010,
- SC_TitleBarLabel = 0x00000100
- };
- Q_DECLARE_FLAGS(SubControls, QWasmSubControl)
-
- enum QWasmStateFlag {
- State_None = 0x00000000,
- State_Enabled = 0x00000001,
- State_Raised = 0x00000002,
- State_Sunken = 0x00000004
- };
- Q_DECLARE_FLAGS(StateFlags, QWasmStateFlag)
-
- struct QWasmTitleBarOptions {
- QRect rect;
- Qt::WindowFlags flags;
- int state;
- QPalette palette;
- QString titleBarOptionsString;
- QWasmCompositor::SubControls subControls;
- };
-
- struct QWasmFrameOptions {
- QRect rect;
- int lineWidth;
- QPalette palette;
- };
-
- void setEnabled(bool enabled);
-
- void addWindow(QWasmWindow *window, QWasmWindow *parentWindow = nullptr);
- void removeWindow(QWasmWindow *window);
+ ~QWasmCompositor() final;
void setVisible(QWasmWindow *window, bool visible);
- void raise(QWasmWindow *window);
- void lower(QWasmWindow *window);
- void setParent(QWasmWindow *window, QWasmWindow *parent);
- void flush(QWasmWindow *surface, const QRegion &region);
+ void onScreenDeleting();
- int windowCount() const;
+ QWasmScreen *screen();
+ void setEnabled(bool enabled);
- void redrawWindowContent();
- void requestRedraw();
+ static bool releaseRequestUpdateHold();
- QWindow *windowAt(QPoint globalPoint, int padding = 0) const;
- QWindow *keyWindow() const;
+ void requestUpdate();
+ enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
+ void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
- bool event(QEvent *event);
+ void handleBackingStoreFlush(QWindow *window);
+ void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window);
- static QWasmTitleBarOptions makeTitleBarOptions(const QWasmWindow *window);
- static QRect titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol);
+private:
+ void frame(const QList<QWasmWindow *> &windows);
- QWasmScreen *screen();
- QOpenGLContext *context();
+ void deregisterEventHandlers();
-private slots:
- void frame();
+ void deliverUpdateRequests();
+ void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
-private:
- void notifyTopWindowChanged(QWasmWindow *window);
- void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
- void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
- void blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry);
-
- void drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
- void drwPanelButton();
-
- QScopedPointer<QOpenGLContext> m_context;
- QScopedPointer<QOpenGLTextureBlitter> m_blitter;
-
- QHash<QWasmWindow *, QWasmCompositedWindow> m_compositedWindows;
- QList<QWasmWindow *> m_windowStack;
- QRegion m_globalDamage; // damage caused by expose, window close, etc.
- bool m_needComposit;
- bool m_inFlush;
- bool m_inResize;
- bool m_isEnabled;
- QSize m_targetSize;
- qreal m_targetDevicePixelRatio;
-
- static QPalette makeWindowPalette();
-
- void drawFrameWindow(QWasmFrameOptions options, QPainter *painter);
- void drawTitlebarWindow(QWasmTitleBarOptions options, QPainter *painter);
- void drawShadePanel(QWasmTitleBarOptions options, QPainter *painter);
- void drawItemPixmap(QPainter *painter, const QRect &rect,
- int alignment, const QPixmap &pixmap) const;
+ bool m_isEnabled = true;
+ QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
+ int m_requestAnimationFrameId = -1;
+ bool m_inDeliverUpdateRequest = false;
+ static bool m_requestUpdateHoldEnabled;
};
-Q_DECLARE_OPERATORS_FOR_FLAGS(QWasmCompositor::SubControls)
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
new file mode 100644
index 0000000000..e0e1a99f48
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
@@ -0,0 +1,248 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmcssstyle.h"
+
+#include "qwasmbase64iconstore.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+const char *Style = R"css(
+.qt-screen {
+ --border-width: 4px;
+ --resize-outline-width: 8px;
+ --resize-outline-half-width: var(--resize-outline-width) / 2;
+
+ position: relative;
+ border: none;
+ caret-color: transparent;
+ cursor: default;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.qt-screen div {
+ touch-action: none;
+}
+
+.qt-window {
+ position: absolute;
+ background-color: lightgray;
+}
+
+.qt-window-contents {
+ overflow: hidden;
+ position: relative;
+}
+
+.qt-window.transparent-for-input {
+ pointer-events: none;
+}
+
+.qt-window.has-shadow {
+ box-shadow: rgb(0 0 0 / 20%) 0px 10px 16px 0px, rgb(0 0 0 / 19%) 0px 6px 20px 0px;
+}
+
+.qt-window.has-border {
+ border: var(--border-width) solid lightgray;
+ caret-color: transparent;
+}
+
+.qt-window.frameless {
+ background-color: transparent;
+}
+
+.resize-outline {
+ position: absolute;
+ display: none;
+}
+
+.qt-window.no-resize > .resize-outline { display: none; }
+
+.qt-window.has-border:not(.maximized):not(.no-resize) .resize-outline {
+ display: block;
+}
+
+.resize-outline.nw {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nwse-resize;
+}
+
+.resize-outline.n {
+ left: var(--resize-outline-half-width);
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ height: var(--resize-outline-width);
+ width: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ cursor: ns-resize;
+}
+
+.resize-outline.ne {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nesw-resize;
+}
+
+.resize-outline.w {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: 0;
+ height: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ width: var(--resize-outline-width);
+ cursor: ew-resize;
+}
+
+.resize-outline.e {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: 0;
+ height: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ width: var(--resize-outline-width);
+ cursor: ew-resize;
+}
+
+.resize-outline.sw {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nesw-resize;
+}
+
+.resize-outline.s {
+ left: var(--resize-outline-half-width);
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ height: var(--resize-outline-width);
+ width: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ cursor: ns-resize;
+}
+
+.resize-outline.se {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nwse-resize;
+}
+
+.title-bar {
+ display: none;
+ align-items: center;
+ overflow: hidden;
+ height: 18px;
+ padding-bottom: 4px;
+}
+
+.qt-window.has-border > .title-bar {
+ display: flex;
+}
+
+.title-bar .window-name {
+ display: none;
+ font-family: 'Lucida Grande';
+ white-space: nowrap;
+ user-select: none;
+ overflow: hidden;
+}
+
+
+.qt-window.has-title .title-bar .window-name {
+ display: block;
+}
+
+.title-bar .spacer {
+ flex-grow: 1
+}
+
+.qt-window.inactive .title-bar {
+ opacity: 0.35;
+}
+
+.qt-window-canvas-container {
+ display: flex;
+ pointer-events: none;
+}
+
+.title-bar div {
+ pointer-events: none;
+}
+
+.qt-window-a11y-container {
+ position: absolute;
+ z-index: -1;
+}
+
+.title-bar .image-button {
+ width: 18px;
+ height: 18px;
+ display: flex;
+ justify-content: center;
+ user-select: none;
+ align-items: center;
+}
+
+.title-bar .image-button img {
+ width: 10px;
+ height: 10px;
+ user-select: none;
+ pointer-events: none;
+ -webkit-user-drag: none;
+ background-size: 10px 10px;
+}
+
+.title-bar .action-button {
+ pointer-events: all;
+}
+
+.qt-window.blocked div {
+ pointer-events: none;
+}
+
+.title-bar .action-button img {
+ transition: filter 0.08s ease-out;
+}
+
+.title-bar .action-button:hover img {
+ filter: invert(0.45);
+}
+
+.title-bar .action-button:active img {
+ filter: invert(0.6);
+}
+
+/* This will clip the content within 50% frame in 1x1 pixel area, preventing it
+ from being rendered on the page, but it should still be read by modern
+ screen readers */
+.hidden-visually-read-by-screen-reader {
+ visibility: visible;
+ clip: rect(1px, 1px, 1px, 1px);
+ clip-path: inset(50%);
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+}
+
+)css";
+
+} // namespace
+
+emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent)
+{
+ auto document = parent["ownerDocument"];
+ auto screenStyle = document.call<emscripten::val>("createElement", emscripten::val("style"));
+
+ screenStyle.set("textContent", std::string(Style));
+ return screenStyle;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.h b/src/plugins/platforms/wasm/qwasmcssstyle.h
new file mode 100644
index 0000000000..fc4cc2d54c
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.h
@@ -0,0 +1,18 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMCSSSTYLE_H
+#define QWASMCSSSTYLE_H
+
+#include <QtCore/qglobal.h>
+
+#include <emscripten/val.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QWasmCSSStyle {
+emscripten::val createStyleElement(emscripten::val parent);
+}
+
+QT_END_NAMESPACE
+#endif // QWASMINLINESTYLEREGISTRY_H
diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp
index 61204517ce..c258befa77 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcursor.cpp
@@ -1,141 +1,101 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmcursor.h"
#include "qwasmscreen.h"
-#include "qwasmstring.h"
+#include "qwasmwindow.h"
+#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
#include <QtGui/qwindow.h>
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
+QT_BEGIN_NAMESPACE
using namespace emscripten;
-void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
-{
- if (!windowCursor || !window)
- return;
- QScreen *screen = window->screen();
- if (!screen)
- return;
-
- // Bitmap and custom cursors are not implemented (will fall back to "auto")
- if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor)
- qWarning() << "QWasmCursor: bitmap and custom cursors are not supported";
-
- QByteArray htmlCursorName = cursorShapeToHtml(windowCursor->shape());
-
- if (htmlCursorName.isEmpty())
- htmlCursorName = "auto";
-
- // Set cursor on the canvas
- val canvas = QWasmScreen::get(screen)->canvas();
- val canvasStyle = canvas["style"];
- canvasStyle.set("cursor", val(htmlCursorName.constData()));
-}
-
-QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape)
+namespace {
+QByteArray cursorToCss(const QCursor *cursor)
{
- QByteArray cursorName;
-
+ auto shape = cursor->shape();
switch (shape) {
case Qt::ArrowCursor:
- cursorName = "default";
- break;
+ return "default";
case Qt::UpArrowCursor:
- cursorName = "n-resize";
- break;
+ return "n-resize";
case Qt::CrossCursor:
- cursorName = "crosshair";
- break;
+ return "crosshair";
case Qt::WaitCursor:
- cursorName = "wait";
- break;
+ return "wait";
case Qt::IBeamCursor:
- cursorName = "text";
- break;
+ return "text";
case Qt::SizeVerCursor:
- cursorName = "ns-resize";
- break;
+ return "ns-resize";
case Qt::SizeHorCursor:
- cursorName = "ew-resize";
- break;
+ return "ew-resize";
case Qt::SizeBDiagCursor:
- cursorName = "nesw-resize";
- break;
+ return "nesw-resize";
case Qt::SizeFDiagCursor:
- cursorName = "nwse-resize";
- break;
+ return "nwse-resize";
case Qt::SizeAllCursor:
- cursorName = "move";
- break;
+ return "move";
case Qt::BlankCursor:
- cursorName = "none";
- break;
+ return "none";
case Qt::SplitVCursor:
- cursorName = "row-resize";
- break;
+ return "row-resize";
case Qt::SplitHCursor:
- cursorName = "col-resize";
- break;
+ return "col-resize";
case Qt::PointingHandCursor:
- cursorName = "pointer";
- break;
+ return "pointer";
case Qt::ForbiddenCursor:
- cursorName = "not-allowed";
- break;
+ return "not-allowed";
case Qt::WhatsThisCursor:
- cursorName = "help";
- break;
+ return "help";
case Qt::BusyCursor:
- cursorName = "progress";
- break;
+ return "progress";
case Qt::OpenHandCursor:
- cursorName = "grab";
- break;
+ return "grab";
case Qt::ClosedHandCursor:
- cursorName = "grabbing";
- break;
+ return "grabbing";
case Qt::DragCopyCursor:
- cursorName = "copy";
- break;
+ return "copy";
case Qt::DragMoveCursor:
- cursorName = "default";
- break;
+ return "default";
case Qt::DragLinkCursor:
- cursorName = "alias";
- break;
+ return "alias";
+ case Qt::BitmapCursor: {
+ auto pixmap = cursor->pixmap();
+ QByteArray cursorAsPng;
+ QBuffer buffer(&cursorAsPng);
+ buffer.open(QBuffer::WriteOnly);
+ pixmap.save(&buffer, "PNG");
+ buffer.close();
+ auto cursorAsBase64 = cursorAsPng.toBase64();
+ auto hotSpot = cursor->hotSpot();
+ auto encodedCursor =
+ QString("url(data:image/png;base64,%1) %2 %3, auto")
+ .arg(QString::fromUtf8(cursorAsBase64),
+ QString::number(hotSpot.x()),
+ QString::number(hotSpot.y()));
+ return encodedCursor.toUtf8();
+ }
default:
- break;
+ static_assert(Qt::CustomCursor == 25,
+ "New cursor type added, handle it");
+ qWarning() << "QWasmCursor: " << shape << " unsupported";
+ return "default";
}
+}
+} // namespace
- return cursorName;
+void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
+{
+ if (!window)
+ return;
+ if (QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle()))
+ wasmWindow->setWindowCursor(windowCursor ? cursorToCss(windowCursor) : "default");
}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcursor.h b/src/plugins/platforms/wasm/qwasmcursor.h
index 516e07aa31..6873602caf 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.h
+++ b/src/plugins/platforms/wasm/qwasmcursor.h
@@ -1,43 +1,19 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMCURSOR_H
#define QWASMCURSOR_H
#include <qpa/qplatformcursor.h>
+QT_BEGIN_NAMESPACE
+
class QWasmCursor : public QPlatformCursor
{
public:
void changeCursor(QCursor *windowCursor, QWindow *window) override;
-
- QByteArray cursorShapeToHtml(Qt::CursorShape shape);
};
+QT_END_NAMESPACE
+
#endif
diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp
new file mode 100644
index 0000000000..6b2b3d0933
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdom.cpp
@@ -0,0 +1,303 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmdom.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qrect.h>
+#include <QtGui/qimage.h>
+#include <private/qstdweb_p.h>
+#include <QtCore/qurl.h>
+
+#include <utility>
+#include <emscripten/wire.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace dom {
+namespace {
+std::string dropActionToDropEffect(Qt::DropAction action)
+{
+ switch (action) {
+ case Qt::DropAction::CopyAction:
+ return "copy";
+ case Qt::DropAction::IgnoreAction:
+ return "none";
+ case Qt::DropAction::LinkAction:
+ return "link";
+ case Qt::DropAction::MoveAction:
+ case Qt::DropAction::TargetMoveAction:
+ return "move";
+ case Qt::DropAction::ActionMask:
+ Q_ASSERT(false);
+ return "";
+ }
+}
+} // namespace
+
+DataTransfer::DataTransfer(emscripten::val webDataTransfer)
+ : webDataTransfer(webDataTransfer) {
+}
+
+DataTransfer::~DataTransfer() = default;
+
+DataTransfer::DataTransfer(const DataTransfer &other) = default;
+
+DataTransfer::DataTransfer(DataTransfer &&other) = default;
+
+DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
+
+DataTransfer &DataTransfer::operator=(DataTransfer &&other) = default;
+
+void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
+{
+ webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
+ emscripten::val(hotspot.y()));
+}
+
+void DataTransfer::setData(std::string format, std::string data)
+{
+ webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
+ emscripten::val(std::move(data)));
+}
+
+void DataTransfer::setDropAction(Qt::DropAction action)
+{
+ webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
+}
+
+void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
+{
+ for (const auto &format : mimeData.formats()) {
+ auto data = mimeData.data(format);
+
+ auto encoded = format.startsWith("text/")
+ ? QString::fromLocal8Bit(data).toStdString()
+ : "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
+
+ setData(format.toStdString(), std::move(encoded));
+ }
+}
+
+// Converts a DataTransfer instance to a QMimeData instance. Invokes the
+// given callback when the conversion is complete. The callback takes ownership
+// of the QMimeData.
+void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
+{
+ enum class ItemKind {
+ File,
+ String,
+ };
+
+ class MimeContext {
+
+ public:
+ MimeContext(int itemCount, std::function<void(QMimeData *)> callback)
+ :m_remainingItemCount(itemCount), m_callback(callback)
+ {
+
+ }
+
+ void deref() {
+ if (--m_remainingItemCount > 0)
+ return;
+
+ mimeData->setUrls(fileUrls);
+
+ m_callback(mimeData);
+
+ // Delete files; we expect that the user callback reads/copies
+ // file content before returning.
+ // Fixme: tie file lifetime to lifetime of the QMimeData?
+ for (QUrl fileUrl: fileUrls)
+ QFile(fileUrl.toLocalFile()).remove();
+
+ delete this;
+ }
+
+ QMimeData *mimeData = new QMimeData();
+ QList<QUrl> fileUrls;
+
+ private:
+ int m_remainingItemCount;
+ std::function<void(QMimeData *)> m_callback;
+ };
+
+ const auto items = webDataTransfer["items"];
+ const int itemCount = items["length"].as<int>();
+ const int fileCount = webDataTransfer["files"]["length"].as<int>();
+ MimeContext *mimeContext = new MimeContext(itemCount, callback);
+
+ for (int i = 0; i < itemCount; ++i) {
+ const auto item = items[i];
+ const auto itemKind =
+ item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+
+ switch (itemKind) {
+ case ItemKind::File: {
+ qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
+
+ if (webfile.size() > 1e+9) { // limit file size to 1 GB
+ qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
+ mimeContext->deref();
+ continue;
+ }
+
+ QString mimeFormat = QString::fromStdString(webfile.type());
+ QString fileName = QString::fromStdString(webfile.name());
+
+ // there's a file, now read it
+ QByteArray fileContent(webfile.size(), Qt::Uninitialized);
+ webfile.stream(fileContent.data(), [=]() {
+
+ // If we get a single file, and that file is an image, then
+ // try to decode the image data. This handles the case where
+ // image data (i.e. not an image file) is pasted. The browsers
+ // will then create a fake "image.png" file which has the image
+ // data. As a side effect Qt will also decode the image for
+ // single-image-file drops, since there is no way to differentiate
+ // the fake "image.png" from a real one.
+ if (fileCount == 1 && mimeFormat.contains("image/")) {
+ QImage image;
+ if (image.loadFromData(fileContent))
+ mimeContext->mimeData->setImageData(image);
+ }
+
+ QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
+ qtTmpDir.mkpath(qtTmpDir.path());
+
+ QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
+ mimeContext->fileUrls.append(fileUrl);
+
+ QFile file(fileUrl.toLocalFile());
+ if (!file.open(QFile::WriteOnly)) {
+ qWarning() << "File was not opened";
+ mimeContext->deref();
+ return;
+ }
+ if (file.write(fileContent) < 0) {
+ qWarning() << "Write failed";
+ file.close();
+ }
+ mimeContext->deref();
+ });
+ break;
+ }
+ case ItemKind::String:
+ if (itemMimeType.contains("STRING", Qt::CaseSensitive)
+ || itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
+ mimeContext->deref();
+ break;
+ }
+ QString a;
+ QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
+ "getData", emscripten::val(itemMimeType.toStdString())));
+
+ if (!data.isEmpty()) {
+ if (itemMimeType == "text/html")
+ mimeContext->mimeData->setHtml(data);
+ else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
+ mimeContext->mimeData->setText(data); // the type can be empty
+ else {
+ // TODO improve encoding
+ if (data.startsWith("QB64")) {
+ data.remove(0, 4);
+ mimeContext->mimeData->setData(itemMimeType,
+ QByteArray::fromBase64(QByteArray::fromStdString(
+ data.toStdString())));
+ } else {
+ mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());
+ }
+ }
+ }
+ mimeContext->deref();
+ break;
+ }
+ } // for items
+}
+
+QMimeData *DataTransfer::toMimeDataPreview()
+{
+ auto data = new QMimeData();
+
+ QList<QUrl> uriList;
+ for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
+ const auto item = webDataTransfer["items"][i];
+ if (item["kind"].as<std::string>() == "file") {
+ uriList.append(QUrl("blob://placeholder"));
+ } else {
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+ data->setData(itemMimeType, QByteArray());
+ }
+ }
+ data->setUrls(uriList);
+ return data;
+}
+
+void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
+{
+ if (flag) {
+ element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
+ return;
+ }
+
+ element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
+}
+
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point)
+{
+ const auto sourceBoundingRect =
+ QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
+ const auto targetBoundingRect =
+ QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
+
+ const auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
+ return point + offset;
+}
+
+void drawImageToWebImageDataArray(const QImage &sourceImage, emscripten::val destinationImageData,
+ const QRect &sourceRect)
+{
+ Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() == "ImageData",
+ Q_FUNC_INFO, "The destination should be an ImageData instance");
+
+ constexpr int BytesPerColor = 4;
+ if (sourceRect.width() == sourceImage.width()) {
+ // Copy a contiguous chunk of memory
+ // ...............
+ // OOOOOOOOOOOOOOO
+ // OOOOOOOOOOOOOOO -> image data
+ // OOOOOOOOOOOOOOO
+ // ...............
+ auto imageMemory = emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()
+ * BytesPerColor,
+ sourceImage.constScanLine(sourceRect.y()));
+ destinationImageData["data"].call<void>(
+ "set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);
+ } else {
+ // Go through the scanlines manually to set the individual lines in bulk. This is
+ // marginally less performant than the above.
+ // ...............
+ // ...OOOOOOOOO... r = 0 -> image data
+ // ...OOOOOOOOO... r = 1 -> image data
+ // ...OOOOOOOOO... r = 2 -> image data
+ // ...............
+ for (int row = 0; row < sourceRect.height(); ++row) {
+ auto scanlineMemory =
+ emscripten::typed_memory_view(sourceRect.width() * BytesPerColor,
+ sourceImage.constScanLine(row + sourceRect.y())
+ + BytesPerColor * sourceRect.x());
+ destinationImageData["data"].call<void>("set", scanlineMemory,
+ (sourceRect.y() + row) * sourceImage.width()
+ * BytesPerColor
+ + sourceRect.x() * BytesPerColor);
+ }
+ }
+}
+
+} // namespace dom
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdom.h b/src/plugins/platforms/wasm/qwasmdom.h
new file mode 100644
index 0000000000..0a520815a3
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdom.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMDOM_H
+#define QWASMDOM_H
+
+#include <QtCore/qtconfigmacros.h>
+#include <QtCore/QPointF>
+#include <private/qstdweb_p.h>
+#include <QtCore/qnamespace.h>
+
+#include <emscripten/val.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <QMimeData>
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+ struct CancellationFlag;
+}
+
+
+class QPoint;
+class QRect;
+
+namespace dom {
+struct DataTransfer
+{
+ explicit DataTransfer(emscripten::val webDataTransfer);
+ ~DataTransfer();
+ DataTransfer(const DataTransfer &other);
+ DataTransfer(DataTransfer &&other);
+ DataTransfer &operator=(const DataTransfer &other);
+ DataTransfer &operator=(DataTransfer &&other);
+
+ void toMimeDataWithFile(std::function<void(QMimeData *)> callback);
+ QMimeData *toMimeDataPreview();
+ void setDragImage(emscripten::val element, const QPoint &hotspot);
+ void setData(std::string format, std::string data);
+ void setDropAction(Qt::DropAction dropAction);
+ void setDataFromMimeData(const QMimeData &mimeData);
+
+ emscripten::val webDataTransfer;
+};
+
+inline emscripten::val document()
+{
+ return emscripten::val::global("document");
+}
+
+void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag);
+
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point);
+
+void drawImageToWebImageDataArray(const QImage &source, emscripten::val destinationImageData,
+ const QRect &sourceRect);
+} // namespace dom
+
+QT_END_NAMESPACE
+#endif // QWASMDOM_H
diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp
new file mode 100644
index 0000000000..d07a46618f
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdrag.cpp
@@ -0,0 +1,291 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmdrag.h"
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmintegration.h"
+
+#include <qpa/qwindowsysteminterface.h>
+
+#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/qeventloop.h>
+#include <QtCore/qmimedata.h>
+#include <QtCore/qtimer.h>
+#include <QFile>
+
+#include <functional>
+#include <string>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+QWindow *windowForDrag(QDrag *drag)
+{
+ QWindow *window = qobject_cast<QWindow *>(drag->source());
+ if (window)
+ return window;
+ if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1)
+ return nullptr;
+
+ QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
+ Q_RETURN_ARG(QWindow *, window));
+ return window;
+}
+
+} // namespace
+
+struct QWasmDrag::DragState
+{
+ class DragImage
+ {
+ public:
+ DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window);
+ ~DragImage();
+
+ emscripten::val htmlElement();
+
+ private:
+ emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData);
+ emscripten::val generateDragImageFromText(const QMimeData *mimeData);
+ emscripten::val generateDefaultDragImage();
+ emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap);
+
+ emscripten::val m_imageDomElement;
+ emscripten::val m_temporaryImageElementParent;
+ };
+
+ DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure);
+ ~DragState();
+ DragState(const QWasmDrag &other) = delete;
+ DragState(QWasmDrag &&other) = delete;
+ DragState &operator=(const QWasmDrag &other) = delete;
+ DragState &operator=(QWasmDrag &&other) = delete;
+
+ QDrag *drag;
+ QWindow *window;
+ std::function<void()> quitEventLoopClosure;
+ std::unique_ptr<DragImage> dragImage;
+ Qt::DropAction dropAction = Qt::DropAction::IgnoreAction;
+};
+
+QWasmDrag::QWasmDrag() = default;
+
+QWasmDrag::~QWasmDrag() = default;
+
+QWasmDrag *QWasmDrag::instance()
+{
+ return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
+}
+
+Qt::DropAction QWasmDrag::drag(QDrag *drag)
+{
+ Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress");
+
+ QWindow *window = windowForDrag(drag);
+ if (!window)
+ return Qt::IgnoreAction;
+
+ Qt::DropAction dragResult = Qt::IgnoreAction;
+ if (qstdweb::haveJspi()) {
+ QEventLoop loop;
+ m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); });
+ loop.exec();
+ dragResult = m_dragState->dropAction;
+ m_dragState.reset();
+ }
+
+ if (dragResult == Qt::IgnoreAction)
+ dragResult = QBasicDrag::drag(drag);
+
+ return dragResult;
+}
+
+void QWasmDrag::onNativeDragStarted(DragEvent *event)
+{
+ Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
+ "The event is not a DragStart event");
+ // It is possible for a drag start event to arrive from another window.
+ if (!m_dragState || m_dragState->window != event->targetWindow) {
+ event->cancelDragStart();
+ return;
+ }
+
+ m_dragState->dragImage = std::make_unique<DragState::DragImage>(
+ m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow);
+ event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
+ m_dragState->drag->hotSpot());
+ event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData());
+}
+
+void QWasmDrag::onNativeDragOver(DragEvent *event)
+{
+ auto mimeDataPreview = event->dataTransfer.toMimeDataPreview();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+
+ const auto dragResponse = QWindowSystemInterface::handleDrag(
+ event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions,
+ event->mouseButton, event->modifiers);
+ event->acceptDragOver();
+ if (dragResponse.isAccepted()) {
+ event->dataTransfer.setDropAction(dragResponse.acceptedAction());
+ } else {
+ event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
+ }
+}
+
+void QWasmDrag::onNativeDrop(DragEvent *event)
+{
+ QWasmWindow *wasmWindow = QWasmWindow::fromWindow(event->targetWindow);
+
+ const auto screenElementPos = dom::mapPoint(
+ event->target(), wasmWindow->platformScreen()->element(), event->localPoint);
+ const auto screenPos =
+ wasmWindow->platformScreen()->mapFromLocal(screenElementPos);
+ const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+ Qt::MouseButton mouseButton = event->mouseButton;
+ QFlags<Qt::KeyboardModifier> modifiers = event->modifiers;
+
+ // Accept the native drop event: We are going to async read any dropped
+ // files, but the browser expects that accepted state is set before any
+ // async calls.
+ event->acceptDrop();
+
+ const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos,
+ actions, mouseButton, modifiers](QMimeData *mimeData) {
+
+ auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
+ *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
+ targetWindowPos, actions,
+ mouseButton, modifiers);
+
+ if (dropResponse->isAccepted())
+ m_dragState->dropAction = dropResponse->acceptedAction();
+
+ delete mimeData;
+ };
+
+ event->dataTransfer.toMimeDataWithFile(dropCallback);
+}
+
+void QWasmDrag::onNativeDragFinished(DragEvent *event)
+{
+ m_dragState->dropAction = event->dropAction;
+ m_dragState->quitEventLoopClosure();
+}
+
+QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData,
+ QWindow *window)
+ : m_temporaryImageElementParent(QWasmWindow::fromWindow(window)->containerElement())
+{
+ m_imageDomElement = generateDragImage(pixmap, mimeData);
+
+ m_imageDomElement.set("className", "hidden-drag-image");
+ m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement);
+}
+
+QWasmDrag::DragState::DragImage::~DragImage()
+{
+ m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement);
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap,
+ const QMimeData *mimeData)
+{
+ if (!pixmap.isNull())
+ return generateDragImageFromPixmap(pixmap);
+ if (mimeData->hasFormat("text/plain"))
+ return generateDragImageFromText(mimeData);
+ return generateDefaultDragImage();
+}
+
+emscripten::val
+QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData)
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("span"));
+
+ constexpr qsizetype MaxCharactersInDragImage = 100;
+
+ const auto text = QString::fromUtf8(mimeData->data("text/plain"));
+ dragImageElement.set(
+ "innerText",
+ text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString());
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage()
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("div"));
+
+ auto innerImgElement = emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("img"));
+ innerImgElement.set("src",
+ "data:image/" + std::string("svg+xml") + ";base64,"
+ + std::string(Base64IconStore::get()->getIcon(
+ Base64IconStore::IconType::QtLogo)));
+
+ constexpr char DragImageSize[] = "50px";
+
+ dragImageElement["style"].set("width", DragImageSize);
+ innerImgElement["style"].set("width", DragImageSize);
+ dragImageElement["style"].set("display", "flex");
+
+ dragImageElement.call<void>("appendChild", innerImgElement);
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap)
+{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("canvas"));
+ dragImageElement.set("width", pixmap.width());
+ dragImageElement.set("height", pixmap.height());
+
+ dragImageElement["style"].set(
+ "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px");
+ dragImageElement["style"].set(
+ "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px");
+
+ auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d"));
+ auto imageData = context2d.call<emscripten::val>(
+ "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height()));
+
+ dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888),
+ imageData, QRect(0, 0, pixmap.width(), pixmap.height()));
+ context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0));
+
+ return dragImageElement;
+}
+
+emscripten::val QWasmDrag::DragState::DragImage::htmlElement()
+{
+ return m_imageDomElement;
+}
+
+QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window,
+ std::function<void()> quitEventLoopClosure)
+ : drag(drag), window(window), quitEventLoopClosure(std::move(quitEventLoopClosure))
+{
+}
+
+QWasmDrag::DragState::~DragState() = default;
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h
new file mode 100644
index 0000000000..146a69ebe8
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdrag.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWINDOWSDRAG_H
+#define QWINDOWSDRAG_H
+
+#include <private/qstdweb_p.h>
+#include <private/qsimpledrag_p.h>
+
+#include <qpa/qplatformdrag.h>
+#include <QtGui/qdrag.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+struct DragEvent;
+
+class QWasmDrag final : public QSimpleDrag
+{
+public:
+ QWasmDrag();
+ ~QWasmDrag() override;
+ QWasmDrag(const QWasmDrag &other) = delete;
+ QWasmDrag(QWasmDrag &&other) = delete;
+ QWasmDrag &operator=(const QWasmDrag &other) = delete;
+ QWasmDrag &operator=(QWasmDrag &&other) = delete;
+
+ static QWasmDrag *instance();
+
+ void onNativeDragOver(DragEvent *event);
+ void onNativeDrop(DragEvent *event);
+ void onNativeDragStarted(DragEvent *event);
+ void onNativeDragFinished(DragEvent *event);
+
+ // QPlatformDrag:
+ Qt::DropAction drag(QDrag *drag) final;
+
+private:
+ struct DragState;
+
+ std::unique_ptr<DragState> m_dragState;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSDRAG_H
diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp
new file mode 100644
index 0000000000..c1d6ce3a2a
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmevent.cpp
@@ -0,0 +1,338 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmevent.h"
+
+#include "qwasmkeytranslator.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/private/qstringiterator_p.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+constexpr std::string_view WebDeadKeyValue = "Dead";
+
+bool isDeadKeyEvent(const char *key)
+{
+ return qstrncmp(key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
+}
+
+Qt::Key getKeyFromCode(const std::string &code)
+{
+ if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(code.c_str()))
+ return *mapping;
+
+ static QRegularExpression regex(QString(QStringLiteral(R"re((?:Key|Digit)(\w))re")));
+ const auto codeQString = QString::fromStdString(code);
+ const auto match = regex.match(codeQString);
+
+ if (!match.hasMatch())
+ return Qt::Key_unknown;
+
+ constexpr size_t CharacterIndex = 1;
+ return static_cast<Qt::Key>(match.capturedView(CharacterIndex).at(0).toLatin1());
+}
+
+Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey,
+ QFlags<Qt::KeyboardModifier> modifiers)
+{
+ if (isDeadKey) {
+ auto mapped = getKeyFromCode(code);
+ switch (mapped) {
+ case Qt::Key_U:
+ return Qt::Key_Dead_Diaeresis;
+ case Qt::Key_E:
+ return Qt::Key_Dead_Acute;
+ case Qt::Key_I:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_N:
+ return Qt::Key_Dead_Tilde;
+ case Qt::Key_QuoteLeft:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Tilde : Qt::Key_Dead_Grave;
+ case Qt::Key_6:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_Apostrophe:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Diaeresis
+ : Qt::Key_Dead_Acute;
+ case Qt::Key_AsciiTilde:
+ return Qt::Key_Dead_Tilde;
+ default:
+ return Qt::Key_unknown;
+ }
+ } else if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(key.c_str())) {
+ return *mapping;
+ }
+
+ // cast to unicode key
+ QString str = QString::fromUtf8(key.c_str()).toUpper();
+ if (str.length() > 1)
+ return Qt::Key_unknown;
+
+ QStringIterator i(str);
+ return static_cast<Qt::Key>(i.next(0));
+}
+} // namespace
+
+namespace KeyboardModifier
+{
+template <>
+QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
+ const EmscriptenKeyboardEvent& event)
+{
+ return internal::Helper<EmscriptenKeyboardEvent>::getModifierForEvent(event) |
+ (event.location == DOM_KEY_LOCATION_NUMPAD ? Qt::KeypadModifier : Qt::NoModifier);
+}
+} // namespace KeyboardModifier
+
+Event::Event(EventType type, emscripten::val webEvent)
+ : webEvent(webEvent), type(type)
+{
+}
+
+Event::~Event() = default;
+
+Event::Event(const Event &other) = default;
+
+Event::Event(Event &&other) = default;
+
+Event &Event::operator=(const Event &other) = default;
+
+Event &Event::operator=(Event &&other) = default;
+
+KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event)
+{
+ const auto code = event["code"].as<std::string>();
+ const auto webKey = event["key"].as<std::string>();
+ deadKey = isDeadKeyEvent(webKey.c_str());
+
+ modifiers = KeyboardModifier::getForEvent(event);
+ key = webKeyToQtKey(code, webKey, deadKey, modifiers);
+
+ text = QString::fromUtf8(webKey);
+ if (text.size() > 1)
+ text.clear();
+
+ if (key == Qt::Key_Tab)
+ text = "\t";
+}
+
+KeyEvent::~KeyEvent() = default;
+
+KeyEvent::KeyEvent(const KeyEvent &other) = default;
+
+KeyEvent::KeyEvent(KeyEvent &&other) = default;
+
+KeyEvent &KeyEvent::operator=(const KeyEvent &other) = default;
+
+KeyEvent &KeyEvent::operator=(KeyEvent &&other) = default;
+
+std::optional<KeyEvent> KeyEvent::fromWebWithDeadKeyTranslation(emscripten::val event,
+ QWasmDeadKeySupport *deadKeySupport)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "keydown")
+ return EventType::KeyDown;
+ else if (eventTypeString == "keyup")
+ return EventType::KeyUp;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+
+ auto result = KeyEvent(*eventType, event);
+ deadKeySupport->applyDeadKeyTranslations(&result);
+
+ return result;
+}
+
+MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event)
+{
+ mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
+ mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
+ // The current button state (event.buttons) may be out of sync for some PointerDown
+ // events where the "down" state is very brief, for example taps on Apple trackpads.
+ // Qt expects that the current button state is in sync with the event, so we sync
+ // it up here.
+ if (type == EventType::PointerDown)
+ mouseButtons |= mouseButton;
+ localPoint = QPointF(event["offsetX"].as<qreal>(), event["offsetY"].as<qreal>());
+ pointInPage = QPointF(event["pageX"].as<qreal>(), event["pageY"].as<qreal>());
+ pointInViewport = QPointF(event["clientX"].as<qreal>(), event["clientY"].as<qreal>());
+ modifiers = KeyboardModifier::getForEvent(event);
+}
+
+MouseEvent::~MouseEvent() = default;
+
+MouseEvent::MouseEvent(const MouseEvent &other) = default;
+
+MouseEvent::MouseEvent(MouseEvent &&other) = default;
+
+MouseEvent &MouseEvent::operator=(const MouseEvent &other) = default;
+
+MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default;
+
+PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
+{
+ pointerId = event["pointerId"].as<int>();
+ pointerType = ([type = event["pointerType"].as<std::string>()]() {
+ if (type == "mouse")
+ return PointerType::Mouse;
+ if (type == "touch")
+ return PointerType::Touch;
+ if (type == "pen")
+ return PointerType::Pen;
+ return PointerType::Other;
+ })();
+ width = event["width"].as<qreal>();
+ height = event["height"].as<qreal>();
+ pressure = event["pressure"].as<qreal>();
+ tiltX = event["tiltX"].as<qreal>();
+ tiltY = event["tiltY"].as<qreal>();
+ tangentialPressure = event["tangentialPressure"].as<qreal>();
+ twist = event["twist"].as<qreal>();
+ isPrimary = event["isPrimary"].as<bool>();
+}
+
+PointerEvent::~PointerEvent() = default;
+
+PointerEvent::PointerEvent(const PointerEvent &other) = default;
+
+PointerEvent::PointerEvent(PointerEvent &&other) = default;
+
+PointerEvent &PointerEvent::operator=(const PointerEvent &other) = default;
+
+PointerEvent &PointerEvent::operator=(PointerEvent &&other) = default;
+
+std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "pointermove")
+ return EventType::PointerMove;
+ else if (eventTypeString == "pointerup")
+ return EventType::PointerUp;
+ else if (eventTypeString == "pointerdown")
+ return EventType::PointerDown;
+ else if (eventTypeString == "pointerenter")
+ return EventType::PointerEnter;
+ else if (eventTypeString == "pointerleave")
+ return EventType::PointerLeave;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+
+ return PointerEvent(*eventType, event);
+}
+
+DragEvent::DragEvent(EventType type, emscripten::val event, QWindow *window)
+ : MouseEvent(type, event), dataTransfer(event["dataTransfer"]), targetWindow(window)
+{
+ dropAction = ([event]() {
+ const std::string effect = event["dataTransfer"]["dropEffect"].as<std::string>();
+
+ if (effect == "copy")
+ return Qt::CopyAction;
+ else if (effect == "move")
+ return Qt::MoveAction;
+ else if (effect == "link")
+ return Qt::LinkAction;
+ return Qt::IgnoreAction;
+ })();
+}
+
+DragEvent::~DragEvent() = default;
+
+DragEvent::DragEvent(const DragEvent &other) = default;
+
+DragEvent::DragEvent(DragEvent &&other) = default;
+
+DragEvent &DragEvent::operator=(const DragEvent &other) = default;
+
+DragEvent &DragEvent::operator=(DragEvent &&other) = default;
+
+std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event, QWindow *targetWindow)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "dragend")
+ return EventType::DragEnd;
+ if (eventTypeString == "dragover")
+ return EventType::DragOver;
+ if (eventTypeString == "dragstart")
+ return EventType::DragStart;
+ if (eventTypeString == "drop")
+ return EventType::Drop;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+ return DragEvent(*eventType, event, targetWindow);
+}
+
+void DragEvent::cancelDragStart()
+{
+ Q_ASSERT_X(type == EventType::DragStart, Q_FUNC_INFO, "Only supported for DragStart");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDragOver()
+{
+ Q_ASSERT_X(type == EventType::DragOver, Q_FUNC_INFO, "Only supported for DragOver");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDrop()
+{
+ Q_ASSERT_X(type == EventType::Drop, Q_FUNC_INFO, "Only supported for Drop");
+ webEvent.call<void>("preventDefault");
+}
+
+WheelEvent::WheelEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
+{
+ deltaMode = ([event]() {
+ const int deltaMode = event["deltaMode"].as<int>();
+ const auto jsWheelEventType = emscripten::val::global("WheelEvent");
+ if (deltaMode == jsWheelEventType["DOM_DELTA_PIXEL"].as<int>())
+ return DeltaMode::Pixel;
+ else if (deltaMode == jsWheelEventType["DOM_DELTA_LINE"].as<int>())
+ return DeltaMode::Line;
+ return DeltaMode::Page;
+ })();
+
+ delta = QPointF(event["deltaX"].as<qreal>(), event["deltaY"].as<qreal>());
+
+ webkitDirectionInvertedFromDevice = event["webkitDirectionInvertedFromDevice"].as<bool>();
+}
+
+WheelEvent::~WheelEvent() = default;
+
+WheelEvent::WheelEvent(const WheelEvent &other) = default;
+
+WheelEvent::WheelEvent(WheelEvent &&other) = default;
+
+WheelEvent &WheelEvent::operator=(const WheelEvent &other) = default;
+
+WheelEvent &WheelEvent::operator=(WheelEvent &&other) = default;
+
+std::optional<WheelEvent> WheelEvent::fromWeb(emscripten::val event)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "wheel")
+ return EventType::Wheel;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+ return WheelEvent(*eventType, event);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h
new file mode 100644
index 0000000000..6ada5393e3
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmevent.h
@@ -0,0 +1,271 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMEVENT_H
+#define QWASMEVENT_H
+
+#include "qwasmplatform.h"
+#include "qwasmdom.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qnamespace.h>
+#include <QtGui/qevent.h>
+#include <private/qstdweb_p.h>
+#include <QPoint>
+
+#include <emscripten/html5.h>
+#include <emscripten/val.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmDeadKeySupport;
+class QWindow;
+
+enum class EventType {
+ DragEnd,
+ DragOver,
+ DragStart,
+ Drop,
+ KeyDown,
+ KeyUp,
+ PointerDown,
+ PointerMove,
+ PointerUp,
+ PointerEnter,
+ PointerLeave,
+ PointerCancel,
+ Wheel,
+};
+
+enum class PointerType {
+ Mouse,
+ Touch,
+ Pen,
+ Other,
+};
+
+enum class WindowArea {
+ NonClient,
+ Client,
+};
+
+enum class DeltaMode { Pixel, Line, Page };
+
+namespace KeyboardModifier {
+namespace internal
+{
+ // Check for the existence of shiftKey, ctrlKey, altKey and metaKey in a type.
+ // Based on that, we can safely assume we are dealing with an emscripten event type.
+ template<typename T>
+ struct IsEmscriptenEvent
+ {
+ template<typename U, EM_BOOL U::*, EM_BOOL U::*, EM_BOOL U::*, EM_BOOL U::*>
+ struct SFINAE {};
+ template<typename U> static char Test(
+ SFINAE<U, &U::shiftKey, &U::ctrlKey, &U::altKey, &U::metaKey>*);
+ template<typename U> static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+ };
+
+ template<class T, typename Enable = void>
+ struct Helper;
+
+ template<class T>
+ struct Helper<T, std::enable_if_t<IsEmscriptenEvent<T>::value>>
+ {
+ static QFlags<Qt::KeyboardModifier> getModifierForEvent(const T& event) {
+ QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
+ if (event.shiftKey)
+ keyModifier |= Qt::ShiftModifier;
+ if (event.ctrlKey)
+ keyModifier |= platform() == Platform::MacOS ? Qt::MetaModifier : Qt::ControlModifier;
+ if (event.altKey)
+ keyModifier |= Qt::AltModifier;
+ if (event.metaKey)
+ keyModifier |= platform() == Platform::MacOS ? Qt::ControlModifier : Qt::MetaModifier;
+
+ return keyModifier;
+ }
+ };
+
+ template<>
+ struct Helper<emscripten::val>
+ {
+ static QFlags<Qt::KeyboardModifier> getModifierForEvent(const emscripten::val& event) {
+ QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
+ if (event["shiftKey"].as<bool>())
+ keyModifier |= Qt::ShiftModifier;
+ if (event["ctrlKey"].as<bool>())
+ keyModifier |= platform() == Platform::MacOS ? Qt::MetaModifier : Qt::ControlModifier;
+ if (event["altKey"].as<bool>())
+ keyModifier |= Qt::AltModifier;
+ if (event["metaKey"].as<bool>())
+ keyModifier |= platform() == Platform::MacOS ? Qt::ControlModifier : Qt::MetaModifier;
+ if (event["constructor"]["name"].as<std::string>() == "KeyboardEvent" &&
+ event["location"].as<unsigned int>() == DOM_KEY_LOCATION_NUMPAD) {
+ keyModifier |= Qt::KeypadModifier;
+ }
+
+ return keyModifier;
+ }
+ };
+} // namespace internal
+
+template <typename Event>
+QFlags<Qt::KeyboardModifier> getForEvent(const Event& event)
+{
+ return internal::Helper<Event>::getModifierForEvent(event);
+}
+
+template <>
+QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
+ const EmscriptenKeyboardEvent& event);
+
+} // namespace KeyboardModifier
+
+struct Event
+{
+ Event(EventType type, emscripten::val webEvent);
+ ~Event();
+ Event(const Event &other);
+ Event(Event &&other);
+ Event &operator=(const Event &other);
+ Event &operator=(Event &&other);
+
+ emscripten::val webEvent;
+ EventType type;
+ emscripten::val target() const { return webEvent["target"]; }
+};
+
+struct KeyEvent : public Event
+{
+ static std::optional<KeyEvent>
+ fromWebWithDeadKeyTranslation(emscripten::val webEvent, QWasmDeadKeySupport *deadKeySupport);
+
+ KeyEvent(EventType type, emscripten::val webEvent);
+ ~KeyEvent();
+ KeyEvent(const KeyEvent &other);
+ KeyEvent(KeyEvent &&other);
+ KeyEvent &operator=(const KeyEvent &other);
+ KeyEvent &operator=(KeyEvent &&other);
+
+ Qt::Key key;
+ QFlags<Qt::KeyboardModifier> modifiers;
+ bool deadKey;
+ QString text;
+};
+
+struct MouseEvent : public Event
+{
+ MouseEvent(EventType type, emscripten::val webEvent);
+ ~MouseEvent();
+ MouseEvent(const MouseEvent &other);
+ MouseEvent(MouseEvent &&other);
+ MouseEvent &operator=(const MouseEvent &other);
+ MouseEvent &operator=(MouseEvent &&other);
+
+ static constexpr Qt::MouseButton buttonFromWeb(int webButton) {
+ switch (webButton) {
+ case 0:
+ return Qt::LeftButton;
+ case 1:
+ return Qt::MiddleButton;
+ case 2:
+ return Qt::RightButton;
+ default:
+ return Qt::NoButton;
+ }
+ }
+
+ static constexpr Qt::MouseButtons buttonsFromWeb(unsigned short webButtons) {
+ // Coincidentally, Qt and web bitfields match.
+ return Qt::MouseButtons::fromInt(webButtons);
+ }
+
+ static constexpr QEvent::Type mouseEventTypeFromEventType(
+ EventType eventType, WindowArea windowArea) {
+ switch (eventType) {
+ case EventType::PointerDown :
+ return windowArea == WindowArea::Client ?
+ QEvent::MouseButtonPress : QEvent::NonClientAreaMouseButtonPress;
+ case EventType::PointerUp :
+ return windowArea == WindowArea::Client ?
+ QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease;
+ case EventType::PointerMove :
+ return windowArea == WindowArea::Client ?
+ QEvent::MouseMove : QEvent::NonClientAreaMouseMove;
+ default:
+ return QEvent::None;
+ }
+ }
+
+ QPointF localPoint;
+ QPointF pointInPage;
+ QPointF pointInViewport;
+ Qt::MouseButton mouseButton;
+ Qt::MouseButtons mouseButtons;
+ QFlags<Qt::KeyboardModifier> modifiers;
+};
+
+struct PointerEvent : public MouseEvent
+{
+ static std::optional<PointerEvent> fromWeb(emscripten::val webEvent);
+
+ PointerEvent(EventType type, emscripten::val webEvent);
+ ~PointerEvent();
+ PointerEvent(const PointerEvent &other);
+ PointerEvent(PointerEvent &&other);
+ PointerEvent &operator=(const PointerEvent &other);
+ PointerEvent &operator=(PointerEvent &&other);
+
+ PointerType pointerType;
+ int pointerId;
+ qreal pressure;
+ qreal tiltX;
+ qreal tiltY;
+ qreal tangentialPressure;
+ qreal twist;
+ qreal width;
+ qreal height;
+ bool isPrimary;
+};
+
+struct DragEvent : public MouseEvent
+{
+ static std::optional<DragEvent> fromWeb(emscripten::val webEvent, QWindow *targetQWindow);
+
+ DragEvent(EventType type, emscripten::val webEvent, QWindow *targetQWindow);
+ ~DragEvent();
+ DragEvent(const DragEvent &other);
+ DragEvent(DragEvent &&other);
+ DragEvent &operator=(const DragEvent &other);
+ DragEvent &operator=(DragEvent &&other);
+
+ void cancelDragStart();
+ void acceptDragOver();
+ void acceptDrop();
+
+ Qt::DropAction dropAction;
+ dom::DataTransfer dataTransfer;
+ QWindow *targetWindow;
+};
+
+struct WheelEvent : public MouseEvent
+{
+ static std::optional<WheelEvent> fromWeb(emscripten::val webEvent);
+
+ WheelEvent(EventType type, emscripten::val webEvent);
+ ~WheelEvent();
+ WheelEvent(const WheelEvent &other);
+ WheelEvent(WheelEvent &&other);
+ WheelEvent &operator=(const WheelEvent &other);
+ WheelEvent &operator=(WheelEvent &&other);
+
+ DeltaMode deltaMode;
+ bool webkitDirectionInvertedFromDevice;
+ QPointF delta;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASMEVENT_H
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
index 2e1b083557..1f2d3095d6 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
@@ -1,207 +1,35 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmeventdispatcher.h"
+#include "qwasmintegration.h"
-#include <QtCore/qcoreapplication.h>
+#include <QtGui/qpa/qwindowsysteminterface.h>
-#include <emscripten.h>
+QT_BEGIN_NAMESPACE
-#if QT_CONFIG(thread)
-#if (__EMSCRIPTEN_major__ > 1 || __EMSCRIPTEN_minor__ > 38 || __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ >= 22)
-# define EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
-#endif
-#endif
-
-#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
-#include <emscripten/threading.h>
-#endif
-
-class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate
-{
-
-};
-
-QWasmEventDispatcher *g_htmlEventDispatcher;
-
-QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent)
- : QUnixEventDispatcherQPA(parent)
+// Note: All event dispatcher functionality is implemented in QEventDispatcherWasm
+// in QtCore, except for processPostedEvents() below which uses API from QtGui.
+bool QWasmEventDispatcher::processPostedEvents()
{
-
- g_htmlEventDispatcher = this;
+ QEventDispatcherWasm::processPostedEvents();
+ return QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
}
-QWasmEventDispatcher::~QWasmEventDispatcher()
+void QWasmEventDispatcher::onLoaded()
{
- g_htmlEventDispatcher = nullptr;
-}
-
-bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function<void(void)> callback)
-{
- if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
- return false;
-
- g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback);
- emscripten_resume_main_loop();
- return true;
-}
+ // This function is called when the application is ready to paint
+ // the first frame. Send the qtlaoder onLoaded event first (via
+ // the base class implementation), and then enable/call requestUpdate
+ // to deliver a frame.
+ QEventDispatcherWasm::onLoaded();
-void QWasmEventDispatcher::maintainTimers()
-{
- if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
- return;
+ // Make sure all screens have a defined size; and pick
+ // up size changes due to onLoaded event handling.
+ QWasmIntegration *wasmIntegration = QWasmIntegration::get();
+ wasmIntegration->resizeAllScreens();
- g_htmlEventDispatcher->doMaintainTimers();
+ wasmIntegration->releaseRequesetUpdateHold();
}
-bool QWasmEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
-{
- // WaitForMoreEvents is not supported (except for in combination with EventLoopExec below),
- // and we don't want the unix event dispatcher base class to attempt to wait either.
- flags &= ~QEventLoop::WaitForMoreEvents;
-
- // Handle normal processEvents.
- if (!(flags & QEventLoop::EventLoopExec))
- return QUnixEventDispatcherQPA::processEvents(flags);
-
- // Handle processEvents from QEventLoop::exec():
- //
- // At this point the application has created its root objects on
- // the stack and has called app.exec() which has called into this
- // function via QEventLoop.
- //
- // The application now expects that exec() will not return until
- // app exit time. However, the browser expects that we return
- // control to it periodically, also after initial setup in main().
-
- // EventLoopExec for nested event loops is not supported.
- Q_ASSERT(!m_hasMainLoop);
- m_hasMainLoop = true;
-
- // Call emscripten_set_main_loop_arg() with a callback which processes
- // events. Also set simulateInfiniteLoop to true which makes emscripten
- // return control to the browser without unwinding the C++ stack.
- auto callback = [](void *eventDispatcher) {
- QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
-
- // Save and clear updateRequest callbacks so we can register new ones
- auto requestUpdateCallbacksCopy = that->m_requestUpdateCallbacks;
- that->m_requestUpdateCallbacks.clear();
-
- // Repaint all windows
- for (auto callback : qAsConst(requestUpdateCallbacksCopy))
- callback();
-
- // Pause main loop if no updates were requested. Updates will be
- // restarted again by registerRequestUpdateCallback().
- if (that->m_requestUpdateCallbacks.isEmpty())
- emscripten_pause_main_loop();
-
- that->doMaintainTimers();
- };
- int fps = 0; // update using requestAnimationFrame
- int simulateInfiniteLoop = 1;
- emscripten_set_main_loop_arg(callback, this, fps, simulateInfiniteLoop);
-
- // Note: the above call never returns, not even at app exit
- return false;
-}
-
-void QWasmEventDispatcher::doMaintainTimers()
-{
- Q_D(QWasmEventDispatcher);
-
- // This functon schedules native timers in order to wake up to
- // process events and activate Qt timers. This is done using the
- // emscripten_async_call() API which schedules a new timer.
- // There is unfortunately no way to cancel or update a current
- // native timer.
-
- // Schedule a zero-timer to continue processing any pending events.
- if (!m_hasZeroTimer && hasPendingEvents()) {
- auto callback = [](void *eventDispatcher) {
- QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
- that->m_hasZeroTimer = false;
- that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
-
- // Processing events may have posted new events or created new timers
- that->doMaintainTimers();
- };
-
- emscripten_async_call(callback, this, 0);
- m_hasZeroTimer = true;
- return;
- }
-
- auto timespecToNanosec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); };
-
- // Get current time and time-to-first-Qt-timer. This polls for system
- // time, and we use this time as the current time for the duration of this call.
- timespec toWait;
- bool hasTimers = d->timerList.timerWait(toWait);
- if (!hasTimers)
- return; // no timer needed
-
- uint64_t currentTime = timespecToNanosec(d->timerList.currentTime);
- uint64_t toWaitDuration = timespecToNanosec(toWait);
-
- // The currently scheduled timer target is stored in m_currentTargetTime.
- // We can re-use it if the new target is equivalent or later.
- uint64_t newTargetTime = currentTime + toWaitDuration;
- if (newTargetTime >= m_currentTargetTime)
- return; // existing timer is good
-
- // Schedule a native timer with a callback which processes events (and timers)
- auto callback = [](void *eventDispatcher) {
- QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
- that->m_currentTargetTime = std::numeric_limits<uint64_t>::max();
- that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
-
- // Processing events may have posted new events or created new timers
- that->doMaintainTimers();
- };
- emscripten_async_call(callback, this, toWaitDuration);
- m_currentTargetTime = newTargetTime;
-}
-
-void QWasmEventDispatcher::wakeUp()
-{
-#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
- if (!emscripten_is_main_runtime_thread())
- if (m_hasMainLoop)
- emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, (void*)(&QWasmEventDispatcher::mainThreadWakeUp), this);
-#endif
- QEventDispatcherUNIX::wakeUp();
-}
-
-void QWasmEventDispatcher::mainThreadWakeUp(void *eventDispatcher)
-{
- emscripten_resume_main_loop(); // Service possible requestUpdate Calls
- static_cast<QWasmEventDispatcher *>(eventDispatcher)->processEvents(QEventLoop::AllEvents);
-}
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.h b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
index 8420f2cf1a..cbf10482e3 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.h
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
@@ -1,64 +1,18 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMEVENTDISPATCHER_H
#define QWASMEVENTDISPATCHER_H
-#include <QtCore/qhash.h>
-#include <QtCore/qloggingcategory.h>
-#include <QtGui/private/qunixeventdispatcher_qpa_p.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
QT_BEGIN_NAMESPACE
-class QWasmEventDispatcherPrivate;
-
-class QWasmEventDispatcher : public QUnixEventDispatcherQPA
+class QWasmEventDispatcher : public QEventDispatcherWasm
{
- Q_DECLARE_PRIVATE(QWasmEventDispatcher)
-public:
- explicit QWasmEventDispatcher(QObject *parent = nullptr);
- ~QWasmEventDispatcher();
-
- static bool registerRequestUpdateCallback(std::function<void(void)> callback);
- static void maintainTimers();
-
protected:
- bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
- void doMaintainTimers();
- void wakeUp() override;
- static void mainThreadWakeUp(void *eventDispatcher);
-
-private:
- bool m_hasMainLoop = false;
- bool m_hasZeroTimer = false;
- uint64_t m_currentTargetTime = std::numeric_limits<uint64_t>::max();
- QList<std::function<void(void)>> m_requestUpdateCallbacks;
+ bool processPostedEvents() override;
+ void onLoaded() override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
deleted file mode 100644
index 2387f3cdea..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
+++ /dev/null
@@ -1,971 +0,0 @@
-/****************************************************************************
-**
-** 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 "qwasmeventtranslator.h"
-#include "qwasmeventdispatcher.h"
-#include "qwasmcompositor.h"
-#include "qwasmintegration.h"
-#include "qwasmclipboard.h"
-#include "qwasmstring.h"
-
-#include <QtGui/qevent.h>
-#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtCore/qglobal.h>
-#include <QtCore/qobject.h>
-
-#include <QtCore/qdeadlinetimer.h>
-#include <private/qmakearray_p.h>
-#include <QtCore/qnamespace.h>
-
-#include <emscripten/bind.h>
-
-#include <iostream>
-
-using namespace emscripten;
-
-QT_BEGIN_NAMESPACE
-
-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, 'D','i','g','i','t','0' >,
- Emkb2Qt< Qt::Key_1, 'D','i','g','i','t','1' >,
- Emkb2Qt< Qt::Key_2, 'D','i','g','i','t','2' >,
- Emkb2Qt< Qt::Key_3, 'D','i','g','i','t','3' >,
- Emkb2Qt< Qt::Key_4, 'D','i','g','i','t','4' >,
- Emkb2Qt< Qt::Key_5, 'D','i','g','i','t','5' >,
- Emkb2Qt< Qt::Key_6, 'D','i','g','i','t','6' >,
- Emkb2Qt< Qt::Key_7, 'D','i','g','i','t','7' >,
- Emkb2Qt< Qt::Key_8, 'D','i','g','i','t','8' >,
- Emkb2Qt< Qt::Key_9, 'D','i','g','i','t','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_usePlatformMacSpecifics = false;
-
-bool g_useNaturalScrolling = true; // natural scrolling is default on linux/windows
-
-static void mouseWheelEvent(emscripten::val event) {
-
- emscripten::val wheelInterted = event["webkitDirectionInvertedFromDevice"];
-
- if (wheelInterted.as<bool>()) {
- g_useNaturalScrolling = true;
- }
-}
-
-EMSCRIPTEN_BINDINGS(qtMouseModule) {
- function("qtMouseWheelEvent", &mouseWheelEvent);
-}
-
-QWasmEventTranslator::QWasmEventTranslator(QWasmScreen *screen)
- : QObject(screen)
- , draggedWindow(nullptr)
- , lastWindow(nullptr)
- , pressedButtons(Qt::NoButton)
- , resizeMode(QWasmWindow::ResizeNone)
-{
- touchDevice = new QPointingDevice;
- touchDevice->setType(QInputDevice::DeviceType::TouchScreen);
- touchDevice->setCapabilities(QPointingDevice::Capability::Position | QPointingDevice::Capability::Area | QPointingDevice::Capability::NormalizedPosition);
- QWindowSystemInterface::registerInputDevice(touchDevice);
-
- initEventHandlers();
-}
-
-void QWasmEventTranslator::initEventHandlers()
-{
- QByteArray canvasSelector = "#" + screen()->canvasId().toUtf8();
-
- // The Platform Detect: expand coverage and move as needed
- enum Platform {
- GenericPlatform,
- MacOSPlatform
- };
- Platform platform = Platform(emscripten::val::global("navigator")["platform"]
- .call<bool>("includes", emscripten::val("Mac")));
- g_usePlatformMacSpecifics = (platform == MacOSPlatform);
-
- if (platform == MacOSPlatform) {
- g_useNaturalScrolling = false; // make this !default on macOS
-
- if (emscripten::val::global("window")["safari"].isUndefined()) {
- val canvas = screen()->canvas();
- canvas.call<void>("addEventListener",
- val("wheel"),
- val::module_property("qtMouseWheelEvent"));
- }
- }
-
- emscripten_set_keydown_callback(canvasSelector.constData(), (void *)this, 1, &keyboard_cb);
- emscripten_set_keyup_callback(canvasSelector.constData(), (void *)this, 1, &keyboard_cb);
-
- emscripten_set_mousedown_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb);
- emscripten_set_mouseup_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb);
- emscripten_set_mousemove_callback(canvasSelector.constData(), (void *)this, 1, &mouse_cb);
-
- emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, 1, &focus_cb);
-
- emscripten_set_wheel_callback(canvasSelector.constData(), (void *)this, 1, &wheel_cb);
-
- emscripten_set_touchstart_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback);
- emscripten_set_touchend_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback);
- emscripten_set_touchmove_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback);
- emscripten_set_touchcancel_callback(canvasSelector.constData(), (void *)this, 1, &touchCallback);
-}
-
-template <typename Event>
-QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translatKeyModifier(const Event *event)
-{
- QFlags<Qt::KeyboardModifier> keyModifier = Qt::NoModifier;
- if (event->shiftKey)
- keyModifier |= Qt::ShiftModifier;
- if (event->ctrlKey) {
- if (g_usePlatformMacSpecifics)
- keyModifier |= Qt::MetaModifier;
- else
- keyModifier |= Qt::ControlModifier;
- }
- if (event->altKey)
- keyModifier |= Qt::AltModifier;
- if (event->metaKey) {
- if (g_usePlatformMacSpecifics)
- keyModifier |= Qt::ControlModifier;
- else
- keyModifier |= Qt::MetaModifier;
- }
- return keyModifier;
-}
-
-QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent)
-{
- QFlags<Qt::KeyboardModifier> keyModifier = translatKeyModifier(keyEvent);
- if (keyEvent->location == DOM_KEY_LOCATION_NUMPAD) {
- keyModifier |= Qt::KeypadModifier;
- }
-
- return keyModifier;
-}
-
-QFlags<Qt::KeyboardModifier> QWasmEventTranslator::translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent)
-{
- return translatKeyModifier(mouseEvent);
-}
-
-int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
-{
- QWasmEventTranslator *wasmTranslator = reinterpret_cast<QWasmEventTranslator *>(userData);
- bool accepted = wasmTranslator->processKeyboard(eventType, keyEvent);
-
- return accepted ? 1 : 0;
-}
-
-QWasmScreen *QWasmEventTranslator::screen()
-{
- return static_cast<QWasmScreen *>(parent());
-}
-
-Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey)
-{
- Qt::Key qtKey = Qt::Key_unknown;
-
- 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);
- }
-
- } else if (qstrncmp(emscriptKey->code, "Key", 3) == 0 || qstrncmp(emscriptKey->code, "Numpad", 6) == 0 ||
- qstrncmp(emscriptKey->code, "Digit", 5) == 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);
- }
- }
-
- 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;
-}
-
-Qt::MouseButton QWasmEventTranslator::translateMouseButton(unsigned short button)
-{
- if (button == 0)
- return Qt::LeftButton;
- else if (button == 1)
- return Qt::MiddleButton;
- else if (button == 2)
- return Qt::RightButton;
-
- return Qt::NoButton;
-}
-
-int QWasmEventTranslator::mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
-{
- QWasmEventTranslator *translator = (QWasmEventTranslator*)userData;
- translator->processMouse(eventType,mouseEvent);
- QWasmEventDispatcher::maintainTimers();
- return 0;
-}
-
-void resizeWindow(QWindow *window, QWasmWindow::ResizeMode mode,
- QRect startRect, QPoint amount)
-{
- if (mode == QWasmWindow::ResizeNone)
- return;
-
- bool top = mode == QWasmWindow::ResizeTopLeft ||
- mode == QWasmWindow::ResizeTop ||
- mode == QWasmWindow::ResizeTopRight;
-
- bool bottom = mode == QWasmWindow::ResizeBottomLeft ||
- mode == QWasmWindow::ResizeBottom ||
- mode == QWasmWindow::ResizeBottomRight;
-
- bool left = mode == QWasmWindow::ResizeLeft ||
- mode == QWasmWindow::ResizeTopLeft ||
- mode == QWasmWindow::ResizeBottomLeft;
-
- bool right = mode == QWasmWindow::ResizeRight ||
- mode == QWasmWindow::ResizeTopRight ||
- mode == QWasmWindow::ResizeBottomRight;
-
- int x1 = startRect.left();
- int y1 = startRect.top();
- int x2 = startRect.right();
- int y2 = startRect.bottom();
-
- if (left)
- x1 += amount.x();
- if (top)
- y1 += amount.y();
- if (right)
- x2 += amount.x();
- if (bottom)
- y2 += amount.y();
-
- int w = x2-x1;
- int h = y2-y1;
-
- if (w < window->minimumWidth()) {
- if (left)
- x1 -= window->minimumWidth() - w;
-
- w = window->minimumWidth();
- }
-
- if (h < window->minimumHeight()) {
- if (top)
- y1 -= window->minimumHeight() - h;
-
- h = window->minimumHeight();
- }
-
- window->setGeometry(x1, y1, w, h);
-}
-
-void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent)
-{
- auto timestamp = emscripten_date_now();
- 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 = screen()->compositor()->windowAt(globalPoint, 5);
-
- if (window2 == nullptr) {
- window2 = lastWindow;
- } else {
- lastWindow = window2;
- }
-
- QPoint localPoint = window2->mapFromGlobal(globalPoint);
- bool interior = window2->geometry().contains(globalPoint);
-
- QWasmWindow *htmlWindow = static_cast<QWasmWindow*>(window2->handle());
- switch (eventType) {
- case EMSCRIPTEN_EVENT_MOUSEDOWN:
- {
- if (window2)
- window2->requestActivate();
-
- pressedButtons.setFlag(button);
-
- if (mouseEvent->button == 0) {
- 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(globalPoint))
- draggedWindow = window2;
- else if (htmlWindow && htmlWindow->isPointOnResizeRegion(globalPoint)) {
- draggedWindow = window2;
- resizeMode = htmlWindow->resizeModeAtPoint(globalPoint);
- resizePoint = globalPoint;
- resizeStartRect = window2->geometry();
- }
- }
- }
-
- htmlWindow->injectMousePressed(localPoint, globalPoint, button, modifiers);
- break;
- }
- case EMSCRIPTEN_EVENT_MOUSEUP:
- {
- pressedButtons.setFlag(translateMouseButton(mouseEvent->button), false);
- buttonEventType = QEvent::MouseButtonRelease;
- QWasmWindow *oldWindow = nullptr;
-
- if (mouseEvent->button == 0 && pressedWindow) {
- oldWindow = static_cast<QWasmWindow*>(pressedWindow->handle());
- pressedWindow = nullptr;
- }
-
- if (mouseEvent->button == 0) {
- draggedWindow = nullptr;
- resizeMode = QWasmWindow::ResizeNone;
- }
-
- if (oldWindow)
- oldWindow->injectMouseReleased(localPoint, globalPoint, button, modifiers);
- break;
- }
- case EMSCRIPTEN_EVENT_MOUSEMOVE: // drag event
- {
- buttonEventType = QEvent::MouseMove;
- if (!(htmlWindow->m_windowState & Qt::WindowFullScreen) && !(htmlWindow->m_windowState & Qt::WindowMaximized)) {
- if (resizeMode == QWasmWindow::ResizeNone && draggedWindow) {
- draggedWindow->setX(draggedWindow->x() + mouseEvent->movementX);
- draggedWindow->setY(draggedWindow->y() + mouseEvent->movementY);
- }
-
- if (resizeMode != QWasmWindow::ResizeNone && !(htmlWindow->m_windowState & Qt::WindowFullScreen)) {
- QPoint delta = QPoint(mouseEvent->targetX, mouseEvent->targetY) - resizePoint;
- resizeWindow(draggedWindow, resizeMode, resizeStartRect, delta);
- }
- }
- break;
- }
- default: // MOUSELEAVE MOUSEENTER
- break;
- };
- if (!window2 && buttonEventType == QEvent::MouseButtonRelease) {
- window2 = lastWindow;
- lastWindow = nullptr;
- interior = true;
- }
- if (window2 && interior) {
- QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>(
- window2, timestamp, localPoint, globalPoint, pressedButtons, button, buttonEventType, modifiers);
- }
-}
-
-int QWasmEventTranslator::focus_cb(int /*eventType*/, const EmscriptenFocusEvent */*focusEvent*/, void */*userData*/)
-{
- return 0;
-}
-
-int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData)
-{
- Q_UNUSED(eventType);
-
- QWasmEventTranslator *eventTranslator = static_cast<QWasmEventTranslator *>(userData);
- EmscriptenMouseEvent mouseEvent = wheelEvent->mouse;
-
- int scrollFactor = 0;
- switch (wheelEvent->deltaMode) {
- case DOM_DELTA_PIXEL://chrome safari
- scrollFactor = 1;
- break;
- case DOM_DELTA_LINE: //firefox
- scrollFactor = 12;
- break;
- case DOM_DELTA_PAGE:
- scrollFactor = 20;
- break;
- };
-
- if (g_useNaturalScrolling) //macOS platform has document oriented scrolling
- scrollFactor = -scrollFactor;
-
- QWasmEventTranslator *translator = (QWasmEventTranslator*)userData;
- Qt::KeyboardModifiers modifiers = translator->translateMouseEventModifier(&mouseEvent);
- auto timestamp = emscripten_date_now();
- QPoint targetPoint(mouseEvent.targetX, mouseEvent.targetY);
- QPoint globalPoint = eventTranslator->screen()->geometry().topLeft() + targetPoint;
-
- 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);
- QWasmEventDispatcher::maintainTimers();
-
- return 1;
-}
-
-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;
-
- for (int i = 0; i < touchEvent->numTouches; i++) {
-
- const EmscriptenTouchPoint *touches = &touchEvent->touches[i];
-
- 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;
-
- touchPoint.area = QRect(0, 0, 8, 8);
- touchPoint.id = touches->identifier;
- 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:
- if (tp != pressedTouchIds.constEnd()) {
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
- } else {
- touchPoint.state = QEventPoint::State::Pressed;
- }
- pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
-
- break;
- case EMSCRIPTEN_EVENT_TOUCHEND:
- touchPoint.state = QEventPoint::State::Released;
- pressedTouchIds.remove(touchPoint.id);
- break;
- case EMSCRIPTEN_EVENT_TOUCHMOVE:
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
-
- pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
- break;
- default:
- break;
- }
-
- touchPointList.append(touchPoint);
- }
-
- QFlags<Qt::KeyboardModifier> keyModifier = translatKeyModifier(touchEvent);
-
- QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
- window2, getTimestamp(), touchDevice, touchPointList, keyModifier);
-
- if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)
- QWindowSystemInterface::handleTouchCancelEvent(window2, getTimestamp(), touchDevice, keyModifier);
-
- QWasmEventDispatcher::maintainTimers();
- return 1;
-}
-
-quint64 QWasmEventTranslator::getTimestamp()
-{
- return QDeadlineTimer::current().deadlineNSecs() / 1000;
-}
-
-struct KeyMapping { Qt::Key from, to; };
-
-constexpr KeyMapping tildeKeyTable[] = { // ~
- { Qt::Key_A, Qt::Key_Atilde },
- { Qt::Key_N, Qt::Key_Ntilde },
- { Qt::Key_O, Qt::Key_Otilde },
-};
-constexpr KeyMapping 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 },
-};
-constexpr KeyMapping 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 },
-};
-constexpr KeyMapping 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 },
-};
-constexpr KeyMapping 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 },
-};
-
-static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
-{
- while (first != last) {
- if (first->from == key)
- return first->to;
- ++first;
- }
- return Qt::Key_unknown;
-}
-
-template <size_t N>
-static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
-{
- return find_impl(map, map + N, key);
-}
-
-Qt::Key QWasmEventTranslator::translateDeadKey(Qt::Key deadKey, Qt::Key accentBaseKey)
-{
- Qt::Key wasmKey = Qt::Key_unknown;
-
- if (deadKey == Qt::Key_QuoteLeft ) {
- if (g_usePlatformMacSpecifics) { // ` macOS: Key_Dead_Grave
- wasmKey = find(graveKeyTable, accentBaseKey);
- } else {
- wasmKey = find(diaeresisKeyTable, accentBaseKey);
- }
- return wasmKey;
- }
-
- switch (deadKey) {
- // case Qt::Key_QuoteLeft:
- case Qt::Key_O: // ´ Key_Dead_Grave
- wasmKey = find(graveKeyTable, accentBaseKey);
- break;
- case Qt::Key_E: // ´ Key_Dead_Acute
- wasmKey = find(acuteKeyTable, accentBaseKey);
- break;
- case Qt::Key_AsciiTilde:
- case Qt::Key_N:// Key_Dead_Tilde
- wasmKey = find(tildeKeyTable, accentBaseKey);
- break;
- case Qt::Key_U:// ¨ Key_Dead_Diaeresis
- wasmKey = find(diaeresisKeyTable, accentBaseKey);
- break;
- case Qt::Key_I:// macOS Key_Dead_Circumflex
- case Qt::Key_6:// linux
- case Qt::Key_Apostrophe:// linux
- wasmKey = find(circumflexKeyTable, accentBaseKey);
- 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;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h
deleted file mode 100644
index f6c95770e9..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/****************************************************************************
-**
-** 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 QWASMEVENTTRANSLATOR_H
-#define QWASMEVENTTRANSLATOR_H
-
-#include <QtCore/qobject.h>
-#include <QtCore/qrect.h>
-#include <QtCore/qpoint.h>
-#include <emscripten/html5.h>
-#include "qwasmwindow.h"
-#include <QtGui/qpointingdevice.h>
-#include <QHash>
-
-QT_BEGIN_NAMESPACE
-
-class QWindow;
-
-class QWasmEventTranslator : public QObject
-{
- Q_OBJECT
-
-public:
-
- 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);
- static int focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData);
- static int wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData);
-
- static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData);
-
- void processEvents();
- void initEventHandlers();
- int handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent);
-
-Q_SIGNALS:
- void getWindowAt(const QPoint &point, QWindow **window);
-private:
- QWasmScreen *screen();
- Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey);
- template <typename Event>
- 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);
-
- QMap <int, QPointF> pressedTouchIds;
-
-private:
- QWindow *draggedWindow;
- QWindow *pressedWindow;
- QWindow *lastWindow;
- Qt::MouseButtons pressedButtons;
-
- QWasmWindow::ResizeMode resizeMode;
- QPoint resizePoint;
- QRect resizeStartRect;
- QPointingDevice *touchDevice;
- quint64 getTimestamp();
-
- Qt::Key m_emDeadKey = Qt::Key_unknown;
- bool m_emStickyDeadKey = false;
-};
-
-QT_END_NAMESPACE
-#endif // QWASMEVENTTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
index 7623444588..3f3dc10f71 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
@@ -1,47 +1,272 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmfontdatabase.h"
+#include "qwasmintegration.h"
#include <QtCore/qfile.h>
+#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+
+#include <emscripten.h>
+#include <emscripten/val.h>
+#include <emscripten/bind.h>
+
+#include <map>
+#include <array>
QT_BEGIN_NAMESPACE
-void QWasmFontDatabase::populateFontDatabase()
+using namespace emscripten;
+using namespace Qt::StringLiterals;
+
+
+namespace {
+
+class FontData
+{
+public:
+ FontData(val fontData)
+ :m_fontData(fontData) {}
+
+ QString family() const
+ {
+ return QString::fromStdString(m_fontData["family"].as<std::string>());
+ }
+
+ QString fullName() const
+ {
+ return QString::fromStdString(m_fontData["fullName"].as<std::string>());
+ }
+
+ QString postscriptName() const
+ {
+ return QString::fromStdString(m_fontData["postscriptName"].as<std::string>());
+ }
+
+ QString style() const
+ {
+ return QString::fromStdString(m_fontData["style"].as<std::string>());
+ }
+
+ val value() const
+ {
+ return m_fontData;
+ }
+
+private:
+ val m_fontData;
+};
+
+val makeObject(const char *key, const char *value)
+{
+ val obj = val::object();
+ obj.set(key, std::string(value));
+ return obj;
+}
+
+void printError(val err) {
+ qCWarning(lcQpaFonts)
+ << QString::fromStdString(err["name"].as<std::string>())
+ << QString::fromStdString(err["message"].as<std::string>());
+ QWasmFontDatabase::endAllFontFileLoading();
+}
+
+void checkFontAccessPermitted(std::function<void(bool)> callback)
+{
+ const val permissions = val::global("navigator")["permissions"];
+ if (permissions.isUndefined()) {
+ callback(false);
+ return;
+ }
+
+ qstdweb::Promise::make(permissions, "query", {
+ .thenFunc = [callback](val status) {
+ callback(status["state"].as<std::string>() == "granted");
+ },
+ }, makeObject("name", "local-fonts"));
+}
+
+void queryLocalFonts(std::function<void(const QList<FontData> &)> callback)
+{
+ emscripten::val window = emscripten::val::global("window");
+ qstdweb::Promise::make(window, "queryLocalFonts", {
+ .thenFunc = [callback](emscripten::val fontArray) {
+ QList<FontData> fonts;
+ const int count = fontArray["length"].as<int>();
+ fonts.reserve(count);
+ for (int i = 0; i < count; ++i)
+ fonts.append(FontData(fontArray.call<emscripten::val>("at", i)));
+ callback(fonts);
+ },
+ .catchFunc = printError
+ });
+}
+
+void readBlob(val blob, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(blob, "arrayBuffer", {
+ .thenFunc = [callback](emscripten::val fontArrayBuffer) {
+ QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
+ callback(fontData);
+ },
+ .catchFunc = printError
+ });
+}
+
+void readFont(FontData font, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(font.value(), "blob", {
+ .thenFunc = [callback](val blob) {
+ readBlob(blob, [callback](const QByteArray &data) {
+ callback(data);
+ });
+ },
+ .catchFunc = printError
+ });
+}
+
+emscripten::val getLocalFontsConfigProperty(const char *name) {
+ emscripten::val qt = val::module_property("qt");
+ if (qt.isUndefined())
+ return emscripten::val();
+ emscripten::val localFonts = qt["localFonts"];
+ if (localFonts.isUndefined())
+ return emscripten::val();
+ return localFonts[name];
+};
+
+bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return prop.as<bool>();
+};
+
+QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return QString::fromStdString(prop.as<std::string>());
+};
+
+QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) {
+ emscripten::val array = getLocalFontsConfigProperty(name);
+ if (array.isUndefined())
+ return defaultValue;
+
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
+ }
+ return list;
+};
+
+} // namespace
+
+QWasmFontDatabase::QWasmFontDatabase()
+:QFreeTypeFontDatabase()
+{
+ m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false;
+ if (m_localFontsApiSupported)
+ beginFontDatabaseStartupTask();
+}
+
+QWasmFontDatabase *QWasmFontDatabase::get()
+{
+ return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase());
+}
+
+// Populates the font database with local fonts. Will make the browser ask
+// the user for permission if needed. Does nothing if the Local Font Access API
+// is not supported.
+void QWasmFontDatabase::populateLocalfonts()
{
- // Load font file from resources. Currently
- // all fonts needs to be bundled with the nexe
- // as Qt resources.
+ // Decide which font families to populate based on user preferences
+ QStringList selectedLocalFontFamilies;
+ bool allFamilies = false;
+
+ switch (m_localFontFamilyLoadSet) {
+ case NoFontFamilies:
+ default:
+ // keep empty selectedLocalFontFamilies
+ break;
+ case DefaultFontFamilies: {
+ const QStringList webSafeFontFamilies =
+ {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
+ "Georgia", "Garamond", "Courier New"};
+ selectedLocalFontFamilies = webSafeFontFamilies;
+ } break;
+ case AllFontFamilies:
+ allFamilies = true;
+ break;
+ }
+
+ selectedLocalFontFamilies += m_extraLocalFontFamilies;
+ if (selectedLocalFontFamilies.isEmpty() && !allFamilies) {
+ endAllFontFileLoading();
+ return;
+ }
+
+ populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies);
+}
+
+namespace {
+ QStringList toStringList(emscripten::val array)
+ {
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
+ }
+ return list;
+ }
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families)
+{
+ if (!m_localFontsApiSupported)
+ return;
+ populateLocalFontFamilies(toStringList(families), false);
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies)
+{
+ queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) {
+ refFontFileLoading();
+ QList<FontData> filteredFonts;
+ std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts),
+ [fontFamilies, allFamilies](FontData fontData) {
+ return allFamilies || fontFamilies.contains(fontData.family());
+ });
+
+ for (const FontData &font: filteredFonts) {
+ refFontFileLoading();
+ readFont(font, [font](const QByteArray &fontData){
+ QFreeTypeFontDatabase::registerFontFamily(font.family());
+ QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
+ derefFontFileLoading();
+ });
+ }
+ derefFontFileLoading();
+ });
+
+}
+
+void QWasmFontDatabase::populateFontDatabase()
+{
+ // Load bundled font file from resources.
const QString fontFileNames[] = {
QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
- QStringLiteral(":/fonts/Vera.ttf"),
QStringLiteral(":/fonts/DejaVuSans.ttf"),
};
for (const QString &fontFileName : fontFileNames) {
@@ -51,11 +276,45 @@ void QWasmFontDatabase::populateFontDatabase()
QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
}
+
+ // Get config options for controlling local fonts usage
+ m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
+ QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
+ m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
+
+ if (fontFamilyLoadSet == "NoFontFamilies") {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
+ m_localFontFamilyLoadSet = DefaultFontFamilies;
+ } else if (fontFamilyLoadSet == "AllFontFamilies") {
+ m_localFontFamilyLoadSet = AllFontFamilies;
+ } else {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
+ }
+
+ if (!m_localFontsApiSupported)
+ return;
+
+ // Populate the font database with local fonts. Either try unconditianlly
+ // if displyaing a fonts permissions dialog at startup is allowed, or else
+ // only if we already have permission.
+ if (m_queryLocalFontsPermission) {
+ populateLocalfonts();
+ } else {
+ checkFontAccessPermitted([this](bool granted) {
+ if (granted)
+ populateLocalfonts();
+ else
+ endAllFontFileLoading();
+ });
+ }
}
QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
{
- return QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ return fontEngine;
}
QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
@@ -65,11 +324,13 @@ QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::
QStringList fallbacks
= QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
- // Add the vera.ttf font (loaded in populateFontDatabase above) as a falback font
+ // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
// to all other fonts (except itself).
- const QString veraFontFamily = QStringLiteral("Bitstream Vera Sans");
- if (family != veraFontFamily)
- fallbacks.append(veraFontFamily);
+ static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
+ for (auto wasmFallbackFont : wasmFallbackFonts) {
+ if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
+ fallbacks.append(wasmFallbackFont);
+ }
return fallbacks;
}
@@ -81,7 +342,63 @@ void QWasmFontDatabase::releaseHandle(void *handle)
QFont QWasmFontDatabase::defaultFont() const
{
- return QFont(QLatin1String("Bitstream Vera Sans"));
+ return QFont("DejaVu Sans"_L1);
}
+namespace {
+ int g_pendingFonts = 0;
+ bool g_fontStartupTaskCompleted = false;
+}
+
+// Registers font loading as a startup task, which makes Qt delay
+// sending onLoaded event until font loading has completed.
+void QWasmFontDatabase::beginFontDatabaseStartupTask()
+{
+ g_fontStartupTaskCompleted = false;
+ QEventDispatcherWasm::registerStartupTask();
+}
+
+// Ends the font loading startup task.
+void QWasmFontDatabase::endFontDatabaseStartupTask()
+{
+ if (!g_fontStartupTaskCompleted) {
+ g_fontStartupTaskCompleted = true;
+ QEventDispatcherWasm::completeStarupTask();
+ }
+}
+
+// Registers that a font file will be loaded.
+void QWasmFontDatabase::refFontFileLoading()
+{
+ g_pendingFonts += 1;
+}
+
+// Registers that one font file has been loaded, and sends notifactions
+// when all pending font files have been loaded.
+void QWasmFontDatabase::derefFontFileLoading()
+{
+ if (--g_pendingFonts <= 0) {
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ endFontDatabaseStartupTask();
+ }
+}
+
+// Unconditionally ends local font loading, for instance if there
+// are no fonts to load or if there was an unexpected error.
+void QWasmFontDatabase::endAllFontFileLoading()
+{
+ bool hadPandingfonts = g_pendingFonts > 0;
+ if (hadPandingfonts) {
+ // The hadPandingfonts counter might no longer be correct; disable counting
+ // and send notifications unconditionally.
+ g_pendingFonts = 0;
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ }
+
+ endFontDatabaseStartupTask();
+}
+
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h
index 420211947c..a1c8f1ff48 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.h
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h
@@ -1,42 +1,21 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMFONTDATABASE_H
#define QWASMFONTDATABASE_H
#include <QtGui/private/qfreetypefontdatabase_p.h>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
class QWasmFontDatabase : public QFreeTypeFontDatabase
{
public:
+ QWasmFontDatabase();
+ static QWasmFontDatabase *get();
+
void populateFontDatabase() override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
@@ -44,6 +23,27 @@ public:
QChar::Script script) const override;
void releaseHandle(void *handle) override;
QFont defaultFont() const override;
+
+ void populateLocalfonts();
+ void populateLocalFontFamilies(emscripten::val families);
+ void populateLocalFontFamilies(const QStringList &famliies, bool allFamilies);
+
+ static void beginFontDatabaseStartupTask();
+ static void endFontDatabaseStartupTask();
+ static void refFontFileLoading();
+ static void derefFontFileLoading();
+ static void endAllFontFileLoading();
+
+private:
+ bool m_localFontsApiSupported = false;
+ bool m_queryLocalFontsPermission = false;
+ enum FontFamilyLoadSet {
+ NoFontFamilies,
+ DefaultFontFamilies,
+ AllFontFamilies,
+ };
+ FontFamilyLoadSet m_localFontFamilyLoadSet;
+ QStringList m_extraLocalFontFamilies;
};
QT_END_NAMESPACE
#endif
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
new file mode 100644
index 0000000000..ae72e7b7f9
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -0,0 +1,161 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <emscripten/bind.h>
+
+#include "qwasminputcontext.h"
+#include "qwasmintegration.h"
+#include "qwasmplatform.h"
+#include <QRectF>
+#include <qpa/qplatforminputcontext.h>
+#include "qwasmscreen.h"
+#include <qguiapplication.h>
+#include <qwindow.h>
+#include <QKeySequence>
+#include <qpa/qwindowsysteminterface.h>
+
+QT_BEGIN_NAMESPACE
+using namespace qstdweb;
+
+static void inputCallback(emscripten::val event)
+{
+ // android sends all the characters typed since the user started typing in this element
+ int length = event["target"]["value"]["length"].as<int>();
+ if (length <= 0)
+ return;
+
+ emscripten::val _incomingCharVal = event["data"];
+ if (_incomingCharVal != emscripten::val::undefined() && _incomingCharVal != emscripten::val::null()) {
+
+ QString str = QString::fromStdString(_incomingCharVal.as<std::string>());
+ QWasmInputContext *wasmInput =
+ reinterpret_cast<QWasmInputContext*>(event["target"]["data-qinputcontext"].as<quintptr>());
+ wasmInput->inputStringChanged(str, EMSCRIPTEN_EVENT_KEYDOWN, wasmInput);
+ }
+ // this clears the input string, so backspaces do not send a character
+ // but stops suggestions
+ event["target"].set("value", "");
+}
+
+EMSCRIPTEN_BINDINGS(clipboard_module) {
+ function("qtInputContextCallback", &inputCallback);
+}
+
+QWasmInputContext::QWasmInputContext()
+{
+ emscripten::val document = emscripten::val::global("document");
+ m_inputElement = document.call<emscripten::val>("createElement", std::string("input"));
+ m_inputElement.set("type", "text");
+ m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
+ m_inputElement.set("contenteditable","true");
+
+ if (platform() == Platform::MacOS || platform() == Platform::iOS) {
+ auto callback = [=](emscripten::val) {
+ m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
+ inputPanelIsOpen = false;
+ };
+ m_blurEventHandler.reset(new EventCallback(m_inputElement, "blur", callback));
+
+ } else {
+
+ const std::string inputType = platform() == Platform::Windows ? "textInput" : "input";
+
+ document.call<void>("addEventListener", inputType,
+ emscripten::val::module_property("qtInputContextCallback"),
+ emscripten::val(false));
+ m_inputElement.set("data-qinputcontext",
+ emscripten::val(quintptr(reinterpret_cast<void *>(this))));
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", m_inputElement);
+ }
+
+ QObject::connect(qGuiApp, &QGuiApplication::focusWindowChanged, this,
+ &QWasmInputContext::focusWindowChanged);
+}
+
+QWasmInputContext::~QWasmInputContext()
+{
+ if (platform() == Platform::Android || platform() == Platform::Windows)
+ emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
+}
+
+void QWasmInputContext::focusWindowChanged(QWindow *focusWindow)
+{
+ m_focusWindow = focusWindow;
+}
+
+emscripten::val QWasmInputContext::inputHandlerElementForFocusedWindow()
+{
+ if (!m_focusWindow)
+ return emscripten::val::undefined();
+ return static_cast<QWasmWindow *>(m_focusWindow->handle())->inputHandlerElement();
+}
+
+void QWasmInputContext::update(Qt::InputMethodQueries queries)
+{
+ QPlatformInputContext::update(queries);
+}
+
+void QWasmInputContext::showInputPanel()
+{
+ if (platform() == Platform::Windows
+ && !inputPanelIsOpen) { // call this only once for win32
+ m_inputElement.call<void>("focus");
+ return;
+ }
+ // this is called each time the keyboard is touched
+
+ // Add the input element as a child of the screen for the
+ // currently focused window and give it focus. The browser
+ // will not display the input element, but mobile browsers
+ // should display the virtual keyboard. Key events will be
+ // captured by the keyboard event handler installed on the
+ // screen element.
+
+ if (platform() == Platform::MacOS // keep for compatibility
+ || platform() == Platform::iOS
+ || platform() == Platform::Windows) {
+ emscripten::val inputWrapper = inputHandlerElementForFocusedWindow();
+ if (inputWrapper.isUndefined())
+ return;
+ inputWrapper.call<void>("appendChild", m_inputElement);
+ }
+
+ m_inputElement.call<void>("focus");
+ inputPanelIsOpen = true;
+}
+
+void QWasmInputContext::hideInputPanel()
+{
+ if (QWasmIntegration::get()->touchPoints < 1)
+ return;
+ m_inputElement.call<void>("blur");
+ inputPanelIsOpen = false;
+}
+
+void QWasmInputContext::inputStringChanged(QString &inputString, int eventType, QWasmInputContext *context)
+{
+ Q_UNUSED(context)
+ QKeySequence keys = QKeySequence::fromString(inputString);
+ Qt::Key thisKey = keys[0].key();
+
+ // synthesize this keyevent as android is not normal
+ if (inputString.size() > 2 && (thisKey < Qt::Key_F35
+ || thisKey > Qt::Key_Back)) {
+ inputString.clear();
+ }
+ if (inputString == QStringLiteral("Escape")) {
+ thisKey = Qt::Key_Escape;
+ inputString.clear();
+ } else if (thisKey == Qt::Key(0)) {
+ thisKey = Qt::Key_Return;
+ }
+
+ QWindowSystemInterface::handleKeyEvent(
+ 0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? QEvent::KeyPress : QEvent::KeyRelease,
+ thisKey, keys[0].keyboardModifiers(),
+ eventType == EMSCRIPTEN_EVENT_KEYDOWN ? inputString : QStringLiteral(""));
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
new file mode 100644
index 0000000000..10dd1a0950
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMINPUTCONTEXT_H
+#define QWASMINPUTCONTEXT_H
+
+
+#include <qpa/qplatforminputcontext.h>
+#include <QtCore/qpointer.h>
+#include <private/qstdweb_p.h>
+#include <emscripten/bind.h>
+#include <emscripten/html5.h>
+#include <emscripten/emscripten.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmInputContext : public QPlatformInputContext
+{
+ Q_DISABLE_COPY(QWasmInputContext)
+ Q_OBJECT
+public:
+ explicit QWasmInputContext();
+ ~QWasmInputContext() override;
+
+ void update(Qt::InputMethodQueries) override;
+
+ void showInputPanel() override;
+ void hideInputPanel() override;
+ bool isValid() const override { return true; }
+
+ void focusWindowChanged(QWindow *focusWindow);
+ void inputStringChanged(QString &, int eventType, QWasmInputContext *context);
+ emscripten::val m_inputElement = emscripten::val::null();
+
+private:
+ emscripten::val inputHandlerElementForFocusedWindow();
+
+ bool m_inputPanelVisible = false;
+
+ QPointer<QWindow> m_focusWindow;
+ std::unique_ptr<qstdweb::EventCallback> m_blurEventHandler;
+ std::unique_ptr<qstdweb::EventCallback> m_inputEventHandler;
+ bool inputPanelIsOpen = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASMINPUTCONTEXT_H
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index 15d396f479..f5cc3e2eee 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -1,85 +1,61 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmintegration.h"
-#include "qwasmeventtranslator.h"
#include "qwasmeventdispatcher.h"
#include "qwasmcompositor.h"
#include "qwasmopenglcontext.h"
#include "qwasmtheme.h"
#include "qwasmclipboard.h"
+#include "qwasmaccessibility.h"
#include "qwasmservices.h"
#include "qwasmoffscreensurface.h"
-#include "qwasmstring.h"
-
+#include "qwasmplatform.h"
#include "qwasmwindow.h"
-#ifndef QT_NO_OPENGL
-# include "qwasmbackingstore.h"
-# include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
-#endif
+#include "qwasmbackingstore.h"
#include "qwasmfontdatabase.h"
-#if defined(Q_OS_UNIX)
-#include <QtGui/private/qgenericunixeventdispatcher_p.h>
-#endif
+#include "qwasmdrag.h"
+
#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 <emscripten/bind.h>
#include <emscripten/val.h>
// this is where EGL headers are pulled in, make sure it is last
#include "qwasmscreen.h"
+#include <private/qsimpledrag_p.h>
-using namespace emscripten;
QT_BEGIN_NAMESPACE
-static void browserBeforeUnload(emscripten::val)
+extern void qt_set_sequence_auto_mnemonic(bool);
+
+using namespace emscripten;
+
+using namespace Qt::StringLiterals;
+
+static void setContainerElements(emscripten::val elementArray)
{
- QWasmIntegration::QWasmBrowserExit();
+ QWasmIntegration::get()->setContainerElements(elementArray);
}
-static void addCanvasElement(emscripten::val canvas)
+static void addContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->addScreen(canvas);
+ QWasmIntegration::get()->addContainerElement(element);
}
-static void removeCanvasElement(emscripten::val canvas)
+static void removeContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->removeScreen(canvas);
+ QWasmIntegration::get()->removeContainerElement(element);
}
-static void resizeCanvasElement(emscripten::val canvas)
+static void resizeContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->resizeScreen(canvas);
+ QWasmIntegration::get()->resizeScreen(element);
}
static void qtUpdateDpi()
@@ -93,83 +69,108 @@ static void resizeAllScreens(emscripten::val event)
QWasmIntegration::get()->resizeAllScreens();
}
+static void loadLocalFontFamilies(emscripten::val event)
+{
+ QWasmIntegration::get()->loadLocalFontFamilies(event);
+}
+
EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)
{
- function("qtBrowserBeforeUnload", &browserBeforeUnload);
- function("qtAddCanvasElement", &addCanvasElement);
- function("qtRemoveCanvasElement", &removeCanvasElement);
- function("qtResizeCanvasElement", &resizeCanvasElement);
+ 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),
- m_clipboard(new QWasmClipboard)
+ : m_fontDb(nullptr)
+ , m_desktopServices(nullptr)
+ , m_clipboard(new QWasmClipboard)
+#if QT_CONFIG(accessibility)
+ , m_accessibility(new QWasmAccessibility)
+#endif
{
s_instance = this;
- // We expect that qtloader.js has populated Module.qtCanvasElements with one or more canvases.
- emscripten::val qtCanvaseElements = val::module_property("qtCanvasElements");
- emscripten::val canvas = val::module_property("canvas"); // TODO: remove for Qt 6.0
-
- if (!qtCanvaseElements.isUndefined()) {
- int screenCount = qtCanvaseElements["length"].as<int>();
- for (int i = 0; i < screenCount; ++i) {
- addScreen(qtCanvaseElements[i].as<emscripten::val>());
+ if (platform() == Platform::MacOS)
+ qt_set_sequence_auto_mnemonic(false);
+
+ 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 if (!canvas.isUndefined()) {
- qWarning() << "Module.canvas is deprecated. A future version of Qt will stop reading this property. "
- << "Instead, set Module.qtCanvasElements to be an array of canvas elements, or use qtloader.js.";
- addScreen(canvas);
+ } 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.";
}
-
- emscripten::val::global("window").set("onbeforeunload", val::module_property("qtBrowserBeforeUnload"));
+ setContainerElements(filtered);
// install browser window resize handler
- auto onWindowResize = [](int eventType, const EmscriptenUiEvent *e, void *userData) -> int {
- Q_UNUSED(eventType);
- Q_UNUSED(e);
- Q_UNUSED(userData);
-
- // This resize event is called when the HTML window is resized. Depending
- // on the page layout the canvas(es) might also have been resized, so we
- // update the Qt screen sizes (and canvas render sizes).
- if (QWasmIntegration *integration = QWasmIntegration::get())
- integration->resizeAllScreens();
- return 0;
- };
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, 1, onWindowResize);
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE,
+ [](int, const EmscriptenUiEvent *, void *) -> int {
+ // 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 0;
+ });
// 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"));
+ val::module_property("qtResizeAllScreens"));
}
+ m_drag = std::make_unique<QWasmDrag>();
}
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 (m_platformInputContext)
+ delete m_platformInputContext;
+#if QT_CONFIG(accessibility)
+ delete m_accessibility;
+#endif
+
+ for (const auto &elementAndScreen : m_screens)
+ elementAndScreen.wasmScreen->deleteScreen();
- for (const auto &canvasAndScreen : m_screens)
- QWindowSystemInterface::handleScreenRemoved(canvasAndScreen.second);
m_screens.clear();
s_instance = nullptr;
}
-void QWasmIntegration::QWasmBrowserExit()
-{
- QCoreApplication *app = QCoreApplication::instance();
- app->quit();
-}
-
bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
switch (cap) {
@@ -186,20 +187,18 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
{
- QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
- return new QWasmWindow(window, compositor, m_backingStores.value(window));
+ auto *wasmScreen = QWasmScreen::get(window->screen());
+ QWasmCompositor *compositor = wasmScreen->compositor();
+ return new QWasmWindow(window, wasmScreen->deadKeySupport(), compositor,
+ m_backingStores.value(window));
}
QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
{
-#ifndef QT_NO_OPENGL
QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window);
m_backingStores.insert(window, backingStore);
return backingStore;
-#else
- return nullptr;
-#endif
}
void QWasmIntegration::removeBackingStore(QWindow* window)
@@ -207,18 +206,33 @@ void QWasmIntegration::removeBackingStore(QWindow* window)
m_backingStores.remove(window);
}
+void QWasmIntegration::releaseRequesetUpdateHold()
+{
+ if (QWasmCompositor::releaseRequestUpdateHold())
+ {
+ for (const auto &elementAndScreen : m_screens) {
+ elementAndScreen.wasmScreen->compositor()->requestUpdate();
+ }
+ }
+}
+
#ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- return new QWasmOpenGLContext(context->format());
+ return new QWasmOpenGLContext(context);
}
#endif
void QWasmIntegration::initialize()
{
- QString icStr = QPlatformInputContextFactory::requested();
- if (!icStr.isNull())
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty() && touchPoints < 1)
+ return;
+
+ if (!icStrs.isEmpty())
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
+ else
+ m_inputContext.reset(new QWasmInputContext());
}
QPlatformInputContext *QWasmIntegration::inputContext() const
@@ -228,7 +242,7 @@ QPlatformInputContext *QWasmIntegration::inputContext() const
QPlatformOffscreenSurface *QWasmIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
- return new QWasmOffscrenSurface(surface);
+ return new QWasmOffscreenSurface(surface);
}
QPlatformFontDatabase *QWasmIntegration::fontDatabase() const
@@ -246,16 +260,20 @@ QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const
QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
{
- if (hint == ShowIsFullScreen)
+ switch (hint) {
+ case ShowIsFullScreen:
return true;
-
- return QPlatformIntegration::styleHint(hint);
+ case UnderlineShortcut:
+ return platform() != Platform::MacOS;
+ default:
+ return QPlatformIntegration::styleHint(hint);
+ }
}
Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const
{
- // Don't maximize dialogs
- if (flags & Qt::Dialog & ~Qt::Window)
+ // Don't maximize dialogs or popups
+ if (flags.testFlag(Qt::Dialog) || flags.testFlag(Qt::Popup))
return Qt::WindowNoState;
return QPlatformIntegration::defaultWindowState(flags);
@@ -263,12 +281,12 @@ Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) cons
QStringList QWasmIntegration::themeNames() const
{
- return QStringList() << QLatin1String("webassembly");
+ return QStringList() << "webassembly"_L1;
}
QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const
{
- if (name == QLatin1String("webassembly"))
+ if (name == "webassembly"_L1)
return new QWasmTheme;
return QPlatformIntegration::createPlatformTheme(name);
}
@@ -285,37 +303,100 @@ QPlatformClipboard* QWasmIntegration::clipboard() const
return m_clipboard;
}
-void QWasmIntegration::addScreen(const emscripten::val &canvas)
+#ifndef QT_NO_ACCESSIBILITY
+QPlatformAccessibility *QWasmIntegration::accessibility() const
{
- QWasmScreen *screen = new QWasmScreen(canvas);
- m_screens.append(qMakePair(canvas, screen));
- m_clipboard->installEventHandlers(canvas);
+ 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::removeScreen(const emscripten::val &canvas)
+void QWasmIntegration::removeContainerElement(emscripten::val element)
{
- auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(canvas); });
+ 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() << "Attempting to remove non-existing screen for canvas" << QWasmString::toQString(canvas["id"]);;
+ qWarning() << "Attempt to remove a nonexistent screen.";
return;
}
- QWasmScreen *exScreen = it->second;
- m_screens.erase(it);
- exScreen->destroy(); // clean up before deleting the screen
- QWindowSystemInterface::handleScreenRemoved(exScreen);
+
+ 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 &canvas)
+void QWasmIntegration::resizeScreen(const emscripten::val &element)
{
auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(canvas); });
+ [&] (const ScreenMapping &candidate) { return candidate.emscriptenVal.equals(element); });
if (it == m_screens.end()) {
- qWarning() << "Attempting to resize non-existing screen for canvas" << QWasmString::toQString(canvas["id"]);;
+ qWarning() << "Attempting to resize non-existing screen for element"
+ << QString::fromEcmaString(element["id"]);
return;
}
- it->second->updateQScreenAndCanvasRenderSize();
+ it->wasmScreen->updateQScreenAndCanvasRenderSize();
}
void QWasmIntegration::updateDpi()
@@ -324,14 +405,31 @@ void QWasmIntegration::updateDpi()
if (dpi.isUndefined())
return;
qreal dpiValue = dpi.as<qreal>();
- for (const auto &canvasAndScreen : m_screens)
- QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(canvasAndScreen.second->screen(), dpiValue, dpiValue);
+ for (const auto &elementAndScreen : m_screens)
+ QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.wasmScreen->screen(), dpiValue, dpiValue);
}
void QWasmIntegration::resizeAllScreens()
{
- for (const auto &canvasAndScreen : m_screens)
- canvasAndScreen.second->updateQScreenAndCanvasRenderSize();
+ for (const auto &elementAndScreen : m_screens)
+ elementAndScreen.wasmScreen->updateQScreenAndCanvasRenderSize();
+}
+
+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
diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h
index f527053489..870bd0d16b 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.h
+++ b/src/plugins/platforms/wasm/qwasmintegration.h
@@ -1,43 +1,21 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMINTEGRATION_H
#define QWASMINTEGRATION_H
#include "qwasmwindow.h"
+#include "qwasminputcontext.h"
+
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformscreen.h>
#include <qpa/qplatforminputcontext.h>
#include <QtCore/qhash.h>
+#include <private/qstdweb_p.h>
+
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>
@@ -52,7 +30,9 @@ class QWasmScreen;
class QWasmCompositor;
class QWasmBackingStore;
class QWasmClipboard;
+class QWasmAccessibility;
class QWasmServices;
+class QWasmDrag;
class QWasmIntegration : public QObject, public QPlatformIntegration
{
@@ -76,30 +56,56 @@ public:
QPlatformTheme *createPlatformTheme(const QString &name) const override;
QPlatformServices *services() const override;
QPlatformClipboard *clipboard() const override;
+#ifndef QT_NO_ACCESSIBILITY
+ QPlatformAccessibility *accessibility() const override;
+#endif
void initialize() override;
QPlatformInputContext *inputContext() const override;
- QWasmClipboard *getWasmClipboard() { return m_clipboard; }
+#if QT_CONFIG(draganddrop)
+ QPlatformDrag *drag() const override;
+#endif
+ QWasmClipboard *getWasmClipboard() { return m_clipboard; }
+ QWasmInputContext *getWasmInputContext() { return m_platformInputContext; }
static QWasmIntegration *get() { return s_instance; }
- static void QWasmBrowserExit();
- void addScreen(const emscripten::val &canvas);
- void removeScreen(const emscripten::val &canvas);
+ void setContainerElements(emscripten::val elementArray);
+ void addContainerElement(emscripten::val elementArray);
+ void removeContainerElement(emscripten::val elementArray);
void resizeScreen(const emscripten::val &canvas);
- void resizeAllScreens();
void updateDpi();
+ void resizeAllScreens();
+ void loadLocalFontFamilies(emscripten::val families);
void removeBackingStore(QWindow* window);
+ void releaseRequesetUpdateHold();
+ static quint64 getTimestamp();
+
+ int touchPoints;
private:
+ struct ScreenMapping {
+ emscripten::val emscriptenVal;
+ QWasmScreen *wasmScreen;
+ };
+
mutable QWasmFontDatabase *m_fontDb;
mutable QWasmServices *m_desktopServices;
mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores;
- QList<QPair<emscripten::val, QWasmScreen *>> m_screens;
+ QList<ScreenMapping> m_screens;
mutable QWasmClipboard *m_clipboard;
+ mutable QWasmAccessibility *m_accessibility;
+
qreal m_fontDpi = -1;
mutable QScopedPointer<QPlatformInputContext> m_inputContext;
static QWasmIntegration *s_instance;
+
+ mutable QWasmInputContext *m_platformInputContext = nullptr;
+
+#if QT_CONFIG(draganddrop)
+ std::unique_ptr<QWasmDrag> m_drag;
+#endif
+
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
new file mode 100644
index 0000000000..8f5240d2d0
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
@@ -0,0 +1,295 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmkeytranslator.h"
+#include "qwasmevent.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+struct WebKb2QtData
+{
+ static constexpr char StringTerminator = '\0';
+
+ const char *web;
+ unsigned int qt;
+
+ constexpr bool operator<=(const WebKb2QtData &that) const noexcept
+ {
+ return !(strcmp(that) > 0);
+ }
+
+ bool operator<(const WebKb2QtData &that) const noexcept { return ::strcmp(web, that.web) < 0; }
+
+ constexpr bool operator==(const WebKb2QtData &that) const noexcept { return strcmp(that) == 0; }
+
+ constexpr int strcmp(const WebKb2QtData &that, const int i = 0) const
+ {
+ return web[i] == StringTerminator && that.web[i] == StringTerminator ? 0
+ : web[i] == StringTerminator ? -1
+ : that.web[i] == StringTerminator ? 1
+ : web[i] < that.web[i] ? -1
+ : web[i] > that.web[i] ? 1
+ : strcmp(that, i + 1);
+ }
+};
+
+template<unsigned int Qt, char... WebChar>
+struct Web2Qt
+{
+ static constexpr const char storage[sizeof...(WebChar) + 1] = { WebChar..., '\0' };
+ using Type = WebKb2QtData;
+ static constexpr Type data() noexcept { return Type{ storage, Qt }; }
+};
+
+template<unsigned int Qt, char... WebChar>
+constexpr char Web2Qt<Qt, WebChar...>::storage[];
+
+static constexpr const auto WebToQtKeyCodeMappings = qMakeArray(
+ QSortedData<Web2Qt<Qt::Key_Alt, 'A', 'l', 't', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Alt, 'A', 'l', 't'>,
+ Web2Qt<Qt::Key_AltGr, 'A', 'l', 't', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_Apostrophe, 'Q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_Backspace, 'B', 'a', 'c', 'k', 's', 'p', 'a', 'c', 'e'>,
+ Web2Qt<Qt::Key_CapsLock, 'C', 'a', 'p', 's', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Control, 'C', 'o', 'n', 't', 'r', 'o', 'l'>,
+ Web2Qt<Qt::Key_Delete, 'D', 'e', 'l', 'e', 't', 'e'>,
+ Web2Qt<Qt::Key_Down, 'A', 'r', 'r', 'o', 'w', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_Escape, 'E', 's', 'c', 'a', 'p', 'e'>,
+ Web2Qt<Qt::Key_F1, 'F', '1'>, Web2Qt<Qt::Key_F2, 'F', '2'>,
+ Web2Qt<Qt::Key_F11, 'F', '1', '1'>, Web2Qt<Qt::Key_F12, 'F', '1', '2'>,
+ Web2Qt<Qt::Key_F13, 'F', '1', '3'>, Web2Qt<Qt::Key_F14, 'F', '1', '4'>,
+ Web2Qt<Qt::Key_F15, 'F', '1', '5'>, Web2Qt<Qt::Key_F16, 'F', '1', '6'>,
+ Web2Qt<Qt::Key_F17, 'F', '1', '7'>, Web2Qt<Qt::Key_F18, 'F', '1', '8'>,
+ Web2Qt<Qt::Key_F19, 'F', '1', '9'>, Web2Qt<Qt::Key_F20, 'F', '2', '0'>,
+ Web2Qt<Qt::Key_F21, 'F', '2', '1'>, Web2Qt<Qt::Key_F22, 'F', '2', '2'>,
+ Web2Qt<Qt::Key_F23, 'F', '2', '3'>,
+ Web2Qt<Qt::Key_F3, 'F', '3'>, Web2Qt<Qt::Key_F4, 'F', '4'>,
+ Web2Qt<Qt::Key_F5, 'F', '5'>, Web2Qt<Qt::Key_F6, 'F', '6'>,
+ Web2Qt<Qt::Key_F7, 'F', '7'>, Web2Qt<Qt::Key_F8, 'F', '8'>,
+ Web2Qt<Qt::Key_F9, 'F', '9'>, Web2Qt<Qt::Key_F10, 'F', '1', '0'>,
+ Web2Qt<Qt::Key_Help, 'H', 'e', 'l', 'p'>,
+ Web2Qt<Qt::Key_Home, 'H', 'o', 'm', 'e'>, Web2Qt<Qt::Key_End, 'E', 'n', 'd'>,
+ Web2Qt<Qt::Key_Insert, 'I', 'n', 's', 'e', 'r', 't'>,
+ Web2Qt<Qt::Key_Left, 'A', 'r', 'r', 'o', 'w', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Meta, 'M', 'e', 't', 'a'>, Web2Qt<Qt::Key_Meta, 'O', 'S'>,
+ Web2Qt<Qt::Key_Menu, 'C', 'o', 'n', 't', 'e', 'x', 't', 'M', 'e', 'n', 'u'>,
+ Web2Qt<Qt::Key_NumLock, 'N', 'u', 'm', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_PageDown, 'P', 'a', 'g', 'e', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_PageUp, 'P', 'a', 'g', 'e', 'U', 'p'>,
+ Web2Qt<Qt::Key_Paste, 'P', 'a', 's', 't', 'e'>,
+ Web2Qt<Qt::Key_Pause, 'C', 'l', 'e', 'a', 'r'>,
+ Web2Qt<Qt::Key_Pause, 'P', 'a', 'u', 's', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'B', 'a', 'c', 'k', 'q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'I', 'n', 't', 'l', 'B', 'a', 'c', 'k', 's', 'l', 'a', 's', 'h'>,
+ Web2Qt<Qt::Key_Return, 'E', 'n', 't', 'e', 'r'>,
+ Web2Qt<Qt::Key_Right, 'A', 'r', 'r', 'o', 'w', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_ScrollLock, 'S', 'c', 'r', 'o', 'l', 'l', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Shift, 'S', 'h', 'i', 'f', 't'>,
+ Web2Qt<Qt::Key_Tab, 'T', 'a', 'b'>,
+ Web2Qt<Qt::Key_Up, 'A', 'r', 'r', 'o', 'w', 'U', 'p'>,
+ Web2Qt<Qt::Key_yen, 'I', 'n', 't', 'l', 'Y', 'e', 'n'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextLowercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\xa1'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\xa2'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\xa4'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\xa6'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\xa0'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\xa5'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\xa3'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\xa7'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\xa9'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\xaa'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\xab'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\xa8'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\xad'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\xae'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\xaf'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\xac'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\xb1'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\xb3'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\xb4'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\xb6'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\xb2'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\xb8'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\xb5'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\xba'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\xbb'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\xbc'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\xb9'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\xbd'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc3', '\xbf'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextUppercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\x81'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\x82'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\x84'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\x86'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\x80'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\x85'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\x83'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\x87'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\x89'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\x8a'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\x8b'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\x88'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\x8d'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\x8e'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\x8f'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\x8c'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\x91'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\x93'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\x94'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\x96'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\x92'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\x98'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\x95'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\x9a'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\x9b'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\x9c'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\x99'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\x9d'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc5', '\xb8'>>::Data{});
+
+static_assert(DiacriticalCharsKeyToTextLowercase.size()
+ == DiacriticalCharsKeyToTextUppercase.size(),
+ "Add the new key to both arrays");
+
+struct KeyMapping
+{
+ Qt::Key from, to;
+};
+
+constexpr KeyMapping tildeKeyTable[] = {
+ // ~
+ { Qt::Key_A, Qt::Key_Atilde },
+ { Qt::Key_N, Qt::Key_Ntilde },
+ { Qt::Key_O, Qt::Key_Otilde },
+};
+constexpr KeyMapping 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 },
+};
+constexpr KeyMapping 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 },
+};
+constexpr KeyMapping 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 },
+};
+constexpr KeyMapping 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 },
+};
+
+static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
+{
+ while (first != last) {
+ if (first->from == key)
+ return first->to;
+ ++first;
+ }
+ return Qt::Key_unknown;
+}
+
+template<size_t N>
+static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
+{
+ return find_impl(map, map + N, key);
+}
+
+Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey)
+{
+ switch (deadKey) {
+ case Qt::Key_Dead_Grave:
+ return find(graveKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Acute:
+ return find(acuteKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Tilde:
+ return find(tildeKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Diaeresis:
+ return find(diaeresisKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Circumflex:
+ return find(circumflexKeyTable, accentBaseKey);
+ default:
+ return Qt::Key_unknown;
+ };
+}
+
+template<class T>
+std::optional<QString> findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey)
+{
+ const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(),
+ [qtKey](const WebKb2QtData &data) { return data.qt == qtKey; });
+ return it != mappingArray.cend() ? it->web : std::optional<QString>();
+}
+} // namespace
+
+std::optional<Qt::Key> QWasmKeyTranslator::mapWebKeyTextToQtKey(const char *toFind)
+{
+ const WebKb2QtData searchKey{ toFind, 0 };
+ const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(),
+ searchKey);
+ return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast<Qt::Key>(it->qt)
+ : std::optional<Qt::Key>();
+}
+
+QWasmDeadKeySupport::QWasmDeadKeySupport() = default;
+
+QWasmDeadKeySupport::~QWasmDeadKeySupport() = default;
+
+void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event)
+{
+ if (event->deadKey) {
+ m_activeDeadKey = event->key;
+ } else if (m_activeDeadKey != Qt::Key_unknown
+ && (((m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
+ && event->type == EventType::KeyDown))
+ || (m_keyModifiedByDeadKeyOnPress == event->key
+ && event->type == EventType::KeyUp))) {
+ const Qt::Key baseKey = event->key;
+ const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_activeDeadKey);
+ if (translatedKey != Qt::Key_unknown) {
+ event->key = translatedKey;
+
+ auto foundText = event->modifiers.testFlag(Qt::ShiftModifier)
+ ? findKeyTextByKeyId(DiacriticalCharsKeyToTextUppercase, event->key)
+ : findKeyTextByKeyId(DiacriticalCharsKeyToTextLowercase, event->key);
+ Q_ASSERT(foundText.has_value());
+ event->text = foundText->size() == 1 ? *foundText : QString();
+ }
+
+ if (!event->text.isEmpty()) {
+ if (event->type == EventType::KeyDown) {
+ // Assume the first keypress with an active dead key is treated as modified,
+ // regardless of whether it has actually been modified or not. Take into account
+ // only events that produce actual key text.
+ if (!event->text.isEmpty())
+ m_keyModifiedByDeadKeyOnPress = baseKey;
+ } else {
+ Q_ASSERT(event->type == EventType::KeyUp);
+ Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
+ m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+ m_activeDeadKey = Qt::Key_unknown;
+ }
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.h b/src/plugins/platforms/wasm/qwasmkeytranslator.h
new file mode 100644
index 0000000000..11a89e6193
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMKEYTRANSLATOR_H
+#define QWASMKEYTRANSLATOR_H
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/qtypes.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+struct KeyEvent;
+
+namespace QWasmKeyTranslator {
+std::optional<Qt::Key> mapWebKeyTextToQtKey(const char *toFind);
+}
+
+class QWasmDeadKeySupport
+{
+public:
+ explicit QWasmDeadKeySupport();
+ ~QWasmDeadKeySupport();
+
+ void applyDeadKeyTranslations(KeyEvent *event);
+
+private:
+ Qt::Key m_activeDeadKey = Qt::Key_unknown;
+ Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMKEYTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
index a205e5ddea..dcfc4433e6 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
@@ -1,41 +1,35 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 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$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmoffscreensurface.h"
-QWasmOffscrenSurface::QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface)
- :QPlatformOffscreenSurface(offscreenSurface)
+QT_BEGIN_NAMESPACE
+
+QWasmOffscreenSurface::QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface)
+ : QPlatformOffscreenSurface(offscreenSurface), m_offscreenCanvas(emscripten::val::undefined())
{
+ const auto offscreenCanvasClass = emscripten::val::global("OffscreenCanvas");
+ // The OffscreenCanvas is not supported on some browsers, most notably on Safari.
+ if (!offscreenCanvasClass)
+ return;
+
+ m_offscreenCanvas = offscreenCanvasClass.new_(offscreenSurface->size().width(),
+ offscreenSurface->size().height());
+
+ m_specialTargetId = std::string("!qtoffscreen_") + std::to_string(uintptr_t(this));
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(m_specialTargetId, m_offscreenCanvas);
}
-QWasmOffscrenSurface::~QWasmOffscrenSurface()
+QWasmOffscreenSurface::~QWasmOffscreenSurface()
{
+ emscripten::val::module_property("specialHTMLTargets").delete_(m_specialTargetId);
+}
+bool QWasmOffscreenSurface::isValid() const
+{
+ return !m_offscreenCanvas.isNull() && !m_offscreenCanvas.isUndefined();
}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.h b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
index 9d3e805be0..1c71310448 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.h
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
@@ -1,47 +1,30 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 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$
-**
-****************************************************************************/
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMOFFSCREENSURFACE_H
#define QWASMOFFSCREENSURFACE_H
#include <qpa/qplatformoffscreensurface.h>
+#include <emscripten/val.h>
+
+#include <string>
+
QT_BEGIN_NAMESPACE
class QOffscreenSurface;
-class QWasmOffscrenSurface : public QPlatformOffscreenSurface
+class QWasmOffscreenSurface final : public QPlatformOffscreenSurface
{
public:
- explicit QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface);
- ~QWasmOffscrenSurface();
-private:
+ explicit QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface);
+ ~QWasmOffscreenSurface() final;
+ const std::string &id() const { return m_specialTargetId; }
+ bool isValid() const override;
+
+private:
+ std::string m_specialTargetId;
+ emscripten::val m_offscreenCanvas;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
index c122335a57..8a4664ec8c 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
@@ -1,65 +1,44 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmopenglcontext.h"
+
+#include "qwasmoffscreensurface.h"
#include "qwasmintegration.h"
#include <EGL/egl.h>
+#include <emscripten/bind.h>
#include <emscripten/val.h>
+namespace {
+void qtDoNothing(emscripten::val) { }
+} // namespace
+
+EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
+{
+ function("qtDoNothing", &qtDoNothing);
+}
+
QT_BEGIN_NAMESPACE
-QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format)
- : m_requestedFormat(format)
+QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
+ : m_actualFormat(context->format()), m_qGlContext(context)
{
- m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES);
+ m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
// if we set one, we need to set the other as well since in webgl, these are tied together
- if (format.depthBufferSize() < 0 && format.stencilBufferSize() > 0)
- m_requestedFormat.setDepthBufferSize(16);
-
- if (format.stencilBufferSize() < 0 && format.depthBufferSize() > 0)
- m_requestedFormat.setStencilBufferSize(8);
+ if (m_actualFormat.depthBufferSize() < 0 && m_actualFormat.stencilBufferSize() > 0)
+ m_actualFormat.setDepthBufferSize(16);
+ if (m_actualFormat.stencilBufferSize() < 0 && m_actualFormat.depthBufferSize() > 0)
+ m_actualFormat.setStencilBufferSize(8);
}
QWasmOpenGLContext::~QWasmOpenGLContext()
{
- if (m_context) {
- // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
- // which removes all event handlers on the canvas by temporarily removing
- // emscripten's JSEvents global object.
- emscripten::val jsEvents = emscripten::val::global("window")["JSEvents"];
- emscripten::val::global("window").set("JSEvents", emscripten::val::undefined());
- emscripten_webgl_destroy_context(m_context);
- emscripten::val::global("window").set("JSEvents", jsEvents);
- m_context = 0;
- }
+ // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
+ // which removes all event handlers on the canvas by temporarily replacing the function
+ // that does the removal with a function that does nothing.
+ destroyWebGLContext(m_ownedWebGLContext.handle);
}
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
@@ -72,28 +51,66 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
(format.majorVersion() == 3 && format.minorVersion() == 0));
}
-bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
{
- // Native emscripten/WebGL contexts are tied to a single screen/canvas. The first
- // call to this function creates a native canvas for the given screen, subsequent
- // calls verify that the surface is on/off the same screen.
- QPlatformScreen *screen = surface->screen();
- if (m_context && !screen)
- return false; // Alternative: return true to support makeCurrent on QOffScreenSurface with
- // no screen. However, Qt likes to substitute QGuiApplication::primaryScreen()
- // for null screens, which foils this plan.
- if (!screen)
- return false;
- if (m_context)
- return m_screen == screen;
+ if (m_ownedWebGLContext.surface == surface)
+ return m_ownedWebGLContext.handle;
+
+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
+ // Reuse the existing context for offscreen drawing, even if it happens to be a canvas
+ // context. This is because it is impossible to re-home an existing context to the
+ // new surface and works as an emulation measure.
+ if (m_ownedWebGLContext.handle)
+ return m_ownedWebGLContext.handle;
+
+ // The non-shared offscreen context is heavily limited on WASM, but we provide it
+ // anyway for potential pixel readbacks.
+ m_ownedWebGLContext =
+ QOpenGLContextData{ .surface = surface,
+ .handle = createEmscriptenContext(
+ static_cast<QWasmOffscreenSurface *>(surface)->id(),
+ m_actualFormat) };
+ } else {
+ destroyWebGLContext(m_ownedWebGLContext.handle);
+
+ // Create a full on-screen context for the window canvas.
+ m_ownedWebGLContext = QOpenGLContextData{
+ .surface = surface,
+ .handle = createEmscriptenContext(static_cast<QWasmWindow *>(surface)->canvasSelector(),
+ m_actualFormat)
+ };
+ }
+
+ EmscriptenWebGLContextAttributes actualAttributes;
+
+ EMSCRIPTEN_RESULT attributesResult = emscripten_webgl_get_context_attributes(m_ownedWebGLContext.handle, &actualAttributes);
+ if (attributesResult == EMSCRIPTEN_RESULT_SUCCESS) {
+ if (actualAttributes.majorVersion == 1) {
+ m_actualFormat.setMajorVersion(2);
+ } else if (actualAttributes.majorVersion == 2) {
+ m_actualFormat.setMajorVersion(3);
+ }
+ m_actualFormat.setMinorVersion(0);
+ }
+
+ return m_ownedWebGLContext.handle;
+}
- QString canvasId = QWasmScreen::get(screen)->canvasId();
- m_context = createEmscriptenContext(canvasId, m_requestedFormat);
- m_screen = screen;
- return true;
+void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
+{
+ if (!contextHandle)
+ return;
+ emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
+ emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
+ jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
+ emscripten_webgl_destroy_context(contextHandle);
+ jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
}
-EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasId, QSurfaceFormat format)
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
+ QSurfaceFormat format)
{
EmscriptenWebGLContextAttributes attributes;
emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes
@@ -102,27 +119,30 @@ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(cons
attributes.failIfMajorPerformanceCaveat = false;
attributes.antialias = true;
attributes.enableExtensionsByDefault = true;
- attributes.majorVersion = format.majorVersion() - 1;
- attributes.minorVersion = format.minorVersion();
-
+ attributes.majorVersion = 2; // try highest supported version ES3.0 / WebGL 2.0
+ attributes.minorVersion = 0; // emscripten only supports minor version 0
// WebGL doesn't allow separate attach buffers to STENCIL_ATTACHMENT and DEPTH_ATTACHMENT
// we need both or none
- bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0);
+ const bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0);
// WebGL offers enable/disable control but not size control for these
attributes.alpha = format.alphaBufferSize() > 0;
attributes.depth = useDepthStencil;
attributes.stencil = useDepthStencil;
+ EMSCRIPTEN_RESULT contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
- QByteArray convasSelector = "#" + canvasId.toUtf8();
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes);
-
- return context;
+ if (contextResult <= 0) {
+ // fallback to opengles2/webgl1
+ // for devices that do not support opengles3/webgl2
+ attributes.majorVersion = 1;
+ contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
+ }
+ return contextResult;
}
QSurfaceFormat QWasmOpenGLContext::format() const
{
- return m_requestedFormat;
+ return m_actualFormat;
}
GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const
@@ -132,11 +152,22 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
{
- bool ok = maybeCreateEmscriptenContext(surface);
- if (!ok)
+ static bool sentSharingWarning = false;
+ if (!sentSharingWarning && isSharing()) {
+ qWarning() << "The functionality for sharing OpenGL contexts is limited, see documentation";
+ sentSharingWarning = true;
+ }
+
+ if (auto *shareContext = m_qGlContext->shareContext())
+ return shareContext->makeCurrent(surface->surface());
+
+ const auto context = obtainEmscriptenContext(surface);
+ if (!context)
return false;
- return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS;
+ m_usedWebGLContextHandle = context;
+
+ return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
}
void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
@@ -152,17 +183,17 @@ void QWasmOpenGLContext::doneCurrent()
bool QWasmOpenGLContext::isSharing() const
{
- return false;
+ return m_qGlContext->shareContext();
}
bool QWasmOpenGLContext::isValid() const
{
- if (!(isOpenGLVersionSupported(m_requestedFormat)))
+ if (!isOpenGLVersionSupported(m_actualFormat))
return false;
// Note: we get isValid() calls before we see the surface and can
// create a native context, so no context is also a valid state.
- return !m_context || !emscripten_is_webgl_context_lost(m_context);
+ return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
}
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h
index cf84379c36..2a8bcc5d9b 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.h
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h
@@ -1,31 +1,8 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMOPENGLCONTEXT_H
+#define QWASMOPENGLCONTEXT_H
#include <qpa/qplatformopenglcontext.h>
@@ -34,11 +11,13 @@
QT_BEGIN_NAMESPACE
+class QOpenGLContext;
class QPlatformScreen;
+class QPlatformSurface;
class QWasmOpenGLContext : public QPlatformOpenGLContext
{
public:
- QWasmOpenGLContext(const QSurfaceFormat &format);
+ explicit QWasmOpenGLContext(QOpenGLContext *context);
~QWasmOpenGLContext();
QSurfaceFormat format() const override;
@@ -51,14 +30,25 @@ public:
QFunctionPointer getProcAddress(const char *procName) override;
private:
+ struct QOpenGLContextData
+ {
+ QPlatformSurface *surface = nullptr;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle = 0;
+ };
+
static bool isOpenGLVersionSupported(QSurfaceFormat format);
- bool maybeCreateEmscriptenContext(QPlatformSurface *surface);
- static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const QString &canvasId, QSurfaceFormat format);
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE obtainEmscriptenContext(QPlatformSurface *surface);
+ static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+ createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);
+
+ static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);
- QSurfaceFormat m_requestedFormat;
- QPlatformScreen *m_screen = nullptr;
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0;
+ QSurfaceFormat m_actualFormat;
+ QOpenGLContext *m_qGlContext;
+ QOpenGLContextData m_ownedWebGLContext;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_usedWebGLContextHandle = 0;
};
QT_END_NAMESPACE
+#endif // QWASMOPENGLCONTEXT_H
diff --git a/src/plugins/platforms/wasm/qwasmplatform.cpp b/src/plugins/platforms/wasm/qwasmplatform.cpp
new file mode 100644
index 0000000000..e54992be1d
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmplatform.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmplatform.h"
+
+QT_BEGIN_NAMESPACE
+
+Platform platform()
+{
+ static const Platform qtDetectedPlatform = ([]() {
+ // The Platform Detect: expand coverage as needed
+ emscripten::val rawPlatform = emscripten::val::global("navigator")["platform"];
+
+ if (rawPlatform.call<bool>("includes", emscripten::val("Mac")))
+ return Platform::MacOS;
+ if (rawPlatform.call<bool>("includes", emscripten::val("iPhone"))
+ || rawPlatform.call<bool>("includes", emscripten::val("iPad")))
+ return Platform::iOS;
+ if (rawPlatform.call<bool>("includes", emscripten::val("Win32")))
+ return Platform::Windows;
+ if (rawPlatform.call<bool>("includes", emscripten::val("Linux"))) {
+ emscripten::val uAgent = emscripten::val::global("navigator")["userAgent"];
+ if (uAgent.call<bool>("includes", emscripten::val("Android")))
+ return Platform::Android;
+ return Platform::Linux;
+ }
+ return Platform::Generic;
+ })();
+ return qtDetectedPlatform;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmplatform.h b/src/plugins/platforms/wasm/qwasmplatform.h
new file mode 100644
index 0000000000..5b32e43633
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmplatform.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMPLATFORM_H
+#define QWASMPLATFORM_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qnamespace.h>
+
+#include <QPoint>
+
+#include <emscripten/val.h>
+
+QT_BEGIN_NAMESPACE
+
+enum class Platform {
+ Generic,
+ MacOS,
+ Windows,
+ Linux,
+ Android,
+ iOS
+};
+
+Platform platform();
+
+QT_END_NAMESPACE
+
+#endif // QWASMPLATFORM_H
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index be9d35cdef..ddf8140c48 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -1,72 +1,126 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// 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>
-using namespace emscripten;
+#include <tuple>
QT_BEGIN_NAMESPACE
-QWasmScreen::QWasmScreen(const emscripten::val &canvas)
- : m_canvas(canvas)
+using namespace emscripten;
+
+const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName =
+ "data-qtCanvasResizeObserverCallbackContext";
+
+QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
+ : m_container(containerOrCanvas),
+ m_intermediateContainer(emscripten::val::undefined()),
+ m_shadowContainer(emscripten::val::undefined()),
+ m_compositor(new QWasmCompositor(this)),
+ m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
{
- m_compositor = new QWasmCompositor(this);
- m_eventTranslator = new QWasmEventTranslator(this);
+ 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;
+ }
+
+ // 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"));
+
+ shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer));
+
+ shadow.call<void>("appendChild", m_shadowContainer);
+
+ 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_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()
{
- destroy();
+ m_intermediateContainer.call<void>("remove");
+
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(eventTargetId().toStdString(), emscripten::val::undefined());
+
+ m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(0)));
}
-void QWasmScreen::destroy()
+void QWasmScreen::deleteScreen()
{
- m_compositor->destroy();
+ // Deletes |this|!
+ QWindowSystemInterface::handleScreenRemoved(this);
}
QWasmScreen *QWasmScreen::get(QPlatformScreen *screen)
@@ -76,27 +130,31 @@ QWasmScreen *QWasmScreen::get(QPlatformScreen *screen)
QWasmScreen *QWasmScreen::get(QScreen *screen)
{
+ if (!screen)
+ return nullptr;
return get(screen->handle());
}
QWasmCompositor *QWasmScreen::compositor()
{
- return m_compositor;
+ return m_compositor.get();
}
-QWasmEventTranslator *QWasmScreen::eventTranslator()
+emscripten::val QWasmScreen::element() const
{
- return m_eventTranslator;
+ return m_shadowContainer;
}
-emscripten::val QWasmScreen::canvas() const
+QString QWasmScreen::eventTargetId() const
{
- return m_canvas;
+ // Return a globally unique id for the canvas. We can choose any string,
+ // as long as it starts with a "!".
+ return QString("!qtcanvas_%1").arg(uintptr_t(this));
}
-QString QWasmScreen::canvasId() const
+QString QWasmScreen::outerScreenId() const
{
- return QWasmString::toQString(m_canvas["id"]);
+ return QString("!outerscreen_%1").arg(uintptr_t(this));
}
QRect QWasmScreen::geometry() const
@@ -148,7 +206,7 @@ qreal QWasmScreen::devicePixelRatio() const
QString QWasmScreen::name() const
{
- return canvasId();
+ return QString::fromEcmaString(m_shadowContainer["id"]);
}
QPlatformCursor *QWasmScreen::cursor() const
@@ -165,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()
@@ -181,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.
@@ -193,27 +282,92 @@ 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 = "#" + canvasId().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 canvasRect = element.call<emscripten::val>("getBoundingClientRect");
+ return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
+ canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
+ };
+
+ setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
+}
- QPoint offset;
- offset.setX(m_canvas["offsetTop"].as<int>());
- offset.setY(m_canvas["offsetLeft"].as<int>());
+void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val)
+{
+ int count = entries["length"].as<int>();
+ if (count == 0)
+ return;
+ emscripten::val entry = entries[0];
+ QWasmScreen *screen = reinterpret_cast<QWasmScreen *>(
+ entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
+ if (!screen) {
+ qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer";
+ return;
+ }
- emscripten::val rect = m_canvas.call<emscripten::val>("getBoundingClientRect");
- QPoint position(rect["left"].as<int>() - offset.x(), rect["top"].as<int>() - offset.y());
+ // 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.
+ screen->updateQScreenAndCanvasRenderSize();
+}
- setGeometry(QRect(position, cssSize.toSize()));
- m_compositor->redrawWindowContent();
+EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback)
+{
+ emscripten::function("qtCanvasResizeObserverCallback",
+ &QWasmScreen::canvasResizeObserverCallback);
+}
+
+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"));
+ 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_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(this)));
+
+ 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
diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h
index ea7ffc4193..da171d3f50 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.h
+++ b/src/plugins/platforms/wasm/qwasmscreen.h
@@ -1,41 +1,18 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMSCREEN_H
#define QWASMSCREEN_H
#include "qwasmcursor.h"
+#include "qwasmwindowtreenode.h"
+
#include <qpa/qplatformscreen.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtextstream.h>
+#include <QtCore/private/qstdweb_p.h>
#include <emscripten/val.h>
@@ -45,24 +22,29 @@ class QPlatformOpenGLContext;
class QWasmWindow;
class QWasmBackingStore;
class QWasmCompositor;
-class QWasmEventTranslator;
+class QWasmDeadKeySupport;
class QOpenGLContext;
-class QWasmScreen : public QObject, public QPlatformScreen
+class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode
{
Q_OBJECT
public:
- QWasmScreen(const emscripten::val &canvas);
+ QWasmScreen(const emscripten::val &containerOrCanvas);
~QWasmScreen();
- void destroy();
+ void deleteScreen();
static QWasmScreen *get(QPlatformScreen *screen);
static QWasmScreen *get(QScreen *screen);
- emscripten::val canvas() const;
- QString canvasId() const;
+ emscripten::val element() const;
+ QString eventTargetId() const;
+ QString outerScreenId() const;
+ QPointingDevice *touchDevice() { return m_touchDevice.get(); }
+ QPointingDevice *tabletDevice() { return m_tabletDevice.get(); }
QWasmCompositor *compositor();
- QWasmEventTranslator *eventTranslator();
+ QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); }
+
+ QList<QWasmWindow *> allWindows();
QRect geometry() const override;
int depth() const override;
@@ -76,20 +58,39 @@ public:
QWindow *topWindow() const;
QWindow *topLevelAt(const QPoint &p) const override;
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
+ QPointF mapFromLocal(const QPointF &p) const;
+ QPointF clipPoint(const QPointF &p) const;
+
void invalidateSize();
void updateQScreenAndCanvasRenderSize();
+ void installCanvasResizeObserver();
+ static void canvasResizeObserverCallback(emscripten::val entries, emscripten::val);
public slots:
void setGeometry(const QRect &rect);
private:
- emscripten::val m_canvas;
- QWasmCompositor *m_compositor = nullptr;
- QWasmEventTranslator *m_eventTranslator = nullptr;
+ // QWasmWindowTreeNode:
+ void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) final;
+
+ emscripten::val m_container;
+ emscripten::val m_intermediateContainer;
+ emscripten::val m_shadowContainer;
+ std::unique_ptr<QWasmCompositor> m_compositor;
+ std::unique_ptr<QPointingDevice> m_touchDevice;
+ std::unique_ptr<QPointingDevice> m_tabletDevice;
+ std::unique_ptr<QWasmDeadKeySupport> m_deadKeySupport;
QRect m_geometry = QRect(0, 0, 100, 100);
int m_depth = 32;
QImage::Format m_format = QImage::Format_RGB32;
QWasmCursor m_cursor;
+ static const char *m_canvasResizeObserverCallbackContextPropertyName;
+ std::unique_ptr<qstdweb::EventCallback> m_onContextMenu;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmservices.cpp b/src/plugins/platforms/wasm/qwasmservices.cpp
index 4eee3fe972..e767295e41 100644
--- a/src/plugins/platforms/wasm/qwasmservices.cpp
+++ b/src/plugins/platforms/wasm/qwasmservices.cpp
@@ -1,34 +1,7 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmservices.h"
-#include "qwasmstring.h"
#include <QtCore/QUrl>
#include <QtCore/QDebug>
@@ -39,8 +12,8 @@ QT_BEGIN_NAMESPACE
bool QWasmServices::openUrl(const QUrl &url)
{
- emscripten::val jsUrl = QWasmString::fromQString(url.toString());
- emscripten::val::global("window").call<void>("open", jsUrl, emscripten::val("_blank"));
+ emscripten::val::global("window").call<void>("open", url.toString().toEcmaString(),
+ emscripten::val("_blank"));
return true;
}
diff --git a/src/plugins/platforms/wasm/qwasmservices.h b/src/plugins/platforms/wasm/qwasmservices.h
index 3b37f21f82..16d4ac5171 100644
--- a/src/plugins/platforms/wasm/qwasmservices.h
+++ b/src/plugins/platforms/wasm/qwasmservices.h
@@ -1,31 +1,5 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMDESKTOPSERVICES_H
#define QWASMDESKTOPSERVICES_H
diff --git a/src/plugins/platforms/wasm/qwasmstring.cpp b/src/plugins/platforms/wasm/qwasmstring.cpp
deleted file mode 100644
index b1be405eeb..0000000000
--- a/src/plugins/platforms/wasm/qwasmstring.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 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 "qwasmstring.h"
-
-QT_BEGIN_NAMESPACE
-
-using namespace emscripten;
-
-val QWasmString::fromQString(const QString &str)
-{
- static const val UTF16ToString(
- val::global("Module")["UTF16ToString"]);
-
- auto ptr = quintptr(str.utf16());
- return UTF16ToString(val(ptr));
-}
-
-QString QWasmString::toQString(const val &v)
-{
- QString result;
- if (!v.isString())
- return result;
-
- static const val stringToUTF16(
- val::global("Module")["stringToUTF16"]);
- static const val length("length");
-
- int len = v[length].as<int>();
- result.resize(len);
- auto ptr = quintptr(result.utf16());
- stringToUTF16(v, val(ptr), val((len + 1) * 2));
- return result;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmstring.h b/src/plugins/platforms/wasm/qwasmstring.h
deleted file mode 100644
index de5da92830..0000000000
--- a/src/plugins/platforms/wasm/qwasmstring.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 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$
-**
-****************************************************************************/
-
-#pragma once
-
-#include <qstring.h>
-
-#include <emscripten/val.h>
-
-QT_BEGIN_NAMESPACE
-
-class QWasmString
-{
-public:
- static emscripten::val fromQString(const QString &str);
- static QString toQString(const emscripten::val &v);
-};
-QT_END_NAMESPACE
-
diff --git a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h
deleted file mode 100644
index 2b5860f42f..0000000000
--- a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/****************************************************************************
-**
-** 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 QWASMSTYLEPIXMAPS_P_H
-#define QWASMSTYLEPIXMAPS_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-/* XPM */
-static const char * const qt_menu_xpm[] = {
-"16 16 72 1",
-" c None",
-". c #65AF36",
-"+ c #66B036",
-"@ c #77B94C",
-"# c #A7D28C",
-"$ c #BADBA4",
-"% c #A4D088",
-"& c #72B646",
-"* c #9ACB7A",
-"= c #7FBD56",
-"- c #85C05F",
-"; c #F4F9F0",
-"> c #FFFFFF",
-", c #E5F1DC",
-"' c #ECF5E7",
-") c #7ABA50",
-"! c #83BF5C",
-"~ c #AED595",
-"{ c #D7EACA",
-"] c #A9D28D",
-"^ c #BCDDA8",
-"/ c #C4E0B1",
-"( c #81BE59",
-"_ c #D0E7C2",
-": c #D4E9C6",
-"< c #6FB542",
-"[ c #6EB440",
-"} c #88C162",
-"| c #98CA78",
-"1 c #F4F9F1",
-"2 c #8FC56C",
-"3 c #F1F8EC",
-"4 c #E8F3E1",
-"5 c #D4E9C7",
-"6 c #74B748",
-"7 c #80BE59",
-"8 c #73B747",
-"9 c #6DB43F",
-"0 c #CBE4BA",
-"a c #80BD58",
-"b c #6DB33F",
-"c c #FEFFFE",
-"d c #68B138",
-"e c #F9FCF7",
-"f c #91C66F",
-"g c #E8F3E0",
-"h c #DCEDD0",
-"i c #91C66E",
-"j c #A3CF86",
-"k c #C9E3B8",
-"l c #B0D697",
-"m c #E3F0DA",
-"n c #95C873",
-"o c #E6F2DE",
-"p c #9ECD80",
-"q c #BEDEAA",
-"r c #C7E2B6",
-"s c #79BA4F",
-"t c #6EB441",
-"u c #BCDCA7",
-"v c #FAFCF8",
-"w c #F6FAF3",
-"x c #84BF5D",
-"y c #EDF6E7",
-"z c #FAFDF9",
-"A c #88C263",
-"B c #98CA77",
-"C c #CDE5BE",
-"D c #67B037",
-"E c #D9EBCD",
-"F c #6AB23C",
-"G c #77B94D",
-" .++++++++++++++",
-".+++++++++++++++",
-"+++@#$%&+++*=+++",
-"++-;>,>')+!>~+++",
-"++{>]+^>/(_>:~<+",
-"+[>>}+|>123>456+",
-"+7>>8+->>90>~+++",
-"+a>>b+a>c[0>~+++",
-"+de>=+f>g+0>~+++",
-"++h>i+j>k+0>~+++",
-"++l>mno>p+q>rst+",
-"++duv>wl++xy>zA+",
-"++++B>Cb++++&D++",
-"+++++0zE++++++++",
-"++++++FG+++++++.",
-"++++++++++++++. "};
-
-static const char * const qt_close_xpm[] = {
-"10 10 2 1",
-"# c #000000",
-". c None",
-"..........",
-".##....##.",
-"..##..##..",
-"...####...",
-"....##....",
-"...####...",
-"..##..##..",
-".##....##.",
-"..........",
-".........."};
-
-static const char * const qt_maximize_xpm[]={
-"10 10 2 1",
-"# c #000000",
-". c None",
-"#########.",
-"#########.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#########.",
-".........."};
-
-
-static const char * const qt_normalizeup_xpm[] = {
-"10 10 2 1",
-"# c #000000",
-". c None",
-"...######.",
-"...######.",
-"...#....#.",
-".######.#.",
-".######.#.",
-".#....###.",
-".#....#...",
-".#....#...",
-".######...",
-".........."};
-
-
-#endif // QWASMSTYLEPIXMAPS_P_H
diff --git a/src/plugins/platforms/wasm/qwasmtheme.cpp b/src/plugins/platforms/wasm/qwasmtheme.cpp
index 978d60d686..b188dcb4b6 100644
--- a/src/plugins/platforms/wasm/qwasmtheme.cpp
+++ b/src/plugins/platforms/wasm/qwasmtheme.cpp
@@ -1,43 +1,19 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmtheme.h"
#include <QtCore/qvariant.h>
#include <QFontDatabase>
+#include <QList>
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
QWasmTheme::QWasmTheme()
{
- QFontDatabase fdb;
- for (auto family : fdb.families())
- if (fdb.isFixedPitch(family))
+ for (auto family : QFontDatabase::families())
+ if (QFontDatabase::isFixedPitch(family))
fixedFont = new QFont(family);
}
@@ -50,7 +26,9 @@ QWasmTheme::~QWasmTheme()
QVariant QWasmTheme::themeHint(ThemeHint hint) const
{
if (hint == QPlatformTheme::StyleNames)
- return QVariant(QStringList() << QLatin1String("fusion"));
+ return QVariant(QStringList() << "Fusion"_L1);
+ if (hint == QPlatformTheme::UiEffects)
+ return QVariant(int(HoverEffect));
return QPlatformTheme::themeHint(hint);
}
diff --git a/src/plugins/platforms/wasm/qwasmtheme.h b/src/plugins/platforms/wasm/qwasmtheme.h
index 7123a1f3d4..90ecbe6ddf 100644
--- a/src/plugins/platforms/wasm/qwasmtheme.h
+++ b/src/plugins/platforms/wasm/qwasmtheme.h
@@ -1,31 +1,5 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMTHEME_H
#define QWASMTHEME_H
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index f95335f891..b8197c5113 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -1,96 +1,232 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qpa/qwindowsysteminterface.h>
#include <private/qguiapplication_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#include <QtCore/qfile.h>
#include <QtGui/private/qwindow_p.h>
-#include <QtGui/qopenglcontext.h>
-
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <private/qpixmapcache_p.h>
+#include <QtGui/qopenglfunctions.h>
+#include <QBuffer>
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmclipboard.h"
+#include "qwasmintegration.h"
+#include "qwasmkeytranslator.h"
#include "qwasmwindow.h"
+#include "qwasmwindowclientarea.h"
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
+#include "qwasmevent.h"
#include "qwasmeventdispatcher.h"
+#include "qwasmaccessibility.h"
+#include "qwasmclipboard.h"
#include <iostream>
+#include <sstream>
-Q_GUI_EXPORT int qt_defaultDpiX();
+#include <emscripten/val.h>
+
+#include <QtCore/private/qstdweb_p.h>
QT_BEGIN_NAMESPACE
-QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore)
+namespace {
+QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint))
+ return QWasmWindowStack::PositionPreference::StayOnTop;
+ if (flags.testFlag(Qt::WindowStaysOnBottomHint))
+ return QWasmWindowStack::PositionPreference::StayOnBottom;
+ return QWasmWindowStack::PositionPreference::Regular;
+}
+} // namespace
+
+Q_GUI_EXPORT int qt_defaultDpiX();
+
+QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
+ QWasmCompositor *compositor, QWasmBackingStore *backingStore)
: QPlatformWindow(w),
m_window(w),
m_compositor(compositor),
- m_backingStore(backingStore)
+ m_backingStore(backingStore),
+ m_deadKeySupport(deadKeySupport),
+ m_document(dom::document()),
+ m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_canvasContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas")))
{
- m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface;
+ m_qtWindow.set("className", "qt-window");
+ m_qtWindow["style"].set("display", std::string("none"));
+
+ m_nonClientArea = std::make_unique<NonClientArea>(this, m_qtWindow);
+ m_nonClientArea->titleBar()->setTitle(window()->title());
+
+ m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_windowContents);
+
+ m_windowContents.set("className", "qt-window-contents");
+ m_qtWindow.call<void>("appendChild", m_windowContents);
+
+ m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content"));
+
+ // Set contenteditable so that the canvas gets clipboard events,
+ // then hide the resulting focus frame.
+ m_canvas.set("contentEditable", std::string("true"));
+ m_canvas["style"].set("outline", std::string("none"));
+
+ QWasmClipboard::installEventHandlers(m_canvas);
+
+ // set inputMode to none to stop mobile keyboard opening
+ // when user clicks anywhere on the canvas.
+ m_canvas.set("inputMode", std::string("none"));
+
+ // Hide the canvas from screen readers.
+ m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
+
+ m_windowContents.call<void>("appendChild", m_canvasContainer);
+
+ m_canvasContainer["classList"].call<void>("add", emscripten::val("qt-window-canvas-container"));
+ m_canvasContainer.call<void>("appendChild", m_canvas);
+
+ m_canvasContainer.call<void>("appendChild", m_a11yContainer);
+ m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container"));
+
+ const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface;
+ if (rendersTo2dContext)
+ m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d"));
static int serialNo = 0;
- m_winid = ++serialNo;
+ m_winId = ++serialNo;
+ m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId));
+ emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas);
+
+ m_flags = window()->flags();
+
+ const auto pointerCallback = std::function([this](emscripten::val event) {
+ if (processPointer(*PointerEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+
+ m_pointerEnterCallback =
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback);
+ m_pointerLeaveCallback =
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback);
+
+ m_wheelEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_qtWindow, "wheel", [this](emscripten::val event) {
+ if (processWheel(*WheelEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+
+ const auto keyCallback = std::function([this](emscripten::val event) {
+ if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+
+ emscripten::val keyFocusWindow;
+ if (QWasmIntegration::get()->inputContext()) {
+ QWasmInputContext *wasmContext =
+ static_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext());
+ // if there is an touchscreen input context,
+ // use that window for key input
+ keyFocusWindow = wasmContext->m_inputElement;
+ } else {
+ keyFocusWindow = m_qtWindow;
+ }
- m_compositor->addWindow(this);
+ m_keyDownCallback =
+ std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback);
+ m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback);
- // Pure OpenGL windows draw directly using egl, disable the compositor.
- m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface);
+ setParent(parent());
}
QWasmWindow::~QWasmWindow()
{
- m_compositor->removeWindow(this);
+ emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector());
+ m_canvasContainer.call<void>("removeChild", m_canvas);
+ m_context2d = emscripten::val::undefined();
+ commitParent(nullptr);
+ if (m_requestAnimationFrameId > -1)
+ emscripten_cancel_animation_frame(m_requestAnimationFrameId);
+#if QT_CONFIG(accessibility)
+ QWasmAccessibility::removeAccessibilityEnableButton(window());
+#endif
}
-void QWasmWindow::destroy()
+QSurfaceFormat QWasmWindow::format() const
{
- if (m_backingStore)
- m_backingStore->destroy();
+ return window()->requestedFormat();
}
-void QWasmWindow::initialize()
+QWasmWindow *QWasmWindow::fromWindow(QWindow *window)
{
- QRect rect = windowGeometry();
+ return static_cast<QWasmWindow *>(window->handle());
+}
- QPlatformWindow::setGeometry(rect);
+void QWasmWindow::onRestoreClicked()
+{
+ window()->setWindowState(Qt::WindowNoState);
+}
- const QSize minimumSize = windowMinimumSize();
- if (rect.width() > 0 || rect.height() > 0) {
- rect.setWidth(qBound(1, rect.width(), 2000));
- rect.setHeight(qBound(1, rect.height(), 2000));
- } else if (minimumSize.width() > 0 || minimumSize.height() > 0) {
- rect.setSize(minimumSize);
- }
+void QWasmWindow::onMaximizeClicked()
+{
+ window()->setWindowState(Qt::WindowMaximized);
+}
+
+void QWasmWindow::onToggleMaximized()
+{
+ window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState
+ : Qt::WindowMaximized);
+}
+
+void QWasmWindow::onCloseClicked()
+{
+ window()->close();
+}
+
+void QWasmWindow::onNonClientAreaInteraction()
+{
+ requestActivateWindow();
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+}
+
+bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
+{
+ QPointF pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
+ return QWindowSystemInterface::handleMouseEvent(
+ window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
+ pointInScreen, event.mouseButtons, event.mouseButton,
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::NonClient),
+ event.modifiers);
+}
+
+void QWasmWindow::initialize()
+{
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowSize, defaultWindowSize);
+ m_normalGeometry = initialGeometry;
setWindowState(window()->windowStates());
setWindowFlags(window()->flags());
setWindowTitle(window()->title());
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
if (window()->isTopLevel())
setWindowIcon(window()->icon());
- m_normalGeometry = rect;
+ QPlatformWindow::setGeometry(m_normalGeometry);
+
+#if QT_CONFIG(accessibility)
+ // Add accessibility-enable button. The user can activate this
+ // button to opt-in to accessibility.
+ if (window()->isTopLevel())
+ QWasmAccessibility::addAccessibilityEnableButton(window());
+#endif
}
QWasmScreen *QWasmWindow::platformScreen() const
@@ -98,314 +234,468 @@ QWasmScreen *QWasmWindow::platformScreen() const
return static_cast<QWasmScreen *>(window()->screen()->handle());
}
-void QWasmWindow::setGeometry(const QRect &rect)
+void QWasmWindow::paint()
{
- QRect r = rect;
- if (m_needsCompositor) {
- int yMin = window()->geometry().top() - window()->frameGeometry().top();
-
- if (r.y() < yMin)
- r.moveTop(yMin);
- }
- QWindowSystemInterface::handleGeometryChange(window(), r);
- QPlatformWindow::setGeometry(r);
+ if (!m_backingStore || !isVisible() || m_context2d.isUndefined())
+ return;
- QWindowSystemInterface::flushWindowSystemEvents();
- invalidate();
+ auto image = m_backingStore->getUpdatedWebImage(this);
+ if (image.isUndefined())
+ return;
+ m_context2d.call<void>("putImageData", image, emscripten::val(0), emscripten::val(0));
}
-void QWasmWindow::setVisible(bool visible)
+void QWasmWindow::setZOrder(int z)
{
- QRect newGeom;
+ m_qtWindow["style"].set("zIndex", std::to_string(z));
+}
- if (visible) {
- const bool forceFullScreen = !m_needsCompositor;//make gl apps fullscreen for now
+void QWasmWindow::setWindowCursor(QByteArray cssCursorName)
+{
+ m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData()));
+}
- if (forceFullScreen || (m_windowState & Qt::WindowFullScreen))
- newGeom = platformScreen()->geometry();
- else if (m_windowState & Qt::WindowMaximized)
- newGeom = platformScreen()->availableGeometry();
+void QWasmWindow::setGeometry(const QRect &rect)
+{
+ const auto margins = frameMargins();
+
+ const QRect clientAreaRect = ([this, &rect, &margins]() {
+ if (m_state.testFlag(Qt::WindowFullScreen))
+ return platformScreen()->geometry();
+ if (m_state.testFlag(Qt::WindowMaximized))
+ return platformScreen()->availableGeometry().marginsRemoved(frameMargins());
+
+ auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint());
+
+ // In viewport
+ auto containerGeometryInViewport =
+ QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"))
+ .toRect();
+
+ auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size());
+
+ QRect cappedGeometry(rectInViewport);
+ if (!parent()) {
+ // Clamp top level windows top position to the screen bounds
+ cappedGeometry.moveTop(
+ std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()),
+ containerGeometryInViewport.y() + margins.top()));
+ }
+ cappedGeometry.setSize(
+ cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));
+ return QRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()),
+ rect.size());
+ })();
+ m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());
+
+ const auto frameRect =
+ clientAreaRect
+ .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
+ .translated(!parent() ? -screen()->geometry().topLeft() : QPoint());
+
+ m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px");
+ m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px");
+ m_canvasContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+ m_canvasContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px");
+ m_a11yContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+ m_a11yContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px");
+
+ // Important for the title flexbox to shrink correctly
+ m_windowContents["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+
+ QSizeF canvasSize = clientAreaRect.size() * devicePixelRatio();
+
+ m_canvas.set("width", canvasSize.width());
+ m_canvas.set("height", canvasSize.height());
+
+ bool shouldInvalidate = true;
+ if (!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) {
+ shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size();
+ m_normalGeometry = clientAreaRect;
}
- QPlatformWindow::setVisible(visible);
+ QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect);
+ if (shouldInvalidate)
+ invalidate();
+ else
+ m_compositor->requestUpdateWindow(this);
+}
- m_compositor->setVisible(this, visible);
+void QWasmWindow::setVisible(bool visible)
+{
+ // TODO(mikolajboc): isVisible()?
+ const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block";
+ if (visible == nowVisible)
+ return;
- if (!newGeom.isEmpty())
- setGeometry(newGeom); // may or may not generate an expose
+ m_compositor->requestUpdateWindow(this, QWasmCompositor::ExposeEventDelivery);
+ m_qtWindow["style"].set("display", visible ? "block" : "none");
+ if (window()->isActive())
+ m_canvas.call<void>("focus");
+ if (visible)
+ applyWindowState();
+}
- invalidate();
+bool QWasmWindow::isVisible() const
+{
+ return window()->isVisible();
}
QMargins QWasmWindow::frameMargins() const
{
- int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0;
- int titleBarHeight = hasTitleBar() ? titleHeight() : 0;
-
- QMargins margins;
- margins.setLeft(border);
- margins.setRight(border);
- margins.setTop(2*border + titleBarHeight);
- margins.setBottom(border);
-
- return margins;
+ const auto frameRect =
+ QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect"));
+ const auto canvasRect =
+ QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect"));
+ return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(),
+ frameRect.right() - canvasRect.right(),
+ frameRect.bottom() - canvasRect.bottom())
+ .toMargins();
}
void QWasmWindow::raise()
{
- m_compositor->raise(this);
- QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size()));
+ bringToTop();
invalidate();
+ if (QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
}
void QWasmWindow::lower()
{
- m_compositor->lower(this);
- QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size()));
+ sendToBottom();
invalidate();
}
WId QWasmWindow::winId() const
{
- return m_winid;
+ return m_winId;
}
void QWasmWindow::propagateSizeHints()
{
-// get rid of base class warning
+ // setGeometry() will take care of minimum and maximum size constraints
+ setGeometry(windowGeometry());
+ m_nonClientArea->propagateSizeHints();
}
-void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
+void QWasmWindow::setOpacity(qreal level)
{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ m_qtWindow["style"].set("opacity", qBound(0.0, level, 1.0));
+}
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+void QWasmWindow::invalidate()
+{
+ m_compositor->requestUpdateWindow(this);
+}
+
+void QWasmWindow::onActivationChanged(bool active)
+{
+ dom::syncCSSClassWith(m_qtWindow, "inactive", !active);
+}
- if (maxButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarMaxButton;
- else if (minButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarMinButton;
- else if (closeButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarCloseButton;
- else if (normButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarNormalButton;
+void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)
+ || flags.testFlag(Qt::WindowStaysOnBottomHint)
+ != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) {
+ onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
+ }
+ m_flags = flags;
+ dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame());
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow());
+ dom::syncCSSClassWith(m_qtWindow, "has-title", hasTitleBar());
+ dom::syncCSSClassWith(m_qtWindow, "transparent-for-input",
+ flags.testFlag(Qt::WindowTransparentForInput));
- invalidate();
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+ m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint));
}
-void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
+void QWasmWindow::setWindowState(Qt::WindowStates newState)
{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ // Child windows can not have window states other than Qt::WindowActive
+ if (parent())
+ newState &= Qt::WindowActive;
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+ const Qt::WindowStates oldState = m_state;
- if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) {
- window()->close();
+ if (newState.testFlag(Qt::WindowMinimized)) {
+ newState.setFlag(Qt::WindowMinimized, false);
+ qWarning("Qt::WindowMinimized is not implemented in wasm");
+ window()->setWindowStates(newState);
return;
}
- if (maxButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarMaxButton) {
- window()->setWindowState(Qt::WindowMaximized);
- platformScreen()->resizeMaximizedWindows();
- }
-
- if (normButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarNormalButton) {
- window()->setWindowState(Qt::WindowNoState);
- setGeometry(normalGeometry());
- }
+ if (newState == oldState)
+ return;
- m_activeControl = QWasmCompositor::SC_None;
+ m_state = newState;
+ m_previousWindowState = oldState;
- invalidate();
+ applyWindowState();
}
-int QWasmWindow::titleHeight() const
+void QWasmWindow::setWindowTitle(const QString &title)
{
- return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.);
+ m_nonClientArea->titleBar()->setTitle(title);
}
-int QWasmWindow::borderWidth() const
+void QWasmWindow::setWindowIcon(const QIcon &icon)
{
- return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.);
+ const auto dpi = screen()->devicePixelRatio();
+ auto pixmap = icon.pixmap(10 * dpi, 10 * dpi);
+ if (pixmap.isNull()) {
+ m_nonClientArea->titleBar()->setIcon(
+ Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
+ return;
+ }
+
+ QByteArray bytes;
+ QBuffer buffer(&bytes);
+ pixmap.save(&buffer, "png");
+ m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(), "png");
}
-QRegion QWasmWindow::titleGeometry() const
+void QWasmWindow::applyWindowState()
{
- int border = borderWidth();
+ QRect newGeom;
- QRegion result(window()->frameGeometry().x() + border,
- window()->frameGeometry().y() + border,
- window()->frameGeometry().width() - 2*border,
- titleHeight());
+ const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen);
+ const bool isMaximized = m_state.testFlag(Qt::WindowMaximized);
+ if (isFullscreen)
+ newGeom = platformScreen()->geometry();
+ else if (isMaximized)
+ newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins());
+ else
+ newGeom = normalGeometry();
- result -= titleControlRegion();
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
- return result;
+ m_nonClientArea->titleBar()->setRestoreVisible(isMaximized);
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+
+ if (isVisible())
+ QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
+ setGeometry(newGeom);
}
-QRegion QWasmWindow::resizeRegion() const
+void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
{
- int border = borderWidth();
- QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border));
- result -= window()->frameGeometry().adjusted(border, border, -border, -border);
-
- return result;
+ onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags()));
+ m_commitedParent = parent;
}
-bool QWasmWindow::isPointOnTitle(QPoint point) const
+bool QWasmWindow::processKey(const KeyEvent &event)
{
- bool ok = titleGeometry().contains(point);
- return ok;
+ constexpr bool ProceedToNativeEvent = false;
+ Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp);
+
+ const auto clipboardResult =
+ QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event);
+
+ using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult;
+ if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded)
+ return ProceedToNativeEvent;
+
+ const auto result = QWindowSystemInterface::handleKeyEvent(
+ 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key,
+ event.modifiers, event.text);
+ return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
+ ? ProceedToNativeEvent
+ : result;
}
-bool QWasmWindow::isPointOnResizeRegion(QPoint point) const
+bool QWasmWindow::processPointer(const PointerEvent &event)
{
- if (window()->flags().testFlag(Qt::Popup))
+ if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
return false;
- return resizeRegion().contains(point);
-}
-
-QWasmWindow::ResizeMode QWasmWindow::resizeModeAtPoint(QPoint point) const
-{
- QPoint p1 = window()->frameGeometry().topLeft() - QPoint(5, 5);
- QPoint p2 = window()->frameGeometry().bottomRight() + QPoint(5, 5);
- int corner = 20;
-
- QRect top(p1, QPoint(p2.x(), p1.y() + corner));
- QRect middle(QPoint(p1.x(), p1.y() + corner), QPoint(p2.x(), p2.y() - corner));
- QRect bottom(QPoint(p1.x(), p2.y() - corner), p2);
-
- QRect left(p1, QPoint(p1.x() + corner, p2.y()));
- QRect center(QPoint(p1.x() + corner, p1.y()), QPoint(p2.x() - corner, p2.y()));
- QRect right(QPoint(p2.x() - corner, p1.y()), p2);
-
- if (top.contains(point)) {
- // Top
- if (left.contains(point))
- return ResizeTopLeft;
- if (center.contains(point))
- return ResizeTop;
- if (right.contains(point))
- return ResizeTopRight;
- } else if (middle.contains(point)) {
- // Middle
- if (left.contains(point))
- return ResizeLeft;
- if (right.contains(point))
- return ResizeRight;
- } else if (bottom.contains(point)) {
- // Bottom
- if (left.contains(point))
- return ResizeBottomLeft;
- if (center.contains(point))
- return ResizeBottom;
- if (right.contains(point))
- return ResizeBottomRight;
+
+ switch (event.type) {
+ case EventType::PointerEnter: {
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
+ QWindowSystemInterface::handleEnterEvent(
+ window(), m_window->mapFromGlobal(pointInScreen), pointInScreen);
+ break;
}
+ case EventType::PointerLeave:
+ QWindowSystemInterface::handleLeaveEvent(window());
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool QWasmWindow::processWheel(const WheelEvent &event)
+{
+ // Web scroll deltas are inverted from Qt deltas - negate.
+ const int scrollFactor = -([&event]() {
+ switch (event.deltaMode) {
+ case DeltaMode::Pixel:
+ return 1;
+ case DeltaMode::Line:
+ return 12;
+ case DeltaMode::Page:
+ return 20;
+ };
+ })();
+
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
- return ResizeNone;
+ return QWindowSystemInterface::handleWheelEvent(
+ window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
+ pointInScreen, (event.delta * scrollFactor).toPoint(),
+ (event.delta * scrollFactor).toPoint(), event.modifiers, Qt::NoScrollPhase,
+ Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice);
}
-QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl)
+QRect QWasmWindow::normalGeometry() const
{
- QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window);
+ return m_normalGeometry;
+}
- QRect r = QWasmCompositor::titlebarRect(options, subControl);
- r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y());
+qreal QWasmWindow::devicePixelRatio() const
+{
+ return screen()->devicePixelRatio();
+}
- return r;
+void QWasmWindow::requestUpdate()
+{
+ m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
}
-QRect QWasmWindow::maxButtonRect() const
+bool QWasmWindow::hasFrame() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton);
+ return !m_flags.testFlag(Qt::FramelessWindowHint);
}
-QRect QWasmWindow::minButtonRect() const
+bool QWasmWindow::hasBorder() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton);
+ return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow)
+ && !windowIsPopupType(m_flags) && !parent();
}
-QRect QWasmWindow::closeButtonRect() const
+bool QWasmWindow::hasTitleBar() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton);
+ return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint);
}
-QRect QWasmWindow::normButtonRect() const
+bool QWasmWindow::hasShadow() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton);
+ return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint);
}
-QRect QWasmWindow::sysMenuRect() const
+bool QWasmWindow::hasMaximizeButton() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu);
+ return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint);
}
-QRegion QWasmWindow::titleControlRegion() const
+bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const
{
- QRegion result;
- result += closeButtonRect();
- result += minButtonRect();
- result += maxButtonRect();
- result += sysMenuRect();
+ if (flags.testFlag(Qt::Tool))
+ return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window
- return result;
+ return (flags.testFlag(Qt::Popup));
}
-void QWasmWindow::invalidate()
+void QWasmWindow::requestActivateWindow()
{
- m_compositor->requestRedraw();
+ QWindow *modalWindow;
+ if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
+ static_cast<QWasmWindow *>(modalWindow->handle())->requestActivateWindow();
+ return;
+ }
+
+ raise();
+ setAsActiveNode();
+
+ if (!QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
+
+ QPlatformWindow::requestActivateWindow();
}
-QWasmCompositor::SubControls QWasmWindow::activeSubControl() const
+bool QWasmWindow::setMouseGrabEnabled(bool grab)
{
- return m_activeControl;
+ Q_UNUSED(grab);
+ return false;
}
-void QWasmWindow::setWindowState(Qt::WindowStates states)
+bool QWasmWindow::windowEvent(QEvent *event)
{
- m_windowState = Qt::WindowNoState;
- if (states & Qt::WindowMinimized)
- m_windowState = Qt::WindowMinimized;
- else if (states & Qt::WindowFullScreen)
- m_windowState = Qt::WindowFullScreen;
- else if (states & Qt::WindowMaximized)
- m_windowState = Qt::WindowMaximized;
+ switch (event->type()) {
+ case QEvent::WindowBlocked:
+ m_qtWindow["classList"].call<void>("add", emscripten::val("blocked"));
+ return false; // Propagate further
+ case QEvent::WindowUnblocked:;
+ m_qtWindow["classList"].call<void>("remove", emscripten::val("blocked"));
+ return false; // Propagate further
+ default:
+ return QPlatformWindow::windowEvent(event);
+ }
}
-QRect QWasmWindow::normalGeometry() const
+void QWasmWindow::setMask(const QRegion &region)
{
- return m_normalGeometry;
+ if (region.isEmpty()) {
+ m_qtWindow["style"].set("clipPath", emscripten::val(""));
+ return;
+ }
+
+ std::ostringstream cssClipPath;
+ cssClipPath << "path('";
+ for (const auto &rect : region) {
+ const auto cssRect = rect.adjusted(0, 0, 1, 1);
+ cssClipPath << "M " << cssRect.left() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.bottom() << " ";
+ cssClipPath << "L " << cssRect.left() << " " << cssRect.bottom() << " z ";
+ }
+ cssClipPath << "')";
+ m_qtWindow["style"].set("clipPath", emscripten::val(cssClipPath.str()));
}
-qreal QWasmWindow::devicePixelRatio() const
+void QWasmWindow::setParent(const QPlatformWindow *)
{
- return screen()->devicePixelRatio();
+ commitParent(parentNode());
}
-void QWasmWindow::requestUpdate()
+std::string QWasmWindow::canvasSelector() const
{
- QPointer<QWindow> windowPointer(window());
- bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){
- if (windowPointer.isNull())
- return;
+ return "!qtwindow" + std::to_string(m_winId);
+}
- deliverUpdateRequest();
- });
+emscripten::val QWasmWindow::containerElement()
+{
+ return m_windowContents;
+}
+
+QWasmWindowTreeNode *QWasmWindow::parentNode()
+{
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
+}
- if (!registered)
- QPlatformWindow::requestUpdate();
+QWasmWindow *QWasmWindow::asWasmWindow()
+{
+ return this;
}
-bool QWasmWindow::hasTitleBar() const
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
{
- return !(m_windowState & Qt::WindowFullScreen) && (window()->flags().testFlag(Qt::WindowTitleHint) && m_needsCompositor)
- && !window()->flags().testFlag(Qt::Popup);
+ if (previous)
+ previous->containerElement().call<void>("removeChild", m_qtWindow);
+ if (current)
+ current->containerElement().call<void>("appendChild", m_qtWindow);
+ QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index a098172649..ab0dc68e83 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -1,128 +1,175 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef QWASMWINDOW_H
#define QWASMWINDOW_H
#include "qwasmintegration.h"
#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#include <emscripten/html5.h>
#include "qwasmbackingstore.h"
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
+#include "qwasmwindownonclientarea.h"
+#include "qwasmwindowstack.h"
+#include "qwasmwindowtreenode.h"
+
+#include <QtCore/private/qstdweb_p.h>
+#include "QtGui/qopenglcontext.h"
+#include <QtOpenGL/qopengltextureblitter.h>
+
+#include <emscripten/val.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
-class QWasmCompositor;
+namespace qstdweb {
+class EventCallback;
+}
+
+class ClientArea;
+struct KeyEvent;
+struct PointerEvent;
+class QWasmDeadKeySupport;
+struct WheelEvent;
-class QWasmWindow : public QPlatformWindow
+class QWasmWindow final : public QPlatformWindow,
+ public QWasmWindowTreeNode,
+ public QNativeInterface::Private::QWasmWindow
{
public:
- enum ResizeMode {
- ResizeNone,
- ResizeTopLeft,
- ResizeTop,
- ResizeTopRight,
- ResizeRight,
- ResizeBottomRight,
- ResizeBottom,
- ResizeBottomLeft,
- ResizeLeft
- };
-
- QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore);
- ~QWasmWindow();
- void destroy();
-
+ QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor,
+ QWasmBackingStore *backingStore);
+ ~QWasmWindow() final;
+
+ static QWasmWindow *fromWindow(QWindow *window);
+ QSurfaceFormat format() const override;
+
+ void paint();
+ void setZOrder(int order);
+ void setWindowCursor(QByteArray cssCursorName);
+ void onActivationChanged(bool active);
+ bool isVisible() const;
+
+ void onNonClientAreaInteraction();
+ void onRestoreClicked();
+ void onMaximizeClicked();
+ void onToggleMaximized();
+ void onCloseClicked();
+ bool onNonClientEvent(const PointerEvent &event);
+
+ // QPlatformWindow:
void initialize() override;
-
void setGeometry(const QRect &) override;
void setVisible(bool visible) override;
QMargins frameMargins() const override;
-
WId winId() const override;
-
void propagateSizeHints() override;
+ void setOpacity(qreal level) override;
void raise() override;
void lower() override;
QRect normalGeometry() const override;
qreal devicePixelRatio() const override;
void requestUpdate() override;
+ void requestActivateWindow() override;
+ void setWindowFlags(Qt::WindowFlags flags) override;
+ void setWindowState(Qt::WindowStates state) override;
+ void setWindowTitle(const QString &title) override;
+ void setWindowIcon(const QIcon &icon) override;
+ bool setKeyboardGrabEnabled(bool) override { return false; }
+ bool setMouseGrabEnabled(bool grab) final;
+ bool windowEvent(QEvent *event) final;
+ void setMask(const QRegion &region) final;
+ void setParent(const QPlatformWindow *window) final;
QWasmScreen *platformScreen() const;
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
QWasmBackingStore *backingStore() const { return m_backingStore; }
QWindow *window() const { return m_window; }
- void injectMousePressed(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods);
- void injectMouseReleased(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods);
-
- int titleHeight() const;
- int borderWidth() const;
- QRegion titleGeometry() const;
- QRegion resizeRegion() const;
- bool isPointOnTitle(QPoint point) const;
- bool isPointOnResizeRegion(QPoint point) const;
- ResizeMode resizeModeAtPoint(QPoint point) const;
- QRect maxButtonRect() const;
- QRect minButtonRect() const;
- QRect closeButtonRect() const;
- QRect sysMenuRect() const;
- QRect normButtonRect() const;
- QRegion titleControlRegion() const;
- QWasmCompositor::SubControls activeSubControl() const;
+ std::string canvasSelector() const;
- void setWindowState(Qt::WindowStates state) override;
- bool setKeyboardGrabEnabled(bool) override { return false; }
- bool setMouseGrabEnabled(bool) override { return false; }
+ emscripten::val context2d() const { return m_context2d; }
+ emscripten::val a11yContainer() const { return m_a11yContainer; }
+ emscripten::val inputHandlerElement() const { return m_windowContents; }
+
+ // QNativeInterface::Private::QWasmWindow
+ emscripten::val document() const override { return m_document; }
+ emscripten::val clientArea() const override { return m_qtWindow; }
+
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
+private:
+ friend class QWasmScreen;
+ static constexpr auto defaultWindowSize = 160;
+
+ // QWasmWindowTreeNode:
+ QWasmWindow *asWasmWindow() final;
+ void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference) final;
-protected:
void invalidate();
+ bool hasFrame() const;
bool hasTitleBar() const;
+ bool hasBorder() const;
+ bool hasShadow() const;
+ bool hasMaximizeButton() const;
+ void applyWindowState();
+ void commitParent(QWasmWindowTreeNode *parent);
-protected:
- friend class QWasmScreen;
+ bool processKey(const KeyEvent &event);
+ bool processPointer(const PointerEvent &event);
+ bool processWheel(const WheelEvent &event);
- QWindow* m_window = nullptr;
+ QWindow *m_window = nullptr;
QWasmCompositor *m_compositor = nullptr;
QWasmBackingStore *m_backingStore = nullptr;
+ QWasmDeadKeySupport *m_deadKeySupport;
QRect m_normalGeometry {0, 0, 0 ,0};
- Qt::WindowState m_windowState = Qt::WindowNoState;
- QWasmCompositor::SubControls m_activeControl = QWasmCompositor::SC_None;
- WId m_winid = 0;
+ emscripten::val m_document;
+ emscripten::val m_qtWindow;
+ emscripten::val m_windowContents;
+ emscripten::val m_canvasContainer;
+ emscripten::val m_a11yContainer;
+ emscripten::val m_canvas;
+ emscripten::val m_context2d = emscripten::val::undefined();
+
+ std::unique_ptr<NonClientArea> m_nonClientArea;
+ std::unique_ptr<ClientArea> m_clientArea;
+
+ QWasmWindowTreeNode *m_commitedParent = nullptr;
+
+ std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_wheelEventCallback;
+
+ Qt::WindowStates m_state = Qt::WindowNoState;
+ Qt::WindowStates m_previousWindowState = Qt::WindowNoState;
+
+ Qt::WindowFlags m_flags = Qt::Widget;
+
+ QPoint m_lastPointerMovePoint;
+
+ WId m_winId = 0;
+ bool m_wantCapture = false;
bool m_hasTitle = false;
bool m_needsCompositor = false;
+ long m_requestAnimationFrameId = -1;
friend class QWasmCompositor;
friend class QWasmEventTranslator;
+ bool windowIsPopupType(Qt::WindowFlags flags) const;
};
+
QT_END_NAMESPACE
#endif // QWASMWINDOW_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
new file mode 100644
index 0000000000..6da3e24c05
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
@@ -0,0 +1,195 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowclientarea.h"
+
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmscreen.h"
+#include "qwasmwindow.h"
+#include "qwasmdrag.h"
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpointingdevice.h>
+
+#include <QtCore/qassert.h>
+
+QT_BEGIN_NAMESPACE
+
+ClientArea::ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val element)
+ : m_screen(screen), m_window(window), m_element(element)
+{
+ const auto callback = std::function([this](emscripten::val event) {
+ processPointer(*PointerEvent::fromWeb(event));
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+
+ m_pointerDownCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointerdown", callback);
+ m_pointerMoveCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointermove", callback);
+ m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(element, "pointerup", callback);
+ m_pointerCancelCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointercancel", callback);
+
+ element.call<void>("setAttribute", emscripten::val("draggable"), emscripten::val("true"));
+
+ m_dragStartCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragstart", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragStarted(&event);
+ });
+ m_dragOverCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragover", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragOver(&event);
+ });
+ m_dropCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "drop", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDrop(&event);
+ });
+ m_dragEndCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragend", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragFinished(&event);
+ });
+
+}
+
+bool ClientArea::processPointer(const PointerEvent &event)
+{
+
+ switch (event.type) {
+ case EventType::PointerDown:
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ if ((m_window->window()->flags() & Qt::WindowDoesNotAcceptFocus)
+ != Qt::WindowDoesNotAcceptFocus
+ && m_window->window()->isTopLevel())
+ m_window->window()->requestActivate();
+ break;
+ case EventType::PointerUp:
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ break;
+ default:
+ break;
+ };
+
+ const bool eventAccepted = deliverEvent(event);
+ if (!eventAccepted && event.type == EventType::PointerDown)
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+ return eventAccepted;
+}
+
+bool ClientArea::deliverEvent(const PointerEvent &event)
+{
+ const auto pointInScreen = m_screen->mapFromLocal(
+ dom::mapPoint(event.target(), m_screen->element(), event.localPoint));
+
+ const auto geometryF = m_screen->geometry().toRectF();
+ const QPointF targetPointClippedToScreen(
+ qBound(geometryF.left(), pointInScreen.x(), geometryF.right()),
+ qBound(geometryF.top(), pointInScreen.y(), geometryF.bottom()));
+
+ if (event.pointerType == PointerType::Mouse) {
+ const QEvent::Type eventType =
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::Client);
+
+ return eventType != QEvent::None
+ && QWindowSystemInterface::handleMouseEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, event.mouseButton,
+ eventType, event.modifiers);
+ }
+
+ if (event.pointerType == PointerType::Pen) {
+ qreal pressure;
+ switch (event.type) {
+ case EventType::PointerDown :
+ case EventType::PointerMove :
+ pressure = event.pressure;
+ break;
+ case EventType::PointerUp :
+ pressure = 0.0;
+ break;
+ default:
+ return false;
+ }
+ // Tilt in the browser is in the range +-90, but QTabletEvent only goes to +-60.
+ qreal xTilt = qBound(-60.0, event.tiltX, 60.0);
+ qreal yTilt = qBound(-60.0, event.tiltY, 60.0);
+ // Barrel rotation is reported as 0 to 359, but QTabletEvent wants a signed value.
+ qreal rotation = event.twist > 180.0 ? 360.0 - event.twist : event.twist;
+ return QWindowSystemInterface::handleTabletEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->tabletDevice(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, pressure, xTilt, yTilt,
+ event.tangentialPressure, rotation, event.modifiers);
+ }
+
+ QWindowSystemInterface::TouchPoint *touchPoint;
+
+ QPointF pointInTargetWindowCoords =
+ QPointF(m_window->window()->mapFromGlobal(targetPointClippedToScreen));
+ QPointF normalPosition(pointInTargetWindowCoords.x() / m_window->window()->width(),
+ pointInTargetWindowCoords.y() / m_window->window()->height());
+
+ const auto tp = m_pointerIdToTouchPoints.find(event.pointerId);
+ if (event.pointerType != PointerType::Pen && tp != m_pointerIdToTouchPoints.end()) {
+ touchPoint = &tp.value();
+ } else {
+ touchPoint = &m_pointerIdToTouchPoints
+ .insert(event.pointerId, QWindowSystemInterface::TouchPoint())
+ .value();
+
+ // Assign touch point id. TouchPoint::id is int, but QGuiApplicationPrivate::processTouchEvent()
+ // will not synthesize mouse events for touch points with negative id; use the absolute value for
+ // the touch point id.
+ touchPoint->id = qAbs(event.pointerId);
+
+ touchPoint->state = QEventPoint::State::Pressed;
+ }
+
+ const bool stationaryTouchPoint = (normalPosition == touchPoint->normalPosition);
+ touchPoint->normalPosition = normalPosition;
+ touchPoint->area = QRectF(targetPointClippedToScreen, QSizeF(event.width, event.height))
+ .translated(-event.width / 2, -event.height / 2);
+ touchPoint->pressure = event.pressure;
+
+ switch (event.type) {
+ case EventType::PointerUp:
+ touchPoint->state = QEventPoint::State::Released;
+ break;
+ case EventType::PointerMove:
+ touchPoint->state = (stationaryTouchPoint ? QEventPoint::State::Stationary
+ : QEventPoint::State::Updated);
+ break;
+ default:
+ break;
+ }
+
+ QList<QWindowSystemInterface::TouchPoint> touchPointList;
+ touchPointList.reserve(m_pointerIdToTouchPoints.size());
+ std::transform(m_pointerIdToTouchPoints.begin(), m_pointerIdToTouchPoints.end(),
+ std::back_inserter(touchPointList),
+ [](const QWindowSystemInterface::TouchPoint &val) { return val; });
+
+ if (event.type == EventType::PointerUp)
+ m_pointerIdToTouchPoints.remove(event.pointerId);
+
+ return event.type == EventType::PointerCancel
+ ? QWindowSystemInterface::handleTouchCancelEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ event.modifiers)
+ : QWindowSystemInterface::handleTouchEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ touchPointList, event.modifiers);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.h b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
new file mode 100644
index 0000000000..ba745a59a8
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWCLIENTAREA_H
+#define QWASMWINDOWCLIENTAREA_H
+
+#include <QtCore/qnamespace.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/QMap>
+
+#include <emscripten/val.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+class EventCallback;
+}
+
+struct PointerEvent;
+class QWasmScreen;
+class QWasmWindow;
+
+class ClientArea
+{
+public:
+ ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val element);
+
+private:
+ bool processPointer(const PointerEvent &event);
+ bool deliverEvent(const PointerEvent &event);
+
+ std::unique_ptr<qstdweb::EventCallback> m_pointerDownCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerCancelCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_dragOverCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragStartCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragEndCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
+
+ QMap<int, QWindowSystemInterface::TouchPoint> m_pointerIdToTouchPoints;
+
+ QWasmScreen *m_screen;
+ QWasmWindow *m_window;
+ emscripten::val m_element;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMWINDOWNONCLIENTAREA_H
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
new file mode 100644
index 0000000000..00fa8fb236
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
@@ -0,0 +1,460 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindownonclientarea.h"
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmintegration.h"
+
+#include <qpa/qwindowsysteminterface.h>
+
+#include <QtCore/qassert.h>
+
+QT_BEGIN_NAMESPACE
+
+WebImageButton::Callbacks::Callbacks() = default;
+WebImageButton::Callbacks::Callbacks(std::function<void()> onInteraction,
+ std::function<void()> onClick)
+ : m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick))
+{
+ Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO,
+ "Both callbacks need to be either null or non-null");
+}
+WebImageButton::Callbacks::~Callbacks() = default;
+
+WebImageButton::Callbacks::Callbacks(Callbacks &&) = default;
+WebImageButton::Callbacks &WebImageButton::Callbacks::operator=(Callbacks &&) = default;
+
+void WebImageButton::Callbacks::onInteraction()
+{
+ return m_onInteraction();
+}
+
+void WebImageButton::Callbacks::onClick()
+{
+ return m_onClick();
+}
+
+WebImageButton::WebImageButton()
+ : m_containerElement(
+ dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_imgElement(dom::document().call<emscripten::val>("createElement", emscripten::val("img")))
+{
+ m_imgElement.set("draggable", false);
+
+ m_containerElement["classList"].call<void>("add", emscripten::val("image-button"));
+ m_containerElement.call<void>("appendChild", m_imgElement);
+}
+
+WebImageButton::~WebImageButton() = default;
+
+void WebImageButton::setCallbacks(Callbacks callbacks)
+{
+ if (callbacks) {
+ if (!m_webClickEventCallback) {
+ m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_containerElement, "pointerdown", [this](emscripten::val event) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ m_callbacks.onInteraction();
+ });
+ m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_containerElement, "click", [this](emscripten::val event) {
+ m_callbacks.onClick();
+ event.call<void>("stopPropagation");
+ });
+ }
+ } else {
+ m_webMouseDownEventCallback.reset();
+ m_webClickEventCallback.reset();
+ }
+ dom::syncCSSClassWith(m_containerElement, "action-button", !!callbacks);
+ m_callbacks = std::move(callbacks);
+}
+
+void WebImageButton::setImage(std::string_view imageData, std::string_view format)
+{
+ m_imgElement.set("src",
+ "data:image/" + std::string(format) + ";base64," + std::string(imageData));
+}
+
+void WebImageButton::setVisible(bool visible)
+{
+ m_containerElement["style"].set("display", visible ? "flex" : "none");
+}
+
+Resizer::ResizerElement::ResizerElement(emscripten::val parentElement, Qt::Edges edges,
+ Resizer *resizer)
+ : m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_edges(edges),
+ m_resizer(resizer)
+{
+ Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null");
+
+ m_element["classList"].call<void>("add", emscripten::val("resize-outline"));
+ m_element["classList"].call<void>("add", emscripten::val(cssClassNameForEdges(edges)));
+
+ parentElement.call<void>("appendChild", m_element);
+
+ m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerdown", [this](emscripten::val event) {
+ if (!onPointerDown(*PointerEvent::fromWeb(event)))
+ return;
+ m_resizer->onInteraction();
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+ m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointermove", [this](emscripten::val event) {
+ if (onPointerMove(*PointerEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+ m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerup", [this](emscripten::val event) {
+ if (onPointerUp(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+}
+
+Resizer::ResizerElement::~ResizerElement()
+{
+ m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
+}
+
+Resizer::ResizerElement::ResizerElement(ResizerElement &&other) = default;
+
+bool Resizer::ResizerElement::onPointerDown(const PointerEvent &event)
+{
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ m_capturedPointerId = event.pointerId;
+
+ m_resizer->startResize(m_edges, event);
+ return true;
+}
+
+bool Resizer::ResizerElement::onPointerMove(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_resizer->continueResize(event);
+ return true;
+}
+
+bool Resizer::ResizerElement::onPointerUp(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_resizer->finishResize();
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ m_capturedPointerId = -1;
+ return true;
+}
+
+Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement)
+ : m_window(window), m_windowElement(parentElement)
+{
+ Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null");
+
+ constexpr std::array<int, 8> ResizeEdges = { Qt::TopEdge | Qt::LeftEdge,
+ Qt::TopEdge,
+ Qt::TopEdge | Qt::RightEdge,
+ Qt::LeftEdge,
+ Qt::RightEdge,
+ Qt::BottomEdge | Qt::LeftEdge,
+ Qt::BottomEdge,
+ Qt::BottomEdge | Qt::RightEdge };
+ std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), std::back_inserter(m_elements),
+ [parentElement, this](int edges) {
+ return std::make_unique<ResizerElement>(parentElement,
+ Qt::Edges::fromInt(edges), this);
+ });
+}
+
+Resizer::~Resizer() = default;
+
+ResizeConstraints Resizer::getResizeConstraints() {
+ const auto *window = m_window->window();
+ const auto minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
+ window->minimumHeight() - window->geometry().height());
+ const auto maxGrow = QPoint(window->maximumWidth() - window->geometry().width(),
+ window->maximumHeight() - window->geometry().height());
+
+ const auto frameRect =
+ QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
+ auto containerGeometry =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+
+ const int maxGrowTop = frameRect.top() - containerGeometry.top();
+
+ return ResizeConstraints{minShrink, maxGrow, maxGrowTop};
+}
+
+void Resizer::onInteraction()
+{
+ m_window->onNonClientAreaInteraction();
+}
+
+void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
+{
+ Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress");
+
+ m_currentResizeData.reset(new ResizeData{
+ .edges = resizeEdges,
+ .originInScreenCoords = dom::mapPoint(
+ event.target(), m_window->platformScreen()->element(), event.localPoint),
+ });
+
+ const auto resizeConstraints = getResizeConstraints();
+ m_currentResizeData->minShrink = resizeConstraints.minShrink;
+
+ m_currentResizeData->maxGrow =
+ QPoint(resizeConstraints.maxGrow.x(),
+ std::min(resizeEdges & Qt::Edge::TopEdge ? resizeConstraints.maxGrowTop : INT_MAX,
+ resizeConstraints.maxGrow.y()));
+
+ m_currentResizeData->initialBounds = m_window->window()->geometry();
+}
+
+void Resizer::continueResize(const PointerEvent &event)
+{
+ const auto pointInScreen =
+ dom::mapPoint(event.target(), m_window->platformScreen()->element(), event.localPoint);
+ const auto amount = (pointInScreen - m_currentResizeData->originInScreenCoords).toPoint();
+ const QPoint cappedGrowVector(
+ std::min(m_currentResizeData->maxGrow.x(),
+ std::max(m_currentResizeData->minShrink.x(),
+ (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x()
+ : (m_currentResizeData->edges & Qt::Edge::RightEdge)
+ ? amount.x()
+ : 0)),
+ std::min(m_currentResizeData->maxGrow.y(),
+ std::max(m_currentResizeData->minShrink.y(),
+ (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y()
+ : (m_currentResizeData->edges & Qt::Edge::BottomEdge)
+ ? amount.y()
+ : 0)));
+
+ auto bounds = m_currentResizeData->initialBounds.adjusted(
+ (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0,
+ (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0,
+ (m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0,
+ (m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0);
+
+ m_window->window()->setGeometry(bounds);
+}
+
+void Resizer::finishResize()
+{
+ Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress");
+ m_currentResizeData.reset();
+}
+
+TitleBar::TitleBar(QWasmWindow *window, emscripten::val parentElement)
+ : m_window(window),
+ m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_label(dom::document().call<emscripten::val>("createElement", emscripten::val("div")))
+{
+ m_icon = std::make_unique<WebImageButton>();
+ m_icon->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
+ m_element.call<void>("appendChild", m_icon->htmlElement());
+ m_element.set("className", "title-bar");
+
+ auto spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
+ spacer["style"].set("width", "4px");
+ m_element.call<void>("appendChild", spacer);
+
+ m_label.set("className", "window-name");
+
+ m_element.call<void>("appendChild", m_label);
+
+ spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
+ spacer.set("className", "spacer");
+ m_element.call<void>("appendChild", spacer);
+
+ m_restore = std::make_unique<WebImageButton>();
+ m_restore->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Restore),
+ "svg+xml");
+ m_restore->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onRestoreClicked(); }));
+
+ m_element.call<void>("appendChild", m_restore->htmlElement());
+
+ m_maximize = std::make_unique<WebImageButton>();
+ m_maximize->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Maximize),
+ "svg+xml");
+ m_maximize->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onMaximizeClicked(); }));
+
+ m_element.call<void>("appendChild", m_maximize->htmlElement());
+
+ m_close = std::make_unique<WebImageButton>();
+ m_close->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::X), "svg+xml");
+ m_close->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onCloseClicked(); }));
+
+ m_element.call<void>("appendChild", m_close->htmlElement());
+
+ parentElement.call<void>("appendChild", m_element);
+
+ m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerdown", [this](emscripten::val event) {
+ if (!onPointerDown(*PointerEvent::fromWeb(event)))
+ return;
+ m_window->onNonClientAreaInteraction();
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+ m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointermove", [this](emscripten::val event) {
+ if (onPointerMove(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ }
+ });
+ m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerup", [this](emscripten::val event) {
+ if (onPointerUp(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+ m_doubleClickEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "dblclick", [this](emscripten::val event) {
+ if (onDoubleClick()) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+}
+
+TitleBar::~TitleBar()
+{
+ m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
+}
+
+void TitleBar::setTitle(const QString &title)
+{
+ m_label.set("innerText", emscripten::val(title.toStdString()));
+}
+
+void TitleBar::setRestoreVisible(bool visible)
+{
+ m_restore->setVisible(visible);
+}
+
+void TitleBar::setMaximizeVisible(bool visible)
+{
+ m_maximize->setVisible(visible);
+}
+
+void TitleBar::setCloseVisible(bool visible)
+{
+ m_close->setVisible(visible);
+}
+
+void TitleBar::setIcon(std::string_view imageData, std::string_view format)
+{
+ m_icon->setImage(imageData, format);
+}
+
+void TitleBar::setWidth(int width)
+{
+ m_element["style"].set("width", std::to_string(width) + "px");
+}
+
+QRectF TitleBar::geometry() const
+{
+ return QRectF::fromDOMRect(m_element.call<emscripten::val>("getBoundingClientRect"));
+}
+
+bool TitleBar::onPointerDown(const PointerEvent &event)
+{
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ m_capturedPointerId = event.pointerId;
+
+ m_moveStartWindowPosition = m_window->window()->position();
+ m_moveStartPoint = clipPointWithScreen(event.localPoint);
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onPointerMove(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ const QPoint delta = (clipPointWithScreen(event.localPoint) - m_moveStartPoint).toPoint();
+
+ m_window->window()->setPosition(m_moveStartWindowPosition + delta);
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onPointerUp(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ m_capturedPointerId = -1;
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onDoubleClick()
+{
+ m_window->onToggleMaximized();
+ return true;
+}
+
+QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const
+{
+ auto containerRect =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+ const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(),
+ pointInTitleBarCoords);
+
+ auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()),
+ qBound(0., qreal(p.y()), containerRect.height()));
+ return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint();
+}
+
+NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement)
+ : m_qtWindowElement(qtWindowElement),
+ m_resizer(std::make_unique<Resizer>(window, m_qtWindowElement)),
+ m_titleBar(std::make_unique<TitleBar>(window, m_qtWindowElement))
+{
+ updateResizability();
+}
+
+NonClientArea::~NonClientArea() = default;
+
+void NonClientArea::onClientAreaWidthChange(int width)
+{
+ m_titleBar->setWidth(width);
+}
+
+void NonClientArea::propagateSizeHints()
+{
+ updateResizability();
+}
+
+void NonClientArea::updateResizability()
+{
+ const auto resizeConstraints = m_resizer->getResizeConstraints();
+ const bool nonResizable = resizeConstraints.minShrink.isNull()
+ && resizeConstraints.maxGrow.isNull() && resizeConstraints.maxGrowTop == 0;
+ dom::syncCSSClassWith(m_qtWindowElement, "no-resize", nonResizable);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.h b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
new file mode 100644
index 0000000000..78c77585a0
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
@@ -0,0 +1,227 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWNONCLIENTAREA_H
+#define QWASMWINDOWNONCLIENTAREA_H
+
+#include <QtCore/qrect.h>
+#include <QtCore/qtconfigmacros.h>
+#include <QtCore/qnamespace.h>
+
+#include <emscripten/val.h>
+
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+class EventCallback;
+}
+
+struct PointerEvent;
+class QWindow;
+class Resizer;
+class TitleBar;
+class QWasmWindow;
+
+class NonClientArea
+{
+public:
+ NonClientArea(QWasmWindow *window, emscripten::val containerElement);
+ ~NonClientArea();
+
+ void onClientAreaWidthChange(int width);
+ void propagateSizeHints();
+ TitleBar *titleBar() const { return m_titleBar.get(); }
+
+private:
+ void updateResizability();
+
+ emscripten::val m_qtWindowElement;
+ std::unique_ptr<Resizer> m_resizer;
+ std::unique_ptr<TitleBar> m_titleBar;
+};
+
+class WebImageButton
+{
+public:
+ class Callbacks
+ {
+ public:
+ Callbacks();
+ Callbacks(std::function<void()> onInteraction, std::function<void()> onClick);
+ ~Callbacks();
+
+ Callbacks(const Callbacks &) = delete;
+ Callbacks(Callbacks &&);
+ Callbacks &operator=(const Callbacks &) = delete;
+ Callbacks &operator=(Callbacks &&);
+
+ operator bool() const { return !!m_onInteraction; }
+
+ void onInteraction();
+ void onClick();
+
+ private:
+ std::function<void()> m_onInteraction;
+ std::function<void()> m_onClick;
+ };
+
+ WebImageButton();
+ ~WebImageButton();
+
+ void setCallbacks(Callbacks callbacks);
+ void setImage(std::string_view imageData, std::string_view format);
+ void setVisible(bool visible);
+
+ emscripten::val htmlElement() const { return m_containerElement; }
+ emscripten::val imageElement() const { return m_imgElement; }
+
+private:
+ emscripten::val m_containerElement;
+ emscripten::val m_imgElement;
+
+ std::unique_ptr<qstdweb::EventCallback> m_webMouseMoveEventCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_webMouseDownEventCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_webClickEventCallback;
+
+ Callbacks m_callbacks;
+};
+
+struct ResizeConstraints {
+ QPoint minShrink;
+ QPoint maxGrow;
+ int maxGrowTop;
+};
+
+class Resizer
+{
+public:
+ class ResizerElement
+ {
+ public:
+ static constexpr const char *cssClassNameForEdges(Qt::Edges edges)
+ {
+ switch (edges) {
+ case Qt::TopEdge | Qt::LeftEdge:;
+ return "nw";
+ case Qt::TopEdge:
+ return "n";
+ case Qt::TopEdge | Qt::RightEdge:
+ return "ne";
+ case Qt::LeftEdge:
+ return "w";
+ case Qt::RightEdge:
+ return "e";
+ case Qt::BottomEdge | Qt::LeftEdge:
+ return "sw";
+ case Qt::BottomEdge:
+ return "s";
+ case Qt::BottomEdge | Qt::RightEdge:
+ return "se";
+ default:
+ return "";
+ }
+ }
+
+ ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer);
+ ~ResizerElement();
+ ResizerElement(const ResizerElement &other) = delete;
+ ResizerElement(ResizerElement &&other);
+ ResizerElement &operator=(const ResizerElement &other) = delete;
+ ResizerElement &operator=(ResizerElement &&other) = delete;
+
+ bool onPointerDown(const PointerEvent &event);
+ bool onPointerMove(const PointerEvent &event);
+ bool onPointerUp(const PointerEvent &event);
+
+ private:
+ emscripten::val m_element;
+
+ int m_capturedPointerId = -1;
+
+ const Qt::Edges m_edges;
+
+ Resizer *m_resizer;
+
+ std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseMoveEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
+ };
+
+ using ClickCallback = std::function<void()>;
+
+ Resizer(QWasmWindow *window, emscripten::val parentElement);
+ ~Resizer();
+
+ ResizeConstraints getResizeConstraints();
+
+private:
+ void onInteraction();
+ void startResize(Qt::Edges resizeEdges, const PointerEvent &event);
+ void continueResize(const PointerEvent &event);
+ void finishResize();
+
+ struct ResizeData
+ {
+ Qt::Edges edges = Qt::Edges::fromInt(0);
+ QPointF originInScreenCoords;
+ QPoint minShrink;
+ QPoint maxGrow;
+ QRect initialBounds;
+ };
+ std::unique_ptr<ResizeData> m_currentResizeData;
+
+ QWasmWindow *m_window;
+ emscripten::val m_windowElement;
+ std::vector<std::unique_ptr<ResizerElement>> m_elements;
+};
+
+class TitleBar
+{
+public:
+ TitleBar(QWasmWindow *window, emscripten::val parentElement);
+ ~TitleBar();
+
+ void setTitle(const QString &title);
+ void setRestoreVisible(bool visible);
+ void setMaximizeVisible(bool visible);
+ void setCloseVisible(bool visible);
+ void setIcon(std::string_view imageData, std::string_view format);
+ void setWidth(int width);
+
+ QRectF geometry() const;
+
+private:
+ bool onPointerDown(const PointerEvent &event);
+ bool onPointerMove(const PointerEvent &event);
+ bool onPointerUp(const PointerEvent &event);
+ bool onDoubleClick();
+
+ QPointF clipPointWithScreen(const QPointF &pointInTitleBarCoords) const;
+
+ QWasmWindow *m_window;
+
+ emscripten::val m_element;
+ emscripten::val m_label;
+
+ std::unique_ptr<WebImageButton> m_close;
+ std::unique_ptr<WebImageButton> m_maximize;
+ std::unique_ptr<WebImageButton> m_restore;
+ std::unique_ptr<WebImageButton> m_icon;
+
+ int m_capturedPointerId = -1;
+ QPointF m_moveStartPoint;
+ QPoint m_moveStartWindowPosition;
+
+ std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseMoveEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_doubleClickEvent;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMWINDOWNONCLIENTAREA_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.cpp b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
new file mode 100644
index 0000000000..d3769c7a1b
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
@@ -0,0 +1,203 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowstack.h"
+
+QT_BEGIN_NAMESPACE
+
+QWasmWindowStack::QWasmWindowStack(WindowOrderChangedCallbackType windowOrderChangedCallback)
+ : m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)),
+ m_regularWindowsBegin(m_windowStack.begin()),
+ m_alwaysOnTopWindowsBegin(m_windowStack.begin())
+{
+}
+
+QWasmWindowStack::~QWasmWindowStack() = default;
+
+void QWasmWindowStack::pushWindow(QWasmWindow *window, PositionPreference position)
+{
+ Q_ASSERT(m_windowStack.count(window) == 0);
+
+ if (position == PositionPreference::StayOnTop) {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_windowStack.push_back(window);
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else if (position == PositionPreference::Regular) {
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_alwaysOnTopWindowsBegin = m_windowStack.insert(m_alwaysOnTopWindowsBegin, window) + 1;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ m_regularWindowsBegin = m_windowStack.insert(m_regularWindowsBegin, window) + 1;
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance + 1;
+ }
+
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::removeWindow(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ const auto position = getWindowPositionPreference(it);
+ const auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+
+ m_windowStack.erase(it);
+
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance
+ - (position != PositionPreference::StayOnTop ? 1 : 0);
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance
+ - (position == PositionPreference::StayOnBottom ? 1 : 0);
+
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::raise(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ if (window == topWindow())
+ return;
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itEnd = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_windowStack.end();
+ case PositionPreference::Regular:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_regularWindowsBegin;
+ }
+ })();
+ std::rotate(it, it + 1, itEnd);
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::lower(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ if (window == *m_windowStack.begin())
+ return;
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itBegin = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::Regular:
+ return m_regularWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_windowStack.begin();
+ }
+ })();
+
+ std::rotate(itBegin, it, it + 1);
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::windowPositionPreferenceChanged(QWasmWindow *window,
+ PositionPreference position)
+{
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ const auto currentPosition = getWindowPositionPreference(it);
+
+ const auto zones = static_cast<int>(position) - static_cast<int>(currentPosition);
+ Q_ASSERT(zones != 0);
+
+ if (zones < 0) {
+ // Perform right rotation so that the window lands on top of regular windows
+ const auto begin = std::make_reverse_iterator(it + 1);
+ const auto end = position == PositionPreference::Regular
+ ? std::make_reverse_iterator(m_alwaysOnTopWindowsBegin)
+ : std::make_reverse_iterator(m_regularWindowsBegin);
+ std::rotate(begin, begin + 1, end);
+ if (zones == -2) {
+ ++m_alwaysOnTopWindowsBegin;
+ ++m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ ++m_alwaysOnTopWindowsBegin;
+ } else {
+ ++m_regularWindowsBegin;
+ }
+ } else {
+ // Perform left rotation so that the window lands at the bottom of always on top windows
+ const auto begin = it;
+ const auto end = position == PositionPreference::Regular ? m_regularWindowsBegin
+ : m_alwaysOnTopWindowsBegin;
+ std::rotate(begin, begin + 1, end);
+ if (zones == 2) {
+ --m_alwaysOnTopWindowsBegin;
+ --m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ --m_regularWindowsBegin;
+ } else {
+ --m_alwaysOnTopWindowsBegin;
+ }
+ }
+ m_windowOrderChangedCallback();
+}
+
+QWasmWindowStack::iterator QWasmWindowStack::begin()
+{
+ return m_windowStack.rbegin();
+}
+
+QWasmWindowStack::iterator QWasmWindowStack::end()
+{
+ return m_windowStack.rend();
+}
+
+QWasmWindowStack::const_iterator QWasmWindowStack::begin() const
+{
+ return m_windowStack.rbegin();
+}
+
+QWasmWindowStack::const_iterator QWasmWindowStack::end() const
+{
+ return m_windowStack.rend();
+}
+
+QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rbegin() const
+{
+ return m_windowStack.begin();
+}
+
+QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rend() const
+{
+ return m_windowStack.end();
+}
+
+bool QWasmWindowStack::empty() const
+{
+ return m_windowStack.empty();
+}
+
+size_t QWasmWindowStack::size() const
+{
+ return m_windowStack.size();
+}
+
+QWasmWindow *QWasmWindowStack::topWindow() const
+{
+ return m_windowStack.empty() ? nullptr : m_windowStack.last();
+}
+
+QWasmWindowStack::PositionPreference
+QWasmWindowStack::getWindowPositionPreference(StorageType::iterator windowIt) const
+{
+ if (windowIt >= m_alwaysOnTopWindowsBegin)
+ return PositionPreference::StayOnTop;
+ if (windowIt >= m_regularWindowsBegin)
+ return PositionPreference::Regular;
+ return PositionPreference::StayOnBottom;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.h b/src/plugins/platforms/wasm/qwasmwindowstack.h
new file mode 100644
index 0000000000..c75001157a
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWSTACK_H
+#define QWASMWINDOWSTACK_H
+
+#include <qglobal.h>
+#include <QtCore/qlist.h>
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmWindow;
+
+// Maintains a z-order hierarchy for a set of windows. The first added window is always treated as
+// the 'root', which always stays at the bottom. Other windows are 'regular', which means they are
+// subject to z-order changes via |raise| and |lower|/
+// If the root is ever removed, all of the current and future windows in the stack are treated as
+// regular.
+// Access to the top element is facilitated by |topWindow|.
+// Changes to the top element are signaled via the |topWindowChangedCallback| supplied at
+// construction.
+class Q_AUTOTEST_EXPORT QWasmWindowStack
+{
+public:
+ using WindowOrderChangedCallbackType = std::function<void()>;
+
+ using StorageType = QList<QWasmWindow *>;
+
+ using iterator = StorageType::reverse_iterator;
+ using const_iterator = StorageType::const_reverse_iterator;
+ using const_reverse_iterator = StorageType::const_iterator;
+
+ enum class PositionPreference {
+ StayOnBottom,
+ Regular,
+ StayOnTop,
+ };
+
+ explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback);
+ ~QWasmWindowStack();
+
+ void pushWindow(QWasmWindow *window, PositionPreference position);
+ void removeWindow(QWasmWindow *window);
+ void raise(QWasmWindow *window);
+ void lower(QWasmWindow *window);
+ void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position);
+
+ // Iterates top-to-bottom
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ // Iterates bottom-to-top
+ const_reverse_iterator rbegin() const;
+ const_reverse_iterator rend() const;
+
+ bool empty() const;
+ size_t size() const;
+ QWasmWindow *topWindow() const;
+
+private:
+ PositionPreference getWindowPositionPreference(StorageType::iterator windowIt) const;
+
+ WindowOrderChangedCallbackType m_windowOrderChangedCallback;
+ QList<QWasmWindow *> m_windowStack;
+ StorageType::iterator m_regularWindowsBegin;
+ StorageType::iterator m_alwaysOnTopWindowsBegin;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASMWINDOWSTACK_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
new file mode 100644
index 0000000000..ea8d8dbcfa
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowtreenode.h"
+
+#include "qwasmwindow.h"
+
+QWasmWindowTreeNode::QWasmWindowTreeNode()
+ : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this))
+{
+}
+
+QWasmWindowTreeNode::~QWasmWindowTreeNode() = default;
+
+void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent,
+ QWasmWindowTreeNode *currentParent,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ auto *window = asWasmWindow();
+ if (previousParent) {
+ previousParent->m_childStack.removeWindow(window);
+ previousParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeRemoval, previousParent,
+ window);
+ }
+
+ if (currentParent) {
+ currentParent->m_childStack.pushWindow(window, positionPreference);
+ currentParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeInsertion, currentParent,
+ window);
+ }
+}
+
+QWasmWindow *QWasmWindowTreeNode::asWasmWindow()
+{
+ return nullptr;
+}
+
+void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && m_childStack.topWindow()
+ && m_childStack.topWindow()->window()) {
+
+ const QVariant showWithoutActivating = m_childStack.topWindow()->window()->property("_q_showWithoutActivating");
+ if (!showWithoutActivating.isValid() || !showWithoutActivating.toBool())
+ m_childStack.topWindow()->requestActivateWindow();
+ }
+
+ if (parentNode())
+ parentNode()->onSubtreeChanged(changeType, parent, child);
+}
+
+void QWasmWindowTreeNode::setWindowZOrder(QWasmWindow *window, int z)
+{
+ window->setZOrder(z);
+}
+
+void QWasmWindowTreeNode::onPositionPreferenceChanged(
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (parentNode()) {
+ parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(),
+ positionPreference);
+ }
+}
+
+void QWasmWindowTreeNode::setAsActiveNode()
+{
+ if (parentNode())
+ parentNode()->setActiveChildNode(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::bringToTop()
+{
+ if (!parentNode())
+ return;
+ parentNode()->m_childStack.raise(asWasmWindow());
+ parentNode()->bringToTop();
+}
+
+void QWasmWindowTreeNode::sendToBottom()
+{
+ if (!parentNode())
+ return;
+ m_childStack.lower(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::onTopWindowChanged()
+{
+ constexpr int zOrderForElementInFrontOfScreen = 3;
+ int z = zOrderForElementInFrontOfScreen;
+ std::for_each(m_childStack.rbegin(), m_childStack.rend(),
+ [this, &z](QWasmWindow *window) { setWindowZOrder(window, z++); });
+}
+
+void QWasmWindowTreeNode::setActiveChildNode(QWasmWindow *activeChild)
+{
+ m_activeChild = activeChild;
+
+ auto it = m_childStack.begin();
+ if (it == m_childStack.end())
+ return;
+ for (; it != m_childStack.end(); ++it)
+ (*it)->onActivationChanged(*it == m_activeChild);
+
+ setAsActiveNode();
+}
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
new file mode 100644
index 0000000000..344fdb43cb
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWTREENODE_H
+#define QWASMWINDOWTREENODE_H
+
+#include "qwasmwindowstack.h"
+
+namespace emscripten {
+class val;
+}
+
+class QWasmWindow;
+
+enum class QWasmWindowTreeNodeChangeType {
+ NodeInsertion,
+ NodeRemoval,
+};
+
+class QWasmWindowTreeNode
+{
+public:
+ QWasmWindowTreeNode();
+ virtual ~QWasmWindowTreeNode();
+
+ virtual emscripten::val containerElement() = 0;
+ virtual QWasmWindowTreeNode *parentNode() = 0;
+
+protected:
+ virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference);
+ virtual QWasmWindow *asWasmWindow();
+ virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child);
+ virtual void setWindowZOrder(QWasmWindow *window, int z);
+
+ void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference);
+ void setAsActiveNode();
+ void bringToTop();
+ void sendToBottom();
+
+ const QWasmWindowStack &childStack() const { return m_childStack; }
+ QWasmWindow *activeChild() const { return m_activeChild; }
+
+private:
+ void onTopWindowChanged();
+ void setActiveChildNode(QWasmWindow *activeChild);
+
+ QWasmWindowStack m_childStack;
+ QWasmWindow *m_activeChild = nullptr;
+};
+
+#endif // QWASMWINDOWTREENODE_H
diff --git a/src/plugins/platforms/wasm/resources/maximize.svg b/src/plugins/platforms/wasm/resources/maximize.svg
new file mode 100644
index 0000000000..b5fad4f707
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/maximize.svg
@@ -0,0 +1 @@
+<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M-.333-.333h1024.666v1024.666H-.333V-.333M127.75 255.833V896.25h768.5V255.833h-768.5z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/qtlogo.svg b/src/plugins/platforms/wasm/resources/qtlogo.svg
new file mode 100644
index 0000000000..bfe2493f46
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/qtlogo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="616" height="452" viewBox="0 0 462 339"><path fill="#41cd52" d="M63.5 0H462v274.79c-21.4 21.47-42.87 42.87-64.39 64.21H0V63.39C21.08 42.18 42.34 21.13 63.5 0Z"/><path d="M122.37 71.33C137.5 61.32 156.21 58.79 174 58.95c16.94.21 34.72 3.18 48.76 13.29 10.2 7.17 16.83 18.24 21.25 29.69 7.15 18.8 9.25 39.1 9.49 59.08.03 20.12-.88 40.68-7.54 59.85-4.46 13.04-12.95 24.62-24.15 32.66 8.06 13.06 16.28 26.02 24.34 39.08-10.13 4.67-20.23 9.37-30.37 14.02-8.63-14.24-17.22-28.51-25.88-42.73-11.71 1.92-23.69 1.77-35.46.47-14.1-1.69-28.47-5.99-39.35-15.48-8.36-7.24-13.61-17.37-17.2-27.67-5.88-17.42-7.46-35.96-7.73-54.24-.14-19.76 1.12-39.83 7.08-58.79 4.61-14.26 12.24-28.49 25.13-36.85ZM294.13 70.69c10.6-.01 21.2-.01 31.8 0 .03 14.02-.01 28.03.02 42.05 13.55.02 27.1 0 40.65.01-.23 9.1-.48 18.2-.74 27.3-13.54.03-27.07-.01-40.61.02.03 22.98-.07 45.96.05 68.94.26 6.29.12 12.93 2.89 18.74 2.02 4.48 7.46 5.63 11.89 5.78 8.35-.03 16.69-.52 25.04-.67.51 8.36 1 16.73 1.48 25.09-16.61 2.79-34.04 6.13-50.54.91-6.95-2.06-13.43-6.67-16.25-13.54-5.05-11.69-5.46-24.7-5.68-37.25-.02-22.67 0-45.33-.01-68-7.39-.02-14.78.01-22.17-.02-.02-9.09-.02-18.19 0-27.29 7.39-.03 14.77.01 22.16-.02.03-14.02-.01-28.03.02-42.05Z" fill="#fff"/><path fill="#41cd52" d="M160.51 87.7c10.29-1.34 21.09-.98 30.83 2.91 7.89 3.12 14.59 9.23 18.13 16.97 5.43 11.73 7.51 24.68 8.56 37.47 1.14 17.02.98 34.2-1.37 51.12-1.65 10.07-4 20.68-10.82 28.62-6.92 7.97-17.59 11.39-27.83 12.19-10.8.79-22.19 0-31.94-5.11-5.69-3.03-10.52-7.78-13.34-13.6-3.42-6.97-5.3-14.58-6.62-22.2-3.98-24.16-4.94-49.16.5-73.18 2.24-9.06 5.5-18.36 12.12-25.19 5.76-5.85 13.78-8.87 21.78-10Z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/restore.svg b/src/plugins/platforms/wasm/resources/restore.svg
new file mode 100644
index 0000000000..70ee19170b
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/restore.svg
@@ -0,0 +1 @@
+<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M449.191 44.905h535.142v528.951H449.191V44.906m66.893 132.237v330.594H917.44V177.143H516.084z"/><path stroke="null" d="M54.906 453.476h535.141v528.952H54.906V453.476m66.892 132.238V916.31h401.357V585.714H121.798z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/x.svg b/src/plugins/platforms/wasm/resources/x.svg
new file mode 100644
index 0000000000..1d9ba7361a
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460.775 460.775" style="enable-background:new 0 0 460.775 460.775" xml:space="preserve"><path d="M285.08 230.397 456.218 59.27c6.076-6.077 6.076-15.911 0-21.986L423.511 4.565a15.55 15.55 0 0 0-21.985 0l-171.138 171.14L59.25 4.565a15.551 15.551 0 0 0-21.985 0L4.558 37.284c-6.077 6.075-6.077 15.909 0 21.986l171.138 171.128L4.575 401.505c-6.074 6.077-6.074 15.911 0 21.986l32.709 32.719a15.555 15.555 0 0 0 21.986 0l171.117-171.12 171.118 171.12a15.551 15.551 0 0 0 21.985 0l32.709-32.719c6.074-6.075 6.074-15.909 0-21.986L285.08 230.397z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/wasm.pro b/src/plugins/platforms/wasm/wasm.pro
deleted file mode 100644
index d6f75969d5..0000000000
--- a/src/plugins/platforms/wasm/wasm.pro
+++ /dev/null
@@ -1,78 +0,0 @@
-TARGET = qwasm
-CONFIG += static plugin
-QT += \
- core-private gui-private
-
-qtConfig(opengl): QT += opengl-private
-
-# Avoid X11 header collision, use generic EGL native types
-DEFINES += QT_EGL_NO_X11
-
-DEFINES += QT_NO_FOREACH
-
-SOURCES = \
- main.cpp \
- qwasmintegration.cpp \
- qwasmwindow.cpp \
- qwasmscreen.cpp \
- qwasmfontdatabase.cpp \
- qwasmeventtranslator.cpp \
- qwasmeventdispatcher.cpp \
- qwasmcompositor.cpp \
- qwasmcursor.cpp \
- qwasmopenglcontext.cpp \
- qwasmtheme.cpp \
- qwasmclipboard.cpp \
- qwasmservices.cpp \
- qwasmoffscreensurface.cpp \
- qwasmstring.cpp
-
-HEADERS = \
- qwasmintegration.h \
- qwasmwindow.h \
- qwasmscreen.h \
- qwasmfontdatabase.h \
- qwasmeventtranslator.h \
- qwasmeventdispatcher.h \
- qwasmcompositor.h \
- qwasmstylepixmaps_p.h \
- qwasmcursor.h \
- qwasmopenglcontext.h \
- qwasmtheme.h \
- qwasmclipboard.h \
- qwasmservices.h \
- qwasmoffscreensurface.h \
- qwasmstring.h
-
-wasmfonts.files = \
- ../../../3rdparty/wasm/Vera.ttf \
- ../../../3rdparty/wasm/DejaVuSans.ttf \
- ../../../3rdparty/wasm/DejaVuSansMono.ttf
-wasmfonts.prefix = /fonts
-wasmfonts.base = ../../../3rdparty/wasm
-RESOURCES += wasmfonts
-
-qtConfig(opengl) {
- QT += opengl
- SOURCES += qwasmbackingstore.cpp
- HEADERS += qwasmbackingstore.h
-}
-CONFIG += egl
-
-OTHER_FILES += \
- wasm.json \
- wasm_shell.html \
- qtloader.js
-
-shell_files.path = $$[QT_INSTALL_PLUGINS]/platforms
-shell_files.files = \
- wasm_shell.html \
- qtloader.js \
- qtlogo.svg
-
-INSTALLS += shell_files
-
-PLUGIN_TYPE = platforms
-PLUGIN_CLASS_NAME = QWasmIntegrationPlugin
-!equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = -
-load(qt_plugin)
diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html
index f5712d0418..702ea1f59d 100644
--- a/src/plugins/platforms/wasm/wasm_shell.html
+++ b/src/plugins/platforms/wasm/wasm_shell.html
@@ -12,13 +12,8 @@
<title>@APPNAME@</title>
<style>
/* Make the html body cover the entire (visual) viewport with no scroll bars. */
- html, body { padding: 0; margin: 0; overflow:hidden; height: 100vh }
- /* 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%; }
- /* 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 }
+ html, body { padding: 0; margin: 0; overflow: hidden; height: 100% }
+ #screen { width: 100%; height: 100%; }
</style>
</head>
<body onload="init()">
@@ -30,43 +25,50 @@
<noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript>
</center>
</figure>
- <canvas id="qtcanvas" oncontextmenu="event.preventDefault()" contenteditable="true"></canvas>
+ <div id="screen"></div>
- <script type='text/javascript'>
- function init() {
- var spinner = document.querySelector('#qtspinner');
- var canvas = document.querySelector('#qtcanvas');
- var status = document.querySelector('#qtstatus')
+ <script type="text/javascript">
+ async function init()
+ {
+ const spinner = document.querySelector('#qtspinner');
+ const screen = document.querySelector('#screen');
+ const status = document.querySelector('#qtstatus');
- var qtLoader = QtLoader({
- canvasElements : [canvas],
- showLoader: function(loaderStatus) {
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- status.innerHTML = loaderStatus + "...";
- },
- showError: function(errorText) {
- status.innerHTML = errorText;
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showExit: function() {
- status.innerHTML = "Application exit";
- if (qtLoader.exitCode !== undefined)
- status.innerHTML += " with code " + qtLoader.exitCode;
- if (qtLoader.exitText !== undefined)
- status.innerHTML += " (" + qtLoader.exitText + ")";
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showCanvas: function() {
- spinner.style.display = 'none';
- canvas.style.display = 'block';
- },
- });
- qtLoader.loadEmscriptenModule("@APPNAME@");
- }
+ const showUi = (ui) => {
+ [spinner, screen].forEach(element => element.style.display = 'none');
+ if (screen === ui)
+ screen.style.position = 'default';
+ ui.style.display = 'block';
+ }
+
+ try {
+ showUi(spinner);
+ status.innerHTML = 'Loading...';
+
+ const instance = await qtLoad({
+ qt: {
+ onLoaded: () => showUi(screen),
+ onExit: exitData =>
+ {
+ status.innerHTML = 'Application exit';
+ status.innerHTML +=
+ exitData.code !== undefined ? ` with code ${exitData.code}` : '';
+ status.innerHTML +=
+ exitData.text !== undefined ? ` (${exitData.text})` : '';
+ showUi(spinner);
+ },
+ entryFunction: window.@APPEXPORTNAME@,
+ containerElements: [screen],
+ @PRELOAD@
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ console.error(e.stack);
+ }
+ }
</script>
+ <script src="@APPNAME@.js"></script>
<script type="text/javascript" src="qtloader.js"></script>
</body>
</html>