1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include "qwasmintegration.h"
31 #include "qwasmeventtranslator.h"
32 #include "qwasmeventdispatcher.h"
33 #include "qwasmcompositor.h"
34 #include "qwasmopenglcontext.h"
35 #include "qwasmtheme.h"
36 #include "qwasmclipboard.h"
37 #include "qwasmservices.h"
38 #include "qwasmoffscreensurface.h"
39 #include "qwasmstring.h"
40
41 #include "qwasmwindow.h"
42 #ifndef QT_NO_OPENGL
43 # include "qwasmbackingstore.h"
44 #endif
45 #include "qwasmfontdatabase.h"
46 #if defined(Q_OS_UNIX)
47 #include <QtEventDispatcherSupport/private/qgenericunixeventdispatcher_p.h>
48 #endif
49 #include <qpa/qplatformwindow.h>
50 #include <QtGui/qscreen.h>
51 #include <qpa/qwindowsysteminterface.h>
52 #include <QtCore/qcoreapplication.h>
53 #include <qpa/qplatforminputcontextfactory_p.h>
54
55 #include <emscripten/bind.h>
56 #include <emscripten/val.h>
57
58 // this is where EGL headers are pulled in, make sure it is last
59 #include "qwasmscreen.h"
60
61 using namespace emscripten;
62 QT_BEGIN_NAMESPACE
63
browserBeforeUnload(emscripten::val)64 static void browserBeforeUnload(emscripten::val)
65 {
66 QWasmIntegration::QWasmBrowserExit();
67 }
68
addCanvasElement(emscripten::val canvas)69 static void addCanvasElement(emscripten::val canvas)
70 {
71 QWasmIntegration::get()->addScreen(canvas);
72 }
73
removeCanvasElement(emscripten::val canvas)74 static void removeCanvasElement(emscripten::val canvas)
75 {
76 QWasmIntegration::get()->removeScreen(canvas);
77 }
78
resizeCanvasElement(emscripten::val canvas)79 static void resizeCanvasElement(emscripten::val canvas)
80 {
81 QWasmIntegration::get()->resizeScreen(canvas);
82 }
83
qtUpdateDpi()84 static void qtUpdateDpi()
85 {
86 QWasmIntegration::get()->updateDpi();
87 }
88
resizeAllScreens(emscripten::val event)89 static void resizeAllScreens(emscripten::val event)
90 {
91 Q_UNUSED(event);
92 QWasmIntegration::get()->resizeAllScreens();
93 }
94
EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)95 EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)
96 {
97 function("qtBrowserBeforeUnload", &browserBeforeUnload);
98 function("qtAddCanvasElement", &addCanvasElement);
99 function("qtRemoveCanvasElement", &removeCanvasElement);
100 function("qtResizeCanvasElement", &resizeCanvasElement);
101 function("qtUpdateDpi", &qtUpdateDpi);
102 function("qtResizeAllScreens", &resizeAllScreens);
103 }
104
105 QWasmIntegration *QWasmIntegration::s_instance;
106
QWasmIntegration()107 QWasmIntegration::QWasmIntegration()
108 : m_fontDb(nullptr),
109 m_desktopServices(nullptr),
110 m_clipboard(new QWasmClipboard)
111 {
112 s_instance = this;
113
114 // We expect that qtloader.js has populated Module.qtCanvasElements with one or more canvases.
115 emscripten::val qtCanvaseElements = val::module_property("qtCanvasElements");
116 emscripten::val canvas = val::module_property("canvas"); // TODO: remove for Qt 6.0
117
118 if (!qtCanvaseElements.isUndefined()) {
119 int screenCount = qtCanvaseElements["length"].as<int>();
120 for (int i = 0; i < screenCount; ++i) {
121 addScreen(qtCanvaseElements[i].as<emscripten::val>());
122 }
123 } else if (!canvas.isUndefined()) {
124 qWarning() << "Module.canvas is deprecated. A future version of Qt will stop reading this property. "
125 << "Instead, set Module.qtCanvasElements to be an array of canvas elements, or use qtloader.js.";
126 addScreen(canvas);
127 }
128
129 emscripten::val::global("window").set("onbeforeunload", val::module_property("qtBrowserBeforeUnload"));
130
131 // install browser window resize handler
132 auto onWindowResize = [](int eventType, const EmscriptenUiEvent *e, void *userData) -> int {
133 Q_UNUSED(eventType);
134 Q_UNUSED(e);
135 Q_UNUSED(userData);
136
137 // This resize event is called when the HTML window is resized. Depending
138 // on the page layout the canvas(es) might also have been resized, so we
139 // update the Qt screen sizes (and canvas render sizes).
140 if (QWasmIntegration *integration = QWasmIntegration::get())
141 integration->resizeAllScreens();
142 return 0;
143 };
144 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, 1, onWindowResize);
145
146 // install visualViewport resize handler which picks up size and scale change on mobile.
147 emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
148 if (!visualViewport.isUndefined()) {
149 visualViewport.call<void>("addEventListener", val("resize"),
150 val::module_property("qtResizeAllScreens"));
151 }
152 }
153
~QWasmIntegration()154 QWasmIntegration::~QWasmIntegration()
155 {
156 delete m_fontDb;
157 delete m_desktopServices;
158
159 for (const auto &canvasAndScreen : m_screens)
160 QWindowSystemInterface::handleScreenRemoved(canvasAndScreen.second);
161 m_screens.clear();
162
163 s_instance = nullptr;
164 }
165
QWasmBrowserExit()166 void QWasmIntegration::QWasmBrowserExit()
167 {
168 QCoreApplication *app = QCoreApplication::instance();
169 app->quit();
170 }
171
hasCapability(QPlatformIntegration::Capability cap) const172 bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
173 {
174 switch (cap) {
175 case ThreadedPixmaps: return true;
176 case OpenGL: return true;
177 case ThreadedOpenGL: return false;
178 case RasterGLSurface: return false; // to enable this you need to fix qopenglwidget and quickwidget for wasm
179 case MultipleWindows: return true;
180 case WindowManagement: return true;
181 case OpenGLOnRasterSurface: return true;
182 default: return QPlatformIntegration::hasCapability(cap);
183 }
184 }
185
createPlatformWindow(QWindow * window) const186 QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
187 {
188 QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
189 return new QWasmWindow(window, compositor, m_backingStores.value(window));
190 }
191
createPlatformBackingStore(QWindow * window) const192 QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
193 {
194 #ifndef QT_NO_OPENGL
195 QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
196 QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window);
197 m_backingStores.insert(window, backingStore);
198 return backingStore;
199 #else
200 return nullptr;
201 #endif
202 }
203
removeBackingStore(QWindow * window)204 void QWasmIntegration::removeBackingStore(QWindow* window)
205 {
206 m_backingStores.remove(window);
207 }
208
209 #ifndef QT_NO_OPENGL
createPlatformOpenGLContext(QOpenGLContext * context) const210 QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
211 {
212 return new QWasmOpenGLContext(context->format());
213 }
214 #endif
215
initialize()216 void QWasmIntegration::initialize()
217 {
218 QString icStr = QPlatformInputContextFactory::requested();
219 if (!icStr.isNull())
220 m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
221 }
222
inputContext() const223 QPlatformInputContext *QWasmIntegration::inputContext() const
224 {
225 return m_inputContext.data();
226 }
227
createPlatformOffscreenSurface(QOffscreenSurface * surface) const228 QPlatformOffscreenSurface *QWasmIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
229 {
230 return new QWasmOffscrenSurface(surface);
231 }
232
fontDatabase() const233 QPlatformFontDatabase *QWasmIntegration::fontDatabase() const
234 {
235 if (m_fontDb == nullptr)
236 m_fontDb = new QWasmFontDatabase;
237
238 return m_fontDb;
239 }
240
createEventDispatcher() const241 QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const
242 {
243 return new QWasmEventDispatcher;
244 }
245
styleHint(QPlatformIntegration::StyleHint hint) const246 QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
247 {
248 if (hint == ShowIsFullScreen)
249 return true;
250
251 return QPlatformIntegration::styleHint(hint);
252 }
253
defaultWindowState(Qt::WindowFlags flags) const254 Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const
255 {
256 // Don't maximize dialogs
257 if (flags & Qt::Dialog & ~Qt::Window)
258 return Qt::WindowNoState;
259
260 return QPlatformIntegration::defaultWindowState(flags);
261 }
262
themeNames() const263 QStringList QWasmIntegration::themeNames() const
264 {
265 return QStringList() << QLatin1String("webassembly");
266 }
267
createPlatformTheme(const QString & name) const268 QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const
269 {
270 if (name == QLatin1String("webassembly"))
271 return new QWasmTheme;
272 return QPlatformIntegration::createPlatformTheme(name);
273 }
274
services() const275 QPlatformServices *QWasmIntegration::services() const
276 {
277 if (m_desktopServices == nullptr)
278 m_desktopServices = new QWasmServices();
279 return m_desktopServices;
280 }
281
clipboard() const282 QPlatformClipboard* QWasmIntegration::clipboard() const
283 {
284 return m_clipboard;
285 }
286
addScreen(const emscripten::val & canvas)287 void QWasmIntegration::addScreen(const emscripten::val &canvas)
288 {
289 QWasmScreen *screen = new QWasmScreen(canvas);
290 m_screens.append(qMakePair(canvas, screen));
291 m_clipboard->installEventHandlers(canvas);
292 QWindowSystemInterface::handleScreenAdded(screen);
293 }
294
removeScreen(const emscripten::val & canvas)295 void QWasmIntegration::removeScreen(const emscripten::val &canvas)
296 {
297 auto it = std::find_if(m_screens.begin(), m_screens.end(),
298 [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(canvas); });
299 if (it == m_screens.end()) {
300 qWarning() << "Attempting to remove non-existing screen for canvas" << QWasmString::toQString(canvas["id"]);;
301 return;
302 }
303 QWasmScreen *exScreen = it->second;
304 m_screens.erase(it);
305 exScreen->destroy(); // clean up before deleting the screen
306 QWindowSystemInterface::handleScreenRemoved(exScreen);
307 }
308
resizeScreen(const emscripten::val & canvas)309 void QWasmIntegration::resizeScreen(const emscripten::val &canvas)
310 {
311 auto it = std::find_if(m_screens.begin(), m_screens.end(),
312 [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(canvas); });
313 if (it == m_screens.end()) {
314 qWarning() << "Attempting to resize non-existing screen for canvas" << QWasmString::toQString(canvas["id"]);;
315 return;
316 }
317 it->second->updateQScreenAndCanvasRenderSize();
318 }
319
updateDpi()320 void QWasmIntegration::updateDpi()
321 {
322 emscripten::val dpi = emscripten::val::module_property("qtFontDpi");
323 if (dpi.isUndefined())
324 return;
325 qreal dpiValue = dpi.as<qreal>();
326 for (const auto &canvasAndScreen : m_screens)
327 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(canvasAndScreen.second->screen(), dpiValue, dpiValue);
328 }
329
resizeAllScreens()330 void QWasmIntegration::resizeAllScreens()
331 {
332 for (const auto &canvasAndScreen : m_screens)
333 canvasAndScreen.second->updateQScreenAndCanvasRenderSize();
334 }
335
336 QT_END_NAMESPACE
337