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 <QCoreApplication>
7 : : #include <QUrl>
8 : : #include <QRegularExpression>
9 : : #include <QFileInfo>
10 : : #include <QProcess>
11 : : #include <QDir>
12 : : #include <QMetaObject>
13 : : #include <QUuid>
14 : : #include <QThread>
15 : : #include <QMimeDatabase>
16 : : #include <QScopedValueRollback>
17 : : #include <qplatformdefs.h>
18 : : #if defined(QT_GUI_LIB)
19 : : # include <QDesktopServices>
20 : : #endif
21 : : #if defined(Q_OS_WINDOWS)
22 : : # define write(a, b, c) _write(a, b, static_cast<unsigned int>(c))
23 : : #endif
24 : :
25 : : #include "global.h"
26 : : #include "applicationinfo.h"
27 : : #include "installationreport.h"
28 : : #include "logging.h"
29 : : #include "exception.h"
30 : : #include "applicationmanager.h"
31 : : #include "applicationmodel.h"
32 : : #include "applicationmanager_p.h"
33 : : #include "application.h"
34 : : #include "runtimefactory.h"
35 : : #include "containerfactory.h"
36 : : #include "quicklauncher.h"
37 : : #include "abstractruntime.h"
38 : : #include "abstractcontainer.h"
39 : : #include "globalruntimeconfiguration.h"
40 : : #include "qml-utilities.h"
41 : : #include "utilities.h"
42 : : #include "qtyaml.h"
43 : : #include "debugwrapper.h"
44 : : #include "amnamespace.h"
45 : : #include "package.h"
46 : : #include "packagemanager.h"
47 : :
48 : : #include <memory>
49 : :
50 : : using namespace Qt::StringLiterals;
51 : :
52 : : /*!
53 : : \qmltype ApplicationManager
54 : : \inqmlmodule QtApplicationManager.SystemUI
55 : : \ingroup system-ui-singletons
56 : : \brief The application model and controller.
57 : :
58 : : The ApplicationManager singleton type is the core of the application manager.
59 : : It provides both a DBus and a QML API for all of its functionality.
60 : :
61 : : The type is derived from \c QAbstractListModel, so it can be used directly
62 : : as a model in app-grid views.
63 : :
64 : : \target ApplicationManager Roles
65 : :
66 : : The following roles are available in this model:
67 : :
68 : : \table
69 : : \header
70 : : \li Role name
71 : : \li Type
72 : : \li Description
73 : : \row
74 : : \li \c applicationId
75 : : \li string
76 : : \li The unique Id of an application, represented as a string (e.g. \c Browser or
77 : : \c com.pelagicore.music)
78 : : \row
79 : : \li \c name
80 : : \li string
81 : : \li The name of the application. If possible, already translated to the current locale.
82 : : If no name was defined for the application, the name of the corresponding package will
83 : : be returned.
84 : : \row
85 : : \li \c description
86 : : \li string
87 : : \li The description of the application. If possible, already translated to the current locale.
88 : : If no description was defined for the application, the description of the corresponding
89 : : package will be returned.
90 : : \row
91 : : \li \c icon
92 : : \li string
93 : : \li The URL of the application's icon.
94 : :
95 : : \row
96 : : \li \c isRunning
97 : : \li bool
98 : : \li A boolean value representing the run-state of the application.
99 : : \row
100 : : \li \c isStartingUp
101 : : \li bool
102 : : \li A boolean value indicating whether the application is starting up and not fully operational yet.
103 : : \row
104 : : \li \c isShuttingDown
105 : : \li bool
106 : : \li A boolean value indicating whether the application is currently shutting down.
107 : : \row
108 : : \li \c isBlocked
109 : : \li bool
110 : : \li A boolean value that gets set when the application manager needs to block the application
111 : : from running: this is normally only the case while an update is applied.
112 : : \row
113 : : \li \c isUpdating
114 : : \li bool
115 : : \li A boolean value indicating whether the application is currently being installed or updated.
116 : : If \c true, the \c updateProgress can be used to track the actual progress.
117 : : \row
118 : : \li \c isRemovable
119 : : \li bool
120 : : \li A boolean value indicating whether this application is user-removable; \c true for all
121 : : dynamically installed third party applications and \c false for all system applications.
122 : :
123 : : \row
124 : : \li \c updateProgress
125 : : \li real
126 : : \li While \c isUpdating is \c true, querying this role returns the actual progress as a floating-point
127 : : value in the \c 0.0 to \c 1.0 range.
128 : :
129 : : \row
130 : : \li \c codeFilePath
131 : : \li string
132 : : \li The filesystem path to the main "executable". The format of this file is dependent on the
133 : : actual runtime though: for QML applications the "executable" is the \c main.qml file.
134 : :
135 : : \row
136 : : \li \c categories
137 : : \li list<string>
138 : : \li The categories this application is registered for via its meta-data file.
139 : :
140 : : \row
141 : : \li \c version
142 : : \li string
143 : : \li The currently installed version of this application.
144 : :
145 : : \row
146 : : \li \c application
147 : : \li ApplicationObject
148 : : \li The underlying \l ApplicationObject for quick access to the properties outside of a
149 : : model delegate.
150 : : \row
151 : : \li \c applicationObject
152 : : \li ApplicationObject
153 : : \li Exactly the same as \c application. This was added to keep the role names between the
154 : : PackageManager and ApplicationManager models as similar as possible.
155 : : This role was introduced in Qt version 6.6.
156 : : \endtable
157 : :
158 : : \note The index-based API is currently not available via DBus. However, the same functionality
159 : : is provided by the id-based API.
160 : :
161 : : After importing, you can just use the ApplicationManager singleton as follows:
162 : :
163 : : \qml
164 : : import QtQuick
165 : : import QtApplicationManager.SystemUI
166 : :
167 : : ListView {
168 : : id: appList
169 : : model: ApplicationManager
170 : :
171 : : delegate: Text {
172 : : text: name + "(" + applicationId + ")"
173 : :
174 : : MouseArea {
175 : : anchors.fill: parent
176 : : onClick: ApplicationManager.startApplication(applicationId)
177 : : }
178 : : }
179 : : }
180 : : \endqml
181 : : */
182 : :
183 : : /*!
184 : : \qmlproperty int ApplicationManager::count
185 : : \readonly
186 : :
187 : : This property holds the number of applications available.
188 : : */
189 : :
190 : : /*!
191 : : \qmlproperty bool ApplicationManager::singleProcess
192 : : \readonly
193 : :
194 : : This property indicates whether the application manager runs in single- or multi-process mode.
195 : : */
196 : :
197 : : /*!
198 : : \qmlproperty bool ApplicationManager::shuttingDown
199 : : \readonly
200 : :
201 : : This property is there to inform the System UI, when the application manager has entered its
202 : : shutdown phase. New application starts are already prevented, but the System UI might want
203 : : to impose additional restrictions in this state.
204 : : */
205 : :
206 : : /*!
207 : : \qmlproperty bool ApplicationManager::securityChecksEnabled
208 : : \readonly
209 : :
210 : : This property holds whether security related checks are enabled.
211 : :
212 : : \sa no-security
213 : : */
214 : :
215 : : /*!
216 : : \qmlproperty var ApplicationManager::systemProperties
217 : : \readonly
218 : :
219 : : Returns the project specific \l{system properties} that were set via the config file.
220 : : */
221 : :
222 : : /*!
223 : : \qmlproperty var ApplicationManager::containerSelectionFunction
224 : :
225 : : A JavaScript function callback that will be called whenever the application manager needs to
226 : : instantiate a container for running an application. See \l {Container Selection Configuration}
227 : : for more information.
228 : : */
229 : :
230 : : /*!
231 : : \qmlsignal ApplicationManager::applicationWasActivated(string id, string aliasId)
232 : :
233 : : This signal is emitted when an application identified by \a id is (re-)started via
234 : : the ApplicationManager API, possibly through an alias, provided in \a aliasId.
235 : :
236 : : The window manager should take care of raising the application's window in this case.
237 : : */
238 : :
239 : : /*!
240 : : \qmlsignal ApplicationManager::applicationRunStateChanged(string id, enumeration runState)
241 : :
242 : : This signal is emitted when the \a runState of the application identified by \a id changed.
243 : : Possible values for the \l {ApplicationObject::runState} {runState} are defined by the
244 : : ApplicationObject type.
245 : :
246 : : For example this signal can be used to restart an application in multi-process mode when
247 : : it has crashed:
248 : :
249 : : \qml
250 : : Connections {
251 : : target: ApplicationManager
252 : : function onApplicationRunStateChanged() {
253 : : if (runState === Am.NotRunning
254 : : && ApplicationManager.application(id).lastExitStatus === Am.CrashExit) {
255 : : ApplicationManager.startApplication(id);
256 : : }
257 : : }
258 : : }
259 : : \endqml
260 : :
261 : : See also Application::runState
262 : : */
263 : :
264 : : /*!
265 : : \qmlsignal ApplicationManager::applicationAdded(string id)
266 : :
267 : : This signal is emitted after a new application, identified by \a id, has been installed via the
268 : : PackageManager. The model has already been update before this signal is sent out.
269 : :
270 : : \note In addition to the normal "low-level" QAbstractListModel signals, the application manager
271 : : will also emit these "high-level" signals for System UIs that cannot work directly on the
272 : : ApplicationManager model: applicationAdded, applicationAboutToBeRemoved and applicationChanged.
273 : : */
274 : :
275 : : /*!
276 : : \qmlsignal ApplicationManager::applicationAboutToBeRemoved(string id)
277 : :
278 : : This signal is emitted before an existing application, identified by \a id, is removed via the
279 : : PackageManager.
280 : :
281 : : \note In addition to the normal "low-level" QAbstractListModel signals, the application manager
282 : : will also emit these "high-level" signals for System UIs that cannot work directly on the
283 : : ApplicationManager model: applicationAdded, applicationAboutToBeRemoved and applicationChanged.
284 : : */
285 : :
286 : : /*!
287 : : \qmlsignal ApplicationManager::applicationChanged(string id, list<string> changedRoles)
288 : :
289 : : Emitted whenever one or more data roles, denoted by \a changedRoles, changed on the application
290 : : identified by \a id. An empty list in the \a changedRoles argument means that all roles should
291 : : be considered modified.
292 : :
293 : : \note In addition to the normal "low-level" QAbstractListModel signals, the application manager
294 : : will also emit these "high-level" signals for System UIs that cannot work directly on the
295 : : ApplicationManager model: applicationAdded, applicationAboutToBeRemoved and applicationChanged.
296 : : */
297 : :
298 : : /*!
299 : : \qmlproperty bool ApplicationManager::windowManagerCompositorReady
300 : : \readonly
301 : :
302 : : This property starts with the value \c false and will change to \c true once the Wayland
303 : : compositor is ready to accept connections from other processes. In multi-process mode, this
304 : : happens implicitly after the System UI's main QML has been loaded.
305 : : */
306 : :
307 : : enum AMRoles
308 : : {
309 : : Id = Qt::UserRole,
310 : : Name,
311 : : Description,
312 : : Icon,
313 : :
314 : : IsRunning,
315 : : IsStartingUp,
316 : : IsShuttingDown,
317 : : IsBlocked,
318 : : IsUpdating,
319 : : IsRemovable,
320 : :
321 : : UpdateProgress,
322 : :
323 : : CodeFilePath,
324 : : RuntimeName,
325 : : RuntimeParameters,
326 : : Capabilities,
327 : : Categories,
328 : : Version,
329 : : ApplicationItem,
330 : : ApplicationObject, // needed to keep the roles similar to PackageManager
331 : :
332 : : LastExitCode,
333 : : LastExitStatus
334 : : };
335 : :
336 : : QT_BEGIN_NAMESPACE_AM
337 : :
338 [ + - + - ]: 53 : ApplicationManagerPrivate::ApplicationManagerPrivate()
339 : : {
340 [ + - + - ]: 53 : currentLocale = QLocale::system().name(); //TODO: language changes
341 : :
342 [ + - + - ]: 53 : roleNames.insert(AMRoles::Id, "applicationId");
343 [ + - + - ]: 53 : roleNames.insert(AMRoles::Name, "name");
344 [ + - + - ]: 53 : roleNames.insert(AMRoles::Description, "description");
345 [ + - + - ]: 53 : roleNames.insert(AMRoles::Icon, "icon");
346 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsRunning, "isRunning");
347 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsStartingUp, "isStartingUp");
348 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsShuttingDown, "isShuttingDown");
349 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsBlocked, "isBlocked");
350 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsUpdating, "isUpdating");
351 [ + - + - ]: 53 : roleNames.insert(AMRoles::IsRemovable, "isRemovable");
352 [ + - + - ]: 53 : roleNames.insert(AMRoles::UpdateProgress, "updateProgress");
353 [ + - + - ]: 53 : roleNames.insert(AMRoles::CodeFilePath, "codeFilePath");
354 [ + - + - ]: 53 : roleNames.insert(AMRoles::RuntimeName, "runtimeName");
355 [ + - + - ]: 53 : roleNames.insert(AMRoles::RuntimeParameters, "runtimeParameters");
356 [ + - + - ]: 53 : roleNames.insert(AMRoles::Capabilities, "capabilities");
357 [ + - + - ]: 53 : roleNames.insert(AMRoles::Categories, "categories");
358 [ + - + - ]: 53 : roleNames.insert(AMRoles::Version, "version");
359 [ + - + - ]: 53 : roleNames.insert(AMRoles::ApplicationItem, "application");
360 [ + - + - ]: 53 : roleNames.insert(AMRoles::ApplicationObject, "applicationObject");
361 [ + - + - ]: 53 : roleNames.insert(AMRoles::LastExitCode, "lastExitCode");
362 [ + - + - ]: 53 : roleNames.insert(AMRoles::LastExitStatus, "lastExitStatus");
363 : 53 : }
364 : :
365 : 52 : ApplicationManagerPrivate::~ApplicationManagerPrivate()
366 : : {
367 [ + + ]: 192 : for (const QString &scheme : std::as_const(registeredMimeSchemes))
368 : 140 : QDesktopServices::unsetUrlHandler(scheme);
369 : 52 : qDeleteAll(apps);
370 : 52 : }
371 : :
372 : : ApplicationManager *ApplicationManager::s_instance = nullptr;
373 : :
374 : 53 : ApplicationManager *ApplicationManager::createInstance(bool singleProcess)
375 : : {
376 [ - + ]: 53 : if (Q_UNLIKELY(s_instance))
377 : 0 : qFatal("ApplicationManager::createInstance() was called a second time.");
378 : :
379 [ + - ]: 53 : std::unique_ptr<ApplicationManager> am(new ApplicationManager(singleProcess));
380 : :
381 [ + - ]: 53 : qRegisterMetaType<Am::RunState>();
382 [ + - ]: 53 : qRegisterMetaType<Am::ExitStatus>();
383 [ + - ]: 53 : qRegisterMetaType<Am::ProcessError>();
384 : :
385 [ + - - + ]: 53 : if (Q_UNLIKELY(!PackageManager::instance()))
386 : 0 : qFatal("ApplicationManager::createInstance() was called before a PackageManager singleton was instantiated.");
387 : :
388 [ + - ]: 53 : s_instance = am.release();
389 [ + - + - ]: 53 : connect(&PackageManager::instance()->internalSignals, &PackageManagerInternalSignals::registerApplication,
390 : 111 : s_instance, [](ApplicationInfo *applicationInfo, Package *package) {
391 : 111 : instance()->addApplication(applicationInfo, package);
392 [ + - + - : 224 : qCDebug(LogSystem).nospace().noquote() << " ++ application: " << applicationInfo->id() << " [package: " << package->id() << "]";
+ - + - +
- + - + -
+ + ]
393 : 111 : });
394 [ + - + - ]: 53 : connect(&PackageManager::instance()->internalSignals, &PackageManagerInternalSignals::unregisterApplication,
395 : 20 : s_instance, [](ApplicationInfo *applicationInfo, Package *package) {
396 : 20 : instance()->removeApplication(applicationInfo, package);
397 [ + - + - : 41 : qCDebug(LogSystem).nospace().noquote() << " -- application: " << applicationInfo->id() << " [package: " << package->id() << "]";
+ - + - +
- + - + -
+ + ]
398 : 20 : });
399 : :
400 : 53 : return s_instance;
401 : 53 : }
402 : :
403 : 838 : ApplicationManager *ApplicationManager::instance()
404 : : {
405 [ - + ]: 838 : if (!s_instance)
406 : 0 : qFatal("ApplicationManager::instance() was called before createInstance().");
407 : 838 : return s_instance;
408 : : }
409 : :
410 : 53 : ApplicationManager::ApplicationManager(bool singleProcess, QObject *parent)
411 : : : QAbstractListModel(parent)
412 [ + - + - ]: 53 : , d(new ApplicationManagerPrivate())
413 : : {
414 : 53 : d->singleProcess = singleProcess;
415 [ + - ]: 53 : connect(this, &QAbstractItemModel::rowsInserted, this, &ApplicationManager::countChanged);
416 [ + - ]: 53 : connect(this, &QAbstractItemModel::rowsRemoved, this, &ApplicationManager::countChanged);
417 [ + - ]: 53 : connect(this, &QAbstractItemModel::layoutChanged, this, &ApplicationManager::countChanged);
418 [ + - ]: 53 : connect(this, &QAbstractItemModel::modelReset, this, &ApplicationManager::countChanged);
419 : 53 : }
420 : :
421 : 104 : ApplicationManager::~ApplicationManager()
422 : : {
423 [ + - ]: 52 : delete d;
424 : 52 : s_instance = nullptr;
425 : 104 : }
426 : :
427 : 96 : bool ApplicationManager::isSingleProcess() const
428 : : {
429 : 37 : return d->singleProcess;
430 : : }
431 : :
432 : 0 : bool ApplicationManager::isShuttingDown() const
433 : : {
434 : 0 : return d->shuttingDown;
435 : : }
436 : :
437 : 2 : bool ApplicationManager::securityChecksEnabled() const
438 : : {
439 : 2 : return d->securityChecksEnabled;
440 : : }
441 : :
442 : 7 : void ApplicationManager::setSecurityChecksEnabled(bool enabled)
443 : : {
444 : 7 : d->securityChecksEnabled = enabled;
445 : 7 : }
446 : :
447 : 128 : QVariantMap ApplicationManager::systemProperties() const
448 : : {
449 [ + + ]: 128 : return d->systemProperties;
450 : : }
451 : :
452 : 52 : void ApplicationManager::setSystemProperties(const QVariantMap &map)
453 : : {
454 : 52 : d->systemProperties = map;
455 : 52 : }
456 : :
457 : 52 : void ApplicationManager::setContainerSelectionConfiguration(const QList<std::pair<QString, QString>> &containerSelectionConfig)
458 : : {
459 : 52 : d->containerSelectionConfig = containerSelectionConfig;
460 : 52 : }
461 : :
462 : 1 : QJSValue ApplicationManager::containerSelectionFunction() const
463 : : {
464 : 1 : return d->containerSelectionFunction;
465 : : }
466 : :
467 : 1 : void ApplicationManager::setContainerSelectionFunction(const QJSValue &callback)
468 : : {
469 [ + - + - ]: 1 : if (callback.isCallable() && !callback.equals(d->containerSelectionFunction)) {
470 : 1 : d->containerSelectionFunction = callback;
471 : 1 : emit containerSelectionFunctionChanged();
472 : : }
473 : 1 : }
474 : :
475 : 90 : bool ApplicationManager::isWindowManagerCompositorReady() const
476 : : {
477 : 1 : return d->windowManagerCompositorReady;
478 : : }
479 : :
480 : 25 : void ApplicationManager::setWindowManagerCompositorReady(bool ready)
481 : : {
482 [ + - ]: 25 : if (d->windowManagerCompositorReady != ready) {
483 : 25 : d->windowManagerCompositorReady = ready;
484 : 25 : emit windowManagerCompositorReadyChanged(ready);
485 : : }
486 : 25 : }
487 : :
488 : 0 : QStringList ApplicationManager::availableRuntimeIds() const
489 : : {
490 : 0 : return RuntimeFactory::instance()->runtimeIds();
491 : : }
492 : :
493 : 2 : QStringList ApplicationManager::availableContainerIds() const
494 : : {
495 : 2 : return ContainerFactory::instance()->containerIds();
496 : : }
497 : :
498 : 0 : QVector<Application *> ApplicationManager::applications() const
499 : : {
500 : 0 : return d->apps;
501 : : }
502 : :
503 : 339 : Application *ApplicationManager::fromId(const QString &id) const
504 : : {
505 [ + + ]: 617 : for (Application *app : std::as_const(d->apps)) {
506 [ + + ]: 599 : if (app->id() == id)
507 : 321 : return app;
508 : : }
509 : : return nullptr;
510 : : }
511 : :
512 : 82 : QVector<Application *> ApplicationManager::fromProcessId(qint64 pid) const
513 : : {
514 : 82 : QVector<Application *> apps;
515 : :
516 : : // pid could be an indirect child (e.g. when started via gdbserver)
517 [ + - ]: 82 : qint64 appmanPid = QCoreApplication::applicationPid();
518 : :
519 : : int level = 0;
520 [ + + + - ]: 166 : while ((pid > 1) && (pid != appmanPid) && (level < 5)) {
521 [ + + ]: 395 : for (Application *app : std::as_const(d->apps)) {
522 [ - + ]: 311 : if (apps.contains(app))
523 : 0 : continue;
524 [ + + + - : 311 : if (app->currentRuntime() && (app->currentRuntime()->applicationProcessId() == pid))
+ + ]
525 [ + - ]: 82 : apps.append(app);
526 : : }
527 [ + - ]: 84 : pid = getParentPid(pid);
528 : 84 : ++level;
529 : : }
530 : 82 : return apps;
531 : 0 : }
532 : :
533 : 0 : Application *ApplicationManager::fromSecurityToken(const QByteArray &securityToken) const
534 : : {
535 [ # # ]: 0 : if (securityToken.size() != AbstractRuntime::SecurityTokenSize)
536 : : return nullptr;
537 : :
538 [ # # ]: 0 : for (Application *app : std::as_const(d->apps)) {
539 [ # # # # : 0 : if (app->currentRuntime() && app->currentRuntime()->securityToken() == securityToken)
# # # # ]
540 : 0 : return app;
541 : : }
542 : : return nullptr;
543 : : }
544 : :
545 : 4 : QVector<Application *> ApplicationManager::schemeHandlers(const QString &scheme) const
546 : : {
547 : 4 : QVector<Application *> handlers;
548 : :
549 [ + + ]: 16 : for (Application *app : std::as_const(d->apps)) {
550 [ + - ]: 12 : const auto mimeTypes = app->supportedMimeTypes();
551 [ + + ]: 20 : for (const QString &mime : mimeTypes) {
552 [ + - ]: 8 : auto pos = mime.indexOf(u'/');
553 : :
554 : 16 : if ((pos > 0)
555 [ + - + + : 16 : && (mime.left(pos) == u"x-scheme-handler")
+ + ]
556 [ + - + - : 20 : && (mime.mid(pos + 1) == scheme)) {
- + + + +
- - - ]
557 [ + - ]: 12 : handlers << app;
558 : : }
559 : : }
560 : 12 : }
561 : 4 : return handlers;
562 : 0 : }
563 : :
564 : 4 : QVector<Application *> ApplicationManager::mimeTypeHandlers(const QString &mimeType) const
565 : : {
566 : 4 : QVector<Application *> handlers;
567 : :
568 [ + + ]: 16 : for (Application *app : std::as_const(d->apps)) {
569 [ + - + + ]: 24 : if (app->supportedMimeTypes().contains(mimeType))
570 [ + - ]: 16 : handlers << app;
571 : : }
572 : 4 : return handlers;
573 : 0 : }
574 : :
575 : 27 : QVariantMap ApplicationManager::get(Application *app) const
576 : : {
577 : 27 : QVariantMap map;
578 [ + + ]: 27 : if (app) {
579 [ + - ]: 25 : const QHash<int, QByteArray> roles = roleNames();
580 [ + + ]: 550 : for (auto it = roles.begin(); it != roles.end(); ++it)
581 [ + - + - : 1050 : map.insert(QString::fromLatin1(it.value()), dataForRole(app, it.key()));
+ - ]
582 : 25 : }
583 : 27 : return map;
584 : 0 : }
585 : :
586 : 131 : void ApplicationManager::registerMimeTypes()
587 : : {
588 : : #if defined(QT_GUI_LIB)
589 : 131 : QSet<QString> schemes;
590 [ + - + - : 131 : schemes << u"file"_s << u"http"_s << u"https"_s;
+ - ]
591 : :
592 [ + + ]: 343 : for (Application *app : std::as_const(d->apps)) {
593 [ + - ]: 212 : const auto mimeTypes = app->supportedMimeTypes();
594 [ + + ]: 224 : for (const QString &mime : mimeTypes) {
595 [ + - ]: 12 : auto pos = mime.indexOf(u'/');
596 : :
597 [ + - + - : 24 : if ((pos > 0) && (mime.left(pos) == u"x-scheme-handler"))
+ + + - +
+ ]
598 [ + - ]: 12 : schemes << mime.mid(pos + 1);
599 : : }
600 : 212 : }
601 [ + - ]: 131 : QSet<QString> registerSchemes = schemes;
602 [ + - ]: 131 : registerSchemes.subtract(d->registeredMimeSchemes);
603 [ + + ]: 131 : QSet<QString> unregisterSchemes = d->registeredMimeSchemes;
604 [ + - ]: 131 : unregisterSchemes.subtract(schemes);
605 : :
606 [ + - - - ]: 131 : for (const QString &scheme : std::as_const(unregisterSchemes))
607 [ # # ]: 0 : QDesktopServices::unsetUrlHandler(scheme);
608 [ + + + - ]: 274 : for (const QString &scheme : std::as_const(registerSchemes))
609 [ + - ]: 143 : QDesktopServices::setUrlHandler(scheme, this, "openUrlRelay");
610 : :
611 : 131 : d->registeredMimeSchemes = schemes;
612 : : #endif
613 : 131 : }
614 : :
615 : 211 : bool ApplicationManager::startApplicationInternal(const QString &appId, const QString &documentUrl,
616 : : const QString &documentMimeType,
617 : : const QString &debugWrapperSpecification,
618 : : QVector<int> &&stdioRedirections) noexcept(false)
619 : : {
620 : 422 : auto redirectionGuard = qScopeGuard([&stdioRedirections]() {
621 : 211 : closeAndClearFileDescriptors(stdioRedirections);
622 [ - + ]: 211 : });
623 : :
624 [ - + ]: 211 : if (d->shuttingDown)
625 : 0 : throw Exception("Cannot start applications during shutdown");
626 [ + - + + ]: 211 : QPointer<Application> app = fromId(appId);
627 [ + + ]: 211 : if (!app)
628 : 2 : throw Exception("Cannot start application: id '%1' is not known").arg(appId);
629 [ + - - + ]: 209 : if (app->isBlocked())
630 [ # # ]: 0 : throw Exception("Application %1 is blocked - cannot start").arg( app->id());
631 : :
632 [ + + ]: 209 : AbstractRuntime *runtime = app->currentRuntime();
633 [ + + + - : 215 : auto runtimeManager = runtime ? runtime->manager() : RuntimeFactory::instance()->manager(app->runtimeName());
+ - + - +
- ]
634 [ + + ]: 209 : if (!runtimeManager)
635 [ + - ]: 1 : throw Exception("No RuntimeManager found for runtime: %1").arg(app->runtimeName());
636 [ + - ]: 208 : bool inProcess = runtimeManager->inProcess();
637 : :
638 : : // validate stdio redirections
639 [ - + ]: 208 : if (stdioRedirections.size() > 3) {
640 : 0 : throw Exception("Tried to start application %1 using an invalid standard IO redirection specification")
641 [ # # ]: 0 : .arg(app->id());
642 : : }
643 [ + + ]: 208 : bool hasStdioRedirections = !stdioRedirections.isEmpty();
644 [ + + ]: 208 : if (hasStdioRedirections) {
645 : : // we have an array - check if it just consists of -1 fds
646 : 2 : hasStdioRedirections = false;
647 : 2 : std::for_each(stdioRedirections.cbegin(), stdioRedirections.cend(), [&hasStdioRedirections](int fd) {
648 [ - + ]: 6 : if (fd >= 0)
649 : 0 : hasStdioRedirections = true;
650 : : });
651 : : }
652 : :
653 : : // validate the debug-wrapper
654 : 208 : QStringList debugWrapperCommand;
655 : 208 : QMap<QString, QString> debugEnvironmentVariables;
656 [ + + ]: 208 : if (!debugWrapperSpecification.isEmpty()) {
657 [ + + ]: 5 : if (isSingleProcess())
658 : 1 : throw Exception("Using debug-wrappers is not supported when the application manager is running in single-process mode.");
659 [ - + ]: 4 : if (inProcess) {
660 : 0 : throw Exception("Using debug-wrappers is not supported when starting an app using an in-process runtime (%1).")
661 [ # # ]: 0 : .arg(runtimeManager->identifier());
662 : : }
663 : :
664 [ + - + + ]: 4 : if (!DebugWrapper::parseSpecification(debugWrapperSpecification, debugWrapperCommand,
665 : : debugEnvironmentVariables)) {
666 : 1 : throw Exception("Tried to start application %1 using an invalid debug-wrapper specification: %2")
667 [ + - ]: 2 : .arg(app->id(), debugWrapperSpecification);
668 : : }
669 : : }
670 : :
671 [ + + ]: 206 : if (runtime) {
672 [ + - + - : 54 : switch (runtime->state()) {
- - ]
673 : 54 : case Am::StartingUp:
674 : 54 : case Am::Running:
675 [ + + ]: 54 : if (!debugWrapperCommand.isEmpty()) {
676 : 1 : throw Exception("Application %1 is already running - cannot start with debug-wrapper: %2")
677 [ + - ]: 2 : .arg(app->id(), debugWrapperSpecification);
678 : : }
679 [ - + ]: 53 : if (hasStdioRedirections) {
680 : 0 : throw Exception("Application %1 is already running - cannot set standard IO redirections")
681 [ # # ]: 0 : .arg(app->id());
682 : : }
683 [ + - ]: 53 : if (!documentUrl.isNull())
684 [ + - ]: 53 : runtime->openDocument(documentUrl, documentMimeType);
685 [ # # # # ]: 0 : else if (!app->documentUrl().isNull())
686 [ - - - - ]: 3 : runtime->openDocument(app->documentUrl(), documentMimeType);
687 : :
688 [ + - ]: 53 : emitActivated(app);
689 : : return true;
690 : :
691 : : case Am::ShuttingDown:
692 : : return false;
693 : :
694 : 0 : case Am::NotRunning:
695 : 0 : throw Exception("Application %1 is not running, but still has a Runtime object attached")
696 [ # # ]: 0 : .arg(app->id());
697 : : }
698 : : }
699 : :
700 : 152 : AbstractContainer *container = nullptr;
701 : 205 : QString containerId;
702 : :
703 [ + + ]: 152 : if (!inProcess) {
704 [ + + ]: 89 : if (d->containerSelectionConfig.isEmpty()) {
705 : 88 : containerId = u"process"_s;
706 : : } else {
707 : : // check config file
708 [ + - ]: 1 : for (const auto &it : std::as_const(d->containerSelectionConfig)) {
709 : 1 : const QString &key = it.first;
710 : 1 : const QString &value = it.second;
711 [ - + ]: 1 : bool hasAsterisk = key.contains(u'*');
712 : :
713 [ # # ]: 0 : if ((hasAsterisk && key.length() == 1)
714 [ + - + - : 3 : || (!hasAsterisk && key == app->id())
- + + - ]
715 [ - + - - : 2 : || QRegularExpression(QRegularExpression::wildcardToRegularExpression(key)).match(app->id()).hasMatch()) {
- - - - -
- - - - -
- + - + -
+ - + + -
- - - - -
- - - -
- ]
716 : 1 : containerId = value;
717 : 1 : break;
718 : : }
719 : : }
720 : : }
721 : :
722 [ + - + + ]: 89 : if (d->containerSelectionFunction.isCallable()) {
723 [ + - + - : 56 : QJSValueList args = { QJSValue(app->id()), QJSValue(containerId) };
+ - - + ]
724 [ + - + - ]: 14 : containerId = d->containerSelectionFunction.call(args).toString();
725 : 14 : }
726 : :
727 [ + - + - : 89 : if (!ContainerFactory::instance()->manager(containerId))
- + ]
728 : 0 : throw Exception("No ContainerManager found for container: %1").arg(containerId);
729 : : }
730 : 152 : bool attachRuntime = false;
731 : :
732 [ + - ]: 152 : if (!runtime) {
733 [ + + ]: 152 : if (!inProcess) {
734 [ + - + + ]: 89 : if (QuickLauncher::instance()) {
735 : : // we cannot use the quicklaunch pool, if
736 : : // (a) a debug-wrapper is being used,
737 : : // (b) stdio is redirected or
738 : : // (c) the app requests special environment variables or
739 : : // (d) the app requests a different OpenGL config from the AM
740 : 11 : const char *cannotUseQuickLaunch = nullptr;
741 : :
742 [ + - ]: 11 : if (!debugWrapperCommand.isEmpty())
743 : : cannotUseQuickLaunch = "the app is started using a debug-wrapper";
744 [ + - ]: 11 : else if (hasStdioRedirections)
745 : : cannotUseQuickLaunch = "standard I/O is redirected";
746 [ + - + - : 22 : else if (!app->runtimeParameters().value(u"environmentVariables"_s).toMap().isEmpty())
+ - + - ]
747 : : cannotUseQuickLaunch = "the app requests custom environment variables";
748 [ + - + - : 11 : else if (app->info()->openGLConfiguration() != GlobalRuntimeConfiguration::instance().openGLConfiguration)
+ - + - +
- ]
749 : : cannotUseQuickLaunch = "the app requests a custom OpenGL configuration";
750 : :
751 : 11 : if (cannotUseQuickLaunch) {
752 [ # # # # : 0 : qCDebug(LogSystem) << "Cannot use quick-launch for application" << app->id()
# # # # #
# # # ]
753 [ # # # # ]: 0 : << "because" << cannotUseQuickLaunch;
754 : : } else {
755 : : // check quicklaunch pool
756 : 11 : QPair<AbstractContainer *, AbstractRuntime *> quickLaunch =
757 [ + - + - : 11 : QuickLauncher::instance()->take(containerId, app->info()->runtimeName());
+ - + - ]
758 : 11 : container = quickLaunch.first;
759 : 11 : runtime = quickLaunch.second;
760 : :
761 [ + - ]: 11 : if (container || runtime) {
762 [ + - - - : 22 : qCDebug(LogSystem) << "Found a quick-launch entry for container" << containerId
- - - - -
+ ]
763 [ # # # # : 0 : << "and runtime" << app->info()->runtimeName() << "->"
# # # # #
# # # ]
764 [ # # # # ]: 0 : << container << runtime;
765 : :
766 [ - + ]: 11 : if (!container && runtime) {
767 [ # # ]: 0 : runtime->deleteLater();
768 [ # # # # : 0 : qCCritical(LogSystem) << "ERROR: QuickLauncher provided a runtime without a container.";
# # # # ]
769 : 0 : return false;
770 : : }
771 : : }
772 : : }
773 : : }
774 : :
775 [ - + ]: 11 : if (!container) {
776 [ + - + - ]: 78 : container = ContainerFactory::instance()->create(containerId, app, std::move(stdioRedirections),
777 : : debugEnvironmentVariables, debugWrapperCommand);
778 : : } else {
779 [ + - ]: 11 : container->setApplication(app);
780 : : }
781 [ - + ]: 89 : if (!container) {
782 [ # # # # : 0 : qCCritical(LogSystem) << "ERROR: Couldn't create Container for Application (" << app->id() <<")!";
# # # # #
# # # #
# ]
783 : : return false;
784 : : }
785 [ + + ]: 89 : if (runtime)
786 : 9 : attachRuntime = true;
787 : :
788 : : } else { // inProcess
789 [ - + ]: 63 : if (hasStdioRedirections) {
790 : 0 : static const char *noRedirectMsg = "NOTE: redirecting standard IO is not possible for in-process runtimes";
791 : :
792 [ # # ]: 0 : int fd = stdioRedirections.value(2, -1);
793 [ # # ]: 0 : if (fd < 0)
794 [ # # ]: 0 : fd = stdioRedirections.value(1, -1);
795 [ # # ]: 0 : if (fd >= 0) {
796 [ # # # # ]: 0 : auto dummy = write(fd, noRedirectMsg, qstrlen(noRedirectMsg));
797 [ # # ]: 0 : dummy += write(fd, "\n", 1);
798 : : Q_UNUSED(dummy)
799 : : }
800 [ # # # # : 0 : qCWarning(LogSystem) << noRedirectMsg;
# # # # ]
801 : : }
802 : : }
803 : :
804 [ + + ]: 152 : if (!runtime)
805 [ + - + - ]: 143 : runtime = RuntimeFactory::instance()->create(container, app);
806 : :
807 [ + - ]: 152 : if (runtime)
808 [ + - ]: 152 : emit internalSignals.newRuntimeCreated(runtime);
809 : : }
810 : :
811 [ - + ]: 152 : if (!runtime) {
812 [ # # # # : 0 : qCCritical(LogSystem) << "ERROR: Couldn't create Runtime for Application (" << app->id() <<")!";
# # # # #
# # # #
# ]
813 : : return false;
814 : : }
815 : :
816 : : // if an app is stopped because of a removal and the container is slow to stop, we might
817 : : // end up with a dead app pointer in this callback at some point
818 [ + - + - ]: 456 : connect(runtime, &AbstractRuntime::stateChanged, this, [this, app, appId](Am::RunState newRuntimeState) {
819 [ + - ]: 583 : if (app)
820 : 583 : app->setRunState(newRuntimeState);
821 : 583 : emit applicationRunStateChanged(appId, newRuntimeState);
822 [ + - ]: 583 : if (app)
823 [ + - ]: 1166 : emitDataChanged(app, QVector<int> { AMRoles::IsRunning, AMRoles::IsStartingUp, AMRoles::IsShuttingDown });
824 : 583 : });
825 : :
826 [ + + ]: 152 : if (!documentUrl.isNull())
827 [ + - ]: 39 : runtime->openDocument(documentUrl, documentMimeType);
828 [ + - - + ]: 113 : else if (!app->documentUrl().isNull())
829 [ # # # # ]: 0 : runtime->openDocument(app->documentUrl(), documentMimeType);
830 : :
831 : 152 : Q_ASSERT(inProcess == containerId.isEmpty());
832 : :
833 [ + - ]: 63 : const QString containerInfo = (containerId.isEmpty() && inProcess)
834 [ + + ]: 152 : ? u"in-process"_s
835 [ + + + - : 545 : : u"in container \""_s + containerId + u'"';
+ + + + +
+ ]
836 : :
837 [ + - - - : 304 : qCDebug(LogSystem).noquote().nospace()
- - - + ]
838 [ # # # # : 0 : << "Starting application \"" << app->id() << "\" " << containerInfo
# # # # #
# ]
839 [ # # # # : 0 : << " using runtime \"" << runtimeManager->identifier() << '"';
# # # # ]
840 [ + + ]: 152 : if (!documentUrl.isEmpty())
841 [ + - - - : 74 : qCDebug(LogSystem) << " documentUrl:" << documentUrl;
- - - - -
+ ]
842 : :
843 : : // We can only start the app when both the container and the windowmanager are ready.
844 : : // Using a state-machine would be one option, but then we would need that state-machine
845 : : // object plus the per-app state. Relying on 2 lambdas is the easier choice for now.
846 : :
847 : 304 : auto doStartInContainer = [this, app, attachRuntime, runtime, containerInfo]() -> bool {
848 : 152 : bool successfullyStarted = false;
849 [ + - ]: 152 : if (app) {
850 [ + + ]: 152 : successfullyStarted = attachRuntime ? runtime->attachApplicationToQuickLauncher(app)
851 : 143 : : runtime->start();
852 : : }
853 [ + + ]: 152 : if (successfullyStarted) {
854 : 150 : emitActivated(app);
855 : : } else {
856 [ + - + + ]: 6 : qCWarning(LogSystem).noquote().nospace()
857 [ + - + - : 4 : << "Failed to start application \"" << app->id() << "\" " << containerInfo
+ - + - +
- ]
858 [ + - + - : 4 : << " using runtime \"" << runtime->manager()->identifier() << '"';
+ - + - +
- ]
859 [ + - ]: 2 : delete runtime; // ~Runtime() will clean up app->m_runtime
860 : : }
861 : 152 : return successfullyStarted;
862 [ + - ]: 456 : };
863 : :
864 : 152 : auto tryStartInContainer = [this, container, inProcess, doStartInContainer]() -> bool {
865 [ + + + - ]: 152 : if (inProcess || container->isReady()) {
866 : : // Since the container is already ready, start the app immediately
867 : 152 : return doStartInContainer();
868 : : } else {
869 : : // We postpone the starting of the application to a later point in time,
870 : : // since the container is not ready yet
871 : : // since the container is not ready yet
872 : : #if defined(Q_CC_MSVC)
873 : : qApp->connect(container, &AbstractContainer::ready, this, doStartInContainer); // MSVC cannot distinguish between static and non-static overloads in lambdas
874 : : #else
875 : 0 : connect(container, &AbstractContainer::ready, this, doStartInContainer);
876 : : #endif
877 : 0 : return true;
878 : : }
879 : 304 : };
880 : :
881 [ + + - + ]: 152 : if (inProcess || isWindowManagerCompositorReady()) {
882 [ + - ]: 152 : return tryStartInContainer();
883 : : } else {
884 [ # # ]: 0 : connect(this, &ApplicationManager::windowManagerCompositorReadyChanged, tryStartInContainer);
885 : 0 : return true;
886 : : }
887 : 217 : }
888 : :
889 : 104 : void ApplicationManager::stopApplicationInternal(Application *app, bool forceKill)
890 : : {
891 [ + - ]: 104 : if (!app)
892 : : return;
893 [ + + ]: 104 : AbstractRuntime *rt = app->currentRuntime();
894 [ + + ]: 104 : if (rt)
895 : 96 : rt->stop(forceKill);
896 : : }
897 : :
898 : : /*!
899 : : \qmlmethod bool ApplicationManager::startApplication(string id, string document)
900 : :
901 : : Instructs the application manager to start the application identified by its unique \a id. The
902 : : optional argument \a document will be supplied to the application as is - most commonly this
903 : : is used to refer to a document to display.
904 : : Returns \c true if the application \a id is valid and the application manager was able to start
905 : : the runtime plugin. Returns \c false otherwise. Note that even though this call may
906 : : indicate success, the application may still later fail to start correctly as the actual
907 : : startup process within the runtime plugin may be asynchronous.
908 : :
909 : : \sa ApplicationObject::start
910 : : */
911 : 197 : bool ApplicationManager::startApplication(const QString &id, const QString &documentUrl)
912 : : {
913 : 197 : try {
914 [ + + ]: 200 : return startApplicationInternal(id, documentUrl);
915 [ - + ]: 3 : } catch (const Exception &e) {
916 [ + - + - : 9 : qCWarning(LogSystem) << e.what();
+ - + + ]
917 : 3 : return false;
918 : 3 : }
919 : : }
920 : :
921 : : /*!
922 : : \qmlmethod bool ApplicationManager::debugApplication(string id, string debugWrapper, string document)
923 : :
924 : : Instructs the application manager to start the application identified by its unique \a id, just
925 : : like startApplication. The application is started via the given \a debugWrapper though. The
926 : : optional argument \a document will be supplied to the application as is - most commonly this is
927 : : used to refer to a document to display.
928 : :
929 : : Returns a \c bool value indicating success. See the full documentation at
930 : : ApplicationManager::startApplication for more information.
931 : :
932 : : Please see the \l{Debugging} page for more information on how to setup and use these
933 : : debug-wrappers.
934 : :
935 : : \sa ApplicationObject::debug
936 : : */
937 : :
938 : 4 : bool ApplicationManager::debugApplication(const QString &id, const QString &debugWrapper, const QString &documentUrl)
939 : : {
940 : 4 : try {
941 [ + + ]: 7 : return startApplicationInternal(id, documentUrl, QString(), debugWrapper);
942 [ - + ]: 3 : } catch (const Exception &e) {
943 [ + - + - : 9 : qCWarning(LogSystem) << e.what();
+ - + + ]
944 : 3 : return false;
945 : 3 : }
946 : : }
947 : :
948 : : /*!
949 : : \qmlmethod ApplicationManager::stopApplication(string id, bool forceKill)
950 : :
951 : : Tells the application manager to stop an application identified by its unique \a id. The
952 : : meaning of the \a forceKill parameter is runtime dependent, but in general you should always try
953 : : to stop an application with \a forceKill set to \c false first in order to allow a clean
954 : : shutdown. Use \a forceKill set to \c true only as a last resort to kill hanging applications.
955 : :
956 : : QML applications and native applications that \l {manifest supportsApplicationInterface}
957 : : {support the ApplicationInterface} will be notified via ApplicationInterface::quit().
958 : : All other applications will be sent the Unix \c TERM signal.
959 : :
960 : : \sa ApplicationObject::stop
961 : : */
962 : 104 : void ApplicationManager::stopApplication(const QString &id, bool forceKill)
963 : : {
964 : 104 : stopApplicationInternal(fromId(id), forceKill);
965 : 104 : }
966 : :
967 : : /*!
968 : : \qmlmethod ApplicationManager::stopAllApplications(bool forceKill)
969 : :
970 : : Tells the application manager to stop all running applications. The meaning of the \a forceKill
971 : : parameter is runtime dependent, but in general you should always try to stop an application
972 : : with \a forceKill set to \c false first in order to allow a clean shutdown.
973 : : Use \a forceKill set to \c true only as a last resort to kill hanging applications.
974 : :
975 : : \sa stopApplication
976 : : */
977 : 40 : void ApplicationManager::stopAllApplications(bool forceKill)
978 : : {
979 [ + + ]: 275 : for (Application *app : std::as_const(d->apps)) {
980 [ + + ]: 235 : AbstractRuntime *rt = app->currentRuntime();
981 [ + + ]: 235 : if (rt)
982 : 27 : rt->stop(forceKill);
983 : : }
984 : 40 : }
985 : :
986 : : /*!
987 : : \qmlmethod bool ApplicationManager::openUrl(string url)
988 : :
989 : : Tries start an application that is capable of handling \a url. The application manager will
990 : : first look at the URL's scheme:
991 : : \list
992 : : \li If it is \c{file:}, the operating system's MIME database will be consulted, which will
993 : : try to find a MIME type match, based on file endings or file content. In case this is
994 : : successful, the application manager will use this MIME type to find all of its applications
995 : : that claim support for it (see the \l{mimeTypes field} in the application's manifest).
996 : : A music player application that can handle \c mp3 and \c wav files, could add this to its
997 : : manifest:
998 : : \badcode
999 : : mimeTypes: [ 'audio/mpeg', 'audio/wav' ]
1000 : : \endcode
1001 : : \li If it is something other than \c{file:}, the application manager will consult its
1002 : : internal database of applications that claim support for a matching \c{x-scheme-handler/...}
1003 : : MIME type. In order to have your web-browser application handle \c{http:} and \c{https:}
1004 : : URLs, you would have to have this in your application's manifest:
1005 : : \badcode
1006 : : mimeTypes: [ 'x-scheme-handler/http', 'x-scheme-handler/https' ]
1007 : : \endcode
1008 : : \endlist
1009 : :
1010 : : If there is at least one possible match, it depends on the signal openUrlRequested() being
1011 : : connected within the System UI:
1012 : : In case the signal is not connected, an arbitrary application from the matching set will be
1013 : : started. Otherwise the application manager will emit the openUrlRequested signal and
1014 : : return \c true. It is up to the receiver of this signal to choose from one of possible
1015 : : applications via acknowledgeOpenUrlRequest or deny the request entirely via rejectOpenUrlRequest.
1016 : : Not calling one of these two functions will result in memory leaks.
1017 : :
1018 : : If an application is started by one of the two mechanisms, the \a url is supplied to
1019 : : the application as a document to open via its ApplicationInterface.
1020 : :
1021 : : Returns \c true, if a match was found in the database, or \c false otherwise.
1022 : :
1023 : : \sa openUrlRequested, acknowledgeOpenUrlRequest, rejectOpenUrlRequest
1024 : : */
1025 : 8 : bool ApplicationManager::openUrl(const QString &urlStr)
1026 : : {
1027 : : //TODO: relay to a well-known Intent call
1028 : :
1029 : : // QDesktopServices::openUrl has a special behavior when called recursively, which makes sense
1030 : : // on the desktop, but is completely counter-productive for the AM.
1031 : 8 : static bool recursionGuard = false;
1032 [ - + ]: 8 : if (recursionGuard) {
1033 [ # # ]: 0 : QMetaObject::invokeMethod(this, [urlStr, this]() { openUrl(urlStr); }, Qt::QueuedConnection);
1034 : 0 : return true; // this is not correct, but the best we can do in this situation
1035 : : }
1036 : 8 : recursionGuard = true;
1037 : :
1038 : 8 : QUrl url(urlStr);
1039 : 8 : QString mimeTypeName;
1040 : 8 : QVector<Application *> apps;
1041 : :
1042 [ + - + - ]: 8 : if (url.isValid()) {
1043 [ + - ]: 8 : QString scheme = url.scheme();
1044 [ + + ]: 8 : if (scheme != u"file")
1045 [ + - ]: 8 : apps = schemeHandlers(scheme);
1046 : :
1047 [ + + ]: 8 : if (apps.isEmpty()) {
1048 [ + - ]: 4 : QMimeDatabase mdb;
1049 [ + - ]: 4 : QMimeType mt = mdb.mimeTypeForUrl(url);
1050 [ + - ]: 4 : mimeTypeName = mt.name();
1051 : :
1052 [ + - ]: 8 : apps = mimeTypeHandlers(mimeTypeName);
1053 : 4 : }
1054 : 8 : }
1055 : :
1056 [ + - ]: 8 : if (!apps.isEmpty()) {
1057 [ + - + - : 8 : if (!isSignalConnected(QMetaMethod::fromSignal(&ApplicationManager::openUrlRequested))) {
+ - ]
1058 : : // If the System UI does not react to the signal, then just use the first match.
1059 : 8 : try {
1060 [ + - + - ]: 16 : startApplicationInternal(apps.constFirst()->id(), urlStr, mimeTypeName);
1061 [ - - ]: 0 : } catch (const Exception &e) {
1062 [ - - - - : 0 : qCWarning(LogSystem) << "openUrl for" << urlStr << "requested app" << apps.constFirst()->id()
- - - - -
- - - - -
- - ]
1063 [ - - - - ]: 0 : << "which could not be started:" << e.errorString();
1064 : 0 : }
1065 : : } else {
1066 : 0 : ApplicationManagerPrivate::OpenUrlRequest req {
1067 [ # # ]: 0 : QUuid::createUuid().toString(),
1068 : : urlStr,
1069 : : mimeTypeName,
1070 : : QStringList()
1071 [ # # ]: 0 : };
1072 [ # # ]: 0 : for (const auto &app : std::as_const(apps))
1073 [ # # ]: 0 : req.possibleAppIds << app->id();
1074 [ # # ]: 0 : d->openUrlRequests << req;
1075 : :
1076 [ # # ]: 0 : emit openUrlRequested(req.requestId, req.urlStr, req.mimeTypeName, req.possibleAppIds);
1077 : 0 : }
1078 : : }
1079 : :
1080 : 8 : recursionGuard = false;
1081 : 8 : return !apps.isEmpty();
1082 : 8 : }
1083 : :
1084 : : /*!
1085 : : \qmlsignal ApplicationManager::openUrlRequested(string requestId, string url, string mimeType, list<string> possibleAppIds)
1086 : :
1087 : : This signal is emitted when the application manager is requested to open an URL. This can happen
1088 : : by calling
1089 : : \list
1090 : : \li Qt.openUrlExternally in an application,
1091 : : \li Qt.openUrlExternally in the System UI,
1092 : : \li ApplicationManager::openUrl in the System UI or
1093 : : \li \c io.qt.ApplicationManager.openUrl via D-Bus
1094 : : \endlist
1095 : : \note This signal is only emitted, if there is a receiver connected at all - see openUrl for the
1096 : : fallback behavior.
1097 : :
1098 : : The receiver of this signal can inspect the requested \a url and its \a mimeType. It can then
1099 : : either call acknowledgeOpenUrlRequest to choose from one of the supplied \a possibleAppIds or
1100 : : rejectOpenUrlRequest to ignore the request. In both cases the unique \a requestId needs to be
1101 : : sent to identify the request.
1102 : : Not calling one of these two functions will result in memory leaks.
1103 : :
1104 : : \sa openUrl, acknowledgeOpenUrlRequest, rejectOpenUrlRequest
1105 : : */
1106 : :
1107 : : /*!
1108 : : \qmlmethod ApplicationManager::acknowledgeOpenUrlRequest(string requestId, string appId)
1109 : :
1110 : : Tells the application manager to go ahead with the request to open an URL, identified by \a
1111 : : requestId. The chosen \a appId needs to be one of the \c possibleAppIds supplied to the
1112 : : receiver of the openUrlRequested signal.
1113 : :
1114 : : \sa openUrl, openUrlRequested
1115 : : */
1116 : 0 : void ApplicationManager::acknowledgeOpenUrlRequest(const QString &requestId, const QString &appId)
1117 : : {
1118 [ # # ]: 0 : for (auto it = d->openUrlRequests.cbegin(); it != d->openUrlRequests.cend(); ++it) {
1119 [ # # ]: 0 : if (it->requestId == requestId) {
1120 [ # # ]: 0 : if (it->possibleAppIds.contains(appId)) {
1121 : 0 : try {
1122 [ # # ]: 0 : startApplicationInternal(appId, it->urlStr, it->mimeTypeName);
1123 [ - - ]: 0 : } catch (const Exception &e) {
1124 [ - - - - : 0 : qCWarning(LogSystem) << "acknowledgeOpenUrlRequest for" << it->urlStr << "requested app"
- - - - -
- - - ]
1125 [ - - - - : 0 : << appId << "which could not be started:" << e.errorString();
- - ]
1126 : 0 : }
1127 : : } else {
1128 [ # # # # : 0 : qCWarning(LogSystem) << "acknowledgeOpenUrlRequest for" << it->urlStr << "requested app"
# # # # ]
1129 [ # # # # : 0 : << appId << "which is not one of the registered possibilities:"
# # ]
1130 [ # # ]: 0 : << it->possibleAppIds;
1131 : : }
1132 : 0 : d->openUrlRequests.erase(it);
1133 : : break;
1134 : : }
1135 : : }
1136 : 0 : }
1137 : :
1138 : : /*!
1139 : : \qmlmethod ApplicationManager::rejectOpenUrlRequest(string requestId)
1140 : :
1141 : : Tells the application manager to ignore the request to open an URL, identified by \a requestId.
1142 : :
1143 : : \sa openUrl, openUrlRequested
1144 : : */
1145 : 0 : void ApplicationManager::rejectOpenUrlRequest(const QString &requestId)
1146 : : {
1147 [ # # ]: 0 : for (auto it = d->openUrlRequests.cbegin(); it != d->openUrlRequests.cend(); ++it) {
1148 [ # # ]: 0 : if (it->requestId == requestId) {
1149 : 0 : d->openUrlRequests.erase(it);
1150 : : break;
1151 : : }
1152 : : }
1153 : 0 : }
1154 : :
1155 : : /*!
1156 : : \qmlmethod list<string> ApplicationManager::capabilities(string id)
1157 : :
1158 : : Returns a list of all capabilities granted by the user to the application identified by \a id.
1159 : : Returns an empty list if the application \a id is not valid.
1160 : : */
1161 : 4 : QStringList ApplicationManager::capabilities(const QString &id) const
1162 : : {
1163 : 4 : Application *app = fromId(id);
1164 [ + - ]: 4 : return app ? app->capabilities() : QStringList();
1165 : : }
1166 : :
1167 : : /*!
1168 : : \qmlmethod string ApplicationManager::identifyApplication(int pid)
1169 : :
1170 : : Validates the process running with process-identifier \a pid as a process started by the
1171 : : application manager.
1172 : :
1173 : : \note If multiple applications are running within the same container process, this function
1174 : : will return only the first matching application. See identifyAllApplications() for
1175 : : a way to retrieve all application ids.
1176 : :
1177 : : Returns the application's \c id on success, or an empty string on failure.
1178 : : */
1179 : 0 : QString ApplicationManager::identifyApplication(qint64 pid) const
1180 : : {
1181 : 0 : const auto apps = fromProcessId(pid);
1182 [ # # # # ]: 0 : return !apps.isEmpty() ? apps.constFirst()->id() : QString();
1183 : 0 : }
1184 : :
1185 : : /*!
1186 : : \qmlmethod list<string> ApplicationManager::identifyAllApplications(int pid)
1187 : :
1188 : : Validates the process running with process-identifier \a pid as a process started by the
1189 : : application manager.
1190 : :
1191 : : If multiple applications are running within the same container process, this function will
1192 : : return all those application ids.
1193 : :
1194 : : Returns a list with the applications' \c ids on success, or an empty list on failure.
1195 : : */
1196 : 0 : QStringList ApplicationManager::identifyAllApplications(qint64 pid) const
1197 : : {
1198 : 0 : const auto apps = fromProcessId(pid);
1199 : 0 : QStringList result;
1200 : 0 : result.reserve(apps.size());
1201 [ # # ]: 0 : for (const auto &app : apps)
1202 [ # # ]: 0 : result << app->id();
1203 : 0 : return result;
1204 : 0 : }
1205 : :
1206 : 52 : void ApplicationManager::shutDown()
1207 : : {
1208 : 52 : d->shuttingDown = true;
1209 : 52 : emit shuttingDownChanged();
1210 : :
1211 : 118 : auto shutdownHelper = [this]() {
1212 : 66 : bool activeRuntime = false;
1213 [ + + ]: 172 : for (Application *app : std::as_const(d->apps)) {
1214 [ + + ]: 115 : AbstractRuntime *rt = app->currentRuntime();
1215 [ + + ]: 115 : if (rt) {
1216 : : activeRuntime = true;
1217 : : break;
1218 : : }
1219 : : }
1220 [ + + ]: 66 : if (!activeRuntime)
1221 : 57 : emit internalSignals.shutDownFinished();
1222 : 118 : };
1223 : :
1224 [ + + ]: 142 : for (Application *app : std::as_const(d->apps)) {
1225 [ + + ]: 90 : AbstractRuntime *rt = app->currentRuntime();
1226 [ + + ]: 90 : if (rt) {
1227 : 14 : connect(rt, &AbstractRuntime::destroyed,
1228 : : this, shutdownHelper);
1229 : 14 : rt->stop();
1230 : : }
1231 : : }
1232 : 52 : shutdownHelper();
1233 : 52 : }
1234 : :
1235 : 4 : void ApplicationManager::openUrlRelay(const QUrl &url)
1236 : : {
1237 [ - + ]: 4 : if (QThread::currentThread() != thread()) {
1238 : 0 : staticMetaObject.invokeMethod(this, "openUrlRelay", Qt::QueuedConnection, Q_ARG(QUrl, url));
1239 : 0 : return;
1240 : : }
1241 [ + - ]: 8 : openUrl(url.toString());
1242 : : }
1243 : :
1244 : 651 : void ApplicationManager::emitDataChanged(Application *app, const QVector<int> &roles)
1245 : : {
1246 [ + - ]: 651 : auto row = d->apps.indexOf(app);
1247 [ + - ]: 651 : if (row >= 0) {
1248 : 651 : emit dataChanged(index(int(row)), index(int(row)), roles);
1249 : :
1250 [ + + + - ]: 688 : static const auto appChanged = QMetaMethod::fromSignal(&ApplicationManager::applicationChanged);
1251 [ + + ]: 651 : if (isSignalConnected(appChanged)) {
1252 : 646 : QStringList stringRoles;
1253 : 646 : stringRoles.reserve(roles.size());
1254 [ + + ]: 2440 : for (auto role : roles)
1255 [ + - + - : 3588 : stringRoles << QString::fromLatin1(d->roleNames[role]);
+ - ]
1256 [ + - + - ]: 1292 : emit applicationChanged(app->id(), stringRoles);
1257 : 646 : }
1258 : : }
1259 : 651 : }
1260 : :
1261 : 203 : void ApplicationManager::emitActivated(Application *app)
1262 : : {
1263 [ + - + - ]: 406 : emit applicationWasActivated(app->id(), app->id());
1264 : 203 : emit app->activated();
1265 : 203 : }
1266 : :
1267 : : // item model part
1268 : :
1269 : 3093 : int ApplicationManager::rowCount(const QModelIndex &parent) const
1270 : : {
1271 [ - + ]: 3093 : if (parent.isValid())
1272 : : return 0;
1273 : 3093 : return int(d->apps.count());
1274 : : }
1275 : :
1276 : 456 : QVariant ApplicationManager::data(const QModelIndex &index, int role) const
1277 : : {
1278 [ + - ]: 912 : if (index.parent().isValid() || !index.isValid())
1279 : 0 : return QVariant();
1280 : :
1281 : 456 : Application *app = d->apps.at(index.row());
1282 : 456 : return dataForRole(app, role);
1283 : : }
1284 : :
1285 : 981 : QVariant ApplicationManager::dataForRole(Application *app, int role) const
1286 : : {
1287 [ + + + + : 981 : switch (role) {
+ + + + +
+ + + + +
+ + + + +
+ + ]
1288 : 27 : case AMRoles::Id:
1289 : 27 : return app->id();
1290 : 29 : case AMRoles::Name:
1291 : 29 : return app->name();
1292 : 25 : case AMRoles::Description:
1293 : 25 : return app->description();
1294 : 27 : case AMRoles::Icon:
1295 : 27 : return app->icon();
1296 : 71 : case AMRoles::IsRunning:
1297 [ + + + + ]: 93 : return app->currentRuntime() ? (app->currentRuntime()->state() == Am::Running) : false;
1298 : 71 : case AMRoles::IsStartingUp:
1299 [ + + + + ]: 93 : return app->currentRuntime() ? (app->currentRuntime()->state() == Am::StartingUp) : false;
1300 : 71 : case AMRoles::IsShuttingDown:
1301 [ + + + + ]: 97 : return app->currentRuntime() ? (app->currentRuntime()->state() == Am::ShuttingDown) : false;
1302 : 27 : case AMRoles::IsBlocked:
1303 : 27 : return app->isBlocked();
1304 : 27 : case AMRoles::IsUpdating:
1305 : 27 : return app->state() != Application::Installed;
1306 : 27 : case AMRoles::UpdateProgress:
1307 : 27 : return app->progress();
1308 : 27 : case AMRoles::IsRemovable:
1309 : 27 : return !app->isBuiltIn();
1310 : 27 : case AMRoles::CodeFilePath:
1311 : 27 : return app->info()->absoluteCodeFilePath();
1312 : 27 : case AMRoles::RuntimeName:
1313 : 27 : return app->runtimeName();
1314 : 25 : case AMRoles::RuntimeParameters:
1315 : 25 : return app->runtimeParameters();
1316 : 29 : case AMRoles::Capabilities:
1317 : 29 : return app->capabilities();
1318 : 25 : case AMRoles::Categories:
1319 : 25 : return app->categories();
1320 : 27 : case AMRoles::Version:
1321 : 27 : return app->version();
1322 : 70 : case AMRoles::ApplicationItem:
1323 : 70 : case AMRoles::ApplicationObject:
1324 : 70 : return QVariant::fromValue(app);
1325 : 25 : case AMRoles::LastExitCode:
1326 : 25 : return app->lastExitCode();
1327 : 25 : case AMRoles::LastExitStatus:
1328 : 25 : return app->lastExitStatus();
1329 : : }
1330 : 272 : return QVariant();
1331 : : }
1332 : :
1333 : 34 : QHash<int, QByteArray> ApplicationManager::roleNames() const
1334 : : {
1335 [ + - ]: 34 : return d->roleNames;
1336 : : }
1337 : :
1338 : 1143 : int ApplicationManager::count() const
1339 : : {
1340 : 1143 : return rowCount();
1341 : : }
1342 : :
1343 : : /*!
1344 : : \qmlmethod object ApplicationManager::get(int index)
1345 : :
1346 : : Retrieves the model data at \a index as a JavaScript object. See the
1347 : : \l {ApplicationManager Roles}{role names} for the expected object fields.
1348 : :
1349 : : Returns an empty object if the specified \a index is invalid.
1350 : :
1351 : : \note This is very inefficient if you only want to access a single property from QML; use
1352 : : application() instead to access the Application object's properties directly.
1353 : : */
1354 : 4 : QVariantMap ApplicationManager::get(int index) const
1355 : : {
1356 [ + + - + ]: 4 : if (index < 0 || index >= count()) {
1357 [ + - + - : 6 : qCWarning(LogSystem) << "ApplicationManager::get(index): invalid index:" << index;
+ + ]
1358 : 4 : return QVariantMap();
1359 : : }
1360 : 2 : return get(d->apps.at(index));
1361 : : }
1362 : :
1363 : : /*!
1364 : : \qmlmethod ApplicationObject ApplicationManager::application(int index)
1365 : :
1366 : : Returns the \l{ApplicationObject}{application} corresponding to the given \a index in the
1367 : : model, or \c null if the index is invalid.
1368 : :
1369 : : \note The object ownership of the returned Application object stays with the application manager.
1370 : : If you want to store this pointer, you can use the ApplicationManager's QAbstractListModel
1371 : : signals or the applicationAboutToBeRemoved signal to get notified if the object is about
1372 : : to be deleted on the C++ side.
1373 : : */
1374 : 668 : Application *ApplicationManager::application(int index) const
1375 : : {
1376 [ + + - + ]: 668 : if (index < 0 || index >= count()) {
1377 [ + - + - : 6 : qCWarning(LogSystem) << "ApplicationManager::application(index): invalid index:" << index;
+ + ]
1378 : : return nullptr;
1379 : : }
1380 : 666 : return d->apps.at(index);
1381 : : }
1382 : :
1383 : : /*!
1384 : : \qmlmethod ApplicationObject ApplicationManager::application(string id)
1385 : :
1386 : : Returns the \l{ApplicationObject}{application} corresponding to the given application \a id,
1387 : : or \c null if the id does not exist.
1388 : :
1389 : : \note The object ownership of the returned Application object stays with the application manager.
1390 : : If you want to store this pointer, you can use the ApplicationManager's QAbstractListModel
1391 : : signals or the applicationAboutToBeRemoved signal to get notified if the object is about
1392 : : to be deleted on the C++ side.
1393 : : */
1394 : 222 : Application *ApplicationManager::application(const QString &id) const
1395 : : {
1396 : 222 : auto index = indexOfApplication(id);
1397 [ + + ]: 222 : return (index < 0) ? nullptr : application(index);
1398 : : }
1399 : :
1400 : : /*!
1401 : : \qmlmethod int ApplicationManager::indexOfApplication(string id)
1402 : :
1403 : : Maps the application corresponding to the given \a id to its position within the model. Returns
1404 : : \c -1 if the specified \a id is invalid.
1405 : : */
1406 : 315 : int ApplicationManager::indexOfApplication(const QString &id) const
1407 : : {
1408 [ + + ]: 607 : for (int i = 0; i < d->apps.size(); ++i) {
1409 [ + + ]: 599 : if (d->apps.at(i)->id() == id)
1410 : 307 : return i;
1411 : : }
1412 : : return -1;
1413 : : }
1414 : :
1415 : : /*!
1416 : : \qmlmethod int ApplicationManager::indexOfApplication(ApplicationObject application)
1417 : :
1418 : : Maps the \a application to its position within this model. Returns \c -1 if the specified
1419 : : application is invalid.
1420 : : */
1421 : 0 : int ApplicationManager::indexOfApplication(Application *application) const
1422 : : {
1423 [ # # ]: 0 : return int(d->apps.indexOf(application));
1424 : : }
1425 : :
1426 : : /*!
1427 : : \qmlmethod list<string> ApplicationManager::applicationIds()
1428 : :
1429 : : Returns a list of all available application ids. This can be used to further query for specific
1430 : : information via get().
1431 : : */
1432 : 7 : QStringList ApplicationManager::applicationIds() const
1433 : : {
1434 : 7 : QStringList ids;
1435 : 7 : ids.reserve(d->apps.size());
1436 [ + + ]: 27 : for (int i = 0; i < d->apps.size(); ++i)
1437 [ + - ]: 40 : ids << d->apps.at(i)->id();
1438 : 7 : return ids;
1439 : 0 : }
1440 : :
1441 : : /*!
1442 : : \qmlmethod object ApplicationManager::get(string id)
1443 : :
1444 : : Retrieves the model data for the application identified by \a id as a JavaScript object.
1445 : : See the \l {ApplicationManager Roles}{role names} for the expected object fields.
1446 : :
1447 : : Returns an empty object if the specified \a id is invalid.
1448 : : */
1449 : 5 : QVariantMap ApplicationManager::get(const QString &id) const
1450 : : {
1451 : 5 : return get(application(id));
1452 : : }
1453 : :
1454 : 61 : Am::RunState ApplicationManager::applicationRunState(const QString &id) const
1455 : : {
1456 : 61 : int index = indexOfApplication(id);
1457 [ + + ]: 61 : return (index < 0) ? Am::NotRunning : d->apps.at(index)->runState();
1458 : : }
1459 : :
1460 : 111 : void ApplicationManager::addApplication(ApplicationInfo *appInfo, Package *package)
1461 : : {
1462 : : // check for id clashes outside of the package (the scanner made sure the package itself is
1463 : : // consistent and doesn't have duplicates already)
1464 [ + + ]: 202 : for (Application *checkApp : std::as_const(d->apps)) {
1465 [ + - + - : 182 : if ((checkApp->id() == appInfo->id()) && (checkApp->package() != package)) {
- + - - -
- - + - -
- - ]
1466 : 0 : throw Exception("found an application with the same id in package %1")
1467 [ # # # # ]: 0 : .arg(checkApp->packageInfo()->id());
1468 : : }
1469 : : }
1470 : :
1471 [ + - ]: 111 : auto app = new Application(appInfo, package);
1472 : 111 : QQmlEngine::setObjectOwnership(app, QQmlEngine::CppOwnership);
1473 : :
1474 : 242 : app->requests.startRequested = [this, app](const QString &documentUrl) {
1475 [ + - ]: 131 : return startApplication(app->id(), documentUrl);
1476 : 111 : };
1477 : :
1478 : 111 : app->requests.debugRequested = [this, app](const QString &debugWrapper, const QString &documentUrl) {
1479 [ # # ]: 0 : return debugApplication(app->id(), debugWrapper, documentUrl);
1480 : 111 : };
1481 : :
1482 : 184 : app->requests.stopRequested = [this, app](bool forceKill) {
1483 [ + - ]: 73 : stopApplication(app->id(), forceKill);
1484 : 184 : };
1485 : :
1486 : 111 : connect(app, &Application::blockedChanged,
1487 : 96 : this, [this, app]() {
1488 [ + - ]: 48 : emitDataChanged(app, QVector<int> { AMRoles::IsBlocked });
1489 : 48 : });
1490 : 111 : connect(app, &Application::bulkChange,
1491 : 20 : this, [this, app]() {
1492 [ + - ]: 20 : emitDataChanged(app);
1493 : 20 : });
1494 : :
1495 : 111 : beginInsertRows(QModelIndex(), int(d->apps.count()), int(d->apps.count()));
1496 : 111 : d->apps << app;
1497 : :
1498 : 111 : endInsertRows();
1499 : :
1500 : 111 : registerMimeTypes();
1501 : :
1502 : 111 : package->addApplication(app);
1503 [ + - ]: 111 : emit applicationAdded(appInfo->id());
1504 : 111 : }
1505 : :
1506 : 20 : void ApplicationManager::removeApplication(ApplicationInfo *appInfo, Package *package)
1507 : : {
1508 : 20 : int index = -1;
1509 : :
1510 [ + - ]: 28 : for (int i = 0; i < d->apps.size(); ++i) {
1511 [ + + ]: 28 : if (d->apps.at(i)->info() == appInfo) {
1512 : : index = i;
1513 : : break;
1514 : : }
1515 : : }
1516 [ + - ]: 20 : if (index < 0)
1517 : : return;
1518 : :
1519 : 20 : Q_ASSERT(d->apps.at(index)->package() == package);
1520 : :
1521 [ - + ]: 20 : if (d->aboutToBeRemoved) {
1522 [ # # # # ]: 0 : qCFatal(LogSystem) << "ApplicationManager::removeApplication was called recursively";
1523 : : return;
1524 : : }
1525 [ + - ]: 20 : QScopedValueRollback<bool> rollback(d->aboutToBeRemoved, true);
1526 : :
1527 [ + - + - ]: 20 : emit applicationAboutToBeRemoved(appInfo->id());
1528 : :
1529 [ + - ]: 20 : package->removeApplication(d->apps.at(index));
1530 : :
1531 [ + - ]: 20 : beginRemoveRows(QModelIndex(), index, index);
1532 [ + - ]: 20 : auto app = d->apps.takeAt(index);
1533 : :
1534 [ + - ]: 20 : endRemoveRows();
1535 : :
1536 [ + - ]: 20 : registerMimeTypes();
1537 : :
1538 [ + - ]: 20 : delete app;
1539 : 20 : }
1540 : :
1541 : : QT_END_NAMESPACE_AM
1542 : :
1543 : : #include "moc_applicationmanager.cpp"
1544 : : #include "moc_amnamespace.cpp" // amnamespace is header only, so we include it here
|