blob: daa278457ded507ff397dabe0be72a6657be8a0f [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Research In Motion.
** Copyright (C) 2019 The Qt Company Ltd.
** 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$
**
****************************************************************************/
#include "conf.h"
#include <QCoreApplication>
#ifdef QT_GUI_LIB
#include <QGuiApplication>
#include <QWindow>
#include <QFileOpenEvent>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#ifdef QT_WIDGETS_LIB
#include <QApplication>
#endif // QT_WIDGETS_LIB
#endif // QT_GUI_LIB
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QStringList>
#include <QScopedPointer>
#include <QDebug>
#include <QStandardPaths>
#include <QTranslator>
#include <QtGlobal>
#include <QLibraryInfo>
#include <qqml.h>
#include <qqmldebug.h>
#include <private/qmemory_p.h>
#include <private/qtqmlglobal_p.h>
#if QT_CONFIG(qml_animation)
#include <private/qabstractanimation_p.h>
#endif
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <memory>
#define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms
enum QmlApplicationType {
QmlApplicationTypeUnknown
, QmlApplicationTypeCore
#ifdef QT_GUI_LIB
, QmlApplicationTypeGui
#ifdef QT_WIDGETS_LIB
, QmlApplicationTypeWidget
#endif // QT_WIDGETS_LIB
#endif // QT_GUI_LIB
};
static QmlApplicationType applicationType =
#ifndef QT_GUI_LIB
QmlApplicationTypeCore;
#else
QmlApplicationTypeGui;
#endif // QT_GUI_LIB
static Config *conf = nullptr;
static QQmlApplicationEngine *qae = nullptr;
#if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB)
static int exitTimerId = -1;
#endif
static const QString iconResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png"));
static const QString confResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/conf/"));
static bool verboseMode = false;
static bool quietMode = false;
static void 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::DataLocation, defaultFileName));
if (fi.exists()) {
settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
} else {
// ### If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path
fi.setFile(confResourcePath + defaultFileName);
settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
builtIn = true;
}
} else {
QFileInfo fi;
fi.setFile(confResourcePath + override + QLatin1String(".qml"));
if (fi.exists()) {
settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
builtIn = true;
} else {
fi.setFile(override);
if (!fi.exists()) {
printf("qml: Couldn't find required configuration file: %s\n",
qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath())));
exit(1);
}
settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath());
}
}
if (!quiet) {
printf("qml: %s\n", QLibraryInfo::build());
if (builtIn) {
printf("qml: Using built-in configuration: %s\n",
qPrintable(override.isEmpty() ? defaultFileName : override));
} else {
printf("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);
conf = qobject_cast<Config*>(c2.create());
if (!conf){
printf("qml: Error loading configuration file: %s\n", qPrintable(c2.errorString()));
exit(1);
}
}
void noFilesGiven()
{
if (!quietMode)
printf("qml: No files specified. Terminating.\n");
exit(1);
}
static void listConfFiles()
{
QDir confResourceDir(confResourcePath);
printf("%s\n", qPrintable(QCoreApplication::translate("main", "Built-in configurations:")));
for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files))
printf(" %s\n", qPrintable(fi.baseName()));
exit(0);
}
#ifdef QT_GUI_LIB
// Loads qml after receiving a QFileOpenEvent
class LoaderApplication : public QGuiApplication
{
public:
LoaderApplication(int& argc, char **argv) : QGuiApplication(argc, argv)
{
setWindowIcon(QIcon(iconResourcePath));
}
bool event(QEvent *ev) override
{
if (ev->type() == QEvent::FileOpen) {
if (exitTimerId >= 0) {
killTimer(exitTimerId);
exitTimerId = -1;
}
qae->load(static_cast<QFileOpenEvent *>(ev)->url());
}
else
return QGuiApplication::event(ev);
return true;
}
void timerEvent(QTimerEvent *) override {
noFilesGiven();
}
};
#endif // QT_GUI_LIB
// 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)
: QObject(e)
, 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 : qAsConst(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");
std::exit(2); // Different return code from qFatal
}
}
void quit() {
// Will be checked before calling exec()
earlyExit = true;
returnCode = 0;
}
void exit(int retCode) {
earlyExit = true;
returnCode = retCode;
}
#if defined(QT_GUI_LIB) && QT_CONFIG(opengl)
void onOpenGlContextCreated(QOpenGLContext *context);
#endif
private:
void contain(QObject *o, const QUrl &containPath);
void checkForWindow(QObject *o);
private:
bool haveWindow = false;
int expectedFileCount;
};
void LoadWatcher::contain(QObject *o, const QUrl &containPath)
{
QQmlComponent c(qae, containPath);
QObject *o2 = c.create();
if (!o2)
return;
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 LoadWatcher::checkForWindow(QObject *o)
{
#if defined(QT_GUI_LIB) && QT_CONFIG(opengl)
if (o->isWindowType() && o->inherits("QQuickWindow")) {
haveWindow = true;
if (verboseMode)
connect(o, SIGNAL(openglContextCreated(QOpenGLContext*)),
this, SLOT(onOpenGlContextCreated(QOpenGLContext*)));
}
#else
Q_UNUSED(o)
#endif // QT_GUI_LIB && !QT_NO_OPENGL
}
#if defined(QT_GUI_LIB) && QT_CONFIG(opengl)
void LoadWatcher::onOpenGlContextCreated(QOpenGLContext *context)
{
context->makeCurrent(qobject_cast<QWindow *>(sender()));
QOpenGLFunctions functions(context);
QByteArray output = "Vendor : ";
output += reinterpret_cast<const char *>(functions.glGetString(GL_VENDOR));
output += "\nRenderer: ";
output += reinterpret_cast<const char *>(functions.glGetString(GL_RENDERER));
output += "\nVersion : ";
output += reinterpret_cast<const char *>(functions.glGetString(GL_VERSION));
output += "\nLanguage: ";
output += reinterpret_cast<const char *>(functions.glGetString(GL_SHADING_LANGUAGE_VERSION));
puts(output.constData());
context->doneCurrent();
}
#endif // QT_GUI_LIB && !QT_NO_OPENGL
void quietMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg)
{
Q_UNUSED(ctxt);
Q_UNUSED(msg);
// Doesn't print anything
switch (type) {
case QtFatalMsg:
exit(-1);
case QtCriticalMsg:
case QtDebugMsg:
case QtInfoMsg:
case QtWarningMsg:
;
}
}
// Called before application initialization, removes arguments it uses
void getAppFlags(int &argc, char **argv)
{
#ifdef QT_GUI_LIB
for (int i=0; i<argc; i++) {
if (!strcmp(argv[i], "--apptype") || !strcmp(argv[i], "-a") || !strcmp(argv[i], "-apptype")) {
applicationType = QmlApplicationTypeUnknown;
if (i+1 < argc) {
if (!strcmp(argv[i+1], "core"))
applicationType = QmlApplicationTypeCore;
else if (!strcmp(argv[i+1], "gui"))
applicationType = QmlApplicationTypeGui;
#ifdef QT_WIDGETS_LIB
else if (!strcmp(argv[i+1], "widget"))
applicationType = QmlApplicationTypeWidget;
#endif // QT_WIDGETS_LIB
}
for (int j=i; j<argc-2; j++)
argv[j] = argv[j+2];
argc -= 2;
}
}
#else
Q_UNUSED(argc)
Q_UNUSED(argv)
#endif // QT_GUI_LIB
}
bool getFileSansBangLine(const QString &path, QByteArray &output)
{
QFile f(path);
if (!f.open(QFile::ReadOnly | QFile::Text))
return false;
output = f.readAll();
if (output.startsWith("#!")) {//Remove first line in this case (except \n, to avoid disturbing line count)
output.remove(0, output.indexOf('\n'));
return true;
}
return false;
}
static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
{
QDir dir(directory+"/dummydata", "*.qml");
QStringList list = dir.entryList();
for (int i = 0; i < list.size(); ++i) {
QString qml = list.at(i);
QQmlComponent comp(&engine, dir.filePath(qml));
QObject *dummyData = comp.create();
if (comp.isError()) {
const QList<QQmlError> errors = comp.errors();
for (const QQmlError &error : errors)
qWarning() << error;
}
if (dummyData && !quietMode) {
printf("qml: Loaded dummy data: %s\n", qPrintable(dir.filePath(qml)));
qml.truncate(qml.length()-4);
engine.rootContext()->setContextProperty(qml, dummyData);
dummyData->setParent(&engine);
}
}
}
int main(int argc, char *argv[])
{
getAppFlags(argc, argv);
std::unique_ptr<QCoreApplication> app;
switch (applicationType) {
#ifdef QT_GUI_LIB
case QmlApplicationTypeGui:
app = qt_make_unique<LoaderApplication>(argc, argv);
break;
#ifdef QT_WIDGETS_LIB
case QmlApplicationTypeWidget:
app = qt_make_unique<QApplication>(argc, argv);
static_cast<QApplication *>(app.get())->setWindowIcon(QIcon(iconResourcePath));
break;
#endif // QT_WIDGETS_LIB
#endif // QT_GUI_LIB
case QmlApplicationTypeCore:
Q_FALLTHROUGH();
default: // QmlApplicationTypeUnknown: not allowed, but we'll exit after checking apptypeOption below
app = qt_make_unique<QCoreApplication>(argc, argv);
break;
}
app->setApplicationName("Qml Runtime");
app->setOrganizationName("QtProject");
app->setOrganizationDomain("qt-project.org");
QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR));
qmlRegisterType<Config>("QmlRuntime.Config", 1, 0, "Configuration");
qmlRegisterType<PartialScene>("QmlRuntime.Config", 1, 0, "PartialScene");
QQmlApplicationEngine e;
QStringList files;
QString confFile;
QString translationFile;
QString dummyDir;
// Handle main arguments
QCommandLineParser parser;
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
#ifdef QT_GUI_LIB
QCommandLineOption apptypeOption(QStringList() << QStringLiteral("a") << QStringLiteral("apptype"),
QCoreApplication::translate("main", "Select which application class to use. Default is gui."),
#ifdef QT_WIDGETS_LIB
QStringLiteral("core|gui|widget"));
#else
QStringLiteral("core|gui"));
#endif // QT_WIDGETS_LIB
parser.addOption(apptypeOption); // Just for the help text... we've already handled this argument above
#endif // QT_GUI_LIB
QCommandLineOption importOption(QStringLiteral("I"),
QCoreApplication::translate("main", "Prepend the given path to the import paths."), QStringLiteral("path"));
parser.addOption(importOption);
QCommandLineOption qmlFileOption(QStringLiteral("f"),
QCoreApplication::translate("main", "Load the given file as a QML file."), QStringLiteral("file"));
parser.addOption(qmlFileOption);
QCommandLineOption configOption(QStringList() << QStringLiteral("c") << QStringLiteral("config"),
QCoreApplication::translate("main", "Load the given built-in configuration or configuration file."), QStringLiteral("file"));
parser.addOption(configOption);
QCommandLineOption listConfOption(QStringList() << QStringLiteral("list-conf"),
QCoreApplication::translate("main", "List the built-in configurations."));
parser.addOption(listConfOption);
QCommandLineOption translationOption(QStringLiteral("translation"),
QCoreApplication::translate("main", "Load the given file as the translations file."), QStringLiteral("file"));
parser.addOption(translationOption);
QCommandLineOption dummyDataOption(QStringLiteral("dummy-data"),
QCoreApplication::translate("main", "Load QML files from the given directory as context properties."), QStringLiteral("file"));
parser.addOption(dummyDataOption);
// OpenGL options
QCommandLineOption glDesktopOption(QStringLiteral("desktop"),
QCoreApplication::translate("main", "Force use of desktop OpenGL (AA_UseDesktopOpenGL)."));
parser.addOption(glDesktopOption);
QCommandLineOption glEsOption(QStringLiteral("gles"),
QCoreApplication::translate("main", "Force use of GLES (AA_UseOpenGLES)."));
parser.addOption(glEsOption);
QCommandLineOption glSoftwareOption(QStringLiteral("software"),
QCoreApplication::translate("main", "Force use of software rendering (AA_UseSoftwareOpenGL)."));
parser.addOption(glSoftwareOption);
QCommandLineOption scalingOption(QStringLiteral("scaling"),
QCoreApplication::translate("main", "Enable High DPI scaling (AA_EnableHighDpiScaling)."));
parser.addOption(scalingOption);
QCommandLineOption noScalingOption(QStringLiteral("no-scaling"),
QCoreApplication::translate("main", "Disable High DPI scaling (AA_DisableHighDpiScaling)."));
parser.addOption(noScalingOption);
// Debugging and verbosity options
QCommandLineOption quietOption(QStringLiteral("quiet"),
QCoreApplication::translate("main", "Suppress all output."));
parser.addOption(quietOption);
QCommandLineOption verboseOption(QStringLiteral("verbose"),
QCoreApplication::translate("main", "Print information about what qml is doing, like specific file URLs being loaded."));
parser.addOption(verboseOption);
QCommandLineOption slowAnimationsOption(QStringLiteral("slow-animations"),
QCoreApplication::translate("main", "Run all animations in slow motion."));
parser.addOption(slowAnimationsOption);
QCommandLineOption fixedAnimationsOption(QStringLiteral("fixed-animations"),
QCoreApplication::translate("main", "Run animations off animation tick rather than wall time."));
parser.addOption(fixedAnimationsOption);
QCommandLineOption rhiOption(QStringList() << QStringLiteral("r") << QStringLiteral("rhi"),
QCoreApplication::translate("main", "Use the Qt graphics abstraction (RHI) instead of OpenGL directly. "
"Backend is one of: default, vulkan, metal, d3d11, gl"),
QStringLiteral("backend"));
parser.addOption(rhiOption);
// Positional arguments
parser.addPositionalArgument("files",
QCoreApplication::translate("main", "Any number of QML files can be loaded. They will share the same engine."), "[files...]");
parser.addPositionalArgument("args",
QCoreApplication::translate("main", "Arguments after '--' are ignored, but passed through to the application.arguments variable in QML."), "[-- args...]");
if (!parser.parse(QCoreApplication::arguments())) {
qWarning() << parser.errorText();
exit(1);
}
if (parser.isSet(versionOption))
parser.showVersion();
if (parser.isSet(helpOption))
parser.showHelp();
if (parser.isSet(listConfOption))
listConfFiles();
if (applicationType == QmlApplicationTypeUnknown) {
#ifdef QT_WIDGETS_LIB
qWarning() << QCoreApplication::translate("main", "--apptype must be followed by one of the following: core gui widget\n");
#else
qWarning() << QCoreApplication::translate("main", "--apptype must be followed by one of the following: core gui\n");
#endif // QT_WIDGETS_LIB
parser.showHelp();
}
if (parser.isSet(verboseOption))
verboseMode = true;
if (parser.isSet(quietOption)) {
quietMode = true;
verboseMode = false;
}
#if QT_CONFIG(qml_animation)
if (parser.isSet(slowAnimationsOption))
QUnifiedTimer::instance()->setSlowModeEnabled(true);
if (parser.isSet(fixedAnimationsOption))
QUnifiedTimer::instance()->setConsistentTiming(true);
#endif
if (parser.isSet(glEsOption))
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
if (parser.isSet(glSoftwareOption))
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
if (parser.isSet(glDesktopOption))
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
if (parser.isSet(scalingOption))
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
if (parser.isSet(noScalingOption))
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
for (const QString &importPath : parser.values(importOption))
e.addImportPath(importPath);
files << parser.values(qmlFileOption);
if (parser.isSet(configOption))
confFile = parser.value(configOption);
if (parser.isSet(translationOption))
translationFile = parser.value(translationOption);
if (parser.isSet(dummyDataOption))
dummyDir = parser.value(dummyDataOption);
if (parser.isSet(rhiOption)) {
qputenv("QSG_RHI", "1");
const QString rhiBackend = parser.value(rhiOption);
if (rhiBackend == QLatin1String("default"))
qunsetenv("QSG_RHI_BACKEND");
else
qputenv("QSG_RHI_BACKEND", rhiBackend.toLatin1());
}
for (QString posArg : parser.positionalArguments()) {
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)) {
app->installTranslator(&translator);
if (verboseMode)
printf("qml: Loaded translation file %s\n", qPrintable(QDir::toNativeSeparators(translationFile)));
} else {
if (!quietMode)
printf("qml: Could not load the translation file %s\n", qPrintable(QDir::toNativeSeparators(translationFile)));
}
}
#else
if (!translationFile.isEmpty() && !quietMode)
printf("qml: Translation file specified, but Qt built without translation support.\n");
#endif
if (quietMode) {
qInstallMessageHandler(quietMessageHandler);
QLoggingCategory::setFilterRules(QStringLiteral("*=false"));
}
if (files.count() <= 0) {
#if defined(Q_OS_DARWIN)
if (applicationType == QmlApplicationTypeGui)
exitTimerId = static_cast<LoaderApplication *>(app.get())->startTimer(FILE_OPEN_EVENT_WAIT_TIME);
else
#endif
noFilesGiven();
}
qae = &e;
loadConf(confFile, !verboseMode);
// Load files
QScopedPointer<LoadWatcher> lw(new LoadWatcher(&e, files.count()));
// Load dummy data before loading QML-files
if (!dummyDir.isEmpty() && QFileInfo (dummyDir).isDir())
loadDummyDataFiles(e, dummyDir);
for (const QString &path : qAsConst(files)) {
QUrl url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile);
if (verboseMode)
printf("qml: loading %s\n", qPrintable(url.toString()));
QByteArray strippedFile;
if (getFileSansBangLine(path, strippedFile))
// QQmlComponent won't resolve it for us: it doesn't know it's a valid file if we loadData
e.loadData(strippedFile, e.baseUrl().resolved(url));
else // Errors or no bang line
e.load(url);
}
if (lw->earlyExit)
return lw->returnCode;
return app->exec();
}
#include "main.moc"