From fa6bab9c58b8e91172de69bb9efb8e8b7d125b57 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 24 Aug 2018 14:21:59 +0200 Subject: [PATCH 01/26] darwin: fix tst_qimage deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the test uses CoreGraphics api directly, so it must link it explicitly. amends b0abe20d4. Change-Id: I734bfbedff56df731c1e528670f686565b63f3b5 Reviewed-by: Tor Arne Vestbø --- tests/auto/gui/image/qimage/qimage.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/auto/gui/image/qimage/qimage.pro b/tests/auto/gui/image/qimage/qimage.pro index 39fb4d0cc3..b40866892e 100644 --- a/tests/auto/gui/image/qimage/qimage.pro +++ b/tests/auto/gui/image/qimage/qimage.pro @@ -8,5 +8,6 @@ qtConfig(c++11): CONFIG += c++11 android:!android-embedded: RESOURCES += qimage.qrc win32:!winrt: LIBS += -lgdi32 -luser32 +darwin: LIBS += -framework CoreGraphics TESTDATA += images/* From a25ee4c1ceb35b9d526e87720bbebf541f2a7fb3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 24 Aug 2018 14:21:59 +0200 Subject: [PATCH 02/26] darwin: fix tst_qsettings deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the test uses CoreFoundation api directly, so it must link it explicitly. amends 6c8f32388b. Change-Id: I76b7d8ab41d6a66138eb45bd0f067cfee8e96cb8 Reviewed-by: Tor Arne Vestbø Reviewed-by: Thiago Macieira --- tests/auto/corelib/io/qsettings/qsettings.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/auto/corelib/io/qsettings/qsettings.pro b/tests/auto/corelib/io/qsettings/qsettings.pro index 5b4cc8a691..79552b62df 100644 --- a/tests/auto/corelib/io/qsettings/qsettings.pro +++ b/tests/auto/corelib/io/qsettings/qsettings.pro @@ -6,4 +6,6 @@ RESOURCES += qsettings.qrc INCLUDEPATH += $$PWD/../../kernel/qmetatype msvc: LIBS += advapi32.lib +darwin: LIBS += -framework CoreFoundation + DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 From ed92be50077f001f98f4ea199383a12e5f1e39ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 28 Aug 2018 14:57:25 +0200 Subject: [PATCH 03/26] Add missing dependencies for tst_qmacautoreleasepool Change-Id: Idf94d419526f02e89608b1d105a0858bceabbba1 Reviewed-by: Andy Shaw --- .../corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro index 26b3a47472..8599596344 100644 --- a/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro +++ b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro @@ -2,3 +2,4 @@ CONFIG += testcase TARGET = tst_qmacautoreleasepool QT = core testlib SOURCES = tst_qmacautoreleasepool.mm +LIBS += -framework Foundation From 825e1a05062aa6b0c98c94b74d2f9b3e2fa6ff61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 28 Aug 2018 14:57:46 +0200 Subject: [PATCH 04/26] Add missing dependencies for tst_macnativeevents Change-Id: I5578b78a90588117339562b65884b7bc46770647 Reviewed-by: Andy Shaw --- tests/auto/other/macnativeevents/macnativeevents.pro | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/auto/other/macnativeevents/macnativeevents.pro b/tests/auto/other/macnativeevents/macnativeevents.pro index 0611377d0b..0fe5949a1d 100644 --- a/tests/auto/other/macnativeevents/macnativeevents.pro +++ b/tests/auto/other/macnativeevents/macnativeevents.pro @@ -8,3 +8,5 @@ SOURCES += tst_macnativeevents.cpp requires(mac) DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 + +LIBS += -framework AppKit From 384fc28b908c23dfbd1a955945f17178bba96ff4 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 28 Aug 2018 14:09:51 +0200 Subject: [PATCH 05/26] Fix out of buffer access in qt_qimageScaleRgba64_up_xy Avoid reading a pixel outside the image even if we multiply the result by 0. This mirrors a similar old fix in the the 32bit scaling path. Change-Id: I7860bc808dc46dbc94918672e99c81b56d4a1d27 Reviewed-by: Simon Hausmann --- src/gui/painting/qimagescale.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/painting/qimagescale.cpp b/src/gui/painting/qimagescale.cpp index ca7930500e..96da5e029c 100644 --- a/src/gui/painting/qimagescale.cpp +++ b/src/gui/painting/qimagescale.cpp @@ -564,7 +564,10 @@ static void qt_qimageScaleRgba64_up_xy(QImageScaleInfo *isi, QRgba64 *dest, for (int x = 0; x < dw; x++) { const QRgba64 *pix = sptr + xpoints[x]; const int xap = xapoints[x]; - *dptr = interpolate256(pix[0], 256 - xap, pix[1], xap); + if (xap > 0) + *dptr = interpolate256(pix[0], 256 - xap, pix[1], xap); + else + *dptr = pix[0]; dptr++; } } From 09f59271e677259e1e2ffd3b42fff107001f89a5 Mon Sep 17 00:00:00 2001 From: Ryan Chu Date: Tue, 28 Aug 2018 23:04:08 +0200 Subject: [PATCH 06/26] Postpone testing docker images and files until running 'make check' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid bothering the user who just wants to build all the test cases in qtbase. It is better to move the docker related testing to the check target. Then, it won't break the build procedure and force users to finish test server provisioning in advance. Change-Id: I2c4212297342c3eec7bcbebff9f991b7346d9503 Reviewed-by: Tor Arne Vestbø (cherry picked from commit e798e8a03ba51fad795c3407dce2aec18027b5f1) --- tests/auto/testserver.pri | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/auto/testserver.pri b/tests/auto/testserver.pri index 2adf85a044..b2f593235f 100644 --- a/tests/auto/testserver.pri +++ b/tests/auto/testserver.pri @@ -53,7 +53,6 @@ TESTSERVER_COMPOSE_FILE = $$dirname(_QMAKE_CONF_)/tests/testserver/docker-compose.yml TESTSERVER_VERSION = $$system(docker-compose --version) -TESTSERVER_IMAGES = $$system(docker images -aq "qt-test-server-*") equals(QMAKE_HOST.os, Windows)|isEmpty(TESTSERVER_VERSION) { # Make check with server "qt-test-server.qt-test-net" as a fallback @@ -66,11 +65,14 @@ equals(QMAKE_HOST.os, Windows)|isEmpty(TESTSERVER_VERSION) { # file which is mandatory for all docker-compose commands. You can get more # detail from the description of TESTSERVER_COMPOSE_FILE above. There is # also an example showing how to configure it manually. - isEmpty(TESTSERVER_COMPOSE_FILE): error("Project variable 'TESTSERVER_COMPOSE_FILE' is not set") + FILE_PRETEST_MSG = "Project variable 'TESTSERVER_COMPOSE_FILE' is not set" + testserver_pretest.commands = $(if $$TESTSERVER_COMPOSE_FILE,,$(error $$FILE_PRETEST_MSG)) # Before starting the test servers, it requires the user to run the setup # script (coin/provisioning/.../testserver/docker_testserver.sh) in advance. - isEmpty(TESTSERVER_IMAGES): error("Docker image qt-test-server-* not found") + IMAGE_PRETEST_CMD = docker images -aq "qt-test-server-*" + IMAGE_PRETEST_MSG = "Docker image qt-test-server-* not found" + testserver_pretest.commands += $(if $(shell $$IMAGE_PRETEST_CMD),,$(error $$IMAGE_PRETEST_MSG)) # The domain name is relevant to https keycert (qnetworkreply/crts/qt-test-net-cacert.pem). DNSDOMAIN = test-net.qt.local @@ -85,6 +87,9 @@ equals(QMAKE_HOST.os, Windows)|isEmpty(TESTSERVER_VERSION) { check.target = check_network testserver_test.target = check + # Pretesting test servers environment + testserver_test.depends = testserver_pretest + # Bring up test servers and make sure the services are ready. testserver_test.commands = $$TEST_ENV docker-compose -f $$TESTSERVER_COMPOSE_FILE up -d \ --force-recreate --timeout 1 $${QT_TEST_SERVER_LIST} && @@ -100,5 +105,5 @@ equals(QMAKE_HOST.os, Windows)|isEmpty(TESTSERVER_VERSION) { testserver_clean.commands = $$TEST_ENV docker-compose -f $$TESTSERVER_COMPOSE_FILE down \ --rmi all - QMAKE_EXTRA_TARGETS += testserver_test testserver_clean + QMAKE_EXTRA_TARGETS += testserver_pretest testserver_test testserver_clean } From d2c05b0c8557625ce45cdbd98ebf102972d2c2f4 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 14 Sep 2017 15:14:32 +0200 Subject: [PATCH 07/26] Tablet example: paint only the changed areas of the canvas This improves performance significantly. Change-Id: I9a8782148ce03a2bc85759c4e8c1eecb37467cf6 Reviewed-by: Friedemann Kleint --- examples/widgets/widgets/tablet/tabletcanvas.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/widgets/widgets/tablet/tabletcanvas.cpp b/examples/widgets/widgets/tablet/tabletcanvas.cpp index 3ff26d2ec8..bfcc84e182 100644 --- a/examples/widgets/widgets/tablet/tabletcanvas.cpp +++ b/examples/widgets/widgets/tablet/tabletcanvas.cpp @@ -148,12 +148,14 @@ void TabletCanvas::initPixmap() m_pixmap = newPixmap; } -void TabletCanvas::paintEvent(QPaintEvent *) +void TabletCanvas::paintEvent(QPaintEvent *event) { if (m_pixmap.isNull()) initPixmap(); QPainter painter(this); - painter.drawPixmap(0, 0, m_pixmap); + QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatioF(), + event->rect().size() * devicePixelRatioF()); + painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion); } //! [4] From 9a3bdbf40af7e4915ed343c8e9917a0f3f54e0ae Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sat, 25 Aug 2018 22:39:38 +0200 Subject: [PATCH 08/26] QSS/QTabBar: properly handle QTabBar::scroller PM_TabBarScrollButtonWidth used the size of the ::scroller subcontrol without considering that there are two QToolButtons and therefore returned the wrong size for a single QToolButton. QStyleSheetStyle::subElementRect() had no handling for SE_TabBarScrollLeft/RightButton and therefore the values for the scroller toolbuttons set via the stylesheet were never used. Fix it by dividing the scroller width by two and add the code path to handle SE_TabBarScrollLeft/RightButton in QStyleSheetStyle::subElementRect Task-number: QTBUG-69653 Change-Id: I1adfe6333f3183bba621bbbb4d10969920c6cd46 Reviewed-by: Richard Moe Gustavsen --- src/widgets/styles/qstylesheetstyle.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index fccc4f33a2..3e55bdaabc 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -4853,13 +4853,12 @@ int QStyleSheetStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const return 0; break; - case PM_TabBarScrollButtonWidth: { + case PM_TabBarScrollButtonWidth: subRule = renderRule(w, opt, PseudoElement_TabBarScroller); if (subRule.hasContentsSize()) { QSize sz = subRule.size(); - return sz.width() != -1 ? sz.width() : sz.height(); + return (sz.width() != -1 ? sz.width() : sz.height()) / 2; } - } break; case PM_TabBarTabShiftHorizontal: @@ -5918,6 +5917,12 @@ QRect QStyleSheetStyle::subElementRect(SubElement se, const QStyleOption *opt, c } break; + case SE_TabBarScrollLeftButton: + case SE_TabBarScrollRightButton: + if (hasStyleRule(w, PseudoElement_TabBarScroller)) + return ParentStyle::subElementRect(se, opt, w); + break; + case SE_TabBarTearIndicator: { QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTear); if (subRule.hasContentsSize()) { From 81e298a51d08c510457b4a26b37c0d4aac5eba65 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Fri, 24 Aug 2018 22:56:45 +0200 Subject: [PATCH 09/26] QWidget: fix setTabOrder for compound widgets When adjusting the tab order for compound widgets, it can happen that the order is already correct. The check for this case forgot one case which lead to a garbled focus chain. Task-number: QTBUG-68393 Task-number: QTBUG-69619 Task-number: QTBUG-10907 Change-Id: Ic3242746bdcf3a4db6ea8daa1498381500ca116d Reviewed-by: Frederik Gladhorn Reviewed-by: Richard Moe Gustavsen --- src/widgets/kernel/qwidget.cpp | 4 ++- .../widgets/kernel/qwidget/tst_qwidget.cpp | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 23d8d32b0d..4adccceebb 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -6963,8 +6963,10 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second) determineLastFocusChild(second, lastFocusChildOfSecond); // If the tab order is already correct, exit early - if (lastFocusChildOfFirst->d_func()->focus_next == second) + if (lastFocusChildOfFirst == second || + lastFocusChildOfFirst->d_func()->focus_next == second) { return; + } // Note that we need to handle two different sections in the tab chain; The section // that 'first' belongs to (firstSection), where we are about to insert 'second', and diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index ae50b32164..360e6986f6 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -188,6 +188,7 @@ private slots: void reverseTabOrder(); void tabOrderWithProxy(); void tabOrderWithCompoundWidgets(); + void tabOrderNoChange(); #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) void activation(); #endif @@ -1930,6 +1931,34 @@ void tst_QWidget::tabOrderWithCompoundWidgets() QVERIFY(lastEdit->hasFocus()); } +static QVector getFocusChain(QWidget *start, bool bForward) +{ + QVector ret; + QWidget *cur = start; + do { + ret += cur; + auto widgetPrivate = static_cast(qt_widget_private(cur)); + cur = bForward ? widgetPrivate->focus_next : widgetPrivate->focus_prev; + } while (cur != start); + return ret; +} + +void tst_QWidget::tabOrderNoChange() +{ + QWidget w; + auto *verticalLayout = new QVBoxLayout(&w); + auto *tabWidget = new QTabWidget(&w); + auto *tv = new QTreeView(tabWidget); + tabWidget->addTab(tv, QStringLiteral("Tab 1")); + verticalLayout->addWidget(tabWidget); + + const auto focusChainForward = getFocusChain(&w, true); + const auto focusChainBackward = getFocusChain(&w, false); + QWidget::setTabOrder(tabWidget, tv); + QCOMPARE(focusChainForward, getFocusChain(&w, true)); + QCOMPARE(focusChainBackward, getFocusChain(&w, false)); +} + #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) void tst_QWidget::activation() { From d02f888417c144963c929512f5cb3a0178af9fb4 Mon Sep 17 00:00:00 2001 From: Romain Pokrzywka Date: Wed, 22 Aug 2018 11:31:34 -0500 Subject: [PATCH 10/26] Windows QPA: Fix duplicate Pointer Touch events and missing TouchCancel translateTouchEvent needs to filter events based on the message type passed in msg, as Windows sends more than just down/up/update events, it also sends enter/leave for each touch and occasionally a few more. One of them is WM_POINTERCAPTURECHANGED which indicates a general loss of touch input, which needs to be translated to a touch cancel event. Ignore WM_POINTERENTER/WM_POINTERLEAVE events as they result in sending duplicate Qt::TouchPointPressed/Qt::TouchPointReleased events otherwise. Also avoid sending duplicate events for each additional touchpoint: Windows already bundles all available touchpoints for a touch event when calling GetPointerFrameTouchInfo, so we get all points at once, but we'll still receive other events for each additional touchpoint, resulting in reading the same bundled data again for each one and sending duplicate events to QWindowSystemInterface. Use SkipPointerFrameMessages() to avoid receiving the additional events for the frame we just processed. Finally, add raw event logging when the platform verbose level is >1. Change-Id: I55d840285f642a00f6ffcda4a3efd7ae3985310b Reviewed-by: Andre de la Rocha --- .../platforms/windows/qtwindowsglobal.h | 1 + .../platforms/windows/qwindowscontext.cpp | 4 ++- .../platforms/windows/qwindowscontext.h | 2 ++ .../windows/qwindowspointerhandler.cpp | 25 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index ef22a4451a..985f13bdc5 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -71,6 +71,7 @@ # define WM_POINTERENTER 0x0249 # define WM_POINTERLEAVE 0x024A # define WM_POINTERACTIVATE 0x024B +# define WM_POINTERCAPTURECHANGED 0x024C # define WM_POINTERWHEEL 0x024E # define WM_POINTERHWHEEL 0x024F #endif // WM_POINTERUPDATE diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 0df6264bcb..aa85b2edbe 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -201,6 +201,7 @@ void QWindowsUser32DLL::init() getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo"); getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo"); getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo"); + skipPointerFrameMessages = (SkipPointerFrameMessages)library.resolve("SkipPointerFrameMessages"); } if (QOperatingSystemVersion::current() @@ -214,7 +215,8 @@ void QWindowsUser32DLL::init() bool QWindowsUser32DLL::supportsPointerApi() { return enableMouseInPointer && getPointerType && getPointerInfo && getPointerDeviceRects - && getPointerTouchInfo && getPointerFrameTouchInfo && getPointerPenInfo; + && getPointerTouchInfo && getPointerFrameTouchInfo && getPointerPenInfo + && skipPointerFrameMessages; } void QWindowsShcoreDLL::init() diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 33bd42a669..8102e0bf19 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -93,6 +93,7 @@ struct QWindowsUser32DLL typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID); typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID); typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID); + typedef BOOL (WINAPI *SkipPointerFrameMessages)(UINT32); typedef BOOL (WINAPI *SetProcessDPIAware)(); typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND); typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); @@ -110,6 +111,7 @@ struct QWindowsUser32DLL GetPointerTouchInfo getPointerTouchInfo = nullptr; GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr; GetPointerPenInfo getPointerPenInfo = nullptr; + SkipPointerFrameMessages skipPointerFrameMessages = nullptr; // Windows Vista onwards SetProcessDPIAware setProcessDPIAware = nullptr; diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index abd4d56da0..af9048828f 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -354,6 +354,17 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, if (count < 1) return false; + if (msg.message == WM_POINTERCAPTURECHANGED) { + QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice, + QWindowsKeyMapper::queryKeyboardModifiers()); + m_lastTouchPositions.clear(); + return true; + } + + // Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc. + if (msg.message > WM_POINTERUP) + return false; + const QScreen *screen = window->screen(); if (!screen) screen = QGuiApplication::primaryScreen(); @@ -366,7 +377,18 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, QList touchPoints; + if (QWindowsContext::verbose > 1) + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << __FUNCTION__ + << " message=" << hex << msg.message + << " count=" << dec << count; + for (quint32 i = 0; i < count; ++i) { + if (QWindowsContext::verbose > 1) + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << " TouchPoint id=" << touchInfo[i].pointerInfo.pointerId + << " frame=" << touchInfo[i].pointerInfo.frameId + << " flags=" << hex << touchInfo[i].pointerInfo.pointerFlags; QWindowSystemInterface::TouchPoint touchPoint; touchPoint.id = touchInfo[i].pointerInfo.pointerId; @@ -398,6 +420,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, m_lastTouchPositions.insert(touchPoint.id, touchPoint.normalPosition); } touchPoints.append(touchPoint); + + // Avoid getting repeated messages for this frame if there are multiple pointerIds + QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId); } QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, From c2165f891c1a4380fdbf3bbf7af4696dc863cb99 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Wed, 22 Aug 2018 15:03:12 +0300 Subject: [PATCH 11/26] Allow registering constructor for a set of dynamic types This feature is needed to register complex types (e.g. Q_GADGETS) at runtime, using a single constructor method and added type parameter. Without having the type id available to the Constructor it is impossible to specialize behavior, thus requiring separate constructors for each type. Generating these separate constructors at compile time is easy, but not at runtime. [ChangeLog][QMetaType] QMetaType can now register constructor for a set of dynamic types. Change-Id: I6071271d0e157864594dd07b4bc3a0abbeb15a44 Reviewed-by: Thiago Macieira --- src/corelib/kernel/qmetatype.cpp | 261 +++++++++++------- src/corelib/kernel/qmetatype.h | 33 ++- src/corelib/kernel/qmetatype_p.h | 12 +- .../corelib/kernel/qmetatype/qmetatype.pro | 2 +- .../kernel/qmetatype/tst_qmetatype.cpp | 246 +++++++++++++++++ 5 files changed, 448 insertions(+), 106 deletions(-) diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 76e3d0d014..82952919dd 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -1054,6 +1054,120 @@ int QMetaType::registerType(const char *typeName, Deleter deleter, return registerNormalizedType(normalizedTypeName, deleter, creator, destructor, constructor, size, flags, metaObject); } +/*! + \internal + \since 5.12 + + Registers a user type for marshalling, with \a typeName, a \a + \a destructor, a \a constructor, and a \a size. Returns the + type's handle, or -1 if the type could not be registered. + */ +int QMetaType::registerType(const char *typeName, + TypedDestructor destructor, + TypedConstructor constructor, + int size, + TypeFlags flags, + const QMetaObject *metaObject) +{ +#ifdef QT_NO_QOBJECT + NS(QByteArray) normalizedTypeName = typeName; +#else + NS(QByteArray) normalizedTypeName = QMetaObject::normalizedType(typeName); +#endif + + return registerNormalizedType(normalizedTypeName, destructor, constructor, size, flags, metaObject); +} + + +static int registerNormalizedType(const NS(QByteArray) &normalizedTypeName, + QMetaType::Destructor destructor, + QMetaType::Constructor constructor, + QMetaType::TypedDestructor typedDestructor, + QMetaType::TypedConstructor typedConstructor, + int size, QMetaType::TypeFlags flags, const QMetaObject *metaObject) +{ + QVector *ct = customTypes(); + if (!ct || normalizedTypeName.isEmpty() || (!destructor && !typedDestructor) || (!constructor && !typedConstructor)) + return -1; + + int idx = qMetaTypeStaticType(normalizedTypeName.constData(), + normalizedTypeName.size()); + + int previousSize = 0; + QMetaType::TypeFlags::Int previousFlags = 0; + if (idx == QMetaType::UnknownType) { + QWriteLocker locker(customTypesLock()); + int posInVector = -1; + idx = qMetaTypeCustomType_unlocked(normalizedTypeName.constData(), + normalizedTypeName.size(), + &posInVector); + if (idx == QMetaType::UnknownType) { + QCustomTypeInfo inf; + inf.typeName = normalizedTypeName; +#ifndef QT_NO_DATASTREAM + inf.loadOp = 0; + inf.saveOp = 0; +#endif + inf.alias = -1; + inf.typedConstructor = typedConstructor; + inf.typedDestructor = typedDestructor; + inf.constructor = constructor; + inf.destructor = destructor; + inf.size = size; + inf.flags = flags; + inf.metaObject = metaObject; + if (posInVector == -1) { + idx = ct->size() + QMetaType::User; + ct->append(inf); + } else { + idx = posInVector + QMetaType::User; + ct->data()[posInVector] = inf; + } + return idx; + } + + if (idx >= QMetaType::User) { + previousSize = ct->at(idx - QMetaType::User).size; + previousFlags = ct->at(idx - QMetaType::User).flags; + + // Set new/additional flags in case of old library/app. + // Ensures that older code works in conjunction with new Qt releases + // requiring the new flags. + if (flags != previousFlags) { + QCustomTypeInfo &inf = ct->data()[idx - QMetaType::User]; + inf.flags |= flags; + if (metaObject) + inf.metaObject = metaObject; + } + } + } + + if (idx < QMetaType::User) { + previousSize = QMetaType::sizeOf(idx); + previousFlags = QMetaType::typeFlags(idx); + } + + if (Q_UNLIKELY(previousSize != size)) { + qFatal("QMetaType::registerType: Binary compatibility break " + "-- Size mismatch for type '%s' [%i]. Previously registered " + "size %i, now registering size %i.", + normalizedTypeName.constData(), idx, previousSize, size); + } + + // these flags cannot change in a binary compatible way: + const int binaryCompatibilityFlag = QMetaType::PointerToQObject | QMetaType::IsEnumeration | QMetaType::SharedPointerToQObject + | QMetaType::WeakPointerToQObject | QMetaType::TrackingPointerToQObject; + if (Q_UNLIKELY((previousFlags ^ flags) & binaryCompatibilityFlag)) { + + const char *msg = "QMetaType::registerType: Binary compatibility break. " + "\nType flags for type '%s' [%i] don't match. Previously " + "registered TypeFlags(0x%x), now registering TypeFlags(0x%x). "; + + qFatal(msg, normalizedTypeName.constData(), idx, previousFlags, int(flags)); + } + + return idx; +} /*! \internal @@ -1085,91 +1199,34 @@ int QMetaType::registerNormalizedType(const NS(QByteArray) &normalizedTypeName, \note normalizedTypeName is not checked for conformance with Qt's normalized format, so it must already conform. + + ### TODO Qt6: remove me */ int QMetaType::registerNormalizedType(const NS(QByteArray) &normalizedTypeName, Destructor destructor, Constructor constructor, int size, TypeFlags flags, const QMetaObject *metaObject) { - QVector *ct = customTypes(); - if (!ct || normalizedTypeName.isEmpty() || !destructor || !constructor) - return -1; + return NS(registerNormalizedType)(normalizedTypeName, destructor, constructor, nullptr, nullptr, size, flags, metaObject); +} - int idx = qMetaTypeStaticType(normalizedTypeName.constData(), - normalizedTypeName.size()); +/*! + \internal + \since 5.12 - int previousSize = 0; - QMetaType::TypeFlags::Int previousFlags = 0; - if (idx == UnknownType) { - QWriteLocker locker(customTypesLock()); - int posInVector = -1; - idx = qMetaTypeCustomType_unlocked(normalizedTypeName.constData(), - normalizedTypeName.size(), - &posInVector); - if (idx == UnknownType) { - QCustomTypeInfo inf; - inf.typeName = normalizedTypeName; -#ifndef QT_NO_DATASTREAM - inf.loadOp = 0; - inf.saveOp = 0; -#endif - inf.alias = -1; - inf.constructor = constructor; - inf.destructor = destructor; - inf.size = size; - inf.flags = flags; - inf.metaObject = metaObject; - if (posInVector == -1) { - idx = ct->size() + User; - ct->append(inf); - } else { - idx = posInVector + User; - ct->data()[posInVector] = inf; - } - return idx; - } + Registers a user type for marshalling, with \a normalizedTypeName, + a \a destructor, a \a constructor, and a \a size. Returns the type's + handle, or -1 if the type could not be registered. - if (idx >= User) { - previousSize = ct->at(idx - User).size; - previousFlags = ct->at(idx - User).flags; - - // Set new/additional flags in case of old library/app. - // Ensures that older code works in conjunction with new Qt releases - // requiring the new flags. - if (flags != previousFlags) { - QCustomTypeInfo &inf = ct->data()[idx - User]; - inf.flags |= flags; - if (metaObject) - inf.metaObject = metaObject; - } - } - } - - if (idx < User) { - previousSize = QMetaType::sizeOf(idx); - previousFlags = QMetaType::typeFlags(idx); - } - - if (Q_UNLIKELY(previousSize != size)) { - qFatal("QMetaType::registerType: Binary compatibility break " - "-- Size mismatch for type '%s' [%i]. Previously registered " - "size %i, now registering size %i.", - normalizedTypeName.constData(), idx, previousSize, size); - } - - // these flags cannot change in a binary compatible way: - const int binaryCompatibilityFlag = PointerToQObject | IsEnumeration | SharedPointerToQObject - | WeakPointerToQObject | TrackingPointerToQObject; - if (Q_UNLIKELY((previousFlags ^ flags) & binaryCompatibilityFlag)) { - - const char *msg = "QMetaType::registerType: Binary compatibility break. " - "\nType flags for type '%s' [%i] don't match. Previously " - "registered TypeFlags(0x%x), now registering TypeFlags(0x%x). "; - - qFatal(msg, normalizedTypeName.constData(), idx, previousFlags, int(flags)); - } - - return idx; + \note normalizedTypeName is not checked for conformance with + Qt's normalized format, so it must already conform. + */ +int QMetaType::registerNormalizedType(const NS(QByteArray) &normalizedTypeName, + TypedDestructor destructor, + TypedConstructor constructor, + int size, TypeFlags flags, const QMetaObject *metaObject) +{ + return NS(registerNormalizedType)(normalizedTypeName, nullptr, nullptr, destructor, constructor, size, flags, metaObject); } /*! @@ -1850,14 +1907,19 @@ private: static void *customTypeConstructor(const int type, void *where, const void *copy) { QMetaType::Constructor ctor; + QMetaType::TypedConstructor tctor; const QVector * const ct = customTypes(); { QReadLocker locker(customTypesLock()); if (Q_UNLIKELY(type < QMetaType::User || !ct || ct->count() <= type - QMetaType::User)) return 0; - ctor = ct->at(type - QMetaType::User).constructor; + const auto &typeInfo = ct->at(type - QMetaType::User); + ctor = typeInfo.constructor; + tctor = typeInfo.typedConstructor; } - Q_ASSERT_X(ctor, "void *QMetaType::construct(int type, void *where, const void *copy)", "The type was not properly registered"); + Q_ASSERT_X((ctor || tctor) , "void *QMetaType::construct(int type, void *where, const void *copy)", "The type was not properly registered"); + if (Q_UNLIKELY(tctor)) + return tctor(type, where, copy); return ctor(where, copy); } @@ -1943,14 +2005,19 @@ private: static void customTypeDestructor(const int type, void *where) { QMetaType::Destructor dtor; + QMetaType::TypedDestructor tdtor; const QVector * const ct = customTypes(); { QReadLocker locker(customTypesLock()); if (Q_UNLIKELY(type < QMetaType::User || !ct || ct->count() <= type - QMetaType::User)) return; - dtor = ct->at(type - QMetaType::User).destructor; + const auto &typeInfo = ct->at(type - QMetaType::User); + dtor = typeInfo.destructor; + tdtor = typeInfo.typedDestructor; } - Q_ASSERT_X(dtor, "void QMetaType::destruct(int type, void *where)", "The type was not properly registered"); + Q_ASSERT_X((dtor || tdtor), "void QMetaType::destruct(int type, void *where)", "The type was not properly registered"); + if (Q_UNLIKELY(tdtor)) + return tdtor(type, where); dtor(where); } @@ -2363,10 +2430,12 @@ QMetaType QMetaType::typeInfo(const int type) { TypeInfo typeInfo(type); QMetaTypeSwitcher::switcher(typeInfo, type, 0); - return typeInfo.info.constructor ? QMetaType(static_cast(QMetaType::CreateEx | QMetaType::DestroyEx) - , static_cast(0) // typeInfo::info is a temporary variable, we can't return address of it. - , 0 // unused - , 0 // unused + return (typeInfo.info.constructor || typeInfo.info.typedConstructor) + ? QMetaType(static_cast(QMetaType::CreateEx | QMetaType::DestroyEx | + (typeInfo.info.typedConstructor ? QMetaType::ConstructEx | QMetaType::DestructEx : 0)) + , static_cast(nullptr) // typeInfo::info is a temporary variable, we can't return address of it. + , typeInfo.info.typedConstructor + , typeInfo.info.typedDestructor , typeInfo.info.saveOp , typeInfo.info.loadOp , typeInfo.info.constructor @@ -2408,8 +2477,8 @@ QMetaType::QMetaType(const int typeId) Copy constructs a QMetaType object. */ QMetaType::QMetaType(const QMetaType &other) - : m_creator_unused(other.m_creator_unused) - , m_deleter_unused(other.m_deleter_unused) + : m_typedConstructor(other.m_typedConstructor) + , m_typedDestructor(other.m_typedDestructor) , m_saveOp(other.m_saveOp) , m_loadOp(other.m_loadOp) , m_constructor(other.m_constructor) @@ -2424,8 +2493,8 @@ QMetaType::QMetaType(const QMetaType &other) QMetaType &QMetaType::operator =(const QMetaType &other) { - m_creator_unused = other.m_creator_unused; - m_deleter_unused = other.m_deleter_unused; + m_typedConstructor = other.m_typedConstructor; + m_typedDestructor = other.m_typedDestructor; m_saveOp = other.m_saveOp; m_loadOp = other.m_loadOp; m_constructor = other.m_constructor; @@ -2481,6 +2550,8 @@ void *QMetaType::createExtended(const void *copy) const { if (m_typeId == QMetaType::UnknownType) return 0; + if (Q_UNLIKELY(m_typedConstructor && !m_constructor)) + return m_typedConstructor(m_typeId, operator new(m_size), copy); return m_constructor(operator new(m_size), copy); } @@ -2495,7 +2566,10 @@ void *QMetaType::createExtended(const void *copy) const */ void QMetaType::destroyExtended(void *data) const { - m_destructor(data); + if (Q_UNLIKELY(m_typedDestructor && !m_destructor)) + m_typedDestructor(m_typeId, data); + else + m_destructor(data); operator delete(data); } @@ -2508,9 +2582,9 @@ void QMetaType::destroyExtended(void *data) const */ void *QMetaType::constructExtended(void *where, const void *copy) const { - Q_UNUSED(where); - Q_UNUSED(copy); - return 0; + if (m_typedConstructor && !m_constructor) + return m_typedConstructor(m_typeId, where, copy); + return nullptr; } /*! @@ -2522,7 +2596,8 @@ void *QMetaType::constructExtended(void *where, const void *copy) const */ void QMetaType::destructExtended(void *data) const { - Q_UNUSED(data); + if (m_typedDestructor && !m_destructor) + m_typedDestructor(m_typeId, data); } /*! diff --git a/src/corelib/kernel/qmetatype.h b/src/corelib/kernel/qmetatype.h index 48cbe7f00b..ed7feee775 100644 --- a/src/corelib/kernel/qmetatype.h +++ b/src/corelib/kernel/qmetatype.h @@ -493,8 +493,12 @@ public: typedef void (*Deleter)(void *); typedef void *(*Creator)(const void *); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) typedef void (*Destructor)(void *); - typedef void *(*Constructor)(void *, const void *); + typedef void *(*Constructor)(void *, const void *); // TODO Qt6: remove me +#endif + typedef void (*TypedDestructor)(int, void *); + typedef void *(*TypedConstructor)(int, void *, const void *); typedef void (*SaveOperator)(QDataStream &, const void *); typedef void (*LoadOperator)(QDataStream &, void *); @@ -513,6 +517,12 @@ public: int size, QMetaType::TypeFlags flags, const QMetaObject *metaObject); + static int registerType(const char *typeName, + TypedDestructor destructor, + TypedConstructor constructor, + int size, + QMetaType::TypeFlags flags, + const QMetaObject *metaObject); static bool unregisterType(int type); static int registerNormalizedType(const QT_PREPEND_NAMESPACE(QByteArray) &normalizedTypeName, Deleter deleter, Creator creator, @@ -526,6 +536,11 @@ public: int size, QMetaType::TypeFlags flags, const QMetaObject *metaObject); + static int registerNormalizedType(const QT_PREPEND_NAMESPACE(QByteArray) &normalizedTypeName, TypedDestructor destructor, + TypedConstructor constructor, + int size, + QMetaType::TypeFlags flags, + const QMetaObject *metaObject); static int registerTypedef(const char *typeName, int aliasId); static int registerNormalizedTypedef(const QT_PREPEND_NAMESPACE(QByteArray) &normalizedTypeName, int aliasId); static int type(const char *typeName); @@ -683,8 +698,8 @@ public: private: static QMetaType typeInfo(const int type); inline QMetaType(const ExtensionFlag extensionFlags, const QMetaTypeInterface *info, - Creator creator, - Deleter deleter, + TypedConstructor creator, + TypedDestructor deleter, SaveOperator saveOp, LoadOperator loadOp, Constructor constructor, @@ -731,8 +746,8 @@ public: static void unregisterConverterFunction(int from, int to); private: - Creator m_creator_unused; - Deleter m_deleter_unused; + TypedConstructor m_typedConstructor; + TypedDestructor m_typedDestructor; SaveOperator m_saveOp; LoadOperator m_loadOp; Constructor m_constructor; @@ -2163,8 +2178,8 @@ QT_BEGIN_NAMESPACE #undef Q_DECLARE_METATYPE_TEMPLATE_SMART_POINTER_ITER inline QMetaType::QMetaType(const ExtensionFlag extensionFlags, const QMetaTypeInterface *info, - Creator creator, - Deleter deleter, + TypedConstructor creator, + TypedDestructor deleter, SaveOperator saveOp, LoadOperator loadOp, Constructor constructor, @@ -2173,8 +2188,8 @@ inline QMetaType::QMetaType(const ExtensionFlag extensionFlags, const QMetaTypeI uint theTypeFlags, int typeId, const QMetaObject *_metaObject) - : m_creator_unused(creator) - , m_deleter_unused(deleter) + : m_typedConstructor(creator) + , m_typedDestructor(deleter) , m_saveOp(saveOp) , m_loadOp(loadOp) , m_constructor(constructor) diff --git a/src/corelib/kernel/qmetatype_p.h b/src/corelib/kernel/qmetatype_p.h index 76f43db8d7..94e9228778 100644 --- a/src/corelib/kernel/qmetatype_p.h +++ b/src/corelib/kernel/qmetatype_p.h @@ -126,11 +126,13 @@ class QMetaTypeInterface public: QMetaType::SaveOperator saveOp; QMetaType::LoadOperator loadOp; - QMetaType::Constructor constructor; + QMetaType::Constructor constructor; // TODO Qt6: remove me QMetaType::Destructor destructor; int size; QMetaType::TypeFlags::Int flags; const QMetaObject *metaObject; + QMetaType::TypedConstructor typedConstructor; + QMetaType::TypedDestructor typedDestructor; }; #ifndef QT_NO_DATASTREAM @@ -161,7 +163,9 @@ public: /*destructor*/(QtMetaTypePrivate::QMetaTypeFunctionHelper::IsAvailable>::Destruct), \ /*size*/(QTypeInfo::sizeOf), \ /*flags*/QtPrivate::QMetaTypeTypeFlags::Flags, \ - /*metaObject*/METAOBJECT_DELEGATE(Type) \ + /*metaObject*/METAOBJECT_DELEGATE(Type), \ + /*typedConstructor*/ nullptr, \ + /*typedDestructor*/ nullptr \ } @@ -184,7 +188,9 @@ public: /*destructor*/ 0, \ /*size*/ 0, \ /*flags*/ 0, \ - /*metaObject*/ 0 \ + /*metaObject*/ 0 , \ + /*typedConstructor*/ nullptr, \ + /*typedDestructor*/ nullptr \ } namespace QtMetaTypePrivate { diff --git a/tests/auto/corelib/kernel/qmetatype/qmetatype.pro b/tests/auto/corelib/kernel/qmetatype/qmetatype.pro index ad148ccc7f..d70befecfd 100644 --- a/tests/auto/corelib/kernel/qmetatype/qmetatype.pro +++ b/tests/auto/corelib/kernel/qmetatype/qmetatype.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qmetatype -QT = core testlib +QT = core-private testlib INCLUDEPATH += $$PWD/../../../other/qvariant_common SOURCES = tst_qmetatype.cpp TESTDATA=./typeFlags.bin diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index 73618db3f4..fdf3bb541e 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include "tst_qmetatype.h" #include "tst_qvariant_common.h" @@ -38,6 +40,7 @@ #endif #include +#include // mingw gcc 4.8 also takes way too long, letting the CI system abort the test #if defined(__MINGW32__) @@ -52,12 +55,19 @@ class tst_QMetaType: public QObject Q_PROPERTY(QList prop READ prop WRITE setProp) public: + struct GadgetPropertyType { + QByteArray type; + QByteArray name; + QVariant testData; + }; + tst_QMetaType() { propList << 42 << "Hello"; } QList prop() const { return propList; } void setProp(const QList &list) { propList = list; } private: + void registerGadget(const char * name, const QVector &gadgetProperties); QList propList; private slots: @@ -89,6 +99,7 @@ private slots: void flagsBinaryCompatibility5_0(); void construct_data(); void construct(); + void typedConstruct(); void constructCopy_data(); void constructCopy(); void typedefs(); @@ -115,6 +126,128 @@ private slots: void customDebugStream(); }; +struct BaseGenericType +{ + int m_typeId = -1; + virtual void *constructor(int typeId, void *where, const void *copy) = 0; + virtual void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) = 0; + virtual void saveOperator(QDataStream & out) const = 0; + virtual void loadOperator(QDataStream &in) = 0; + virtual ~BaseGenericType() {} +}; + +struct GenericGadgetType : BaseGenericType +{ + void *constructor(int typeId, void *where, const void *copy) override + { + GenericGadgetType *ret = where ? new(where) GenericGadgetType : new GenericGadgetType; + ret->m_typeId = typeId; + if (copy) { + Q_ASSERT(ret->m_typeId == reinterpret_cast(copy)->m_typeId); + *ret = *reinterpret_cast(copy); + } else { + ret->properties = properties; + } + return ret; + } + + void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) override + { + if (_c == QMetaObject::ReadProperty) { + if (_id < properties.size()) { + const auto &prop = properties.at(_id); + QMetaType::destruct(int(prop.userType()), _a[0]); + QMetaType::construct(int(prop.userType()), _a[0], prop.constData()); + } + } else if (_c == QMetaObject::WriteProperty) { + if (_id < properties.size()) { + auto & prop = properties[_id]; + prop = QVariant(prop.userType(), _a[0]); + } + } + } + + void saveOperator(QDataStream & out) const override + { + for (const auto &prop : properties) + out << prop; + } + + void loadOperator(QDataStream &in) override + { + for (auto &prop : properties) + in >> prop; + } + QVector properties; +}; + +struct GenericPODType : BaseGenericType +{ + // BaseGenericType interface + void *constructor(int typeId, void *where, const void *copy) override + { + GenericPODType *ret = where ? new(where) GenericPODType : new GenericPODType; + ret->m_typeId = typeId; + if (copy) { + Q_ASSERT(ret->m_typeId == reinterpret_cast(copy)->m_typeId); + *ret = *reinterpret_cast(copy); + } else { + ret->podData = podData; + } + return ret; + } + + void staticMetacallFunction(QMetaObject::Call _c, int _id, void **_a) override + { + Q_UNUSED(_c); + Q_UNUSED(_id); + Q_UNUSED(_a); + Q_ASSERT(false); + } + + void saveOperator(QDataStream &out) const override + { + out << podData; + } + void loadOperator(QDataStream &in) override + { + in >> podData; + } + QByteArray podData; +}; + +using RegisteredType = QPair, std::shared_ptr>; +static QHash s_managedTypes; + +static void GadgetsStaticMetacallFunction(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + reinterpret_cast(_o)->staticMetacallFunction(_c, _id, _a); +} + +static void GadgetTypedDestructor(int typeId, void *ptr) +{ + QCOMPARE(typeId, reinterpret_cast(ptr)->m_typeId); + reinterpret_cast(ptr)->~BaseGenericType(); +} + +static void *GadgetTypedConstructor(int type, void *where, const void *copy) +{ + auto it = s_managedTypes.find(type); + if (it == s_managedTypes.end()) + return nullptr; // crash the test + return it->first->constructor(type, where, copy); +} + +static void GadgetSaveOperator(QDataStream & out, const void *data) +{ + reinterpret_cast(data)->saveOperator(out); +} + +static void GadgetLoadOperator(QDataStream &in, void *data) +{ + reinterpret_cast(data)->loadOperator(in); +} + struct Foo { int i; }; @@ -149,6 +282,35 @@ class GadgetDerivedAndTyped : public CustomGadget {}; Q_DECLARE_METATYPE(GadgetDerivedAndTyped) Q_DECLARE_METATYPE(GadgetDerivedAndTyped*) +void tst_QMetaType::registerGadget(const char *name, const QVector &gadgetProperties) +{ + QMetaObjectBuilder gadgetBuilder; + gadgetBuilder.setClassName(name); + QMetaObjectBuilder::MetaObjectFlags metaObjectflags = QMetaObjectBuilder::DynamicMetaObject; + metaObjectflags |= QMetaObjectBuilder::MetaObjectFlags(PropertyAccessInStaticMetaCall); + gadgetBuilder.setFlags(metaObjectflags); + auto dynamicGadgetProperties = std::make_shared(); + for (const auto &prop : gadgetProperties) { + int propertyType = QMetaType::type(prop.type); + dynamicGadgetProperties->properties.push_back(QVariant(QVariant::Type(propertyType))); + auto dynamicPropery = gadgetBuilder.addProperty(prop.name, prop.type); + dynamicPropery.setWritable(true); + dynamicPropery.setReadable(true); + } + auto meta = gadgetBuilder.toMetaObject(); + meta->d.static_metacall = &GadgetsStaticMetacallFunction; + meta->d.superdata = nullptr; + const auto flags = QMetaType::WasDeclaredAsMetaType | QMetaType::IsGadget | QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + int gadgetTypeId = QMetaType::registerType(name, + &GadgetTypedDestructor, + &GadgetTypedConstructor, + sizeof(GenericGadgetType), + flags, meta); + QVERIFY(gadgetTypeId > 0); + QMetaType::registerStreamOperators(gadgetTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + s_managedTypes[gadgetTypeId] = qMakePair(dynamicGadgetProperties, std::shared_ptr{meta, [](QMetaObject *ptr){ ::free(ptr); }}); +} + void tst_QMetaType::defined() { QCOMPARE(int(QMetaTypeId2::Defined), 1); @@ -906,6 +1068,90 @@ FOR_EACH_CORE_METATYPE(RETURN_CONSTRUCT_FUNCTION) TypeTestFunctionGetter::get(type)(); } +void tst_QMetaType::typedConstruct() +{ + auto testMetaObjectWriteOnGadget = [](QVariant &gadget, const QVector &properties) + { + auto metaObject = QMetaType::metaObjectForType(gadget.userType()); + QVERIFY(metaObject != nullptr); + QCOMPARE(metaObject->methodCount(), 0); + QCOMPARE(metaObject->propertyCount(), properties.size()); + for (int i = 0; i < metaObject->propertyCount(); ++i) { + auto prop = metaObject->property(i); + QCOMPARE(properties[i].name, prop.name()); + QCOMPARE(properties[i].type, prop.typeName()); + prop.writeOnGadget(gadget.data(), properties[i].testData); + } + }; + + auto testMetaObjectReadOnGadget = [](QVariant gadget, const QVector &properties) + { + auto metaObject = QMetaType::metaObjectForType(gadget.userType()); + QVERIFY(metaObject != nullptr); + QCOMPARE(metaObject->methodCount(), 0); + QCOMPARE(metaObject->propertyCount(), properties.size()); + for (int i = 0; i < metaObject->propertyCount(); ++i) { + auto prop = metaObject->property(i); + QCOMPARE(properties[i].name, prop.name()); + QCOMPARE(properties[i].type, prop.typeName()); + if (!QMetaType::typeFlags(prop.userType()).testFlag(QMetaType::IsGadget)) + QCOMPARE(properties[i].testData, prop.readOnGadget(gadget.constData())); + } + }; + + QVector dynamicGadget1 = { + {"int", "int_prop", 34526}, + {"float", "float_prop", 1.23f}, + {"QString", "string_prop", QString{"Test QString"}} + }; + registerGadget("DynamicGadget1", dynamicGadget1); + + QVariant testGadget1(QVariant::Type(QMetaType::type("DynamicGadget1"))); + testMetaObjectWriteOnGadget(testGadget1, dynamicGadget1); + testMetaObjectReadOnGadget(testGadget1, dynamicGadget1); + + + QVector dynamicGadget2 = { + {"int", "int_prop", 512}, + {"double", "double_prop", 4.56}, + {"QString", "string_prop", QString{"Another String"}}, + {"DynamicGadget1", "dynamicGadget1_prop", testGadget1} + }; + registerGadget("DynamicGadget2", dynamicGadget2); + QVariant testGadget2(QVariant::Type(QMetaType::type("DynamicGadget2"))); + testMetaObjectWriteOnGadget(testGadget2, dynamicGadget2); + testMetaObjectReadOnGadget(testGadget2, dynamicGadget2); + auto g2mo = QMetaType::metaObjectForType(testGadget2.userType()); + auto dynamicGadget1_prop = g2mo->property(g2mo->indexOfProperty("dynamicGadget1_prop")); + testMetaObjectReadOnGadget(dynamicGadget1_prop.readOnGadget(testGadget2.constData()), dynamicGadget1); + + + // Register POD + const QByteArray myPodTesData = "My POD test data"; + const char podTypeName[] = "DynamicPOD"; + auto dynamicGadgetProperties = std::make_shared(); + dynamicGadgetProperties->podData = myPodTesData; + const auto flags = QMetaType::NeedsConstruction | QMetaType::NeedsDestruction; + int podTypeId = QMetaType::registerType(podTypeName, + &GadgetTypedDestructor, + &GadgetTypedConstructor, + sizeof(GenericGadgetType), + flags, nullptr); + QVERIFY(podTypeId > 0); + QMetaType::registerStreamOperators(podTypeId, &GadgetSaveOperator, &GadgetLoadOperator); + s_managedTypes[podTypeId] = qMakePair(dynamicGadgetProperties, std::shared_ptr{}); + + // Test POD + QCOMPARE(podTypeId, QMetaType::type(podTypeName)); + QVariant podVariant{QVariant::Type(podTypeId)}; + QCOMPARE(myPodTesData, static_cast(reinterpret_cast(podVariant.constData()))->podData); + + QVariant podVariant1{podVariant}; + podVariant1.detach(); // Test stream operators + static_cast(reinterpret_cast(podVariant.data()))->podData.clear(); + QCOMPARE(myPodTesData, static_cast(reinterpret_cast(podVariant1.constData()))->podData); +} + template static void testConstructCopyHelper() { From 19b12ac9c21f4dd079e482c4ed926f51c1776e52 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Fri, 24 Aug 2018 11:23:47 +0300 Subject: [PATCH 12/26] Update QMetaObjectBuilder::MetaObjectFlag enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Synced QMetaObjectBuilder::MetaObjectFlag with the MetaObjectFlags enum from qmetaobject_p.h. Also added a few comments for Qt 6. Change-Id: Ieccd5cf8d512a6bf7256b2f4db88d45662774536 Reviewed-by: Thiago Macieira Reviewed-by: Mårten Nordheim --- src/corelib/kernel/qmetaobject_p.h | 4 +++- src/corelib/kernel/qmetaobjectbuilder_p.h | 7 +++++-- tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp | 4 +--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/corelib/kernel/qmetaobject_p.h b/src/corelib/kernel/qmetaobject_p.h index 4df9a8de77..522bd78e42 100644 --- a/src/corelib/kernel/qmetaobject_p.h +++ b/src/corelib/kernel/qmetaobject_p.h @@ -60,6 +60,8 @@ #include QT_BEGIN_NAMESPACE +// ### TODO Qt6: add a proper namespace with Q_NAMESPACE and use scoped enums +// A namespace and scoped are needed to avoid enum clashes enum PropertyFlags { Invalid = 0x00000000, @@ -103,7 +105,7 @@ enum MethodFlags { MethodRevisioned = 0x80 }; -enum MetaObjectFlags { +enum MetaObjectFlags { // keep it in sync with QMetaObjectBuilder::MetaObjectFlag enum DynamicMetaObject = 0x01, RequiresVariantMetaObject = 0x02, PropertyAccessInStaticMetaCall = 0x04 // since Qt 5.5, property code is in the static metacall diff --git a/src/corelib/kernel/qmetaobjectbuilder_p.h b/src/corelib/kernel/qmetaobjectbuilder_p.h index 781d206e0b..115ec835aa 100644 --- a/src/corelib/kernel/qmetaobjectbuilder_p.h +++ b/src/corelib/kernel/qmetaobjectbuilder_p.h @@ -93,8 +93,11 @@ public: }; Q_DECLARE_FLAGS(AddMembers, AddMember) - enum MetaObjectFlag { - DynamicMetaObject = 0x01 + // ### TODO Qt6: remove me and use the MetaObjectFlags enum from qmetaobject_p.h + enum MetaObjectFlag { // keep it in sync with enum MetaObjectFlags from qmetaobject_p.h + DynamicMetaObject = 0x01, + RequiresVariantMetaObject = 0x02, + PropertyAccessInStaticMetaCall = 0x04 // since Qt 5.5, property code is in the static metacall }; Q_DECLARE_FLAGS(MetaObjectFlags, MetaObjectFlag) diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index fdf3bb541e..5d9b5ca95c 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -29,7 +29,6 @@ #include #include -#include #include #include "tst_qmetatype.h" @@ -286,8 +285,7 @@ void tst_QMetaType::registerGadget(const char *name, const QVector(); for (const auto &prop : gadgetProperties) { From 999d4d5777b098f9c1f311af4addad910e44ab62 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 23 Aug 2018 20:21:24 +0200 Subject: [PATCH 13/26] qmake: rework .prl filename construction instead of trying to reverse-engineer it from the final target including extension and possible bundle path, construct the basename explicitly. this avoids that we mangle the filename if the actual target contains a period for some reason. Task-number: QTBUG-70097 Change-Id: I0bae9f010ab82e258680830250f8e28656f09d67 Reviewed-by: Thiago Macieira --- qmake/generators/makefile.cpp | 13 +------------ qmake/generators/unix/unixmake2.cpp | 7 ++++++- qmake/generators/win32/mingw_make.cpp | 2 -- qmake/generators/win32/winmakefile.cpp | 3 ++- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/qmake/generators/makefile.cpp b/qmake/generators/makefile.cpp index cae179cc93..7c359071bf 100644 --- a/qmake/generators/makefile.cpp +++ b/qmake/generators/makefile.cpp @@ -1102,18 +1102,7 @@ MakefileGenerator::write() QString MakefileGenerator::prlFileName(bool fixify) { - QString ret = project->first("TARGET_PRL").toQString(); - if(ret.isEmpty()) - ret = project->first("TARGET").toQString(); - int slsh = ret.lastIndexOf(Option::dir_sep); - if(slsh != -1) - ret.remove(0, slsh); - if(!ret.endsWith(Option::prl_ext)) { - int dot = ret.indexOf('.'); - if(dot != -1) - ret.truncate(dot); - ret += Option::prl_ext; - } + QString ret = project->first("PRL_TARGET") + Option::prl_ext; if(!project->isEmpty("QMAKE_BUNDLE")) ret.prepend(project->first("QMAKE_BUNDLE") + Option::dir_sep); if(fixify) { diff --git a/qmake/generators/unix/unixmake2.cpp b/qmake/generators/unix/unixmake2.cpp index 2ed1866584..ab946fa439 100644 --- a/qmake/generators/unix/unixmake2.cpp +++ b/qmake/generators/unix/unixmake2.cpp @@ -1244,7 +1244,8 @@ void UnixMakefileGenerator::init2() if(!project->isEmpty("TARGET")) project->values("TARGET").first().prepend(project->first("DESTDIR")); } else if (project->isActiveConfig("staticlib")) { - project->values("TARGET").first().prepend(project->first("QMAKE_PREFIX_STATICLIB")); + project->values("PRL_TARGET") = + project->values("TARGET").first().prepend(project->first("QMAKE_PREFIX_STATICLIB")); project->values("TARGET").first() += "." + project->first("QMAKE_EXTENSION_STATICLIB"); if(project->values("QMAKE_AR_CMD").isEmpty()) project->values("QMAKE_AR_CMD").append("$(AR) $(DESTDIR)$(TARGET) $(OBJECTS)"); @@ -1278,6 +1279,7 @@ void UnixMakefileGenerator::init2() QString prefix; if(!project->isActiveConfig("no_plugin_name_prefix")) prefix = "lib"; + project->values("PRL_TARGET").prepend(prefix + project->first("TARGET")); project->values("TARGET_x.y.z").append(prefix + project->first("TARGET") + "." + project->first("QMAKE_EXTENSION_PLUGIN")); @@ -1291,6 +1293,7 @@ void UnixMakefileGenerator::init2() "." + project->first("VER_MAJ")); project->values("TARGET") = project->values("TARGET_x.y.z"); } else if (!project->isEmpty("QMAKE_HPUX_SHLIB")) { + project->values("PRL_TARGET").prepend("lib" + project->first("TARGET")); project->values("TARGET_").append("lib" + project->first("TARGET") + ".sl"); if(project->isActiveConfig("lib_version_first")) project->values("TARGET_x").append("lib" + project->first("VER_MAJ") + "." + @@ -1300,6 +1303,7 @@ void UnixMakefileGenerator::init2() project->first("VER_MAJ")); project->values("TARGET") = project->values("TARGET_x"); } else if (!project->isEmpty("QMAKE_AIX_SHLIB")) { + project->values("PRL_TARGET").prepend("lib" + project->first("TARGET")); project->values("TARGET_").append(project->first("QMAKE_PREFIX_STATICLIB") + project->first("TARGET") + "." + project->first("QMAKE_EXTENSION_STATICLIB")); if(project->isActiveConfig("lib_version_first")) { @@ -1331,6 +1335,7 @@ void UnixMakefileGenerator::init2() } project->values("TARGET") = project->values("TARGET_x.y.z"); } else { + project->values("PRL_TARGET").prepend("lib" + project->first("TARGET")); project->values("TARGET_").append("lib" + project->first("TARGET") + "." + project->first("QMAKE_EXTENSION_SHLIB")); if(project->isActiveConfig("lib_version_first")) { diff --git a/qmake/generators/win32/mingw_make.cpp b/qmake/generators/win32/mingw_make.cpp index 95f69dbbc3..13412e971a 100644 --- a/qmake/generators/win32/mingw_make.cpp +++ b/qmake/generators/win32/mingw_make.cpp @@ -204,8 +204,6 @@ void MingwMakefileGenerator::init() return; } - project->values("TARGET_PRL").append(project->first("TARGET")); - processVars(); project->values("QMAKE_LIBS") += project->values("RES_FILE"); diff --git a/qmake/generators/win32/winmakefile.cpp b/qmake/generators/win32/winmakefile.cpp index 3c730299be..1388e120e7 100644 --- a/qmake/generators/win32/winmakefile.cpp +++ b/qmake/generators/win32/winmakefile.cpp @@ -185,7 +185,8 @@ void Win32MakefileGenerator::processVars() if (project->first("TEMPLATE").endsWith("aux")) return; - project->values("QMAKE_ORIG_TARGET") = project->values("TARGET"); + project->values("PRL_TARGET") = + project->values("QMAKE_ORIG_TARGET") = project->values("TARGET"); if (project->isEmpty("QMAKE_PROJECT_NAME")) project->values("QMAKE_PROJECT_NAME") = project->values("QMAKE_ORIG_TARGET"); else if (project->first("TEMPLATE").startsWith("vc")) From f9431f5632ca577a099bd4063f1412cff81f5f8d Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 22 Aug 2018 15:32:33 +0200 Subject: [PATCH 14/26] Fix tst_QSizePolicy::constExpr() to compile with MSVC 15.8.1 For ControlType != QSizePolicy::DefaultType, qCountTrailingZeroBits() is used which, MSVC 15.8.1 does not consider constexpr due to built-ins. Exclude the check by #ifdef. Task-number: QTBUG-69983 Change-Id: Ifc3dc3d94a804246bee1705fc8f3ac9b04ee4d32 Reviewed-by: Alexander Shevchenko Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- .../widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp index eb3264be53..89bb946be9 100644 --- a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp +++ b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp @@ -116,8 +116,16 @@ void tst_QSizePolicy::constExpr() { Q_CONSTEXPR QSizePolicy sp = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); Q_UNUSED(sp); } { Q_CONSTEXPR QSizePolicy sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding, QSizePolicy::DefaultType); Q_CONSTEXPR QSizePolicy tp = sp.transposed(); Q_UNUSED(tp); } - { Q_RELAXED_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::CheckBox); - Q_RELAXED_CONSTEXPR auto tp = sp.transposed(); Q_UNUSED(tp); } + { + // QTBUG-69983: For ControlType != QSizePolicy::DefaultType, qCountTrailingZeroBits() + // is used, which MSVC 15.8.1 does not consider constexpr due to built-ins +# if defined(QT_HAS_CONSTEXPR_BUILTINS) && (!defined(Q_CC_MSVC) || _MSC_VER < 1915) + Q_RELAXED_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::CheckBox); +# else + Q_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding, QSizePolicy::DefaultType); +# endif + Q_RELAXED_CONSTEXPR auto tp = sp.transposed(); Q_UNUSED(tp); + } #else QSKIP("QSizePolicy cannot be constexpr with this version of the compiler."); #endif From 64e6441d9c017663df134b1b02324295e0450baf Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 29 Aug 2018 08:31:08 +0200 Subject: [PATCH 15/26] Windows code: Fix clang-tidy warnings about C-style casts Replace by reinterpret_cast or const_cast, respectively. Use auto when initializing a variable to fix Clang warnings about repeating the type name, do minor tidying along the way, and a few conversions of 0 or NULL to nullptr. Change-Id: Ieb271a87ddcf064f536e1ff05d23b1e688b1b56a Reviewed-by: Edward Welbourne Reviewed-by: Thiago Macieira --- .../global/qoperatingsystemversion_win.cpp | 2 +- src/corelib/io/qfilesystemengine_win.cpp | 16 +++++++---- src/corelib/io/qsettings_win.cpp | 28 +++++++++---------- src/corelib/kernel/qeventdispatcher_win.cpp | 12 ++++---- src/corelib/kernel/qsharedmemory_win.cpp | 9 ++++-- src/corelib/kernel/qsystemsemaphore_win.cpp | 7 +++-- src/corelib/plugin/qlibrary_win.cpp | 4 +-- src/corelib/plugin/qsystemlibrary.cpp | 2 +- src/corelib/thread/qthread_win.cpp | 10 ++++--- src/corelib/tools/qlocale_win.cpp | 2 +- src/corelib/tools/qtimezoneprivate_win.cpp | 18 ++++++------ src/network/kernel/qhostinfo_win.cpp | 8 +++--- src/network/kernel/qnetworkinterface_win.cpp | 21 +++++++------- src/network/kernel/qnetworkproxy_win.cpp | 11 ++++---- src/network/socket/qlocalserver_win.cpp | 10 +++---- src/network/socket/qlocalsocket_win.cpp | 6 ++-- .../socket/qnativesocketengine_win.cpp | 7 +++-- .../windows/qwindowsfontdatabase.cpp | 6 ++-- .../windows/qwindowsfontengine.cpp | 7 ++--- .../windows/qwindowsprintdevice.cpp | 21 ++++++++------ .../windows/qwindowsprintdevice.h | 2 ++ src/printsupport/kernel/qprintengine_win.cpp | 14 ++++++---- src/widgets/dialogs/qwizard_win.cpp | 6 ++-- 23 files changed, 126 insertions(+), 103 deletions(-) diff --git a/src/corelib/global/qoperatingsystemversion_win.cpp b/src/corelib/global/qoperatingsystemversion_win.cpp index 7659fb2550..2da190da48 100644 --- a/src/corelib/global/qoperatingsystemversion_win.cpp +++ b/src/corelib/global/qoperatingsystemversion_win.cpp @@ -103,7 +103,7 @@ static inline OSVERSIONINFOEX determineWinOsVersion() // GetVersionEx() has been deprecated in Windows 8.1 and will return // only Windows 8 from that version on, so use the kernel API function. - pRtlGetVersion((LPOSVERSIONINFO) &result); // always returns STATUS_SUCCESS + pRtlGetVersion(reinterpret_cast(&result)); // always returns STATUS_SUCCESS #else // !Q_OS_WINCE GetVersionEx(&result); #endif diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index fadc058110..74081b4ffa 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -935,7 +935,7 @@ static bool tryFindFallback(const QFileSystemEntry &fname, QFileSystemMetaData & bool QFileSystemEngine::fillMetaData(int fd, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) { - HANDLE fHandle = (HANDLE)_get_osfhandle(fd); + auto fHandle = reinterpret_cast(_get_osfhandle(fd)); if (fHandle != INVALID_HANDLE_VALUE) { return fillMetaData(fHandle, data, what); } @@ -1014,7 +1014,8 @@ bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemM WIN32_FIND_DATA findData; // The memory structure for WIN32_FIND_DATA is same as WIN32_FILE_ATTRIBUTE_DATA // for all members used by fillFindData(). - bool ok = ::GetFileAttributesEx((wchar_t*)fname.nativeFilePath().utf16(), GetFileExInfoStandard, + bool ok = ::GetFileAttributesEx(reinterpret_cast(fname.nativeFilePath().utf16()), + GetFileExInfoStandard, reinterpret_cast(&findData)); if (ok) { data.fillFromFindData(findData, false, fname.isDriveRoot()); @@ -1069,19 +1070,22 @@ static bool isDirPath(const QString &dirPath, bool *existed) if (path.length() == 2 && path.at(1) == QLatin1Char(':')) path += QLatin1Char('\\'); + const QString longPath = QFSFileEnginePrivate::longFileName(path); #ifndef Q_OS_WINRT - DWORD fileAttrib = ::GetFileAttributes((wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16()); + DWORD fileAttrib = ::GetFileAttributes(reinterpret_cast(longPath.utf16())); #else // Q_OS_WINRT DWORD fileAttrib = INVALID_FILE_ATTRIBUTES; WIN32_FILE_ATTRIBUTE_DATA data; - if (::GetFileAttributesEx((const wchar_t*)QFSFileEnginePrivate::longFileName(path).utf16(), GetFileExInfoStandard, &data)) + if (::GetFileAttributesEx(reinterpret_cast(longPath.utf16()), + GetFileExInfoStandard, &data)) { fileAttrib = data.dwFileAttributes; + } #endif // Q_OS_WINRT if (fileAttrib == INVALID_FILE_ATTRIBUTES) { int errorCode = GetLastError(); if (errorCode == ERROR_ACCESS_DENIED || errorCode == ERROR_SHARING_VIOLATION) { WIN32_FIND_DATA findData; - if (getFindData(QFSFileEnginePrivate::longFileName(path), findData)) + if (getFindData(longPath, findData)) fileAttrib = findData.dwFileAttributes; } } @@ -1411,7 +1415,7 @@ bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Per if (mode == 0) // not supported return false; - bool ret = (::_wchmod((wchar_t*)entry.nativeFilePath().utf16(), mode) == 0); + bool ret = ::_wchmod(reinterpret_cast(entry.nativeFilePath().utf16()), mode) == 0; if(!ret) error = QSystemError(errno, QSystemError::StandardLibraryError); return ret; diff --git a/src/corelib/io/qsettings_win.cpp b/src/corelib/io/qsettings_win.cpp index 43e1411c76..e1922674ef 100644 --- a/src/corelib/io/qsettings_win.cpp +++ b/src/corelib/io/qsettings_win.cpp @@ -515,7 +515,7 @@ bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVa case REG_SZ: { QString s; if (dataSize) { - s = QString::fromWCharArray(((const wchar_t *)data.constData())); + s = QString::fromWCharArray(reinterpret_cast(data.constData())); } if (value != 0) *value = stringToVariant(s); @@ -527,7 +527,7 @@ bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVa if (dataSize) { int i = 0; for (;;) { - QString s = QString::fromWCharArray((const wchar_t *)data.constData() + i); + QString s = QString::fromWCharArray(reinterpret_cast(data.constData()) + i); i += s.length() + 1; if (s.isEmpty()) @@ -544,7 +544,7 @@ bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVa case REG_BINARY: { QString s; if (dataSize) { - s = QString::fromWCharArray((const wchar_t *)data.constData(), data.size() / 2); + s = QString::fromWCharArray(reinterpret_cast(data.constData()), data.size() / 2); } if (value != 0) *value = stringToVariant(s); @@ -555,7 +555,7 @@ bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVa case REG_DWORD: { Q_ASSERT(data.size() == sizeof(int)); int i; - memcpy((char*)&i, data.constData(), sizeof(int)); + memcpy(reinterpret_cast(&i), data.constData(), sizeof(int)); if (value != 0) *value = i; break; @@ -564,7 +564,7 @@ bool QWinSettingsPrivate::readKey(HKEY parentHandle, const QString &rSubKey, QVa case REG_QWORD: { Q_ASSERT(data.size() == sizeof(qint64)); qint64 i; - memcpy((char*)&i, data.constData(), sizeof(qint64)); + memcpy(reinterpret_cast(&i), data.constData(), sizeof(qint64)); if (value != 0) *value = i; break; @@ -688,12 +688,12 @@ void QWinSettingsPrivate::set(const QString &uKey, const QVariant &value) if (type == REG_BINARY) { QString s = variantToString(value); - regValueBuff = QByteArray((const char*)s.utf16(), s.length() * 2); + regValueBuff = QByteArray(reinterpret_cast(s.utf16()), s.length() * 2); } else { QStringList::const_iterator it = l.constBegin(); for (; it != l.constEnd(); ++it) { const QString &s = *it; - regValueBuff += QByteArray((const char*)s.utf16(), (s.length() + 1) * 2); + regValueBuff += QByteArray(reinterpret_cast(s.utf16()), (s.length() + 1) * 2); } regValueBuff.append((char)0); regValueBuff.append((char)0); @@ -705,7 +705,7 @@ void QWinSettingsPrivate::set(const QString &uKey, const QVariant &value) case QVariant::UInt: { type = REG_DWORD; qint32 i = value.toInt(); - regValueBuff = QByteArray((const char*)&i, sizeof(qint32)); + regValueBuff = QByteArray(reinterpret_cast(&i), sizeof(qint32)); break; } @@ -713,7 +713,7 @@ void QWinSettingsPrivate::set(const QString &uKey, const QVariant &value) case QVariant::ULongLong: { type = REG_QWORD; qint64 i = value.toLongLong(); - regValueBuff = QByteArray((const char*)&i, sizeof(qint64)); + regValueBuff = QByteArray(reinterpret_cast(&i), sizeof(qint64)); break; } @@ -725,11 +725,11 @@ void QWinSettingsPrivate::set(const QString &uKey, const QVariant &value) // string type. Otherwise we use REG_BINARY. QString s = variantToString(value); type = s.contains(QChar::Null) ? REG_BINARY : REG_SZ; - if (type == REG_BINARY) { - regValueBuff = QByteArray((const char*)s.utf16(), s.length() * 2); - } else { - regValueBuff = QByteArray((const char*)s.utf16(), (s.length() + 1) * 2); - } + int length = s.length(); + if (type == REG_SZ) + ++length; + regValueBuff = QByteArray(reinterpret_cast(s.utf16()), + int(sizeof(wchar_t)) * length); break; } } diff --git a/src/corelib/kernel/qeventdispatcher_win.cpp b/src/corelib/kernel/qeventdispatcher_win.cpp index f1a4088d98..2862f42b2b 100644 --- a/src/corelib/kernel/qeventdispatcher_win.cpp +++ b/src/corelib/kernel/qeventdispatcher_win.cpp @@ -120,7 +120,7 @@ void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, { if (!timerId) // sanity check return; - WinTimerInfo *t = (WinTimerInfo*)user; + auto t = reinterpret_cast(user); Q_ASSERT(t); QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId)); } @@ -146,9 +146,9 @@ LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPA } #ifdef GWLP_USERDATA - QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + auto q = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); #else - QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLong(hwnd, GWL_USERDATA); + auto q = reinterpret_cast(GetWindowLong(hwnd, GWL_USERDATA)); #endif QEventDispatcherWin32Private *d = 0; if (q != 0) @@ -369,9 +369,9 @@ static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatch } #ifdef GWLP_USERDATA - SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)eventDispatcher); + SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast(eventDispatcher)); #else - SetWindowLong(wnd, GWL_USERDATA, (LONG)eventDispatcher); + SetWindowLong(wnd, GWL_USERDATA, reinterpret_cast(eventDispatcher)); #endif return wnd; @@ -603,7 +603,7 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) if (haveMessage) { // WinCE doesn't support hooks at all, so we have to call this by hand :( if (!d->getMessageHook) - (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg); + (void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast(&msg)); if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) { if (seenWM_QT_SENDPOSTEDEVENTS) { diff --git a/src/corelib/kernel/qsharedmemory_win.cpp b/src/corelib/kernel/qsharedmemory_win.cpp index be42a369c2..467398e039 100644 --- a/src/corelib/kernel/qsharedmemory_win.cpp +++ b/src/corelib/kernel/qsharedmemory_win.cpp @@ -101,7 +101,8 @@ HANDLE QSharedMemoryPrivate::handle() #if defined(Q_OS_WINRT) hand = OpenFileMappingFromApp(FILE_MAP_ALL_ACCESS, FALSE, reinterpret_cast(nativeKey.utf16())); #else - hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, (wchar_t*)nativeKey.utf16()); + hand = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, + reinterpret_cast(nativeKey.utf16())); #endif if (!hand) { setErrorString(function); @@ -133,9 +134,11 @@ bool QSharedMemoryPrivate::create(int size) // Create the file mapping. #if defined(Q_OS_WINRT) - hand = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, size, (PCWSTR)nativeKey.utf16()); + hand = CreateFileMappingFromApp(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, size, + reinterpret_cast(nativeKey.utf16())); #else - hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, size, (wchar_t*)nativeKey.utf16()); + hand = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, size, + reinterpret_cast(nativeKey.utf16())); #endif setErrorString(function); diff --git a/src/corelib/kernel/qsystemsemaphore_win.cpp b/src/corelib/kernel/qsystemsemaphore_win.cpp index 3395f5641e..2b35803291 100644 --- a/src/corelib/kernel/qsystemsemaphore_win.cpp +++ b/src/corelib/kernel/qsystemsemaphore_win.cpp @@ -86,9 +86,12 @@ HANDLE QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode) // Create it if it doesn't already exists. if (semaphore == 0) { #if defined(Q_OS_WINRT) - semaphore = CreateSemaphoreEx(0, initialValue, MAXLONG, (wchar_t*)fileName.utf16(), 0, SEMAPHORE_ALL_ACCESS); + semaphore = CreateSemaphoreEx(0, initialValue, MAXLONG, + reinterpret_cast(fileName.utf16()), + 0, SEMAPHORE_ALL_ACCESS); #else - semaphore = CreateSemaphore(0, initialValue, MAXLONG, (wchar_t*)fileName.utf16()); + semaphore = CreateSemaphore(0, initialValue, MAXLONG, + reinterpret_cast(fileName.utf16())); #endif if (semaphore == NULL) setErrorString(QLatin1String("QSystemSemaphore::handle")); diff --git a/src/corelib/plugin/qlibrary_win.cpp b/src/corelib/plugin/qlibrary_win.cpp index 9368e53b3f..05a93d467e 100644 --- a/src/corelib/plugin/qlibrary_win.cpp +++ b/src/corelib/plugin/qlibrary_win.cpp @@ -97,10 +97,10 @@ bool QLibraryPrivate::load_sys() for (const QString &attempt : qAsConst(attempts)) { #ifndef Q_OS_WINRT - pHnd = LoadLibrary((wchar_t*)QDir::toNativeSeparators(attempt).utf16()); + pHnd = LoadLibrary(reinterpret_cast(QDir::toNativeSeparators(attempt).utf16())); #else // Q_OS_WINRT QString path = QDir::toNativeSeparators(QDir::current().relativeFilePath(attempt)); - pHnd = LoadPackagedLibrary((LPCWSTR)path.utf16(), 0); + pHnd = LoadPackagedLibrary(reinterpret_cast(path.utf16()), 0); if (pHnd) qualifiedFileName = attempt; #endif // !Q_OS_WINRT diff --git a/src/corelib/plugin/qsystemlibrary.cpp b/src/corelib/plugin/qsystemlibrary.cpp index 7c80fbbd42..1f8cef790c 100644 --- a/src/corelib/plugin/qsystemlibrary.cpp +++ b/src/corelib/plugin/qsystemlibrary.cpp @@ -121,7 +121,7 @@ HINSTANCE QSystemLibrary::load(const wchar_t *libraryName, bool onlySystemDirect fullPathAttempt.append(QLatin1Char('\\')); } fullPathAttempt.append(fileName); - HINSTANCE inst = ::LoadLibrary((const wchar_t *)fullPathAttempt.utf16()); + HINSTANCE inst = ::LoadLibrary(reinterpret_cast(fullPathAttempt.utf16())); if (inst != 0) return inst; } diff --git a/src/corelib/thread/qthread_win.cpp b/src/corelib/thread/qthread_win.cpp index 57ae671897..5714f5236c 100644 --- a/src/corelib/thread/qthread_win.cpp +++ b/src/corelib/thread/qthread_win.cpp @@ -318,7 +318,8 @@ void qt_set_thread_name(HANDLE threadId, LPCSTR threadName) __try { - RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (const ULONG_PTR*)&info); + RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), + reinterpret_cast(&info)); } __except (EXCEPTION_CONTINUE_EXECUTION) { @@ -365,7 +366,7 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi #if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) && !defined(Q_OS_WINRT) // sets the name of the current thread. QByteArray objectName = thr->objectName().toLocal8Bit(); - qt_set_thread_name((HANDLE)-1, + qt_set_thread_name(HANDLE(-1), objectName.isEmpty() ? thr->metaObject()->className() : objectName.constData()); #endif @@ -508,8 +509,9 @@ void QThread::start(Priority priority) this, CREATE_SUSPENDED, &(d->id)); #else // MSVC -MD or -MDd or MinGW build - d->handle = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start, - this, CREATE_SUSPENDED, reinterpret_cast(&d->id)); + d->handle = CreateThread(nullptr, d->stackSize, + reinterpret_cast(QThreadPrivate::start), + this, CREATE_SUSPENDED, reinterpret_cast(&d->id)); #endif // Q_OS_WINRT if (!d->handle) { diff --git a/src/corelib/tools/qlocale_win.cpp b/src/corelib/tools/qlocale_win.cpp index 30aefb71c1..e1a61fa9d7 100644 --- a/src/corelib/tools/qlocale_win.cpp +++ b/src/corelib/tools/qlocale_win.cpp @@ -274,7 +274,7 @@ QSystemLocalePrivate::SubstitutionType QSystemLocalePrivate::substitution() QString &QSystemLocalePrivate::substituteDigits(QString &string) { ushort zero = zeroDigit().unicode(); - ushort *qch = (ushort *)string.data(); + ushort *qch = reinterpret_cast(string.data()); for (ushort *end = qch + string.size(); qch != end; ++qch) { if (*qch >= '0' && *qch <= '9') *qch = zero + (*qch - '0'); diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp index ec91b7e8a8..ca7ea24fc5 100644 --- a/src/corelib/tools/qtimezoneprivate_win.cpp +++ b/src/corelib/tools/qtimezoneprivate_win.cpp @@ -140,15 +140,15 @@ bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMATION &tz #ifdef QT_USE_REGISTRY_TIMEZONE bool openRegistryKey(const QString &keyPath, HKEY *key) { - return (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (const wchar_t*)keyPath.utf16(), 0, KEY_READ, key) - == ERROR_SUCCESS); + return RegOpenKeyEx(HKEY_LOCAL_MACHINE, reinterpret_cast(keyPath.utf16()), + 0, KEY_READ, key) == ERROR_SUCCESS; } QString readRegistryString(const HKEY &key, const wchar_t *value) { wchar_t buffer[MAX_PATH] = {0}; DWORD size = sizeof(wchar_t) * MAX_PATH; - RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (LPBYTE)buffer, &size); + RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast(buffer), &size); return QString::fromWCharArray(buffer); } @@ -156,7 +156,7 @@ int readRegistryValue(const HKEY &key, const wchar_t *value) { DWORD buffer; DWORD size = sizeof(buffer); - RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (LPBYTE)&buffer, &size); + RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast(&buffer), &size); return buffer; } @@ -167,7 +167,7 @@ QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(const HKEY &key, QWinTimeZonePrivate::QWinTransitionRule rule; REG_TZI_FORMAT tzi; DWORD tziSize = sizeof(tzi); - if (RegQueryValueEx(key, (LPCWSTR)value, NULL, NULL, (BYTE *)&tzi, &tziSize) + if (RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast(&tzi), &tziSize) == ERROR_SUCCESS) { rule.startYear = 0; rule.standardTimeBias = tzi.Bias + tzi.StandardBias; @@ -192,12 +192,12 @@ TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *ok) if (openRegistryKey(tziKeyPath, &key)) { DWORD size = sizeof(tzi.DaylightName); - RegQueryValueEx(key, L"Dlt", NULL, NULL, (LPBYTE)tzi.DaylightName, &size); + RegQueryValueEx(key, L"Dlt", nullptr, nullptr, reinterpret_cast(tzi.DaylightName), &size); size = sizeof(tzi.StandardName); - RegQueryValueEx(key, L"Std", NULL, NULL, (LPBYTE)tzi.StandardName, &size); + RegQueryValueEx(key, L"Std", nullptr, nullptr, reinterpret_cast(tzi.StandardName), &size); - if (RegQueryValueEx(key, L"TZI", NULL, NULL, (BYTE *) ®Tzi, ®TziSize) + if (RegQueryValueEx(key, L"TZI", nullptr, nullptr, reinterpret_cast(®Tzi), ®TziSize) == ERROR_SUCCESS) { tzi.Bias = regTzi.Bias; tzi.StandardBias = regTzi.StandardBias; @@ -590,7 +590,7 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) for (int year = startYear; year <= endYear; ++year) { bool ruleOk; QWinTransitionRule rule = readRegistryRule(dynamicKey, - (LPCWSTR)QString::number(year).utf16(), + reinterpret_cast(QString::number(year).utf16()), &ruleOk); if (ruleOk // Don't repeat a recurrent rule: diff --git a/src/network/kernel/qhostinfo_win.cpp b/src/network/kernel/qhostinfo_win.cpp index bea24b0af2..c51e9968f8 100644 --- a/src/network/kernel/qhostinfo_win.cpp +++ b/src/network/kernel/qhostinfo_win.cpp @@ -92,13 +92,13 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) sockaddr *sa; QT_SOCKLEN_T saSize; if (address.protocol() == QAbstractSocket::IPv4Protocol) { - sa = (sockaddr *)&sa4; + sa = reinterpret_cast(&sa4); saSize = sizeof(sa4); memset(&sa4, 0, sizeof(sa4)); sa4.sin_family = AF_INET; sa4.sin_addr.s_addr = htonl(address.toIPv4Address()); } else { - sa = (sockaddr *)&sa6; + sa = reinterpret_cast(&sa6); saSize = sizeof(sa6); memset(&sa6, 0, sizeof(sa6)); sa6.sin6_family = AF_INET6; @@ -132,14 +132,14 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName) switch (p->ai_family) { case AF_INET: { QHostAddress addr; - addr.setAddress(ntohl(((sockaddr_in *) p->ai_addr)->sin_addr.s_addr)); + addr.setAddress(ntohl(reinterpret_cast(p->ai_addr)->sin_addr.s_addr)); if (!addresses.contains(addr)) addresses.append(addr); } break; case AF_INET6: { QHostAddress addr; - addr.setAddress(((sockaddr_in6 *) p->ai_addr)->sin6_addr.s6_addr); + addr.setAddress(reinterpret_cast(p->ai_addr)->sin6_addr.s6_addr); if (!addresses.contains(addr)) addresses.append(addr); } diff --git a/src/network/kernel/qnetworkinterface_win.cpp b/src/network/kernel/qnetworkinterface_win.cpp index 150553f673..a8d56a9b11 100644 --- a/src/network/kernel/qnetworkinterface_win.cpp +++ b/src/network/kernel/qnetworkinterface_win.cpp @@ -74,15 +74,16 @@ static QHostAddress addressFromSockaddr(sockaddr *sa) if (!sa) return address; - if (sa->sa_family == AF_INET) - address.setAddress(htonl(((sockaddr_in *)sa)->sin_addr.s_addr)); - else if (sa->sa_family == AF_INET6) { - address.setAddress(((sockaddr_in6 *)sa)->sin6_addr.s6_addr); - int scope = ((sockaddr_in6 *)sa)->sin6_scope_id; - if (scope) - address.setScopeId(QNetworkInterfaceManager::interfaceNameFromIndex(scope)); - } else + if (sa->sa_family == AF_INET) { + address.setAddress(htonl(reinterpret_cast(sa)->sin_addr.s_addr)); + } else if (sa->sa_family == AF_INET6) { + auto sai6 = reinterpret_cast(sa); + address.setAddress(sai6->sin6_addr.s6_addr); + if (sai6->sin6_scope_id) + address.setScopeId(QNetworkInterfaceManager::interfaceNameFromIndex(sai6->sin6_scope_id)); + } else { qWarning("Got unknown socket family %d", sa->sa_family); + } return address; } @@ -121,7 +122,7 @@ static QList interfaceListing() ULONG retval = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAdapter, &bufSize); if (retval == ERROR_BUFFER_OVERFLOW) { // need more memory - pAdapter = (IP_ADAPTER_ADDRESSES *)malloc(bufSize); + pAdapter = reinterpret_cast(malloc(bufSize)); if (!pAdapter) return interfaces; // try again @@ -255,7 +256,7 @@ QString QHostInfo::localDomainName() ULONG bufSize = sizeof info; pinfo = &info; if (GetNetworkParams(pinfo, &bufSize) == ERROR_BUFFER_OVERFLOW) { - pinfo = (FIXED_INFO *)malloc(bufSize); + pinfo = reinterpret_cast(malloc(bufSize)); if (!pinfo) return QString(); // try again diff --git a/src/network/kernel/qnetworkproxy_win.cpp b/src/network/kernel/qnetworkproxy_win.cpp index 949f9fe12b..e67faaf856 100644 --- a/src/network/kernel/qnetworkproxy_win.cpp +++ b/src/network/kernel/qnetworkproxy_win.cpp @@ -566,7 +566,7 @@ void QWindowsSystemProxy::init() WINHTTP_AUTO_DETECT_TYPE_DNS_A; } else { autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; - autoProxyOptions.lpszAutoConfigUrl = (LPCWSTR)autoConfigUrl.utf16(); + autoProxyOptions.lpszAutoConfigUrl = reinterpret_cast(autoConfigUrl.utf16()); } } @@ -608,7 +608,7 @@ QList QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro } bool getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession, - (LPCWSTR)urlQueryString.utf16(), + reinterpret_cast(urlQueryString.utf16()), &sp->autoProxyOptions, &proxyInfo); DWORD getProxyError = GetLastError(); @@ -623,9 +623,10 @@ QList QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro } else { //pac file URL is specified as well, try using that sp->autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; - sp->autoProxyOptions.lpszAutoConfigUrl = (LPCWSTR)sp->autoConfigUrl.utf16(); + sp->autoProxyOptions.lpszAutoConfigUrl = + reinterpret_cast(sp->autoConfigUrl.utf16()); getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession, - (LPCWSTR)urlQueryString.utf16(), + reinterpret_cast(urlQueryString.utf16()), &sp->autoProxyOptions, &proxyInfo); getProxyError = GetLastError(); @@ -638,7 +639,7 @@ QList QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro // But now we've to enable it (http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx) sp->autoProxyOptions.fAutoLogonIfChallenged = TRUE; getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession, - (LPCWSTR)urlQueryString.utf16(), + reinterpret_cast(urlQueryString.utf16()), &sp->autoProxyOptions, &proxyInfo); getProxyError = GetLastError(); diff --git a/src/network/socket/qlocalserver_win.cpp b/src/network/socket/qlocalserver_win.cpp index ced923ced1..2d71a7e730 100644 --- a/src/network/socket/qlocalserver_win.cpp +++ b/src/network/socket/qlocalserver_win.cpp @@ -89,7 +89,7 @@ bool QLocalServerPrivate::addListener() DWORD dwBufferSize = 0; GetTokenInformation(hToken, TokenUser, 0, 0, &dwBufferSize); tokenUserBuffer.fill(0, dwBufferSize); - PTOKEN_USER pTokenUser = (PTOKEN_USER)tokenUserBuffer.data(); + auto pTokenUser = reinterpret_cast(tokenUserBuffer.data()); if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)) { setError(QLatin1String("QLocalServerPrivate::addListener")); CloseHandle(hToken); @@ -99,7 +99,7 @@ bool QLocalServerPrivate::addListener() dwBufferSize = 0; GetTokenInformation(hToken, TokenPrimaryGroup, 0, 0, &dwBufferSize); tokenGroupBuffer.fill(0, dwBufferSize); - PTOKEN_PRIMARY_GROUP pTokenGroup = (PTOKEN_PRIMARY_GROUP)tokenGroupBuffer.data(); + auto pTokenGroup = reinterpret_cast(tokenGroupBuffer.data()); if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dwBufferSize, &dwBufferSize)) { setError(QLatin1String("QLocalServerPrivate::addListener")); CloseHandle(hToken); @@ -140,7 +140,7 @@ bool QLocalServerPrivate::addListener() aclSize = (aclSize + (sizeof(DWORD) - 1)) & 0xfffffffc; aclBuffer.fill(0, aclSize); - PACL acl = (PACL)aclBuffer.data(); + auto acl = reinterpret_cast(aclBuffer.data()); InitializeAcl(acl, aclSize, ACL_REVISION_DS); if (socketOptions & QLocalServer::UserAccessOption) { @@ -176,7 +176,7 @@ bool QLocalServerPrivate::addListener() } listener.handle = CreateNamedPipe( - (const wchar_t *)fullServerName.utf16(), // pipe name + reinterpret_cast(fullServerName.utf16()), // pipe name PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access PIPE_TYPE_BYTE | // byte type pipe PIPE_READMODE_BYTE | // byte-read mode @@ -299,7 +299,7 @@ void QLocalServerPrivate::_q_onNewConnection() tryAgain = true; // Make this the last thing so connected slots can wreak the least havoc - q->incomingConnection((quintptr)handle); + q->incomingConnection(reinterpret_cast(handle)); } else { if (GetLastError() != ERROR_IO_INCOMPLETE) { q->close(); diff --git a/src/network/socket/qlocalsocket_win.cpp b/src/network/socket/qlocalsocket_win.cpp index ae94cb9d51..8e20f9efbe 100644 --- a/src/network/socket/qlocalsocket_win.cpp +++ b/src/network/socket/qlocalsocket_win.cpp @@ -155,7 +155,7 @@ void QLocalSocket::connectToServer(OpenMode openMode) forever { DWORD permissions = (openMode & QIODevice::ReadOnly) ? GENERIC_READ : 0; permissions |= (openMode & QIODevice::WriteOnly) ? GENERIC_WRITE : 0; - localSocket = CreateFile((const wchar_t *)d->fullServerName.utf16(), // pipe name + localSocket = CreateFile(reinterpret_cast(d->fullServerName.utf16()), // pipe name permissions, 0, // no sharing NULL, // default security attributes @@ -183,7 +183,7 @@ void QLocalSocket::connectToServer(OpenMode openMode) } // we have a valid handle - if (setSocketDescriptor((qintptr)localSocket, ConnectedState, openMode)) + if (setSocketDescriptor(reinterpret_cast(localSocket), ConnectedState, openMode)) emit connected(); } @@ -371,7 +371,7 @@ void QLocalSocketPrivate::_q_canWrite() qintptr QLocalSocket::socketDescriptor() const { Q_D(const QLocalSocket); - return (qintptr)d->handle; + return reinterpret_cast(d->handle); } qint64 QLocalSocket::readBufferSize() const diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index 1b84b26d83..c999bd2088 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -294,7 +294,8 @@ static inline QAbstractSocket::SocketType qt_socket_getType(qintptr socketDescri { int value = 0; QT_SOCKLEN_T valueSize = sizeof(value); - if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, (char *) &value, &valueSize) != 0) { + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, + reinterpret_cast(&value), &valueSize) != 0) { WS_ERROR_DEBUG(WSAGetLastError()); } else { if (value == SOCK_STREAM) @@ -361,7 +362,7 @@ bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType soc #ifdef HANDLE_FLAG_INHERIT if (socket != INVALID_SOCKET) { // make non inheritable the old way - BOOL handleFlags = SetHandleInformation((HANDLE)socket, HANDLE_FLAG_INHERIT, 0); + BOOL handleFlags = SetHandleInformation(reinterpret_cast(socket), HANDLE_FLAG_INHERIT, 0); #ifdef QNATIVESOCKETENGINE_DEBUG qDebug() << "QNativeSocketEnginePrivate::createNewSocket - set inheritable" << handleFlags; #else @@ -1443,7 +1444,7 @@ qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) for (;;) { WSABUF buf; - buf.buf = (char*)data + ret; + buf.buf = const_cast(data) + ret; buf.len = bytesToSend; DWORD flags = 0; DWORD bytesWritten = 0; diff --git a/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp b/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp index 890792fa2f..6b86f01616 100644 --- a/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp +++ b/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp @@ -1608,7 +1608,8 @@ void QWindowsFontDatabase::removeApplicationFonts() if (font.handle) { RemoveFontMemResourceEx(font.handle); } else { - RemoveFontResourceExW((LPCWSTR)font.fileName.utf16(), FR_PRIVATE, 0); + RemoveFontResourceExW(reinterpret_cast(font.fileName.utf16()), + FR_PRIVATE, nullptr); } } m_applicationFonts.clear(); @@ -1652,7 +1653,8 @@ void QWindowsFontDatabase::refUniqueFont(const QString &uniqueFont) // ### fixme Qt 6 (QTBUG-58610): See comment at QWindowsFontDatabase::systemDefaultFont() HFONT QWindowsFontDatabase::systemFont() { - static const HFONT stock_sysfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + static const auto stock_sysfont = + reinterpret_cast(GetStockObject(DEFAULT_GUI_FONT)); return stock_sysfont; } diff --git a/src/platformsupport/fontdatabases/windows/qwindowsfontengine.cpp b/src/platformsupport/fontdatabases/windows/qwindowsfontengine.cpp index 584e4db05d..2a41209225 100644 --- a/src/platformsupport/fontdatabases/windows/qwindowsfontengine.cpp +++ b/src/platformsupport/fontdatabases/windows/qwindowsfontengine.cpp @@ -111,9 +111,8 @@ QFixed QWindowsFontEngine::lineThickness() const static OUTLINETEXTMETRIC *getOutlineTextMetric(HDC hdc) { - int size; - size = GetOutlineTextMetrics(hdc, 0, 0); - OUTLINETEXTMETRIC *otm = (OUTLINETEXTMETRIC *)malloc(size); + const auto size = GetOutlineTextMetrics(hdc, 0, nullptr); + auto otm = reinterpret_cast(malloc(size)); GetOutlineTextMetrics(hdc, size, otm); return otm; } @@ -1140,7 +1139,7 @@ QImage QWindowsFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed, const QTra QImage rgbMask(mask->width(), mask->height(), QImage::Format_RGB32); for (int y=0; yheight(); ++y) { - uint *dest = (uint *) rgbMask.scanLine(y); + auto dest = reinterpret_cast(rgbMask.scanLine(y)); const uint *src = reinterpret_cast(source.constScanLine(y)); for (int x=0; xwidth(); ++x) { dest[x] = 0xffffffff - (0x00ffffff & src[x]); diff --git a/src/plugins/printsupport/windows/qwindowsprintdevice.cpp b/src/plugins/printsupport/windows/qwindowsprintdevice.cpp index 300d553628..071bc01192 100644 --- a/src/plugins/printsupport/windows/qwindowsprintdevice.cpp +++ b/src/plugins/printsupport/windows/qwindowsprintdevice.cpp @@ -112,7 +112,7 @@ QWindowsPrintDevice::QWindowsPrintDevice(const QString &id) { // First do a fast lookup to see if printer exists, if it does then open it if (!id.isEmpty() && QWindowsPrintDevice::availablePrintDeviceIds().contains(id)) { - if (OpenPrinter((LPWSTR)m_id.utf16(), &m_hPrinter, NULL)) { + if (OpenPrinter(const_cast(wcharId()), &m_hPrinter, nullptr)) { DWORD needed = 0; GetPrinter(m_hPrinter, 2, 0, 0, &needed); QScopedArrayPointer buffer(new BYTE[needed]); @@ -351,16 +351,19 @@ int QWindowsPrintDevice::defaultResolution() const void QWindowsPrintDevice::loadInputSlots() const { - DWORD binCount = DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_BINS, NULL, NULL); + const auto printerId = wcharId(); + DWORD binCount = DeviceCapabilities(printerId, nullptr, DC_BINS, nullptr, nullptr); if (int(binCount) > 0 - && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_BINNAMES, NULL, NULL) == binCount) { + && DeviceCapabilities(printerId, nullptr, DC_BINNAMES, nullptr, nullptr) == binCount) { QScopedArrayPointer bins(new WORD[binCount*sizeof(WORD)]); QScopedArrayPointer binNames(new wchar_t[binCount*24]); // Get the details and match the default paper size - if (DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_BINS, (LPWSTR)bins.data(), NULL) == binCount - && DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_BINNAMES, binNames.data(), NULL) == binCount) { + if (DeviceCapabilities(printerId, nullptr, DC_BINS, + reinterpret_cast(bins.data()), nullptr) == binCount + && DeviceCapabilities(printerId, nullptr, DC_BINNAMES, binNames.data(), + nullptr) == binCount) { for (int i = 0; i < int(binCount); ++i) { wchar_t *binName = binNames.data() + (i * 24); @@ -410,7 +413,7 @@ void QWindowsPrintDevice::loadOutputBins() const void QWindowsPrintDevice::loadDuplexModes() const { m_duplexModes.append(QPrint::DuplexNone); - DWORD duplex = DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_DUPLEX, NULL, NULL); + DWORD duplex = DeviceCapabilities(wcharId(), nullptr, DC_DUPLEX, nullptr, nullptr); if (int(duplex) == 1) { // TODO Assume if duplex flag supports both modes m_duplexModes.append(QPrint::DuplexAuto); @@ -444,7 +447,7 @@ QPrint::DuplexMode QWindowsPrintDevice::defaultDuplexMode() const void QWindowsPrintDevice::loadColorModes() const { m_colorModes.append(QPrint::GrayScale); - DWORD color = DeviceCapabilities((LPWSTR)m_id.utf16(), NULL, DC_COLORDEVICE, NULL, NULL); + DWORD color = DeviceCapabilities(wcharId(), nullptr, DC_COLORDEVICE, nullptr, nullptr); if (int(color) == 1) m_colorModes.append(QPrint::Color); m_haveColorModes = true; @@ -503,7 +506,7 @@ QString QWindowsPrintDevice::defaultPrintDeviceId() void QWindowsPrintDevice::loadCopiesSupport() const { - LPWSTR printerId = const_cast(reinterpret_cast(m_id.utf16())); + auto printerId = wcharId(); m_supportsMultipleCopies = (DeviceCapabilities(printerId, NULL, DC_COPIES, NULL, NULL) > 1); m_supportsCollateCopies = DeviceCapabilities(printerId, NULL, DC_COLLATE, NULL, NULL); m_haveCopies = true; @@ -552,7 +555,7 @@ void QWindowsPrintDevice::loadMinMaxPageSizes() const { // Min/Max custom size is in tenths of a millimeter const qreal multiplier = qt_pointMultiplier(QPageLayout::Millimeter); - LPWSTR printerId = const_cast(reinterpret_cast(m_id.utf16())); + auto printerId = wcharId(); DWORD min = DeviceCapabilities(printerId, NULL, DC_MINEXTENT, NULL, NULL); m_minimumPhysicalPageSize = QSize((LOWORD(min) / 10.0) * multiplier, (HIWORD(min) / 10.0) * multiplier); DWORD max = DeviceCapabilities(printerId, NULL, DC_MAXEXTENT, NULL, NULL); diff --git a/src/plugins/printsupport/windows/qwindowsprintdevice.h b/src/plugins/printsupport/windows/qwindowsprintdevice.h index 9f6d31da5f..166f0f65b2 100644 --- a/src/plugins/printsupport/windows/qwindowsprintdevice.h +++ b/src/plugins/printsupport/windows/qwindowsprintdevice.h @@ -140,6 +140,8 @@ protected: void loadMinMaxPageSizes() const; private: + LPCWSTR wcharId() const { return reinterpret_cast(m_id.utf16()); } + HANDLE m_hPrinter; mutable bool m_haveCopies; mutable bool m_haveMinMaxPageSizes; diff --git a/src/printsupport/kernel/qprintengine_win.cpp b/src/printsupport/kernel/qprintengine_win.cpp index 5ee93a46b3..e3a5c3d2e8 100644 --- a/src/printsupport/kernel/qprintengine_win.cpp +++ b/src/printsupport/kernel/qprintengine_win.cpp @@ -1556,14 +1556,15 @@ HGLOBAL *QWin32PrintEngine::createGlobalDevNames() Q_D(QWin32PrintEngine); int size = sizeof(DEVNAMES) + d->m_printDevice.id().length() * 2 + 2; - HGLOBAL *hGlobal = (HGLOBAL *) GlobalAlloc(GMEM_MOVEABLE, size); - DEVNAMES *dn = (DEVNAMES*) GlobalLock(hGlobal); + auto hGlobal = reinterpret_cast(GlobalAlloc(GMEM_MOVEABLE, size)); + auto dn = reinterpret_cast(GlobalLock(hGlobal)); dn->wDriverOffset = 0; dn->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t); dn->wOutputOffset = 0; - memcpy((ushort*)dn + dn->wDeviceOffset, d->m_printDevice.id().utf16(), d->m_printDevice.id().length() * 2 + 2); + memcpy(reinterpret_cast(dn) + dn->wDeviceOffset, + d->m_printDevice.id().utf16(), d->m_printDevice.id().length() * 2 + 2); dn->wDefault = 0; GlobalUnlock(hGlobal); @@ -1574,8 +1575,9 @@ void QWin32PrintEngine::setGlobalDevMode(HGLOBAL globalDevNames, HGLOBAL globalD { Q_D(QWin32PrintEngine); if (globalDevNames) { - DEVNAMES *dn = (DEVNAMES*) GlobalLock(globalDevNames); - QString id = QString::fromWCharArray((wchar_t*)(dn) + dn->wDeviceOffset); + auto dn = reinterpret_cast(GlobalLock(globalDevNames)); + const QString id = + QString::fromWCharArray(reinterpret_cast(dn) + dn->wDeviceOffset); QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get(); if (ps) d->m_printDevice = ps->createPrintDevice(id.isEmpty() ? ps->defaultPrintDeviceId() : id); @@ -1583,7 +1585,7 @@ void QWin32PrintEngine::setGlobalDevMode(HGLOBAL globalDevNames, HGLOBAL globalD } if (globalDevMode) { - DEVMODE *dm = (DEVMODE*) GlobalLock(globalDevMode); + auto dm = reinterpret_cast(GlobalLock(globalDevMode)); d->release(); d->globalDevMode = globalDevMode; if (d->ownsDevMode) { diff --git a/src/widgets/dialogs/qwizard_win.cpp b/src/widgets/dialogs/qwizard_win.cpp index 5ef304c9f1..aa9ad7f290 100644 --- a/src/widgets/dialogs/qwizard_win.cpp +++ b/src/widgets/dialogs/qwizard_win.cpp @@ -606,8 +606,8 @@ bool QVistaHelper::drawTitleText(QPainter *painter, const QString &text, const Q // Set up the DC const LOGFONT captionLogFont = getCaptionLogFont(hTheme); const HFONT hCaptionFont = CreateFontIndirect(&captionLogFont); - HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); - HFONT hOldFont = (HFONT)SelectObject(dcMem, (HGDIOBJ) hCaptionFont); + auto hOldBmp = reinterpret_cast(SelectObject(dcMem, (HGDIOBJ) bmp)); + auto hOldFont = reinterpret_cast(SelectObject(dcMem, (HGDIOBJ) hCaptionFont)); // Draw the text! DTTOPTS dto; @@ -654,7 +654,7 @@ bool QVistaHelper::drawBlackRect(const QRect &rect, HDC hdc) dib.bmiHeader.biCompression = BI_RGB; bmp = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); - HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); + auto hOldBmp = reinterpret_cast(SelectObject(dcMem, (HGDIOBJ) bmp)); BitBlt(hdc, rectDp.left(), rectDp.top(), rectDp.width(), rectDp.height(), dcMem, 0, 0, SRCCOPY); SelectObject(dcMem, (HGDIOBJ) hOldBmp); From 707ae5b66774a2ed4713da3b8fa69c1d002c3beb Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Tue, 28 Aug 2018 09:05:53 +0200 Subject: [PATCH 16/26] Silence warning in gcc 4.9 for Android When building with warnings are errors for Android with gcc 4.9, it wrongfully fails on use of uninitialized variable. Change-Id: I39c5e8413c30567fba60cbba6a8b26d537c5836e Reviewed-by: Konstantin Ritt --- src/gui/text/qfontengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 3b64ee0136..64623c86df 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -1327,7 +1327,7 @@ const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSy resolveTable: *isSymbolFont = (symbolTable > -1); - quint32 unicode_table; + quint32 unicode_table = 0; if (!qSafeFromBigEndian(maps + 8 * tableToUse + 4, endPtr, &unicode_table)) return 0; From 29c0377f07f4942f9957ea87d59c252148dc9e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Fri, 1 Jun 2018 15:13:30 +0200 Subject: [PATCH 17/26] WebAssembly for QtBase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the squashed diff from wip/webassembly to dev. Done-with: Peng Wu Done-with: Sami Enne Done-with: Morten Johan Sørvig Started-by: Andrew Knight Change-Id: I6562433c0a38d6ec49ab675e0f104f2665f3392d Reviewed-by: Lorn Potter Reviewed-by: Eskil Abrahamsen Blomfeldt --- configure.json | 5 +- configure.pri | 11 +- mkspecs/features/toolchain.prf | 4 +- mkspecs/features/wasm/qt.prf | 12 + mkspecs/features/wasm/wasm.prf | 81 ++ mkspecs/wasm-emscripten/qmake.conf | 90 +++ mkspecs/wasm-emscripten/qplatformdefs.h | 181 +++++ .../include/double-conversion/utils.h | 2 +- src/3rdparty/wasm/DejaVuSans.ttf | Bin 0 -> 493564 bytes src/3rdparty/wasm/LICENSE | 15 + src/3rdparty/wasm/Vera.ttf | Bin 0 -> 65932 bytes src/3rdparty/wasm/qt_attribution.json | 21 + src/corelib/codecs/qtextcodec_p.h | 2 +- src/corelib/configure.json | 4 +- src/corelib/global/archdetect.cpp | 2 + src/corelib/global/qcompilerdetection.h | 3 + src/corelib/global/qlogging.cpp | 37 + src/corelib/global/qprocessordetection.h | 6 + src/corelib/global/qsystemdetection.h | 2 + src/corelib/io/qfilesystemengine_unix.cpp | 4 +- src/corelib/io/qsettings.cpp | 11 + src/corelib/kernel/qcoreapplication.cpp | 22 + src/corelib/kernel/qeventdispatcher_unix.cpp | 2 +- src/corelib/kernel/qeventloop.cpp | 24 + src/corelib/mimetypes/qmimedatabase.cpp | 2 + src/corelib/tools/qsimd.cpp | 17 +- src/corelib/tools/qunicodetables_p.h | 6 + src/corelib/tools/tools.pri | 2 +- src/gui/configure.json | 10 +- src/gui/configure.pri | 1 + src/gui/kernel/qguiapplication.cpp | 12 +- src/gui/kernel/qopenglcontext.cpp | 3 + src/gui/opengl/qopenglframebufferobject.cpp | 45 ++ src/network/access/access.pri | 7 + src/network/access/qnetworkaccessmanager.cpp | 13 + src/network/access/qnetworkaccessmanager.h | 3 + src/network/access/qnetworkreplywasmimpl.cpp | 640 ++++++++++++++++ src/network/access/qnetworkreplywasmimpl_p.h | 153 ++++ src/network/configure.json | 3 +- src/opengl/qglframebufferobject.cpp | 22 + src/plugins/platforms/platforms.pro | 2 + src/plugins/platforms/wasm/main.cpp | 54 ++ src/plugins/platforms/wasm/qtloader.js | 516 +++++++++++++ src/plugins/platforms/wasm/qtlogo.svg | 67 ++ .../platforms/wasm/qwasmbackingstore.cpp | 165 ++++ .../platforms/wasm/qwasmbackingstore.h | 70 ++ .../platforms/wasm/qwasmcompositor.cpp | 721 ++++++++++++++++++ src/plugins/platforms/wasm/qwasmcompositor.h | 169 ++++ src/plugins/platforms/wasm/qwasmcursor.cpp | 131 ++++ src/plugins/platforms/wasm/qwasmcursor.h | 43 ++ .../platforms/wasm/qwasmeventdispatcher.cpp | 181 +++++ .../platforms/wasm/qwasmeventdispatcher.h | 64 ++ .../platforms/wasm/qwasmeventtranslator.cpp | 522 +++++++++++++ .../platforms/wasm/qwasmeventtranslator.h | 210 +++++ .../platforms/wasm/qwasmfontdatabase.cpp | 86 +++ .../platforms/wasm/qwasmfontdatabase.h | 49 ++ .../platforms/wasm/qwasmintegration.cpp | 219 ++++++ src/plugins/platforms/wasm/qwasmintegration.h | 92 +++ .../platforms/wasm/qwasmopenglcontext.cpp | 147 ++++ .../platforms/wasm/qwasmopenglcontext.h | 63 ++ src/plugins/platforms/wasm/qwasmscreen.cpp | 118 +++ src/plugins/platforms/wasm/qwasmscreen.h | 82 ++ .../platforms/wasm/qwasmstylepixmaps_p.h | 183 +++++ src/plugins/platforms/wasm/qwasmtheme.cpp | 50 ++ src/plugins/platforms/wasm/qwasmtheme.h | 56 ++ src/plugins/platforms/wasm/qwasmwindow.cpp | 398 ++++++++++ src/plugins/platforms/wasm/qwasmwindow.h | 124 +++ src/plugins/platforms/wasm/wasm.json | 3 + src/plugins/platforms/wasm/wasm.pro | 65 ++ src/plugins/platforms/wasm/wasm_shell.html | 61 ++ src/widgets/dialogs/qfiledialog.cpp | 2 +- src/widgets/graphicsview/qgraphicsitem_p.h | 4 +- src/widgets/kernel/qopenglwidget.cpp | 10 + util/unicode/main.cpp | 8 + 74 files changed, 6191 insertions(+), 24 deletions(-) create mode 100644 mkspecs/features/wasm/qt.prf create mode 100644 mkspecs/features/wasm/wasm.prf create mode 100644 mkspecs/wasm-emscripten/qmake.conf create mode 100644 mkspecs/wasm-emscripten/qplatformdefs.h create mode 100644 src/3rdparty/wasm/DejaVuSans.ttf create mode 100644 src/3rdparty/wasm/LICENSE create mode 100644 src/3rdparty/wasm/Vera.ttf create mode 100644 src/3rdparty/wasm/qt_attribution.json create mode 100644 src/network/access/qnetworkreplywasmimpl.cpp create mode 100644 src/network/access/qnetworkreplywasmimpl_p.h create mode 100644 src/plugins/platforms/wasm/main.cpp create mode 100644 src/plugins/platforms/wasm/qtloader.js create mode 100644 src/plugins/platforms/wasm/qtlogo.svg create mode 100644 src/plugins/platforms/wasm/qwasmbackingstore.cpp create mode 100644 src/plugins/platforms/wasm/qwasmbackingstore.h create mode 100644 src/plugins/platforms/wasm/qwasmcompositor.cpp create mode 100644 src/plugins/platforms/wasm/qwasmcompositor.h create mode 100644 src/plugins/platforms/wasm/qwasmcursor.cpp create mode 100644 src/plugins/platforms/wasm/qwasmcursor.h create mode 100644 src/plugins/platforms/wasm/qwasmeventdispatcher.cpp create mode 100644 src/plugins/platforms/wasm/qwasmeventdispatcher.h create mode 100644 src/plugins/platforms/wasm/qwasmeventtranslator.cpp create mode 100644 src/plugins/platforms/wasm/qwasmeventtranslator.h create mode 100644 src/plugins/platforms/wasm/qwasmfontdatabase.cpp create mode 100644 src/plugins/platforms/wasm/qwasmfontdatabase.h create mode 100644 src/plugins/platforms/wasm/qwasmintegration.cpp create mode 100644 src/plugins/platforms/wasm/qwasmintegration.h create mode 100644 src/plugins/platforms/wasm/qwasmopenglcontext.cpp create mode 100644 src/plugins/platforms/wasm/qwasmopenglcontext.h create mode 100644 src/plugins/platforms/wasm/qwasmscreen.cpp create mode 100644 src/plugins/platforms/wasm/qwasmscreen.h create mode 100644 src/plugins/platforms/wasm/qwasmstylepixmaps_p.h create mode 100644 src/plugins/platforms/wasm/qwasmtheme.cpp create mode 100644 src/plugins/platforms/wasm/qwasmtheme.h create mode 100644 src/plugins/platforms/wasm/qwasmwindow.cpp create mode 100644 src/plugins/platforms/wasm/qwasmwindow.h create mode 100644 src/plugins/platforms/wasm/wasm.json create mode 100644 src/plugins/platforms/wasm/wasm.pro create mode 100644 src/plugins/platforms/wasm/wasm_shell.html diff --git a/configure.json b/configure.json index 1f45f2ccb2..8c8118f39a 100644 --- a/configure.json +++ b/configure.json @@ -162,7 +162,8 @@ "sources": [ { "libs": "-lzdll", "condition": "config.msvc" }, { "libs": "-lzlib", "condition": "config.msvc" }, - { "libs": "-lz", "condition": "!config.msvc" } + { "libs": "-lz", "condition": "!config.msvc" }, + { "libs": "-s USE_ZLIB=1", "condition": "config.wasm" } ] }, "dbus": { @@ -613,7 +614,7 @@ }, "use_gold_linker": { "label": "Using gold linker", - "condition": "!config.win32 && !config.integrity && tests.use_gold_linker", + "condition": "!config.win32 && !config.integrity && !config.wasm && tests.use_gold_linker", "output": [ "privateConfig", "useGoldLinker" ] }, "optimize_debug": { diff --git a/configure.pri b/configure.pri index 03eacd3c07..805b985fa6 100644 --- a/configure.pri +++ b/configure.pri @@ -68,7 +68,7 @@ defineReplace(qtConfFunc_crossCompile) { } defineReplace(qtConfFunc_licenseCheck) { - exists($$QT_SOURCE_TREE/LICENSE.LGPL3)|exists($$QT_SOURCE_TREE/LICENSE.GPL2): \ + exists($$QT_SOURCE_TREE/LICENSE.LGPL3)|exists($$QT_SOURCE_TREE/LICENSE.GPL2)|exists($$QT_SOURCE_TREE/LICENSE.GPL3): \ hasOpenSource = true else: \ hasOpenSource = false @@ -187,8 +187,13 @@ defineReplace(qtConfFunc_licenseCheck) { theLicense = "GNU Lesser General Public License (LGPL) version 3" showWhat = "Type 'L' to view the GNU Lesser General Public License version 3 (LGPLv3)." gpl2Ok = false + gpl3Ok = false winrt { notTheLicense = "Note: GPL version 2 is not available on WinRT." + } else: wasm { + gpl3Ok = true + theLicense = "GNU General Public License (GPL) version 3" + showWhat = "Type 'G' to view the GNU General Public License version 3 (GPLv3)." } else: $$qtConfEvaluate("features.android-style-assets") { notTheLicense = "Note: GPL version 2 is not available due to using Android style assets." } else { @@ -230,6 +235,8 @@ defineReplace(qtConfFunc_licenseCheck) { licenseFile = $$QT_SOURCE_TREE/LICENSE.LGPL3 } else: equals(commercial, no):equals(val, g):$$gpl2Ok { licenseFile = $$QT_SOURCE_TREE/LICENSE.GPL2 + } else: equals(commercial, no):equals(val, g):$$gpl3Ok { + licenseFile = $$QT_SOURCE_TREE/LICENSE.GPL3 } else { next() } @@ -268,6 +275,8 @@ defineTest(qtConfTest_architecture) { content = $$cat($$test_out_dir/arch.exe, blob) else: android:exists($$test_out_dir/libarch.so): \ content = $$cat($$test_out_dir/libarch.so, blob) + else: wasm:exists($$test_out_dir/arch.wasm): \ + content = $$cat($$test_out_dir/arch.wasm, blob) else: \ error("$$eval($${1}.label) detection binary not found.") diff --git a/mkspecs/features/toolchain.prf b/mkspecs/features/toolchain.prf index bce3ef3954..4ecfb8d889 100644 --- a/mkspecs/features/toolchain.prf +++ b/mkspecs/features/toolchain.prf @@ -34,7 +34,9 @@ isEmpty($${target_prefix}.INCDIRS) { # # Get default include and library paths from compiler # - gcc { + wasm { + # wasm compiler does not work here, just use defaults + } else: gcc { cmd_suffix = "<$$QMAKE_SYSTEM_NULL_DEVICE >$$QMAKE_SYSTEM_NULL_DEVICE" equals(QMAKE_HOST.os, Windows): \ cmd_prefix = "set LC_ALL=C&" diff --git a/mkspecs/features/wasm/qt.prf b/mkspecs/features/wasm/qt.prf new file mode 100644 index 0000000000..9b9b58d3de --- /dev/null +++ b/mkspecs/features/wasm/qt.prf @@ -0,0 +1,12 @@ + +qt_depends = $$resolve_depends(QT, "QT.") +equals(TEMPLATE, app):contains(qt_depends, gui(-private)?) { + LIBS *= -L$$[QT_INSTALL_PLUGINS/get]/platforms + + lib_name = wasm + lib_path_and_base = $$[QT_INSTALL_PLUGINS/get]/platforms/lib$${lib_name}$$qtPlatformTargetSuffix() + LIBS += -l$${lib_name}$$qtPlatformTargetSuffix() $$fromfile($${lib_path_and_base}.prl, QMAKE_PRL_LIBS) +} + +load(qt) + diff --git a/mkspecs/features/wasm/wasm.prf b/mkspecs/features/wasm/wasm.prf new file mode 100644 index 0000000000..278a6719c7 --- /dev/null +++ b/mkspecs/features/wasm/wasm.prf @@ -0,0 +1,81 @@ + +# DESTDIR will be empty if not set in the app .pro file; make sure it has a value +isEmpty(DESTDIR): DESTDIR = $$OUT_PWD + +# Create js and wasm files for applications +contains(TEMPLATE, .*app) { + TARGET_BASE = $${TARGET} + TARGET_HTML = $${TARGET}.html + TARGET_JS = $${TARGET}.js + + # Make the emscripten compiler generate a js file + TARGET = $$TARGET_JS + + QMAKE_INCDIR += $$(HOME)/.emscripten_ports/openssl/include + + CONFIG += static + js_file.files = $$TARGET_JS + js_file.path = $$target.path + isEmpty(js_file.path): \ + js_file.path += ./ + INSTALLS += js_file + + # Copy hosting html and javascript to the application build directory. + exists($$[QT_INSTALL_PLUGINS]/platforms/wasm_shell.html) { + # don't pass this until it's installed somewhere + # otherwise makespec test fails during qt configure + WASM_PLUGIN_PATH = $$[QT_INSTALL_PLUGINS]/platforms + } else { + ## internal build. not installed + WASM_PLUGIN_PATH = $$PWD/../../../src/plugins/platforms/wasm + } + + # Copy/Generate main .html file (e.g. myapp.html) from the webassembly_shell.html by + # replacing the app name placeholder with the actual app name. + apphtml.name = application main html file + apphtml.output = $$DESTDIR/$$TARGET_HTML + apphtml.commands = sed -e s/APPNAME/$$TARGET_BASE/g $$WASM_PLUGIN_PATH/wasm_shell.html > $$DESTDIR/$$TARGET_HTML + apphtml.input = $$WASM_PLUGIN_PATH/wasm_shell.html + apphtml.depends = $$apphtml.input + QMAKE_EXTRA_COMPILERS += apphtml + + appjs.name = application qtloader.js + appjs.output = $$DESTDIR/qtloader.js + appjs.commands = $$QMAKE_COPY $$WASM_PLUGIN_PATH/qtloader.js $$DESTDIR + appjs.input = $$WASM_PLUGIN_PATH/qtloader.js + appjs.depends = $$appjs.input + QMAKE_EXTRA_COMPILERS += appjs + + appsvg.name = application qtlogo.svg + appsvg.output = $$DESTDIR/qtlogo.svg + appsvg.commands = $$QMAKE_COPY $$WASM_PLUGIN_PATH/qtlogo.svg $$DESTDIR + appsvg.input = $$WASM_PLUGIN_PATH/qtlogo.svg + appsvg.depends = $$appsvg.input + QMAKE_EXTRA_COMPILERS += appsvg + + QMAKE_EXTRA_TARGETS += apphtml appjs appsvg + POST_TARGETDEPS += apphtml appjs appsvg + + # Add manual target to make "make -B shellfiles" work. + shellfiles.target = shellfiles + shellfiles.depends = apphtml appjs appsvg + QMAKE_EXTRA_TARGETS += shellfiles + + # emscripten ports are linked into the main module (this app), not the Qt + # libs which reference them + qt { + qt_depends = $$resolve_depends(QT, "QT.") + contains(qt_depends, core(-private)?): QMAKE_LFLAGS += \ + $$QMAKE_LIBS_THREAD $$QMAKE_LIBS_ZLIB + contains(qt_depends, gui(-private)?): QMAKE_LFLAGS += \ + $$QMAKE_LIBS_FREETYPE $$QMAKE_LIBS_LIBPNG + } +} + +# Creates the stand-alone version of the library from bitcode +!static:contains(TEMPLATE, .*lib): { + load(resolve_target) + QMAKE_POST_LINK += $$QMAKE_LINK_SHLIB $$QMAKE_RESOLVED_TARGET -o $${QMAKE_RESOLVED_TARGET}.js + + QMAKE_INCDIR += $$(HOME)/.emscripten_ports/openssl/include +} diff --git a/mkspecs/wasm-emscripten/qmake.conf b/mkspecs/wasm-emscripten/qmake.conf new file mode 100644 index 0000000000..c3b67310c8 --- /dev/null +++ b/mkspecs/wasm-emscripten/qmake.conf @@ -0,0 +1,90 @@ +# qmake configuration for building with emscripten +MAKEFILE_GENERATOR = UNIX +QMAKE_PLATFORM = wasm unix + +include(../common/gcc-base.conf) +include(../common/clang.conf) + +EMTERP_FLAGS = \ + -s EMTERPRETIFY=1 \ + -s EMTERPRETIFY_ASYNC=1 \ + -s \"EMTERPRETIFY_FILE=\'data.binary\'\" \ + -s ASSERTIONS=1 \ + --profiling-funcs + +EMCC_COMMON_CFLAGS = \ + -s USE_LIBPNG=1 \ + -s USE_FREETYPE=1 \ + -s USE_ZLIB=1 + +EMCC_COMMON_LFLAGS = \ + -s WASM=1 \ + -s FULL_ES2=1 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s USE_WEBGL2=1 \ + -s NO_EXIT_RUNTIME=0 \ + -s ERROR_ON_UNDEFINED_SYMBOLS=1 \ + --bind \ + -s \"BINARYEN_METHOD=\'native-wasm\'\" \ + -s \"BINARYEN_TRAP_MODE=\'clamp\'\" + +EMCC_COMMON_LFLAGS_DEBUG = \ + $$EMCC_COMMON_LFLAGS \ + -s ASSERTIONS=2 \ + -s DEMANGLE_SUPPORT=1 \ + # -s LIBRARY_DEBUG=1 \ #print out library calls, verbose + # -s SYSCALL_DEBUG=1 \ #print out sys calls, verbose + # -s FS_LOG=1 \ #print out filesystem ops, verbose + # -s SOCKET_DEBUG \ #print out socket,network data transfer + -s GL_DEBUG=1 + +# the -s arguments can also be used with release builds +# but here in debug for clarity + +QMAKE_COMPILER += emscripten + +QMAKE_CC = emcc +QMAKE_CXX = em++ + +QMAKE_CFLAGS += $$EMCC_COMMON_CFLAGS +QMAKE_CXXFLAGS += $$EMCC_COMMON_CFLAGS + +# Practical debugging setup: +# "-g4" preserves function names for stack traces +# "-Os" produces reasonably sized binaries +QMAKE_CFLAGS_DEBUG -= -g +QMAKE_CXXFLAGS_DEBUG -= -g +QMAKE_CFLAGS_DEBUG += -Os -g4 +QMAKE_CXXFLAGS_DEBUG += -Os -g4 +QMAKE_LFLAGS_DEBUG += -Os -g4 + +QMAKE_CXXFLAGS_RELEASE -= -O2 +QMAKE_CXXFLAGS_RELEASE += -O3 +QMAKE_CFLAGS_RELEASE -= -O2 +QMAKE_CFLAGS_RELEASE += -O3 +QMAKE_LFLAGS_RELEASE += -O3 +MAKE_CFLAGS_OPTIMIZE += -O3 +MAKE_CFLAGS_OPTIMIZE_FULL += -Oz + +QMAKE_LINK = $$QMAKE_CXX +QMAKE_LINK_SHLIB = $$QMAKE_CXX +QMAKE_LINK_C = $$QMAKE_CC +QMAKE_LINK_C_SHLIB = $$QMAKE_CC + +QMAKE_LIBS_THREAD = $$QMAKE_CFLAGS_THREAD + +QMAKE_LFLAGS += $$EMCC_COMMON_LFLAGS +QMAKE_LFLAGS_DEBUG += $$EMCC_COMMON_LFLAGS_DEBUG + +QMAKE_PREFIX_SHLIB = lib +QMAKE_EXTENSION_SHLIB = so # llvm bitcode, linked to js in post_link +QMAKE_PREFIX_STATICLIB = lib +QMAKE_EXTENSION_STATICLIB = a # llvm bitcode + +QMAKE_AR = emar cqs +QMAKE_DISTCLEAN += *.html *.js *.wasm + +QT_QPA_DEFAULT_PLATFORM = wasm + +QTPLUGIN.platforms = wasm +load(qt_config) diff --git a/mkspecs/wasm-emscripten/qplatformdefs.h b/mkspecs/wasm-emscripten/qplatformdefs.h new file mode 100644 index 0000000000..c1a0d7b1a8 --- /dev/null +++ b/mkspecs/wasm-emscripten/qplatformdefs.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the qmake spec of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMDEFS_H +#define QPLATFORMDEFS_H + +// Get Qt defines/settings + +#include "qglobal.h" + +// Set any POSIX/XOPEN defines at the top of this file to turn on specific APIs + +// 1) need to reset default environment if _BSD_SOURCE is defined +// 2) need to specify POSIX thread interfaces explicitly in glibc 2.0 +// 3) it seems older glibc need this to include the X/Open stuff + +#include + +// We are hot - unistd.h should have turned on the specific APIs we requested + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_IPV6IFNAME +#include +#endif + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#ifdef QT_LARGEFILE_SUPPORT +#define QT_STATBUF struct stat64 +#define QT_STATBUF4TSTAT struct stat64 +#define QT_STAT ::stat64 +#define QT_FSTAT ::fstat64 +#define QT_LSTAT ::lstat64 +#define QT_OPEN ::open64 +#define QT_TRUNCATE ::truncate64 +#define QT_FTRUNCATE ::ftruncate64 +#define QT_LSEEK ::lseek64 +#else +#define QT_STATBUF struct stat +#define QT_STATBUF4TSTAT struct stat +#define QT_STAT ::stat +#define QT_FSTAT ::fstat +#define QT_LSTAT ::lstat +#define QT_OPEN ::open +#define QT_TRUNCATE ::truncate +#define QT_FTRUNCATE ::ftruncate +#define QT_LSEEK ::lseek +#endif + +#ifdef QT_LARGEFILE_SUPPORT +#define QT_FOPEN ::fopen64 +#define QT_FSEEK ::fseeko64 +#define QT_FTELL ::ftello64 +#define QT_FGETPOS ::fgetpos64 +#define QT_FSETPOS ::fsetpos64 +#define QT_MMAP ::mmap64 +#define QT_FPOS_T fpos64_t +#define QT_OFF_T off64_t +#else +#define QT_FOPEN ::fopen +#define QT_FSEEK ::fseek +#define QT_FTELL ::ftell +#define QT_FGETPOS ::fgetpos +#define QT_FSETPOS ::fsetpos +#define QT_MMAP ::mmap +#define QT_FPOS_T fpos_t +#define QT_OFF_T long +#endif + +#define QT_STAT_REG S_IFREG +#define QT_STAT_DIR S_IFDIR +#define QT_STAT_MASK S_IFMT +#define QT_STAT_LNK S_IFLNK +#define QT_SOCKET_CONNECT ::connect +#define QT_SOCKET_BIND ::bind +#define QT_FILENO fileno +#define QT_CLOSE ::close +#define QT_READ ::read +#define QT_WRITE ::write +#define QT_ACCESS ::access +#define QT_GETCWD ::getcwd +#define QT_CHDIR ::chdir +#define QT_MKDIR ::mkdir +#define QT_RMDIR ::rmdir +#define QT_OPEN_LARGEFILE O_LARGEFILE +#define QT_OPEN_RDONLY O_RDONLY +#define QT_OPEN_WRONLY O_WRONLY +#define QT_OPEN_RDWR O_RDWR +#define QT_OPEN_CREAT O_CREAT +#define QT_OPEN_TRUNC O_TRUNC +#define QT_OPEN_APPEND O_APPEND +#define QT_OPEN_EXCL O_EXCL + +// Directory iteration +#define QT_DIR DIR + +#define QT_OPENDIR ::opendir +#define QT_CLOSEDIR ::closedir + +#if defined(QT_LARGEFILE_SUPPORT) \ + && defined(QT_USE_XOPEN_LFS_EXTENSIONS) \ + && !defined(QT_NO_READDIR64) +#define QT_DIRENT struct dirent64 +#define QT_READDIR ::readdir64 +#define QT_READDIR_R ::readdir64_r +#else +#define QT_DIRENT struct dirent +#define QT_READDIR ::readdir +#define QT_READDIR_R ::readdir_r +#endif + +#define QT_SOCKET_CONNECT ::connect +#define QT_SOCKET_BIND ::bind + + +#define QT_SIGNAL_RETTYPE void +#define QT_SIGNAL_ARGS int +#define QT_SIGNAL_IGNORE SIG_IGN + +#define QT_SOCKLEN_T socklen_t + +#if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500) +#define QT_SNPRINTF ::snprintf +#define QT_VSNPRINTF ::vsnprintf +#endif + +#endif // QPLATFORMDEFS_H diff --git a/src/3rdparty/double-conversion/include/double-conversion/utils.h b/src/3rdparty/double-conversion/include/double-conversion/utils.h index ca7646d817..ff00855bea 100644 --- a/src/3rdparty/double-conversion/include/double-conversion/utils.h +++ b/src/3rdparty/double-conversion/include/double-conversion/utils.h @@ -77,7 +77,7 @@ inline void abort_noreturn() { abort(); } defined(__SH4__) || defined(__alpha__) || \ defined(_MIPS_ARCH_MIPS32R2) || \ defined(__AARCH64EL__) || defined(__aarch64__) || \ - defined(__riscv) + defined(__riscv) || defined(__EMSCRIPTEN__) #define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1 #elif defined(__mc68000__) #undef DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS diff --git a/src/3rdparty/wasm/DejaVuSans.ttf b/src/3rdparty/wasm/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7e411a71beeb3087ff9a23027dd5aeb415766942 GIT binary patch literal 493564 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 z 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 literal 0 HcmV?d00001 diff --git a/src/3rdparty/wasm/LICENSE b/src/3rdparty/wasm/LICENSE new file mode 100644 index 0000000000..4f9e8544b7 --- /dev/null +++ b/src/3rdparty/wasm/LICENSE @@ -0,0 +1,15 @@ +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (“Fonts”) and associated documentation files (the “Font Software”), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words “Bitstream” or the word “Vera”. + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the “Bitstream Vera” names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of GNOME, the GNOME Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the GNOME Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. diff --git a/src/3rdparty/wasm/Vera.ttf b/src/3rdparty/wasm/Vera.ttf new file mode 100644 index 0000000000000000000000000000000000000000..58cd6b5e61eff273e920942e28041f8ddcf1e1b5 GIT binary patch literal 65932 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

B-* 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 405 diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index a931b43220..2aabaa2ffb 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -94,6 +94,10 @@ # include "private/qcore_unix_p.h" #endif +#ifdef Q_OS_WASM +#include +#endif + #if QT_CONFIG(regularexpression) # ifdef __UCLIBC__ # if __UCLIBC_HAS_BACKTRACE__ @@ -1690,6 +1694,37 @@ static bool win_message_handler(QtMsgType type, const QMessageLogContext &contex } #endif +#ifdef Q_OS_WASM +static bool wasm_default_message_handler(QtMsgType type, + const QMessageLogContext &context, + const QString &message) +{ + if (shouldLogToStderr()) + return false; // Leave logging up to stderr handler + + QString formattedMessage = qFormatLogMessage(type, context, message); + int emOutputFlags = (EM_LOG_CONSOLE | EM_LOG_DEMANGLE); + QByteArray localMsg = message.toLocal8Bit(); + switch (type) { + case QtDebugMsg: + break; + case QtInfoMsg: + break; + case QtWarningMsg: + emOutputFlags |= EM_LOG_WARN; + break; + case QtCriticalMsg: + emOutputFlags |= EM_LOG_ERROR; + break; + case QtFatalMsg: + emOutputFlags |= EM_LOG_ERROR; + } + emscripten_log(emOutputFlags, "%s\n", qPrintable(formattedMessage)); + + return true; // Prevent further output to stderr +} +#endif + #endif // Bootstrap check // -------------------------------------------------------------------------- @@ -1735,6 +1770,8 @@ static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &con # elif defined(QT_USE_APPLE_UNIFIED_LOGGING) if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) handledStderr |= AppleUnifiedLogger::messageHandler(type, context, message); +# elif defined Q_OS_WASM + handledStderr |= wasm_default_message_handler(type, context, message); # endif #endif diff --git a/src/corelib/global/qprocessordetection.h b/src/corelib/global/qprocessordetection.h index 9a3dfd776d..aaa27dff4a 100644 --- a/src/corelib/global/qprocessordetection.h +++ b/src/corelib/global/qprocessordetection.h @@ -320,6 +320,12 @@ # endif # define Q_BYTE_ORDER Q_BIG_ENDIAN +// -- Web Assembly -- +#elif defined(__EMSCRIPTEN__) +# define Q_PROCESSOR_WASM +# define Q_PROCESSOR_X86 6 // enables SIMD support +# define Q_BYTE_ORDER Q_LITTLE_ENDIAN +# define Q_PROCESSOR_WORDSIZE 8 #endif /* diff --git a/src/corelib/global/qsystemdetection.h b/src/corelib/global/qsystemdetection.h index cacb95b674..aabe46f3c2 100644 --- a/src/corelib/global/qsystemdetection.h +++ b/src/corelib/global/qsystemdetection.h @@ -137,6 +137,8 @@ # define Q_OS_HPUX #elif defined(__native_client__) # define Q_OS_NACL +#elif defined(__EMSCRIPTEN__) +# define Q_OS_WASM #elif defined(__linux__) || defined(__linux) # define Q_OS_LINUX #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 835e6a6a44..eebf36416e 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -860,7 +860,7 @@ QString QFileSystemEngine::resolveUserName(uint userId) QVarLengthArray buf(size_max); #endif -#if !defined(Q_OS_INTEGRITY) +#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM) struct passwd *pw = 0; #if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS) struct passwd entry; @@ -884,7 +884,7 @@ QString QFileSystemEngine::resolveGroupName(uint groupId) QVarLengthArray buf(size_max); #endif -#if !defined(Q_OS_INTEGRITY) +#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_WASM) struct group *gr = 0; #if QT_CONFIG(thread) && defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_VXWORKS) && (!defined(Q_OS_ANDROID) || defined(Q_OS_ANDROID) && (__ANDROID_API__ >= 24)) size_max = sysconf(_SC_GETGR_R_SIZE_MAX); diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp index 1134c6bb85..391d2a49af 100644 --- a/src/corelib/io/qsettings.cpp +++ b/src/corelib/io/qsettings.cpp @@ -77,6 +77,10 @@ # include #endif +#ifdef Q_OS_WASM +#include +#endif + #include #include @@ -1544,6 +1548,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile) perms |= QFile::ReadGroup | QFile::ReadOther; QFile(confFile->name).setPermissions(perms); } +#ifdef Q_OS_WASM + EM_ASM( + // Sync sandbox filesystem to persistent database filesystem. See QTBUG-70002 + FS.syncfs(false, function(err) { + }); + ); +#endif } else { setStatus(QSettings::AccessError); } diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 0303916c11..1350a7aa94 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -118,6 +118,10 @@ # include #endif +#ifdef Q_OS_WASM +#include +#endif + #ifdef QT_BOOTSTRAPPED #include #else @@ -486,6 +490,13 @@ QCoreApplicationPrivate::QCoreApplicationPrivate(int &aargc, char **aargv, uint QCoreApplicationPrivate::~QCoreApplicationPrivate() { +#ifdef Q_OS_WASM + EM_ASM( + // unmount persistent directory as IDBFS + // see also QTBUG-70002 + FS.unmount('/home/web_user'); + ); +#endif #ifndef QT_NO_QOBJECT cleanupThreadData(); #endif @@ -777,6 +788,17 @@ void QCoreApplicationPrivate::init() Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object"); QCoreApplication::self = q; +#ifdef Q_OS_WASM + EM_ASM( + // mount and sync persistent filesystem to sandbox + FS.mount(IDBFS, {}, '/home/web_user'); + FS.syncfs(true, function(err) { + if (err) + Module.print(err); + }); + ); +#endif + // Store app name/version (so they're still available after QCoreApplication is destroyed) if (!coreappdata()->applicationNameSet) coreappdata()->application = appName(); diff --git a/src/corelib/kernel/qeventdispatcher_unix.cpp b/src/corelib/kernel/qeventdispatcher_unix.cpp index a28f2e3f0a..535f86fefe 100644 --- a/src/corelib/kernel/qeventdispatcher_unix.cpp +++ b/src/corelib/kernel/qeventdispatcher_unix.cpp @@ -130,7 +130,7 @@ static void initThreadPipeFD(int fd) bool QThreadPipe::init() { -#if defined(Q_OS_NACL) +#if defined(Q_OS_NACL) || defined(Q_OS_WASM) // do nothing. #elif defined(Q_OS_VXWORKS) qsnprintf(name, sizeof(name), "/pipe/qt_%08x", int(taskIdSelf())); diff --git a/src/corelib/kernel/qeventloop.cpp b/src/corelib/kernel/qeventloop.cpp index f1d32b15d1..a6cc51621a 100644 --- a/src/corelib/kernel/qeventloop.cpp +++ b/src/corelib/kernel/qeventloop.cpp @@ -48,6 +48,10 @@ #include "qeventloop_p.h" #include +#ifdef Q_OS_WASM +#include +#endif + QT_BEGIN_NAMESPACE /*! @@ -208,6 +212,15 @@ int QEventLoop::exec(ProcessEventsFlags flags) if (app && app->thread() == thread()) QCoreApplication::removePostedEvents(app, QEvent::Quit); +#ifdef Q_OS_WASM + // Partial support for nested event loops: Make the runtime throw a JavaSrcript + // exception, which returns control to the browser while preserving the C++ stack. + // Event processing then continues as normal. The sleep call below never returns. + // QTBUG-70185 + if (d->threadData->loopLevel > 1) + emscripten_sleep(1); +#endif + while (!d->exit.loadAcquire()) processEvents(flags | WaitForMoreEvents | EventLoopExec); @@ -269,6 +282,17 @@ void QEventLoop::exit(int returnCode) d->returnCode.store(returnCode); d->exit.storeRelease(true); d->threadData->eventDispatcher.load()->interrupt(); + +#ifdef Q_OS_WASM + // QEventLoop::exec() never returns in emscripten. We implement approximate behavior here. + // QTBUG-70185 + if (d->threadData->loopLevel == 1) { + emscripten_force_exit(returnCode); + } else { + d->inExec = false; + --d->threadData->loopLevel; + } +#endif } /*! diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp index a58698af53..10b5c8eafd 100644 --- a/src/corelib/mimetypes/qmimedatabase.cpp +++ b/src/corelib/mimetypes/qmimedatabase.cpp @@ -151,7 +151,9 @@ void QMimeDatabasePrivate::loadProviders() QVector QMimeDatabasePrivate::providers() { +#ifndef Q_OS_WASM // stub implementation always returns true Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex +#endif if (m_providers.isEmpty()) { loadProviders(); m_lastCheck.start(); diff --git a/src/corelib/tools/qsimd.cpp b/src/corelib/tools/qsimd.cpp index 85efd3cded..07a8b022bc 100644 --- a/src/corelib/tools/qsimd.cpp +++ b/src/corelib/tools/qsimd.cpp @@ -190,7 +190,9 @@ static inline quint64 detectProcessorFeatures() static int maxBasicCpuidSupported() { -#if defined(Q_CC_GNU) +#if defined(Q_CC_EMSCRIPTEN) + return 6; // All features supported by Emscripten +#elif defined(Q_CC_GNU) qregisterint tmp1; # if Q_PROCESSOR_X86 < 5 @@ -235,7 +237,7 @@ static int maxBasicCpuidSupported() static void cpuidFeatures01(uint &ecx, uint &edx) { -#if defined(Q_CC_GNU) +#if defined(Q_CC_GNU) && !defined(Q_CC_EMSCRIPTEN) qregisterint tmp1; asm ("xchg " PICreg", %2\n" "cpuid\n" @@ -252,6 +254,9 @@ static void cpuidFeatures01(uint &ecx, uint &edx) __CPUID(1, info); ecx = info[2]; edx = info[3]; +#else + Q_UNUSED(ecx); + Q_UNUSED(edx); #endif } @@ -261,7 +266,7 @@ inline void __cpuidex(int info[4], int, __int64) { memset(info, 0, 4*sizeof(int) static void cpuidFeatures07_00(uint &ebx, uint &ecx, uint &edx) { -#if defined(Q_CC_GNU) +#if defined(Q_CC_GNU) && !defined(Q_CC_EMSCRIPTEN) qregisteruint rbx; // in case it's 64-bit qregisteruint rcx = 0; qregisteruint rdx = 0; @@ -294,7 +299,7 @@ inline quint64 _xgetbv(__int64) { return 0; } #endif static void xgetbv(uint in, uint &eax, uint &edx) { -#if defined(Q_CC_GNU) || defined(Q_CC_GHS) +#if (defined(Q_CC_GNU) && !defined(Q_CC_EMSCRIPTEN)) || defined(Q_CC_GHS) asm (".byte 0x0F, 0x01, 0xD0" // xgetbv instruction : "=a" (eax), "=d" (edx) : "c" (in)); @@ -302,6 +307,10 @@ static void xgetbv(uint in, uint &eax, uint &edx) quint64 result = _xgetbv(in); eax = result; edx = result >> 32; +#else + Q_UNUSED(in); + Q_UNUSED(eax); + Q_UNUSED(edx); #endif } diff --git a/src/corelib/tools/qunicodetables_p.h b/src/corelib/tools/qunicodetables_p.h index f3fb6ec1b0..3f2e91a9b2 100644 --- a/src/corelib/tools/qunicodetables_p.h +++ b/src/corelib/tools/qunicodetables_p.h @@ -72,6 +72,9 @@ struct Properties { signed short mirrorDiff : 16; ushort lowerCaseSpecial : 1; signed short lowerCaseDiff : 15; +#ifdef Q_OS_WASM + unsigned char : 0; //wasm 64 packing trick +#endif ushort upperCaseSpecial : 1; signed short upperCaseDiff : 15; ushort titleCaseSpecial : 1; @@ -80,6 +83,9 @@ struct Properties { signed short caseFoldDiff : 15; ushort unicodeVersion : 8; /* 5 used */ ushort nfQuickCheck : 8; +#ifdef Q_OS_WASM + unsigned char : 0; //wasm 64 packing trick +#endif ushort graphemeBreakClass : 5; /* 5 used */ ushort wordBreakClass : 5; /* 5 used */ ushort sentenceBreakClass : 8; /* 4 used */ diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 9ec6ea4894..dc28e0e0a2 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -220,7 +220,7 @@ qtConfig(system-doubleconversion) { } # Note: libm should be present by default becaue this is C++ -unix:!macx-icc:!vxworks:!haiku:!integrity: LIBS_PRIVATE += -lm +unix:!macx-icc:!vxworks:!haiku:!integrity:!wasm: LIBS_PRIVATE += -lm TR_EXCLUDE += ../3rdparty/* diff --git a/src/gui/configure.json b/src/gui/configure.json index 26a5ae9a3d..4741ed345a 100644 --- a/src/gui/configure.json +++ b/src/gui/configure.json @@ -164,7 +164,8 @@ }, "sources": [ { "type": "pkgConfig", "args": "freetype2" }, - { "type": "freetype", "libs": "-lfreetype" } + { "type": "freetype", "libs": "-lfreetype", "condition": "!config.wasm" }, + { "type": "freetype", "libs": "-s USE_FREETYPE=1", "condition": "config.wasm" } ] }, "fontconfig": { @@ -308,7 +309,8 @@ { "libs": "-llibpng16", "condition": "config.msvc" }, { "libs": "-llibpng", "condition": "config.msvc" }, { "libs": "-lpng16", "condition": "!config.msvc" }, - { "libs": "-lpng", "condition": "!config.msvc" } + { "libs": "-lpng", "condition": "!config.msvc" }, + { "libs": "-s USE_LIBPNG=1", "condition": "config.wasm" } ], "use": [ { "lib": "zlib", "condition": "features.system-zlib" } @@ -1111,7 +1113,7 @@ }, "opengles3": { "label": "OpenGL ES 3.0", - "condition": "features.opengles2 && !features.angle && tests.opengles3", + "condition": "features.opengles2 && !features.angle && tests.opengles3 && !config.wasm", "output": [ "publicFeature", { "type": "define", "name": "QT_OPENGL_ES_3" } @@ -1138,7 +1140,7 @@ "enable": "input.opengl == 'desktop'", "disable": "input.opengl == 'es2' || input.opengl == 'dynamic' || input.opengl == 'no'", "condition": "(config.win32 && !config.winrt && !features.opengles2 && (config.msvc || libs.opengl)) - || (!config.watchos && !config.win32 && libs.opengl)" + || (!config.watchos && !config.win32 && !config.wasm && libs.opengl)" }, "opengl-dynamic": { "label": "Dynamic OpenGL", diff --git a/src/gui/configure.pri b/src/gui/configure.pri index b40536e41d..2971fd136e 100644 --- a/src/gui/configure.pri +++ b/src/gui/configure.pri @@ -55,6 +55,7 @@ defineTest(qtConfTest_qpaDefaultPlatform) { else: qnx: name = qnx else: integrity: name = integrityfb else: haiku: name = haiku + else: wasm: name = webassembly else: name = xcb $${1}.value = $$name diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 93e8b6ee8f..bc1f83a9c8 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -114,6 +114,10 @@ # include #endif // Q_OS_WIN +#ifdef Q_OS_WASM +#include +#endif + #include #include @@ -1620,7 +1624,13 @@ QGuiApplicationPrivate::~QGuiApplicationPrivate() qt_gl_set_global_share_context(0); } #endif - +#ifdef Q_OS_WASM + EM_ASM( + // unmount persistent directory as IDBFS + // see QTBUG-70002 + FS.unmount('/home/web_user'); + ); +#endif platform_integration->destroy(); delete platform_theme; diff --git a/src/gui/kernel/qopenglcontext.cpp b/src/gui/kernel/qopenglcontext.cpp index 7bf941b9a1..cd50294a6c 100644 --- a/src/gui/kernel/qopenglcontext.cpp +++ b/src/gui/kernel/qopenglcontext.cpp @@ -977,8 +977,11 @@ bool QOpenGLContext::makeCurrent(QSurface *surface) if (!surface->surfaceHandle()) return false; if (!surface->supportsOpenGL()) { +#ifndef Q_OS_WASM // ### work around the WASM platform plugin using QOpenGLContext with raster surfaces. + // see QTBUG-70076 qWarning() << "QOpenGLContext::makeCurrent() called with non-opengl surface" << surface; return false; +#endif } QOpenGLContext *previous = QOpenGLContextPrivate::setCurrentContext(this); diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp index 6486a447ee..cae3d516c4 100644 --- a/src/gui/opengl/qopenglframebufferobject.cpp +++ b/src/gui/opengl/qopenglframebufferobject.cpp @@ -142,6 +142,14 @@ QT_BEGIN_NAMESPACE #define GL_CONTEXT_LOST 0x0507 #endif +#ifndef GL_DEPTH_STENCIL_ATTACHMENT +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#endif + +#ifndef GL_DEPTH_STENCIL +#define GL_DEPTH_STENCIL 0x84F9 +#endif + /*! @@ -619,7 +627,11 @@ void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext // free existing attachments if (depth_buffer_guard) { +#ifdef Q_OS_WASM + funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); +#else funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); +#endif depth_buffer_guard->free(); } if (stencil_buffer_guard) { @@ -637,7 +649,35 @@ void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext // In practice, a combined depth-stencil buffer is supported by all desktop platforms, while a // separate stencil buffer is not. On embedded devices however, a combined depth-stencil buffer // might not be supported while separate buffers are, according to QTBUG-12861. +#ifdef Q_OS_WASM + // WebGL doesn't allow separately attach buffers to + // STENCIL_ATTACHMENT and DEPTH_ATTACHMENT + // QTBUG-69913 + if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil) { + funcs.glGenRenderbuffers(1, &depth_buffer); + funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer); + Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer)); + GLenum storageFormat = GL_DEPTH_STENCIL; + + if (samples != 0 ) { + funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, + storageFormat, dsSize.width(), dsSize.height()); + } else { + funcs.glRenderbufferStorage(GL_RENDERBUFFER, storageFormat, + dsSize.width(), dsSize.height()); + } + + funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, depth_buffer); + + valid = checkFramebufferStatus(ctx); + if (!valid) { + funcs.glDeleteRenderbuffers(1, &depth_buffer); + depth_buffer = 0; + } + } +#else if (attachment == QOpenGLFramebufferObject::CombinedDepthStencil && funcs.hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil)) { @@ -729,11 +769,16 @@ void QOpenGLFramebufferObjectPrivate::initDepthStencilAttachments(QOpenGLContext stencil_buffer = 0; } } +#endif //Q_OS_WASM // The FBO might have become valid after removing the depth or stencil buffer. valid = checkFramebufferStatus(ctx); +#ifdef Q_OS_WASM + if (depth_buffer) { +#else if (depth_buffer && stencil_buffer) { +#endif fbo_attachment = QOpenGLFramebufferObject::CombinedDepthStencil; } else if (depth_buffer) { fbo_attachment = QOpenGLFramebufferObject::Depth; diff --git a/src/network/access/access.pri b/src/network/access/access.pri index a129beda15..b068f96283 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -68,6 +68,13 @@ qtConfig(networkdiskcache) { mac: LIBS_PRIVATE += -framework Security +wasm { + SOURCES += \ + access/qnetworkreplywasmimpl.cpp + HEADERS += \ + access/qnetworkreplywasmimpl_p.h +} + include($$PWD/../../3rdparty/zlib_dependency.pri) qtConfig(http) { diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 96e3f92db1..bec98a3f58 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -82,6 +82,9 @@ #include #include #endif +#ifdef Q_OS_WASM +#include "qnetworkreplywasmimpl_p.h" +#endif QT_BEGIN_NAMESPACE @@ -1344,6 +1347,16 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera bool isLocalFile = req.url().isLocalFile(); QString scheme = req.url().scheme(); +#ifdef Q_OS_WASM + if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) { + QNetworkReplyWasmImpl *reply = new QNetworkReplyWasmImpl(this); + QNetworkReplyWasmImplPrivate *priv = reply->d_func(); + priv->manager = this; + priv->setup(op, req, outgoingData); + return reply; + } +#endif + // fast path for GET on file:// URLs // The QNetworkAccessFileBackend will right now only be used for PUT if (op == QNetworkAccessManager::GetOperation diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index a0ce3eddcd..67b3a8b71b 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -195,6 +195,9 @@ private: friend class QNetworkReplyHttpImplPrivate; friend class QNetworkReplyFileImpl; +#ifdef Q_OS_WASM + friend class QNetworkReplyWasmImpl; +#endif Q_DECLARE_PRIVATE(QNetworkAccessManager) Q_PRIVATE_SLOT(d_func(), void _q_replyFinished()) Q_PRIVATE_SLOT(d_func(), void _q_replyEncrypted()) diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp new file mode 100644 index 0000000000..9c2ff8fb89 --- /dev/null +++ b/src/network/access/qnetworkreplywasmimpl.cpp @@ -0,0 +1,640 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkreplywasmimpl_p.h" +#include "qnetworkrequest.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate() + : QNetworkReplyPrivate() + , managerPrivate(0) + , downloadBufferReadPosition(0) + , downloadBufferCurrentSize(0) + , totalDownloadSize(0) + , percentFinished(0) +{ +} + +QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate() +{ +} + +QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl() +{ +} + +QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent) + : QNetworkReply(*new QNetworkReplyWasmImplPrivate(), parent) +{ +} + +QByteArray QNetworkReplyWasmImpl::methodName() const +{ + switch (operation()) { + case QNetworkAccessManager::HeadOperation: + return "HEAD"; + case QNetworkAccessManager::GetOperation: + return "GET"; + case QNetworkAccessManager::PutOperation: + return "PUT"; + case QNetworkAccessManager::PostOperation: + return "POST"; + case QNetworkAccessManager::DeleteOperation: + return "DELETE"; + default: + break; + } + return QByteArray(); +} + +void QNetworkReplyWasmImpl::close() +{ + QNetworkReply::close(); +} + +void QNetworkReplyWasmImpl::abort() +{ + close(); +} + +qint64 QNetworkReplyWasmImpl::bytesAvailable() const +{ + Q_D(const QNetworkReplyWasmImpl); + + if (!d->isFinished) + return QNetworkReply::bytesAvailable(); + + return QNetworkReply::bytesAvailable() + d->downloadBufferCurrentSize - d->downloadBufferReadPosition; +} + +bool QNetworkReplyWasmImpl::isSequential() const +{ + return true; +} + +qint64 QNetworkReplyWasmImpl::size() const +{ + return QNetworkReply::size(); +} + +/*! + \internal +*/ +qint64 QNetworkReplyWasmImpl::readData(char *data, qint64 maxlen) +{ + Q_D(QNetworkReplyWasmImpl); + + qint64 howMuch = qMin(maxlen, (d->downloadBuffer.size() - d->downloadBufferReadPosition)); + memcpy(data, d->downloadBuffer.constData(), howMuch); + d->downloadBufferReadPosition += howMuch; + + return howMuch; +} + +void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *data) +{ + Q_Q(QNetworkReplyWasmImpl); + + outgoingData = data; + request = req; + url = request.url(); + operation = op; + + q->QIODevice::open(QIODevice::ReadOnly); + if (outgoingData && outgoingData->isSequential()) { + bool bufferingDisallowed = + request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, false).toBool(); + + if (bufferingDisallowed) { + // if a valid content-length header for the request was supplied, we can disable buffering + // if not, we will buffer anyway + if (!request.header(QNetworkRequest::ContentLengthHeader).isValid()) { + state = Buffering; + _q_bufferOutgoingData(); + return; + } + } else { + // doSendRequest will be called when the buffering has finished. + state = Buffering; + _q_bufferOutgoingData(); + return; + } + } + // No outgoing data (POST, ..) + doSendRequest(); +} + +void QNetworkReplyWasmImplPrivate::onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int buffer, int bufferSize) +{ + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast(data); + + const QString reasonStr = QString::fromUtf8(reinterpret_cast(statusReason)); + + switch (readyState) { + case 0://unsent + break; + case 1://opened + break; + case 2://headers received + break; + case 3://loading + break; + case 4: {//done + handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + if (!reasonStr.isEmpty()) + handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr); + + if (statusCode >= 400) { + if (!reasonStr.isEmpty()) + handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr); + } else { + handler->dataReceived(reinterpret_cast(buffer), bufferSize); + } + } + break; + }; + } + +void QNetworkReplyWasmImplPrivate::onProgressCallback(void* data, int bytesWritten, int total, uint timestamp) +{ + Q_UNUSED(timestamp); + + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast(data); + handler->emitDataReadProgress(bytesWritten, total); +} + +void QNetworkReplyWasmImplPrivate::onRequestErrorCallback(void* data, int statusCode, int statusReason) +{ + QString reasonStr = QString::fromUtf8(reinterpret_cast(statusReason)); + + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast(data); + + handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode); + if (!reasonStr.isEmpty()) + handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr); + + if (statusCode >= 400) { + if (!reasonStr.isEmpty()) + handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr); + } +} + +void QNetworkReplyWasmImplPrivate::onResponseHeadersCallback(void* data, int headers) +{ + QNetworkReplyWasmImplPrivate *handler = reinterpret_cast(data); + handler->headersReceived(reinterpret_cast(headers)); +} + +void QNetworkReplyWasmImplPrivate::doSendRequest() +{ + Q_Q(QNetworkReplyWasmImpl); + totalDownloadSize = 0; + jsRequest(QString::fromUtf8(q->methodName()), // GET POST + request.url().toString(), + (void *)&onLoadCallback, + (void *)&onProgressCallback, + (void *)&onRequestErrorCallback, + (void *)&onResponseHeadersCallback); +} + +/* const QString &body, const QList > &headers ,*/ +void QNetworkReplyWasmImplPrivate::jsRequest(const QString &verb, const QString &url, + void *loadCallback, void *progressCallback, + void *errorCallback, void *onResponseHeadersCallback) +{ + QString extraDataString; + + QByteArray extraData; + if (outgoingData) + extraData = outgoingData->readAll(); + + if (extraData.size() > 0) + extraDataString.fromUtf8(extraData); + + if (extraDataString.size() >= 0 && verb == QStringLiteral("POST") && extraDataString.startsWith(QStringLiteral("?"))) + extraDataString.remove(QStringLiteral("?")); + + // Probably a good idea to save any shared pointers as members in C++ + // so the objects they point to survive as long as you need them + + QStringList headersList; + for (auto header : request.rawHeaderList()) + headersList << QString::fromUtf8(header + ":" + request.rawHeader(header)); + + EM_ASM_ARGS({ + var verb = Pointer_stringify($0); + var url = Pointer_stringify($1); + var onLoadCallbackPointer = $2; + var onProgressCallbackPointer = $3; + var onErrorCallbackPointer = $4; + var onHeadersCallback = $5; + var handler = $8; + + var dataToSend; + var extraRequestData = Pointer_stringify($6); // request parameters + var headersData = Pointer_stringify($7); + + var xhr; + xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + + xhr.open(verb, url, true); //async + + function handleError(xhrStatusCode, xhrStatusText) { + var errorPtr = allocate(intArrayFromString(xhrStatusText), 'i8', ALLOC_NORMAL); + Runtime.dynCall('viii', onErrorCallbackPointer, [handler, xhrStatusCode, errorPtr]); + _free(errorPtr); + } + + if (headersData) { + var headers = headersData.split("&"); + for (var i = 0; i < headers.length; i++) { + var header = headers[i].split(":")[0]; + var value = headers[i].split(":")[1]; + + if (verb === 'POST' && value.toLowerCase().includes('json')) { + if (extraRequestData) { + xhr.responseType = 'json'; + dataToSend = extraRequestData; + } + } + if (verb === 'POST' && value.toLowerCase().includes('form')) { + if (extraRequestData) { + var formData = new FormData(); + var extra = extraRequestData.split("&"); + for (var i = 0; i < extra.length; i++) { + formData.append(extra[i].split("=")[0],extra[i].split("=")[1]); + } + dataToSend = formData; + } + } + xhr.setRequestHeader(header, value); + } + } + + xhr.onprogress = function(e) { + switch (xhr.status) { + case 200: + case 206: + case 300: + case 301: + case 302: { + var date = xhr.getResponseHeader('Last-Modified'); + date = ((date != null) ? new Date(date).getTime() / 1000 : 0); + Runtime.dynCall('viiii', onProgressCallbackPointer, [handler, e.loaded, e.total, date]); + } + break; + } + }; + + xhr.onreadystatechange = function() { + if (this.readyState == this.HEADERS_RECEIVED) { + var responseStr = this.getAllResponseHeaders(); + if (responseStr.length > 0) { + var ptr = allocate(intArrayFromString(responseStr), 'i8', ALLOC_NORMAL); + Runtime.dynCall('vii', onHeadersCallback, [handler, ptr]); + _free(ptr); + } + } + }; + + xhr.onload = function(e) { + if (xhr.status >= 300) { //error + handleError(xhr.status, xhr.statusText); + } else { + if (this.status == 200 || this.status == 203) { + var datalength; + var byteArray = 0; + var buffer; + if (this.responseType.length === 0 || this.responseType === 'document') { + byteArray = new Uint8Array(this.responseText); + } else if (this.responseType === 'json') { + var jsonResponse = JSON.stringify(this.response); + buffer = allocate(intArrayFromString(jsonResponse), 'i8', ALLOC_NORMAL); + datalength = jsonResponse.length; + } else if (this.responseType === 'arraybuffer') { + byteArray = new Uint8Array(xhr.response); + } + if (byteArray != 0 ) { + datalength = byteArray.length; + buffer = _malloc(datalength); + HEAPU8.set(byteArray, buffer); + } + var reasonPtr = allocate(intArrayFromString(this.statusText), 'i8', ALLOC_NORMAL); + Runtime.dynCall('viiiiii', onLoadCallbackPointer, [handler, this.status, reasonPtr, this.readyState, buffer, datalength]); + _free(buffer); + _free(reasonPtr); + } + } + }; + + xhr.onerror = function(e) { + handleError(xhr.status, xhr.statusText); + }; + //TODO other operations, handle user/pass, handle binary data, data streaming + xhr.send(dataToSend); + + }, verb.toLatin1().data(), + url.toLatin1().data(), + loadCallback, + progressCallback, + errorCallback, + onResponseHeadersCallback, + extraDataString.size() > 0 ? extraDataString.toLatin1().data() : extraData.data(), + headersList.join(QStringLiteral("&")).toLatin1().data(), + this + ); +} + +void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString) +{ + Q_UNUSED(errorCode) + Q_Q(QNetworkReplyWasmImpl); + + q->setError(errorCode, errorString); + emit q->error(errorCode); + + q->setFinished(true); + emit q->finished(); +} + +void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + Q_Q(QNetworkReplyWasmImpl); + + totalDownloadSize = bytesTotal; + + percentFinished = (bytesReceived / bytesTotal) * 100; + + emit q->downloadProgress(bytesReceived, totalDownloadSize); +} + +void QNetworkReplyWasmImplPrivate::dataReceived(char *buffer, int bufferSize) +{ + Q_Q(QNetworkReplyWasmImpl); + + if (bufferSize > 0) + q->setReadBufferSize(bufferSize); + + bytesDownloaded = bufferSize; + + if (percentFinished != 100) + downloadBufferCurrentSize += bufferSize; + else + downloadBufferCurrentSize = bufferSize; + + totalDownloadSize = downloadBufferCurrentSize; + + downloadBuffer.append(buffer, bufferSize); + + if (downloadBufferCurrentSize == totalDownloadSize) { + q->setFinished(true); + emit q->finished(); + } +} + +//taken from qnetworkrequest.cpp +static int parseHeaderName(const QByteArray &headerName) +{ + if (headerName.isEmpty()) + return -1; + + switch (tolower(headerName.at(0))) { + case 'c': + if (qstricmp(headerName.constData(), "content-type") == 0) + return QNetworkRequest::ContentTypeHeader; + else if (qstricmp(headerName.constData(), "content-length") == 0) + return QNetworkRequest::ContentLengthHeader; + else if (qstricmp(headerName.constData(), "cookie") == 0) + return QNetworkRequest::CookieHeader; + break; + + case 'l': + if (qstricmp(headerName.constData(), "location") == 0) + return QNetworkRequest::LocationHeader; + else if (qstricmp(headerName.constData(), "last-modified") == 0) + return QNetworkRequest::LastModifiedHeader; + break; + + case 's': + if (qstricmp(headerName.constData(), "set-cookie") == 0) + return QNetworkRequest::SetCookieHeader; + else if (qstricmp(headerName.constData(), "server") == 0) + return QNetworkRequest::ServerHeader; + break; + + case 'u': + if (qstricmp(headerName.constData(), "user-agent") == 0) + return QNetworkRequest::UserAgentHeader; + break; + } + + return -1; // nothing found +} + + +void QNetworkReplyWasmImplPrivate::headersReceived(char *buffer) +{ + Q_Q(QNetworkReplyWasmImpl); + + QString bufferString = QString::fromUtf8(buffer); + if (!bufferString.isEmpty()) { + QStringList headers = bufferString.split(QString::fromUtf8("\r\n"), QString::SkipEmptyParts); + + for (int i = 0; i < headers.size(); i++) { + QString headerName = headers.at(i).split(QString::fromUtf8(": ")).at(0); + QString headersValue = headers.at(i).split(QString::fromUtf8(": ")).at(1); + if (headerName.isEmpty() || headersValue.isEmpty()) + continue; + + int headerIndex = parseHeaderName(headerName.toLocal8Bit()); + + if (headerIndex == -1) + q->setRawHeader(headerName.toLocal8Bit(), headersValue.toLocal8Bit()); + else + q->setHeader(static_cast(headerIndex), (QVariant)headersValue); + } + } + emit q->metaDataChanged(); +} + +void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingDataFinished() +{ + Q_Q(QNetworkReplyWasmImpl); + + // make sure this is only called once, ever. + //_q_bufferOutgoingData may call it or the readChannelFinished emission + if (state != Buffering) + return; + + // disconnect signals + QObject::disconnect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::disconnect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + + // finally, start the request + doSendRequest(); +} + +void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData() +{ + Q_Q(QNetworkReplyWasmImpl); + + if (!outgoingDataBuffer) { + // first call, create our buffer + outgoingDataBuffer = QSharedPointer::create(); + + QObject::connect(outgoingData, SIGNAL(readyRead()), q, SLOT(_q_bufferOutgoingData())); + QObject::connect(outgoingData, SIGNAL(readChannelFinished()), q, SLOT(_q_bufferOutgoingDataFinished())); + } + + qint64 bytesBuffered = 0; + qint64 bytesToBuffer = 0; + + // read data into our buffer + forever { + bytesToBuffer = outgoingData->bytesAvailable(); + // unknown? just try 2 kB, this also ensures we always try to read the EOF + if (bytesToBuffer <= 0) + bytesToBuffer = 2*1024; + + char *dst = outgoingDataBuffer->reserve(bytesToBuffer); + bytesBuffered = outgoingData->read(dst, bytesToBuffer); + + if (bytesBuffered == -1) { + // EOF has been reached. + outgoingDataBuffer->chop(bytesToBuffer); + + _q_bufferOutgoingDataFinished(); + break; + } else if (bytesBuffered == 0) { + // nothing read right now, just wait until we get called again + outgoingDataBuffer->chop(bytesToBuffer); + + break; + } else { + // don't break, try to read() again + outgoingDataBuffer->chop(bytesToBuffer - bytesBuffered); + } + } +} + +//taken from qhttpthreaddelegate.cpp +QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url) +{ + QNetworkReply::NetworkError code; + // we've got an error + switch (httpStatusCode) { + case 400: // Bad Request + code = QNetworkReply::ProtocolInvalidOperationError; + break; + + case 401: // Authorization required + code = QNetworkReply::AuthenticationRequiredError; + break; + + case 403: // Access denied + code = QNetworkReply::ContentAccessDenied; + break; + + case 404: // Not Found + code = QNetworkReply::ContentNotFoundError; + break; + + case 405: // Method Not Allowed + code = QNetworkReply::ContentOperationNotPermittedError; + break; + + case 407: + code = QNetworkReply::ProxyAuthenticationRequiredError; + break; + + case 409: // Resource Conflict + code = QNetworkReply::ContentConflictError; + break; + + case 410: // Content no longer available + code = QNetworkReply::ContentGoneError; + break; + + case 418: // I'm a teapot + code = QNetworkReply::ProtocolInvalidOperationError; + break; + + case 500: // Internal Server Error + code = QNetworkReply::InternalServerError; + break; + + case 501: // Server does not support this functionality + code = QNetworkReply::OperationNotImplementedError; + break; + + case 503: // Service unavailable + code = QNetworkReply::ServiceUnavailableError; + break; + + default: + if (httpStatusCode > 500) { + // some kind of server error + code = QNetworkReply::UnknownServerError; + } else if (httpStatusCode >= 400) { + // content error we did not handle above + code = QNetworkReply::UnknownContentError; + } else { + qWarning("QNetworkAccess: got HTTP status code %d which is not expected from url: \"%s\"", + httpStatusCode, qPrintable(url.toString())); + code = QNetworkReply::ProtocolFailure; + } + }; + + return code; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qnetworkreplywasmimpl_p.h b/src/network/access/qnetworkreplywasmimpl_p.h new file mode 100644 index 0000000000..a707390503 --- /dev/null +++ b/src/network/access/qnetworkreplywasmimpl_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNETWORKREPLYWASMIMPL_H +#define QNETWORKREPLYWASMIMPL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Network Access API. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qnetworkreply.h" +#include "qnetworkreply_p.h" +#include "qnetworkaccessmanager.h" + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; + +class QNetworkReplyWasmImplPrivate; +class QNetworkReplyWasmImpl: public QNetworkReply +{ + Q_OBJECT +public: + QNetworkReplyWasmImpl(QObject *parent = nullptr); + ~QNetworkReplyWasmImpl(); + virtual void abort() override; + + // reimplemented from QNetworkReply + virtual void close() override; + virtual qint64 bytesAvailable() const override; + virtual bool isSequential () const override; + qint64 size() const override; + + virtual qint64 readData(char *data, qint64 maxlen) override; + + void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QIODevice *outgoingData); + + Q_DECLARE_PRIVATE(QNetworkReplyWasmImpl) + + Q_PRIVATE_SLOT(d_func(), void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)) + Q_PRIVATE_SLOT(d_func(), void emitDataReadProgress(qint64 done, qint64 total)) + Q_PRIVATE_SLOT(d_func(), void dataReceived(char *buffer, int bufferSize)) + +private: + QByteArray methodName() const; + +}; + +class QNetworkReplyWasmImplPrivate: public QNetworkReplyPrivate +{ +public: + QNetworkReplyWasmImplPrivate(); + ~QNetworkReplyWasmImplPrivate(); + + QNetworkAccessManagerPrivate *managerPrivate; + void doSendRequest(); + + void jsRequest(const QString &verb, const QString &url, void *, void *, void *, void *); + + static void onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int textBuffer, int size); + static void onProgressCallback(void *data, int done, int bytesTotal, uint timestamp); + static void onRequestErrorCallback(void *data, int statusCode, int statusReason); + static void onStateChangedCallback(int status); + static void onResponseHeadersCallback(void *data, int headers); + + void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &); + void emitDataReadProgress(qint64 done, qint64 total); + void dataReceived(char *buffer, int bufferSize); + void headersReceived(char *buffer); + + void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request, + QIODevice *outgoingData); + + State state; + void _q_bufferOutgoingData(); + void _q_bufferOutgoingDataFinished(); + + QSharedPointer pendingDownloadData; + QSharedPointer pendingDownloadProgress; + + qint64 bytesDownloaded; + qint64 bytesBuffered; + + qint64 downloadBufferReadPosition; + qint64 downloadBufferCurrentSize; + qint64 totalDownloadSize; + qint64 percentFinished; + QByteArray downloadBuffer; + + QIODevice *outgoingData; + QSharedPointer outgoingDataBuffer; + + static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url); + Q_DECLARE_PUBLIC(QNetworkReplyWasmImpl) +}; + +QT_END_NAMESPACE + +//Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders) + +#endif // QNETWORKREPLYWASMIMPL_H diff --git a/src/network/configure.json b/src/network/configure.json index 32fcfb499f..327131ba11 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -198,7 +198,7 @@ "label": "OpenSSL", "enable": "input.openssl == 'yes' || input.openssl == 'linked' || input.openssl == 'runtime'", "disable": "input.openssl == 'no' || input.ssl == 'no'", - "autoDetect": "!config.winrt", + "autoDetect": "!config.winrt && !config.wasm", "condition": "!features.securetransport && (features.openssl-linked || libs.openssl_headers)", "output": [ "privateFeature", @@ -288,6 +288,7 @@ "networkinterface": { "label": "QNetworkInterface", "purpose": "Supports enumerating a host's IP addresses and network interfaces.", + "condition": "!config.wasm", "section": "Networking", "output": [ "publicFeature", "feature" ] }, diff --git a/src/opengl/qglframebufferobject.cpp b/src/opengl/qglframebufferobject.cpp index 0b386ededc..0b2ddf97fe 100644 --- a/src/opengl/qglframebufferobject.cpp +++ b/src/opengl/qglframebufferobject.cpp @@ -103,6 +103,14 @@ extern QImage qt_gl_read_frame_buffer(const QSize&, bool, bool); #define GL_DRAW_FRAMEBUFFER 0x8CA9 #endif +#ifndef GL_DEPTH_STENCIL_ATTACHMENT +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#endif + +#ifndef GL_DEPTH_STENCIL +#define GL_DEPTH_STENCIL 0x84F9 +#endif + /*! \class QGLFramebufferObjectFormat \inmodule QtOpenGL @@ -562,6 +570,7 @@ void QGLFramebufferObjectPrivate::init(QGLFramebufferObject *q, const QSize &sz, funcs.glGenRenderbuffers(1, &depth_buffer); funcs.glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer); Q_ASSERT(funcs.glIsRenderbuffer(depth_buffer)); +#ifndef Q_OS_WASM if (samples != 0 && funcs.hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, size.width(), size.height()); @@ -574,6 +583,19 @@ void QGLFramebufferObjectPrivate::init(QGLFramebufferObject *q, const QSize &sz, GL_RENDERBUFFER, depth_buffer); funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencil_buffer); +#else + // webgl does not allow separate depth and stencil attachments + if (samples != 0) { + funcs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, + GL_DEPTH_STENCIL, size.width(), size.height()); + } else { + funcs.glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, + size.width(), size.height()); + } + stencil_buffer = depth_buffer; + funcs.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, depth_buffer); +#endif valid = checkFramebufferStatus(); if (!valid) { diff --git a/src/plugins/platforms/platforms.pro b/src/plugins/platforms/platforms.pro index e61887618f..5bf2b77421 100644 --- a/src/plugins/platforms/platforms.pro +++ b/src/plugins/platforms/platforms.pro @@ -46,6 +46,8 @@ haiku { SUBDIRS += haiku } +wasm: SUBDIRS = wasm + qtConfig(mirclient): SUBDIRS += mirclient qtConfig(integrityfb): SUBDIRS += integrity diff --git a/src/plugins/platforms/wasm/main.cpp b/src/plugins/platforms/wasm/main.cpp new file mode 100644 index 0000000000..a4d23b4e0d --- /dev/null +++ b/src/plugins/platforms/wasm/main.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 +#include "qwasmintegration.h" + +QT_BEGIN_NAMESPACE + +class QWasmIntegrationPlugin : public QPlatformIntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "wasm.json") +public: + QPlatformIntegration *create(const QString &, const QStringList &) override; +}; + +QPlatformIntegration *QWasmIntegrationPlugin::create(const QString& system, const QStringList& paramList) +{ + Q_UNUSED(paramList); + if (!system.compare(QStringLiteral("wasm"), Qt::CaseInsensitive)) + return new QWasmIntegration; + + return nullptr; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js new file mode 100644 index 0000000000..37a5308034 --- /dev/null +++ b/src/plugins/platforms/wasm/qtloader.js @@ -0,0 +1,516 @@ +/**************************************************************************** +** +** 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 = { +// 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. +// 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 : +// 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 : +// Restart attempts limit. The default is 10. +// stdoutEnabled : +// stderrEnabled : +// environment : +// 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. + + +var Module = {} + +function QtLoader(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; + } + } + + 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); + } + + // Set default state handler functions if needed + if (config.containerElements !== undefined) { + config.showError = config.showError || function(errorText, container) { + removeChildren(container); + var errorTextElement = document.createElement("text"); + errorTextElement.className = "QtError" + errorTextElement.innerHTML = errorText; + return errorTextElement; + } + + config.showLoader = config.showLoader || function(loadingState, container) { + removeChildren(container); + var loadingText = document.createElement("text"); + loadingText.className = "QtLoading" + loadingText.innerHTML = '

${loadingState}...

'; + return loadingText; + }; + + config.showCanvas = config.showCanvas || function(container) { + removeChildren(container); + var canvas = document.createElement("canvas"); + canvas.className = "QtCanvas" + canvas.style = "height: 100%; width: 100%;" + return canvas; + } + + 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 = ` ${crashSymbols[symbolIndex]} ` + var errorElement = document.createElement("text"); + errorElement.className = "QtExit" + errorElement.innerHTML = errorHtml; + return errorElement; + } + } + + 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 = {}; + + var publicAPI = {}; + publicAPI.webAssemblySupported = webAssemblySupported(); + publicAPI.webGLSupported = webGLSupported(); + publicAPI.canLoadQt = canLoadQt(); + publicAPI.canLoadApplication = canLoadQt(); + publicAPI.status = undefined; + publicAPI.loadEmscriptenModule = loadEmscriptenModule; + + 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; + } + }); + } + + function fetchText(filePath) { + return fetchResource(filePath).then(function(response) { + return response.text(); + }); + } + + 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); + } + }); + } + + 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) { + return WebAssembly.instantiate(wasmModule, imports).then(function(instance) { + successCallback(instance); + return instance; + }, function(error) { + self.error = error; + setStatus("Error"); + }); + }; + + 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)) { + Module.ENV[key.toUpperCase()] = value; + } + }); + + 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); + } + } + + function setCanvasContent() { + var firstCanvas; + if (config.containerElements === undefined) { + firstCanvas = config.showCanvas(); + } else { + for (container of config.containerElements) { + var canvasElement = config.showCanvas(container); + container.appendChild(canvasElement); + } + firstCanvas = config.containerElements[0].firstChild; + } + + if (Module.canvas === undefined) { + Module.canvas = firstCanvas; + } + } + + 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; + + for (container of config.containerElements) { + var loaderElement = config.showExit(publicAPI.crashed, publicAPI.exitCode, container); + if (loaderElement !== undefined) + container.appendChild(loaderElement); + } + } + + 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(); + } + } + + // 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); + } + + setStatus("Created"); + + return publicAPI; +} diff --git a/src/plugins/platforms/wasm/qtlogo.svg b/src/plugins/platforms/wasm/qtlogo.svg new file mode 100644 index 0000000000..cb8989bb79 --- /dev/null +++ b/src/plugins/platforms/wasm/qtlogo.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp new file mode 100644 index 0000000000..5b40c44807 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** 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 "qwasmbackingstore.h" +#include "qwasmwindow.h" +#include "qwasmcompositor.h" + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window) + : QPlatformBackingStore(window) + , m_compositor(compositor) + , m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D)) +{ + QWasmWindow *wasmWindow = static_cast(window->handle()); + if (wasmWindow) + wasmWindow->setBackingStore(this); +} + +QWasmBackingStore::~QWasmBackingStore() +{ +} + +QPaintDevice *QWasmBackingStore::paintDevice() +{ + return &m_image; +} + +void QWasmBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +{ + Q_UNUSED(window); + Q_UNUSED(region); + Q_UNUSED(offset); + + m_dirty |= region; + m_compositor->requestRedraw(); +} + +void QWasmBackingStore::updateTexture() +{ + if (m_dirty.isNull()) + return; + + 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; + 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); + + // intersect with image rect to be sure + QRect r = imageRect & deviceRect; + // if the rect is wide enough it is cheaper to just extend it instead of doing an image copy + if (r.width() >= imageRect.width() / 2) { + r.setX(0); + r.setWidth(imageRect.width()); + } + + fixed |= 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 */ + + m_dirty = QRegion(); +} + +void QWasmBackingStore::beginPaint(const QRegion ®ion) +{ + m_dirty |= region; + // Keep backing store device pixel ratio in sync with window + if (m_image.devicePixelRatio() != window()->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); +} + +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()); + + if (m_texture->isCreated()) + m_texture->destroy(); +} + +QImage QWasmBackingStore::toImage() const +{ + // used by QPlatformBackingStore::composeAndFlush + return m_image; +} + +const QImage &QWasmBackingStore::getImageRef() const +{ + return m_image; +} + +const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture() +{ + updateTexture(); + return m_texture.data(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.h b/src/plugins/platforms/wasm/qwasmbackingstore.h new file mode 100644 index 0000000000..6ffa262e3d --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmbackingstore.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 QWASMBACKINGSTORE_H +#define QWASMBACKINGSTORE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QOpenGLTexture; +class QRegion; +class QWasmCompositor; + +class QWasmBackingStore : public QPlatformBackingStore +{ +public: + QWasmBackingStore(QWasmCompositor *compositor, QWindow *window); + ~QWasmBackingStore(); + + QPaintDevice *paintDevice() override; + + void beginPaint(const QRegion &) override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; + QImage toImage() const override; + const QImage &getImageRef() const; + + const QOpenGLTexture *getUpdatedTexture(); + +protected: + void updateTexture(); + +private: + QWasmCompositor *m_compositor; + QImage m_image; + QScopedPointer m_texture; + QRegion m_dirty; +}; + +QT_END_NAMESPACE + +#endif // QWASMBACKINGSTORE_H diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp new file mode 100644 index 0000000000..f3ea013325 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** 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 "qwasmcompositor.h" +#include "qwasmwindow.h" +#include "qwasmstylepixmaps_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +Q_GUI_EXPORT int qt_defaultDpiX(); + +QWasmCompositedWindow::QWasmCompositedWindow() + : window(nullptr) + , parentWindow(nullptr) + , flushPending(false) + , visible(false) +{ +} + +QWasmCompositor::QWasmCompositor() + : m_frameBuffer(nullptr) + , m_blitter(new QOpenGLTextureBlitter) + , m_needComposit(false) + , m_inFlush(false) + , m_inResize(false) + , m_isEnabled(true) + , m_targetDevicePixelRatio(1) +{ +} + +QWasmCompositor::~QWasmCompositor() +{ + delete m_frameBuffer; +} + +void QWasmCompositor::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + +void QWasmCompositor::addWindow(QWasmWindow *window, QWasmWindow *parentWindow) +{ + 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); +} + +void QWasmCompositor::removeWindow(QWasmWindow *window) +{ + QWasmWindow *platformWindow = m_compositedWindows[window].parentWindow; + + if (platformWindow) { + QWasmWindow *parentWindow = window; + m_compositedWindows[parentWindow].childWindows.removeAll(window); + } + + m_windowStack.removeAll(window); + m_compositedWindows.remove(window); + + notifyTopWindowChanged(window); +} + +void QWasmCompositor::setScreen(QWasmScreen *screen) +{ + m_screen = screen; +} + +void QWasmCompositor::setVisible(QWasmWindow *window, bool visible) +{ + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + if (compositedWindow.visible == visible) + 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) + 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 ®ion) +{ + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + compositedWindow.flushPending = true; + compositedWindow.damage = region; + + requestRedraw(); +} + +int QWasmCompositor::windowCount() const +{ + return m_windowStack.count(); +} + + +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( + 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 p, 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(p)) + 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(); + + QOpenGLTexture const *texture = backingStore->getUpdatedTexture(); + + blit(blitter, screen, texture, window->geometry()); +} + +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; + }; + + 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; +} + +int dpiScaled(qreal value) +{ + 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(); + + 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; + } + + + 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(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); + } +} + +void QWasmCompositor::drawShadePanel(QWasmTitleBarOptions options, QPainter *painter) +{ + 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(); + QVector 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++); + + x2 = x1; + y1 = y + h - 2; + for (i = 0; i < lineWidth; i++) // left shado + lines << QLineF(x1++, y1, x2++, y2--); + + painter->drawLines(lines); + lines.clear(); + painter->setPen(shade); + x1 = x; + y1 = y2 = y+h-1; + x2 = x+w-1; + for (i=0; idrawLines(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) +{ + if (window->window()->type() != Qt::Popup) + drawWindowDecorations(blitter, screen, window); + drawWindowContent(blitter, screen, window); +} + +void QWasmCompositor::frame() +{ + if (!m_needComposit) + return; + + m_needComposit = false; + + if (m_windowStack.empty() || !m_screen) + return; + + QWasmWindow *someWindow = nullptr; + + foreach (QWasmWindow *window, m_windowStack) { + if (window->window()->surfaceClass() == QSurface::Window + && qt_window_private(static_cast(window->window()))->receivedExpose) { + someWindow = window; + break; + } + } + + if (!someWindow) + return; + + if (m_context.isNull()) { + m_context.reset(new QOpenGLContext()); + //mContext->setFormat(mScreen->format()); + m_context->setScreen(m_screen->screen()); + m_context->create(); + } + + m_context->makeCurrent(someWindow->window()); + + if (!m_blitter->isCreated()) + m_blitter->create(); + + qreal dpr = m_screen->devicePixelRatio(); + glViewport(0, 0, m_screen->geometry().width() * dpr, m_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); + + foreach (QWasmWindow *window, m_windowStack) { + QWasmCompositedWindow &compositedWindow = m_compositedWindows[window]; + + if (!compositedWindow.visible) + continue; + + drawWindow(m_blitter.data(), m_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(modalWindow->handle())); + return; + } + + requestRedraw(); + QWindowSystemInterface::handleWindowActivated(window->window()); +} diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h new file mode 100644 index 0000000000..4e5ed46cec --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcompositor.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 QWASMCOMPOSITOR_H +#define QWASMCOMPOSITOR_H + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmWindow; +class QWasmScreen; +class QOpenGLContext; +class QOpenGLTextureBlitter; + +class QWasmCompositedWindow +{ +public: + QWasmCompositedWindow(); + + QWasmWindow *window; + QWasmWindow *parentWindow; + QRegion damage; + bool flushPending; + bool visible; + QList childWindows; +}; + +class QWasmCompositor : public QObject +{ + Q_OBJECT +public: + QWasmCompositor(); + ~QWasmCompositor(); + + 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); + void setScreen(QWasmScreen *screen); + + 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 ®ion); + + int windowCount() const; + + void redrawWindowContent(); + void requestRedraw(); + + QWindow *windowAt(QPoint p, int padding = 0) const; + QWindow *keyWindow() const; + + bool event(QEvent *event); + + static QWasmTitleBarOptions makeTitleBarOptions(const QWasmWindow *window); + static QRect titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol); + +private slots: + void frame(); + +private: + void createFrameBuffer(); + void flushCompletedCallback(int32_t); + 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(); + + QImage *m_frameBuffer; + QScopedPointer m_context; + QScopedPointer m_blitter; + QWasmScreen *m_screen; + + QHash m_compositedWindows; + QList 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; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QWasmCompositor::SubControls) + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp new file mode 100644 index 0000000000..54804a55b3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcursor.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 "qwasmcursor.h" + +#include + +#include + +void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window) +{ + if (windowCursor == nullptr) + return; + + // FIXME: The HTML5 plugin sets the cursor on the native canvas; when using multiple windows + // multiple cursors need to be managed taking mouse postion and stacking into account. + Q_UNUSED(window); + + // 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 main canvas + EM_ASM_ARGS({ + if (Module['canvas']) { + Module['canvas'].style['cursor'] = Pointer_stringify($0); + } + }, htmlCursorName.constData()); +} + +QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape) +{ + QByteArray cursorName; + + switch (shape) { + case Qt::ArrowCursor: + cursorName = "default"; + break; + case Qt::UpArrowCursor: + break; + case Qt::CrossCursor: + cursorName = "crosshair"; + break; + case Qt::WaitCursor: + cursorName = "wait"; + break; + case Qt::IBeamCursor: + cursorName = "text"; + break; + case Qt::SizeVerCursor: + cursorName = "ns-resize"; + break; + case Qt::SizeHorCursor: + cursorName = "ew-resize"; + break; + case Qt::SizeBDiagCursor: + cursorName = "nesw-resize"; + break; + case Qt::SizeFDiagCursor: + cursorName = "nwse-resize"; + break; + case Qt::SizeAllCursor: + break; // no equivalent? + case Qt::BlankCursor: + cursorName = "none"; + break; + case Qt::SplitVCursor: + cursorName = "row-resize"; + break; + case Qt::SplitHCursor: + cursorName = "col-resize"; + break; + case Qt::PointingHandCursor: + cursorName = "pointer"; + break; + case Qt::ForbiddenCursor: + cursorName = "not-allowed"; + break; + case Qt::WhatsThisCursor: + cursorName = "help"; + break; + case Qt::BusyCursor: + cursorName = "wait"; + break; + case Qt::OpenHandCursor: + break; // no equivalent? + case Qt::ClosedHandCursor: + break; // no equivalent? + case Qt::DragCopyCursor: + break; // no equivalent? + case Qt::DragMoveCursor: + break; // no equivalent? + case Qt::DragLinkCursor: + break; // no equivalent? + default: + break; + } + + return cursorName; +} diff --git a/src/plugins/platforms/wasm/qwasmcursor.h b/src/plugins/platforms/wasm/qwasmcursor.h new file mode 100644 index 0000000000..516e07aa31 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmcursor.h @@ -0,0 +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$ +** +****************************************************************************/ + +#ifndef QWASMCURSOR_H +#define QWASMCURSOR_H + +#include + +class QWasmCursor : public QPlatformCursor +{ +public: + void changeCursor(QCursor *windowCursor, QWindow *window) override; + + QByteArray cursorShapeToHtml(Qt::CursorShape shape); +}; + +#endif diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp new file mode 100644 index 0000000000..41355d72ae --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** 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 "qwasmeventdispatcher.h" + +#include + +#include + +class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate +{ + +}; + +QWasmEventDispatcher *g_htmlEventDispatcher; + +QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent) + : QUnixEventDispatcherQPA(parent) +{ + + g_htmlEventDispatcher = this; +} + +QWasmEventDispatcher::~QWasmEventDispatcher() +{ + g_htmlEventDispatcher = nullptr; +} + +bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function callback) +{ + if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop) + return false; + + g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback); + emscripten_resume_main_loop(); + return true; +} + +void QWasmEventDispatcher::maintainTimers() +{ + if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop) + return; + + g_htmlEventDispatcher->doMaintainTimers(); +} + +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(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(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(eventDispatcher); + that->m_currentTargetTime = std::numeric_limits::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; +} diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.h b/src/plugins/platforms/wasm/qwasmeventdispatcher.h new file mode 100644 index 0000000000..5300b3de73 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 QWASMEVENTDISPATCHER_H +#define QWASMEVENTDISPATCHER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventDispatcherPrivate; + +class QWasmEventDispatcher : public QUnixEventDispatcherQPA +{ + Q_DECLARE_PRIVATE(QWasmEventDispatcher) +public: + explicit QWasmEventDispatcher(QObject *parent = nullptr); + ~QWasmEventDispatcher(); + + static bool registerRequestUpdateCallback(std::function callback); + static void maintainTimers(); + +protected: + bool processEvents(QEventLoop::ProcessEventsFlags flags) override; + void doMaintainTimers(); + +private: + bool m_hasMainLoop = false; + bool m_hasZeroTimer = false; + uint64_t m_currentTargetTime = std::numeric_limits::max(); + QVector> m_requestUpdateCallbacks; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp new file mode 100644 index 0000000000..6545eda4e3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp @@ -0,0 +1,522 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +// 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_usePlatformMacCtrlMetaSwitching = false; + +QWasmEventTranslator::QWasmEventTranslator(QObject *parent) + : QObject(parent) + , draggedWindow(nullptr) + , pressedButtons(Qt::NoButton) + , resizeMode(QWasmWindow::ResizeNone) +{ + emscripten_set_keydown_callback(0, (void *)this, 1, &keyboard_cb); + emscripten_set_keyup_callback(0, (void *)this, 1, &keyboard_cb); + + emscripten_set_mousedown_callback(0, (void *)this, 1, &mouse_cb); + emscripten_set_mouseup_callback(0, (void *)this, 1, &mouse_cb); + emscripten_set_mousemove_callback(0, (void *)this, 1, &mouse_cb); + + emscripten_set_focus_callback(0, (void *)this, 1, &focus_cb); + + emscripten_set_wheel_callback(0, (void *)this, 1, &wheel_cb); + + touchDevice = new QTouchDevice; + touchDevice->setType(QTouchDevice::TouchScreen); + touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); + QWindowSystemInterface::registerTouchDevice(touchDevice); + + emscripten_set_touchstart_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchend_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchmove_callback("#canvas", (void *)this, 1, &touchCallback); + emscripten_set_touchcancel_callback("#canvas", (void *)this, 1, &touchCallback); + + // The Platform Detect: expand coverage and move as needed + enum Platform { + GenericPlatform, + MacOSPlatform + }; + Platform platform = + Platform(EM_ASM_INT("if (navigator.platform.includes(\"Mac\")) return 1; return 0;")); + + g_usePlatformMacCtrlMetaSwitching = (platform == MacOSPlatform); +} + +template +QFlags QWasmEventTranslator::translatKeyModifier(const Event *event) +{ + QFlags keyModifier = Qt::NoModifier; + if (event->shiftKey) + keyModifier |= Qt::ShiftModifier; + if (event->ctrlKey) { + if (g_usePlatformMacCtrlMetaSwitching) + keyModifier |= Qt::MetaModifier; + else + keyModifier |= Qt::ControlModifier; + } + if (event->altKey) + keyModifier |= Qt::AltModifier; + if (event->metaKey) { + if (g_usePlatformMacCtrlMetaSwitching) + keyModifier |= Qt::ControlModifier; + else + keyModifier |= Qt::MetaModifier; + } + return keyModifier; +} + +QFlags QWasmEventTranslator::translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent) +{ + QFlags keyModifier = translatKeyModifier(keyEvent); + if (keyEvent->location == DOM_KEY_LOCATION_NUMPAD) { + keyModifier |= Qt::KeypadModifier; + } + + return keyModifier; +} + +QFlags QWasmEventTranslator::translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent) +{ + return translatKeyModifier(mouseEvent); +} + +int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) +{ + Q_UNUSED(userData) + + bool alphanumeric; + Qt::Key qtKey = translateEmscriptKey(keyEvent, &alphanumeric); + + QEvent::Type keyType = QEvent::None; + switch (eventType) { + case EMSCRIPTEN_EVENT_KEYPRESS: + case EMSCRIPTEN_EVENT_KEYDOWN: //down + keyType = QEvent::KeyPress; + break; + case EMSCRIPTEN_EVENT_KEYUP: //up + keyType = QEvent::KeyRelease; + break; + default: + break; + }; + + if (keyType == QEvent::None) + return 0; + + QString keyText = alphanumeric ? QString(keyEvent->key) : QString(); + bool accepted = QWindowSystemInterface::handleKeyEvent( + 0, keyType, qtKey, translateKeyboardEventModifier(keyEvent), keyText); + QWasmEventDispatcher::maintainTimers(); + return accepted ? 1 : 0; +} + +Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumeric) +{ + Qt::Key qtKey; + if (outAlphanumeric) + *outAlphanumeric = false; + + switch (emscriptKey->keyCode) { + case KeyMultiply: qtKey = Qt::Key_Asterisk; *outAlphanumeric = true; break; + case KeyAdd: qtKey = Qt::Key_Plus; *outAlphanumeric = true; break; + case KeyMinus: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; + case KeySubtract: qtKey = Qt::Key_Minus; *outAlphanumeric = true; break; + case KeyDecimal: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; + case KeyDivide: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; + case KeyNumPad0: qtKey = Qt::Key_0; *outAlphanumeric = true; break; + case KeyNumPad1: qtKey = Qt::Key_1; *outAlphanumeric = true; break; + case KeyNumPad2: qtKey = Qt::Key_2; *outAlphanumeric = true; break; + case KeyNumPad3: qtKey = Qt::Key_3; *outAlphanumeric = true; break; + case KeyNumPad4: qtKey = Qt::Key_4; *outAlphanumeric = true; break; + case KeyNumPad5: qtKey = Qt::Key_5; *outAlphanumeric = true; break; + case KeyNumPad6: qtKey = Qt::Key_6; *outAlphanumeric = true; break; + case KeyNumPad7: qtKey = Qt::Key_7; *outAlphanumeric = true; break; + case KeyNumPad8: qtKey = Qt::Key_8; *outAlphanumeric = true; break; + case KeyNumPad9: qtKey = Qt::Key_9; *outAlphanumeric = true; break; + case KeyComma: qtKey = Qt::Key_Comma; *outAlphanumeric = true; break; + case KeyPeriod: qtKey = Qt::Key_Period; *outAlphanumeric = true; break; + case KeySlash: qtKey = Qt::Key_Slash; *outAlphanumeric = true; break; + case KeySemiColon: qtKey = Qt::Key_Semicolon; *outAlphanumeric = true; break; + case KeyEquals: qtKey = Qt::Key_Equal; *outAlphanumeric = true; break; + case KeyOpenBracket: qtKey = Qt::Key_BracketLeft; *outAlphanumeric = true; break; + case KeyCloseBracket: qtKey = Qt::Key_BracketRight; *outAlphanumeric = true; break; + case KeyBackSlash: qtKey = Qt::Key_Backslash; *outAlphanumeric = true; break; + case KeyMeta: + Q_FALLTHROUGH(); + case KeyMetaRight: + qtKey = Qt::Key_Meta; + break; + case KeyTab: qtKey = Qt::Key_Tab; break; + case KeyClear: qtKey = Qt::Key_Clear; break; + case KeyBackSpace: qtKey = Qt::Key_Backspace; break; + case KeyEnter: qtKey = Qt::Key_Return; break; + case KeyShift: qtKey = Qt::Key_Shift; break; + case KeyControl: qtKey = Qt::Key_Control; break; + case KeyAlt: qtKey = Qt::Key_Alt; break; + case KeyCapsLock: qtKey = Qt::Key_CapsLock; break; + case KeyEscape: qtKey = Qt::Key_Escape; break; + case KeyPageUp: qtKey = Qt::Key_PageUp; break; + case KeyPageDown: qtKey = Qt::Key_PageDown; break; + case KeyEnd: qtKey = Qt::Key_End; break; + case KeyHome: qtKey = Qt::Key_Home; break; + case KeyLeft: qtKey = Qt::Key_Left; break; + case KeyUp: qtKey = Qt::Key_Up; break; + case KeyRight: qtKey = Qt::Key_Right; break; + case KeyDown: qtKey = Qt::Key_Down; break; + case KeyBrightnessDown: qtKey = Qt::Key_MonBrightnessDown; break; + case KeyBrightnessUp: qtKey = Qt::Key_MonBrightnessUp; break; + case KeyMediaTrackPrevious: qtKey = Qt::Key_MediaPrevious; break; + case KeyMediaPlayPause: qtKey = Qt::Key_MediaTogglePlayPause; break; + case KeyMediaTrackNext: qtKey = Qt::Key_MediaNext; break; + case KeyAudioVolumeMute: qtKey = Qt::Key_VolumeMute; break; + case KeyAudioVolumeDown: qtKey = Qt::Key_VolumeDown; break; + case KeyAudioVolumeUp: qtKey = Qt::Key_VolumeUp; break; + case KeyDelete: qtKey = Qt::Key_Delete; break; + + case KeyF1: qtKey = Qt::Key_F1; break; + case KeyF2: qtKey = Qt::Key_F2; break; + case KeyF3: qtKey = Qt::Key_F3; break; + case KeyF4: qtKey = Qt::Key_F4; break; + case KeyF5: qtKey = Qt::Key_F5; break; + case KeyF6: qtKey = Qt::Key_F6; break; + case KeyF7: qtKey = Qt::Key_F7; break; + case KeyF8: qtKey = Qt::Key_F8; break; + case KeyF9: qtKey = Qt::Key_F9; break; + case KeyF10: qtKey = Qt::Key_F10; break; + case KeyF11: qtKey = Qt::Key_F11; break; + case KeyF12: qtKey = Qt::Key_F12; break; + case 124: qtKey = Qt::Key_F13; break; + case 125: qtKey = Qt::Key_F14; break; + + case KeySpace: + default: + if (outAlphanumeric) + *outAlphanumeric = true; + qtKey = static_cast(emscriptKey->keyCode); + break; + } + + // Handle Mac command key. Using event->keyCode as above is + // no reliable since the codes differ between browsers. + if (qstrncmp(emscriptKey->key, "Meta", 4) == 0) { + qtKey = Qt::Key_Meta; + *outAlphanumeric = false; + } + + if (g_usePlatformMacCtrlMetaSwitching) { + if (qtKey == Qt::Key_Meta) + qtKey = Qt::Key_Control; + else if (qtKey == Qt::Key_Control) + qtKey = Qt::Key_Meta; + } + + 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 = mouseEvent->timestamp; + QPoint point(mouseEvent->canvasX, mouseEvent->canvasY); + + QEvent::Type buttonEventType = QEvent::None; + + Qt::MouseButton button = translateMouseButton(mouseEvent->button); + Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent); + + QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); + QWasmWindow *htmlWindow = static_cast(window2->handle()); + bool onFrame = false; + if (window2 && !window2->geometry().contains(point)) + onFrame = true; + + QPoint localPoint(point.x() - window2->geometry().x(), point.y() - window2->geometry().y()); + + switch (eventType) { + case 5: //down + { + if (window2) + window2->raise(); + + pressedButtons.setFlag(button); + + if (mouseEvent->button == 0) { + pressedWindow = window2; + buttonEventType = QEvent::MouseButtonPress; + if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) && htmlWindow->isPointOnTitle(point)) + draggedWindow = window2; + else if (htmlWindow && htmlWindow->isPointOnResizeRegion(point)) { + draggedWindow = window2; + resizeMode = htmlWindow->resizeModeAtPoint(point); + resizePoint = point; + resizeStartRect = window2->geometry(); + } + } + + htmlWindow->injectMousePressed(localPoint, point, button, modifiers); + break; + } + case 6: //up + { + pressedButtons.setFlag(translateMouseButton(mouseEvent->button), false); + buttonEventType = QEvent::MouseButtonRelease; + QWasmWindow *oldWindow = nullptr; + + if (mouseEvent->button == 0 && pressedWindow) { + oldWindow = static_cast(pressedWindow->handle()); + pressedWindow = nullptr; + } + + + if (mouseEvent->button == 0) { + draggedWindow = nullptr; + resizeMode = QWasmWindow::ResizeNone; + } + + if (oldWindow) + oldWindow->injectMouseReleased(localPoint, point, button, modifiers); + break; + } + case 8://move //drag event + { + buttonEventType = QEvent::MouseMove; + if (resizeMode == QWasmWindow::ResizeNone && draggedWindow) { + draggedWindow->setX(draggedWindow->x() + mouseEvent->movementX); + draggedWindow->setY(draggedWindow->y() + mouseEvent->movementY); + } + + if (resizeMode != QWasmWindow::ResizeNone) { + QPoint delta = QPoint(mouseEvent->canvasX, mouseEvent->canvasY) - resizePoint; + resizeWindow(draggedWindow, resizeMode, resizeStartRect, delta); + } + break; + } + default: + break; + }; + + if (window2 && !onFrame) { + QWindowSystemInterface::handleMouseEvent( + window2, timestamp, localPoint, point, 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) + Q_UNUSED(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; + }; + + Qt::KeyboardModifiers modifiers = translateMouseEventModifier(&mouseEvent); + auto timestamp = mouseEvent.timestamp; + QPoint globalPoint(mouseEvent.canvasX, mouseEvent.canvasY); + + QWindow *window2 = QWasmIntegration::get()->compositor()->windowAt(globalPoint, 5); + + QPoint localPoint(globalPoint.x() - window2->geometry().x(), globalPoint.y() - window2->geometry().y()); + + 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); + return 1; +} + +int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + QList touchPointList; + touchPointList.reserve(touchEvent->numTouches); + QWindow *window2; + + for (int i = 0; i < touchEvent->numTouches; i++) { + + const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; + + QPoint point(touches->canvasX, touches->canvasY); + window2 = QWasmIntegration::get()->compositor()->windowAt(point, 5); + + QWindowSystemInterface::TouchPoint touchPoint; + + auto cX = point.x(); + auto cY = point.y(); + touchPoint.area = QRect(0, 0, 8, 8); + touchPoint.area.moveCenter(QPointF(cX,cY)); // simulate area + + touchPoint.id = touches->identifier; + touchPoint.normalPosition = QPointF(cX / window2->width(), cY / window2->height()); + + switch (eventType) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + touchPoint.state = Qt::TouchPointPressed; + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + touchPoint.state = Qt::TouchPointReleased; + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + touchPoint.state = Qt::TouchPointMoved; + break; + default: + Q_UNREACHABLE(); + } + + touchPointList.append(touchPoint); + } + + QWasmEventTranslator *wasmEventTranslator = (QWasmEventTranslator*)userData; + QFlags keyModifier = translatKeyModifier(touchEvent); + + if (eventType != EMSCRIPTEN_EVENT_TOUCHCANCEL) + QWindowSystemInterface::handleTouchEvent(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, touchPointList, keyModifier); + else + QWindowSystemInterface::handleTouchCancelEvent(window2, wasmEventTranslator->getTimestamp(), wasmEventTranslator->touchDevice, keyModifier); + + QCoreApplication::processEvents(); + return 1; +} + +quint64 QWasmEventTranslator::getTimestamp() +{ + return QDeadlineTimer::current().deadlineNSecs() / 1000; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h new file mode 100644 index 0000000000..11430a57a2 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmeventtranslator.h @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include "qwasmwindow.h" +#include + +QT_BEGIN_NAMESPACE + +class QWindow; + +class QWasmEventTranslator : public QObject +{ + Q_OBJECT + + enum KeyCode { + // numpad + KeyNumPad0 = 0x60, + KeyNumPad1 = 0x61, + KeyNumPad2 = 0x62, + KeyNumPad3 = 0x63, + KeyNumPad4 = 0x64, + KeyNumPad5 = 0x65, + KeyNumPad6 = 0x66, + KeyNumPad7 = 0x67, + KeyNumPad8 = 0x68, + KeyNumPad9 = 0x69, + KeyMultiply = 0x6A, + KeyAdd = 0x6B, + KeySeparator = 0x6C, + KeySubtract = 0x6D, + KeyDecimal = 0x6E, + KeyDivide = 0x6F, + KeyMeta = 0x5B, + KeyMetaRight = 0x5C, + //////// + KeyClear = 0x90, + KeyEnter = 0xD, + KeyBackSpace = 0x08, + KeyCancel = 0x03, + KeyTab = 0x09, + KeyShift = 0x10, + KeyControl = 0x11, + KeyAlt = 0x12, + KeyPause = 0x13, + KeyCapsLock = 0x14, + KeyEscape = 0x1B, + KeySpace = 0x20, + KeyPageUp = 0x21, + KeyPageDown = 0x22, + KeyEnd = 0x23, + KeyHome = 0x24, + KeyLeft = 0x25, + KeyUp = 0x26, + KeyRight = 0x27, + KeyDown = 0x28, + KeyComma = 0xBC, + KeyPeriod = 0xBE, + KeySlash = 0xBF, + KeyZero = 0x30, + KeyOne = 0x31, + KeyTwo = 0x32, + KeyThree = 0x33, + KeyFour = 0x34, + KeyFive = 0x35, + KeySix = 0x36, + KeySeven = 0x37, + KeyEight = 0x38, + KeyNine = 0x39, + KeyBrightnessDown = 0xD8, + KeyBrightnessUp = 0xD9, + KeyMediaTrackPrevious = 0xB1, + KeyMediaPlayPause = 0xB3, + KeyMediaTrackNext = 0xB0, + KeyAudioVolumeMute = 0xAD, + KeyAudioVolumeDown = 0xAE, + KeyAudioVolumeUp = 0xAF, + KeySemiColon = 0xBA, + KeyEquals = 0xBB, + KeyMinus = 0xBD, + KeyA = 0x41, + KeyB = 0x42, + KeyC = 0x43, + KeyD = 0x44, + KeyE = 0x45, + KeyF = 0x46, + KeyG = 0x47, + KeyH = 0x48, + KeyI = 0x49, + KeyJ = 0x4A, + KeyK = 0x4B, + KeyL = 0x4C, + KeyM = 0x4D, + KeyN = 0x4E, + KeyO = 0x4F, + KeyP = 0x50, + KeyQ = 0x51, + KeyR = 0x52, + KeyS = 0x53, + KeyT = 0x54, + KeyU = 0x55, + KeyV = 0x56, + KeyW = 0x57, + KeyX = 0x58, + KeyY = 0x59, + KeyZ = 0x5A, + KeyOpenBracket = 0xDB, + KeyBackSlash = 0xDC, + KeyCloseBracket = 0xDD, + KeyF1 = 0x70, + KeyF2 = 0x71, + KeyF3 = 0x72, + KeyF4 = 0x73, + KeyF5 = 0x74, + KeyF6 = 0x75, + KeyF7 = 0x76, + KeyF8 = 0x77, + KeyF9 = 0x78, + KeyF10 = 0x79, + KeyF11 = 0x7A, + KeyF12 = 0x7B, + KeyDelete = 0x2E, + KeyNumLock = 0x90, + KeyScrollLock = 0x91, + KeyPrintScreen = 0x9A, + KeyInsert = 0x9B, + KeyHelp = 0x9C, + KeyBackQuote = 0xC0, + KeyQuote = 0xDE, + KeyFinal = 0x18, + KeyConvert = 0x1C, + KeyNonConvert = 0x1D, + KeyAccept = 0x1E, + KeyModeChange = 0x1F, + KeyKana = 0x15, + KeyKanji = 0x19, + KeyUndefined = 0x0 + }; + +public: + + explicit QWasmEventTranslator(QObject *parent = 0); + + 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(); + +Q_SIGNALS: + void getWindowAt(const QPoint &point, QWindow **window); +private: + static Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey, bool *outAlphanumretic); + template + static QFlags translatKeyModifier(const Event *event); + static QFlags translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent); + static QFlags translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent); + static Qt::MouseButton translateMouseButton(unsigned short button); + + void processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent); + +private: + QWindow *draggedWindow; + QWindow *pressedWindow; + Qt::MouseButtons pressedButtons; + + QWasmWindow::ResizeMode resizeMode; + QPoint resizePoint; + QRect resizeStartRect; + QTouchDevice *touchDevice; + quint64 getTimestamp(); +}; + +QT_END_NAMESPACE +#endif // QWASMEVENTTRANSLATOR_H diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp new file mode 100644 index 0000000000..0c72dfddc4 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 "qwasmfontdatabase.h" + +#include + +QT_BEGIN_NAMESPACE + +void QWasmFontDatabase::populateFontDatabase() +{ + // Load font file from resources. Currently + // all fonts needs to be bundled with the nexe + // as Qt resources. + QStringList fontFileNames = QStringList() << QStringLiteral(":/fonts/Vera.ttf") + << QStringLiteral(":/fonts/DejaVuSans.ttf"); + + foreach (const QString &fontFileName, fontFileNames) { + QFile theFont(fontFileName); + if (!theFont.open(QIODevice::ReadOnly)) + break; + + QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1()); + } +} + +QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle) +{ + return QFreeTypeFontDatabase::fontEngine(fontDef, handle); +} + +QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, + QFont::StyleHint styleHint, + QChar::Script script) const +{ + QStringList fallbacks + = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script); + + // Add the vera.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); + + return fallbacks; +} + +QStringList QWasmFontDatabase::addApplicationFont(const QByteArray &fontData, + const QString &fileName) +{ + return QFreeTypeFontDatabase::addApplicationFont(fontData, fileName); +} + +void QWasmFontDatabase::releaseHandle(void *handle) +{ + QFreeTypeFontDatabase::releaseHandle(handle); +} + + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h new file mode 100644 index 0000000000..891f12859e --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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 QWASMFONTDATABASE_H +#define QWASMFONTDATABASE_H + +#include + +QT_BEGIN_NAMESPACE + +class QWasmFontDatabase : public QFreeTypeFontDatabase +{ +public: + void populateFontDatabase() override; + QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override; + QStringList fallbacksForFamily(const QString &family, QFont::Style style, + QFont::StyleHint styleHint, + QChar::Script script) const override; + QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName) override; + void releaseHandle(void *handle) override; +}; +QT_END_NAMESPACE +#endif diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp new file mode 100644 index 0000000000..accc1fd2fe --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** 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 "qwasmintegration.h" +#include "qwasmeventtranslator.h" +#include "qwasmeventdispatcher.h" +#include "qwasmcompositor.h" +#include "qwasmopenglcontext.h" +#include "qwasmtheme.h" + +#include "qwasmwindow.h" +#ifndef QT_NO_OPENGL +# include "qwasmbackingstore.h" +#endif +#include "qwasmfontdatabase.h" +#if defined(Q_OS_UNIX) +#include +#endif +#include +#include +#include +#include + +#include + +// this is where EGL headers are pulled in, make sure it is last +#include "qwasmscreen.h" + +using namespace emscripten; +QT_BEGIN_NAMESPACE + +void browserBeforeUnload() +{ + QWasmIntegration::QWasmBrowserExit(); +} + +EMSCRIPTEN_BINDINGS(my_module) +{ + function("browserBeforeUnload", &browserBeforeUnload); +} + +static QWasmIntegration *globalHtml5Integration; +QWasmIntegration *QWasmIntegration::get() { return globalHtml5Integration; } + +QWasmIntegration::QWasmIntegration() + : m_fontDb(nullptr), + m_compositor(new QWasmCompositor), + m_screen(new QWasmScreen(m_compositor)), + m_eventDispatcher(nullptr) +{ + + globalHtml5Integration = this; + + updateQScreenAndCanvasRenderSize(); + screenAdded(m_screen); + emscripten_set_resize_callback(0, (void *)this, 1, uiEvent_cb); + + m_eventTranslator = new QWasmEventTranslator; + + EM_ASM(// exit app if browser closes + window.onbeforeunload = function () { + Module.browserBeforeUnload(); + }; + ); +} + +QWasmIntegration::~QWasmIntegration() +{ + delete m_compositor; + destroyScreen(m_screen); + delete m_fontDb; + delete m_eventTranslator; +} + +void QWasmIntegration::QWasmBrowserExit() +{ + QCoreApplication *app = QCoreApplication::instance(); + app->quit(); +} + +bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const +{ + switch (cap) { + case ThreadedPixmaps: return true; + case OpenGL: return true; + case ThreadedOpenGL: return true; + case RasterGLSurface: return true; + case MultipleWindows: return true; + case WindowManagement: return true; + default: return QPlatformIntegration::hasCapability(cap); + } +} + +QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const +{ + return new QWasmWindow(window, m_compositor, m_backingStores.value(window)); +} + +QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const +{ +#ifndef QT_NO_OPENGL + QWasmBackingStore *backingStore = new QWasmBackingStore(m_compositor, window); + m_backingStores.insert(window, backingStore); + return backingStore; +#else + return nullptr; +#endif +} + +#ifndef QT_NO_OPENGL +QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const +{ + return new QWasmOpenGLContext(context->format()); +} +#endif + +QPlatformFontDatabase *QWasmIntegration::fontDatabase() const +{ + if (m_fontDb == nullptr) + m_fontDb = new QWasmFontDatabase; + + return m_fontDb; +} + +QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const +{ + return new QWasmEventDispatcher; +} + +QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const +{ + return QPlatformIntegration::styleHint(hint); +} + +QStringList QWasmIntegration::themeNames() const +{ + return QStringList() << QLatin1String("webassembly"); +} + +QPlatformTheme *QWasmIntegration::createPlatformTheme(const QString &name) const +{ + if (name == QLatin1String("webassembly")) + return new QWasmTheme; + return QPlatformIntegration::createPlatformTheme(name); +} + +int QWasmIntegration::uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData) +{ + Q_UNUSED(e) + Q_UNUSED(userData) + + if (eventType == EMSCRIPTEN_EVENT_RESIZE) { + // This resize event is called when the HTML window is resized. Depending + // on the page layout the the canvas might also have been resized, so we + // update the Qt screen size (and canvas render size). + updateQScreenAndCanvasRenderSize(); + } + + return 0; +} + +static void set_canvas_size(double width, double height) +{ + EM_ASM_({ + var canvas = Module.canvas; + canvas.width = $0; + canvas.height = $1; + }, width, height); +} + +void QWasmIntegration::updateQScreenAndCanvasRenderSize() +{ + // The HTML canvas has two sizes: the CSS size and the canvas render size. + // The CSS size is determined according to standard CSS rules, while the + // render size is set using the "width" and "height" attributes. The render + // 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. + + double css_width; + double css_height; + emscripten_get_element_css_size(0, &css_width, &css_height); + QSizeF cssSize(css_width, css_height); + + QWasmScreen *screen = QWasmIntegration::get()->m_screen; + QSizeF canvasSize = cssSize * screen->devicePixelRatio(); + + set_canvas_size(canvasSize.width(), canvasSize.height()); + screen->setGeometry(QRect(QPoint(0, 0), cssSize.toSize())); + QWasmIntegration::get()->m_compositor->redrawWindowContent(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h new file mode 100644 index 0000000000..ebc3d9d431 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmintegration.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QWASMINTEGRATION_H +#define QWASMINTEGRATION_H + +#include "qwasmwindow.h" + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventTranslator; +class QWasmFontDatabase; +class QWasmWindow; +class QWasmEventDispatcher; +class QWasmScreen; +class QWasmCompositor; +class QWasmBackingStore; + +class QWasmIntegration : public QObject, public QPlatformIntegration +{ + Q_OBJECT +public: + QWasmIntegration(); + ~QWasmIntegration(); + + bool hasCapability(QPlatformIntegration::Capability cap) const override; + QPlatformWindow *createPlatformWindow(QWindow *window) const override; + QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override; +#ifndef QT_NO_OPENGL + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override; +#endif + QPlatformFontDatabase *fontDatabase() const override; + QAbstractEventDispatcher *createEventDispatcher() const override; + QVariant styleHint(QPlatformIntegration::StyleHint hint) const override; + QStringList themeNames() const override; + QPlatformTheme *createPlatformTheme(const QString &name) const override; + + static QWasmIntegration *get(); + QWasmScreen *screen() { return m_screen; } + QWasmCompositor *compositor() { return m_compositor; } + QWasmEventTranslator *eventTranslator() { return m_eventTranslator; } + + static void QWasmBrowserExit(); + static void updateQScreenAndCanvasRenderSize(); + +private: + mutable QWasmFontDatabase *m_fontDb; + QWasmCompositor *m_compositor; + mutable QWasmScreen *m_screen; + mutable QWasmEventTranslator *m_eventTranslator; + mutable QWasmEventDispatcher *m_eventDispatcher; + static int uiEvent_cb(int eventType, const EmscriptenUiEvent *e, void *userData); + mutable QHash m_backingStores; +}; + +QT_END_NAMESPACE + +#endif // QWASMINTEGRATION_H diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp new file mode 100644 index 0000000000..73af3d1878 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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 "qwasmopenglcontext.h" + +#include + +QT_BEGIN_NAMESPACE + +QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format) + : m_requestedFormat(format) +{ + m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES); +} + +QWasmOpenGLContext::~QWasmOpenGLContext() +{ + if (m_context) + emscripten_webgl_destroy_context(m_context); +} + +void QWasmOpenGLContext::maybeRecreateEmscriptenContext(QPlatformSurface *surface) +{ + // Native emscripten contexts are tied to a single surface. Recreate + // the context if the surface is changed. + if (surface != m_surface) { + m_surface = surface; + + // Destroy existing context + if (m_context) + emscripten_webgl_destroy_context(m_context); + + // Create new context + const char *canvasId = 0; // (use default canvas) FIXME: get the actual canvas from the surface. + m_context = createEmscriptenContext(canvasId, m_requestedFormat); + + // Register context-lost callback. + auto callback = [](int eventType, const void *reserved, void *userData) -> EM_BOOL + { + Q_UNUSED(eventType); + Q_UNUSED(reserved); + // The application may get contex-lost if e.g. moved to the background. Set + // m_contextLost which will make isValid() return false. Application code will + // then detect this and recrate the the context, resulting in a new QWasmOpenGLContext + // instance. + reinterpret_cast(userData)->m_contextLost = true; + return true; + }; + bool capture = true; + emscripten_set_webglcontextlost_callback(canvasId, this, capture, callback); + } +} + +EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const char *canvasId, QSurfaceFormat format) +{ + EmscriptenWebGLContextAttributes attributes; + emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes + + attributes.preferLowPowerToHighPerformance = false; + attributes.failIfMajorPerformanceCaveat = false; + attributes.antialias = true; + attributes.enableExtensionsByDefault = true; + + if (format.majorVersion() == 3) { + attributes.majorVersion = 2; + } + + // WebGL offers enable/disable control but not size control for these + attributes.alpha = format.alphaBufferSize() > 0; + attributes.depth = format.depthBufferSize() > 0; + attributes.stencil = format.stencilBufferSize() > 0; + + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(canvasId, &attributes); + + return context; +} + +QSurfaceFormat QWasmOpenGLContext::format() const +{ + return m_requestedFormat; +} + +GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const +{ + return QPlatformOpenGLContext::defaultFramebufferObject(surface); +} + +bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface) +{ + maybeRecreateEmscriptenContext(surface); + + return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS; +} + +void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface) +{ + Q_UNUSED(surface); + // No swapbuffers on WebGl +} + +void QWasmOpenGLContext::doneCurrent() +{ + // No doneCurrent on WebGl +} + +bool QWasmOpenGLContext::isSharing() const +{ + return false; +} + +bool QWasmOpenGLContext::isValid() const +{ + return (m_contextLost == false); +} + +QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName) +{ + return reinterpret_cast(eglGetProcAddress(procName)); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h new file mode 100644 index 0000000000..9123100479 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWasmOpenGLContext : public QPlatformOpenGLContext +{ +public: + QWasmOpenGLContext(const QSurfaceFormat &format); + ~QWasmOpenGLContext(); + + QSurfaceFormat format() const override; + void swapBuffers(QPlatformSurface *surface) override; + GLuint defaultFramebufferObject(QPlatformSurface *surface) const override; + bool makeCurrent(QPlatformSurface *surface) override; + void doneCurrent() override; + bool isSharing() const override; + bool isValid() const override; + QFunctionPointer getProcAddress(const char *procName) override; + +private: + void maybeRecreateEmscriptenContext(QPlatformSurface *surface); + static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const char *canvasId, QSurfaceFormat format); + + bool m_contextLost = false; + QSurfaceFormat m_requestedFormat; + QPlatformSurface *m_surface = nullptr; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0; +}; + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp new file mode 100644 index 0000000000..93e9906ffc --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmscreen.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 "qwasmscreen.h" +#include "qwasmwindow.h" +#include "qwasmcompositor.h" + +#include +#ifndef QT_NO_OPENGL +# include +#endif +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +QWasmScreen::QWasmScreen(QWasmCompositor *compositor) + : m_compositor(compositor) + , m_depth(32) + , m_format(QImage::Format_RGB32) +{ + m_compositor->setScreen(this); +} + +QWasmScreen::~QWasmScreen() +{ + +} + +QRect QWasmScreen::geometry() const +{ + return m_geometry; +} + +int QWasmScreen::depth() const +{ + return m_depth; +} + +QImage::Format QWasmScreen::format() const +{ + return m_format; +} + +qreal QWasmScreen::devicePixelRatio() const +{ + // FIXME: The effective device pixel ratio may be different from the + // HTML window dpr if the OpenGL driver/GPU allocates a less than + // full resolution surface. Use emscripten_webgl_get_drawing_buffer_size() + // and compute the dpr instead. + double htmlWindowDpr = EM_ASM_DOUBLE({ + return window.devicePixelRatio; + }); + return qreal(htmlWindowDpr); +} + +QPlatformCursor *QWasmScreen::cursor() const +{ + return const_cast(&m_cursor); +} + +void QWasmScreen::resizeMaximizedWindows() +{ + QPlatformScreen::resizeMaximizedWindows(); +} + +QWindow *QWasmScreen::topWindow() const +{ + return m_compositor->keyWindow(); +} + +QWindow *QWasmScreen::topLevelAt(const QPoint &p) const +{ + return m_compositor->windowAt(p); +} + +void QWasmScreen::invalidateSize() +{ + m_geometry = QRect(); +} + +void QWasmScreen::setGeometry(const QRect &rect) +{ + m_geometry = rect; + QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry()); + resizeMaximizedWindows(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h new file mode 100644 index 0000000000..3891db77bb --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmscreen.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 QWASMSCREEN_H +#define QWASMSCREEN_H + +#include "qwasmcursor.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPlatformOpenGLContext; +class QWasmWindow; +class QWasmBackingStore; +class QWasmCompositor; +class QOpenGLContext; + +class QWasmScreen : public QObject, public QPlatformScreen +{ + Q_OBJECT +public: + + QWasmScreen(QWasmCompositor *compositor); + ~QWasmScreen(); + + QRect geometry() const override; + int depth() const override; + QImage::Format format() const override; + qreal devicePixelRatio() const override; + QPlatformCursor *cursor() const override; + + void resizeMaximizedWindows(); + QWindow *topWindow() const; + QWindow *topLevelAt(const QPoint &p) const override; + + void invalidateSize(); + +public slots: + void setGeometry(const QRect &rect); +protected: + +private: + QWasmCompositor *m_compositor; + + QRect m_geometry = QRect(0, 0, 100, 100); + int m_depth; + QImage::Format m_format; + QWasmCursor m_cursor; +}; + +QT_END_NAMESPACE +#endif // QWASMSCREEN_H diff --git a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h new file mode 100644 index 0000000000..2b5860f42f --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 new file mode 100644 index 0000000000..a7f2db3bd3 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmtheme.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 "qwasmtheme.h" +#include + +QT_BEGIN_NAMESPACE + +QWasmTheme::QWasmTheme() +{ +} + +QWasmTheme::~QWasmTheme() +{ +} + +QVariant QWasmTheme::themeHint(ThemeHint hint) const +{ + if (hint == QPlatformTheme::StyleNames) + return QVariant(QStringList() << QLatin1String("fusion")); + return QPlatformTheme::themeHint(hint); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmtheme.h b/src/plugins/platforms/wasm/qwasmtheme.h new file mode 100644 index 0000000000..e4cc06e049 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmtheme.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 QWASMTHEME_H +#define QWASMTHEME_H + +#include + +QT_BEGIN_NAMESPACE + +class QWasmEventTranslator; +class QWasmFontDatabase; +class QWasmWindow; +class QWasmEventDispatcher; +class QWasmScreen; +class QWasmCompositor; +class QWasmBackingStore; + +class QWasmTheme : public QPlatformTheme +{ +public: + QWasmTheme(); + ~QWasmTheme(); + + QVariant themeHint(ThemeHint hint) const override; +}; + +QT_END_NAMESPACE + +#endif // QWASMTHEME_H diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp new file mode 100644 index 0000000000..0489813929 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include + +#include "qwasmwindow.h" +#include "qwasmscreen.h" +#include "qwasmcompositor.h" +#include "qwasmeventdispatcher.h" + +#include + +Q_GUI_EXPORT int qt_defaultDpiX(); + +QT_BEGIN_NAMESPACE + +QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore) + : QPlatformWindow(w), + m_window(w), + m_compositor(compositor), + m_backingStore(backingStore) +{ + m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface; + static int serialNo = 0; + m_winid = ++serialNo; + qWarning("QWasmWindow %p: %p 0x%x\n", this, w, uint(m_winid)); + + m_compositor->addWindow(this); + + // Pure OpenGL windows draw directly using egl, disable the compositor. + m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface); +} + +QWasmWindow::~QWasmWindow() +{ + m_compositor->removeWindow(this); +} + +void QWasmWindow::initialize() +{ + QRect rect = windowGeometry(); + + QPlatformWindow::setGeometry(rect); + + 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); + } + + setWindowState(window()->windowStates()); + setWindowFlags(window()->flags()); + setWindowTitle(window()->title()); + m_hasTitle = window()->flags().testFlag(Qt::WindowTitleHint) && m_needsCompositor; + + if (window()->isTopLevel()) + setWindowIcon(window()->icon()); + m_normalGeometry = rect; +} + +QWasmScreen *QWasmWindow::platformScreen() const +{ + return static_cast(window()->screen()->handle()); +} + +void QWasmWindow::setGeometry(const QRect &rect) +{ + 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); + + QWindowSystemInterface::flushWindowSystemEvents(); + invalidate(); +} + +void QWasmWindow::setVisible(bool visible) +{ + QRect newGeom; + + if (visible) { + const bool forceFullScreen = !m_needsCompositor;//make gl apps fullscreen for now + + if (forceFullScreen || (m_windowState & Qt::WindowFullScreen)) + newGeom = platformScreen()->geometry(); + else if (m_windowState & Qt::WindowMaximized) + newGeom = platformScreen()->availableGeometry(); + } + QPlatformWindow::setVisible(visible); + + m_compositor->setVisible(this, visible); + + if (!newGeom.isEmpty()) + setGeometry(newGeom); // may or may not generate an expose + + invalidate(); +} + +QMargins QWasmWindow::frameMargins() const +{ + int border = m_hasTitle ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0; + int titleBarHeight = m_hasTitle ? titleHeight() : 0; + + QMargins margins; + margins.setLeft(border); + margins.setRight(border); + margins.setTop(2*border + titleBarHeight); + margins.setBottom(border); + + return margins; +} + +void QWasmWindow::raise() +{ + m_compositor->raise(this); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + invalidate(); +} + +void QWasmWindow::lower() +{ + m_compositor->lower(this); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size())); + invalidate(); +} + +WId QWasmWindow::winId() const +{ + return m_winid; +} + +void QWasmWindow::propagateSizeHints() +{ +// get rid of base class warning +} + +void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global, + Qt::MouseButton button, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local); + Q_UNUSED(mods); + + if (!m_hasTitle || button != Qt::LeftButton) + return; + + 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; + + invalidate(); +} + +void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global, + Qt::MouseButton button, Qt::KeyboardModifiers mods) +{ + Q_UNUSED(local); + Q_UNUSED(mods); + + if (!m_hasTitle || button != Qt::LeftButton) + return; + + if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) + window()->close(); + + 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()); + } + + m_activeControl = QWasmCompositor::SC_None; + + invalidate(); +} + +int QWasmWindow::titleHeight() const +{ + return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.); +} + +int QWasmWindow::borderWidth() const +{ + return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.); +} + +QRegion QWasmWindow::titleGeometry() const +{ + int border = borderWidth(); + + QRegion result(window()->frameGeometry().x() + border, + window()->frameGeometry().y() + border, + window()->frameGeometry().width() - 2*border, + titleHeight()); + + result -= titleControlRegion(); + + return result; +} + +QRegion QWasmWindow::resizeRegion() const +{ + int border = borderWidth(); + QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border)); + result -= window()->frameGeometry().adjusted(border, border, -border, -border); + + return result; +} + +bool QWasmWindow::isPointOnTitle(QPoint point) const +{ + bool ok = titleGeometry().contains(point); + return ok; +} + +bool QWasmWindow::isPointOnResizeRegion(QPoint point) const +{ + 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; + } + + return ResizeNone; +} + +QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl) +{ + QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window); + + QRect r = QWasmCompositor::titlebarRect(options, subControl); + r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y()); + + return r; +} + +QRect QWasmWindow::maxButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton); +} + +QRect QWasmWindow::minButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton); +} + +QRect QWasmWindow::closeButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton); +} + +QRect QWasmWindow::normButtonRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton); +} + +QRect QWasmWindow::sysMenuRect() const +{ + return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu); +} + +QRegion QWasmWindow::titleControlRegion() const +{ + QRegion result; + result += closeButtonRect(); + result += minButtonRect(); + result += maxButtonRect(); + result += sysMenuRect(); + + return result; +} + +void QWasmWindow::invalidate() +{ + m_compositor->requestRedraw(); +} + +QWasmCompositor::SubControls QWasmWindow::activeSubControl() const +{ + return m_activeControl; +} + +void QWasmWindow::setWindowState(Qt::WindowStates states) +{ + 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; +} + +QRect QWasmWindow::normalGeometry() const +{ + return m_normalGeometry; +} + +qreal QWasmWindow::devicePixelRatio() const +{ + return screen()->devicePixelRatio(); +} + +void QWasmWindow::requestUpdate() +{ + QPointer windowPointer(window()); + bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){ + if (windowPointer.isNull()) + return; + + deliverUpdateRequest(); + }); + + if (!registered) + QPlatformWindow::requestUpdate(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h new file mode 100644 index 0000000000..a0c463e796 --- /dev/null +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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 QWASMWINDOW_H +#define QWASMWINDOW_H + +#include "qwasmintegration.h" +#include +#include +#include "qwasmbackingstore.h" +#include "qwasmscreen.h" +#include "qwasmcompositor.h" + +QT_BEGIN_NAMESPACE + +class QWasmCompositor; + +class QWasmWindow : public QPlatformWindow +{ +public: + enum ResizeMode { + ResizeNone, + ResizeTopLeft, + ResizeTop, + ResizeTopRight, + ResizeRight, + ResizeBottomRight, + ResizeBottom, + ResizeBottomLeft, + ResizeLeft + }; + + QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore); + ~QWasmWindow(); + + 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 raise() override; + void lower() override; + QRect normalGeometry() const override; + qreal devicePixelRatio() const override; + void requestUpdate() override; + + 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; + + void setWindowState(Qt::WindowStates state) override; + bool setKeyboardGrabEnabled(bool) override { return false; } + bool setMouseGrabEnabled(bool) override { return false; } + +protected: + void invalidate(); + +protected: + friend class QWasmScreen; + + QWindow* m_window = nullptr; + QWasmCompositor *m_compositor = nullptr; + QWasmBackingStore *m_backingStore = nullptr; + QRect m_normalGeometry {0, 0, 0 ,0}; + + Qt::WindowState m_windowState = Qt::WindowNoState; + QWasmCompositor::SubControls m_activeControl = QWasmCompositor::SC_None; + WId m_winid = 0; + bool m_hasTitle = false; + bool m_needsCompositor = false; +}; +QT_END_NAMESPACE +#endif // QWASMWINDOW_H diff --git a/src/plugins/platforms/wasm/wasm.json b/src/plugins/platforms/wasm/wasm.json new file mode 100644 index 0000000000..6e700e06b9 --- /dev/null +++ b/src/plugins/platforms/wasm/wasm.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "wasm" ] +} diff --git a/src/plugins/platforms/wasm/wasm.pro b/src/plugins/platforms/wasm/wasm.pro new file mode 100644 index 0000000000..f1205702ef --- /dev/null +++ b/src/plugins/platforms/wasm/wasm.pro @@ -0,0 +1,65 @@ +TARGET = wasm +CONFIG += static plugin +QT += \ + core-private gui-private \ + eventdispatcher_support-private fontdatabase_support-private egl_support-private + +# Avoid X11 header collision, use generic EGL native types +DEFINES += QT_EGL_NO_X11 + +SOURCES = \ + main.cpp \ + qwasmintegration.cpp \ + qwasmwindow.cpp \ + qwasmscreen.cpp \ + qwasmfontdatabase.cpp \ + qwasmeventtranslator.cpp \ + qwasmeventdispatcher.cpp \ + qwasmcompositor.cpp \ + qwasmcursor.cpp \ + qwasmopenglcontext.cpp \ + qwasmtheme.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 + +wasmfonts.files = \ + ../../../3rdparty/wasm/Vera.ttf \ + ../../../3rdparty/wasm/DejaVuSans.ttf +wasmfonts.prefix = /fonts +wasmfonts.base = ../../../3rdparty/wasm +RESOURCES += wasmfonts + +qtConfig(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 new file mode 100644 index 0000000000..67bfcdfbdc --- /dev/null +++ b/src/plugins/platforms/wasm/wasm_shell.html @@ -0,0 +1,61 @@ + + + + + + APPNAME + + + +

+
+ + Qt for WebAssembly: APPNAME +
+ +
+
+ + + + + + diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index 9116bf61fe..6b037726a0 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -1113,7 +1113,7 @@ Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path) const QString homePath = QDir::homePath(); #else const QByteArray userName = path.midRef(1, separatorPosition - 1).toLocal8Bit(); -# if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) +# if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(Q_OS_OPENBSD) && !defined(Q_OS_WASM) passwd pw; passwd *tmpPw; char buf[200]; diff --git a/src/widgets/graphicsview/qgraphicsitem_p.h b/src/widgets/graphicsview/qgraphicsitem_p.h index 9fc6c0794a..d586a22544 100644 --- a/src/widgets/graphicsview/qgraphicsitem_p.h +++ b/src/widgets/graphicsview/qgraphicsitem_p.h @@ -500,7 +500,9 @@ public: quint32 filtersDescendantEvents : 1; quint32 sceneTransformTranslateOnly : 1; quint32 notifyBoundingRectChanged : 1; - +#ifdef Q_OS_WASM + unsigned char :0; //this aligns 64bit field for wasm see QTBUG-65259 +#endif // New 32 bits quint32 notifyInvalidated : 1; quint32 mouseSetsFocus : 1; diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp index 51e78ec765..ed0fe0ed91 100644 --- a/src/widgets/kernel/qopenglwidget.cpp +++ b/src/widgets/kernel/qopenglwidget.cpp @@ -906,9 +906,19 @@ void QOpenGLWidgetPrivate::invalidateFbo() const int gl_color_attachment0 = 0x8CE0; // GL_COLOR_ATTACHMENT0 const int gl_depth_attachment = 0x8D00; // GL_DEPTH_ATTACHMENT const int gl_stencil_attachment = 0x8D20; // GL_STENCIL_ATTACHMENT +#ifdef Q_OS_WASM + // webgl does not allow separate depth and stencil attachments + // QTBUG-69913 + const int gl_depth_stencil_attachment = 0x821A; // GL_DEPTH_STENCIL_ATTACHMENT + + const GLenum attachments[] = { + gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment, gl_depth_stencil_attachment + }; +#else const GLenum attachments[] = { gl_color_attachment0, gl_depth_attachment, gl_stencil_attachment }; +#endif f->glDiscardFramebufferEXT(GL_FRAMEBUFFER, sizeof attachments / sizeof *attachments, attachments); } else { f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/util/unicode/main.cpp b/util/unicode/main.cpp index 0f3c28137d..0c3c0b2ee1 100644 --- a/util/unicode/main.cpp +++ b/util/unicode/main.cpp @@ -2473,6 +2473,10 @@ static QByteArray createPropertyInfo() out += ", "; out += QByteArray::number( p.lowerCaseDiff ); out += ", "; + out += "#ifdef Q_OS_WASM \n" +// " unsigned char : 0; //wasm 64 packing trick QTBUG-65259\n" + out += "#endif \n" + out += ", "; // " ushort upperCaseSpecial : 1;\n" // " signed short upperCaseDiff : 15;\n" out += QByteArray::number( p.upperCaseSpecial ); @@ -2497,6 +2501,10 @@ static QByteArray createPropertyInfo() // " ushort nfQuickCheck : 8;\n" out += QByteArray::number( p.nfQuickCheck ); out += ", "; + out += "#ifdef Q_OS_WASM \n" +// " unsigned char : 0; //wasm 64 packing trick QTBUG-65259\n" + out += "#endif \n" + out += ", "; // " ushort graphemeBreakClass : 5; /* 5 used */\n" // " ushort wordBreakClass : 5; /* 5 used */\n" // " ushort sentenceBreakClass : 8; /* 4 used */\n" From b02fe1bfe7bc5daa503dc3a274570aad0d8dca5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 24 Aug 2018 15:41:42 +0200 Subject: [PATCH 18/26] Remove codepaths and checks for unsupported Apple platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We no longer support macOS 10.11, iOS/tvOS 10, or watchOS 3. Change-Id: Ide03d8fac06185ef4162ba75ee54a0adf6916905 Reviewed-by: Morten Johan Sørvig --- mkspecs/features/uikit/default_pre.prf | 2 - src/corelib/global/qlogging.cpp | 3 +- src/corelib/io/qfilesystemengine_unix.cpp | 32 +++----- src/corelib/kernel/qcore_mac_p.h | 79 ++++--------------- .../cocoa/qcocoaapplicationdelegate.mm | 12 ++- .../platforms/cocoa/qcocoabackingstore.mm | 5 -- src/plugins/platforms/cocoa/qnswindow.mm | 33 +++----- src/plugins/platforms/ios/qiosintegration.mm | 6 +- src/plugins/platforms/ios/qiosscreen.mm | 7 +- .../platforms/ios/qiostextresponder.mm | 14 ++-- src/plugins/platforms/ios/quiview.mm | 43 +++------- src/plugins/styles/mac/qmacstyle_mac.mm | 7 -- src/testlib/qappletestlogger.cpp | 68 +++++++--------- .../auto/corelib/kernel/qtimer/tst_qtimer.cpp | 3 +- 14 files changed, 98 insertions(+), 216 deletions(-) diff --git a/mkspecs/features/uikit/default_pre.prf b/mkspecs/features/uikit/default_pre.prf index 6a44a67bca..ea6882fbc8 100644 --- a/mkspecs/features/uikit/default_pre.prf +++ b/mkspecs/features/uikit/default_pre.prf @@ -24,5 +24,3 @@ load(default_pre) !versionAtLeast(QMAKE_XCODE_VERSION, 4.3): \ error("This mkspec requires Xcode 4.3 or later") -ios:shared:!versionAtLeast(QMAKE_IOS_DEPLOYMENT_TARGET, 8.0): \ - QMAKE_IOS_DEPLOYMENT_TARGET = 8.0 diff --git a/src/corelib/global/qlogging.cpp b/src/corelib/global/qlogging.cpp index 2aabaa2ffb..9f47b8eb98 100644 --- a/src/corelib/global/qlogging.cpp +++ b/src/corelib/global/qlogging.cpp @@ -1768,8 +1768,7 @@ static void qDefaultMessageHandler(QtMsgType type, const QMessageLogContext &con # elif defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) handledStderr |= android_default_message_handler(type, context, message); # elif defined(QT_USE_APPLE_UNIFIED_LOGGING) - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) - handledStderr |= AppleUnifiedLogger::messageHandler(type, context, message); + handledStderr |= AppleUnifiedLogger::messageHandler(type, context, message); # elif defined Q_OS_WASM handledStderr |= wasm_default_message_handler(type, context, message); # endif diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index eebf36416e..0159bc4128 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -76,9 +76,7 @@ #endif #if defined(Q_OS_DARWIN) -# if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(101200, 100000, 100000, 30000) -# include -# endif +# include # include // We cannot include (it's an Objective-C header), but // we need these declarations: @@ -1258,20 +1256,18 @@ bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(101200, 100000, 100000, 30000) - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - if (::clonefile(source.nativeFilePath().constData(), - target.nativeFilePath().constData(), 0) == 0) - return true; - error = QSystemError(errno, QSystemError::StandardLibraryError); - return false; - } +#if defined(Q_OS_DARWIN) + if (::clonefile(source.nativeFilePath().constData(), + target.nativeFilePath().constData(), 0) == 0) + return true; + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; #else Q_UNUSED(source); Q_UNUSED(target); -#endif error = QSystemError(ENOSYS, QSystemError::StandardLibraryError); //Function not implemented return false; +#endif } //static @@ -1295,13 +1291,11 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy } #endif #if defined(Q_OS_DARWIN) && defined(RENAME_EXCL) - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0) - return true; - if (errno != ENOTSUP) { - error = QSystemError(errno, QSystemError::StandardLibraryError); - return false; - } + if (renameatx_np(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_EXCL) == 0) + return true; + if (errno != ENOTSUP) { + error = QSystemError(errno, QSystemError::StandardLibraryError); + return false; } #endif diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h index 8f2714aa12..b14a494296 100644 --- a/src/corelib/kernel/qcore_mac_p.h +++ b/src/corelib/kernel/qcore_mac_p.h @@ -59,36 +59,6 @@ // -------------------------------------------------------------------------- -#if !defined(QT_BOOTSTRAPPED) && (QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) || !defined(Q_OS_MACOS)) -#define QT_USE_APPLE_ACTIVITIES - -#if defined(OS_ACTIVITY_OBJECT_API) -#error The file has already been included -#endif - -// We runtime-check all use of the activity APIs, so we can safely build -// with them included, even if the deployment target is macOS 10.11 -#if QT_MACOS_DEPLOYMENT_TARGET_BELOW(__MAC_10_12) -#undef __MAC_OS_X_VERSION_MIN_REQUIRED -#define __MAC_OS_X_VERSION_MIN_REQUIRED __MAC_10_12 -#define DID_OVERRIDE_DEPLOYMENT_TARGET -#endif - -#include -#if !OS_ACTIVITY_OBJECT_API -#error "Expected activity API to be available" -#endif - -#if defined(DID_OVERRIDE_DEPLOYMENT_TARGET) -#undef __MAC_OS_X_VERSION_MIN_REQUIRED -#define __MAC_OS_X_VERSION_MIN_REQUIRED __MAC_10_11 -#undef DID_OVERRIDE_DEPLOYMENT_TARGET -#endif - -#endif - -// -------------------------------------------------------------------------- - #if defined(QT_BOOTSTRAPPED) #include #else @@ -220,41 +190,31 @@ Q_CORE_EXPORT AppleApplication *qt_apple_sharedApplication(); // -------------------------------------------------------------------------- -#if !defined(QT_BOOTSTRAPPED) && (QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) || !defined(Q_OS_MACOS)) +#if !defined(QT_BOOTSTRAPPED) #define QT_USE_APPLE_UNIFIED_LOGGING QT_END_NAMESPACE #include - -// The compiler isn't smart enough to realize that we're calling these functions -// guarded by __builtin_available, so we need to also tag each function with the -// runtime requirements. -#include -#define OS_LOG_AVAILABILITY API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0)) QT_BEGIN_NAMESPACE class Q_CORE_EXPORT AppleUnifiedLogger { public: static bool messageHandler(QtMsgType msgType, const QMessageLogContext &context, const QString &message, - const QString &subsystem = QString()) OS_LOG_AVAILABILITY; + const QString &subsystem = QString()); private: - static os_log_type_t logTypeForMessageType(QtMsgType msgType) OS_LOG_AVAILABILITY; - static os_log_t cachedLog(const QString &subsystem, const QString &category) OS_LOG_AVAILABILITY; + static os_log_type_t logTypeForMessageType(QtMsgType msgType); + static os_log_t cachedLog(const QString &subsystem, const QString &category); }; -#undef OS_LOG_AVAILABILITY - #endif // -------------------------------------------------------------------------- -#if defined(QT_USE_APPLE_ACTIVITIES) +#if !defined(QT_BOOTSTRAPPED) QT_END_NAMESPACE -#include -#define OS_ACTIVITY_AVAILABILITY API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0)) -#define OS_ACTIVITY_AVAILABILITY_CHECK __builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *) +#include QT_BEGIN_NAMESPACE template using QAppleOsType = QAppleRefCounted; @@ -263,7 +223,7 @@ class Q_CORE_EXPORT QAppleLogActivity { public: QAppleLogActivity() : activity(nullptr) {} - QAppleLogActivity(os_activity_t activity) OS_ACTIVITY_AVAILABILITY : activity(activity) {} + QAppleLogActivity(os_activity_t activity) : activity(activity) {} ~QAppleLogActivity() { if (activity) leave(); } QAppleLogActivity(const QAppleLogActivity &) = delete; @@ -284,21 +244,17 @@ public: QAppleLogActivity&& enter() { - if (activity) { - if (OS_ACTIVITY_AVAILABILITY_CHECK) - os_activity_scope_enter(static_cast(*this), &state); - } + if (activity) + os_activity_scope_enter(static_cast(*this), &state); return std::move(*this); } void leave() { - if (activity) { - if (OS_ACTIVITY_AVAILABILITY_CHECK) - os_activity_scope_leave(&state); - } + if (activity) + os_activity_scope_leave(&state); } - operator os_activity_t() OS_ACTIVITY_AVAILABILITY + operator os_activity_t() { return reinterpret_cast(static_cast(activity)); } @@ -312,9 +268,7 @@ private: #define QT_APPLE_LOG_ACTIVITY_CREATE(condition, description, parent) []() { \ if (!(condition)) \ return QAppleLogActivity(); \ - if (OS_ACTIVITY_AVAILABILITY_CHECK) \ - return QAppleLogActivity(os_activity_create(description, parent, OS_ACTIVITY_FLAG_DEFAULT)); \ - return QAppleLogActivity(); \ + return QAppleLogActivity(os_activity_create(description, parent, OS_ACTIVITY_FLAG_DEFAULT)); \ }() #define QT_VA_ARGS_CHOOSE(_1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) N @@ -335,12 +289,7 @@ QT_MAC_WEAK_IMPORT(_os_activity_current); #define QT_APPLE_SCOPED_LOG_ACTIVITY(...) QAppleLogActivity scopedLogActivity = QT_APPLE_LOG_ACTIVITY(__VA_ARGS__).enter(); -#else -// No-ops for macOS 10.11. We don't need to provide QT_APPLE_SCOPED_LOG_ACTIVITY, -// as all the call sites for that are in code that's only built on 10.12 and above. -#define QT_APPLE_LOG_ACTIVITY_WITH_PARENT(...) -#define QT_APPLE_LOG_ACTIVITY(...) -#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE +#endif // !defined(QT_BOOTSTRAPPED) // ------------------------------------------------------------------------- diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 7601fb7e17..221a8b0866 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -267,13 +267,11 @@ QT_USE_NAMESPACE inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { - if (__builtin_available(macOS 10.12, *)) { - // Move the application window to front to avoid launching behind the terminal. - // Ignoring other apps is necessary (we must ignore the terminal), but makes - // Qt apps play slightly less nice with other apps when lanching from Finder - // (See the activateIgnoringOtherApps docs.) - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; - } + // Move the application window to front to avoid launching behind the terminal. + // Ignoring other apps is necessary (we must ignore the terminal), but makes + // Qt apps play slightly less nice with other apps when lanching from Finder + // (See the activateIgnoringOtherApps docs.) + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } } diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 13b4e2ecd1..81a0a7d040 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -67,11 +67,6 @@ QImage::Format QCocoaBackingStore::format() const return QRasterBackingStore::format(); } -#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) -static const NSCompositingOperation NSCompositingOperationCopy = NSCompositeCopy; -static const NSCompositingOperation NSCompositingOperationSourceOver = NSCompositeSourceOver; -#endif - /*! Flushes the given \a region from the specified \a window onto the screen. diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index c959fdd917..1ea49ad253 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -262,29 +262,20 @@ static bool isMouseEvent(NSEvent *ev) NSEnumerator *windowEnumerator = nullptr; NSApplication *application = [NSApplication sharedApplication]; -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) - if (__builtin_available(macOS 10.12, *)) { - // Unfortunately there's no NSWindowListOrderedBackToFront, - // so we have to manually reverse the order using an array. - NSMutableArray *windows = [NSMutableArray new]; - [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack - usingBlock:^(NSWindow *window, BOOL *) { - // For some reason AppKit will give us nil-windows, skip those - if (!window) - return; + // Unfortunately there's no NSWindowListOrderedBackToFront, + // so we have to manually reverse the order using an array. + NSMutableArray *windows = [NSMutableArray new]; + [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *window, BOOL *) { + // For some reason AppKit will give us nil-windows, skip those + if (!window) + return; - [windows addObject:window]; - } - ]; + [windows addObject:window]; + } + ]; - windowEnumerator = windows.reverseObjectEnumerator; - } else -#endif - { - // No way to get ordered list of windows, so fall back to unordered, - // list, which typically corresponds to window creation order. - windowEnumerator = application.windows.objectEnumerator; - } + windowEnumerator = windows.reverseObjectEnumerator; for (NSWindow *window in windowEnumerator) { // We're meddling with normal and floating windows, so leave others alone diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm index 9a5a0ab499..bec8354fcc 100644 --- a/src/plugins/platforms/ios/qiosintegration.mm +++ b/src/plugins/platforms/ios/qiosintegration.mm @@ -115,10 +115,8 @@ void QIOSIntegration::initialize() m_touchDevice = new QTouchDevice; m_touchDevice->setType(QTouchDevice::TouchScreen); QTouchDevice::Capabilities touchCapabilities = QTouchDevice::Position | QTouchDevice::NormalizedPosition; - if (__builtin_available(iOS 9, *)) { - if (mainScreen.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) - touchCapabilities |= QTouchDevice::Pressure; - } + if (mainScreen.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) + touchCapabilities |= QTouchDevice::Pressure; m_touchDevice->setCapabilities(touchCapabilities); QWindowSystemInterface::registerTouchDevice(m_touchDevice); #if QT_CONFIG(tabletevent) diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm index f1121a102b..4f753be21a 100644 --- a/src/plugins/platforms/ios/qiosscreen.mm +++ b/src/plugins/platforms/ios/qiosscreen.mm @@ -456,12 +456,7 @@ qreal QIOSScreen::devicePixelRatio() const qreal QIOSScreen::refreshRate() const { -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 100300, 110000, __WATCHOS_NA) - if (__builtin_available(iOS 10.3, tvOS 11, *)) - return m_uiScreen.maximumFramesPerSecond; -#endif - - return 60.0; + return m_uiScreen.maximumFramesPerSecond; } Qt::ScreenOrientation QIOSScreen::nativeOrientation() const diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm index 91a088ede1..396c769be8 100644 --- a/src/plugins/platforms/ios/qiostextresponder.mm +++ b/src/plugins/platforms/ios/qiostextresponder.mm @@ -244,17 +244,15 @@ self.inputAccessoryView = [[[WrapperView alloc] initWithView:accessoryView] autorelease]; #ifndef Q_OS_TVOS - if (__builtin_available(iOS 9, *)) { - if (platformData.value(kImePlatformDataHideShortcutsBar).toBool()) { - // According to the docs, leadingBarButtonGroups/trailingBarButtonGroups should be set to nil to hide the shortcuts bar. - // However, starting with iOS 10, the API has been surrounded with NS_ASSUME_NONNULL, which contradicts this and causes - // compiler warnings. Still it is the way to go to really hide the space reserved for that. + if (platformData.value(kImePlatformDataHideShortcutsBar).toBool()) { + // According to the docs, leadingBarButtonGroups/trailingBarButtonGroups should be set to nil to hide the shortcuts bar. + // However, starting with iOS 10, the API has been surrounded with NS_ASSUME_NONNULL, which contradicts this and causes + // compiler warnings. Still it is the way to go to really hide the space reserved for that. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - self.inputAssistantItem.leadingBarButtonGroups = nil; - self.inputAssistantItem.trailingBarButtonGroups = nil; + self.inputAssistantItem.leadingBarButtonGroups = nil; + self.inputAssistantItem.trailingBarButtonGroups = nil; #pragma clang diagnostic pop - } } #endif diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index de60bd7c36..e64c05d099 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -128,21 +128,17 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") self.layer.borderWidth = 1.0; } -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 110000, 110000, __WATCHOS_NA) if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) { - if (__builtin_available(iOS 11, tvOS 11, *)) { - UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero]; - [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]]; - [self addSubview:safeAreaOverlay]; + UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero]; + [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]]; + [self addSubview:safeAreaOverlay]; - safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO; - [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; - [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES; - [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES; - [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; - } + safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO; + [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; + [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES; + [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES; + [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; } -#endif } return self; @@ -355,12 +351,10 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") QTouchDevice *touchDevice = QIOSIntegration::instance()->touchDevice(); QTouchDevice::Capabilities touchCapabilities = touchDevice->capabilities(); - if (__builtin_available(iOS 9, *)) { - if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) - touchCapabilities |= QTouchDevice::Pressure; - else - touchCapabilities &= ~QTouchDevice::Pressure; - } + if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) + touchCapabilities |= QTouchDevice::Pressure; + else + touchCapabilities &= ~QTouchDevice::Pressure; touchDevice->setCapabilities(touchCapabilities); } @@ -668,18 +662,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") - (UIEdgeInsets)qt_safeAreaInsets { -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_NA, 110000, 110000, __WATCHOS_NA) - if (__builtin_available(iOS 11, tvOS 11, *)) - return self.safeAreaInsets; -#endif - - // Fallback for iOS < 11 - UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero; - CGPoint topInset = [self convertPoint:CGPointMake(0, self.viewController.topLayoutGuide.length) fromView:nil]; - CGPoint bottomInset = [self convertPoint:CGPointMake(0, self.viewController.bottomLayoutGuide.length) fromView:nil]; - safeAreaInsets.top = topInset.y; - safeAreaInsets.bottom = bottomInset.y; - return safeAreaInsets; + return self.safeAreaInsets; } @end diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index dd7959f3cf..dbf8a0c2e8 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -1735,17 +1735,10 @@ NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const } Q_UNREACHABLE(); } (); -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) const auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; -#else - const auto styleMask = NSTitledWindowMask - | NSClosableWindowMask - | NSMiniaturizableWindowMask - | NSResizableWindowMask; -#endif bv = [NSWindow standardWindowButton:button forStyleMask:styleMask]; [bv retain]; break; diff --git a/src/testlib/qappletestlogger.cpp b/src/testlib/qappletestlogger.cpp index e970f008d2..2c1005ad80 100644 --- a/src/testlib/qappletestlogger.cpp +++ b/src/testlib/qappletestlogger.cpp @@ -52,10 +52,7 @@ bool QAppleTestLogger::debugLoggingEnabled() // Debug-level messages are only captured in memory when debug logging is // enabled through a configuration change, which can happen automatically // when running inside Xcode, or with the Console application open. - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) - return os_log_type_enabled(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG); - - return false; + return os_log_type_enabled(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG); } QAppleTestLogger::QAppleTestLogger(QAbstractTestLogger *logger) @@ -71,15 +68,13 @@ void QAppleTestLogger::enterTestFunction(const char *function) // Re-create activity each time testFunctionActivity = QT_APPLE_LOG_ACTIVITY("Running test function").enter(); - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - QTestCharBuffer testIdentifier; - QTestPrivate::generateTestIdentifier(&testIdentifier); - QString identifier = QString::fromLatin1(testIdentifier.data()); - QMessageLogContext context(nullptr, 0, nullptr, "qt.test.enter"); - QString message = identifier; - if (AppleUnifiedLogger::messageHandler(QtDebugMsg, context, message, identifier)) - return; // AUL already printed to stderr - } + QTestCharBuffer testIdentifier; + QTestPrivate::generateTestIdentifier(&testIdentifier); + QString identifier = QString::fromLatin1(testIdentifier.data()); + QMessageLogContext context(nullptr, 0, nullptr, "qt.test.enter"); + QString message = identifier; + if (AppleUnifiedLogger::messageHandler(QtDebugMsg, context, message, identifier)) + return; // AUL already printed to stderr m_logger->enterTestFunction(function); } @@ -113,41 +108,38 @@ static IncidentClassification incidentTypeToClassification(QAbstractTestLogger:: void QAppleTestLogger::addIncident(IncidentTypes type, const char *description, const char *file, int line) { - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - IncidentClassification incidentClassification = incidentTypeToClassification(type); - QTestCharBuffer category; - QTest::qt_asprintf(&category, "qt.test.%s", incidentClassification.second); - QMessageLogContext context(file, line, /* function = */ nullptr, category.data()); + IncidentClassification incidentClassification = incidentTypeToClassification(type); - QTestCharBuffer subsystemBuffer; - // It would be nice to have the data tag as part of the subsystem too, but that - // will for some tests results in hundreds of thousands of log objects being - // created, so we limit the subsystem to test functions, which we can hope - // are reasonably limited. - generateTestIdentifier(&subsystemBuffer, TestObject | TestFunction); - QString subsystem = QString::fromLatin1(subsystemBuffer.data()); + QTestCharBuffer category; + QTest::qt_asprintf(&category, "qt.test.%s", incidentClassification.second); + QMessageLogContext context(file, line, /* function = */ nullptr, category.data()); - // We still want the full identifier as part of the message though - QTestCharBuffer testIdentifier; - generateTestIdentifier(&testIdentifier); - QString message = QString::fromLatin1(testIdentifier.data()); - if (qstrlen(description)) - message += QLatin1Char('\n') % QString::fromLatin1(description); + QTestCharBuffer subsystemBuffer; + // It would be nice to have the data tag as part of the subsystem too, but that + // will for some tests results in hundreds of thousands of log objects being + // created, so we limit the subsystem to test functions, which we can hope + // are reasonably limited. + generateTestIdentifier(&subsystemBuffer, TestObject | TestFunction); + QString subsystem = QString::fromLatin1(subsystemBuffer.data()); - if (AppleUnifiedLogger::messageHandler(incidentClassification.first, context, message, subsystem)) - return; // AUL already printed to stderr - } + // We still want the full identifier as part of the message though + QTestCharBuffer testIdentifier; + generateTestIdentifier(&testIdentifier); + QString message = QString::fromLatin1(testIdentifier.data()); + if (qstrlen(description)) + message += QLatin1Char('\n') % QString::fromLatin1(description); + + if (AppleUnifiedLogger::messageHandler(incidentClassification.first, context, message, subsystem)) + return; // AUL already printed to stderr m_logger->addIncident(type, description, file, line); } void QAppleTestLogger::addMessage(QtMsgType type, const QMessageLogContext &context, const QString &message) { - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - if (AppleUnifiedLogger::messageHandler(type, context, message)) - return; // AUL already printed to stderr - } + if (AppleUnifiedLogger::messageHandler(type, context, message)) + return; // AUL already printed to stderr m_logger->addMessage(type, context, message); } diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp index f004ec7856..927ba49f83 100644 --- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp +++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp @@ -456,8 +456,7 @@ void tst_QTimer::moveToThread() #if defined(Q_OS_WIN32) QSKIP("Does not work reliably on Windows :("); #elif defined(Q_OS_MACOS) - if (__builtin_available(macOS 10.12, *)) - QSKIP("Does not work reliably on macOS 10.12 (QTBUG-59679)"); + QSKIP("Does not work reliably on macOS 10.12+ (QTBUG-59679)"); #endif QTimer ti1; QTimer ti2; From 556beea4c373395e16aa00ae7e5e5db8e6f46468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 24 Aug 2018 16:51:23 +0200 Subject: [PATCH 19/26] macOS: Update all deprecated enums to their 10.12+ equivalents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I3034258da95c9c70eb6758db92967f438617f6e9 Reviewed-by: Morten Johan Sørvig --- .../platforms/cocoa/qcocoaapplication.mm | 6 +- .../platforms/cocoa/qcocoaeventdispatcher.mm | 46 +++++++-------- .../platforms/cocoa/qcocoafiledialoghelper.mm | 4 +- src/plugins/platforms/cocoa/qcocoahelpers.mm | 30 +++++----- src/plugins/platforms/cocoa/qcocoamenu.mm | 2 +- src/plugins/platforms/cocoa/qcocoamenuitem.mm | 10 ++-- .../platforms/cocoa/qcocoamenuloader.mm | 2 +- src/plugins/platforms/cocoa/qcocoansmenu.mm | 3 +- src/plugins/platforms/cocoa/qcocoawindow.mm | 58 ++++++++++--------- src/plugins/platforms/cocoa/qnsview_keys.mm | 22 +++---- src/plugins/platforms/cocoa/qnsview_mouse.mm | 29 ++++------ src/plugins/platforms/cocoa/qnsview_tablet.mm | 12 ++-- src/plugins/platforms/cocoa/qnswindow.mm | 24 ++++---- src/plugins/styles/mac/qmacstyle_mac.mm | 20 +++---- 14 files changed, 133 insertions(+), 135 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm index 11f3bd0384..340191622a 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm @@ -116,8 +116,8 @@ static bool qt_filterEvent(NSEvent *event) filterNativeEvent(q_macLocalEventType, static_cast(event), nullptr)) return true; - if ([event type] == NSApplicationDefined) { - switch (static_cast([event subtype])) { + if (event.type == NSEventTypeApplicationDefined) { + switch (static_cast(event.subtype)) { case QtCocoaEventSubTypePostMessage: qt_sendPostedMessage(event); return true; @@ -137,7 +137,7 @@ static void qt_maybeSendKeyEquivalentUpEvent(NSEvent *event) // and forward the key event to the key (focus) window. // However, non-Qt windows will not (and should not) get // any special treatment, only QWindow-owned NSWindows. - if (event.type == NSKeyUp && (event.modifierFlags & NSCommandKeyMask)) { + if (event.type == NSEventTypeKeyUp && (event.modifierFlags & NSEventModifierFlagCommand)) { NSWindow *targetWindow = event.window; if ([targetWindow.class conformsToProtocol:@protocol(QNSWindowProtocol)]) [targetWindow sendEvent:event]; diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index 611853642a..2a41498564 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -293,25 +293,25 @@ static bool IsMouseOrKeyEvent( NSEvent* event ) switch( [event type] ) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: // ?? - case NSLeftMouseDragged: - case NSRightMouseDragged: - case NSMouseEntered: - case NSMouseExited: - case NSKeyDown: - case NSKeyUp: - case NSFlagsChanged: // key modifiers changed? - case NSCursorUpdate: // ?? - case NSScrollWheel: - case NSTabletPoint: - case NSTabletProximity: - case NSOtherMouseDown: - case NSOtherMouseUp: - case NSOtherMouseDragged: + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + case NSEventTypeMouseMoved: // ?? + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeMouseEntered: + case NSEventTypeMouseExited: + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + case NSEventTypeFlagsChanged: // key modifiers changed? + case NSEventTypeCursorUpdate: // ?? + case NSEventTypeScrollWheel: + case NSEventTypeTabletPoint: + case NSEventTypeTabletProximity: + case NSEventTypeOtherMouseDown: + case NSEventTypeOtherMouseUp: + case NSEventTypeOtherMouseDragged: #ifndef QT_NO_GESTURES case NSEventTypeGesture: // touch events case NSEventTypeMagnify: @@ -335,7 +335,7 @@ static inline void qt_mac_waitForMoreEvents(NSString *runLoopMode = NSDefaultRun // at least one event occur. Setting 'dequeuing' to 'no' in the following call // causes it to hang under certain circumstances (QTBUG-28283), so we tell it // to dequeue instead, just to repost the event again: - NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantFuture] inMode:runLoopMode dequeue:YES]; @@ -460,7 +460,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // Dispatch all non-user events (but que non-user events up for later). In // this case, we need more control over which events gets dispatched, and // cannot use [NSApp runModalSession:session]: - event = [NSApp nextEventMatchingMask:NSAnyEventMask + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSModalPanelRunLoopMode dequeue: YES]; @@ -479,7 +479,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) } while (!d->interrupt && event); } else do { // INVARIANT: No modal window is executing. - event = [NSApp nextEventMatchingMask:NSAnyEventMask + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSDefaultRunLoopMode dequeue: YES]; @@ -936,7 +936,7 @@ void QCocoaEventDispatcherPrivate::cancelWaitForMoreEvents() // In case the event dispatcher is waiting for more // events somewhere, we post a dummy event to wake it up: QMacAutoReleasePool pool; - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint + [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0. windowNumber:0 context:nil subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; } diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index dcc57daf11..e7243ec250 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -515,8 +515,8 @@ static QString strippedText(QString s) NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } }; mTextField = [[NSTextField alloc] initWithFrame:textRect]; [[mTextField cell] setFont:[NSFont systemFontOfSize: - [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; - [mTextField setAlignment:NSRightTextAlignment]; + [NSFont systemFontSizeForControlSize:NSControlSizeRegular]]]; + [mTextField setAlignment:NSTextAlignmentRight]; [mTextField setEditable:false]; [mTextField setSelectable:false]; [mTextField setBordered:false]; diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index c7ddd8458d..0f5ddfa49a 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -295,12 +295,12 @@ Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) Qt::MouseButton cocoaButton2QtButton(NSEvent *event) { switch (event.type) { - case NSMouseMoved: + case NSEventTypeMouseMoved: return Qt::NoButton; - case NSRightMouseUp: - case NSRightMouseDown: - case NSRightMouseDragged: + case NSEventTypeRightMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseDragged: return Qt::RightButton; default: @@ -318,22 +318,22 @@ Qt::MouseButton cocoaButton2QtButton(NSEvent *event) QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event) { switch (event.type) { - case NSLeftMouseDown: - case NSRightMouseDown: - case NSOtherMouseDown: + case NSEventTypeLeftMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeOtherMouseDown: return QEvent::MouseButtonPress; - case NSLeftMouseUp: - case NSRightMouseUp: - case NSOtherMouseUp: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseUp: + case NSEventTypeOtherMouseUp: return QEvent::MouseButtonRelease; - case NSLeftMouseDragged: - case NSRightMouseDragged: - case NSOtherMouseDragged: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: return QEvent::MouseMove; - case NSMouseMoved: + case NSEventTypeMouseMoved: return QEvent::MouseMove; default: @@ -432,7 +432,7 @@ QT_END_NAMESPACE // FIXME: Not obvious, from Cocoa's documentation, that QString::toNSString() makes a deep copy button.title = (NSString *)cleanTitle.toCFString(); ((NSButtonCell *)button.cell).font = - [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeRegular]]; [self addSubview:button]; return button; } diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 90e90b32b6..7b96fca3f9 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -391,7 +391,7 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, if (view) { // Finally, we need to synthesize an event. - NSEvent *menuEvent = [NSEvent mouseEventWithType:NSRightMouseDown + NSEvent *menuEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:nsPos modifierFlags:0 timestamp:0 diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index e1572e1800..21faa6d985 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -61,13 +61,13 @@ static quint32 constructModifierMask(quint32 accel_key) quint32 ret = 0; const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); if ((accel_key & Qt::CTRL) == Qt::CTRL) - ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask); + ret |= (dontSwap ? NSEventModifierFlagControl : NSEventModifierFlagCommand); if ((accel_key & Qt::META) == Qt::META) - ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask); + ret |= (dontSwap ? NSEventModifierFlagCommand : NSEventModifierFlagControl); if ((accel_key & Qt::ALT) == Qt::ALT) - ret |= NSAlternateKeyMask; + ret |= NSEventModifierFlagOption; if ((accel_key & Qt::SHIFT) == Qt::SHIFT) - ret |= NSShiftKeyMask; + ret |= NSEventModifierFlagShift; return ret; } @@ -343,7 +343,7 @@ NSMenuItem *QCocoaMenuItem::sync() #endif { m_native.keyEquivalent = @""; - m_native.keyEquivalentModifierMask = NSCommandKeyMask; + m_native.keyEquivalentModifierMask = NSEventModifierFlagCommand; } NSImage *img = nil; diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm index 5610c36ea1..da0fc5c6a1 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -155,7 +155,7 @@ action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; hideAllOthersItem.target = self; - hideAllOthersItem.keyEquivalentModifierMask = NSCommandKeyMask | NSAlternateKeyMask; + hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; [appMenu addItem:hideAllOthersItem]; // Show All diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm index 752f38d68d..65b0832d9f 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.mm +++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm @@ -225,7 +225,8 @@ static NSString *qt_mac_removePrivateUnicode(NSString *string) CHECK_MENU_CLASS(menu); // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... - static const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; + static const NSUInteger mask = NSEventModifierFlagShift | NSEventModifierFlagControl + | NSEventModifierFlagCommand | NSEventModifierFlagOption; // Change the private unicode keys to the ones used in setting the "Key Equivalents" NSString *characters = qt_mac_removePrivateUnicode(event.charactersIgnoringModifiers); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 3148501006..2178f3bf23 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -337,9 +337,9 @@ void QCocoaWindow::setVisible(bool visible) // Since this isn't a native popup, the window manager doesn't close the popup when you click outside NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); NSUInteger parentStyleMask = nativeParentWindow.styleMask; - if ((m_resizableTransientParent = (parentStyleMask & NSResizableWindowMask)) - && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) - nativeParentWindow.styleMask &= ~NSResizableWindowMask; + if ((m_resizableTransientParent = (parentStyleMask & NSWindowStyleMaskResizable)) + && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen)) + nativeParentWindow.styleMask &= ~NSWindowStyleMaskResizable; } } @@ -384,7 +384,9 @@ void QCocoaWindow::setVisible(bool visible) ((NSPanel *)m_view.window).worksWhenModal = YES; if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { removeMonitor(); - monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { + NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown + | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved; + monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) { const auto button = cocoaButton2QtButton(e); const auto buttons = currentlyPressedMouseButtons(); const auto eventType = cocoaEvent2QtMouseEvent(e); @@ -446,9 +448,9 @@ void QCocoaWindow::setVisible(bool visible) if (parentCocoaWindow && window()->type() == Qt::Popup) { NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); if (m_resizableTransientParent - && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen)) // A window should not be resizable while a transient popup is open - nativeParentWindow.styleMask |= NSResizableWindowMask; + nativeParentWindow.styleMask |= NSWindowStyleMaskResizable; } } @@ -491,39 +493,39 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type); // Remove zoom button by disabling resize for CustomizeWindowHint windows, except for - // Qt::Tool windows (e.g. dock windows) which should always be resizeable. - const bool resizeable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool); + // Qt::Tool windows (e.g. dock windows) which should always be resizable. + const bool resizable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool); // Select base window type. Note that the value of NSBorderlessWindowMask is 0. - NSUInteger styleMask = (frameless || !resizeable) ? NSBorderlessWindowMask : NSResizableWindowMask; + NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable; if (frameless) { // No further customizations for frameless since there are no window decorations. } else if (flags & Qt::CustomizeWindowHint) { if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; + styleMask |= NSWindowStyleMaskTitled; if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; + styleMask |= NSWindowStyleMaskClosable; if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; + styleMask |= NSWindowStyleMaskMiniaturizable; if (flags & Qt::WindowMaximizeButtonHint) - styleMask |= NSResizableWindowMask; + styleMask |= NSWindowStyleMaskResizable; } else { - styleMask |= NSClosableWindowMask | NSTitledWindowMask; + styleMask |= NSWindowStyleMaskClosable | NSWindowStyleMaskTitled; if (type != Qt::Dialog) - styleMask |= NSMiniaturizableWindowMask; + styleMask |= NSWindowStyleMaskMiniaturizable; } if (type == Qt::Tool) - styleMask |= NSUtilityWindowMask; + styleMask |= NSWindowStyleMaskUtilityWindow; if (m_drawContentBorderGradient) - styleMask |= NSTexturedBackgroundWindowMask; + styleMask |= NSWindowStyleMaskTexturedBackground; // Don't wipe fullscreen state - if (m_view.window.styleMask & NSFullScreenWindowMask) - styleMask |= NSFullScreenWindowMask; + if (m_view.window.styleMask & NSWindowStyleMaskFullScreen) + styleMask |= NSWindowStyleMaskFullScreen; return styleMask; } @@ -630,7 +632,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) const NSWindow *nsWindow = m_view.window; - if (nsWindow.styleMask & NSUtilityWindowMask + if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { qWarning() << window()->type() << "windows can not be made" << newState; handleWindowStateChanged(HandleUnconditionally); @@ -704,14 +706,14 @@ void QCocoaWindow::toggleMaximized() // The NSWindow needs to be resizable, otherwise the window will // not be possible to zoom back to non-zoomed state. - const bool wasResizable = window.styleMask & NSResizableWindowMask; - window.styleMask |= NSResizableWindowMask; + const bool wasResizable = window.styleMask & NSWindowStyleMaskResizable; + window.styleMask |= NSWindowStyleMaskResizable; const id sender = window; [window zoom:sender]; if (!wasResizable) - window.styleMask &= ~NSResizableWindowMask; + window.styleMask &= ~NSWindowStyleMaskResizable; } void QCocoaWindow::toggleFullScreen() @@ -735,13 +737,13 @@ void QCocoaWindow::windowWillEnterFullScreen() // The NSWindow needs to be resizable, otherwise we'll end up with // the normal window geometry, centered in the middle of the screen // on a black background. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSWindowStyleMaskResizable; } bool QCocoaWindow::isTransitioningToFullScreen() const { NSWindow *window = m_view.window; - return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen; + return window.styleMask & NSWindowStyleMaskFullScreen && !window.qt_fullScreen; } void QCocoaWindow::windowDidEnterFullScreen() @@ -765,7 +767,7 @@ void QCocoaWindow::windowWillExitFullScreen() // The NSWindow needs to be resizable, otherwise we'll end up with // a weird zoom animation. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSWindowStyleMaskResizable; } void QCocoaWindow::windowDidExitFullScreen() @@ -1707,7 +1709,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) return; if (!m_drawContentBorderGradient) { - window.styleMask = window.styleMask & ~NSTexturedBackgroundWindowMask; + window.styleMask = window.styleMask & ~NSWindowStyleMaskTexturedBackground; [window.contentView.superview setNeedsDisplay:YES]; window.titlebarAppearsTransparent = NO; return; @@ -1733,7 +1735,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness; - [window setStyleMask:[window styleMask] | NSTexturedBackgroundWindowMask]; + [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground]; window.titlebarAppearsTransparent = YES; [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge]; diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index 57ce6a009f..85c0607265 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -45,15 +45,15 @@ { const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); Qt::KeyboardModifiers qtMods =Qt::NoModifier; - if (modifierFlags & NSShiftKeyMask) + if (modifierFlags & NSEventModifierFlagShift) qtMods |= Qt::ShiftModifier; - if (modifierFlags & NSControlKeyMask) + if (modifierFlags & NSEventModifierFlagShift) qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; - if (modifierFlags & NSAlternateKeyMask) + if (modifierFlags & NSEventModifierFlagOption) qtMods |= Qt::AltModifier; - if (modifierFlags & NSCommandKeyMask) + if (modifierFlags & NSEventModifierFlagCommand) qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; - if (modifierFlags & NSNumericPadKeyMask) + if (modifierFlags & NSEventModifierFlagCommand) qtMods |= Qt::KeypadModifier; return qtMods; } @@ -201,7 +201,7 @@ Q_UNUSED(sender); NSEvent *currentEvent = [NSApp currentEvent]; - if (!currentEvent || currentEvent.type != NSKeyDown) + if (!currentEvent || currentEvent.type != NSEventTypeKeyDown) return; // Handling the key event may recurse back here through interpretKeyEvents @@ -233,11 +233,11 @@ Qt::Key qt_code; }; static qt_mac_enum_mapper modifier_key_symbols[] = { - { NSShiftKeyMask, Qt::Key_Shift }, - { NSControlKeyMask, Qt::Key_Meta }, - { NSCommandKeyMask, Qt::Key_Control }, - { NSAlternateKeyMask, Qt::Key_Alt }, - { NSAlphaShiftKeyMask, Qt::Key_CapsLock }, + { NSEventModifierFlagShift, Qt::Key_Shift }, + { NSEventModifierFlagControl, Qt::Key_Meta }, + { NSEventModifierFlagCommand, Qt::Key_Control }, + { NSEventModifierFlagOption, Qt::Key_Alt }, + { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }, { 0ul, Qt::Key_unknown } }; for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { uint mac_mask = modifier_key_symbols[i].mac_mask; diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index 65bc9f837d..468f26ffb4 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -91,31 +91,26 @@ if (m_buttons != Qt::NoButton) return; - NSEventType ty = [theEvent type]; - switch (ty) { - case NSLeftMouseDown: + switch (theEvent.type) { + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseDragged: m_frameStrutButtons |= Qt::LeftButton; break; - case NSLeftMouseUp: + case NSEventTypeLeftMouseUp: m_frameStrutButtons &= ~Qt::LeftButton; break; - case NSRightMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseDragged: m_frameStrutButtons |= Qt::RightButton; break; - case NSLeftMouseDragged: - m_frameStrutButtons |= Qt::LeftButton; - break; - case NSRightMouseDragged: - m_frameStrutButtons |= Qt::RightButton; - break; - case NSRightMouseUp: + case NSEventTypeRightMouseUp: m_frameStrutButtons &= ~Qt::RightButton; break; - case NSOtherMouseDown: - m_frameStrutButtons |= cocoaButton2QtButton([theEvent buttonNumber]); + case NSEventTypeOtherMouseDown: + m_frameStrutButtons |= cocoaButton2QtButton(theEvent.buttonNumber); break; - case NSOtherMouseUp: - m_frameStrutButtons &= ~cocoaButton2QtButton([theEvent buttonNumber]); + case NSEventTypeOtherMouseUp: + m_frameStrutButtons &= ~cocoaButton2QtButton(theEvent.buttonNumber); default: break; } @@ -581,7 +576,7 @@ // knowing whether or not a NSEventPhaseEnded will be followed by a momentum phase. // The best we can do is to look at the event queue and hope that the system has // had time to emit a momentum phase event. - if ([NSApp nextEventMatchingMask:NSScrollWheelMask untilDate:[NSDate distantPast] + if ([NSApp nextEventMatchingMask:NSEventMaskScrollWheel untilDate:[NSDate distantPast] inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) { Q_ASSERT(pixelDelta.isNull() && angleDelta.isNull()); return; // Ignore this event, as it has a delta of 0,0 diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm index 0b56123955..43b0aa0960 100644 --- a/src/plugins/platforms/cocoa/qnsview_tablet.mm +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -64,7 +64,7 @@ Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) return false; NSEventType eventType = [theEvent type]; - if (eventType != NSTabletPoint && [theEvent subtype] != NSTabletPointEventSubtype) + if (eventType != NSEventTypeTabletPoint && [theEvent subtype] != NSEventSubtypeTabletPoint) return false; // Not a tablet event. ulong timestamp = [theEvent timestamp] * 1000; @@ -82,7 +82,7 @@ Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) } const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); - bool down = (eventType != NSMouseMoved); + bool down = (eventType != NSEventTypeMouseMoved); qreal pressure; if (down) { @@ -182,17 +182,17 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) deviceData.capabilityMask = [theEvent capabilityMask]; switch ([theEvent pointingDeviceType]) { - case NSUnknownPointingDevice: + case NSPointingDeviceTypeUnknown: default: deviceData.pointerType = QTabletEvent::UnknownPointer; break; - case NSPenPointingDevice: + case NSPointingDeviceTypePen: deviceData.pointerType = QTabletEvent::Pen; break; - case NSCursorPointingDevice: + case NSPointingDeviceTypeCursor: deviceData.pointerType = QTabletEvent::Cursor; break; - case NSEraserPointingDevice: + case NSPointingDeviceTypeEraser: deviceData.pointerType = QTabletEvent::Eraser; break; } diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index 1ea49ad253..1b9dd95cbc 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -50,13 +50,13 @@ Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events"); static bool isMouseEvent(NSEvent *ev) { switch ([ev type]) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: - case NSLeftMouseDragged: - case NSRightMouseDragged: + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: return true; default: return false; @@ -186,16 +186,16 @@ static bool isMouseEvent(NSEvent *ev) /*! Borderless windows need a transparent background - Technically windows with NSTexturedBackgroundWindowMask (such - as windows with unified toolbars) need to draw the textured + Technically windows with NSWindowStyleMaskTexturedBackground + (such as windows with unified toolbars) need to draw the textured background of the NSWindow, and can't have a transparent - background, but as NSBorderlessWindowMask is 0, you can't - have a window with NSTexturedBackgroundWindowMask that is + background, but as NSWindowStyleMaskBorderless is 0, you can't + have a window with NSWindowStyleMaskTexturedBackground that is also borderless. */ - (NSColor *)backgroundColor { - return self.styleMask == NSBorderlessWindowMask + return self.styleMask == NSWindowStyleMaskBorderless ? [NSColor clearColor] : qt_objcDynamicSuper(); } diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index dbf8a0c2e8..c70d1729c0 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -394,9 +394,9 @@ static bool setupSlider(NSSlider *slider, const QStyleOptionSlider *sl) const bool ticksAbove = sl->tickPosition == QSlider::TicksAbove; if (sl->orientation == Qt::Horizontal) - slider.tickMarkPosition = ticksAbove ? NSTickMarkAbove : NSTickMarkBelow; + slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionAbove : NSTickMarkPositionBelow; else - slider.tickMarkPosition = ticksAbove ? NSTickMarkLeft : NSTickMarkRight; + slider.tickMarkPosition = ticksAbove ? NSTickMarkPositionLeading : NSTickMarkPositionTrailing; } else { slider.numberOfTickMarks = 0; } @@ -1785,10 +1785,10 @@ NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const auto *ctrl = static_cast(bv); switch (widget.size) { case QStyleHelper::SizeSmall: - ctrl.controlSize = NSSmallControlSize; + ctrl.controlSize = NSControlSizeSmall; break; case QStyleHelper::SizeMini: - ctrl.controlSize = NSMiniControlSize; + ctrl.controlSize = NSControlSizeMini; break; default: break; @@ -1799,10 +1799,10 @@ NSView *QMacStylePrivate::cocoaControl(CocoaControl widget) const pi.indeterminate = (widget.type == ProgressIndicator_Indeterminate); switch (widget.size) { case QStyleHelper::SizeSmall: - pi.controlSize = NSSmallControlSize; + pi.controlSize = NSControlSizeSmall; break; case QStyleHelper::SizeMini: - pi.controlSize = NSMiniControlSize; + pi.controlSize = NSControlSizeMini; break; default: break; @@ -1848,10 +1848,10 @@ NSCell *QMacStylePrivate::cocoaCell(CocoaControl widget) const switch (widget.size) { case QStyleHelper::SizeSmall: - cell.controlSize = NSSmallControlSize; + cell.controlSize = NSControlSizeSmall; break; case QStyleHelper::SizeMini: - cell.controlSize = NSMiniControlSize; + cell.controlSize = NSControlSizeMini; break; default: break; @@ -2191,9 +2191,9 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW ret = 0; break; case PM_TitleBarHeight: { - NSUInteger style = NSTitledWindowMask; + NSUInteger style = NSWindowStyleMaskTitled; if (widget && ((widget->windowFlags() & Qt::Tool) == Qt::Tool)) - style |= NSUtilityWindowMask; + style |= NSWindowStyleMaskUtilityWindow; ret = int([NSWindow frameRectForContentRect:NSZeroRect styleMask:style].size.height); break; } From 917c2de203943328228bdf32ce714f47a6508192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 29 Aug 2018 19:38:33 +0200 Subject: [PATCH 20/26] Disable tst_macnativeevents It's almost completely blacklisted anyways, and the parts that are not are flakey. Change-Id: I4601d751a2963c94c62ec439dd78b874034069a0 Reviewed-by: Liang Qi --- tests/auto/other/other.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/other/other.pro b/tests/auto/other/other.pro index fa09e4d03f..61a464356c 100644 --- a/tests/auto/other/other.pro +++ b/tests/auto/other/other.pro @@ -7,7 +7,7 @@ SUBDIRS=\ lancelot \ languagechange \ macgui \ - macnativeevents \ + #macnativeevents \ macplist \ networkselftest \ qaccessibility \ From 2664e843223e558a781d00191b7dffd962aa5618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Mon, 27 Aug 2018 12:43:50 +0200 Subject: [PATCH 21/26] macOS: Remove support for QSurfaceFormat::StereoBuffers The NSOpenGLPFAStereo attribute was deprecated in macOS 10.12, without any replacement, and adding the attribute to the pixel format results in context creation failure, so we're assuming the feature is no longer supported an disable it wholesale on macOS. Change-Id: I27d9f300fdaff9abe90781e3160b97f8b66121ad Reviewed-by: Laszlo Agocs --- src/plugins/platforms/cocoa/qcocoaglcontext.mm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 4d0fa2e28e..c23636a1a0 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -217,9 +217,6 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface << NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples()); } - if (format.stereo()) - attrs << NSOpenGLPFAStereo; - // Allow rendering on GPUs without a connected display attrs << NSOpenGLPFAAllowOfflineRenderers; @@ -277,6 +274,9 @@ void QCocoaGLContext::updateSurfaceFormat() // Debug contexts not supported on macOS m_format.setOption(QSurfaceFormat::DebugContext, false); + // Nor are stereo buffers (deprecated in macOS 10.12) + m_format.setOption(QSurfaceFormat::StereoBuffers, false); + // ------------------ Query the pixel format ------------------ NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat; @@ -306,8 +306,6 @@ void QCocoaGLContext::updateSurfaceFormat() else m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer); - m_format.setOption(QSurfaceFormat::StereoBuffers, [pixelFormat qt_getAttribute:NSOpenGLPFAStereo]); - // ------------------- Query the context ------------------- m_format.setSwapInterval([m_context qt_getParameter:NSOpenGLCPSwapInterval]); From e59b32362805a39383e28f0003c43098d83164d9 Mon Sep 17 00:00:00 2001 From: Lorn Potter Date: Thu, 5 Jul 2018 16:33:39 +1000 Subject: [PATCH 22/26] wasm: disable RasterGlSurface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QOpenGlWidget and QuickQidget do not work at this time anyway. This also causes gl to be initialized for widget apps, so disabling this will optimize widget apps. We could use Qt::AA_ForceRasterWidgets as well. Task-number: QTBUG-67797 Change-Id: I8f86a9f72e6f3d437a818b4fbe3d6f3658e17d04 Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/wasm/qwasmintegration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp index accc1fd2fe..1be909f0a0 100644 --- a/src/plugins/platforms/wasm/qwasmintegration.cpp +++ b/src/plugins/platforms/wasm/qwasmintegration.cpp @@ -110,7 +110,7 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return true; - case RasterGLSurface: return true; + case RasterGLSurface: return false; // to enable this you need to fix qopenglwidget and quickwidget for wasm case MultipleWindows: return true; case WindowManagement: return true; default: return QPlatformIntegration::hasCapability(cap); From 8ada0633cd58e0608df2e16880f1293286025504 Mon Sep 17 00:00:00 2001 From: Andre de la Rocha Date: Thu, 30 Aug 2018 20:37:21 +0200 Subject: [PATCH 23/26] Windows QPA: Stop synthesizing mouse events from tablet/touch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These fake mouse events should be synthesized by QtGui, not the QPA plugin, when the tablet/touch events are not handled by the application and the Qt::AA_SynthesizeMouseForUnhandledTouchEvents or Qt::AA_SynthesizeMouseForUnhandledTabletEvents flags are set. Task-number: QTBUG-47007 Task-number: QTBUG-60437 Change-Id: I4d6d5e6667d245c45a4eb4f3a94db05cf9772f52 Reviewed-by: Shawn Rutledge Reviewed-by: Romain Pokrzywka Reviewed-by: Jan Arve Sæther Reviewed-by: Friedemann Kleint --- src/gui/kernel/qwindowsysteminterface_p.h | 2 +- .../platforms/windows/qwindowscontext.cpp | 9 +++++-- .../windows/qwindowspointerhandler.cpp | 24 ------------------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/gui/kernel/qwindowsysteminterface_p.h b/src/gui/kernel/qwindowsysteminterface_p.h index c3fb19d21a..492f559f22 100644 --- a/src/gui/kernel/qwindowsysteminterface_p.h +++ b/src/gui/kernel/qwindowsysteminterface_p.h @@ -365,7 +365,7 @@ public: QUrl url; }; - class TabletEvent : public InputEvent { + class Q_GUI_EXPORT TabletEvent : public InputEvent { public: static void handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index aa85b2edbe..03bb1bee48 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -65,6 +65,7 @@ #include #include +#include #include #include #include @@ -329,8 +330,12 @@ bool QWindowsContext::initTouch(unsigned integrationOptions) if (!touchDevice) return false; - if (!(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) - touchDevice->setCapabilities(touchDevice->capabilities() | QTouchDevice::MouseEmulation); + if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) { + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + } else { + if (!(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) + touchDevice->setCapabilities(touchDevice->capabilities() | QTouchDevice::MouseEmulation); + } QWindowSystemInterface::registerTouchDevice(touchDevice); diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp index af9048828f..d8918d1b3d 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -428,21 +428,6 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, QWindowSystemInterface::handleTouchEvent(window, m_touchDevice, touchPoints, QWindowsKeyMapper::queryKeyboardModifiers()); - if (!(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) { - - const QPoint globalPos = QPoint(touchInfo->pointerInfo.ptPixelLocation.x, touchInfo->pointerInfo.ptPixelLocation.y); - const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos); - const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - const Qt::MouseButtons mouseButtons = queryMouseButtons(); - - QEvent::Type eventType; - Qt::MouseButton button; - getMouseEventInfo(msg.message, touchInfo->pointerInfo.ButtonChangeType, globalPos, &eventType, &button); - - QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos, mouseButtons, button, eventType, - keyModifiers, Qt::MouseEventSynthesizedByQt); - } - return true; } @@ -535,15 +520,6 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, pointerId, keyModifiers); - - if (!(QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)) { - QEvent::Type eventType; - Qt::MouseButton button; - getMouseEventInfo(msg.message, penInfo->pointerInfo.ButtonChangeType, globalPos, &eventType, &button); - - QWindowSystemInterface::handleMouseEvent(target, localPos, globalPos, mouseButtons, button, eventType, - keyModifiers, Qt::MouseEventSynthesizedByQt); - } break; } } From 7391662f80470549b9f9c182da43cf433efabdf7 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 18 Jul 2018 12:52:19 -0700 Subject: [PATCH 24/26] Update TinyCBOR Updated to https://github.com/thiagomacieira/tinycbor commit 1286d99bdc664de6acf7c5274702325f5a095a0f Change-Id: I0d3cc366baaa49f3ad28fffd15428e886333bb38 Reviewed-by: Kai Koehne --- src/3rdparty/tinycbor/src/cborinternal_p.h | 69 +++++++++++++++++++ src/3rdparty/tinycbor/src/compilersupport_p.h | 59 ---------------- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/src/3rdparty/tinycbor/src/cborinternal_p.h b/src/3rdparty/tinycbor/src/cborinternal_p.h index 8d77f28466..c5fe63003f 100644 --- a/src/3rdparty/tinycbor/src/cborinternal_p.h +++ b/src/3rdparty/tinycbor/src/cborinternal_p.h @@ -27,6 +27,75 @@ #include "compilersupport_p.h" +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#else +# ifndef CBOR_NO_HALF_FLOAT_TYPE +# define CBOR_NO_HALF_FLOAT_TYPE 1 +# endif +#endif + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +# ifdef __F16C__ +# include +static inline unsigned short encode_half(double val) +{ + return _cvtss_sh((float)val, 3); +} +static inline double decode_half(unsigned short half) +{ + return _cvtsh_ss(half); +} +# else +/* software implementation of float-to-fp16 conversions */ +static inline unsigned short encode_half(double val) +{ + uint64_t v; + int sign, exp, mant; + memcpy(&v, &val, sizeof(v)); + sign = v >> 63 << 15; + exp = (v >> 52) & 0x7ff; + mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return (unsigned short)(sign | ((exp + 15) << 10) | mant); +} + +/* this function was copied & adapted from RFC 7049 Appendix D */ +static inline double decode_half(unsigned short half) +{ + int exp = (half >> 10) & 0x1f; + int mant = half & 0x3ff; + double val; + if (exp == 0) val = ldexp(mant, -24); + else if (exp != 31) val = ldexp(mant + 1024, exp - 25); + else val = mant == 0 ? INFINITY : NAN; + return half & 0x8000 ? -val : val; +} +# endif +#endif /* CBOR_NO_HALF_FLOAT_TYPE */ + #ifndef CBOR_INTERNAL_API # define CBOR_INTERNAL_API #endif diff --git a/src/3rdparty/tinycbor/src/compilersupport_p.h b/src/3rdparty/tinycbor/src/compilersupport_p.h index dc4c4cfd8a..2b9491d34d 100644 --- a/src/3rdparty/tinycbor/src/compilersupport_p.h +++ b/src/3rdparty/tinycbor/src/compilersupport_p.h @@ -36,8 +36,6 @@ #ifndef assert # include #endif -#include -#include #include #include #include @@ -46,10 +44,6 @@ # include #endif -#ifdef __F16C__ -# include -#endif - #if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L || __cpp_static_assert >= 200410 # define cbor_static_assert(x) static_assert(x, #x) #elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) @@ -207,58 +201,5 @@ static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r) #endif } -static inline unsigned short encode_half(double val) -{ -#ifdef __F16C__ - return _cvtss_sh((float)val, 3); -#else - uint64_t v; - int sign, exp, mant; - memcpy(&v, &val, sizeof(v)); - sign = v >> 63 << 15; - exp = (v >> 52) & 0x7ff; - mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ - exp -= 1023; - if (exp == 1024) { - /* infinity or NaN */ - exp = 16; - mant >>= 1; - } else if (exp >= 16) { - /* overflow, as largest number */ - exp = 15; - mant = 1023; - } else if (exp >= -14) { - /* regular normal */ - } else if (exp >= -24) { - /* subnormal */ - mant |= 1024; - mant >>= -(exp + 14); - exp = -15; - } else { - /* underflow, make zero */ - return 0; - } - - /* safe cast here as bit operations above guarantee not to overflow */ - return (unsigned short)(sign | ((exp + 15) << 10) | mant); -#endif -} - -/* this function was copied & adapted from RFC 7049 Appendix D */ -static inline double decode_half(unsigned short half) -{ -#ifdef __F16C__ - return _cvtsh_ss(half); -#else - int exp = (half >> 10) & 0x1f; - int mant = half & 0x3ff; - double val; - if (exp == 0) val = ldexp(mant, -24); - else if (exp != 31) val = ldexp(mant + 1024, exp - 25); - else val = mant == 0 ? INFINITY : NAN; - return half & 0x8000 ? -val : val; -#endif -} - #endif /* COMPILERSUPPORT_H */ From d9766ddc3d525cf08acec4c3483e61d86c9899a8 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 26 Jun 2018 22:46:49 -0700 Subject: [PATCH 25/26] Plugins: store the metadata in CBOR instead of binary JSON In preparation for Qt 6 deprecating the binary JSON format. Also reduces the size of the metadata a little: for the xcb platform plugin, it went down from 264 bytes to 138; for the jpeg image plugin, it went from 320 to 135. I've had to change the signature so older versions of Qt won't try to parse the CBOR data as Binary JSON. Unfortunately, before QJsonDocument could get a chance to reject it, qJsonFromRawLibraryMetaData() needed to allocate memory and that causes crashes with Qt < 5.11.2. Change-Id: Ieb48f7c0dd0e4e0fb35efffd153bee34e16ce347 Reviewed-by: Simon Hausmann --- src/corelib/plugin/plugin.pri | 1 + src/corelib/plugin/qfactoryloader.cpp | 73 ++++++-- src/corelib/plugin/qfactoryloader_p.h | 3 +- src/corelib/plugin/qlibrary.cpp | 44 +++-- src/corelib/plugin/qplugin_p.h | 77 +++++++++ src/corelib/plugin/qpluginloader.cpp | 11 +- src/tools/moc/cbordevice.h | 94 +++++++++++ src/tools/moc/generator.cpp | 156 +++++++++++++----- src/tools/moc/moc.pri | 6 +- .../corelib/plugin/qplugin/tst_qplugin.cpp | 50 +++++- .../corelib/plugin/qplugin/tst_qplugin.pro | 2 +- 11 files changed, 432 insertions(+), 85 deletions(-) create mode 100644 src/corelib/plugin/qplugin_p.h create mode 100644 src/tools/moc/cbordevice.h diff --git a/src/corelib/plugin/plugin.pri b/src/corelib/plugin/plugin.pri index a0e0d76044..13153e8d0a 100644 --- a/src/corelib/plugin/plugin.pri +++ b/src/corelib/plugin/plugin.pri @@ -4,6 +4,7 @@ HEADERS += \ plugin/qfactoryinterface.h \ plugin/qpluginloader.h \ plugin/qplugin.h \ + plugin/qplugin_p.h \ plugin/quuid.h \ plugin/qfactoryloader_p.h diff --git a/src/corelib/plugin/qfactoryloader.cpp b/src/corelib/plugin/qfactoryloader.cpp index 0b35f41ca3..9f877ccda8 100644 --- a/src/corelib/plugin/qfactoryloader.cpp +++ b/src/corelib/plugin/qfactoryloader.cpp @@ -47,9 +47,12 @@ #include #include "qmutex.h" #include "qplugin.h" +#include "qplugin_p.h" #include "qpluginloader.h" #include "private/qobject_p.h" #include "private/qcoreapplication_p.h" +#include "qcbormap.h" +#include "qcborvalue.h" #include "qjsondocument.h" #include "qjsonvalue.h" #include "qjsonobject.h" @@ -64,22 +67,70 @@ static inline int metaDataSignatureLength() return sizeof("QTMETADATA ") - 1; } -QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype sectionSize) +static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QString *errMsg) +{ + if (Q_UNLIKELY(raw[-1] != '!')) { + *errMsg = QStringLiteral("Invalid metadata signature"); + return QJsonDocument(); + } + + QByteArray ba = QByteArray::fromRawData(raw, int(size)); + QCborParserError err; + QCborValue metadata = QCborValue::fromCbor(ba, &err); + + if (err.error != QCborError::NoError) { + *errMsg = QLatin1String("Metadata parsing error: ") + err.error.toString(); + return QJsonDocument(); + } + + if (!metadata.isMap()) { + *errMsg = QStringLiteral("Unexpected metadata contents"); + return QJsonDocument(); + } + + // convert the top-level map integer keys + QJsonObject o; + for (auto it : metadata.toMap()) { + QString key; + if (it.first.isInteger()) { + switch (it.first.toInteger()) { +#define CONVERT_TO_STRING(IntKey, StringKey, Description) \ + case int(IntKey): key = QStringLiteral(StringKey); break; + QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING) +#undef CONVERT_TO_STRING + } + } else { + key = it.first.toString(); + } + + if (!key.isEmpty()) + o.insert(key, it.second.toJsonValue()); + } + return QJsonDocument(o); +} + +QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype sectionSize, QString *errMsg) { raw += metaDataSignatureLength(); sectionSize -= metaDataSignatureLength(); - // the size of the embedded JSON object can be found 8 bytes into the data (see qjson_p.h) - uint size = qFromLittleEndian(raw + 8); - // but the maximum size of binary JSON is 128 MB - size = qMin(size, 128U * 1024 * 1024); - // and it doesn't include the size of the header (8 bytes) - size += 8; - // finally, it can't be bigger than the file or section size - size = qMin(sectionSize, qsizetype(size)); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (Q_UNLIKELY(raw[-1] == ' ')) { + // the size of the embedded JSON object can be found 8 bytes into the data (see qjson_p.h) + uint size = qFromLittleEndian(raw + 8); + // but the maximum size of binary JSON is 128 MB + size = qMin(size, 128U * 1024 * 1024); + // and it doesn't include the size of the header (8 bytes) + size += 8; + // finally, it can't be bigger than the file or section size + size = qMin(sectionSize, qsizetype(size)); - QByteArray json(raw, size); - return QJsonDocument::fromBinaryData(json); + QByteArray json(raw, size); + return QJsonDocument::fromBinaryData(json); + } +#endif + + return jsonFromCborMetaData(raw, sectionSize, errMsg); } class QFactoryLoaderPrivate : public QObjectPrivate diff --git a/src/corelib/plugin/qfactoryloader_p.h b/src/corelib/plugin/qfactoryloader_p.h index fe722999ae..7815ea0b5d 100644 --- a/src/corelib/plugin/qfactoryloader_p.h +++ b/src/corelib/plugin/qfactoryloader_p.h @@ -56,6 +56,7 @@ #include "QtCore/qobject.h" #include "QtCore/qstringlist.h" +#include "QtCore/qcborvalue.h" #include "QtCore/qjsonobject.h" #include "QtCore/qjsondocument.h" #include "QtCore/qmap.h" @@ -66,7 +67,7 @@ QT_BEGIN_NAMESPACE -QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype size); +QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype size, QString *errMsg); class QFactoryLoaderPrivate; class Q_CORE_EXPORT QFactoryLoader : public QObject diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp index 869ef6181f..aa63ed1a6b 100644 --- a/src/corelib/plugin/qlibrary.cpp +++ b/src/corelib/plugin/qlibrary.cpp @@ -268,7 +268,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) */ bool hasMetaData = false; qsizetype pos = 0; - char pattern[] = "qTMETADATA "; + char pattern[] = "qTMETADATA "; pattern[0] = 'Q'; // Ensure the pattern "QTMETADATA" is not found in this library should QPluginLoader ever encounter it. const ulong plen = qstrlen(pattern); #if defined (Q_OF_ELF) && defined(Q_CC_GNU) @@ -314,10 +314,14 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib) bool ret = false; - if (pos >= 0) { - if (hasMetaData) { - const char *data = filedata + pos; - QJsonDocument doc = qJsonFromRawLibraryMetaData(data, qsizetype(fdlen)); + if (pos >= 0 && hasMetaData) { + const char *data = filedata + pos; + QString errMsg; + QJsonDocument doc = qJsonFromRawLibraryMetaData(data, fdlen, &errMsg); + if (doc.isNull()) { + qWarning("Found invalid metadata in lib %s: %s", + qPrintable(library), qPrintable(errMsg)); + } else { lib->metaData = doc.object(); if (qt_debug_component()) qWarning("Found metadata in lib %s, metadata=\n%s\n", @@ -679,20 +683,26 @@ bool QLibrary::isLibrary(const QString &fileName) #endif } -typedef const char * (*QtPluginQueryVerificationDataFunction)(); - -static bool qt_get_metadata(QtPluginQueryVerificationDataFunction pfn, QLibraryPrivate *priv) +static bool qt_get_metadata(QLibraryPrivate *priv, QString *errMsg) { - const char *szData = 0; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + auto getMetaData = [](QFunctionPointer fptr) { + auto f = reinterpret_cast(fptr); + return qMakePair(f(), INT_MAX); + }; +#else + auto getMetaData = [](QFunctionPointer fptr) { + auto f = reinterpret_cast (*)()>(fptr); + return f(); + }; +#endif + + QFunctionPointer pfn = priv->resolve("qt_plugin_query_metadata"); if (!pfn) return false; - szData = pfn(); - if (!szData) - return false; - - // the data is already loaded, so the size doesn't matter - QJsonDocument doc = qJsonFromRawLibraryMetaData(szData, INT_MAX); + auto metaData = getMetaData(pfn); + QJsonDocument doc = qJsonFromRawLibraryMetaData(metaData.first, metaData.second, errMsg); if (doc.isNull()) return false; priv->metaData = doc.object(); @@ -735,9 +745,7 @@ void QLibraryPrivate::updatePluginState() } else { // library is already loaded (probably via QLibrary) // simply get the target function and call it. - QtPluginQueryVerificationDataFunction getMetaData = NULL; - getMetaData = (QtPluginQueryVerificationDataFunction) resolve("qt_plugin_query_metadata"); - success = qt_get_metadata(getMetaData, this); + success = qt_get_metadata(this, &errorString); } if (!success) { diff --git a/src/corelib/plugin/qplugin_p.h b/src/corelib/plugin/qplugin_p.h new file mode 100644 index 0000000000..99ee16a3e8 --- /dev/null +++ b/src/corelib/plugin/qplugin_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLUGIN_P_H +#define QPLUGIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +enum class QtPluginMetaDataKeys { + QtVersion, + Debug, + IID, + ClassName, + MetaData +}; + +// F(IntKey, StringKey, Description) +// Keep this list sorted in the order moc should output. +#define QT_PLUGIN_FOREACH_METADATA(F) \ + F(QtPluginMetaDataKeys::QtVersion, "version", "Qt version built against") \ + F(QtPluginMetaDataKeys::Debug, "debug", "Whether it is a debug build") \ + F(QtPluginMetaDataKeys::IID, "IID", "Plugin's Interface ID") \ + F(QtPluginMetaDataKeys::ClassName, "className", "Plugin class name") \ + F(QtPluginMetaDataKeys::MetaData, "MetaData", "Other meta data") + +QT_END_NAMESPACE + +#endif // QPLUGIN_P_H diff --git a/src/corelib/plugin/qpluginloader.cpp b/src/corelib/plugin/qpluginloader.cpp index 83cbcd2b44..0f94bb6adf 100644 --- a/src/corelib/plugin/qpluginloader.cpp +++ b/src/corelib/plugin/qpluginloader.cpp @@ -475,10 +475,19 @@ QVector QPluginLoader::staticPlugins() */ QJsonObject QStaticPlugin::metaData() const { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // the data is already loaded, so this doesn't matter qsizetype rawMetaDataSize = INT_MAX; + const char *ptr = rawMetaData(); +#else + auto ptr = static_cast(rawMetaData); +#endif - return qJsonFromRawLibraryMetaData(rawMetaData(), rawMetaDataSize).object(); + QString errMsg; + QJsonDocument doc = qJsonFromRawLibraryMetaData(ptr, rawMetaDataSize, &errMsg); + Q_ASSERT(doc.isObject()); + Q_ASSERT(errMsg.isEmpty()); + return doc.object(); } QT_END_NAMESPACE diff --git a/src/tools/moc/cbordevice.h b/src/tools/moc/cbordevice.h new file mode 100644 index 0000000000..25b75b79eb --- /dev/null +++ b/src/tools/moc/cbordevice.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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 CBORDEVICE_H +#define CBORDEVICE_H + +#include +#include + +#define CBOR_API inline +#define CBOR_PRIVATE_API inline +#define CBOR_NO_PARSER_API 1 +#include + +class CborDevice +{ +public: + CborDevice(FILE *out) : out(out) {} + + void nextItem(const char *comment = nullptr) + { + i = 0; + if (comment) + fprintf(out, "\n // %s", comment); + } + + static CborError callback(void *self, const void *ptr, size_t len, CborEncoderAppendType t) + { + auto that = static_cast(self); + auto data = static_cast(ptr); + if (t == CborEncoderAppendCborData) { + while (len--) + that->putByte(*data++); + } else { + while (len--) + that->putChar(*data++); + } + return CborNoError; + } + +private: + FILE *out; + int i = 0; + + void putNewline() + { + if ((i++ % 8) == 0) + fputs("\n ", out); + } + + void putByte(uint8_t c) + { + putNewline(); + fprintf(out, " 0x%02x, ", c); + } + + void putChar(char c) + { + putNewline(); + if (c < 0x20 || c >= 0x7f) + fprintf(out, " '\\x%x',", uint8_t(c)); + else if (c == '\'' || c == '\\') + fprintf(out, " '\\%c',", c); + else + fprintf(out, " '%c', ", c); + } +}; + +#endif // CBORDEVICE_H diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp index a3ef1147d6..e5154b779b 100644 --- a/src/tools/moc/generator.cpp +++ b/src/tools/moc/generator.cpp @@ -2,6 +2,7 @@ ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2013 Olivier Goffart +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. @@ -28,6 +29,7 @@ ****************************************************************************/ #include "generator.h" +#include "cbordevice.h" #include "outputrevision.h" #include "utils.h" #include @@ -36,9 +38,13 @@ #include #include #include +#include + +#include #include #include //for the flags. +#include //for the flags. QT_BEGIN_NAMESPACE @@ -1560,31 +1566,56 @@ void Generator::generateSignal(FunctionDef *def,int index) fprintf(out, "}\n"); } -static void writePluginMetaData(FILE *out, const QJsonObject &data) +static CborError jsonValueToCbor(CborEncoder *parent, const QJsonValue &v); +static CborError jsonObjectToCbor(CborEncoder *parent, const QJsonObject &o) { - const QJsonDocument doc(data); + auto it = o.constBegin(); + auto end = o.constEnd(); + CborEncoder map; + cbor_encoder_create_map(parent, &map, o.size()); - fputs("\nQT_PLUGIN_METADATA_SECTION\n" - "static const unsigned char qt_pluginMetaData[] = {\n" - " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', ' ',\n ", out); -#if 0 - fprintf(out, "\"%s\";\n", doc.toJson().constData()); -#else - const QByteArray binary = doc.toBinaryData(); - const int last = binary.size() - 1; - for (int i = 0; i < last; ++i) { - uchar c = (uchar)binary.at(i); - if (c < 0x20 || c >= 0x7f) - fprintf(out, " 0x%02x,", c); - else if (c == '\'' || c == '\\') - fprintf(out, " '\\%c',", c); - else - fprintf(out, " '%c', ", c); - if (!((i + 1) % 8)) - fputs("\n ", out); + for ( ; it != end; ++it) { + QByteArray key = it.key().toUtf8(); + cbor_encode_text_string(&map, key.constData(), key.size()); + jsonValueToCbor(&map, it.value()); } - fprintf(out, " 0x%02x\n};\n", (uchar)binary.at(last)); -#endif + return cbor_encoder_close_container(parent, &map); +} + +static CborError jsonArrayToCbor(CborEncoder *parent, const QJsonArray &a) +{ + CborEncoder array; + cbor_encoder_create_array(parent, &array, a.size()); + for (const QJsonValue &v : a) + jsonValueToCbor(&array, v); + return cbor_encoder_close_container(parent, &array); +} + +static CborError jsonValueToCbor(CborEncoder *parent, const QJsonValue &v) +{ + switch (v.type()) { + case QJsonValue::Null: + case QJsonValue::Undefined: + return cbor_encode_null(parent); + case QJsonValue::Bool: + return cbor_encode_boolean(parent, v.toBool()); + case QJsonValue::Array: + return jsonArrayToCbor(parent, v.toArray()); + case QJsonValue::Object: + return jsonObjectToCbor(parent, v.toObject()); + case QJsonValue::String: { + QByteArray s = v.toString().toUtf8(); + return cbor_encode_text_string(parent, s.constData(), s.size()); + } + case QJsonValue::Double: { + double d = v.toDouble(); + if (d == floor(d) && fabs(d) <= (Q_INT64_C(1) << std::numeric_limits::digits)) + return cbor_encode_int(parent, qint64(d)); + return cbor_encode_double(parent, d); + } + } + Q_UNREACHABLE(); + return CborUnknownError; } void Generator::generatePluginMetaData() @@ -1592,32 +1623,59 @@ void Generator::generatePluginMetaData() if (cdef->pluginData.iid.isEmpty()) return; - // Write plugin meta data #ifdefed QT_NO_DEBUG with debug=false, - // true, respectively. + fputs("\nQT_PLUGIN_METADATA_SECTION\n" + "static const unsigned char qt_pluginMetaData[] = {\n" + " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',", out); - QJsonObject data; - const QString debugKey = QStringLiteral("debug"); - data.insert(QStringLiteral("IID"), QLatin1String(cdef->pluginData.iid.constData())); - data.insert(QStringLiteral("className"), QLatin1String(cdef->classname.constData())); - data.insert(QStringLiteral("version"), (int)QT_VERSION); - data.insert(debugKey, QJsonValue(false)); - data.insert(QStringLiteral("MetaData"), cdef->pluginData.metaData.object()); + CborDevice dev(out); + CborEncoder enc; + cbor_encoder_init_writer(&enc, CborDevice::callback, &dev); + + CborEncoder map; + cbor_encoder_create_map(&enc, &map, CborIndefiniteLength); + + dev.nextItem("\"version\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::QtVersion)); + cbor_encode_int(&map, QT_VERSION); + + fputs("\n#ifdef QT_NO_DEBUG", out); + dev.nextItem("\"debug\" = false"); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::Debug)); + cbor_encode_boolean(&map, false); + fputs("\n#else", out); + dev.nextItem("\"debug\" = true"); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::Debug)); + cbor_encode_boolean(&map, true); + fputs("\n#endif", out); + + dev.nextItem("\"IID\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::IID)); + cbor_encode_text_string(&map, cdef->pluginData.iid.constData(), cdef->pluginData.iid.size()); + + dev.nextItem("\"className\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::ClassName)); + cbor_encode_text_string(&map, cdef->classname.constData(), cdef->classname.size()); + + QJsonObject o = cdef->pluginData.metaData.object(); + if (!o.isEmpty()) { + dev.nextItem("\"MetaData\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::MetaData)); + jsonObjectToCbor(&map, o); + } // Add -M args from the command line: - for (auto it = cdef->pluginData.metaArgs.cbegin(), end = cdef->pluginData.metaArgs.cend(); it != end; ++it) - data.insert(it.key(), it.value()); + for (auto it = cdef->pluginData.metaArgs.cbegin(), end = cdef->pluginData.metaArgs.cend(); it != end; ++it) { + const QJsonArray &a = it.value(); + QByteArray key = it.key().toUtf8(); + dev.nextItem(QByteArray("command-line \"" + key + "\"").constData()); + cbor_encode_text_string(&map, key.constData(), key.size()); + jsonArrayToCbor(&map, a); + } - fputs("\nQT_PLUGIN_METADATA_SECTION const uint qt_section_alignment_dummy = 42;\n\n" - "#ifdef QT_NO_DEBUG\n", out); - writePluginMetaData(out, data); - - fputs("\n#else // QT_NO_DEBUG\n", out); - - data.remove(debugKey); - data.insert(debugKey, QJsonValue(true)); - writePluginMetaData(out, data); - - fputs("#endif // QT_NO_DEBUG\n\n", out); + // Close the CBOR map manually + dev.nextItem(); + cbor_encoder_close_container(&enc, &map); + fputs("\n};\n", out); // 'Use' all namespaces. int pos = cdef->qualified.indexOf("::"); @@ -1627,4 +1685,14 @@ void Generator::generatePluginMetaData() cdef->qualified.constData(), cdef->classname.constData()); } +QT_WARNING_DISABLE_GCC("-Wunused-function") +QT_WARNING_DISABLE_CLANG("-Wunused-function") +QT_WARNING_DISABLE_CLANG("-Wundefined-internal") +QT_WARNING_DISABLE_MSVC(4334) // '<<': result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) + +#define CBOR_ENCODER_WRITER_CONTROL 1 +#define CBOR_ENCODER_WRITE_FUNCTION CborDevice::callback + QT_END_NAMESPACE + +#include "cborencoder.c" diff --git a/src/tools/moc/moc.pri b/src/tools/moc/moc.pri index b689a35478..90839a445b 100644 --- a/src/tools/moc/moc.pri +++ b/src/tools/moc/moc.pri @@ -1,5 +1,6 @@ -INCLUDEPATH += $$PWD +INCLUDEPATH += $$PWD \ + $$PWD/../../3rdparty/tinycbor/src HEADERS = $$PWD/moc.h \ $$PWD/preprocessor.h \ @@ -8,7 +9,8 @@ HEADERS = $$PWD/moc.h \ $$PWD/token.h \ $$PWD/utils.h \ $$PWD/generator.h \ - $$PWD/outputrevision.h + $$PWD/outputrevision.h \ + $$PWD/cbordevice.h SOURCES = $$PWD/moc.cpp \ $$PWD/preprocessor.cpp \ $$PWD/generator.cpp \ diff --git a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp index d285ed79c0..8a5c325041 100644 --- a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp +++ b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -32,6 +33,8 @@ #include #include +#include + class tst_QPlugin : public QObject { Q_OBJECT @@ -124,7 +127,10 @@ void tst_QPlugin::scanInvalidPlugin_data() { QTest::addColumn("metadata"); QTest::addColumn("loads"); + QTest::addColumn("errMsg"); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // Binary JSON metadata QByteArray prefix = "QTMETADATA "; { @@ -138,27 +144,52 @@ void tst_QPlugin::scanInvalidPlugin_data() obj.insert("debug", true); #endif obj.insert("MetaData", QJsonObject()); - QTest::newRow("control") << (prefix + QJsonDocument(obj).toBinaryData()) << true; + QTest::newRow("json-control") << (prefix + QJsonDocument(obj).toBinaryData()) << true << ""; } - QTest::newRow("zeroes") << prefix << false; + QTest::newRow("json-zeroes") << prefix << false << ""; prefix += "qbjs"; - QTest::newRow("bad-json-version0") << prefix << false; - QTest::newRow("bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false; + QTest::newRow("bad-json-version0") << prefix << false << ""; + QTest::newRow("bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false << ""; // valid qbjs version 1 prefix += QByteArray("\1\0\0\0"); // too large for the file (100 MB) - QTest::newRow("bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false; + QTest::newRow("bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false << ""; // too large for binary JSON (512 MB) - QTest::newRow("bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false; + QTest::newRow("bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false << ""; // could overflow - QTest::newRow("bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false; + QTest::newRow("bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false << ""; +#endif + // CBOR metadata + QByteArray cprefix = "QTMETADATA !"; + + { + QCborMap m; + m.insert(int(QtPluginMetaDataKeys::IID), QLatin1String("org.qt-project.tst_qplugin")); + m.insert(int(QtPluginMetaDataKeys::ClassName), QLatin1String("tst")); + m.insert(int(QtPluginMetaDataKeys::QtVersion), int(QT_VERSION)); +#ifdef QT_NO_DEBUG + m.insert(int(QtPluginMetaDataKeys::Debug), false); +#else + m.insert(int(QtPluginMetaDataKeys::Debug), true); +#endif + m.insert(int(QtPluginMetaDataKeys::MetaData), QCborMap()); + + QTest::newRow("cbor-control") << (cprefix + QCborValue(m).toCbor()) << true << ""; + } + + QTest::newRow("cbor-invalid") << (cprefix + "\xff") << false + << "Metadata parsing error: Invalid CBOR stream: unexpected 'break' byte"; + QTest::newRow("cbor-not-map1") << (cprefix + "\x01") << false + << "Unexpected metadata contents"; + QTest::newRow("cbor-not-map2") << (cprefix + "\x81\x01") << false + << "Unexpected metadata contents"; } static const char invalidPluginSignature[] = "qplugin testfile"; @@ -214,6 +245,11 @@ void tst_QPlugin::scanInvalidPlugin() // now try to load this QFETCH(bool, loads); + QFETCH(QString, errMsg); + if (!loads) + QTest::ignoreMessage(QtWarningMsg, + "Found invalid metadata in lib " + QFile::encodeName(newName) + + ": " + errMsg.toUtf8()); QPluginLoader loader(newName); QCOMPARE(loader.load(), loads); if (loads) diff --git a/tests/auto/corelib/plugin/qplugin/tst_qplugin.pro b/tests/auto/corelib/plugin/qplugin/tst_qplugin.pro index 8c6540fe87..4432ee20c1 100644 --- a/tests/auto/corelib/plugin/qplugin/tst_qplugin.pro +++ b/tests/auto/corelib/plugin/qplugin/tst_qplugin.pro @@ -1,6 +1,6 @@ CONFIG += testcase TARGET = tst_qplugin -QT = core testlib +QT = core-private testlib SOURCES = tst_qplugin.cpp TESTDATA += plugins/* From 7bd79b3cffbbbece23867c5e111a3dd2ebcad016 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sun, 1 Jul 2018 10:20:21 -0700 Subject: [PATCH 26/26] Plugins: Save some architectural requirement flags ...not just the debug flag. The information is saved outside of the CBOR map for two reasons: 1) removing the hack that depended on how QCborStreamWriter and TinyCBOR internally work, allowing for the extra parameter to be written directly. We wouldn't be able to use that hack anyway and would have needed a further, uglier hack to encode a byte whose value we don't know. 2) outside the map, this information can be parsed more quickly and then we can discard any plugins we shouldn't actually load. Since we're doing this for a flag, I decided to move the Qt version there too for reason #2. Change-Id: I61ecce6b1324410bbab4fffd153d4e5fc696d19e Reviewed-by: Lars Knoll Reviewed-by: Oswald Buddenhagen --- src/corelib/plugin/qfactoryloader.cpp | 22 +++++++- src/corelib/plugin/qplugin.h | 15 +++++ src/corelib/plugin/qplugin_p.h | 4 +- src/tools/moc/generator.cpp | 21 ++----- .../corelib/plugin/qplugin/tst_qplugin.cpp | 56 ++++++++++++------- 5 files changed, 75 insertions(+), 43 deletions(-) diff --git a/src/corelib/plugin/qfactoryloader.cpp b/src/corelib/plugin/qfactoryloader.cpp index 9f877ccda8..35c64180d4 100644 --- a/src/corelib/plugin/qfactoryloader.cpp +++ b/src/corelib/plugin/qfactoryloader.cpp @@ -69,11 +69,17 @@ static inline int metaDataSignatureLength() static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QString *errMsg) { - if (Q_UNLIKELY(raw[-1] != '!')) { - *errMsg = QStringLiteral("Invalid metadata signature"); + // extract the keys not stored in CBOR + int qt_metadataVersion = quint8(raw[0]); + int qt_version = qFromBigEndian(raw + 1); + int qt_archRequirements = quint8(raw[3]); + if (Q_UNLIKELY(raw[-1] != '!' || qt_metadataVersion != 0)) { + *errMsg = QStringLiteral("Invalid metadata version"); return QJsonDocument(); } + raw += 4; + size -= 4; QByteArray ba = QByteArray::fromRawData(raw, int(size)); QCborParserError err; QCborValue metadata = QCborValue::fromCbor(ba, &err); @@ -88,8 +94,12 @@ static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QStri return QJsonDocument(); } - // convert the top-level map integer keys QJsonObject o; + o.insert(QLatin1String("version"), qt_version << 8); + o.insert(QLatin1String("debug"), bool(qt_archRequirements & 1)); + o.insert(QLatin1String("archreq"), qt_archRequirements); + + // convert the top-level map integer keys for (auto it : metadata.toMap()) { QString key; if (it.first.isInteger()) { @@ -98,6 +108,12 @@ static QJsonDocument jsonFromCborMetaData(const char *raw, qsizetype size, QStri case int(IntKey): key = QStringLiteral(StringKey); break; QT_PLUGIN_FOREACH_METADATA(CONVERT_TO_STRING) #undef CONVERT_TO_STRING + + case int(QtPluginMetaDataKeys::Requirements): + // special case: recreate the debug key + o.insert(QLatin1String("debug"), bool(it.second.toInteger() & 1)); + key = QStringLiteral("archreq"); + break; } } else { key = it.first.toString(); diff --git a/src/corelib/plugin/qplugin.h b/src/corelib/plugin/qplugin.h index 9922026f97..5aca22497a 100644 --- a/src/corelib/plugin/qplugin.h +++ b/src/corelib/plugin/qplugin.h @@ -55,6 +55,21 @@ QT_BEGIN_NAMESPACE # endif #endif +inline constexpr unsigned char qPluginArchRequirements() +{ + return 0 +#ifndef QT_NO_DEBUG + | 1 +#endif +#ifdef __AVX2__ + | 2 +# ifdef __AVX512F__ + | 4 +# endif +#endif + ; +} + typedef QObject *(*QtPluginInstanceFunction)(); typedef const char *(*QtPluginMetaDataFunction)(); diff --git a/src/corelib/plugin/qplugin_p.h b/src/corelib/plugin/qplugin_p.h index 99ee16a3e8..717129268b 100644 --- a/src/corelib/plugin/qplugin_p.h +++ b/src/corelib/plugin/qplugin_p.h @@ -57,7 +57,7 @@ QT_BEGIN_NAMESPACE enum class QtPluginMetaDataKeys { QtVersion, - Debug, + Requirements, IID, ClassName, MetaData @@ -66,8 +66,6 @@ enum class QtPluginMetaDataKeys { // F(IntKey, StringKey, Description) // Keep this list sorted in the order moc should output. #define QT_PLUGIN_FOREACH_METADATA(F) \ - F(QtPluginMetaDataKeys::QtVersion, "version", "Qt version built against") \ - F(QtPluginMetaDataKeys::Debug, "debug", "Whether it is a debug build") \ F(QtPluginMetaDataKeys::IID, "IID", "Plugin's Interface ID") \ F(QtPluginMetaDataKeys::ClassName, "className", "Plugin class name") \ F(QtPluginMetaDataKeys::MetaData, "MetaData", "Other meta data") diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp index e5154b779b..e499d22618 100644 --- a/src/tools/moc/generator.cpp +++ b/src/tools/moc/generator.cpp @@ -1624,8 +1624,11 @@ void Generator::generatePluginMetaData() return; fputs("\nQT_PLUGIN_METADATA_SECTION\n" - "static const unsigned char qt_pluginMetaData[] = {\n" - " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',", out); + "static constexpr unsigned char qt_pluginMetaData[] = {\n" + " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',\n" + " // metadata version, Qt version, architectural requirements\n" + " 0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(),", out); + CborDevice dev(out); CborEncoder enc; @@ -1634,20 +1637,6 @@ void Generator::generatePluginMetaData() CborEncoder map; cbor_encoder_create_map(&enc, &map, CborIndefiniteLength); - dev.nextItem("\"version\""); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::QtVersion)); - cbor_encode_int(&map, QT_VERSION); - - fputs("\n#ifdef QT_NO_DEBUG", out); - dev.nextItem("\"debug\" = false"); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::Debug)); - cbor_encode_boolean(&map, false); - fputs("\n#else", out); - dev.nextItem("\"debug\" = true"); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::Debug)); - cbor_encode_boolean(&map, true); - fputs("\n#endif", out); - dev.nextItem("\"IID\""); cbor_encode_int(&map, int(QtPluginMetaDataKeys::IID)); cbor_encode_text_string(&map, cdef->pluginData.iid.constData(), cdef->pluginData.iid.size()); diff --git a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp index 8a5c325041..a290c012df 100644 --- a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp +++ b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -147,49 +148,62 @@ void tst_QPlugin::scanInvalidPlugin_data() QTest::newRow("json-control") << (prefix + QJsonDocument(obj).toBinaryData()) << true << ""; } - QTest::newRow("json-zeroes") << prefix << false << ""; + QTest::newRow("json-zeroes") << prefix << false << " "; prefix += "qbjs"; - QTest::newRow("bad-json-version0") << prefix << false << ""; - QTest::newRow("bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false << ""; + QTest::newRow("bad-json-version0") << prefix << false << " "; + QTest::newRow("bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false << " "; // valid qbjs version 1 prefix += QByteArray("\1\0\0\0"); // too large for the file (100 MB) - QTest::newRow("bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false << ""; + QTest::newRow("bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false << " "; // too large for binary JSON (512 MB) - QTest::newRow("bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false << ""; + QTest::newRow("bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false << " "; // could overflow - QTest::newRow("bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false << ""; + QTest::newRow("bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false << " "; #endif // CBOR metadata - QByteArray cprefix = "QTMETADATA !"; + QByteArray cprefix = "QTMETADATA !1234"; + cprefix[12] = 0; // current version + cprefix[13] = QT_VERSION_MAJOR; + cprefix[14] = QT_VERSION_MINOR; + cprefix[15] = qPluginArchRequirements(); - { + QByteArray cborValid = [] { QCborMap m; m.insert(int(QtPluginMetaDataKeys::IID), QLatin1String("org.qt-project.tst_qplugin")); m.insert(int(QtPluginMetaDataKeys::ClassName), QLatin1String("tst")); - m.insert(int(QtPluginMetaDataKeys::QtVersion), int(QT_VERSION)); -#ifdef QT_NO_DEBUG - m.insert(int(QtPluginMetaDataKeys::Debug), false); -#else - m.insert(int(QtPluginMetaDataKeys::Debug), true); -#endif m.insert(int(QtPluginMetaDataKeys::MetaData), QCborMap()); + return QCborValue(m).toCbor(); + }(); + QTest::newRow("cbor-control") << (cprefix + cborValid) << true << ""; - QTest::newRow("cbor-control") << (cprefix + QCborValue(m).toCbor()) << true << ""; - } + cprefix[12] = 1; + QTest::newRow("cbor-major-too-new") << (cprefix + cborValid) << false + << " Invalid metadata version"; + + cprefix[12] = 0; + cprefix[13] = QT_VERSION_MAJOR + 1; + QTest::newRow("cbor-major-too-new") << (cprefix + cborValid) << false << ""; + + cprefix[13] = QT_VERSION_MAJOR - 1; + QTest::newRow("cbor-major-too-old") << (cprefix + cborValid) << false << ""; + + cprefix[13] = QT_VERSION_MAJOR; + cprefix[14] = QT_VERSION_MINOR + 1; + QTest::newRow("cbor-minor-too-new") << (cprefix + cborValid) << false << ""; QTest::newRow("cbor-invalid") << (cprefix + "\xff") << false - << "Metadata parsing error: Invalid CBOR stream: unexpected 'break' byte"; + << " Metadata parsing error: Invalid CBOR stream: unexpected 'break' byte"; QTest::newRow("cbor-not-map1") << (cprefix + "\x01") << false - << "Unexpected metadata contents"; + << " Unexpected metadata contents"; QTest::newRow("cbor-not-map2") << (cprefix + "\x81\x01") << false - << "Unexpected metadata contents"; + << " Unexpected metadata contents"; } static const char invalidPluginSignature[] = "qplugin testfile"; @@ -246,10 +260,10 @@ void tst_QPlugin::scanInvalidPlugin() // now try to load this QFETCH(bool, loads); QFETCH(QString, errMsg); - if (!loads) + if (!errMsg.isEmpty()) QTest::ignoreMessage(QtWarningMsg, "Found invalid metadata in lib " + QFile::encodeName(newName) + - ": " + errMsg.toUtf8()); + ":" + errMsg.toUtf8()); QPluginLoader loader(newName); QCOMPARE(loader.load(), loads); if (loads)