Branch data Line data Source code
1 : : // Copyright (C) 2021 The Qt Company Ltd.
2 : : // Copyright (C) 2019 Luxoft Sweden AB
3 : : // Copyright (C) 2018 Pelagicore AG
4 : : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
5 : :
6 : : #include <memory>
7 : : #include <cstdlib>
8 : : #include <qglobal.h>
9 : :
10 : : #include "qtappman_common-config_p.h"
11 : :
12 : : #if defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
13 : : # include <QDBusConnection>
14 : : # include <QDBusAbstractAdaptor>
15 : : # include <QDBusServer>
16 : : # include "dbusdaemon.h"
17 : : # include "dbuspolicy.h"
18 : : # include "dbuscontextadaptor.h"
19 : : # include "applicationmanager_adaptor.h"
20 : : # include "packagemanager_adaptor.h"
21 : : # include "windowmanager_adaptor.h"
22 : : # include "notifications_adaptor.h"
23 : : #endif
24 : :
25 : : #include <QFile>
26 : : #include <QDir>
27 : : #include <QStringList>
28 : : #include <QVariant>
29 : : #include <QFileInfo>
30 : : #include <QQmlContext>
31 : : #include <QQmlComponent>
32 : : #include <QQmlApplicationEngine>
33 : : #include <QTimer>
34 : : #include <QUrl>
35 : : #include <QLibrary>
36 : : #include <QFunctionPointer>
37 : : #include <QProcess>
38 : : #include <QQmlDebuggingEnabler>
39 : : #include <QNetworkInterface>
40 : : #include <private/qabstractanimation_p.h>
41 : : #include <QGuiApplication>
42 : : #include <QQuickView>
43 : : #include <QQuickItem>
44 : : #include <QInputDevice>
45 : : #include <QLocalServer>
46 : : #include <QLibraryInfo>
47 : : #include <QStandardPaths>
48 : : #include <QLockFile>
49 : : #if defined(Q_OS_LINUX)
50 : : # include <sys/file.h>
51 : : #endif
52 : :
53 : : #include "global.h"
54 : : #include "logging.h"
55 : : #include "main.h"
56 : : #include "configuration.h"
57 : : #include "applicationmanager.h"
58 : : #include "package.h"
59 : : #include "packagemanager.h"
60 : : #include "packagedatabase.h"
61 : : #include "installationreport.h"
62 : : #include "yamlpackagescanner.h"
63 : : #include "sudo.h"
64 : : #if QT_CONFIG(am_installer)
65 : : # include "packageutilities.h"
66 : : #endif
67 : : #include "runtimefactory.h"
68 : : #include "containerfactory.h"
69 : : #include "globalruntimeconfiguration.h"
70 : : #include "quicklauncher.h"
71 : : #if QT_CONFIG(am_multi_process)
72 : : # include "processcontainer.h"
73 : : # include "nativeruntime.h"
74 : : #endif
75 : : #include "plugincontainer.h"
76 : : #include "notification.h"
77 : : #include "notificationmanager.h"
78 : : #include "qmlinprocruntime.h"
79 : : #include "qml-utilities.h"
80 : : #include "dbus-utilities.h"
81 : : #include "intentserver.h"
82 : : #include "intentaminterface.h"
83 : :
84 : : #include "windowmanager.h"
85 : : #include "applicationmanagerwindow.h"
86 : : #include "frametimer.h"
87 : : #include "gpustatus.h"
88 : :
89 : : #include "configuration.h"
90 : : #include "utilities.h"
91 : : #include "exception.h"
92 : : #include "crashhandler.h"
93 : : #include "qmllogger.h"
94 : : #include "startuptimer.h"
95 : : #include "unixsignalhandler.h"
96 : :
97 : : // monitor-lib
98 : : #include "cpustatus.h"
99 : : #include "iostatus.h"
100 : : #include "memorystatus.h"
101 : : #include "monitormodel.h"
102 : : #include "processstatus.h"
103 : :
104 : : #include "../plugin-interfaces/startupinterface.h"
105 : :
106 : : using namespace Qt::StringLiterals;
107 : : using namespace std::chrono_literals;
108 : :
109 : :
110 : : AM_QML_REGISTER_TYPES(QtApplicationManager_SystemUI)
111 : : AM_QML_REGISTER_TYPES(QtApplicationManager)
112 : : AM_QML_REGISTER_TYPES(QtApplicationManager_Application)
113 : :
114 : : QT_BEGIN_NAMESPACE_AM
115 : :
116 : : static bool unexpectedShutdown = true;
117 : :
118 : : // We need to do some things BEFORE the Q*Application constructor runs, so we're using this
119 : : // old trick to do this hooking transparently for the user of the class.
120 : 58 : int &Main::preConstructor(int &argc, char **argv, InitFlags initFlags)
121 : : {
122 [ + + ]: 58 : if (initFlags & InitFlag::InitializeLogging) {
123 : 41 : Logging::initialize(argc, argv);
124 : 41 : StartupTimer::instance()->checkpoint("after logging initialization");
125 : : }
126 [ + + ]: 58 : if (initFlags & InitFlag::ForkSudoServer) {
127 : 41 : Sudo::forkServer(Sudo::DropPrivilegesPermanently);
128 : 41 : StartupTimer::instance()->checkpoint("after sudo server fork");
129 : : }
130 : 58 : return argc;
131 : : }
132 : :
133 : 58 : Main::Main(int &argc, char **argv, InitFlags initFlags)
134 : : : MainBase(SharedMain::preConstructor(Main::preConstructor(argc, argv, initFlags)), argv)
135 [ + - + - ]: 58 : , SharedMain()
136 : : {
137 : 116 : m_isRunningOnEmbedded =
138 : : #if defined(Q_OS_LINUX)
139 : 58 : !qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP");
140 : : #else
141 : : false;
142 : : #endif
143 : :
144 : 58 : static bool once = false;
145 [ + + ]: 58 : if (!once) {
146 : 43 : once = true;
147 : :
148 [ + - + - ]: 43 : UnixSignalHandler::instance()->install(UnixSignalHandler::ForwardedToEventLoopHandler,
149 : : { SIGINT, SIGTERM },
150 : 0 : [](int sig) {
151 : 0 : UnixSignalHandler::instance()->resetToDefault(sig);
152 [ # # ]: 0 : static_cast<Main *>(QCoreApplication::instance())->shutDown((sig == SIGINT) ? "Ctrl+C" : "SIGTERM");
153 : 0 : });
154 : :
155 : 43 : atexit([]() {
156 [ - + ]: 43 : if (unexpectedShutdown)
157 : 0 : fputs("ERROR: Some code outside the Qt ApplicationManager called exit()\n", stderr);
158 : 43 : unexpectedShutdown = true;
159 : 43 : });
160 : : }
161 [ + - + - ]: 58 : StartupTimer::instance()->checkpoint("after application constructor");
162 : 58 : }
163 : :
164 : 116 : Main::~Main()
165 : : {
166 [ + + ]: 58 : delete m_engine;
167 : :
168 [ + + ]: 58 : delete m_intentServer;
169 [ + + ]: 58 : delete m_notificationManager;
170 [ + + ]: 58 : delete m_windowManager;
171 [ + + ]: 58 : delete m_view;
172 [ + + ]: 58 : delete m_applicationManager;
173 [ + + ]: 58 : delete m_packageManager;
174 [ + + ]: 58 : delete m_quickLauncher;
175 : :
176 [ + - ]: 58 : delete RuntimeFactory::instance();
177 [ + - ]: 58 : delete ContainerFactory::instance();
178 [ + - ]: 58 : delete StartupTimer::instance();
179 : :
180 : : #if defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
181 [ + + ]: 58 : delete DBusPolicy::instance();
182 : : #endif
183 : 116 : }
184 : :
185 : : /*! \internal
186 : : The caller has to make sure that cfg will be available even after this function returns:
187 : : we will access the cfg object from delayed init functions via lambdas!
188 : : */
189 : 58 : void Main::setup(const Configuration *cfg) noexcept(false)
190 : : {
191 : 58 : StartupTimer::instance()->checkpoint("after configuration parsing");
192 : :
193 : 58 : CrashHandler::setCrashActionConfiguration(cfg->yaml.crashAction.printBacktrace,
194 : 58 : cfg->yaml.crashAction.printQmlStack,
195 : 58 : cfg->yaml.crashAction.waitForGdbAttach.count(),
196 : 58 : cfg->yaml.crashAction.dumpCore,
197 : 58 : cfg->yaml.crashAction.stackFramesToIgnore.onCrash,
198 : 58 : cfg->yaml.crashAction.stackFramesToIgnore.onException);
199 : 58 : setupQmlDebugging(cfg->qmlDebugging());
200 [ + - ]: 58 : if (Logging::isDltAvailable()) {
201 [ + - - + ]: 58 : if (!cfg->yaml.logging.dlt.id.isEmpty() || !cfg->yaml.logging.dlt.description.isEmpty())
202 [ # # # # ]: 0 : Logging::setSystemUiDltId(cfg->yaml.logging.dlt.id.toLocal8Bit(),
203 : 0 : cfg->yaml.logging.dlt.description.toLocal8Bit());
204 : 58 : Logging::setDltLongMessageBehavior(cfg->yaml.logging.dlt.longMessageBehavior);
205 : 58 : Logging::registerUnregisteredDltContexts();
206 : : }
207 : 58 : setupLogging(cfg->verbose(), cfg->yaml.logging.rules, cfg->yaml.logging.messagePattern,
208 : 58 : cfg->yaml.logging.useAMConsoleLogger);
209 : :
210 [ + + ]: 58 : if (!cfg->isWatchdogDisabled())
211 : 17 : setupWatchdog(cfg->yaml.watchdog);
212 : :
213 : 58 : registerResources(cfg->yaml.ui.resources);
214 : :
215 : 58 : setupOpenGL(cfg->yaml.ui.opengl);
216 : 58 : setupIconTheme(cfg->yaml.ui.iconThemeSearchPaths, cfg->yaml.ui.iconThemeName);
217 : :
218 : 58 : loadStartupPlugins(cfg->yaml.plugins.startup);
219 : 58 : parseSystemProperties(cfg->yaml.systemProperties);
220 : :
221 : 58 : setMainQmlFile(cfg->yaml.ui.mainQml);
222 : 52 : setupSingleOrMultiProcess(cfg);
223 : 52 : setupRuntimesAndContainers(cfg);
224 : :
225 : 52 : loadPackageDatabase(cfg);
226 : :
227 : 52 : setupSingletons(cfg);
228 : 52 : setupQuickLauncher(cfg);
229 : 52 : setupIntents(cfg);
230 : :
231 : 52 : registerPackages();
232 : :
233 [ + + ]: 52 : if (cfg->yaml.applications.installationDir.isEmpty())
234 : 31 : StartupTimer::instance()->checkpoint("skipping installer");
235 : : else
236 : 21 : setupInstaller(cfg);
237 : :
238 [ + - + - ]: 104 : setLibraryPaths(libraryPaths() + cfg->yaml.ui.pluginPaths);
239 : 52 : setupQmlEngine(cfg->yaml.ui.importPaths, cfg->yaml.ui.style);
240 : :
241 : : // For development only: set an icon, so you know which window is the AM
242 [ - + ]: 52 : if (!isRunningOnEmbedded() && !cfg->yaml.ui.windowIcon.isEmpty())
243 [ # # ]: 0 : QGuiApplication::setWindowIcon(QIcon(cfg->yaml.ui.windowIcon));
244 : :
245 : 52 : setupWindowManager(cfg);
246 : 52 : setupDBus(cfg);
247 : :
248 : 52 : createInstanceInfoFile(cfg->yaml.instanceId);
249 : :
250 : 52 : m_showFullscreen = cfg->yaml.ui.fullscreen;
251 : 52 : }
252 : :
253 : 0 : bool Main::isSingleProcessMode() const
254 : : {
255 : 0 : return m_isSingleProcessMode;
256 : : }
257 : :
258 : 73 : bool Main::isRunningOnEmbedded() const
259 : : {
260 [ + - ]: 52 : return m_isRunningOnEmbedded;
261 : : }
262 : :
263 : 52 : void Main::shutDown(const char *shutdownReason, int exitCode)
264 : : {
265 : 52 : enum {
266 : : ApplicationManagerDown = 0x01,
267 : : QuickLauncherDown = 0x02,
268 : : WindowManagerDown = 0x04
269 : : };
270 : :
271 : 52 : static int down = 0;
272 [ + + + - ]: 52 : static int code = exitCode;
273 [ + + + - ]: 52 : static const char *reason = shutdownReason;
274 : :
275 : 104 : static auto finalShutdown = []() {
276 : 52 : unexpectedShutdown = false;
277 [ + + ]: 52 : if (reason)
278 [ + - + - : 123 : qCInfo(LogSystem) << "Shutting down due to" << reason;
+ + ]
279 : 52 : QCoreApplication::exit(code);
280 : 52 : };
281 : :
282 : 167 : static auto checkShutDownFinished = [](int nextDown) {
283 : 115 : down |= nextDown;
284 [ + + ]: 115 : if (down == (ApplicationManagerDown | QuickLauncherDown | WindowManagerDown)) {
285 : 52 : down = 0;
286 : 52 : finalShutdown();
287 : : }
288 : 115 : };
289 : :
290 [ + - ]: 52 : if (m_applicationManager) {
291 : 52 : connect(&m_applicationManager->internalSignals, &ApplicationManagerInternalSignals::shutDownFinished,
292 : 57 : this, []() { checkShutDownFinished(ApplicationManagerDown); });
293 : 52 : m_applicationManager->shutDown();
294 : : }
295 [ + + ]: 52 : if (m_quickLauncher) {
296 : 6 : connect(m_quickLauncher, &QuickLauncher::shutDownFinished,
297 : 6 : this, []() { checkShutDownFinished(QuickLauncherDown); });
298 : 6 : m_quickLauncher->shutDown();
299 : : } else {
300 : 46 : down |= QuickLauncherDown;
301 : : }
302 [ + - ]: 52 : if (m_windowManager) {
303 : 52 : connect(&m_windowManager->internalSignals, &WindowManagerInternalSignals::shutDownFinished,
304 : 52 : this, []() { checkShutDownFinished(WindowManagerDown); });
305 : 52 : m_windowManager->shutDown();
306 : : }
307 : :
308 : 52 : QTimer::singleShot(5000, this, [] {
309 : 0 : QStringList resources;
310 [ # # ]: 0 : if (!(down & ApplicationManagerDown))
311 [ # # ]: 0 : resources << u"runtimes"_s;
312 [ # # ]: 0 : if (!(down & QuickLauncherDown))
313 [ # # ]: 0 : resources << u"quick-launchers"_s;
314 [ # # ]: 0 : if (!(down & WindowManagerDown))
315 [ # # ]: 0 : resources << u"windows"_s;
316 [ # # # # : 0 : qCCritical(LogSystem, "There are still resources in use (%s). Check your System UI implementation. "
# # # # #
# ]
317 : : "Exiting regardless.", resources.join(u", "_s).toLocal8Bit().constData());
318 [ # # ]: 0 : finalShutdown();
319 : 0 : });
320 : 52 : }
321 : :
322 : 41 : QQmlApplicationEngine *Main::qmlEngine() const
323 : : {
324 : 41 : return m_engine;
325 : : }
326 : :
327 : 11 : int Main::exec()
328 : : {
329 : 11 : return MainBase::exec();
330 : : }
331 : :
332 : 58 : void Main::registerResources(const QStringList &resources) const
333 : : {
334 [ + + ]: 58 : if (resources.isEmpty())
335 : : return;
336 [ + + ]: 6 : for (const QString &resource: resources) {
337 : 4 : try {
338 [ + - ]: 4 : loadResource(resource);
339 [ - - ]: 0 : } catch (const Exception &e) {
340 [ - - - - : 0 : qCWarning(LogSystem).noquote() << e.errorString();
- - - - ]
341 : 0 : }
342 : : }
343 : 2 : StartupTimer::instance()->checkpoint("after resource registration");
344 : : }
345 : :
346 : 58 : void Main::loadStartupPlugins(const QStringList &startupPluginPaths) noexcept(false)
347 : : {
348 : 58 : QStringList systemStartupPluginPaths;
349 [ + - + - : 174 : const QDir systemStartupPluginDir(QLibraryInfo::path(QLibraryInfo::PluginsPath) + QDir::separator() + u"appman_startup"_s);
+ - ]
350 [ + - ]: 58 : const auto allPluginNames = systemStartupPluginDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
351 [ - + ]: 58 : for (const auto &pluginName : allPluginNames) {
352 [ # # ]: 0 : const QString filePath = systemStartupPluginDir.absoluteFilePath(pluginName);
353 [ # # # # ]: 0 : if (!QLibrary::isLibrary(filePath))
354 : 0 : continue;
355 [ # # ]: 0 : systemStartupPluginPaths += filePath;
356 : 0 : }
357 : :
358 [ + - + - ]: 58 : m_startupPlugins = loadPlugins<StartupInterface>("startup", systemStartupPluginPaths + startupPluginPaths);
359 [ + - + - ]: 58 : StartupTimer::instance()->checkpoint("after startup-plugin load");
360 : 58 : }
361 : :
362 : 58 : void Main::parseSystemProperties(const QVariantMap &rawSystemProperties)
363 : : {
364 : 58 : m_systemProperties.resize(SP_SystemUi + 1);
365 [ + + ]: 58 : QVariantMap rawMap = rawSystemProperties;
366 : :
367 [ + - + - : 58 : m_systemProperties[SP_ThirdParty] = rawMap.value(u"public"_s).toMap();
+ - ]
368 : :
369 [ + - ]: 58 : m_systemProperties[SP_BuiltIn] = m_systemProperties.at(SP_ThirdParty);
370 [ + - + - ]: 116 : const QVariantMap pro = rawMap.value(u"protected"_s).toMap();
371 [ + + + + : 138 : for (auto it = pro.cbegin(); it != pro.cend(); ++it)
+ + ]
372 [ + - + - ]: 10 : m_systemProperties[SP_BuiltIn].insert(it.key(), it.value());
373 : :
374 [ + - ]: 58 : m_systemProperties[SP_SystemUi] = m_systemProperties.at(SP_BuiltIn);
375 [ + - + - ]: 116 : const QVariantMap pri = rawMap.value(u"private"_s).toMap();
376 [ + + + + : 168 : for (auto it = pri.cbegin(); it != pri.cend(); ++it)
+ + ]
377 [ + - + - ]: 24 : m_systemProperties[SP_SystemUi].insert(it.key(), it.value());
378 : :
379 [ - + ]: 58 : for (auto iface : std::as_const(m_startupPlugins))
380 [ # # ]: 0 : iface->initialize(m_systemProperties.at(SP_SystemUi));
381 : 58 : }
382 : :
383 : 58 : void Main::setMainQmlFile(const QString &mainQml) noexcept(false)
384 : : {
385 : : // For some weird reason, QFile cannot cope with "qrc:/" and QUrl cannot cope with ":/",
386 : : // so we have to translate ourselves between those two "worlds".
387 : :
388 [ + - ]: 58 : m_mainQml = filePathToUrl(mainQml, QDir::currentPath());
389 : 58 : m_mainQmlLocalFile = urlToLocalFilePath(m_mainQml);
390 : :
391 [ + - + + ]: 58 : if (!QFileInfo(m_mainQmlLocalFile).isFile()) {
392 [ + + ]: 8 : if (mainQml.isEmpty())
393 : 1 : throw Exception("No main QML file specified");
394 : :
395 : : // basically accept schemes other than file and qrc:
396 [ + + ]: 7 : if (!m_mainQmlLocalFile.isEmpty())
397 : 5 : throw Exception("Invalid main QML file specified: %1").arg(mainQml);
398 : : }
399 : 52 : }
400 : :
401 : 52 : void Main::setupSingleOrMultiProcess(const Configuration *cfg) noexcept(false)
402 : : {
403 : 52 : m_isSingleProcessMode = cfg->yaml.flags.forceSingleProcess;
404 [ + + - + ]: 52 : if (cfg->yaml.flags.forceMultiProcess && m_isSingleProcessMode)
405 : 0 : throw Exception("You cannot enforce multi- and single-process mode at the same time.");
406 : :
407 : 52 : if (!QT_CONFIG(am_multi_process)) {
408 : : if (cfg->yaml.flags.forceMultiProcess)
409 : : throw Exception("This application manager build is not multi-process capable.");
410 : : m_isSingleProcessMode = true;
411 : : }
412 : 52 : }
413 : :
414 : 52 : void Main::setupRuntimesAndContainers(const Configuration *cfg)
415 : : {
416 : 52 : auto &grc = GlobalRuntimeConfiguration::instance();
417 : 52 : grc.openGLConfiguration = cfg->yaml.ui.opengl;
418 : 52 : grc.watchdogDisabled = cfg->isWatchdogDisabled();
419 : 52 : grc.watchdogConfiguration = cfg->yaml.watchdog;
420 : 52 : grc.iconThemeSearchPaths = cfg->yaml.ui.iconThemeSearchPaths;
421 : 52 : grc.iconThemeName = cfg->yaml.ui.iconThemeName;
422 : 52 : grc.systemPropertiesForBuiltInApps = m_systemProperties.at(SP_BuiltIn);
423 : 52 : grc.systemPropertiesForThirdPartyApps= m_systemProperties.at(SP_ThirdParty);
424 : :
425 : 52 : QVector<PluginContainerManager *> pluginContainerManagers;
426 : :
427 [ + + ]: 52 : if (m_isSingleProcessMode) {
428 [ + - + - : 34 : RuntimeFactory::instance()->registerRuntime(new QmlInProcRuntimeManager());
+ - ]
429 [ + - + - : 34 : RuntimeFactory::instance()->registerRuntime(new QmlInProcRuntimeManager(u"qml"_s));
+ - + - ]
430 : : } else {
431 [ + - + - : 70 : RuntimeFactory::instance()->registerRuntime(new QmlInProcRuntimeManager());
+ - ]
432 : : #if QT_CONFIG(am_multi_process)
433 [ + - + - : 70 : RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager());
+ - ]
434 [ + - + - : 35 : RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(u"qml"_s));
+ - + - ]
435 : :
436 [ - + ]: 35 : for (const QString &runtimeId : cfg->yaml.runtimes.additionalLaunchers)
437 [ # # # # : 0 : RuntimeFactory::instance()->registerRuntime(new NativeRuntimeManager(runtimeId));
# # # # ]
438 : :
439 [ + - + - : 70 : ContainerFactory::instance()->registerContainer(new ProcessContainerManager());
+ - ]
440 : : #else
441 : : if (!cfg->yaml.runtimes.additionalLaunchers.isEmpty())
442 : : qCWarning(LogSystem) << "Addtional runtime launchers are ignored in single-process mode";
443 : : #endif
444 : 35 : QStringList systemContainerPluginPaths;
445 [ + - + - : 105 : const QDir systemContainerPluginDir(QLibraryInfo::path(QLibraryInfo::PluginsPath) + QDir::separator() + u"appman_container"_s);
+ - ]
446 [ + - ]: 35 : const auto allPluginNames = systemContainerPluginDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
447 [ + + ]: 105 : for (const auto &pluginName : allPluginNames) {
448 [ + - ]: 70 : const QString filePath = systemContainerPluginDir.absoluteFilePath(pluginName);
449 [ + - + + ]: 70 : if (!QLibrary::isLibrary(filePath))
450 : 35 : continue;
451 [ + - ]: 70 : systemContainerPluginPaths += filePath;
452 : 70 : }
453 : :
454 : 35 : QSet<QString> containersWithConfiguration(cfg->yaml.containers.configurations.keyBegin(),
455 [ + + + + : 37 : cfg->yaml.containers.configurations.keyEnd());
+ - ]
456 : :
457 [ + - + - ]: 35 : auto containerPlugins = loadPlugins<ContainerManagerInterface>("container", systemContainerPluginPaths + cfg->yaml.plugins.container);
458 : 35 : pluginContainerManagers.reserve(containerPlugins.size());
459 [ + + ]: 70 : for (auto iface : std::as_const(containerPlugins)) {
460 [ + - + + ]: 70 : if (!containersWithConfiguration.contains(iface->identifier()))
461 : 34 : continue;
462 [ + - + - ]: 1 : auto pcm = new PluginContainerManager(iface);
463 [ + - ]: 1 : pluginContainerManagers << pcm;
464 [ + - + - ]: 1 : ContainerFactory::instance()->registerContainer(pcm);
465 : : }
466 : 35 : }
467 [ - + ]: 52 : for (auto iface : std::as_const(m_startupPlugins))
468 [ # # ]: 0 : iface->afterRuntimeRegistration();
469 : :
470 [ + - + - ]: 52 : ContainerFactory::instance()->setConfiguration(cfg->yaml.containers.configurations);
471 : :
472 [ + + ]: 53 : for (auto pcm : std::as_const(pluginContainerManagers)) {
473 [ + - - + ]: 1 : if (!pcm->initialize()) {
474 [ # # # # : 0 : ContainerFactory::instance()->disableContainer(pcm->identifier());
# # ]
475 [ # # # # : 0 : qCWarning(LogSystem).noquote() << "Disabling container plugin" << pcm->identifier() << "as it failed to initialize";
# # # # #
# # # #
# ]
476 : : }
477 : : }
478 : :
479 [ + - + - ]: 52 : RuntimeFactory::instance()->setConfiguration(cfg->yaml.runtimes.configurations);
480 : :
481 [ + - + - ]: 52 : StartupTimer::instance()->checkpoint("after runtime registration");
482 : 52 : }
483 : :
484 : 52 : void Main::loadPackageDatabase(const Configuration *cfg) noexcept(false)
485 : : {
486 [ + + ]: 52 : if (!cfg->singleApp().isEmpty()) {
487 [ + - + - ]: 2 : m_packageDatabase = new PackageDatabase(cfg->singleApp());
488 : : } else {
489 : 150 : m_packageDatabase = new PackageDatabase(cfg->yaml.applications.builtinAppsManifestDir,
490 : 50 : cfg->yaml.applications.installationDir,
491 [ + - ]: 50 : cfg->yaml.applications.installationDirMountPoint);
492 [ + - - + ]: 50 : if (!cfg->clearCache() && !cfg->noCache())
493 : 0 : m_packageDatabase->enableLoadFromCache();
494 : 50 : m_packageDatabase->enableSaveToCache();
495 : : }
496 : 52 : m_packageDatabase->parse();
497 : :
498 : 52 : const QVector<PackageInfo *> allPackages =
499 [ + - ]: 104 : m_packageDatabase->builtInPackages()
500 : 104 : + m_packageDatabase->installedPackages();
501 : :
502 [ + + ]: 137 : for (auto package : allPackages) {
503 : : // check that the runtimes are supported in this instance of the AM
504 [ + - ]: 85 : const auto apps = package->applications();
505 : :
506 [ + + ]: 181 : for (const auto app : apps) {
507 [ + - + - : 96 : if (!RuntimeFactory::instance()->manager(app->runtimeName()))
+ - + + ]
508 [ + - + - : 6 : qCWarning(LogSystem) << "Application" << app->id() << "uses an unknown runtime:" << app->runtimeName();
+ - + - +
- + - + -
+ - + + ]
509 : : }
510 : 85 : }
511 : :
512 [ + - + - ]: 52 : StartupTimer::instance()->checkpoint("after package database loading");
513 : 52 : }
514 : :
515 : 52 : void Main::setupIntents(const Configuration *cfg)
516 : : {
517 : 104 : m_intentServer = IntentAMImplementation::createIntentServerAndClientInstance(
518 : : m_packageManager,
519 : 52 : cfg->yaml.intents.timeouts.disambiguation.count(),
520 : 52 : cfg->yaml.intents.timeouts.startApplication.count(),
521 : 52 : cfg->yaml.intents.timeouts.replyFromApplication.count(),
522 : 52 : cfg->yaml.intents.timeouts.replyFromSystem.count());
523 : 52 : StartupTimer::instance()->checkpoint("after IntentServer instantiation");
524 : 52 : }
525 : :
526 : 52 : void Main::setupSingletons(const Configuration *cfg) noexcept(false)
527 : : {
528 : 52 : m_packageManager = PackageManager::createInstance(m_packageDatabase, cfg->yaml.applications.documentDir);
529 : 52 : m_applicationManager = ApplicationManager::createInstance(m_isSingleProcessMode);
530 : :
531 [ + + ]: 52 : if (cfg->yaml.flags.noSecurity)
532 : 7 : m_applicationManager->setSecurityChecksEnabled(false);
533 [ + + ]: 52 : if (cfg->yaml.flags.developmentMode)
534 : 2 : m_packageManager->setDevelopmentMode(true);
535 : :
536 : 52 : m_applicationManager->setSystemProperties(m_systemProperties.at(SP_SystemUi));
537 : 52 : m_applicationManager->setContainerSelectionConfiguration(cfg->yaml.containers.selection);
538 : :
539 : 52 : StartupTimer::instance()->checkpoint("after ApplicationManager instantiation");
540 : :
541 : 52 : m_notificationManager = NotificationManager::createInstance();
542 : 52 : StartupTimer::instance()->checkpoint("after NotificationManager instantiation");
543 : 52 : }
544 : :
545 : 52 : void Main::setupQuickLauncher(const Configuration *cfg)
546 : : {
547 [ + + + - ]: 52 : if (!cfg->yaml.quicklaunch.runtimesPerContainer.isEmpty()) {
548 : 12 : m_quickLauncher = QuickLauncher::createInstance(cfg->yaml.quicklaunch.runtimesPerContainer,
549 : 6 : cfg->yaml.quicklaunch.idleLoad,
550 : 6 : cfg->yaml.quicklaunch.failedStartLimit,
551 : 6 : cfg->yaml.quicklaunch.failedStartLimitIntervalSec.count());
552 : 6 : StartupTimer::instance()->checkpoint("after quick-launcher setup");
553 : : } else {
554 [ - - - + ]: 92 : qCDebug(LogSystem) << "Not setting up the quick-launch pool (runtimesPerContainer is 0)";
555 : : }
556 : 52 : }
557 : :
558 : 21 : void Main::setupInstaller(const Configuration *cfg) noexcept(false)
559 : : {
560 : : #if QT_CONFIG(am_installer)
561 : : // make sure the installation and document dirs are valid
562 : 21 : Q_ASSERT(!cfg->yaml.applications.installationDir.isEmpty());
563 [ + - ]: 21 : const auto instPath = QDir(cfg->yaml.applications.installationDir).canonicalPath();
564 [ + + ]: 21 : const auto docPath = cfg->yaml.applications.documentDir.isEmpty()
565 [ + + + - : 21 : ? QString { } : QDir(cfg->yaml.applications.documentDir).canonicalPath();
+ - ]
566 : :
567 [ + + + - : 21 : if (!docPath.isEmpty() && (instPath.startsWith(docPath) || docPath.startsWith(instPath)))
+ - + - -
+ ]
568 : 0 : throw Exception("either installationDir or documentDir cannot be a sub-directory of the other");
569 : :
570 [ + - - + ]: 21 : if (Q_UNLIKELY(hardwareId().isEmpty()))
571 : 0 : throw Exception("the installer is enabled, but the device-id is empty");
572 : :
573 [ + - ]: 21 : if (!isRunningOnEmbedded()) { // this is just for convenience sake during development
574 [ + - + - : 21 : if (Q_UNLIKELY(!cfg->yaml.applications.installationDir.isEmpty() && !QDir::root().mkpath(cfg->yaml.applications.installationDir)))
+ - - + +
- - + -
- ]
575 : 0 : throw Exception("could not create package installation directory: \'%1\'").arg(cfg->yaml.applications.installationDir);
576 [ + + + - : 21 : if (Q_UNLIKELY(!cfg->yaml.applications.documentDir.isEmpty() && !QDir::root().mkpath(cfg->yaml.applications.documentDir)))
+ - - + +
+ - + -
- ]
577 : 0 : throw Exception("could not create document directory for packages: \'%1\'").arg(cfg->yaml.applications.documentDir);
578 : : }
579 : :
580 [ + - + - ]: 21 : StartupTimer::instance()->checkpoint("after installer setup checks");
581 : :
582 [ + + - + ]: 21 : if (cfg->yaml.flags.noSecurity || cfg->yaml.flags.allowUnsignedPackages)
583 [ + - ]: 2 : m_packageManager->setAllowInstallationOfUnsignedPackages(true);
584 : :
585 [ + + ]: 21 : if (!cfg->yaml.flags.noSecurity) {
586 : 19 : QByteArrayList caCertificateList;
587 : 19 : caCertificateList.reserve(cfg->yaml.installer.caCertificates.size());
588 : :
589 [ - + ]: 19 : for (const auto &caFile : cfg->yaml.installer.caCertificates) {
590 [ # # ]: 0 : QFile f(caFile);
591 [ # # # # ]: 0 : if (Q_UNLIKELY(!f.open(QFile::ReadOnly)))
592 : 0 : throw Exception(f, "could not open CA-certificate file");
593 [ # # ]: 0 : QByteArray cert = f.readAll();
594 [ # # ]: 0 : if (Q_UNLIKELY(cert.isEmpty()))
595 : 0 : throw Exception(f, "CA-certificate file is empty");
596 [ # # ]: 0 : caCertificateList << cert;
597 : 0 : }
598 [ + - ]: 19 : m_packageManager->setCACertificates(caCertificateList);
599 : 19 : }
600 : :
601 [ + - ]: 21 : m_packageManager->enableInstaller();
602 : :
603 [ + - + - ]: 21 : StartupTimer::instance()->checkpoint("after installer setup");
604 : : #else
605 : : Q_UNUSED(cfg)
606 : : #endif // QT_CONFIG(am_installer)
607 : 21 : }
608 : :
609 : 52 : void Main::registerPackages()
610 : : {
611 : : // the installation dir might not be mounted yet, so we have to watch for the package
612 : : // DB's signal and then register these packages later in the already running system
613 [ - + ]: 52 : if (!(m_packageDatabase->parsedPackageLocations() & PackageDatabase::Installed)) {
614 : 0 : connect(m_packageDatabase, &PackageDatabase::installedPackagesParsed,
615 : 0 : m_packageManager, [this]() {
616 : : // we are not in main() anymore: we can't just throw
617 : 0 : try {
618 [ # # ]: 0 : m_packageManager->registerPackages();
619 [ - - ]: 0 : } catch (const Exception &e) {
620 [ - - - - : 0 : qCCritical(LogInstaller) << "Failed to register packages:" << e.what();
- - - - -
- ]
621 : 0 : std::abort(); // there is no qCFatal()
622 : 0 : }
623 : 0 : });
624 : 0 : StartupTimer::instance()->checkpoint("after package registration (delayed)");
625 : : } else {
626 : 52 : m_packageManager->registerPackages();
627 : 52 : StartupTimer::instance()->checkpoint("after package registration");
628 : : }
629 : 52 : }
630 : :
631 : 52 : void Main::setupQmlEngine(const QStringList &importPaths, const QString &quickControlsStyle)
632 : : {
633 [ - + ]: 52 : if (!quickControlsStyle.isEmpty())
634 [ # # ]: 0 : qputenv("QT_QUICK_CONTROLS_STYLE", quickControlsStyle.toLocal8Bit());
635 : :
636 : 52 : StartupTimer::instance()->checkpoint("after QML registrations");
637 : :
638 [ + - ]: 52 : m_engine = new QQmlApplicationEngine(this);
639 : 52 : disconnect(m_engine, &QQmlEngine::quit, qApp, nullptr);
640 : 52 : disconnect(m_engine, &QQmlEngine::exit, qApp, nullptr);
641 : 93 : connect(m_engine, &QQmlEngine::quit, this, [this]() { shutDown("Qt.quit()"); });
642 : 52 : connect(m_engine, &QQmlEngine::exit, this, [this](int retCode) { shutDown("Qt.exit()", retCode); });
643 : 52 : CrashHandler::setQmlEngine(m_engine);
644 [ + - ]: 52 : new QmlLogger(m_engine);
645 : 52 : m_engine->setOutputWarningsToStandardError(false);
646 [ + - + - ]: 104 : m_engine->setImportPathList(m_engine->importPathList() + importPaths);
647 : :
648 : 52 : StartupTimer::instance()->checkpoint("after QML engine instantiation");
649 : 52 : }
650 : :
651 : 52 : void Main::setupWindowManager(const Configuration *cfg)
652 : : {
653 : 52 : QUnifiedTimer::instance()->setSlowModeEnabled(cfg->slowAnimations());
654 : :
655 : 155 : auto waylandSocketName = [cfg]() -> QString {
656 : 52 : QString socketName = cfg->waylandSocketName(); // get the default value
657 [ + + ]: 52 : if (!socketName.isEmpty())
658 : 1 : return socketName;
659 [ - + ]: 51 : if (!cfg->yaml.wayland.socketName.isEmpty())
660 : 0 : return cfg->yaml.wayland.socketName;
661 : :
662 : : #if defined(Q_OS_LINUX)
663 : : // modelled after wl_socket_lock() in wayland_server.c
664 [ + - + - ]: 102 : const QString xdgDir = qEnvironmentVariable("XDG_RUNTIME_DIR") + u"/"_s;
665 : 51 : const QString pattern = u"qtam-wayland-%1"_s;
666 : 51 : const QString lockSuffix = u".lock"_s;
667 : :
668 [ + - ]: 51 : for (int i = 0; i < 32; ++i) {
669 [ + - ]: 51 : socketName = pattern.arg(i);
670 [ + - + - ]: 51 : QFile lock(xdgDir + socketName + lockSuffix);
671 [ + - + - ]: 51 : if (lock.open(QIODevice::ReadWrite)) {
672 [ + - + - ]: 51 : if (::flock(lock.handle(), LOCK_EX | LOCK_NB) == 0) {
673 [ + - + - ]: 51 : QFile socket(xdgDir + socketName);
674 [ + - - + : 51 : if (!socket.exists() || socket.remove())
- - - - ]
675 : 51 : return socketName;
676 : 51 : }
677 : : }
678 : 51 : }
679 : : #endif
680 : 51 : return QString();
681 : 103 : };
682 : :
683 [ + - ]: 52 : m_windowManager = WindowManager::createInstance(m_engine, waylandSocketName());
684 [ + + + - ]: 97 : m_windowManager->setAllowUnknownUiClients(cfg->yaml.flags.noSecurity || cfg->yaml.flags.allowUnknownUiClients);
685 : 52 : m_windowManager->setSlowAnimations(cfg->slowAnimations());
686 [ + + ]: 52 : if (!cfg->isWatchdogDisabled()) {
687 : 11 : m_windowManager->setWatchdogTimeouts(cfg->yaml.watchdog.wayland.checkInterval,
688 : : cfg->yaml.watchdog.wayland.warnTimeout,
689 : 11 : cfg->yaml.watchdog.wayland.killTimeout);
690 : : }
691 : :
692 : : #if defined(QT_WAYLANDCOMPOSITOR_LIB)
693 : 52 : connect(&m_windowManager->internalSignals, &WindowManagerInternalSignals::compositorAboutToBeCreated,
694 : 25 : this, [this, cfg] {
695 : : // This needs to be delayed until directly before creating the Wayland compositor.
696 : : // Otherwise we have a "dangling" socket you cannot connect to.
697 : :
698 [ - + ]: 25 : for (const auto &wes : cfg->yaml.wayland.extraSockets) {
699 : 0 : const QString path = wes.path;
700 : :
701 [ # # ]: 0 : if (path.isEmpty())
702 : 0 : continue;
703 : :
704 : 0 : try {
705 [ # # ]: 0 : QFileInfo fi(path);
706 [ # # # # : 0 : if (!fi.dir().mkpath(u"."_s))
# # ]
707 : 0 : throw Exception("could not create path to extra Wayland socket: %1").arg(path);
708 : :
709 [ # # ]: 0 : auto sudo = SudoClient::instance();
710 : :
711 [ # # # # : 0 : if (!sudo || sudo->isFallbackImplementation()) {
# # ]
712 [ # # # # ]: 0 : if (!QLocalServer::removeServer(path)) {
713 : 0 : throw Exception("could not clean up leftover extra Wayland socket %1").arg(path);
714 : : }
715 : : } else {
716 [ # # ]: 0 : sudo->removeRecursive(path);
717 : : }
718 : :
719 [ # # # # ]: 0 : std::unique_ptr<QLocalServer> extraSocket(new QLocalServer);
720 [ # # ]: 0 : extraSocket->setMaxPendingConnections(0); // disable Qt's new connection handling
721 [ # # # # ]: 0 : if (!extraSocket->listen(path)) {
722 [ # # ]: 0 : throw Exception("could not listen on extra Wayland socket %1: %2")
723 [ # # ]: 0 : .arg(path, extraSocket->errorString());
724 : : }
725 : 0 : int mode = wes.permissions;
726 : 0 : int uid = wes.userId;
727 : 0 : int gid = wes.groupId;
728 : :
729 [ # # ]: 0 : QByteArray encodedPath = QFile::encodeName(path);
730 : :
731 [ # # # # : 0 : if ((mode > 0) || (uid != -1) || (gid != -1)) {
# # ]
732 [ # # # # : 0 : if (!sudo || sudo->isFallbackImplementation()) {
# # ]
733 [ # # ]: 0 : if (mode > 0) {
734 [ # # # # ]: 0 : if (::chmod(encodedPath, static_cast<mode_t>(mode)) != 0)
735 : 0 : throw Exception(errno, "could not chmod(mode: %1) the extra Wayland socket %2")
736 [ # # ]: 0 : .arg(QString::number(mode, 8), path);
737 : : }
738 [ # # # # ]: 0 : if ((uid != -1) || (gid != -1)) {
739 [ # # # # ]: 0 : if (::chown(encodedPath, static_cast<uid_t>(uid), static_cast<gid_t>(gid)) != 0)
740 : 0 : throw Exception(errno, "could not chown(uid: %1, gid: %2) the extra Wayland socket %3")
741 : 0 : .arg(uid).arg(gid).arg(path);
742 : : }
743 : : } else {
744 [ # # # # ]: 0 : if (!sudo->setOwnerAndPermissionsRecursive(path, static_cast<uid_t>(uid),
745 : : static_cast<gid_t>(gid),
746 : : static_cast<mode_t>(mode))) {
747 : 0 : throw Exception(Error::IO, "could not change the owner to %1:%2 and the permission"
748 : : " bits to %3 for the extra Wayland socket %4: %5")
749 : 0 : .arg(uid).arg(gid).arg(mode, 0, 8).arg(path).arg(sudo->lastError());
750 : : }
751 : : // if we changed the owner, ~QLocalServer might not be able to clean up the
752 : : // socket inode, so we need to sudo this removal as well
753 [ # # ]: 0 : QObject::connect(extraSocket.get(), &QObject::destroyed, [path, sudo]() {
754 : 0 : sudo->removeRecursive(path);
755 : : });
756 : : }
757 : : }
758 : :
759 [ # # ]: 0 : m_windowManager->addWaylandSocket(extraSocket.release());
760 [ # # ]: 0 : } catch (const std::exception &e) {
761 [ - - - - : 0 : qCCritical(LogSystem) << "ERROR:" << e.what();
- - - - -
- ]
762 : 0 : }
763 : 0 : }
764 : 25 : });
765 : : #endif
766 : :
767 : 52 : QObject::connect(&m_applicationManager->internalSignals, &ApplicationManagerInternalSignals::newRuntimeCreated,
768 : 52 : m_windowManager, &WindowManager::setupInProcessRuntime);
769 : 52 : QObject::connect(m_applicationManager, &ApplicationManager::applicationWasActivated,
770 : 52 : m_windowManager, &WindowManager::raiseApplicationWindow);
771 : 52 : }
772 : :
773 : 52 : void Main::createInstanceInfoFile(const QString &instanceId) noexcept(false)
774 : : {
775 : : // This is needed for the appman-controller tool to talk to running appman instances.
776 : : // (the tool does not even have a session bus, when started via ssh)
777 : :
778 [ + + + - ]: 52 : static const QString defaultInstanceId = u"appman"_s;
779 : :
780 : 52 : QString rtPath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
781 [ - + ]: 52 : if (rtPath.isEmpty())
782 [ # # ]: 0 : rtPath = QDir::tempPath();
783 [ + - + - ]: 104 : QDir rtDir(rtPath + u"/qtapplicationmanager"_s);
784 [ + - - + ]: 52 : if (!rtDir.mkpath(u"."_s))
785 [ # # ]: 0 : throw Exception("Could not create runtime state directory (%1) for the instance info").arg(rtDir.absolutePath());
786 : :
787 : 52 : QString fileName;
788 [ + + + - ]: 53 : QString filePattern = (instanceId.isEmpty() ? defaultInstanceId : instanceId) + u"-%1";
789 : :
790 [ + + + - ]: 52 : static std::unique_ptr<QLockFile> lockf;
791 [ + + + - : 104 : static std::unique_ptr<QFile, void (*)(QFile *)> infof(nullptr, [](QFile *f) { f->remove(); delete f; });
+ - ]
792 : :
793 [ + - ]: 52 : for (int i = 0; i < 32; ++i) { // Wayland sockets are limited to 32 instances as well
794 [ + - ]: 52 : QString tryPattern = filePattern.arg(i);
795 [ + - + - : 104 : lockf.reset(new QLockFile(rtDir.absoluteFilePath(tryPattern + u".lock"_s)));
+ - + - ]
796 [ + - ]: 52 : lockf->setStaleLockTime(0);
797 [ + - + - ]: 52 : if (lockf->tryLock()) {
798 : 52 : fileName = tryPattern;
799 : 52 : break; // found a free instance id
800 : : }
801 : 52 : }
802 [ - + ]: 52 : if (fileName.isEmpty())
803 [ # # ]: 0 : throw Exception("Could not create a lock file for the instance info at %1").arg(rtDir.absolutePath());;
804 : :
805 [ + - + - : 104 : infof.reset(new QFile(rtDir.absoluteFilePath(fileName + u".json"_s)));
+ - + - ]
806 : :
807 [ + - + - ]: 104 : const QByteArray json = QJsonDocument::fromVariant(m_infoFileContents).toJson(QJsonDocument::Indented);
808 [ + - - + ]: 52 : if (!infof->open(QIODevice::WriteOnly))
809 : 0 : throw Exception(*infof.get(), "failed to create instance info file");
810 [ + - - + ]: 52 : if (infof->write(json) !=json.size())
811 : 0 : throw Exception(*infof.get(), "failed to write instance info file");
812 [ + - ]: 52 : infof->close();
813 : 52 : }
814 : :
815 : 42 : void Main::loadQml() noexcept(false)
816 : : {
817 [ - + ]: 42 : for (auto iface : std::as_const(m_startupPlugins))
818 : 0 : iface->beforeQmlEngineLoad(m_engine);
819 : :
820 : : // protect our namespace from this point onward
821 : 42 : qmlProtectModule("QtApplicationManager", 1);
822 : 42 : qmlProtectModule("QtApplicationManager.SystemUI", 1);
823 : 42 : qmlProtectModule("QtApplicationManager", 2);
824 : 42 : qmlProtectModule("QtApplicationManager.SystemUI", 2);
825 : :
826 : 42 : m_engine->load(m_mainQml);
827 [ - + ]: 42 : if (Q_UNLIKELY(m_engine->rootObjects().isEmpty()))
828 : 0 : throw Exception("Qml scene does not have a root object");
829 : :
830 [ - + ]: 42 : for (auto iface : std::as_const(m_startupPlugins))
831 : 0 : iface->afterQmlEngineLoad(m_engine);
832 : :
833 : 42 : StartupTimer::instance()->checkpoint("after loading main QML file");
834 : 42 : }
835 : :
836 : 0 : void Main::showWindow(bool showFullscreen)
837 : : {
838 : 0 : m_showFullscreen = showFullscreen;
839 : 0 : showWindow();
840 : 0 : }
841 : :
842 : 42 : void Main::showWindow()
843 : : {
844 : 42 : setQuitOnLastWindowClosed(false);
845 : 42 : connect(this, &QGuiApplication::lastWindowClosed, this, [this]() { shutDown("window closed"); });
846 : :
847 : 42 : QQuickWindow *window = nullptr;
848 : 42 : QObject *rootObject = m_engine->rootObjects().constFirst();
849 : :
850 [ - + ]: 42 : if (rootObject->isWindowType()) {
851 : 0 : window = qobject_cast<QQuickWindow *>(rootObject);
852 : : } else {
853 [ + - ]: 42 : QQuickItem *contentItem = qobject_cast<QQuickItem *>(rootObject);
854 : 42 : if (contentItem) {
855 [ + - ]: 42 : m_view = new QQuickView(m_engine, nullptr);
856 : 42 : m_view->setContent(m_mainQml, nullptr, rootObject);
857 : 42 : m_view->setVisible(contentItem->isVisible());
858 : 42 : connect(contentItem, &QQuickItem::visibleChanged, this, [this, contentItem]() {
859 : 0 : m_view->setVisible(contentItem->isVisible());
860 : 0 : });
861 : 42 : window = m_view;
862 : : }
863 : : }
864 : :
865 [ - + ]: 42 : for (auto iface : std::as_const(m_startupPlugins))
866 : 0 : iface->beforeWindowShow(window);
867 : :
868 [ - + ]: 42 : if (!window) {
869 : 0 : const QWindowList windowList = allWindows();
870 [ # # ]: 0 : for (QWindow *w : windowList) {
871 [ # # # # ]: 0 : if (w->isVisible()) {
872 [ # # ]: 0 : window = qobject_cast<QQuickWindow*>(w);
873 : : break;
874 : : }
875 : : }
876 : 0 : }
877 : :
878 [ + - ]: 42 : if (window) {
879 : 42 : Q_ASSERT(m_engine->incubationController());
880 : :
881 : 42 : StartupTimer::instance()->checkpoint("after Window instantiation/setup");
882 : :
883 : 122 : static QMetaObject::Connection conn = QObject::connect(window, &QQuickWindow::frameSwapped, this, []() {
884 : : // this is a queued signal, so there may be still one in the queue after calling disconnect()
885 [ + + ]: 80 : if (conn) {
886 : : #if defined(Q_CC_MSVC)
887 : : qApp->disconnect(conn); // MSVC cannot distinguish between static and non-static overloads in lambdas
888 : : #else
889 : 40 : QObject::disconnect(conn);
890 : : #endif
891 : 40 : auto st = StartupTimer::instance();
892 : 40 : st->checkFirstFrame();
893 [ + - ]: 80 : st->createAutomaticReport(u"System UI"_s);
894 : : }
895 [ + - + - : 122 : });
+ - ]
896 : :
897 : : // Main window will always be shown, neglecting visible property for backwards compatibility
898 [ - + ]: 42 : if (Q_LIKELY(m_showFullscreen))
899 : 0 : window->showFullScreen();
900 : : else
901 : 42 : window->setVisible(true);
902 : :
903 : : // now check the surface format, in case we had requested a specific GL version/profile
904 [ + - ]: 42 : checkOpenGLFormat("main window", window->format());
905 : :
906 [ - + ]: 42 : for (auto iface : std::as_const(m_startupPlugins))
907 : 0 : iface->afterWindowShow(window);
908 : :
909 : 42 : StartupTimer::instance()->checkpoint("after window show");
910 : : } else {
911 : 0 : static QMetaObject::Connection conn =
912 : 0 : connect(this, &QGuiApplication::focusWindowChanged, this, [this] (QWindow *win) {
913 [ # # ]: 0 : if (conn) {
914 : 0 : QObject::disconnect(conn);
915 [ # # ]: 0 : checkOpenGLFormat("first window", win->format());
916 : : }
917 [ # # # # : 0 : });
# # ]
918 [ # # ]: 0 : StartupTimer::instance()->createAutomaticReport(u"System UI"_s);
919 : : }
920 : 42 : }
921 : :
922 : 52 : void Main::setupDBus(const Configuration *cfg)
923 : : {
924 : : #if defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
925 : 52 : registerDBusTypes();
926 : :
927 [ + - ]: 104 : DBusPolicy::createInstance([](qint64 pid) { return ApplicationManager::instance()->identifyAllApplications(pid); },
928 : 0 : [](const QString &appId) { return ApplicationManager::instance()->capabilities(appId); });
929 : :
930 : : // <0> DBusContextAdaptor instance
931 : : // <1> D-Bus name (extracted from callback function busForInterface)
932 : : // <2> D-Bus service
933 : : // <3> D-Bus path
934 : : // <4> Interface name (extracted from Q_CLASSINFO below)
935 : :
936 : 52 : std::vector<std::tuple<DBusContextAdaptor *, QString, QString, QString, QString>> ifaces;
937 : :
938 : 260 : auto addInterface = [&](DBusContextAdaptor *adaptor, const QString &service, const QString &path) {
939 : 208 : int idx = adaptor->parent()->metaObject()->indexOfClassInfo("D-Bus Interface");
940 [ - + ]: 208 : if (idx < 0) {
941 : 0 : throw Exception("Could not get class-info \"D-Bus Interface\" for D-Bus adapter %1")
942 [ # # # # : 0 : .arg(QString::fromLatin1(adaptor->parent()->metaObject()->className()));
# # # # ]
943 : : }
944 [ + - ]: 416 : QString interfaceName = QString::fromLatin1(adaptor->parent()->metaObject()->classInfo(idx).value());
945 [ - + ]: 208 : auto iit = cfg->yaml.dbus.registrations.constFind(interfaceName);
946 [ - + - + : 208 : QString bus = (iit != cfg->yaml.dbus.registrations.cend()) ? iit->toString() : cfg->dbus();
- - + - ]
947 : :
948 [ + - ]: 208 : ifaces.emplace_back(adaptor, bus, service, path, interfaceName);
949 : 208 : };
950 : :
951 [ + - ]: 104 : addInterface(DBusContextAdaptor::create<PackageManagerAdaptor>(m_packageManager),
952 [ + - ]: 104 : u"io.qt.ApplicationManager"_s, u"/PackageManager"_s);
953 [ + - ]: 104 : addInterface(DBusContextAdaptor::create<WindowManagerAdaptor>(m_windowManager),
954 [ + - ]: 104 : u"io.qt.ApplicationManager"_s, u"/WindowManager"_s);
955 [ + - ]: 104 : addInterface(DBusContextAdaptor::create<NotificationsAdaptor>(m_notificationManager),
956 [ + - ]: 104 : u"org.freedesktop.Notifications"_s, u"/org/freedesktop/Notifications"_s);
957 [ + - ]: 104 : addInterface(DBusContextAdaptor::create<ApplicationManagerAdaptor>(m_applicationManager),
958 [ + - ]: 104 : u"io.qt.ApplicationManager"_s, u"/ApplicationManager"_s);
959 : :
960 : 52 : bool autoOnly = true;
961 : 52 : bool noneOnly = true;
962 : :
963 : : // check if all interfaces are on the "auto" bus and replace the "none" bus with nullptr
964 [ + + ]: 260 : for (auto &&iface : ifaces) {
965 : 208 : QString dbusName = std::get<1>(iface);
966 [ + + ]: 208 : if (dbusName != u"auto"_s)
967 : 44 : autoOnly = false;
968 [ + + ]: 208 : if (dbusName == u"none")
969 : 44 : std::get<1>(iface).clear();
970 : : else
971 : : noneOnly = false;
972 : 208 : }
973 : :
974 : : // start a private dbus-daemon session instance if all interfaces are set to "auto"
975 [ + + ]: 52 : if (Q_UNLIKELY(autoOnly)) {
976 : 41 : try {
977 [ + - ]: 41 : DBusDaemonProcess::start();
978 [ + - + - ]: 41 : StartupTimer::instance()->checkpoint("after starting session D-Bus");
979 [ - - ]: 0 : } catch (const Exception &e) {
980 : : # if defined(Q_OS_LINUX)
981 [ - - - - : 0 : qCWarning(LogDBus) << "Disabling external D-Bus interfaces:" << e.what();
- - - - -
- ]
982 [ - - ]: 0 : for (auto &&iface : ifaces)
983 : 0 : std::get<1>(iface).clear();
984 : 0 : noneOnly = true;
985 : : # else
986 : : qCWarning(LogDBus) << "Could not start a private dbus-daemon:" << e.what();
987 : : qCInfo(LogDBus) << "Enabling DBus P2P access for appman-controller";
988 : : for (auto &&iface : ifaces) {
989 : : QString &dbusName = std::get<1>(iface);
990 : : if (dbusName == u"auto")
991 : : dbusName = u"p2p"_s;
992 : : }
993 : : # endif
994 : 0 : }
995 : : }
996 : :
997 [ + + ]: 52 : if (!noneOnly) {
998 [ + - - - : 82 : qCDebug(LogDBus) << "Registering D-Bus services:";
- - - + ]
999 : :
1000 [ + + ]: 205 : for (auto &&iface : ifaces) {
1001 [ + - ]: 164 : auto *generatedAdaptor = std::get<0>(iface)->generatedAdaptor<QDBusAbstractAdaptor>();
1002 [ - + ]: 164 : QString &dbusName = std::get<1>(iface);
1003 [ - + ]: 164 : QString &interfaceName = std::get<4>(iface);
1004 : :
1005 [ - + ]: 164 : if (dbusName.isEmpty())
1006 : 0 : continue;
1007 : :
1008 [ + - ]: 164 : auto dbusAddress = registerDBusObject(generatedAdaptor, dbusName,
1009 [ + - ]: 164 : std::get<2>(iface), std::get<3>(iface));
1010 [ + - + - ]: 328 : auto policy = cfg->yaml.dbus.policies.value(interfaceName).toMap();
1011 : :
1012 [ + - + - : 164 : if (!DBusPolicy::instance()->add(generatedAdaptor, policy))
- + ]
1013 : 0 : throw Exception(Error::DBus, "could not set DBus policy for %1").arg(interfaceName);
1014 : :
1015 : : // Write the bus address to our info file for the appman-controller tool
1016 [ + + ]: 164 : if (interfaceName.startsWith(u"io.qt.")) {
1017 [ + - + - ]: 123 : auto map = m_infoFileContents[u"dbus"_s].toMap();
1018 [ + - ]: 123 : map.insert(interfaceName, dbusAddress);
1019 [ + - ]: 123 : m_infoFileContents[u"dbus"_s] = map;
1020 : 123 : }
1021 : 164 : }
1022 : : }
1023 : : #else
1024 : : Q_UNUSED(cfg)
1025 : : Q_UNUSED(m_p2pServer)
1026 : : Q_UNUSED(m_p2pAdaptors)
1027 : : Q_UNUSED(m_p2pFailed)
1028 : : #endif // defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
1029 : 52 : }
1030 : :
1031 : 164 : QString Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const QString &dbusName,
1032 : : const QString &serviceName, const QString &path) noexcept(false)
1033 : : {
1034 : : #if defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
1035 : 164 : QString dbusAddress;
1036 : 164 : QString dbusRealName = dbusName;
1037 [ + - ]: 164 : QDBusConnection conn((QString()));
1038 : 164 : bool isP2P = false;
1039 : :
1040 [ - + ]: 164 : if (dbusName.isEmpty()) {
1041 : 0 : return { };
1042 [ - + ]: 164 : } else if (dbusName == u"system") {
1043 [ # # ]: 0 : dbusAddress = QString::fromLocal8Bit(qgetenv("DBUS_SYSTEM_BUS_ADDRESS"));
1044 : : # if defined(Q_OS_LINUX)
1045 [ # # ]: 0 : if (dbusAddress.isEmpty())
1046 : 0 : dbusAddress = u"unix:path=/var/run/dbus/system_bus_socket"_s;
1047 : : # endif
1048 [ # # ]: 0 : conn = QDBusConnection::systemBus();
1049 [ - + ]: 164 : } else if (dbusName == u"session") {
1050 [ # # ]: 0 : dbusAddress = QString::fromLocal8Bit(qgetenv("DBUS_SESSION_BUS_ADDRESS"));
1051 [ # # ]: 0 : conn = QDBusConnection::sessionBus();
1052 [ + - ]: 164 : } else if (dbusName == u"auto") {
1053 [ + - ]: 328 : dbusAddress = QString::fromLocal8Bit(qgetenv("DBUS_SESSION_BUS_ADDRESS"));
1054 : : // we cannot be using QDBusConnection::sessionBus() here, because some plugin
1055 : : // might have called that function before we could spawn our own session bus. In
1056 : : // this case, Qt has cached the bus name and we would get the old one back.
1057 [ + - ]: 164 : conn = QDBusConnection::connectToBus(dbusAddress, u"qtam_session"_s);
1058 [ + - - + ]: 164 : if (!conn.isConnected())
1059 : 0 : return { };
1060 : 164 : dbusRealName = u"session"_s;
1061 [ # # ]: 0 : } else if (dbusName == u"p2p") {
1062 [ # # # # ]: 0 : if (!m_p2pServer && !m_p2pFailed) {
1063 [ # # # # ]: 0 : m_p2pServer = new QDBusServer(this);
1064 [ # # ]: 0 : m_p2pServer->setAnonymousAuthenticationAllowed(true);
1065 : :
1066 [ # # # # ]: 0 : if (!m_p2pServer->isConnected()) {
1067 : 0 : m_p2pFailed = true;
1068 [ # # ]: 0 : delete m_p2pServer;
1069 : 0 : m_p2pServer = nullptr;
1070 [ # # # # : 0 : qCCritical(LogDBus) << "Failed to create a P2P DBus server for appman-controller";
# # # # ]
1071 : : } else {
1072 [ # # ]: 0 : QObject::connect(m_p2pServer, &QDBusServer::newConnection,
1073 : 0 : this, [this](const QDBusConnection &conn) {
1074 [ # # # # ]: 0 : for (const auto &[path, object] : std::as_const(m_p2pAdaptors).asKeyValueRange())
1075 [ # # # # ]: 0 : object->registerOnDBus(conn, path);
1076 : 0 : });
1077 : : }
1078 : : }
1079 [ # # ]: 0 : if (m_p2pFailed)
1080 : 0 : return { };
1081 [ # # # # ]: 0 : m_p2pAdaptors.insert(path, qobject_cast<DBusContextAdaptor *>(adaptor->parent()));
1082 [ # # # # ]: 0 : dbusAddress = u"p2p:"_s + m_p2pServer->address();
1083 : 0 : isP2P = true;
1084 : : } else {
1085 : 0 : dbusAddress = dbusName;
1086 [ # # ]: 0 : conn = QDBusConnection::connectToBus(dbusAddress, u"custom"_s);
1087 : : }
1088 : :
1089 : 164 : if (!isP2P) {
1090 [ + - - + ]: 164 : if (!conn.isConnected()) {
1091 [ # # ]: 0 : throw Exception("could not connect to D-Bus (%1): %2")
1092 [ # # # # : 0 : .arg(dbusAddress.isEmpty() ? dbusRealName : dbusAddress).arg(conn.lastError().message());
# # ]
1093 : : }
1094 : :
1095 [ + - - + ]: 164 : if (!conn.registerObject(path, adaptor->parent(), QDBusConnection::ExportAdaptors)) {
1096 : 0 : throw Exception("could not register object %1 on D-Bus (%2): %3")
1097 [ # # # # ]: 0 : .arg(path).arg(dbusRealName).arg(conn.lastError().message());
1098 : : }
1099 : :
1100 [ + - - + ]: 164 : if (!conn.registerService(serviceName)) {
1101 : 0 : throw Exception("could not register service %1 on D-Bus (%2): %3")
1102 [ # # # # ]: 0 : .arg(serviceName).arg(dbusRealName).arg(conn.lastError().message());
1103 : : }
1104 : : }
1105 : :
1106 [ + - + - ]: 164 : if (adaptor->parent() && adaptor->parent()->parent()) {
1107 : : // we need this information later on to tell apps where services are listening
1108 [ + - ]: 164 : adaptor->parent()->parent()->setProperty("_am_dbus_name", dbusRealName);
1109 [ + - ]: 164 : adaptor->parent()->parent()->setProperty("_am_dbus_address", dbusAddress);
1110 : : }
1111 : :
1112 [ + - - - : 328 : qCDebug(LogDBus).nospace().noquote() << " * " << serviceName << path << " [on bus: " << dbusRealName << "]";
- - - - -
- - - - -
- - - + ]
1113 : :
1114 [ + - ]: 164 : return dbusAddress.isEmpty() ? dbusRealName : dbusAddress;
1115 : : #else
1116 : : Q_UNUSED(adaptor)
1117 : : Q_UNUSED(dbusName)
1118 : : Q_UNUSED(serviceName)
1119 : : Q_UNUSED(path)
1120 : : return { };
1121 : : #endif // defined(QT_DBUS_LIB) && QT_CONFIG(am_external_dbus_interfaces)
1122 : 164 : }
1123 : :
1124 : 21 : QString Main::hardwareId() const
1125 : : {
1126 [ + + + - ]: 21 : static QString hardwareId;
1127 [ + + ]: 21 : if (hardwareId.isEmpty()) {
1128 : : #if defined(QT_AM_HARDWARE_ID)
1129 : : hardwareId = QString::fromLocal8Bit(QT_AM_HARDWARE_ID);
1130 : : if (hardwareId.startsWith(u'@')) {
1131 : : QFile f(hardwareId.mid(1));
1132 : : hardwareId.clear();
1133 : : if (f.open(QFile::ReadOnly))
1134 : : hardwareId = QString::fromLocal8Bit(f.readAll().trimmed());
1135 : : }
1136 : : #else
1137 : 12 : QVector<QNetworkInterface> candidateIfaces;
1138 [ + - ]: 12 : const auto allIfaces = QNetworkInterface::allInterfaces();
1139 [ + + ]: 48 : for (const QNetworkInterface &iface : allIfaces) {
1140 [ + - ]: 36 : if (iface.isValid()
1141 [ + - + + ]: 36 : && !(iface.flags() & (QNetworkInterface::IsPointToPoint | QNetworkInterface::IsLoopBack))
1142 [ + - + - ]: 24 : && iface.type() > QNetworkInterface::Virtual
1143 [ + - + - : 96 : && !iface.hardwareAddress().isEmpty()) {
- + - + +
+ ]
1144 [ + - ]: 60 : candidateIfaces << iface;
1145 : : }
1146 : : }
1147 [ + - ]: 12 : if (!candidateIfaces.isEmpty()) {
1148 [ + - + - ]: 24 : std::sort(candidateIfaces.begin(), candidateIfaces.end(), [](const QNetworkInterface &first, const QNetworkInterface &second) {
1149 [ + - ]: 12 : return first.name().compare(second.name()) < 0;
1150 : : });
1151 [ + - + - ]: 12 : hardwareId = candidateIfaces.constFirst().hardwareAddress().replace(u':', u'-');
1152 : : }
1153 : : #endif
1154 : 12 : }
1155 : 21 : return hardwareId;
1156 : : }
1157 : :
1158 : 78349 : bool Main::notify(QObject *receiver, QEvent *event)
1159 : : {
1160 : 78349 : const SharedMain::EventNotifyWatcher enw(receiver, event);
1161 [ + - ]: 156698 : return MainBase::notify(receiver, event);
1162 : 78349 : }
1163 : :
1164 : : QT_END_NAMESPACE_AM
1165 : :
1166 : : #include "moc_main.cpp"
|