/**************************************************************************** ** ** 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 #ifdef QT_GUI_LIB #include #include #include #include #include #ifdef QT_WIDGETS_LIB #include #endif // QT_WIDGETS_LIB #endif // QT_GUI_LIB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_CONFIG(qml_animation) #include #endif #include #include #include #define VERSION_MAJ 1 #define VERSION_MIN 1 #define VERSION_STR "1.1" #define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms static Config *conf = nullptr; static QQmlApplicationEngine *qae = nullptr; #if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB) static int exitTimerId = -1; #endif bool verboseMode = false; static const QString iconResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png")); static void loadConf(const QString &override, bool quiet) // Terminates app on failure { const QString defaultFileName = QLatin1String("configuration.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 settingsUrl = QUrl(QLatin1String("qrc:///qt-project.org/QmlRuntime/conf/") + defaultFileName); builtIn = true; } } else { QFileInfo fi; 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.\n"); else printf("qml: Using configuration file: %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(c2.create()); if (!conf){ printf("qml: Error loading configuration file: %s\n", qPrintable(c2.errorString())); exit(1); } } #ifdef QT_GUI_LIB void noFilesGiven(); // 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(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); } bool earlyExit = false; int returnCode = 0; 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: int expectedFileCount; bool haveWindow = false; }; 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(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(sender())); QOpenGLFunctions functions(context); QByteArray output = "Vendor : "; output += reinterpret_cast(functions.glGetString(GL_VENDOR)); output += "\nRenderer: "; output += reinterpret_cast(functions.glGetString(GL_RENDERER)); output += "\nVersion : "; output += reinterpret_cast(functions.glGetString(GL_VERSION)); output += "\nLanguage: "; output += reinterpret_cast(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 QtWarningMsg: default: ; } } // ### Should command line arguments have translations? Qt creator doesn't, so maybe it's not worth it. enum QmlApplicationType { QmlApplicationTypeUnknown , QmlApplicationTypeCore #ifdef QT_GUI_LIB , QmlApplicationTypeGui #ifdef QT_WIDGETS_LIB , QmlApplicationTypeWidget #endif // QT_WIDGETS_LIB #endif // QT_GUI_LIB }; #ifndef QT_GUI_LIB QmlApplicationType applicationType = QmlApplicationTypeCore; #else QmlApplicationType applicationType = QmlApplicationTypeGui; #endif // QT_GUI_LIB bool quietMode = false; void printVersion() { printf("qml binary version "); printf(VERSION_STR); printf("\nbuilt with Qt version "); printf(QT_VERSION_STR); printf("\n"); exit(0); } void printUsage() { printf("Usage: qml [options] [files] [-- args]\n"); printf("\n"); printf("Any unknown argument before '--' will be treated as a QML file to be loaded.\n"); printf("Any number of QML files can be loaded. They will share the same engine.\n"); printf("'gui' application type is only available if the QtGui module is available.\n"); printf("'widget' application type is only available if the QtWidgets module is available.\n"); printf("\n"); printf("General Options:\n"); printf("\t-h, -help..................... Print this usage information and exit.\n"); printf("\t-v, -version.................. Print the version information and exit.\n"); #ifdef QT_GUI_LIB #ifndef QT_WIDGETS_LIB printf("\t-apptype [core|gui] .......... Select which application class to use. Default is gui.\n"); #else printf("\t-apptype [core|gui|widget] ... Select which application class to use. Default is gui.\n"); #endif // QT_WIDGETS_LIB #endif // QT_GUI_LIB printf("\t-quiet ....................... Suppress all output.\n"); printf("\t-I [path] .................... Prepend the given path to the import paths.\n"); printf("\t-f [file] .................... Load the given file as a QML file.\n"); printf("\t-config [file] ............... Load the given file as the configuration file.\n"); printf("\t-- ........................... Arguments after this one are ignored by the launcher, but may be used within the QML application.\n"); printf("\tGL options:\n"); printf("\t-desktop.......................Force use of desktop GL (AA_UseDesktopOpenGL)\n"); printf("\t-gles..........................Force use of GLES (AA_UseOpenGLES)\n"); printf("\t-software......................Force use of software rendering (AA_UseOpenGLES)\n"); printf("\t-scaling.......................Enable High DPI scaling (AA_EnableHighDpiScaling)\n"); printf("\t-no-scaling....................Disable High DPI scaling (AA_DisableHighDpiScaling)\n"); printf("\tDebugging options:\n"); printf("\t-verbose ..................... Print information about what qml is doing, like specific file urls being loaded.\n"); printf("\t-translation [file] .......... Load the given file as the translations file.\n"); printf("\t-dummy-data [directory] ...... Load QML files from the given directory as context properties.\n"); printf("\t-slow-animations ............. Run all animations in slow motion.\n"); printf("\t-fixed-animations ............ Run animations off animation tick rather than wall time.\n"); exit(0); } void noFilesGiven() { if (!quietMode) printf("qml: No files specified. Terminating.\n"); exit(1); } // Called before application initialization, removes arguments it uses void getAppFlags(int &argc, char **argv) { #ifdef QT_GUI_LIB for (int i=0; i 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); QCoreApplication *app = nullptr; switch (applicationType) { case QmlApplicationTypeCore: app = new QCoreApplication(argc, argv); break; #ifdef QT_GUI_LIB case QmlApplicationTypeGui: app = new LoaderApplication(argc, argv); break; #ifdef QT_WIDGETS_LIB case QmlApplicationTypeWidget: app = new QApplication(argc, argv); static_cast(app)->setWindowIcon(QIcon(iconResourcePath)); break; #endif // QT_WIDGETS_LIB #endif // QT_GUI_LIB default: Q_ASSERT_X(false, Q_FUNC_INFO, "impossible case"); break; } app->setApplicationName("Qml Runtime"); app->setOrganizationName("QtProject"); app->setOrganizationDomain("qt-project.org"); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); qmlRegisterType("QmlRuntime.Config", 1, 0, "Configuration"); qmlRegisterType("QmlRuntime.Config", 1, 0, "PartialScene"); QQmlApplicationEngine e; QStringList files; QString confFile; QString translationFile; QString dummyDir; // Handle main arguments const QStringList argList = app->arguments(); for (int i = 1; i < argList.count(); i++) { const QString &arg = argList[i]; if (arg == QLatin1String("-quiet")) quietMode = true; else if (arg == QLatin1String("-v") || arg == QLatin1String("-version")) printVersion(); else if (arg == QLatin1String("-h") || arg == QLatin1String("-help")) printUsage(); else if (arg == QLatin1String("--")) break; else if (arg == QLatin1String("-verbose")) verboseMode = true; #if QT_CONFIG(qml_animation) else if (arg == QLatin1String("-slow-animations")) QUnifiedTimer::instance()->setSlowModeEnabled(true); else if (arg == QLatin1String("-fixed-animations")) QUnifiedTimer::instance()->setConsistentTiming(true); #endif else if (arg == QLatin1String("-I")) { if (i+1 == argList.count()) continue; // Invalid usage, but just ignore it e.addImportPath(argList[i+1]); i++; } else if (arg == QLatin1String("-f")) { if (i+1 == argList.count()) continue; // Invalid usage, but just ignore it files << argList[i+1]; i++; } else if (arg == QLatin1String("-config")){ if (i+1 == argList.count()) continue; // Invalid usage, but just ignore it confFile = argList[i+1]; i++; } else if (arg == QLatin1String("-translation")){ if (i+1 == argList.count()) continue; // Invalid usage, but just ignore it translationFile = argList[i+1]; i++; } else if (arg == QLatin1String("-dummy-data")){ if (i+1 == argList.count()) continue; // Invalid usage, but just ignore it dummyDir = argList[i+1]; i++; } else if (arg == QLatin1String("-gles")) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); } else if (arg == QLatin1String("-software")) { QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); } else if (arg == QLatin1String("-desktop")) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); } else if (arg == QLatin1String("-scaling")) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } else if (arg == QLatin1String("-no-scaling")) { QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); } else { files << arg; } } if (quietMode && verboseMode) verboseMode = false; #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); if (files.count() <= 0) { #if defined(Q_OS_DARWIN) if (applicationType == QmlApplicationTypeGui) exitTimerId = static_cast(app)->startTimer(FILE_OPEN_EVENT_WAIT_TIME); else #endif noFilesGiven(); } qae = &e; loadConf(confFile, !verboseMode); // Load files QScopedPointer 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"