summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Griebl <robert.griebl@pelagicore.com>2017-03-06 12:09:59 +0100
committerDominik Holland <dominik.holland@pelagicore.com>2017-03-13 16:14:33 +0000
commit6c73901c09acd25dc423d4b55f840e4c447f4332 (patch)
treedae795d99e4dbe9931c5daac41c5062a1d23ca41
parent3df2058ef31adcd10fdd43c71fcbedf1b10e7a19 (diff)
downloadqtapplicationmanager-6c73901c09acd25dc423d4b55f840e4c447f4332.tar.gz
Restructure manager/main.cpp
This file has grown so much that it was overdue to split the main() function down into more manageable parts. Change-Id: If5d4bd184c4cbb4a018623875e648fc7197342ce Reviewed-by: Dominik Holland <dominik.holland@pelagicore.com>
-rw-r--r--src/common-lib/dbus-utilities.cpp10
-rw-r--r--src/manager/configuration.cpp13
-rw-r--r--src/manager/configuration.h6
-rw-r--r--src/manager/main.cpp1317
-rw-r--r--src/manager/main.h156
-rw-r--r--src/manager/manager.pri1
-rw-r--r--src/tools/testrunner/testrunner.cpp34
-rw-r--r--src/tools/testrunner/testrunner.h2
8 files changed, 881 insertions, 658 deletions
diff --git a/src/common-lib/dbus-utilities.cpp b/src/common-lib/dbus-utilities.cpp
index 0c2a4d22..88ef388d 100644
--- a/src/common-lib/dbus-utilities.cpp
+++ b/src/common-lib/dbus-utilities.cpp
@@ -146,9 +146,13 @@ QVariant convertFromDBusVariant(const QVariant &variant)
void registerDBusTypes()
{
#if defined(QT_DBUS_LIB)
- qDBusRegisterMetaType<QUrl>();
- qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
- qDBusRegisterMetaType<QT_PREPEND_NAMESPACE_AM(UnixFdMap)>();
+ static bool once = false;
+ if (!once) {
+ qDBusRegisterMetaType<QUrl>();
+ qDBusRegisterMetaType<QMap<QString, QDBusUnixFileDescriptor>>();
+ qDBusRegisterMetaType<QT_PREPEND_NAMESPACE_AM(UnixFdMap)>();
+ once = true;
+ }
#endif
}
diff --git a/src/manager/configuration.cpp b/src/manager/configuration.cpp
index ffb8d092..cdcb6139 100644
--- a/src/manager/configuration.cpp
+++ b/src/manager/configuration.cpp
@@ -42,6 +42,7 @@
#include <QFile>
#include <QVariantMap>
#include <QFileInfo>
+#include <QCoreApplication>
#include <QDebug>
#include <functional>
@@ -201,8 +202,6 @@ Configuration::Configuration()
d->clp.addOption({ qSL("build-config"), qSL("dumps the build configuration and exits.") });
d->clp.addOption({ qSL("qml-debug"), qSL("enables QML debugging and profiling.") });
d->clp.addOption({ { qSL("o"), qSL("option") }, qSL("override a specific config option."), qSL("yaml-snippet") });
-
- initialize();
}
Configuration::~Configuration()
@@ -254,7 +253,7 @@ static void showParserMessage(const QString &message, MessageType type)
// ^^^^ copied from QCommandLineParser ... why is this not public API?
-void Configuration::initialize()
+void Configuration::parse()
{
if (!d->clp.parse(QCoreApplication::arguments())) {
showParserMessage(d->clp.errorText() + qL1C('\n'), ErrorMessage);
@@ -691,9 +690,13 @@ QStringList Configuration::pluginFilePaths(const char *type) const
return variantToStringList(d->findInConfigFile({ qSL("plugins"), qL1S(type) }));
}
-QStringList Configuration::positionalArguments() const
+QStringList Configuration::testRunnerArguments() const
{
- return d->clp.positionalArguments();
+ QStringList targs = d->clp.positionalArguments();
+ if (!targs.isEmpty() && targs.constFirst().endsWith(qL1S(".qml")))
+ targs.removeFirst();
+ targs.prepend(QCoreApplication::arguments().constFirst());
+ return targs;
}
QT_END_NAMESPACE_AM
diff --git a/src/manager/configuration.h b/src/manager/configuration.h
index 4776fa9b..6171de54 100644
--- a/src/manager/configuration.h
+++ b/src/manager/configuration.h
@@ -55,6 +55,8 @@ public:
Configuration();
~Configuration();
+ void parse();
+
QString mainQmlFile() const;
QString database() const;
bool recreateDatabase() const;
@@ -110,11 +112,9 @@ public:
QStringList pluginFilePaths(const char *type) const;
- QStringList positionalArguments() const;
+ QStringList testRunnerArguments() const;
private:
- void initialize();
-
ConfigurationPrivate *d;
};
diff --git a/src/manager/main.cpp b/src/manager/main.cpp
index 7618b6e1..1bec3bac 100644
--- a/src/manager/main.cpp
+++ b/src/manager/main.cpp
@@ -84,6 +84,7 @@
#include "global.h"
#include "logging.h"
+#include "main.h"
#include "application.h"
#include "applicationmanager.h"
#include "applicationdatabase.h"
@@ -129,32 +130,692 @@
#include "../plugin-interfaces/startupinterface.h"
-#ifdef AM_TESTRUNNER
-#include "testrunner.h"
-#include "qtyaml.h"
+#if defined(AM_TESTRUNNER)
+# include "testrunner.h"
+# include "qtyaml.h"
#endif
+
+QT_USE_NAMESPACE_AM
+
+int main(int argc, char *argv[])
+{
+ StartupTimer::instance()->checkpoint("entered main");
+
+ QCoreApplication::setApplicationName(qSL("ApplicationManager"));
+ QCoreApplication::setOrganizationName(qSL("Pelagicore AG"));
+ QCoreApplication::setOrganizationDomain(qSL("pelagicore.com"));
+ QCoreApplication::setApplicationVersion(qSL(AM_VERSION));
+ for (int i = 1; i < argc; ++i) {
+ if (strcmp("--no-dlt-logging", argv[i]) == 0) {
+ Logging::setDltEnabled(false);
+ break;
+ }
+ }
+ Logging::initialize();
+ StartupTimer::instance()->checkpoint("after basic initialization");
+
+#if !defined(AM_DISABLE_INSTALLER)
+ ensureCorrectLocale();
+
+ QString error;
+ if (Q_UNLIKELY(!forkSudoServer(DropPrivilegesPermanently, &error))) {
+ qCCritical(LogSystem) << "ERROR:" << qPrintable(error);
+ return 2;
+ }
+ StartupTimer::instance()->checkpoint("after sudo server fork");
+#endif
+
+ try {
+ Main a(argc, argv);
+
+ a.setup();
+ return a.exec();
+
+ } catch (const std::exception &e) {
+ qCCritical(LogSystem) << "ERROR:" << e.what();
+ return 2;
+ }
+}
+
QT_BEGIN_NAMESPACE_AM
-static Configuration *configuration = 0;
+Main::Main(int &argc, char **argv)
+ : MainBase(argc, argv)
+{
+#if !defined(AM_HEADLESS)
+ // this is needed for both WebEngine and Wayland Multi-screen rendering
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
+# if !defined(QT_NO_SESSIONMANAGER)
+ QGuiApplication::setFallbackSessionManagementEnabled(false);
+# endif
+#endif
+ UnixSignalHandler::instance()->install(UnixSignalHandler::ForwardedToEventLoopHandler, SIGINT,
+ [](int /*sig*/) {
+ UnixSignalHandler::instance()->resetToDefault(SIGINT);
+ qCritical(" > RECEIVED CTRL+C ... EXITING\n");
+#if defined(AM_HEADLESS)
+ auto windows = qApp->allWindows();
+ for (QWindow *w : windows)
+ w->metaObject()->invokeMethod(w, "close");
+#else
+ qApp->exit(1);
+#endif
+ });
+ StartupTimer::instance()->checkpoint("after application constructor");
+}
+
+Main::~Main()
+{
+#if defined(AM_TESTRUNNER)
+ Q_UNUSED(m_notificationManager);
+ Q_UNUSED(m_systemMonitor);
+ Q_UNUSED(m_applicationIPCManager);
+ Q_UNUSED(m_debuggingEnabler);
+ delete m_engine;
+#else
+ delete m_engine;
+
+ delete m_notificationManager;
+# if !defined(AM_HEADLESS)
+ delete m_windowManager;
+ delete m_view;
+# endif
+ delete m_applicationManager;
+ delete m_quickLauncher;
+ delete m_systemMonitor;
+ delete m_applicationIPCManager;
+ delete m_debuggingEnabler;
+#endif // defined(AM_TESTRUNNER)
+}
+
+void Main::setup()
+{
+ m_config.parse();
+ StartupTimer::instance()->checkpoint("after command line parse");
+
+ CrashHandler::setCrashActionConfiguration(m_config.managerCrashAction());
+ setupQmlDebugging();
+ setupLoggingRules();
+ Logging::registerUnregisteredDltContexts();
+
+#if defined(AM_TESTRUNNER)
+ TestRunner::initialize(m_config.testRunnerArguments());
+#endif
+
+ loadStartupPlugins();
+ parseSystemProperties();
+ setupDBus();
+ checkMainQmlFile();
+ setupInstaller();
+ setupSingleOrMultiProcess();
+ setupRuntimesAndContainers();
+ loadApplicationDatabase();
+ setupSingletons();
+ setupQmlEngine();
+ setupWindowTitle();
+ setupWindowManager();
+ loadQml();
+ showWindow();
+ setupDebugWrappers();
+ setupShellServer();
+ setupSSDPService();
+}
+
+int Main::exec()
+{
+ int res;
+#if defined(AM_TESTRUNNER)
+ res = TestRunner::exec(m_engine);
+#else
+ res = MainBase::exec();
+
+ // the eventloop stopped, so any pending "retakes" would not be executed
+ retakeSingletonOwnershipFromQmlEngine(m_engine, m_applicationManager, true);
+#endif // defined(AM_TESTRUNNER)
+
+#if defined(QT_PSSDP_LIB)
+ if (m_ssdpOk)
+ m_ssdp.setActive(false);
+#endif // QT_PSSDP_LIB
+
+ return res;
+}
+
+bool Main::isSingleProcessMode() const
+{
+ return m_isSingleProcessMode;
+}
+
+void Main::setupQmlDebugging()
+{
+ if (m_config.qmlDebugging()) {
+#if !defined(QT_NO_QML_DEBUGGER)
+ m_debuggingEnabler = new QQmlDebuggingEnabler(true);
+#else
+ qCWarning(LogSystem) << "The --qml-debug option is ignored, because Qt was built without support for QML Debugging!";
+#endif
+ }
+}
+
+void Main::setupLoggingRules()
+{
+ QStringList loggingRules = m_config.loggingRules();
+ bool verbose = m_config.verbose();
+
+ if (loggingRules.isEmpty() && !verbose)
+ loggingRules.append(qSL("*.debug=false"));
+
+ if (verbose) {
+ // we are prepending here to allow the user to override these in the config file.
+ loggingRules.prepend(qSL("qt.qpa.*.debug=false"));
+ loggingRules.prepend(qSL("qt.quick.*.debug=false"));
+ loggingRules.prepend(qSL("qt.scenegraph.*.debug=false"));
+ loggingRules.prepend(qSL("qt.compositor.input.*.debug=false"));
+ loggingRules.prepend(qSL("*.debug=true"));
+ }
+
+ QLoggingCategory::setFilterRules(loggingRules.join(qL1C('\n')));
+
+ // setting this for child processes //TODO: use a more generic IPC approach
+ qputenv("AM_LOGGING_RULES", loggingRules.join(qL1C('\n')).toUtf8());
+ StartupTimer::instance()->checkpoint("after logging setup");
+}
+
+void Main::loadStartupPlugins()
+{
+ m_startupPlugins = loadPlugins<StartupInterface>("startup", m_config.pluginFilePaths("startup"));
+ StartupTimer::instance()->checkpoint("after startup-plugin load");
+}
+
+void Main::parseSystemProperties()
+{
+ m_systemProperties.resize(SP_SystemUi + 1);
+ QVariantMap rawMap = m_config.rawSystemProperties();
+
+ m_systemProperties[SP_ThirdParty] = rawMap.value(qSL("public")).toMap();
+
+ m_systemProperties[SP_BuiltIn] = m_systemProperties.at(SP_ThirdParty);
+ const QVariantMap pro = rawMap.value(qSL("protected")).toMap();
+ for (auto it = pro.cbegin(); it != pro.cend(); ++it)
+ m_systemProperties[SP_BuiltIn].insert(it.key(), it.value());
+
+ m_systemProperties[SP_SystemUi] = m_systemProperties.at(SP_BuiltIn);
+ const QVariantMap pri = rawMap.value(qSL("private")).toMap();
+ for (auto it = pri.cbegin(); it != pri.cend(); ++it)
+ m_systemProperties[SP_SystemUi].insert(it.key(), it.value());
+
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->initialize(m_systemProperties.at(SP_SystemUi));
+}
+
+void Main::setupDBus()
+{
#if defined(QT_DBUS_LIB)
+ // delay the D-Bus registrations: D-Bus is asynchronous anyway
+ int dbusDelay = qMax(0, m_config.dbusRegistrationDelay());
+ QTimer::singleShot(dbusDelay, this, &Main::registerDBusInterfaces);
+
+ if (Q_LIKELY(!m_config.dbusStartSessionBus()))
+ return;
+
+ class DBusDaemonProcess : public QProcess // clazy:exclude=missing-qobject-macro
+ {
+ public:
+ DBusDaemonProcess(QObject *parent = 0)
+ : QProcess(parent)
+ {
+ setProgram(qSL("dbus-daemon"));
+ setArguments({ qSL("--nofork"), qSL("--print-address"), qSL("--session")});
+ }
+ ~DBusDaemonProcess() override
+ {
+ kill();
+ waitForFinished();
+ }
-static QString dbusInterfaceName(QObject *o) throw (Exception)
+ protected:
+ void setupChildProcess() override
+ {
+# if defined(Q_OS_LINUX)
+ // at least on Linux we can make sure that those dbus-daemons are always killed
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+# endif
+ QProcess::setupChildProcess();
+ }
+ };
+
+ auto dbusDaemon = new DBusDaemonProcess(this);
+ dbusDaemon->start(QIODevice::ReadOnly);
+ if (!dbusDaemon->waitForStarted() || !dbusDaemon->waitForReadyRead())
+ throw Exception("could not start a dbus-launch process: %1").arg(dbusDaemon->errorString());
+
+ QByteArray busAddress = dbusDaemon->readAllStandardOutput().trimmed();
+ qputenv("DBUS_SESSION_BUS_ADDRESS", busAddress);
+ qCInfo(LogSystem, "NOTICE: running on private D-Bus session bus to avoid conflicts:");
+ qCInfo(LogSystem, " DBUS_SESSION_BUS_ADDRESS=%s", busAddress.constData());
+
+ StartupTimer::instance()->checkpoint("after starting session D-Bus");
+#endif // QT_DBUS_LIB
+}
+
+void Main::checkMainQmlFile()
+{
+ m_mainQml = m_config.mainQmlFile();
+ if (Q_UNLIKELY(!QFile::exists(m_mainQml)))
+ throw Exception("no/invalid main QML file specified: %1").arg(m_mainQml);
+}
+
+void Main::setupInstaller()
{
+#if !defined(AM_DISABLE_INSTALLER)
+ if (!checkCorrectLocale()) {
+ // we should really throw here, but so many embedded systems are badly set up
+ qCCritical(LogSystem) << "WARNING: the appman installer needs a UTF-8 locale to work correctly:\n"
+ " even automatically switching to C.UTF-8 or en_US.UTF-8 failed.";
+ }
+
+ if (Q_UNLIKELY(hardwareId().isEmpty()))
+ throw Exception("the installer is enabled, but the device-id is empty");
+
+ m_installationLocations = InstallationLocation::parseInstallationLocations(m_config.installationLocations());
+
+ if (Q_UNLIKELY(!QDir::root().mkpath(m_config.installedAppsManifestDir())))
+ throw Exception("could not create manifest directory %1").arg(m_config.installedAppsManifestDir());
+
+ if (Q_UNLIKELY(!QDir::root().mkpath(m_config.appImageMountDir())))
+ throw Exception("could not create the image-mount directory %1").arg(m_config.appImageMountDir());
+
+ StartupTimer::instance()->checkpoint("after installer setup checks");
+#endif // AM_DISABLE_INSTALLER
+}
+
+void Main::setupSingleOrMultiProcess()
+{
+ bool forceSingleProcess = m_config.forceSingleProcess();
+ bool forceMultiProcess = m_config.forceMultiProcess();
+ if (forceMultiProcess && forceSingleProcess)
+ throw Exception("You cannot enforce multi- and single-process mode at the same time.");
+
+#if !defined(AM_MULTI_PROCESS)
+ if (forceMultiProcess)
+ throw Exception("This application manager build is not multi-process capable.");
+ m_isSingleProcessMode = true;
+#endif
+}
+
+void Main::setupRuntimesAndContainers()
+{
+ if (m_isSingleProcessMode) {
+ RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager());
+ RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager(qSL("qml")));
+ } else {
+ RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager());
+#if defined(AM_NATIVE_RUNTIME_AVAILABLE)
+ RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager());
+ RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(qSL("qml")));
+ //RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(qSL("html")));
+#endif
+#if defined(AM_HOST_CONTAINER_AVAILABLE)
+ ContainerFactory::instance()->registerContainer(new ProcessContainerManager());
+#endif
+ auto containerPlugins = loadPlugins<ContainerManagerInterface>("container", m_config.pluginFilePaths("container"));
+ for (auto iface : qAsConst(containerPlugins))
+ ContainerFactory::instance()->registerContainer(new PluginContainerManager(iface));
+ }
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->afterRuntimeRegistration();
+
+ ContainerFactory::instance()->setConfiguration(m_config.containerConfigurations());
+ RuntimeFactory::instance()->setConfiguration(m_config.runtimeConfigurations());
+
+ RuntimeFactory::instance()->setSystemProperties(m_systemProperties.at(SP_ThirdParty),
+ m_systemProperties.at(SP_BuiltIn));
+
+ StartupTimer::instance()->checkpoint("after runtime registration");
+}
+
+void Main::loadApplicationDatabase()
+{
+ QString singleApp = m_config.singleApp();
+ bool recreateDatabase = m_config.recreateDatabase();
+
+ m_applicationDatabase.reset(singleApp.isEmpty() ? new ApplicationDatabase(m_config.database())
+ : new ApplicationDatabase());
+
+ if (Q_UNLIKELY(!m_applicationDatabase->isValid() && !recreateDatabase)) {
+ throw Exception("database file %1 is not a valid application database: %2")
+ .arg(m_applicationDatabase->name(), m_applicationDatabase->errorString());
+ }
+
+ if (!m_applicationDatabase->isValid() || recreateDatabase) {
+ QVector<const Application *> apps;
+
+ if (!singleApp.isEmpty()) {
+ apps = scanForApplication(singleApp, m_config.builtinAppsManifestDirs());
+ } else {
+ apps = scanForApplications(m_config.builtinAppsManifestDirs(),
+ m_config.installedAppsManifestDir(),
+ m_installationLocations);
+ }
+
+ if (LogSystem().isDebugEnabled()) {
+ qCDebug(LogSystem) << "Registering applications:";
+ foreach (const Application *app, apps)
+ qCDebug(LogSystem).nospace().noquote() << " * " << app->id() << " [at: " << app->codeDir().path() << "]";
+ }
+
+ m_applicationDatabase->write(apps);
+ qDeleteAll(apps);
+ }
+
+ StartupTimer::instance()->checkpoint("after application database loading");
+}
+
+void Main::setupSingletons()
+{
+ bool noSecurity = m_config.noSecurity();
+
+ QString error;
+ m_applicationManager = ApplicationManager::createInstance(m_applicationDatabase.take(),
+ m_isSingleProcessMode, &error);
+ if (Q_UNLIKELY(!m_applicationManager))
+ throw Exception(Error::System, error);
+ if (noSecurity)
+ m_applicationManager->setSecurityChecksEnabled(false);
+
+ m_applicationManager->setSystemProperties(m_systemProperties.at(SP_SystemUi));
+ m_applicationManager->setContainerSelectionConfiguration(m_config.containerSelectionConfiguration());
+
+ QObject::connect(this, &QCoreApplication::aboutToQuit,
+ m_applicationManager, &ApplicationManager::killAll);
+
+ StartupTimer::instance()->checkpoint("after ApplicationManager instantiation");
+
+ m_notificationManager = NotificationManager::createInstance();
+ StartupTimer::instance()->checkpoint("after NotificationManager instantiation");
+
+ m_applicationIPCManager = ApplicationIPCManager::createInstance();
+ StartupTimer::instance()->checkpoint("after ApplicationIPCManager instantiation");
+
+ m_systemMonitor = SystemMonitor::createInstance();
+ StartupTimer::instance()->checkpoint("after SystemMonitor instantiation");
+
+ m_quickLauncher = QuickLauncher::instance();
+ m_quickLauncher->initialize(m_config.quickLaunchRuntimesPerContainer(), m_config.quickLaunchIdleLoad());
+ StartupTimer::instance()->checkpoint("after quick-launcher setup");
+
+#if !defined(AM_DISABLE_INSTALLER)
+ m_applicationInstaller = ApplicationInstaller::createInstance(m_installationLocations,
+ m_config.installedAppsManifestDir(),
+ m_config.appImageMountDir(),
+ &error);
+ if (Q_UNLIKELY(!m_applicationInstaller))
+ throw Exception(Error::System, error);
+ if (noSecurity) {
+ m_applicationInstaller->setDevelopmentMode(true);
+ m_applicationInstaller->setAllowInstallationOfUnsignedPackages(true);
+ } else {
+ QList<QByteArray> caCertificateList;
+
+ const auto caFiles = m_config.caCertificates();
+ for (const auto &caFile : caFiles) {
+ QFile f(caFile);
+ if (Q_UNLIKELY(!f.open(QFile::ReadOnly)))
+ throw Exception(f, "could not open CA-certificate file");
+ QByteArray cert = f.readAll();
+ if (Q_UNLIKELY(cert.isEmpty()))
+ throw Exception(f, "CA-certificate file is empty");
+ caCertificateList << cert;
+ }
+ m_applicationInstaller->setCACertificates(caCertificateList);
+ }
+
+ uint minUserId, maxUserId, commonGroupId;
+ if (m_config.applicationUserIdSeparation(&minUserId, &maxUserId, &commonGroupId)) {
+# if defined(Q_OS_LINUX)
+ if (!m_applicationInstaller->enableApplicationUserIdSeparation(minUserId, maxUserId, commonGroupId))
+ throw Exception("could not enable application user-id separation in the installer.");
+# else
+ qCCritical(LogSystem) << "WARNING: application user-id separation requested, but not possible on this platform.";
+# endif // Q_OS_LINUX
+ }
+
+ //TODO: this could be delayed, but needs to have a lock on the app-db in this case
+ m_applicationInstaller->cleanupBrokenInstallations();
+
+ StartupTimer::instance()->checkpoint("after ApplicationInstaller instantiation");
+#endif // AM_DISABLE_INSTALLER
+}
+
+void Main::setupQmlEngine()
+{
+ qmlRegisterType<QmlInProcessNotification>("QtApplicationManager", 1, 0, "Notification");
+ qmlRegisterType<QmlInProcessApplicationInterfaceExtension>("QtApplicationManager", 1, 0, "ApplicationInterfaceExtension");
+
+#if !defined(AM_HEADLESS)
+ qmlRegisterType<FakeApplicationManagerWindow>("QtApplicationManager", 1, 0, "ApplicationManagerWindow");
+#endif
+ StartupTimer::instance()->checkpoint("after QML registrations");
+
+ m_engine = new QQmlApplicationEngine(this);
+ new QmlLogger(m_engine);
+ m_engine->setOutputWarningsToStandardError(false);
+ m_engine->setImportPathList(m_engine->importPathList() + m_config.importPaths());
+ m_engine->rootContext()->setContextProperty("StartupTimer", StartupTimer::instance());
+
+ StartupTimer::instance()->checkpoint("after QML engine instantiation");
+
+#if defined(AM_TESTRUNNER)
+ QFile f(qSL(":/build-config.yaml"));
+ QVector<QVariant> docs;
+ if (f.open(QFile::ReadOnly))
+ docs = QtYaml::variantDocumentsFromYaml(f.readAll());
+ f.close();
+ m_engine->rootContext()->setContextProperty("buildConfig", docs.toList());
+#endif
+}
+
+void Main::setupWindowTitle()
+{
+#if !defined(AM_HEADLESS)
+ // For development only: set an icon, so you know which window is the AM
+ bool setIcon =
+# if defined(Q_OS_LINUX)
+ (platformName() == qL1S("xcb"));
+# else
+ true;
+# endif
+ if (Q_UNLIKELY(setIcon)) {
+ QString icon = m_config.windowIcon();
+ if (!icon.isEmpty())
+ QGuiApplication::setWindowIcon(QIcon(icon));
+ }
+ //TODO: set window title via QGuiApplication::setApplicationDisplayName()
+#endif // AM_HEADLESS
+}
+
+void Main::setupWindowManager()
+{
+#if !defined(AM_HEADLESS)
+ QUnifiedTimer::instance()->setSlowModeEnabled(m_config.slowAnimations());
+
+ m_windowManager = WindowManager::createInstance(m_engine, m_config.waylandSocketName());
+ m_windowManager->enableWatchdog(!m_config.noUiWatchdog());
+
+ QObject::connect(m_applicationManager, &ApplicationManager::inProcessRuntimeCreated,
+ m_windowManager, &WindowManager::setupInProcessRuntime);
+ QObject::connect(m_applicationManager, &ApplicationManager::applicationWasActivated,
+ m_windowManager, &WindowManager::raiseApplicationWindow);
+#endif
+}
+
+
+void Main::loadQml()
+{
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->beforeQmlEngineLoad(m_engine);
+
+ if (Q_UNLIKELY(m_config.loadDummyData())) {
+ loadDummyDataFiles();
+ StartupTimer::instance()->checkpoint("after loading dummy-data");
+ }
+
+ m_engine->load(m_mainQml);
+ if (Q_UNLIKELY(m_engine->rootObjects().isEmpty()))
+ throw Exception("Qml scene does not have a root object");
+
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->afterQmlEngineLoad(m_engine);
+
+ StartupTimer::instance()->checkpoint("after loading main QML file");
+}
+
+void Main::showWindow()
+{
+#if !defined(AM_HEADLESS)
+ QQuickWindow *window = nullptr;
+ QObject *rootObject = m_engine->rootObjects().constFirst();
+
+ if (!rootObject->isWindowType()) {
+ m_view = new QQuickView(m_engine, 0);
+ StartupTimer::instance()->checkpoint("after WindowManager/QuickView instantiation");
+ m_view->setContent(m_mainQml, 0, rootObject);
+ window = m_view;
+ } else {
+ window = qobject_cast<QQuickWindow *>(rootObject);
+ if (!m_engine->incubationController())
+ m_engine->setIncubationController(window->incubationController());
+ }
+ Q_ASSERT(window);
+
+ static QMetaObject::Connection conn = QObject::connect(window, &QQuickWindow::frameSwapped, this, []() {
+ // this is a queued signal, so there may be still one in the queue after calling disconnect()
+ if (conn) {
+ QObject::disconnect(conn);
+ StartupTimer::instance()->checkpoint("after first frame drawn");
+ StartupTimer::instance()->createReport(qSL("System UI"));
+ }
+ });
+
+ m_windowManager->registerCompositorView(window);
+
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->beforeWindowShow(window);
+
+ // --no-fullscreen on the command line trumps the fullscreen setting in the config file
+ if (Q_LIKELY(m_config.fullscreen() && !m_config.noFullscreen()))
+ window->showFullScreen();
+ else
+ window->show();
+
+ for (auto iface : qAsConst(m_startupPlugins))
+ iface->afterWindowShow(window);
+
+ StartupTimer::instance()->checkpoint("after window show");
+#endif
+}
+
+void Main::setupDebugWrappers()
+{
+ // delay debug-wrapper setup
+ //TODO: find a better solution than hardcoding an 1.5 sec delay
+ QTimer::singleShot(1500, this, [this]() {
+ m_applicationManager->setDebugWrapperConfiguration(m_config.debugWrappers());
+ });
+}
+
+void Main::setupShellServer()
+{
+ //TODO: could be delayed as well
+#if defined(QT_PSHELLSERVER_LIB)
+ struct AMShellFactory : public PAbstractShellFactory
+ {
+ public:
+ AMShellFactory(QQmlEngine *engine, QObject *object)
+ : m_engine(engine)
+ , m_object(object)
+ {
+ Q_ASSERT(engine);
+ Q_ASSERT(object);
+ }
+
+ PAbstractShell *create(QObject *parent)
+ {
+ return new PDeclarativeShell(m_engine, m_object, parent);
+ }
+
+ private:
+ QQmlEngine *m_engine;
+ QObject *m_object;
+ };
+
+ // have a JavaScript shell reachable via telnet protocol
+ PTelnetServer telnetServer;
+ AMShellFactory shellFactory(m_engine, m_engine->rootObjects().constFirst());
+ telnetServer.setShellFactory(&shellFactory);
+
+ if (!telnetServer.listen(QHostAddress(m_config.telnetAddress()), m_config.telnetPort())) {
+ throw Exception("could not start Telnet server");
+ } else {
+ qCDebug(LogSystem) << "Telnet server listening on \n " << telnetServer.serverAddress().toString()
+ << "port" << telnetServer.serverPort();
+ }
+
+ // register all objects that should be reachable from the telnet shell
+ m_engine->rootContext()->setContextProperty("_ApplicationManager", m_am);
+ m_engine->rootContext()->setContextProperty("_WindowManager", m_wm);
+#endif // QT_PSHELLSERVER_LIB
+}
+
+void Main::setupSSDPService()
+{
+ //TODO: could be delayed as well
+#if defined(QT_PSSDP_LIB)
+ // announce ourselves via SSDP (the discovery protocol of UPnP)
+
+ QUuid uuid = QUuid::createUuid(); // should really be QUuid::Time version...
+ PSsdpService ssdp;
+
+ bool ssdpOk = ssdp.initialize();
+ if (!ssdpOk) {
+ throw Exception(LogSystem, "could not initialze SSDP service");
+ } else {
+ ssdp.setDevice(uuid, QLatin1String("application-manager"), 1, QLatin1String("pelagicore.com"));
+
+# if defined(QT_PSHELLSERVER_LIB)
+ QMap<QString, QString> extraTelnet;
+ extraTelnet.insert("LOCATION", QString::fromLatin1("${HOST}:%1").arg(telnetServer.serverPort()));
+ ssdp.addService(QLatin1String("jsshell"), 1, QLatin1String("pelagicore.com"), extraTelnet);
+# endif // QT_PSHELLSERVER_LIB
+
+ ssdp.setActive(true);
+ }
+ m_engine->rootContext()->setContextProperty("ssdp", &ssdp);
+#endif // QT_PSSDP_LIB
+}
+
+QString Main::dbusInterfaceName(QObject *o)
+{
+#if defined(QT_DBUS_LIB)
int idx = o->metaObject()->indexOfClassInfo("D-Bus Interface");
if (idx < 0) {
throw Exception("Could not get class-info \"D-Bus Interface\" for D-Bus adapter %1")
.arg(o->metaObject()->className());
}
return QLatin1String(o->metaObject()->classInfo(idx).value());
+#else
+ return QString();
+#endif
}
-static void registerDBusObject(QDBusAbstractAdaptor *adaptor, const char *serviceName, const char *path) throw (Exception)
+void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const char *serviceName, const char *path)
{
+#if defined(QT_DBUS_LIB)
QString interfaceName = dbusInterfaceName(adaptor);
- QString dbusName = configuration->dbusRegistration(interfaceName);
+ QString dbusName = m_config.dbusRegistration(interfaceName);
QString dbusAddress;
QDBusConnection conn((QString()));
@@ -162,10 +823,10 @@ static void registerDBusObject(QDBusAbstractAdaptor *adaptor, const char *servic
return;
} else if (dbusName == qL1S("system")) {
dbusAddress = qgetenv("DBUS_SYSTEM_BUS_ADDRESS");
-#if defined(Q_OS_LINUX)
+# if defined(Q_OS_LINUX)
if (dbusAddress.isEmpty())
dbusAddress = qL1S("unix:path=/var/run/dbus/system_bus_socket");
-#endif
+# endif
conn = QDBusConnection::systemBus();
} else if (dbusName == qL1S("session")) {
dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS");
@@ -212,67 +873,61 @@ static void registerDBusObject(QDBusAbstractAdaptor *adaptor, const char *servic
atexit([]() { foreach (const QString &ftd, filesToDelete) QFile::remove(ftd); });
filesToDelete << f.fileName();
}
-
- static bool once = false;
- if (!once) {
- once = true;
- registerDBusTypes();
- }
+#endif // QT_DBUS_LIB
}
-static void dbusInitialization()
+void Main::registerDBusInterfaces() Q_DECL_NOEXCEPT
{
+#if defined(QT_DBUS_LIB)
+ registerDBusTypes();
+
try {
qCDebug(LogSystem) << "Registering D-Bus services:";
- auto am = ApplicationManager::instance();
- auto ama = new ApplicationManagerAdaptor(am);
+ auto ama = new ApplicationManagerAdaptor(m_applicationManager);
// connect this signal manually, since it needs a type conversion
// (the automatic signal relay fails in this case)
- QObject::connect(am, &ApplicationManager::applicationRunStateChanged,
+ QObject::connect(m_applicationManager, &ApplicationManager::applicationRunStateChanged,
ama, &ApplicationManagerAdaptor::applicationRunStateChanged);
registerDBusObject(ama, "io.qt.ApplicationManager", "/ApplicationManager");
- if (!am->setDBusPolicy(configuration->dbusPolicy(dbusInterfaceName(am))))
+ if (!m_applicationManager->setDBusPolicy(m_config.dbusPolicy(dbusInterfaceName(m_applicationManager))))
throw Exception(Error::DBus, "could not set DBus policy for ApplicationManager");
# if !defined(AM_DISABLE_INSTALLER)
- auto ai = ApplicationInstaller::instance();
- registerDBusObject(new ApplicationInstallerAdaptor(ai), "io.qt.ApplicationManager", "/ApplicationInstaller");
- if (!ai->setDBusPolicy(configuration->dbusPolicy(dbusInterfaceName(ai))))
+ registerDBusObject(new ApplicationInstallerAdaptor(m_applicationInstaller), "io.qt.ApplicationManager", "/ApplicationInstaller");
+ if (!m_applicationInstaller->setDBusPolicy(m_config.dbusPolicy(dbusInterfaceName(m_applicationInstaller))))
throw Exception(Error::DBus, "could not set DBus policy for ApplicationInstaller");
# endif
# if !defined(AM_HEADLESS)
try {
- auto nm = NotificationManager::instance();
- registerDBusObject(new NotificationsAdaptor(nm), "org.freedesktop.Notifications", "/org/freedesktop/Notifications");
+ registerDBusObject(new NotificationsAdaptor(m_notificationManager), "org.freedesktop.Notifications", "/org/freedesktop/Notifications");
} catch (const Exception &e) {
//TODO: what should we do here? on the desktop this will obviously always fail
qCCritical(LogSystem) << "WARNING:" << e.what();
}
- auto wm = WindowManager::instance();
- registerDBusObject(new WindowManagerAdaptor(wm), "io.qt.ApplicationManager", "/WindowManager");
- if (!wm->setDBusPolicy(configuration->dbusPolicy(dbusInterfaceName(wm))))
+ registerDBusObject(new WindowManagerAdaptor(m_windowManager), "io.qt.ApplicationManager", "/WindowManager");
+ if (!m_windowManager->setDBusPolicy(m_config.dbusPolicy(dbusInterfaceName(m_windowManager))))
throw Exception(Error::DBus, "could not set DBus policy for WindowManager");
#endif
} catch (const std::exception &e) {
qCCritical(LogSystem) << "ERROR:" << e.what();
qApp->exit(2);
}
-}
-
#endif // QT_DBUS_LIB
-
+}
// copied straight from Qt 5.1.0 qmlscene/main.cpp for now - needs to be revised
-static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
+void Main::loadDummyDataFiles()
{
+ QString directory = QFileInfo(m_mainQml).path();
+
QDir dir(directory + qSL("/dummydata"), qSL("*.qml"));
QStringList list = dir.entryList();
for (int i = 0; i < list.size(); ++i) {
QString qml = list.at(i);
- QQmlComponent comp(&engine, dir.filePath(qml));
+ QQmlComponent comp(m_engine, dir.filePath(qml));
QObject *dummyData = comp.create();
if (comp.isError()) {
@@ -284,38 +939,13 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
if (dummyData) {
qWarning() << "Loaded dummy data:" << dir.filePath(qml);
qml.truncate(qml.length()-4);
- engine.rootContext()->setContextProperty(qml, dummyData);
- dummyData->setParent(&engine);
+ m_engine->rootContext()->setContextProperty(qml, dummyData);
+ dummyData->setParent(m_engine);
}
}
}
-#if defined(QT_PSHELLSERVER_LIB)
-
-struct AMShellFactory : public PAbstractShellFactory
-{
-public:
- AMShellFactory(QQmlEngine *engine, QObject *object)
- : m_engine(engine)
- , m_object(object)
- {
- Q_ASSERT(engine);
- Q_ASSERT(object);
- }
-
- PAbstractShell *create(QObject *parent)
- {
- return new PDeclarativeShell(m_engine, m_object, parent);
- }
-
-private:
- QQmlEngine *m_engine;
- QObject *m_object;
-};
-
-#endif // QT_PSHELLSERVER_LIB
-
-static QVector<const Application *> scanForApplication(const QString &singleAppInfoYaml, const QStringList &builtinAppsDirs)
+QVector<const Application *> Main::scanForApplication(const QString &singleAppInfoYaml, const QStringList &builtinAppsDirs)
{
QVector<const Application *> result;
YamlApplicationScanner yas;
@@ -359,16 +989,9 @@ static QVector<const Application *> scanForApplication(const QString &singleAppI
return result;
}
-#if !defined(AM_DISABLE_INSTALLER)
-static QVector<const Application *> scanForApplications(const QStringList &builtinAppsDirs, const QString &installedAppsDir,
+QVector<const Application *> Main::scanForApplications(const QStringList &builtinAppsDirs, const QString &installedAppsDir,
const QVector<InstallationLocation> &installationLocations)
{
-#else
-static QVector<const Application *> scanForApplications(const QStringList &builtinAppsDirs)
-{
- int installationLocations; // dummy variable to get rid of #ifdef within lambda below
-#endif
-
QVector<const Application *> result;
YamlApplicationScanner yas;
@@ -461,560 +1084,4 @@ static QVector<const Application *> scanForApplications(const QStringList &built
return result;
}
-struct SystemProperties
-{
- enum {
- ThirdParty,
- BuiltIn,
- SystemUi
- };
-
- static QVector<QVariantMap> partition(const QVariantMap &rawMap)
- {
- QVector<QVariantMap> props(SystemUi + 1);
-
- props[ThirdParty] = rawMap.value(qSL("public")).toMap();
-
- props[BuiltIn] = props.at(ThirdParty);
- const QVariantMap pro = rawMap.value(qSL("protected")).toMap();
- for (auto it = pro.cbegin(); it != pro.cend(); ++it)
- props[BuiltIn].insert(it.key(), it.value());
-
- props[SystemUi] = props.at(BuiltIn);
- const QVariantMap pri = rawMap.value(qSL("private")).toMap();
- for (auto it = pri.cbegin(); it != pri.cend(); ++it)
- props[SystemUi].insert(it.key(), it.value());
-
- return props;
- }
-};
-
QT_END_NAMESPACE_AM
-
-QT_USE_NAMESPACE_AM
-
-int main(int argc, char *argv[])
-{
- StartupTimer::instance()->checkpoint("entered main");
-
- QCoreApplication::setApplicationName(qSL("ApplicationManager"));
- QCoreApplication::setOrganizationName(qSL("Pelagicore AG"));
- QCoreApplication::setOrganizationDomain(qSL("pelagicore.com"));
- QCoreApplication::setApplicationVersion(qSL(AM_VERSION));
- for (int i = 1; i < argc; ++i) {
- if (strcmp("--no-dlt-logging", argv[i]) == 0) {
- Logging::setDltEnabled(false);
- break;
- }
- }
- Logging::initialize();
-
- QString error;
-
- StartupTimer::instance()->checkpoint("after basic initialization");
-
-#if !defined(AM_DISABLE_INSTALLER)
- ensureCorrectLocale();
-
- if (Q_UNLIKELY(!forkSudoServer(DropPrivilegesPermanently, &error))) {
- qCCritical(LogSystem) << "ERROR:" << qPrintable(error);
- return 2;
- }
-#endif
-
- StartupTimer::instance()->checkpoint("after sudo server fork");
-
-#if defined(AM_HEADLESS)
- QCoreApplication a(argc, argv);
-#else
- // this is needed for both WebEngine and Wayland Multi-screen rendering
- QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
-# if !defined(QT_NO_SESSIONMANAGER)
- QGuiApplication::setFallbackSessionManagementEnabled(false);
-#endif
- QGuiApplication a(argc, argv);
-
- UnixSignalHandler::instance()->install(UnixSignalHandler::ForwardedToEventLoopHandler, SIGINT,
- [](int /*sig*/) {
- UnixSignalHandler::instance()->resetToDefault(SIGINT);
-
- qCritical(" > RECEIVED CTRL+C ... EXITING\n");
-
- auto windows = qApp->allWindows();
- for (QWindow *w : windows)
- w->metaObject()->invokeMethod(w, "close");
- });
-#endif
-
- StartupTimer::instance()->checkpoint("after application constructor");
-
- Configuration config_;
- configuration = &config_;
-
- StartupTimer::instance()->checkpoint("after command line parse");
-
- CrashHandler::setCrashActionConfiguration(configuration->managerCrashAction());
-
-#if !defined(QT_NO_QML_DEBUGGER)
- QQmlDebuggingEnabler *debuggingEnabler = nullptr;
- if (configuration->qmlDebugging())
- debuggingEnabler = new QQmlDebuggingEnabler(true);
-#else
- if (configuration->qmlDebugging())
- qCWarning(LogSystem) << "The --qml-debug option is ignored, because Qt was built without support for QML Debugging!";
-#endif
-
- try {
- QStringList loggingRules = configuration->loggingRules();
- bool verbose = configuration->verbose();
-
- if (loggingRules.isEmpty() && !verbose)
- loggingRules.append(qSL("*.debug=false"));
-
- if (verbose) {
- // we are prepending here to allow the user to override these in the config file.
- loggingRules.prepend(qSL("qt.qpa.*.debug=false"));
- loggingRules.prepend(qSL("qt.quick.*.debug=false"));
- loggingRules.prepend(qSL("qt.scenegraph.*.debug=false"));
- loggingRules.prepend(qSL("qt.compositor.input.*.debug=false"));
- loggingRules.prepend(qSL("*.debug=true"));
- }
-
- QLoggingCategory::setFilterRules(loggingRules.join(qL1C('\n')));
-
- // setting this for child processes //TODO: use a more generic IPC approach
- qputenv("AM_LOGGING_RULES", loggingRules.join(qL1C('\n')).toUtf8());
-
- Logging::registerUnregisteredDltContexts();
-
- StartupTimer::instance()->checkpoint("after logging setup");
-
-#ifdef AM_TESTRUNNER
- TestRunner::initialize(argv[0], configuration->positionalArguments());
-#endif
-
- auto startupPlugins = loadPlugins<StartupInterface>("startup", configuration->pluginFilePaths("startup"));
-
- const QVector<QVariantMap> sysProps = SystemProperties::partition(configuration->rawSystemProperties());
- foreach (StartupInterface *iface, startupPlugins)
- iface->initialize(sysProps.at(SystemProperties::SystemUi));
-
- StartupTimer::instance()->checkpoint("after startup-plugin load");
-
-#if defined(QT_DBUS_LIB)
- if (Q_UNLIKELY(configuration->dbusStartSessionBus())) {
- class DBusDaemonProcess : public QProcess // clazy:exclude=missing-qobject-macro
- {
- public:
- DBusDaemonProcess(QObject *parent = 0)
- : QProcess(parent)
- {
- setProgram(qSL("dbus-daemon"));
- setArguments({ qSL("--nofork"), qSL("--print-address"), qSL("--session")});
- }
- ~DBusDaemonProcess() override
- {
- kill();
- waitForFinished();
- }
-
- protected:
- void setupChildProcess() override
- {
-# if defined(Q_OS_LINUX)
- // at least on Linux we can make sure that those dbus-daemons are always killed
- prctl(PR_SET_PDEATHSIG, SIGKILL);
-# endif
- QProcess::setupChildProcess();
- }
- };
-
- auto dbusDaemon = new DBusDaemonProcess(&a);
- dbusDaemon->start(QIODevice::ReadOnly);
- if (!dbusDaemon->waitForStarted() || !dbusDaemon->waitForReadyRead())
- throw Exception("could not start a dbus-launch process: %1").arg(dbusDaemon->errorString());
-
- QByteArray busAddress = dbusDaemon->readAllStandardOutput().trimmed();
- qputenv("DBUS_SESSION_BUS_ADDRESS", busAddress);
- qCInfo(LogSystem) << "NOTICE: running on private D-Bus session bus to avoid conflicts:";
- qCInfo(LogSystem, " DBUS_SESSION_BUS_ADDRESS=%s", busAddress.constData());
-
- StartupTimer::instance()->checkpoint("after starting session D-Bus");
- }
-#endif
- if (Q_UNLIKELY(!QFile::exists(configuration->mainQmlFile())))
- throw Exception("no/invalid main QML file specified: %1").arg(configuration->mainQmlFile());
-
-#if !defined(AM_DISABLE_INSTALLER)
- if (!checkCorrectLocale()) {
- // we should really throw here, but so many embedded systems are badly set up
- qCCritical(LogSystem) << "WARNING: the appman installer needs a UTF-8 locale to work correctly:\n"
- " even automatically switching to C.UTF-8 or en_US.UTF-8 failed.";
- }
-
- if (Q_UNLIKELY(hardwareId().isEmpty()))
- throw Exception("the installer is enabled, but the device-id is empty");
-
- QVector<InstallationLocation> installationLocations = InstallationLocation::parseInstallationLocations(configuration->installationLocations());
-
- if (Q_UNLIKELY(!QDir::root().mkpath(configuration->installedAppsManifestDir())))
- throw Exception("could not create manifest directory %1").arg(configuration->installedAppsManifestDir());
-
- if (Q_UNLIKELY(!QDir::root().mkpath(configuration->appImageMountDir())))
- throw Exception("could not create the image-mount directory %1").arg(configuration->appImageMountDir());
-
- StartupTimer::instance()->checkpoint("after installer setup checks");
-#endif
-
- bool forceSingleProcess = configuration->forceSingleProcess();
- bool forceMultiProcess = configuration->forceMultiProcess();
-
- if (forceMultiProcess && forceSingleProcess)
- throw Exception("You cannot enforce multi- and single-process mode at the same time.");
-
-#if !defined(AM_MULTI_PROCESS)
- if (forceMultiProcess)
- throw Exception("This application manager build is not multi-process capable.");
- forceSingleProcess = true;
-#endif
-
- if (forceSingleProcess) {
- RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager());
- RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager(qSL("qml")));
- } else {
- RuntimeFactory::instance()->registerRuntime(new QmlInProcessRuntimeManager());
-#if defined(AM_NATIVE_RUNTIME_AVAILABLE)
- RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager());
- RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(qSL("qml")));
- //RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(qSL("html")));
-#endif
-#if defined(AM_HOST_CONTAINER_AVAILABLE)
- ContainerFactory::instance()->registerContainer(new ProcessContainerManager());
-#endif
- auto containerPlugins = loadPlugins<ContainerManagerInterface>("container", configuration->pluginFilePaths("container"));
- foreach (ContainerManagerInterface *iface, containerPlugins)
- ContainerFactory::instance()->registerContainer(new PluginContainerManager(iface));
- }
- foreach (StartupInterface *iface, startupPlugins)
- iface->afterRuntimeRegistration();
-
- ContainerFactory::instance()->setConfiguration(configuration->containerConfigurations());
- RuntimeFactory::instance()->setConfiguration(configuration->runtimeConfigurations());
-
- RuntimeFactory::instance()->setSystemProperties(sysProps.at(SystemProperties::ThirdParty),
- sysProps.at(SystemProperties::BuiltIn));
-
- StartupTimer::instance()->checkpoint("after runtime registration");
-
- QScopedPointer<ApplicationDatabase> adb(configuration->singleApp().isEmpty()
- ? new ApplicationDatabase(configuration->database())
- : new ApplicationDatabase());
-
- if (Q_UNLIKELY(!adb->isValid() && !configuration->recreateDatabase()))
- throw Exception("database file %1 is not a valid application database: %2").arg(adb->name(), adb->errorString());
-
- if (!adb->isValid() || configuration->recreateDatabase()) {
- QVector<const Application *> apps;
-
- if (!configuration->singleApp().isEmpty()) {
- apps = scanForApplication(configuration->singleApp(), configuration->builtinAppsManifestDirs());
- } else {
- apps = scanForApplications(configuration->builtinAppsManifestDirs()
-#if !defined(AM_DISABLE_INSTALLER)
- , configuration->installedAppsManifestDir(), installationLocations
-#endif
- );
- }
-
- qCDebug(LogSystem) << "Registering applications:";
- foreach (const Application *app, apps)
- qCDebug(LogSystem).nospace().noquote() << " * " << app->id() << " [at: " << app->codeDir().path() << "]";
-
- adb->write(apps);
- qDeleteAll(apps);
- }
-
- StartupTimer::instance()->checkpoint("after application database loading");
-
- ApplicationManager *am = ApplicationManager::createInstance(adb.take(), forceSingleProcess, &error);
- if (Q_UNLIKELY(!am))
- throw Exception(Error::System, error);
- if (configuration->noSecurity())
- am->setSecurityChecksEnabled(false);
-
- am->setSystemProperties(sysProps.at(SystemProperties::SystemUi));
- am->setContainerSelectionConfiguration(configuration->containerSelectionConfiguration());
-
- StartupTimer::instance()->checkpoint("after ApplicationManager instantiation");
-
- NotificationManager *nm = NotificationManager::createInstance();
-
- StartupTimer::instance()->checkpoint("after NotificationManager instantiation");
-
- SystemMonitor *sysmon = SystemMonitor::createInstance();
-
- StartupTimer::instance()->checkpoint("after SystemMonitor instantiation");
-
- QuickLauncher *ql = QuickLauncher::instance();
- ql->initialize(configuration->quickLaunchRuntimesPerContainer(), configuration->quickLaunchIdleLoad());
-
- StartupTimer::instance()->checkpoint("after quick-launcher setup");
-
-#if !defined(AM_DISABLE_INSTALLER)
- ApplicationInstaller *ai = ApplicationInstaller::createInstance(installationLocations,
- configuration->installedAppsManifestDir(),
- configuration->appImageMountDir(),
- &error);
- if (Q_UNLIKELY(!ai))
- throw Exception(Error::System, error);
- if (configuration->noSecurity()) {
- ai->setDevelopmentMode(true);
- ai->setAllowInstallationOfUnsignedPackages(true);
- } else {
- QList<QByteArray> caCertificateList;
-
- const auto caFiles = configuration->caCertificates();
- for (const auto &caFile : caFiles) {
- QFile f(caFile);
- if (Q_UNLIKELY(!f.open(QFile::ReadOnly)))
- throw Exception(f, "could not open CA-certificate file");
- QByteArray cert = f.readAll();
- if (Q_UNLIKELY(cert.isEmpty()))
- throw Exception(f, "CA-certificate file is empty");
- caCertificateList << cert;
- }
- ai->setCACertificates(caCertificateList);
- }
-
- uint minUserId, maxUserId, commonGroupId;
- if (configuration->applicationUserIdSeparation(&minUserId, &maxUserId, &commonGroupId)) {
-# if defined(Q_OS_LINUX)
- if (!ai->enableApplicationUserIdSeparation(minUserId, maxUserId, commonGroupId))
- throw Exception("could not enable application user-id separation in the installer.");
-# else
- qCCritical(LogSystem) << "WARNING: application user-id separation requested, but not possible on this platform.";
-# endif // Q_OS_LINUX
- }
-
- //TODO: this could be delayed, but needs to have a lock on the app-db in this case
- ai->cleanupBrokenInstallations();
-
- StartupTimer::instance()->checkpoint("after ApplicationInstaller instantiation");
-
-#endif // AM_DISABLE_INSTALLER
-
- qmlRegisterType<QmlInProcessNotification>("QtApplicationManager", 1, 0, "Notification");
- qmlRegisterType<QmlInProcessApplicationInterfaceExtension>("QtApplicationManager", 1, 0, "ApplicationInterfaceExtension");
-
-#if !defined(AM_HEADLESS)
- qmlRegisterType<FakeApplicationManagerWindow>("QtApplicationManager", 1, 0, "ApplicationManagerWindow");
-#endif
- StartupTimer::instance()->checkpoint("after QML registrations");
-
- ApplicationIPCManager *aipcm = ApplicationIPCManager::createInstance();
-
- QQmlApplicationEngine *engine = new QQmlApplicationEngine(&a);
- new QmlLogger(engine);
- engine->setOutputWarningsToStandardError(false);
- engine->setImportPathList(engine->importPathList() + configuration->importPaths());
- engine->rootContext()->setContextProperty("StartupTimer", StartupTimer::instance());
-
- StartupTimer::instance()->checkpoint("after QML engine instantiation");
-
-#ifdef AM_TESTRUNNER
- QFile f(qSL(":/build-config.yaml"));
- QVector<QVariant> docs;
- if (f.open(QFile::ReadOnly))
- docs = QtYaml::variantDocumentsFromYaml(f.readAll());
- f.close();
-
- engine->rootContext()->setContextProperty("buildConfig", docs.toList());
-#endif
-
-#if !defined(AM_HEADLESS)
-
- // For development only: set an icon, so you know which window is the AM
- bool setIcon =
-# if defined(Q_OS_LINUX)
- (a.platformName() == qL1S("xcb"));
-# else
- true;
-# endif
- if (Q_UNLIKELY(setIcon)) {
- QString icon = configuration->windowIcon();
- if (!icon.isEmpty())
- QGuiApplication::setWindowIcon(QIcon(icon));
- }
-
- QUnifiedTimer::instance()->setSlowModeEnabled(configuration->slowAnimations());
-
- WindowManager *wm = WindowManager::createInstance(engine, configuration->waylandSocketName());
- wm->enableWatchdog(!configuration->noUiWatchdog());
-
- QObject::connect(am, &ApplicationManager::inProcessRuntimeCreated,
- wm, &WindowManager::setupInProcessRuntime);
- QObject::connect(am, &ApplicationManager::applicationWasActivated,
- wm, &WindowManager::raiseApplicationWindow);
-#endif
-
- if (Q_UNLIKELY(configuration->loadDummyData())) {
- loadDummyDataFiles(*engine, QFileInfo(configuration->mainQmlFile()).path());
- StartupTimer::instance()->checkpoint("after loading dummy-data");
- }
-
-#if defined(QT_DBUS_LIB)
- // can we delay the D-Bus initialization? it is asynchronous anyway...
- int dbusDelay = configuration->dbusRegistrationDelay();
- if (dbusDelay < 0) {
- dbusInitialization();
- StartupTimer::instance()->checkpoint("after D-Bus registrations");
- } else {
- QTimer::singleShot(dbusDelay, qApp, dbusInitialization);
- }
-#endif
- foreach (StartupInterface *iface, startupPlugins)
- iface->beforeQmlEngineLoad(engine);
-
- engine->load(configuration->mainQmlFile());
- if (Q_UNLIKELY(engine->rootObjects().isEmpty()))
- throw Exception("Qml scene does not have a root object");
- QObject *rootObject = engine->rootObjects().at(0);
-
- foreach (StartupInterface *iface, startupPlugins)
- iface->afterQmlEngineLoad(engine);
-
- StartupTimer::instance()->checkpoint("after loading main QML file");
-
-#if !defined(AM_HEADLESS)
- QQuickWindow *window = nullptr;
- QQuickView *view = nullptr; // only set if we allocate the window ourselves
-
- if (!rootObject->isWindowType()) {
- view = new QQuickView(engine, 0);
- StartupTimer::instance()->checkpoint("after WindowManager/QuickView instantiation");
- view->setContent(configuration->mainQmlFile(), 0, rootObject);
- window = view;
- } else {
- window = qobject_cast<QQuickWindow *>(rootObject);
- if (!engine->incubationController())
- engine->setIncubationController(window->incubationController());
- }
-
- Q_ASSERT(window);
- static QMetaObject::Connection conn = QObject::connect(window, &QQuickWindow::frameSwapped, qApp, []() { // clazy:exclude=lambda-in-connect
- // this is a queued signal, so there may be still one in the queue after calling disconnect()
- if (conn) {
- QObject::disconnect(conn);
- StartupTimer::instance()->checkpoint("after first frame drawn");
- StartupTimer::instance()->createReport(qSL("System UI"));
- }
- });
-
- wm->registerCompositorView(window);
-
- foreach (StartupInterface *iface, startupPlugins)
- iface->beforeWindowShow(window);
-
- // --no-fullscreen on the command line trumps the fullscreen setting in the config file
- if (Q_LIKELY(configuration->fullscreen() && !configuration->noFullscreen()))
- window->showFullScreen();
- else
- window->show();
-
- foreach (StartupInterface *iface, startupPlugins)
- iface->afterWindowShow(window);
-
- StartupTimer::instance()->checkpoint("after window show");
-#endif
-
- // delay debug-wrapper setup
- QTimer::singleShot(1500, qApp, []() {
- ApplicationManager::instance()->setDebugWrapperConfiguration(configuration->debugWrappers());
- });
-
-#if defined(QT_PSHELLSERVER_LIB)
- // have a JavaScript shell reachable via telnet protocol
- PTelnetServer telnetServer;
- AMShellFactory shellFactory(engine, rootObject);
- telnetServer.setShellFactory(&shellFactory);
-
- if (!telnetServer.listen(QHostAddress(configuration->telnetAddress()), configuration->telnetPort())) {
- throw Exception("could not start Telnet server");
- } else {
- qCDebug(LogSystem) << "Telnet server listening on \n " << telnetServer.serverAddress().toString()
- << "port" << telnetServer.serverPort();
- }
-
- // register all objects that should be reachable from the telnet shell
- engine->rootContext()->setContextProperty("_ApplicationManager", am);
- engine->rootContext()->setContextProperty("_WindowManager", wm);
-#endif // QT_PSHELLSERVER_LIB
-
-#if defined(QT_PSSDP_LIB)
- // announce ourselves via SSDP (the discovery protocol of UPnP)
-
- QUuid uuid = QUuid::createUuid(); // should really be QUuid::Time version...
- PSsdpService ssdp;
-
- bool ssdpOk = ssdp.initialize();
- if (!ssdpOk) {
- throw Exception(LogSystem, "could not initialze SSDP service");
- } else {
- ssdp.setDevice(uuid, QLatin1String("application-manager"), 1, QLatin1String("pelagicore.com"));
-
-# if defined(QT_PSHELLSERVER_LIB)
- QMap<QString, QString> extraTelnet;
- extraTelnet.insert("LOCATION", QString::fromLatin1("${HOST}:%1").arg(telnetServer.serverPort()));
- ssdp.addService(QLatin1String("jsshell"), 1, QLatin1String("pelagicore.com"), extraTelnet);
-# endif // QT_PSHELLSERVER_LIB
-
- ssdp.setActive(true);
- }
- engine->rootContext()->setContextProperty("ssdp", &ssdp);
-#endif // QT_PSSDP_LIB
-
- QObject::connect(qApp, &QCoreApplication::aboutToQuit,
- ApplicationManager::instance(), &ApplicationManager::killAll);
-#ifdef AM_TESTRUNNER
- Q_UNUSED(nm);
- Q_UNUSED(sysmon);
- Q_UNUSED(aipcm);
-#if !defined(QT_NO_QML_DEBUGGER)
- Q_UNUSED(debuggingEnabler);
-#endif
-
- int res = TestRunner::exec(engine);
- delete engine;
-#else
- int res = a.exec();
-
- // the eventloop stopped, so any pending "retakes" would not be executed
- retakeSingletonOwnershipFromQmlEngine(engine, am, true);
- delete engine;
-
- delete nm;
-#if !defined(AM_HEADLESS)
- delete wm;
- delete view;
-#endif
- delete am;
- delete ql;
- delete sysmon;
- delete aipcm;
-#if !defined(QT_NO_QML_DEBUGGER)
- delete debuggingEnabler;
-#endif
-#endif
-
-#if defined(QT_PSSDP_LIB)
- if (ssdpOk)
- ssdp.setActive(false);
-#endif // QT_PSSDP_LIB
-
- return res;
-
- } catch (const std::exception &e) {
- qCCritical(LogSystem) << "ERROR:" << e.what();
- return 2;
- }
-}
diff --git a/src/manager/main.h b/src/manager/main.h
new file mode 100644
index 00000000..4f5e9833
--- /dev/null
+++ b/src/manager/main.h
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Pelagicore Application Manager.
+**
+** $QT_BEGIN_LICENSE:LGPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite licenses may use
+** this file in accordance with the commercial license agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and The Qt Company. For
+** licensing terms and conditions see https://www.qt.io/terms-conditions.
+** For further information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: LGPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtAppManCommon/global.h>
+
+#if defined(AM_HEADLESS)
+# include <QCoreApplication>
+typedef QCoreApplication MainBase;
+#else
+# include <QGuiApplication>
+typedef QGuiApplication MainBase;
+#endif
+
+#include "configuration.h"
+#include "installationlocation.h"
+
+QT_FORWARD_DECLARE_CLASS(QQmlApplicationEngine)
+QT_FORWARD_DECLARE_STRUCT(QQmlDebuggingEnabler)
+QT_FORWARD_DECLARE_CLASS(QQuickView)
+QT_FORWARD_DECLARE_CLASS(QDBusAbstractAdaptor)
+
+class StartupInterface;
+
+QT_BEGIN_NAMESPACE_AM
+
+class Application;
+class StartupTimer;
+class ApplicationIPCManager;
+class ApplicationDatabase;
+class ApplicationManager;
+class ApplicationInstaller;
+class NotificationManager;
+class WindowManager;
+class QuickLauncher;
+class SystemMonitor;
+
+class Main : public MainBase
+{
+ Q_OBJECT
+ Q_PROPERTY(bool singleProcessMode READ isSingleProcessMode)
+
+public:
+ Main(int &argc, char **argv);
+ ~Main();
+
+ bool isSingleProcessMode() const;
+
+ void setup();
+ int exec();
+
+protected:
+ void setupQmlDebugging();
+ void setupLoggingRules();
+ void loadStartupPlugins();
+ void parseSystemProperties();
+ void setupDBus();
+ void checkMainQmlFile();
+ void setupInstaller();
+ void setupSingleOrMultiProcess();
+ void setupRuntimesAndContainers();
+ void loadApplicationDatabase();
+ void setupSingletons();
+
+ void setupQmlEngine();
+ void setupWindowTitle();
+ void setupWindowManager();
+
+ void loadQml();
+ void showWindow();
+ void setupDebugWrappers();
+ void setupShellServer();
+ void setupSSDPService();
+
+ enum SystemProperties {
+ SP_ThirdParty = 0,
+ SP_BuiltIn,
+ SP_SystemUi
+ };
+
+private slots:
+ void registerDBusInterfaces() Q_DECL_NOEXCEPT;
+
+private:
+ void loadDummyDataFiles();
+ QString dbusInterfaceName(QObject *o);
+ void registerDBusObject(QDBusAbstractAdaptor *adaptor, const char *serviceName, const char *path);
+
+ static QVector<const Application *> scanForApplication(const QString &singleAppInfoYaml,
+ const QStringList &builtinAppsDirs);
+ static QVector<const Application *> scanForApplications(const QStringList &builtinAppsDirs,
+ const QString &installedAppsDir,
+ const QVector<InstallationLocation> &installationLocations);
+
+private:
+ Configuration m_config;
+ QVector<InstallationLocation> m_installationLocations;
+ bool m_isSingleProcessMode = false;
+ QString m_mainQml;
+
+ QQmlDebuggingEnabler *m_debuggingEnabler = nullptr;
+ QQmlApplicationEngine *m_engine = nullptr;
+ QQuickView *m_view = nullptr; // only set if we allocate the window ourselves
+
+ QScopedPointer<ApplicationDatabase> m_applicationDatabase;
+ ApplicationManager *m_applicationManager = nullptr;
+ ApplicationIPCManager *m_applicationIPCManager = nullptr;
+ ApplicationInstaller *m_applicationInstaller = nullptr;
+ NotificationManager *m_notificationManager = nullptr;
+ WindowManager *m_windowManager = nullptr;
+ QuickLauncher *m_quickLauncher = nullptr;
+ SystemMonitor *m_systemMonitor = nullptr;
+ QVector<StartupInterface *> m_startupPlugins;
+ QVector<QVariantMap> m_systemProperties;
+};
+
+QT_END_NAMESPACE_AM
diff --git a/src/manager/manager.pri b/src/manager/manager.pri
index e0d81654..e62421f1 100644
--- a/src/manager/manager.pri
+++ b/src/manager/manager.pri
@@ -20,6 +20,7 @@ win32:LIBS += -luser32
HEADERS += \
$$PWD/qmllogger.h \
$$PWD/configuration.h \
+ $$PWD/main.h
SOURCES += \
$$PWD/main.cpp \
diff --git a/src/tools/testrunner/testrunner.cpp b/src/tools/testrunner/testrunner.cpp
index d2eabee7..f46eb6c0 100644
--- a/src/tools/testrunner/testrunner.cpp
+++ b/src/tools/testrunner/testrunner.cpp
@@ -121,28 +121,20 @@ static QObject *testRootObject(QQmlEngine *engine, QJSEngine *jsEngine)
return QTestRootObject::instance();
}
-void TestRunner::initialize(char *name, const QStringList &positionalArguments)
+void TestRunner::initialize(const QStringList &testRunnerArguments)
{
- QuickTestResult::setCurrentAppname(name);
- QuickTestResult::setProgramName(name);
-
- //Convert all the positional arguments back into a char * array.
- QScopedArrayPointer<char *> testArgV(new char *[positionalArguments.count() + 1]);
- testArgV[0] = name;
- int testArgC = 1;
-
- static QList<QByteArray> argList;
- argList.append("");
- for (const auto &arg : positionalArguments) {
- //This is already parsed by appman
- if (arg.endsWith(qL1S(".qml")))
- continue;
- argList.append(arg.toLocal8Bit());
- testArgV[testArgC] = argList[testArgC].data();
- testArgC++;
- }
-
- QuickTestResult::parseArgs(testArgC, testArgV.data());
+ Q_ASSERT(!testRunnerArguments.isEmpty());
+
+ // Convert all the arguments back into a char * array.
+ // These need to be alive as long as the program is running!
+ static QVector<char *> testArgV;
+ for (const auto &arg : testRunnerArguments)
+ testArgV << strdup(arg.toLocal8Bit().constData());
+ atexit([]() { std::for_each(testArgV.constBegin(), testArgV.constEnd(), free); });
+
+ QuickTestResult::setCurrentAppname(testArgV.constFirst());
+ QuickTestResult::setProgramName(testArgV.constFirst());
+ QuickTestResult::parseArgs(testArgV.size(), testArgV.data());
qputenv("QT_QTESTLIB_RUNNING", "1");
// Register the test object
diff --git a/src/tools/testrunner/testrunner.h b/src/tools/testrunner/testrunner.h
index 9199bae8..717b38e2 100644
--- a/src/tools/testrunner/testrunner.h
+++ b/src/tools/testrunner/testrunner.h
@@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE_AM
class TestRunner
{
public:
- static void initialize(char *name, const QStringList &positionalArguments);
+ static void initialize(const QStringList &testRunnerArguments);
static int exec(QQmlEngine *engine);
};