feat(applications): route launching and quitting through the app runtime

main
reionwong 3 weeks ago
parent dbb514c648
commit b4ea1e83c0

@ -3,6 +3,8 @@ find_package(Qt6 REQUIRED COMPONENTS Core DBus Gui Qml)
set(CUTEFISH_APPLICATIONS_SRCS
applicationlauncher.cpp
applicationlauncher.h
applicationruntime.cpp
applicationruntime.h
applicationregistry.cpp
applicationregistry.h
desktopentry.cpp

@ -1,26 +1,16 @@
#include "applicationlauncher.h"
#include <QDBusConnection>
#include <QDBusInterface>
#include <QProcess>
#include "applicationruntime.h"
bool ApplicationLauncher::startDetached(const QStringList &command,
const QString &workingDirectory)
const QString &workingDirectory,
const QString &appId)
{
if (command.isEmpty() || command.first().isEmpty())
return false;
const QString program = command.first();
const QStringList arguments = command.mid(1);
// Keep using the session launch service when it is available. It applies
// the desktop session's environment and startup handling consistently.
QDBusInterface session("com.cutefish.Session", "/Session",
"com.cutefish.Session", QDBusConnection::sessionBus());
if (session.isValid()) {
session.asyncCall("launch", program, arguments);
return true;
}
// Every application start of the session goes through the runtime, which
// falls back to starting the process here when it is not running.
return ApplicationRuntime::instance()->launchCommand(command, workingDirectory, appId);
}
return QProcess::startDetached(program, arguments, workingDirectory);
bool ApplicationLauncher::quit(const QString &appId)
{
return ApplicationRuntime::instance()->quitApplication(appId);
}

@ -6,6 +6,11 @@
class ApplicationLauncher
{
public:
// appId is only a hint for the runtime: it is the key an instance is
// tracked and can later be quit under.
static bool startDetached(const QStringList &command,
const QString &workingDirectory = QString());
const QString &workingDirectory = QString(),
const QString &appId = QString());
static bool quit(const QString &appId);
};

@ -1,4 +1,5 @@
#include "applicationregistry.h"
#include "applicationruntime.h"
#include <QDir>
#include <QDirIterator>
@ -427,6 +428,11 @@ void registerApplicationsQmlTypes()
[](QQmlEngine *, QJSEngine *) -> QObject * {
return ApplicationRegistry::instance();
});
qmlRegisterSingletonType<ApplicationRuntime>(
"Cutefish.Applications", 1, 0, "AppRuntime",
[](QQmlEngine *, QJSEngine *) -> QObject * {
return ApplicationRuntime::instance();
});
qmlRegisterUncreatableType<DesktopEntry>(
"Cutefish.Applications", 1, 0, "DesktopEntry",
QStringLiteral("DesktopEntry objects are provided by DesktopEntries"));

