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