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 <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Burak Hancerli
2022-12-05 00:23:38 +01:00
parent 51fe3ca59e
commit 76565fa8d7
17 changed files with 1088 additions and 270 deletions

View File

@@ -35,7 +35,7 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath,
processFinishCallback); processFinishCallback);
if (forwardOutput == puppetMode || forwardOutput == "all") { if (forwardOutput == puppetMode || forwardOutput == "all") {
puppetProcess->setProcessChannelMode(QProcess::MergedChannels); puppetProcess->setProcessChannelMode(QProcess::ForwardedChannels);
QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback);
} }
puppetProcess->setWorkingDirectory(workingDirectory); puppetProcess->setWorkingDirectory(workingDirectory);
@@ -46,7 +46,6 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath,
else else
processArguments = {socketToken, puppetMode}; processArguments = {socketToken, puppetMode};
processArguments.push_back("-graphicssystem raster");
processArguments.push_back(freeTypeOption); processArguments.push_back(freeTypeOption);
puppetProcess->start(puppetPath, processArguments); puppetProcess->start(puppetPath, processArguments);

View File

@@ -35,12 +35,22 @@ add_qtc_executable(qml2puppet
Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate
QmlPuppetCommunication QmlPuppetCommunication
SOURCES SOURCES
qml2puppet/qml2puppetmain.cpp qml2puppet/main.cpp
qmlpuppet.qrc qmlpuppet.qrc
) )
set_target_properties(qml2puppet PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION}) 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 extend_qtc_executable(qml2puppet
CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0
SOURCES SOURCES
@@ -166,6 +176,16 @@ extend_qtc_executable(qml2puppet
animationdriver.cpp animationdriver.h 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 extend_qtc_executable(qml2puppet
SOURCES_PREFIX qmlprivategate SOURCES_PREFIX qmlprivategate
SOURCES SOURCES
@@ -186,11 +206,21 @@ extend_qtc_executable(qml2puppet
PUBLIC_INCLUDES src/libs PUBLIC_INCLUDES src/libs
) )
extend_qtc_executable(qml2puppet
PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/qml2puppet/runner/runtime
)
extend_qtc_executable(qml2puppet extend_qtc_executable(qml2puppet
CONDITION TARGET Nanotrace CONDITION TARGET Nanotrace
DEPENDS 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) if (QTC_STATIC_BUILD AND Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0)
qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR}) qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR})
endif() endif()

View File

@@ -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();
}

View File

@@ -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 <qt5nodeinstanceclientproxy.h>
#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER
#include <sqlitelibraryinitializer.h>
#endif
#include <QQmlComponent>
#include <QQmlEngine>
#include <QDebug>
#include <QApplication>
#include <QStringList>
#include <QFileInfo>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#ifdef ENABLE_QT_BREAKPAD
#include <qtsystemexceptionhandler.h>
#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 <windows.h>
#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<std::string, std::string> annotations;
annotations["qt-version"] = QT_VERSION_STR;
// Optional arguments to pass to the handler
std::vector<std::string> 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 <stream file> [control stream file]";
qDebug() << "--rendericon <icon size> <icon file name> <icon source qml>";
qDebug() << "--import3dAsset <source asset file name> <output dir> <id number> <import options JSON>";
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);
}
}

View File

@@ -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 <QCommandLineParser>
#include <QLoggingCategory>
#include <app/app_version.h>
// 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

View File

@@ -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 <windows.h>
#endif
#define START_CRASHPAD
#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN)
startCrashpad()
#endif
#ifdef ENABLE_QT_BREAKPAD
#include <qtsystemexceptionhandler.h>
#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<std::string, std::string> annotations;
annotations["qt-version"] = QT_VERSION_STR;
// Optional arguments to pass to the handler
std::vector<std::string> 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
}

View File

@@ -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 <sqlitelibraryinitializer.h>
#endif
#include <QFileInfo>
#include <QQmlComponent>
#include <QQmlEngine>
#include "qml2puppet/iconrenderer/iconrenderer.h"
#include "qml2puppet/import3d/import3d.h"
#include "configcrashpad.h"
#include <qt5nodeinstanceclientproxy.h>
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<QCoreApplication>();
#else
#if defined QT_WIDGETS_LIB
if (!useGuiApplication)
createCoreApp<QApplication>();
else
#endif //QT_WIDGETS_LIB
createCoreApp<QGuiApplication>();
#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<QObject>(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);
}

View File

@@ -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<IconRenderer> m_iconRenderer;
};

View File

@@ -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 <QDir>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQmlContext>
#include <QFileInfo>
#include <QFileOpenEvent>
#include <QLibraryInfo>
#include <QSurfaceFormat>
#include <QCommandLineParser>
#include <QStandardPaths>
#include <QTranslator>
#include <QSharedPointer>
#include "appmetadata.h"
#include <iostream>
#include <QApplication>
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<QCoreApplication> 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<typename T>
void createCoreApp()
{
m_coreApp.reset(new T(m_args.argc, m_args.argv));
}
QSharedPointer<QCoreApplication> m_coreApp;
QCommandLineParser m_argParser;
QSharedPointer<QQmlApplicationEngine> 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());
}
}
};

View File

@@ -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 <QCoreApplication>
#include <QQmlApplicationEngine>
//// 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<QObject *>(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;
};

View File

@@ -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 <QUrl>
#include <QtQml/qqml.h>
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<PartialScene> 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<PartialScene> sceneCompleters()
{
return QQmlListProperty<PartialScene>(this, &completers);
}
QList<PartialScene *> completers;
};

View File

@@ -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 <QIcon>
#include "loadwatcher.h"
#include "qmlruntime.h"
#include <private/qqmlimport_p.h>
#include <private/qtqmlglobal_p.h>
#if QT_CONFIG(qml_animation)
#include <private/qabstractanimation_p.h>
#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<QCoreApplication>();
}
else if (!strcmp(m_args.argv[i], "gui")) {
createCoreApp<QGuiApplication>();
}
#ifdef QT_WIDGETS_LIB
else if (!strcmp(m_args.argv[i], "widget")) {
createCoreApp<QApplication>();
static_cast<QApplication *>(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<QGuiApplication *>(m_coreApp.data())) {
m_exitTimerId = static_cast<QGuiApplication *>(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<LoadWatcher> 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<Config *>(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);
}
}

View File

@@ -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<Config> m_conf;
bool m_verboseMode = false;
bool m_quietMode = false;
int m_exitTimerId = -1;
};

View File

@@ -12,4 +12,9 @@
<file>mockfiles/ToolBarButton.qml</file> <file>mockfiles/ToolBarButton.qml</file>
<file>mockfiles/ToggleButton.qml</file> <file>mockfiles/ToggleButton.qml</file>
</qresource> </qresource>
<qresource prefix="/runner">
<file>runnerconf/qmlruntime/default.qml</file>
<file>runnerconf/qmlruntime/content/resizeItemToWindow.qml</file>
<file>runnerconf/qmlruntime/content/resizeWindowToItem.qml</file>
</qresource>
</RCC> </RCC>

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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")
}
}