From 76565fa8d76ca350a01700d5fd546aefa3b64452 Mon Sep 17 00:00:00 2001 From: Burak Hancerli Date: Mon, 5 Dec 2022 00:23:38 +0100 Subject: [PATCH] QmlDesigner: add support for multiple qml backends This patch includes; * Fork of original QML runtime as an alternative QML backend for QDS * Flexible structure for adding/removing different types of QML interpreters Note: When forking the original QML the config.h is renamed as qmlconfiguration.h because it was clashing with sqlite and QmlRuntime.QmlConfiguration uses "magic" name matching. QmlConfiguration/qmlconfiguration is unlikely to conflict with anything. Task-number: QDS-8373 Change-Id: Ifaa1b766c717ce12d6b3c9ddbbc0665669797e36 Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann Reviewed-by: --- .../designercore/instances/puppetstarter.cpp | 3 +- src/tools/qml2puppet/CMakeLists.txt | 32 +- src/tools/qml2puppet/qml2puppet/main.cpp | 28 ++ .../qml2puppet/qml2puppet/qml2puppetmain.cpp | 267 -------------- .../qml2puppet/runner/appmetadata.h | 99 +++++ .../qml2puppet/runner/puppet/configcrashpad.h | 68 ++++ .../qml2puppet/runner/puppet/qmlpuppet.cpp | 126 +++++++ .../qml2puppet/runner/puppet/qmlpuppet.h | 21 ++ .../qml2puppet/qml2puppet/runner/qmlbase.h | 111 ++++++ .../qml2puppet/runner/runtime/loadwatcher.h | 97 +++++ .../runner/runtime/qmlconfiguration.h | 66 ++++ .../qml2puppet/runner/runtime/qmlruntime.cpp | 348 ++++++++++++++++++ .../qml2puppet/runner/runtime/qmlruntime.h | 30 ++ src/tools/qml2puppet/qmlpuppet.qrc | 5 + .../qmlruntime/content/resizeItemToWindow.qml | 25 ++ .../qmlruntime/content/resizeWindowToItem.qml | 22 ++ .../runnerconf/qmlruntime/default.qml | 10 + 17 files changed, 1088 insertions(+), 270 deletions(-) create mode 100644 src/tools/qml2puppet/qml2puppet/main.cpp delete mode 100644 src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp create mode 100644 src/tools/qml2puppet/qml2puppet/runner/appmetadata.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/puppet/configcrashpad.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.cpp create mode 100644 src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/qmlbase.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/runtime/loadwatcher.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/runtime/qmlconfiguration.h create mode 100644 src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.cpp create mode 100644 src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.h create mode 100644 src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml create mode 100644 src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml create mode 100644 src/tools/qml2puppet/runnerconf/qmlruntime/default.qml diff --git a/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp index 268f43eb42d..56b3cdafc2d 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp @@ -35,7 +35,7 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath, processFinishCallback); if (forwardOutput == puppetMode || forwardOutput == "all") { - puppetProcess->setProcessChannelMode(QProcess::MergedChannels); + puppetProcess->setProcessChannelMode(QProcess::ForwardedChannels); QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); @@ -46,7 +46,6 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath, else processArguments = {socketToken, puppetMode}; - processArguments.push_back("-graphicssystem raster"); processArguments.push_back(freeTypeOption); puppetProcess->start(puppetPath, processArguments); diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 074099be894..1976a2bc149 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -35,12 +35,22 @@ add_qtc_executable(qml2puppet Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate QmlPuppetCommunication SOURCES - qml2puppet/qml2puppetmain.cpp + qml2puppet/main.cpp qmlpuppet.qrc ) set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION}) +execute_process( + COMMAND git describe --tags --always --dirty=+ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_SHA_RESULT + OUTPUT_VARIABLE GIT_SHA_OUTPUT + ERROR_VARIABLE GIT_SHA_ERROR +) + +add_definitions( -D GIT_SHA=${GIT_SHA_OUTPUT} ) + extend_qtc_executable(qml2puppet CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 SOURCES @@ -166,6 +176,16 @@ extend_qtc_executable(qml2puppet animationdriver.cpp animationdriver.h ) +extend_qtc_executable(qml2puppet + DEPENDS app_version + SOURCES_PREFIX qml2puppet/runner + SOURCES + runtime/qmlruntime.h runtime/qmlruntime.cpp + runtime/qmlconfiguration.h runtime/loadwatcher.h + puppet/qmlpuppet.h puppet/qmlpuppet.cpp puppet/configcrashpad.h + qmlbase.h appmetadata.h +) + extend_qtc_executable(qml2puppet SOURCES_PREFIX qmlprivategate SOURCES @@ -186,11 +206,21 @@ extend_qtc_executable(qml2puppet PUBLIC_INCLUDES src/libs ) +extend_qtc_executable(qml2puppet +PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/qml2puppet/runner/runtime +) + extend_qtc_executable(qml2puppet CONDITION TARGET Nanotrace DEPENDS Nanotrace ) +# Turn the tool into its own self-contained qml module +qt6_add_qml_module(qml2puppet + URI QmlRuntime.QmlConfiguration + VERSION 1.0 +) + if (QTC_STATIC_BUILD AND Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0) qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR}) endif() diff --git a/src/tools/qml2puppet/qml2puppet/main.cpp b/src/tools/qml2puppet/qml2puppet/main.cpp new file mode 100644 index 00000000000..66d6a43f0b1 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/main.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "runner/puppet/qmlpuppet.h" +#include "runner/runtime/qmlruntime.h" + +QmlBase *getQmlRunner(int &argc, char **argv) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--qml-runtime")){ + qInfo() << "Starting QML Runtime"; + return new QmlRuntime(argc, argv); + } + } +#endif + qInfo() << "Starting QML Puppet"; + return new QmlPuppet(argc, argv); +} + +int main(int argc, char *argv[]) +{ + QDSMeta::Logging::registerMessageHandler(); + QDSMeta::AppInfo::registerAppInfo("Qml2Puppet"); + + QmlBase *qmlRunner = getQmlRunner(argc, argv); + return qmlRunner->run(); +} diff --git a/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp b/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp deleted file mode 100644 index 2d7518b81bc..00000000000 --- a/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "iconrenderer/iconrenderer.h" -#include "import3d/import3d.h" - -#include -#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef ENABLE_QT_BREAKPAD -#include -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) -#define NOMINMAX -#include "client/crashpad_client.h" -#include "client/crash_report_database.h" -#include "client/settings.h" -#endif - -#ifdef Q_OS_WIN -#include -#endif - -namespace { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QByteArray localMsg = msg.toLocal8Bit(); - switch (type) { - case QtDebugMsg: - fprintf(stderr, - "Debug: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtInfoMsg: - fprintf(stderr, - "Info: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtWarningMsg: - fprintf(stderr, - "Warning: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtCriticalMsg: - fprintf(stderr, - "Critical: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtFatalMsg: - fprintf(stderr, - "Fatal: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - abort(); - } -} -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) -bool startCrashpad() -{ - using namespace crashpad; - - // Cache directory that will store crashpad information and minidumps - base::FilePath database(L"crashpad_reports"); - base::FilePath handler(L"crashpad_handler.exe"); - - // URL used to submit minidumps to - std::string url(CRASHPAD_BACKEND_URL); - - // Optional annotations passed via --annotations to the handler - std::map annotations; - annotations["qt-version"] = QT_VERSION_STR; - - // Optional arguments to pass to the handler - std::vector arguments; - arguments.push_back("--no-rate-limit"); - - CrashpadClient *client = new CrashpadClient(); - bool success = client->StartHandler( - handler, - database, - database, - url, - annotations, - arguments, - /* restartable */ true, - /* asynchronous_start */ true - ); - // TODO: research using this method, should avoid creating a separate CrashpadClient for the - // puppet (needed only on windows according to docs). -// client->SetHandlerIPCPipe(L"\\\\.\\pipe\\qml2puppet"); - - return success; -} -#endif - -int internalMain(QGuiApplication *application) -{ - QCoreApplication::setOrganizationName("QtProject"); - QCoreApplication::setOrganizationDomain("qt-project.org"); - QCoreApplication::setApplicationName("Qml2Puppet"); - QCoreApplication::setApplicationVersion("1.0.0"); - - if (application->arguments().count() < 2 - || (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() < 3) - || (application->arguments().at(1) == "--rendericon" && application->arguments().count() < 5) - || (application->arguments().at(1) == "--import3dAsset" && application->arguments().count() < 6)) { - qDebug() << "Usage:\n"; - qDebug() << "--test"; - qDebug() << "--version"; - qDebug() << "--readcapturedstream [control stream file]"; - qDebug() << "--rendericon "; - qDebug() << "--import3dAsset "; - - return -1; - } - - if (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() > 2) { - QFileInfo inputStreamFileInfo(application->arguments().at(2)); - if (!inputStreamFileInfo.exists()) { - qDebug() << "Input stream does not exist:" << inputStreamFileInfo.absoluteFilePath(); - - return -1; - } - - if (application->arguments().count() > 3) { - QFileInfo controlStreamFileInfo(application->arguments().at(3)); - if (!controlStreamFileInfo.exists()) { - qDebug() << "Output stream does not exist:" << controlStreamFileInfo.absoluteFilePath(); - - return -1; - } - } - } - - if (application->arguments().count() == 2 && application->arguments().at(1) == "--test") { - qDebug() << QCoreApplication::applicationVersion(); - QQmlEngine engine; - - QQmlComponent component(&engine); - component.setData("import QtQuick 2.0\nItem {\n}\n", QUrl::fromLocalFile("test.qml")); - - QObject *object = component.create(); - - if (object) { - qDebug() << "Basic QtQuick 2.0 working..."; - } else { - qDebug() << "Basic QtQuick 2.0 not working..."; - qDebug() << component.errorString(); - } - delete object; - return 0; - } - - if (application->arguments().count() == 2 && application->arguments().at(1) == "--version") { - std::cout << 2; - return 0; - } - - if (application->arguments().at(1) != "--readcapturedstream" && application->arguments().count() < 4) { - qDebug() << "Wrong argument count: " << application->arguments().count(); - return -1; - } - - if (application->arguments().at(1) == "--rendericon") { - int size = application->arguments().at(2).toInt(); - QString iconFileName = application->arguments().at(3); - QString iconSource = application->arguments().at(4); - - IconRenderer *iconRenderer = new IconRenderer(size, iconFileName, iconSource); - iconRenderer->setupRender(); - - return application->exec(); - } - - if (application->arguments().at(1) == "--import3dAsset") { - QString sourceAsset = application->arguments().at(2); - QString outDir = application->arguments().at(3); - QString options = application->arguments().at(4); - - Import3D::import3D(sourceAsset, outDir, options); - - return application->exec(); - } - -#ifdef ENABLE_QT_BREAKPAD - const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH; - QtSystemExceptionHandler systemExceptionHandler(libexecPath); -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) - /* startCrashpad(); */ -#endif - - new QmlDesigner::Qt5NodeInstanceClientProxy(application); - -#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG) - SetErrorMode(SEM_NOGPFAULTERRORBOX); //We do not want to see any message boxes -#endif - - if (application->arguments().at(1) == "--readcapturedstream") - return 0; - - return application->exec(); -} -} // namespace - -int main(int argc, char *argv[]) -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - qInstallMessageHandler(myMessageOutput); -#endif - // Since we always render text into an FBO, we need to globally disable - // subpixel antialiasing and instead use gray. - qputenv("QSG_DISTANCEFIELD_ANTIALIASING", "gray"); -#ifdef Q_OS_MACOS - qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "true"); -#endif - - //If a style different from Desktop is set we have to use QGuiApplication - bool useGuiApplication = (!qEnvironmentVariableIsSet("QMLDESIGNER_FORCE_QAPPLICATION") - || qgetenv("QMLDESIGNER_FORCE_QAPPLICATION") != "true") - && qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE") - && qgetenv("QT_QUICK_CONTROLS_STYLE") != "Desktop"; - -#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER - Sqlite::LibraryInitializer::initialize(); -#endif - - if (useGuiApplication) { - QGuiApplication application(argc, argv); - return internalMain(&application); - } else { - QApplication application(argc, argv); - return internalMain(&application); - } -} diff --git a/src/tools/qml2puppet/qml2puppet/runner/appmetadata.h b/src/tools/qml2puppet/qml2puppet/runner/appmetadata.h new file mode 100644 index 00000000000..552c1334398 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/appmetadata.h @@ -0,0 +1,99 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH +// Qt-GPL-exception-1.0 +#pragma once + +#include +#include + +#include + +// Common functions can be used in all QDS apps +namespace QDSMeta { + +namespace Logging { +inline Q_LOGGING_CATEGORY(deprecated, "qt.tools.qds.deprecated"); +inline Q_LOGGING_CATEGORY(verbose1, "qt.tools.qds.verbose1"); +inline Q_LOGGING_CATEGORY(verbose2, "qt.tools.qds.verbose2"); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +inline void registerMessageHandler() +{ + qInstallMessageHandler( + [](QtMsgType type, const QMessageLogContext &context, const QString &msg) { + auto tPrinter = [&](const QString &msgPrefix) { + fprintf(stderr, + "%s: %s (%s:%u, %s)\n", + msgPrefix.toLocal8Bit().constData(), + msg.toLocal8Bit().constData(), + context.file, + context.line, + context.function); + }; + + if (type == QtDebugMsg) + tPrinter("Debug"); + else if (type == QtInfoMsg) + tPrinter("Info"); + else if (type == QtWarningMsg) + tPrinter("Warning"); + else if (type == QtCriticalMsg) + tPrinter("Critical"); + else if (type == QtFatalMsg) { + tPrinter("Fatal"); + abort(); + } + }); +} +#endif + +} // namespace Logging + +namespace AppInfo { + +#define STRINGIFY_INTERNAL(x) #x +#define QDS_STRINGIFY(x) STRINGIFY_INTERNAL(x) + +inline void printAppInfo() +{ + qInfo() << Qt::endl + << "<< QDS Meta Info >>" << Qt::endl + << "App Info" << Qt::endl + << " - Name :" << Core::Constants::IDE_ID << Qt::endl + << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl + << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl + << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl + << " - App :" << QCoreApplication::applicationName() << Qt::endl + << "Build Info " << Qt::endl + << " - Date :" << __DATE__ << Qt::endl + << " - Commit :" << QStringLiteral(QDS_STRINGIFY(GIT_SHA)) << Qt::endl + << " - Qt Version :" << QT_VERSION_STR << Qt::endl + << "Compiler Info " << Qt::endl +#if defined(__GNUC__) + << " - GCC :" << __GNUC__ << Qt::endl + << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl + << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl +#endif +#if defined(_MSC_VER) + << " - MSC Short :" << _MSC_VER << Qt::endl + << " - MSC Full :" << _MSC_FULL_VER << Qt::endl +#endif +#if defined(__clang__) + << " - clang maj :" << __clang_major__ << Qt::endl + << " - clang min :" << __clang_minor__ << Qt::endl + << " - clang patch :" << __clang_patchlevel__ << Qt::endl +#endif + << "<< End Of QDS Meta Info >>" << Qt::endl; + exit(0); +} + +inline void registerAppInfo(const QString &appName) +{ + QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR); + QCoreApplication::setOrganizationDomain("qt-project.org"); + QCoreApplication::setApplicationName(appName); + QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG); +} + +} // namespace AppInfo +} // namespace QDSMeta diff --git a/src/tools/qml2puppet/qml2puppet/runner/puppet/configcrashpad.h b/src/tools/qml2puppet/qml2puppet/runner/puppet/configcrashpad.h new file mode 100644 index 00000000000..39b45e5f1c3 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/puppet/configcrashpad.h @@ -0,0 +1,68 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#pragma once + +#ifdef Q_OS_WIN +#include +#endif + +#define START_CRASHPAD +#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) +startCrashpad() +#endif + +#ifdef ENABLE_QT_BREAKPAD +#include +#endif + +#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) +#define NOMINMAX +#include "client/crash_report_database.h" +#include "client/crashpad_client.h" +#include "client/settings.h" +#endif + +namespace { +#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) + bool startCrashpad() + { + using namespace crashpad; + + // Cache directory that will store crashpad information and minidumps + base::FilePath database(L"crashpad_reports"); + base::FilePath handler(L"crashpad_handler.exe"); + + // URL used to submit minidumps to + std::string url(CRASHPAD_BACKEND_URL); + + // Optional annotations passed via --annotations to the handler + std::map annotations; + annotations["qt-version"] = QT_VERSION_STR; + + // Optional arguments to pass to the handler + std::vector arguments; + arguments.push_back("--no-rate-limit"); + + CrashpadClient *client = new CrashpadClient(); + bool success = client->StartHandler(handler, + database, + database, + url, + annotations, + arguments, + /* restartable */ true, + /* asynchronous_start */ true); + // TODO: research using this method, should avoid creating a separate CrashpadClient for the + // puppet (needed only on windows according to docs). + // client->SetHandlerIPCPipe(L"\\\\.\\pipe\\qml2puppet"); + + return success; + } + +#ifdef ENABLE_QT_BREAKPAD + const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + + RELATIVE_LIBEXEC_PATH; + QtSystemExceptionHandler systemExceptionHandler(libexecPath); +#endif +#endif +} diff --git a/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.cpp b/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.cpp new file mode 100644 index 00000000000..45d29038b1e --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.cpp @@ -0,0 +1,126 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qmlpuppet.h" + +#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER +#include +#endif + +#include +#include +#include + +#include "qml2puppet/iconrenderer/iconrenderer.h" +#include "qml2puppet/import3d/import3d.h" + +#include "configcrashpad.h" + +#include + +void QmlPuppet::initCoreApp() +{ + // Since we always render text into an FBO, we need to globally disable + // subpixel antialiasing and instead use gray. + qputenv("QSG_DISTANCEFIELD_ANTIALIASING", "gray"); +#ifdef Q_OS_MACOS + qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "true"); +#endif +#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER + Sqlite::LibraryInitializer::initialize(); +#endif + + //If a style different from Desktop is set we have to use QGuiApplication + bool useGuiApplication = (!qEnvironmentVariableIsSet("QMLDESIGNER_FORCE_QAPPLICATION") + || qgetenv("QMLDESIGNER_FORCE_QAPPLICATION") != "true") + && qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE") + && qgetenv("QT_QUICK_CONTROLS_STYLE") != "Desktop"; +#ifndef QT_GUI_LIB + createCoreApp(); +#else +#if defined QT_WIDGETS_LIB + if (!useGuiApplication) + createCoreApp(); + else +#endif //QT_WIDGETS_LIB + createCoreApp(); +#endif //QT_GUI_LIB +} + +int QmlPuppet::startTestMode() +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData("import QtQuick 2.0\nItem {\n}\n", QUrl::fromLocalFile("test.qml")); + + if (!QSharedPointer(component.create())) { + qDebug() << "Basic QtQuick 2.0 not working..."; + qDebug() << component.errorString(); + return -1; + } + + qDebug() << "Basic QtQuick 2.0 working..."; + return 0; +} + +void QmlPuppet::populateParser() +{ + // we're not using the commandline parser but just populating the help text + m_argParser.addOptions( + {{"readcapturedstream", "Read captured stream.", "inputStream, [outputStream]"}, + {"rendericon", "Renders icon.", "size, fileName, sourceQml"}, + {"import3dAsset", "Import 3d asset.", "sourceAsset, outDir, importOptJson"}}); +} + +void QmlPuppet::initQmlRunner() +{ + if (m_coreApp->arguments().count() < 2 + || (m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() < 3) + || (m_argParser.isSet("rendericon") && m_coreApp->arguments().count() < 5) + || (m_argParser.isSet("import3dAsset") && m_coreApp->arguments().count() < 6) + || (!m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() < 4)) { + qDebug() << "Wrong argument count: " << m_coreApp->arguments().count(); + m_argParser.showHelp(1); + } + + if (m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() > 2) { + QString fileName = m_argParser.value("readcapturedstream"); + if (!QFile::exists(fileName)) { + qDebug() << "Input stream does not exist:" << fileName; + exit(-1); + } + + if (m_coreApp->arguments().count() > 3) { + fileName = m_coreApp->arguments().at(3); + if (!QFile::exists(fileName)) { + qDebug() << "Output stream does not exist:" << fileName; + exit(-1); + } + } + } + + if (m_argParser.isSet("rendericon")) { + int size = m_coreApp->arguments().at(2).toInt(); + QString iconFileName = m_coreApp->arguments().at(3); + QString iconSource = m_coreApp->arguments().at(4); + + m_iconRenderer.reset(new IconRenderer(size, iconFileName, iconSource)); + m_iconRenderer->setupRender(); + } else if (m_argParser.isSet("import3dAsset")) { + QString sourceAsset = m_coreApp->arguments().at(2); + QString outDir = m_coreApp->arguments().at(3); + QString options = m_coreApp->arguments().at(4); + + Import3D::import3D(sourceAsset, outDir, options); + } + + START_CRASHPAD; + new QmlDesigner::Qt5NodeInstanceClientProxy(m_coreApp.get()); + +#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG) + SetErrorMode(SEM_NOGPFAULTERRORBOX); //We do not want to see any message boxes +#endif + + if (m_argParser.isSet("readcapturedstream")) + exit(0); +} diff --git a/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.h b/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.h new file mode 100644 index 00000000000..66838164cbb --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/puppet/qmlpuppet.h @@ -0,0 +1,21 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmlbase.h" + +class IconRenderer; +class QmlPuppet : public QmlBase +{ + using QmlBase::QmlBase; + +private: + void initCoreApp() override; + void populateParser() override; + int startTestMode() override; + void initQmlRunner() override; + +private: + QSharedPointer m_iconRenderer; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/qmlbase.h b/src/tools/qml2puppet/qml2puppet/runner/qmlbase.h new file mode 100644 index 00000000000..9bc41bef752 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/qmlbase.h @@ -0,0 +1,111 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH +// Qt-GPL-exception-1.0 + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "appmetadata.h" +#include + +#include +class QmlBase : public QObject +{ + Q_OBJECT +public: + struct AppArgs + { + public: + int argc; + char **argv; + }; + + QmlBase(int &argc, char **argv, QObject *parent = nullptr) + : QObject{parent} + , m_args({argc, argv}) + { + m_argParser.setApplicationDescription("QML Runtime Provider for QDS"); + m_argParser.addOptions( + {{"qml-puppet", "Run QML Puppet (default)"}, + {"qml-runtime", "Run QML Runtime"}, + {"appinfo", "Print build information"}, + {"test", "Run test mode"} + }); + } + + int run() + { + populateParser(); + initCoreApp(); + initParser(); + initQmlRunner(); + return m_coreApp->exec(); + } + + QSharedPointer coreApp() const { return m_coreApp; } + +protected: + virtual void initCoreApp() = 0; + virtual void populateParser() = 0; + virtual void initQmlRunner() = 0; + + virtual int startTestMode() + { + qDebug() << "Test mode is not implemented for this type of runner"; + return 0; + } + + template + void createCoreApp() + { + m_coreApp.reset(new T(m_args.argc, m_args.argv)); + } + + QSharedPointer m_coreApp; + QCommandLineParser m_argParser; + QSharedPointer m_qmlEngine; + + AppArgs m_args; + +private: + void initParser() + { + QCommandLineOption optHelp = m_argParser.addHelpOption(); + QCommandLineOption optVers = m_argParser.addVersionOption(); + + if (!m_coreApp) { + qCritical() << "Cannot initialize coreapp!"; + m_argParser.showHelp(); + } + + if (!m_argParser.parse(m_coreApp->arguments())) { + std::cout << "Error: " << m_argParser.errorText().toStdString() << std::endl + << std::endl; + m_argParser.showHelp(1); + } else if (m_argParser.isSet(optVers)) { + m_argParser.showVersion(); + } else if (m_argParser.isSet(optHelp)) { + m_argParser.showHelp(0); + } else if (m_argParser.isSet("appinfo")) { + QDSMeta::AppInfo::printAppInfo(); + } else if (m_argParser.isSet("test")) { + exit(startTestMode()); + } + } +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/runtime/loadwatcher.h b/src/tools/qml2puppet/qml2puppet/runner/runtime/loadwatcher.h new file mode 100644 index 00000000000..737557a45cc --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/runtime/loadwatcher.h @@ -0,0 +1,97 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmlconfiguration.h" + +#include "QtQml/qqmlcomponent.h" +#include +#include + +//// Listens to the appEngine signals to determine if all files failed to load +class LoadWatcher : public QObject +{ + // Q_OBJECT +public: + LoadWatcher(QQmlApplicationEngine *e, int expected, Config *conf) + : QObject(e) + , qae(e) + , conf(conf) + , expectedFileCount(expected) + { + connect(e, &QQmlApplicationEngine::objectCreated, this, &LoadWatcher::checkFinished); + // QQmlApplicationEngine also connects quit() to QCoreApplication::quit + // and exit() to QCoreApplication::exit but if called before exec() + // then QCoreApplication::quit or QCoreApplication::exit does nothing + connect(e, &QQmlEngine::quit, this, &LoadWatcher::quit); + connect(e, &QQmlEngine::exit, this, &LoadWatcher::exit); + } + + int returnCode = 0; + bool earlyExit = false; + +public Q_SLOTS: + void checkFinished(QObject *o, const QUrl &url) + { + Q_UNUSED(url); + if (o) { + checkForWindow(o); + if (conf && qae) { + for (PartialScene *ps : std::as_const(conf->completers)) { + if (o->inherits(ps->itemType().toUtf8().constData())) + contain(o, ps->container()); + } + } + } + if (haveWindow) + return; + + if (!--expectedFileCount) { + printf("qml: Did not load any objects, exiting.\n"); + exit(2); + QCoreApplication::exit(2); + } + } + + void quit() + { + // Will be checked before calling exec() + earlyExit = true; + returnCode = 0; + } + void exit(int retCode) + { + earlyExit = true; + returnCode = retCode; + } + +private: + void contain(QObject *o, const QUrl &containPath) + { + QQmlComponent c(qae, containPath); + QObject *o2 = c.create(); + if (!o2) + return; + o2->setParent(this); + checkForWindow(o2); + bool success = false; + int idx; + if ((idx = o2->metaObject()->indexOfProperty("containedObject")) != -1) + success = o2->metaObject()->property(idx).write(o2, QVariant::fromValue(o)); + if (!success) + o->setParent(o2); // Set QObject parent, and assume container will react as needed + } + void checkForWindow(QObject *o) + { + if (o->isWindowType() && o->inherits("QQuickWindow")) + haveWindow = true; + } + +private: + QQmlApplicationEngine *qae; + Config *conf; + + bool haveWindow = false; + int expectedFileCount; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlconfiguration.h b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlconfiguration.h new file mode 100644 index 00000000000..7563d7ce781 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlconfiguration.h @@ -0,0 +1,66 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +class PartialScene : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUrl container READ container WRITE setContainer NOTIFY containerChanged) + Q_PROPERTY(QString itemType READ itemType WRITE setItemType NOTIFY itemTypeChanged) + QML_ELEMENT + QML_ADDED_IN_VERSION(1, 0) +public: + PartialScene(QObject *parent = nullptr) + : QObject(parent) + {} + + const QUrl container() const { return m_container; } + const QString itemType() const { return m_itemType; } + + void setContainer(const QUrl &a) + { + if (a == m_container) + return; + m_container = a; + emit containerChanged(); + } + void setItemType(const QString &a) + { + if (a == m_itemType) + return; + m_itemType = a; + emit itemTypeChanged(); + } + +signals: + void containerChanged(); + void itemTypeChanged(); + +private: + QUrl m_container; + QString m_itemType; +}; + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty sceneCompleters READ sceneCompleters) + Q_CLASSINFO("DefaultProperty", "sceneCompleters") + QML_NAMED_ELEMENT(Configuration) + QML_ADDED_IN_VERSION(1, 0) +public: + Config(QObject *parent = nullptr) + : QObject(parent) + {} + + QQmlListProperty sceneCompleters() + { + return QQmlListProperty(this, &completers); + } + + QList completers; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.cpp b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.cpp new file mode 100644 index 00000000000..e3e962483f8 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.cpp @@ -0,0 +1,348 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include + +#include "loadwatcher.h" +#include "qmlruntime.h" + +#include +#include +#if QT_CONFIG(qml_animation) +#include +#endif + +#define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms +#define QSL QStringLiteral + +void QmlRuntime::populateParser() +{ + m_argParser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + m_argParser.setOptionsAfterPositionalArgumentsMode( + QCommandLineParser::ParseAsPositionalArguments); + + m_argParser.addOptions( + {{QStringList() << QSL("a") << QSL("apptype"), + QSL("Select which application class to use. Default is gui."), + QSL("core|gui|widget")}, // just for translation + + {QSL("I"), QSL("Prepend the given path to the import paths."), QSL("path")}, + + {QSL("f"), QSL("Load the given file as a QML file."), QSL("file")}, + + {QStringList() << QSL("c") << QSL("config"), + QSL("Load the given built-in configuration or configuration file."), + QSL("file")}, + + {QStringList() << QSL("list-conf"), QSL("List the built-in configurations.")}, + + {QSL("translation"), QSL("Load the given file as the translations file."), QSL("file")}, + +#ifdef QT_GUI_LIB + // OpenGL options + {QSL("desktop"), QSL("Force use of desktop OpenGL (AA_UseDesktopOpenGL).")}, + + {QSL("gles"), QSL("Force use of GLES (AA_UseOpenGLES).")}, + + {QSL("software"), QSL("Force use of software rendering (AA_UseSoftwareOpenGL).")}, + + {QSL("core-profile"), QSL("Force use of OpenGL Core Profile.")}, + + {QSL("disable-context-sharing"), + QSL("Disable the use of a shared GL context for QtQuick Windows")}, +#endif // QT_GUI_LIB + + // Debugging and verbosity options + {QSL("quiet"), QSL("Suppress all output.")}, + + {QSL("verbose"), + QSL("Print information about what qml is doing, like specific file URLs being loaded.")}, + + {QSL("slow-animations"), QSL("Run all animations in slow motion.")}, + + {QSL("fixed-animations"), QSL("Run animations off animation tick rather than wall time.")}, + + {QStringList() << QSL("r") << QSL("rhi"), + QSL("Set the backend for the Qt graphics abstraction (RHI). " + "Backend is one of: default, vulkan, metal, d3d11, gl"), + QSL("backend")}, + + {QSL("S"), QSL("Add selector to the list of QQmlFileSelectors."), QSL("selector")}}); + + // Positional arguments + m_argParser.addPositionalArgument( + "files", + QSL("Any number of QML files can be loaded. They will share the same engine."), + "[files...]"); + m_argParser.addPositionalArgument("args", + QSL("Arguments after '--' are ignored, but passed through to " + "the application.arguments variable in QML."), + "[-- args...]"); +} + +void QmlRuntime::initCoreApp() +{ + bool glShareContexts = true; + + // these attributes must be set before the QCoreApp is initialized + for (int i = 0; i < m_args.argc; i++) { + if (!strcmp(m_args.argv[i], "-desktop") || !strcmp(m_args.argv[i], "--desktop")) { + QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + } else if (!strcmp(m_args.argv[i], "-gles") || !strcmp(m_args.argv[i], "--gles")) { + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); + } else if (!strcmp(m_args.argv[i], "-software") || !strcmp(m_args.argv[i], "--software")) { + QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + } else if (!strcmp(m_args.argv[i], "-disable-context-sharing") + || !strcmp(m_args.argv[i], "--disable-context-sharing")) { + glShareContexts = false; + } + } + + if (glShareContexts) + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + + // since we handled all attributes above, now we can initialize the core app + for (int i = 0; i < m_args.argc; i++) { + if (!strcmp(m_args.argv[i], "--apptype") || !strcmp(m_args.argv[i], "-a") + || !strcmp(m_args.argv[i], "-apptype")) { + if (i + 1 < m_args.argc) { + ++i; + if (!strcmp(m_args.argv[i], "core")) { + createCoreApp(); + } + else if (!strcmp(m_args.argv[i], "gui")) { + createCoreApp(); + } +#ifdef QT_WIDGETS_LIB + else if (!strcmp(m_args.argv[i], "widget")) { + createCoreApp(); + static_cast(m_coreApp.get()) + ->setWindowIcon(QIcon(m_iconResourcePath)); + } +#endif // QT_WIDGETS_LIB + } + } + } +} + +void QmlRuntime::initQmlRunner() +{ + m_qmlEngine.reset(new QQmlApplicationEngine()); + + QStringList files; + QString confFile; + QString translationFile; + + if (!m_argParser.parse(QCoreApplication::arguments())) { + qWarning() << m_argParser.errorText(); + exit(1); + } + + // TODO: replace below logging modes with a proper logging category + m_verboseMode = m_argParser.isSet("verbose"); + m_quietMode = (!m_verboseMode && m_argParser.isSet("quiet")); + // FIXME: need to re-evaluate. we have our own message handler. + // if (quietMode) { + // qInstallMessageHandler(quietMessageHandler); + // QLoggingCategory::setFilterRules(QStringLiteral("*=false")); + // } + + if (m_argParser.isSet("list-conf")) { + listConfFiles(); + exit(0); + } + +#if QT_CONFIG(qml_animation) + if (m_argParser.isSet("slow-animations")) + QUnifiedTimer::instance()->setSlowModeEnabled(true); + if (m_argParser.isSet("fixed-animations")) + QUnifiedTimer::instance()->setConsistentTiming(true); +#endif + const auto valsImportPath = m_argParser.values("I"); + for (const QString &importPath : valsImportPath) + m_qmlEngine->addImportPath(importPath); + + QStringList customSelectors; + + const auto valsSelectors = m_argParser.values("S"); + for (const QString &selector : valsSelectors) + customSelectors.append(selector); + + if (!customSelectors.isEmpty()) + m_qmlEngine->setExtraFileSelectors(customSelectors); + + if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE") + || qEnvironmentVariableIsSet("QML_CORE_PROFILE") || m_argParser.isSet("core-profile")) { + QSurfaceFormat surfaceFormat; + surfaceFormat.setStencilBufferSize(8); + surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setVersion(4, 1); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(surfaceFormat); + } + + if (m_argParser.isSet("config")) + confFile = m_argParser.value("config"); + if (m_argParser.isSet("translation")) + translationFile = m_argParser.value("translation"); + if (m_argParser.isSet("rhi")) { + const QString rhiBackend = m_argParser.value("rhi"); + if (rhiBackend == QLatin1String("default")) + qunsetenv("QSG_RHI_BACKEND"); + else + qputenv("QSG_RHI_BACKEND", rhiBackend.toLatin1()); + } + + const auto valsPosArgs = m_argParser.positionalArguments(); + files << m_argParser.values("f"); + for (const QString &posArg : valsPosArgs) { + if (posArg == QLatin1String("--")) + break; + else + files << posArg; + } + +#if QT_CONFIG(translation) + // Need to be installed before QQmlApplicationEngine's automatic translation loading + // (qt_ translations are loaded there) + if (!translationFile.isEmpty()) { + QTranslator translator; + + if (translator.load(translationFile)) { + m_coreApp->installTranslator(&translator); + if (m_verboseMode) + qInfo() << "qml: Loaded translation file %s\n", + qPrintable(QDir::toNativeSeparators(translationFile)); + } else { + if (!m_quietMode) + qInfo() << "qml: Could not load the translation file %s\n", + qPrintable(QDir::toNativeSeparators(translationFile)); + } + } +#else + if (!translationFile.isEmpty() && !quietMode) + qInfo() << "qml: Translation file specified, but Qt built without translation support.\n"); +#endif + + if (files.size() <= 0) { +#if defined(Q_OS_DARWIN) + if (qobject_cast(m_coreApp.data())) { + m_exitTimerId = static_cast(m_coreApp.get()) + ->startTimer(FILE_OPEN_EVENT_WAIT_TIME); + } else +#endif + { + if (!m_quietMode) + qCritical() << "No files specified. Terminating.\n"; + exit(1); + } + } + + loadConf(confFile, !m_verboseMode); + + // Load files + QScopedPointer lw(new LoadWatcher(m_qmlEngine.data(), files.size(), m_conf.data())); + + for (const QString &path : std::as_const(files)) { + QUrl url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + if (m_verboseMode) + qInfo() << "qml: loading %s\n", qPrintable(url.toString()); + m_qmlEngine->load(url); + } + + if (lw->earlyExit) + exit(lw->returnCode); +} + +void QmlRuntime::loadConf(const QString &override, bool quiet) // Terminates app on failure +{ + const QString defaultFileName = QLatin1String("default.qml"); + QUrl settingsUrl; + bool builtIn = false; //just for keeping track of the warning + if (override.isEmpty()) { + QFileInfo fi; + fi.setFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, defaultFileName)); + if (fi.exists()) { + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + } else { + // If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path + fi.setFile(m_confResourcePath + defaultFileName); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + builtIn = true; + } + } else { + QFileInfo fi; + fi.setFile(m_confResourcePath + override + QLatin1String(".qml")); + if (fi.exists()) { + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + builtIn = true; + } else { + fi.setFile(QDir(QStandardPaths::locate(QStandardPaths::AppConfigLocation, + override, + QStandardPaths::LocateDirectory)), + m_confResourcePath); + if (fi.exists()) + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + else + fi.setFile(override); + if (!fi.exists()) { + qCritical() << "qml: Couldn't find required configuration file: %s\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath())); + exit(1); + } + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + } + } + + if (!quiet) { + qInfo() << "qml: %s\n", QLibraryInfo::build(); + if (builtIn) { + qInfo() << "qml: Using built-in configuration: %s\n", + qPrintable(override.isEmpty() ? defaultFileName : override); + } else { + qInfo() << "qml: Using configuration: %s\n", + qPrintable(settingsUrl.isLocalFile() + ? QDir::toNativeSeparators(settingsUrl.toLocalFile()) + : settingsUrl.toString()); + } + } + + // TODO: When we have better engine control, ban QtQuick* imports on this engine + QQmlEngine e2; + QQmlComponent c2(&e2, settingsUrl); + m_conf.reset(qobject_cast(c2.create())); + + if (!m_conf) { + qCritical() << "qml: Error loading configuration file: %s\n", qPrintable(c2.errorString()); + exit(1); + } +} + +void QmlRuntime::listConfFiles() +{ + const QDir confResourceDir(m_confResourcePath); + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Built-in configurations:")); + for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files)) + qInfo() << " %s\n", qPrintable(fi.baseName()); + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Other configurations:")); + bool foundOther = false; + const QStringList otherLocations = QStandardPaths::standardLocations( + QStandardPaths::AppConfigLocation); + for (const auto &confDirPath : otherLocations) { + const QDir confDir(confDirPath); + for (const QFileInfo &fi : confDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { + foundOther = true; + if (m_verboseMode) + qInfo() << " %s\n", qPrintable(fi.absoluteFilePath()); + else + qInfo() << " %s\n", qPrintable(fi.baseName()); + } + } + if (!foundOther) + qInfo() << " %s\n", qPrintable(QCoreApplication::translate("main", "none")); + if (m_verboseMode) { + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Checked in:")); + for (const auto &confDirPath : otherLocations) + qInfo() << " %s\n", qPrintable(confDirPath); + } +} diff --git a/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.h b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.h new file mode 100644 index 00000000000..c69481ca039 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/runtime/qmlruntime.h @@ -0,0 +1,30 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmlbase.h" +#include "qmlconfiguration.h" + +class QmlRuntime : public QmlBase +{ + using QmlBase::QmlBase; + +private: + void initCoreApp() override; + void populateParser() override; + void initQmlRunner() override; + + void listConfFiles(); + void loadConf(const QString &override, bool quiet); + + const QString m_iconResourcePath = QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png"); + const QString m_confResourcePath = QStringLiteral(":/runner/runnerconf/qmlruntime/"); + + + QSharedPointer m_conf; + bool m_verboseMode = false; + bool m_quietMode = false; + int m_exitTimerId = -1; +}; + diff --git a/src/tools/qml2puppet/qmlpuppet.qrc b/src/tools/qml2puppet/qmlpuppet.qrc index 162c955ac14..7ec26950034 100644 --- a/src/tools/qml2puppet/qmlpuppet.qrc +++ b/src/tools/qml2puppet/qmlpuppet.qrc @@ -12,4 +12,9 @@ mockfiles/ToolBarButton.qml mockfiles/ToggleButton.qml + + runnerconf/qmlruntime/default.qml + runnerconf/qmlruntime/content/resizeItemToWindow.qml + runnerconf/qmlruntime/content/resizeWindowToItem.qml + diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml new file mode 100644 index 00000000000..ca4618ba734 --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml @@ -0,0 +1,25 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + property bool __resizeGuard: false + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false + return + } + __resizeGuard = true + width = containedObject.width + height = containedObject.height + containedObject.parent = contentItem + visible = true + __resizeGuard = false + } + onWidthChanged: if (!__resizeGuard && containedObject) + containedObject.width = width + onHeightChanged: if (!__resizeGuard && containedObject) + containedObject.height = height +} diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml new file mode 100644 index 00000000000..30029bf973f --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false + return + } + width = Qt.binding(function () { + return containedObject.width + }) + height = Qt.binding(function () { + return containedObject.height + }) + containedObject.parent = contentItem + visible = true + } +} diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml new file mode 100644 index 00000000000..961f2a890bd --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QmlRuntime.QmlConfiguration 1.0 + +Configuration { + PartialScene { + itemType: "QQuickItem" + container: Qt.resolvedUrl("content/resizeItemToWindow.qml") + } +}