@ -0,0 +1,152 @@
#include "applicationruntime.h"
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QProcess>
static const char *kService = "com.cutefish.AppRuntime";
static const char *kPath = "/AppRuntime";
ApplicationRuntime *ApplicationRuntime::instance()
{
static ApplicationRuntime *runtime = new ApplicationRuntime;
return runtime;
}
ApplicationRuntime::ApplicationRuntime(QObject *parent)
: QObject(parent)
{
}
QDBusInterface *ApplicationRuntime::interface()
{
if (!m_interface) {
m_interface = new QDBusInterface(QLatin1String(kService), QLatin1String(kPath),
QLatin1String(kService),
QDBusConnection::sessionBus(), this);
// Launching is a blocking call from user interface code, so a runtime
// that stops answering must not freeze the caller for the default
// half minute.
m_interface->setTimeout(5000);
QDBusConnection::sessionBus().connect(QLatin1String(kService), QLatin1String(kPath),
QLatin1String(kService),
QStringLiteral("applicationLaunched"), this,
SIGNAL(applicationLaunched(QString, quint32)));
QDBusConnection::sessionBus().connect(QLatin1String(kService), QLatin1String(kPath),
QLatin1String(kService),
QStringLiteral("applicationQuit"), this,
SIGNAL(applicationQuit(QString, quint32)));
}
return m_interface;
}
bool ApplicationRuntime::available()
{
return interface()->isValid();
}
bool ApplicationRuntime::launchApplication(const QString &appId, const QStringList &arguments)
{
if (appId.isEmpty())
return false;
QDBusInterface *iface = interface();
if (!iface->isValid())
return false;
const QDBusReply<uint> reply = iface->call(QStringLiteral("launchApplication"), appId, arguments);
return reply.isValid() && reply.value() != 0;
}
bool ApplicationRuntime::launchCommand(const QStringList &command,
const QString &workingDirectory,
const QString &appId)
{
if (command.isEmpty() || command.first().isEmpty())
return false;
QDBusInterface *iface = interface();
if (iface->isValid()) {
const QDBusReply<uint> reply = iface->call(QStringLiteral("launchCommand"), appId,
command, workingDirectory);
if (reply.isValid() && reply.value() != 0)
return true;
}
return QProcess::startDetached(command.first(), command.mid(1), workingDirectory);
}
bool ApplicationRuntime::quitApplication(const QString &appId)
{
if (appId.isEmpty())
return false;
QDBusInterface *iface = interface();
if (!iface->isValid())
return false;
const QDBusReply<bool> reply = iface->call(QStringLiteral("quitApplication"), appId);
return reply.isValid() && reply.value();
}
bool ApplicationRuntime::quitAll()
{
QDBusInterface *iface = interface();
if (!iface->isValid())
return false;
const QDBusReply<bool> reply = iface->call(QStringLiteral("quitAll"));
return reply.isValid() && reply.value();
}
bool ApplicationRuntime::quitByPid(quint32 pid)
{
if (pid == 0)
return false;
QDBusInterface *iface = interface();
if (!iface->isValid())
return false;
const QDBusReply<bool> reply = iface->call(QStringLiteral("quitByPid"), pid);
return reply.isValid() && reply.value();
}
bool ApplicationRuntime::isRunning(const QString &appId)
{
QDBusInterface *iface = interface();
if (!iface->isValid())
return false;
const QDBusReply<bool> reply = iface->call(QStringLiteral("isRunning"), appId);
return reply.isValid() && reply.value();
}
QStringList ApplicationRuntime::runningApplications()
{
QDBusInterface *iface = interface();
if (!iface->isValid())
return QStringList();
const QDBusReply<QStringList> reply = iface->call(QStringLiteral("runningApplications"));
return reply.isValid() ? reply.value() : QStringList();
}
QList<quint32> ApplicationRuntime::pidsForApplication(const QString &appId)
{
QDBusInterface *iface = interface();
if (!iface->isValid())
return QList<quint32>();
const QDBusReply<QList<uint>> reply = iface->call(QStringLiteral("pidsForApplication"), appId);
return reply.isValid() ? reply.value() : QList<quint32>();
}

@ -0,0 +1,44 @@
#pragma once
#include <QObject>
#include <QStringList>
class QDBusInterface;
/**
* Client side of com.cutefish.AppRuntime, the process that owns every
* application start of the session. Falls back to starting the process in
* place when the runtime is not available, so callers never have to care.
*/
class ApplicationRuntime : public QObject
{
Q_OBJECT
public:
static ApplicationRuntime *instance();
explicit ApplicationRuntime(QObject *parent = nullptr);
bool available();
Q_INVOKABLE bool launchApplication(const QString &appId,
const QStringList &arguments = QStringList());
Q_INVOKABLE bool launchCommand(const QStringList &command,
const QString &workingDirectory = QString(),
const QString &appId = QString());
Q_INVOKABLE bool quitApplication(const QString &appId);
Q_INVOKABLE bool quitAll();
Q_INVOKABLE bool quitByPid(quint32 pid);
Q_INVOKABLE bool isRunning(const QString &appId);
Q_INVOKABLE QStringList runningApplications();
Q_INVOKABLE QList<quint32> pidsForApplication(const QString &appId);
signals:
void applicationLaunched(const QString &appId, quint32 pid);
void applicationQuit(const QString &appId, quint32 pid);
private:
QDBusInterface *interface();
QDBusInterface *m_interface = nullptr;
};

@ -1,6 +1,7 @@
#include "desktopentry.h"
#include "applicationlauncher.h"
#include "applicationruntime.h"
#include <QFile>
#include <QFileInfo>
@ -246,8 +247,13 @@ QStringList DesktopEntry::commandForArguments(const QStringList &arguments) cons
bool DesktopEntry::launch(const QStringList &arguments) const
{
// Prefer the entry id so the runtime can tie the process to this
// application; the expanded command is only the fallback path.
if (ApplicationRuntime::instance()->launchApplication(m_id, arguments))
return true;
return ApplicationLauncher::startDetached(commandForArguments(arguments),
m_workingDirectory);
m_workingDirectory, m_id);
}
QStringList DesktopEntry::parseExec(const QString &exec)

Loading…
Cancel
Save