mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-09 21:13:08 +00:00
Add Game Center daemon target
Build and install the Game Center daemon as a DBus-activated\nuser service. This introduces the new src/gamecenter target and\nwires it into the main build.
This commit is contained in:
parent
55d5a70656
commit
017073c162
11 changed files with 1811 additions and 0 deletions
|
|
@ -137,3 +137,5 @@ ecm_add_qml_module(alakarte URI org.kde.alakarte
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS alakarte ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
install(TARGETS alakarte ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|
||||||
|
add_subdirectory(gamecenter)
|
||||||
|
|
|
||||||
44
src/gamecenter/CMakeLists.txt
Normal file
44
src/gamecenter/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
# SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
add_executable(alakarte_gamecenter
|
||||||
|
main.cpp
|
||||||
|
gamecenterdaemon.cpp
|
||||||
|
gamecenterdaemon.h
|
||||||
|
processscanner.cpp
|
||||||
|
processscanner.h
|
||||||
|
systemdusermanager.cpp
|
||||||
|
systemdusermanager.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(alakarte_gamecenter PRIVATE
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::DBus
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(alakarte_gamecenter PROPERTIES
|
||||||
|
OUTPUT_NAME "alakarte-gamecenter"
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS alakarte_gamecenter ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/dbus")
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/systemd")
|
||||||
|
|
||||||
|
configure_file(dbus/org.kde.GameCenter1.service.in
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/dbus/org.kde.GameCenter1.service"
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_file(systemd/org.kde.GameCenter1.service.in
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/systemd/org.kde.GameCenter1.service"
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/org.kde.GameCenter1.service"
|
||||||
|
DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/org.kde.GameCenter1.service"
|
||||||
|
DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}
|
||||||
|
)
|
||||||
4
src/gamecenter/dbus/org.kde.GameCenter1.service.in
Normal file
4
src/gamecenter/dbus/org.kde.GameCenter1.service.in
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[D-BUS Service]
|
||||||
|
Name=org.kde.GameCenter1
|
||||||
|
Exec=@CMAKE_INSTALL_PREFIX@/@KDE_INSTALL_BINDIR@/alakarte-gamecenter
|
||||||
|
SystemdService=org.kde.GameCenter1.service
|
||||||
1155
src/gamecenter/gamecenterdaemon.cpp
Normal file
1155
src/gamecenter/gamecenterdaemon.cpp
Normal file
File diff suppressed because it is too large
Load diff
96
src/gamecenter/gamecenterdaemon.h
Normal file
96
src/gamecenter/gamecenterdaemon.h
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
#include "processscanner.h"
|
||||||
|
#include "systemdusermanager.h"
|
||||||
|
|
||||||
|
class GameCenterDaemon : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_CLASSINFO("D-Bus Interface", "org.kde.GameCenter1")
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GameCenterDaemon(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
QString Ping() const;
|
||||||
|
void Version(uint &major, uint &minor) const;
|
||||||
|
QVariantMap GetCapabilities() const;
|
||||||
|
|
||||||
|
QVariantList ListSessions() const;
|
||||||
|
QVariantMap GetSession(const QString &sessionId) const;
|
||||||
|
|
||||||
|
QString Launch(const QVariantMap &launchSpec);
|
||||||
|
void Stop(const QString &sessionId);
|
||||||
|
void StopByGameId(const QString &gameId);
|
||||||
|
|
||||||
|
void SetPolicy(const QVariantMap &policy);
|
||||||
|
QVariantMap GetPolicy() const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void SessionAdded(const QVariantMap &session);
|
||||||
|
void SessionChanged(const QVariantMap &session);
|
||||||
|
void SessionRemoved(const QString &sessionId, const QVariantMap &finalState);
|
||||||
|
void LaunchFailed(const QVariantMap &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Session {
|
||||||
|
QString sessionId;
|
||||||
|
QString gameId;
|
||||||
|
QString displayName;
|
||||||
|
QString unitName;
|
||||||
|
QDBusObjectPath unitPath;
|
||||||
|
QString provider;
|
||||||
|
QDateTime startTime;
|
||||||
|
QPointer<QProcess> process;
|
||||||
|
QPointer<ProcessScanner> scanner;
|
||||||
|
uint mainPid = 0;
|
||||||
|
bool stopping = false;
|
||||||
|
bool hasExitInfo = false;
|
||||||
|
int exitCode = 0;
|
||||||
|
int exitStatus = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVariantMap sessionToVariantMap(const Session &session, const QString &state) const;
|
||||||
|
static QString sessionState(const Session &session);
|
||||||
|
void failLaunch(const QString &reason, const QVariantMap &context = {});
|
||||||
|
void recoverExistingSessions();
|
||||||
|
|
||||||
|
void setupSystemdSubscriptions();
|
||||||
|
void watchSystemdUnit(const QString &sessionId, const QString &unitName, const QDBusObjectPath &unitPath);
|
||||||
|
void unwatchSystemdUnit(const QString &unitName, const QDBusObjectPath &unitPath);
|
||||||
|
void handleSystemdUnitPropertiesChanged(const QDBusObjectPath &unitPath, const QVariantMap &changedProperties);
|
||||||
|
void removeSessionInternal(const QString &sessionId, const QString &finalState);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void handleSystemdUnitNew(const QString &unitName, const QDBusObjectPath &unitPath);
|
||||||
|
void handleSystemdUnitRemoved(const QString &unitName, const QDBusObjectPath &unitPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString launchDirect(const QVariantMap &launchSpec);
|
||||||
|
QString launchMonitored(const QVariantMap &launchSpec, const QString &provider);
|
||||||
|
void attachPidsToSession(const QString &sessionId, const QList<uint> &pids);
|
||||||
|
|
||||||
|
static QString detectProvider(const QVariantMap &launchSpec);
|
||||||
|
static QString extractSteamAppId(const QString &command);
|
||||||
|
static QString extractLutrisId(const QString &command);
|
||||||
|
|
||||||
|
SystemdUserManager m_systemd;
|
||||||
|
QHash<QString, Session> m_sessions;
|
||||||
|
int m_maxConcurrent = 0;
|
||||||
|
QHash<QString, QString> m_unitPathToSessionId;
|
||||||
|
QHash<QString, QString> m_unitNameToSessionId;
|
||||||
|
QHash<QString, QObject *> m_unitPathWatchers;
|
||||||
|
};
|
||||||
18
src/gamecenter/main.cpp
Normal file
18
src/gamecenter/main.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include "gamecenterdaemon.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
GameCenterDaemon daemon;
|
||||||
|
if (!daemon.init()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
183
src/gamecenter/processscanner.cpp
Normal file
183
src/gamecenter/processscanner.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#include "processscanner.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
ProcessScanner::ProcessScanner(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
m_timer.setSingleShot(false);
|
||||||
|
m_deadline.setSingleShot(true);
|
||||||
|
|
||||||
|
connect(&m_deadline, &QTimer::timeout, this, [this]() {
|
||||||
|
m_timer.stop();
|
||||||
|
Q_EMIT timedOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&m_timer, &QTimer::timeout, this, [this]() {
|
||||||
|
if (!m_matcher) {
|
||||||
|
m_timer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QList<Match> results = m_matcher();
|
||||||
|
if (!results.isEmpty()) {
|
||||||
|
m_timer.stop();
|
||||||
|
m_deadline.stop();
|
||||||
|
Q_EMIT found(results);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray readProcFile(const QString &path, qint64 maxSize = 65536)
|
||||||
|
{
|
||||||
|
QFile f(path);
|
||||||
|
if (!f.open(QIODevice::ReadOnly)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return f.read(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString readExeLink(const QString &pidDir)
|
||||||
|
{
|
||||||
|
return QFileInfo(pidDir + QStringLiteral("/exe")).symLinkTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray readCmdline(const QString &pidDir)
|
||||||
|
{
|
||||||
|
return readProcFile(pidDir + QStringLiteral("/cmdline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray readEnviron(const QString &pidDir)
|
||||||
|
{
|
||||||
|
return readProcFile(pidDir + QStringLiteral("/environ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<uint> listPids()
|
||||||
|
{
|
||||||
|
QList<uint> pids;
|
||||||
|
const QDir proc(QStringLiteral("/proc"));
|
||||||
|
const QStringList entries = proc.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
for (const QString &entry : entries) {
|
||||||
|
bool ok = false;
|
||||||
|
const uint pid = entry.toUInt(&ok);
|
||||||
|
if (ok && pid > 1) {
|
||||||
|
pids.push_back(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pids;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> ProcessScanner::findByEnvironment(const QString &key, const QString &value)
|
||||||
|
{
|
||||||
|
QList<Match> matches;
|
||||||
|
const QByteArray needle = (key + QLatin1Char('=') + value).toUtf8();
|
||||||
|
|
||||||
|
const QList<uint> pids = listPids();
|
||||||
|
for (uint pid : pids) {
|
||||||
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||||||
|
const QByteArray env = readEnviron(pidDir);
|
||||||
|
if (env.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// environ entries are separated by null bytes
|
||||||
|
bool found = false;
|
||||||
|
int start = 0;
|
||||||
|
while (start < env.size()) {
|
||||||
|
int end = env.indexOf('\0', start);
|
||||||
|
if (end < 0) {
|
||||||
|
end = env.size();
|
||||||
|
}
|
||||||
|
if (env.mid(start, end - start) == needle) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
Match m;
|
||||||
|
m.pid = pid;
|
||||||
|
m.exe = readExeLink(pidDir);
|
||||||
|
const QByteArray cmd = readCmdline(pidDir);
|
||||||
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||||||
|
matches.push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> ProcessScanner::findByCmdline(const QString &substring)
|
||||||
|
{
|
||||||
|
QList<Match> matches;
|
||||||
|
const QByteArray needle = substring.toUtf8();
|
||||||
|
|
||||||
|
const QList<uint> pids = listPids();
|
||||||
|
for (uint pid : pids) {
|
||||||
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||||||
|
const QByteArray cmd = readCmdline(pidDir);
|
||||||
|
if (cmd.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdline has null-separated args; search the whole blob
|
||||||
|
if (cmd.contains(needle)) {
|
||||||
|
Match m;
|
||||||
|
m.pid = pid;
|
||||||
|
m.exe = readExeLink(pidDir);
|
||||||
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||||||
|
matches.push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProcessScanner::Match> ProcessScanner::findByExePath(const QString &dirPrefix)
|
||||||
|
{
|
||||||
|
QList<Match> matches;
|
||||||
|
|
||||||
|
const QList<uint> pids = listPids();
|
||||||
|
for (uint pid : pids) {
|
||||||
|
const QString pidDir = QStringLiteral("/proc/%1").arg(pid);
|
||||||
|
const QString exe = readExeLink(pidDir);
|
||||||
|
if (exe.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (exe.startsWith(dirPrefix)) {
|
||||||
|
Match m;
|
||||||
|
m.pid = pid;
|
||||||
|
m.exe = exe;
|
||||||
|
const QByteArray cmd = readCmdline(pidDir);
|
||||||
|
m.cmdline = QString::fromLocal8Bit(cmd).replace(QLatin1Char('\0'), QLatin1Char(' ')).trimmed();
|
||||||
|
matches.push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessScanner::pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs, int timeoutMs)
|
||||||
|
{
|
||||||
|
cancel();
|
||||||
|
m_matcher = std::move(matcher);
|
||||||
|
|
||||||
|
// Try once immediately
|
||||||
|
const QList<Match> immediate = m_matcher();
|
||||||
|
if (!immediate.isEmpty()) {
|
||||||
|
Q_EMIT found(immediate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_deadline.start(timeoutMs);
|
||||||
|
m_timer.start(intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessScanner::cancel()
|
||||||
|
{
|
||||||
|
m_timer.stop();
|
||||||
|
m_deadline.stop();
|
||||||
|
m_matcher = nullptr;
|
||||||
|
}
|
||||||
48
src/gamecenter/processscanner.h
Normal file
48
src/gamecenter/processscanner.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class ProcessScanner : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ProcessScanner(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
struct Match {
|
||||||
|
uint pid = 0;
|
||||||
|
QString exe;
|
||||||
|
QString cmdline;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find PIDs whose /proc/<pid>/environ contains key=value
|
||||||
|
static QList<Match> findByEnvironment(const QString &key, const QString &value);
|
||||||
|
|
||||||
|
// Find PIDs whose /proc/<pid>/cmdline contains the substring
|
||||||
|
static QList<Match> findByCmdline(const QString &substring);
|
||||||
|
|
||||||
|
// Find PIDs whose /proc/<pid>/exe resolves to a path under the given directory
|
||||||
|
static QList<Match> findByExePath(const QString &dirPrefix);
|
||||||
|
|
||||||
|
// Async poll: calls matcher repeatedly until it returns non-empty or timeout.
|
||||||
|
// Emits found() with matching PIDs, or timedOut() on failure.
|
||||||
|
void pollUntilFound(std::function<QList<Match>()> matcher, int intervalMs = 500, int timeoutMs = 15000);
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void found(const QList<ProcessScanner::Match> &matches);
|
||||||
|
void timedOut();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer m_timer;
|
||||||
|
QTimer m_deadline;
|
||||||
|
std::function<QList<Match>()> m_matcher;
|
||||||
|
};
|
||||||
9
src/gamecenter/systemd/org.kde.GameCenter1.service.in
Normal file
9
src/gamecenter/systemd/org.kde.GameCenter1.service.in
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=A-La-Karte Game Center
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=@CMAKE_INSTALL_PREFIX@/@KDE_INSTALL_BINDIR@/alakarte-gamecenter
|
||||||
|
Type=dbus
|
||||||
|
BusName=org.kde.GameCenter1
|
||||||
|
Restart=no
|
||||||
183
src/gamecenter/systemdusermanager.cpp
Normal file
183
src/gamecenter/systemdusermanager.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#include "systemdusermanager.h"
|
||||||
|
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#include <QDBusMetaType>
|
||||||
|
#include <QDBusVariant>
|
||||||
|
|
||||||
|
static QDBusArgument &operator<<(QDBusArgument &argument, const SystemdProperty &prop)
|
||||||
|
{
|
||||||
|
argument.beginStructure();
|
||||||
|
argument << prop.name << QDBusVariant(prop.value);
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdProperty &prop)
|
||||||
|
{
|
||||||
|
QDBusVariant variant;
|
||||||
|
|
||||||
|
argument.beginStructure();
|
||||||
|
argument >> prop.name >> variant;
|
||||||
|
argument.endStructure();
|
||||||
|
|
||||||
|
prop.value = variant.variant();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDBusArgument &operator<<(QDBusArgument &argument, const SystemdAuxUnit &unit)
|
||||||
|
{
|
||||||
|
argument.beginStructure();
|
||||||
|
argument << unit.name << unit.properties;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdAuxUnit &unit)
|
||||||
|
{
|
||||||
|
argument.beginStructure();
|
||||||
|
argument >> unit.name >> unit.properties;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDBusArgument &operator<<(QDBusArgument &argument, const SystemdUnitInfo &info)
|
||||||
|
{
|
||||||
|
argument.beginStructure();
|
||||||
|
argument << info.name << info.description << info.loadState << info.activeState << info.subState << info.following << info.objectPath << info.jobId
|
||||||
|
<< info.jobType << info.jobPath;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdUnitInfo &info)
|
||||||
|
{
|
||||||
|
argument.beginStructure();
|
||||||
|
argument >> info.name >> info.description >> info.loadState >> info.activeState >> info.subState >> info.following >> info.objectPath >> info.jobId
|
||||||
|
>> info.jobType >> info.jobPath;
|
||||||
|
argument.endStructure();
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemdUserManager::SystemdUserManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
ensureTypesRegistered();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemdUserManager::ensureTypesRegistered()
|
||||||
|
{
|
||||||
|
static bool registered = false;
|
||||||
|
if (registered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDBusRegisterMetaType<SystemdProperty>();
|
||||||
|
qDBusRegisterMetaType<SystemdProperties>();
|
||||||
|
qDBusRegisterMetaType<SystemdAuxUnit>();
|
||||||
|
qDBusRegisterMetaType<SystemdAuxUnits>();
|
||||||
|
qDBusRegisterMetaType<SystemdUnitInfo>();
|
||||||
|
qDBusRegisterMetaType<SystemdUnitInfoList>();
|
||||||
|
|
||||||
|
registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SystemdUserManager::isAvailable() const
|
||||||
|
{
|
||||||
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
QStringLiteral("/org/freedesktop/systemd1"),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
return manager.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> SystemdUserManager::startTransientScope(const QString &unitName, const QList<uint> &pids, const QString &description)
|
||||||
|
{
|
||||||
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
QStringLiteral("/org/freedesktop/systemd1"),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
SystemdProperties props;
|
||||||
|
props.push_back({QStringLiteral("Description"), description});
|
||||||
|
props.push_back({QStringLiteral("PIDs"), QVariant::fromValue(pids)});
|
||||||
|
props.push_back({QStringLiteral("CollectMode"), QStringLiteral("inactive-or-failed")});
|
||||||
|
props.push_back({QStringLiteral("KillMode"), QStringLiteral("control-group")});
|
||||||
|
props.push_back({QStringLiteral("SendSIGKILL"), true});
|
||||||
|
props.push_back({QStringLiteral("TimeoutStopUSec"), static_cast<qulonglong>(10) * 1000 * 1000});
|
||||||
|
|
||||||
|
const SystemdAuxUnits aux;
|
||||||
|
|
||||||
|
const QDBusMessage reply =
|
||||||
|
manager.call(QStringLiteral("StartTransientUnit"), unitName, QStringLiteral("replace"), QVariant::fromValue(props), QVariant::fromValue(aux));
|
||||||
|
return QDBusReply<QDBusObjectPath>(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> SystemdUserManager::stopUnit(const QString &unitName, const QString &mode)
|
||||||
|
{
|
||||||
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
QStringLiteral("/org/freedesktop/systemd1"),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
const QDBusMessage reply = manager.call(QStringLiteral("StopUnit"), unitName, mode);
|
||||||
|
return QDBusReply<QDBusObjectPath>(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> SystemdUserManager::getUnit(const QString &unitName)
|
||||||
|
{
|
||||||
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
QStringLiteral("/org/freedesktop/systemd1"),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
const QDBusMessage reply = manager.call(QStringLiteral("GetUnit"), unitName);
|
||||||
|
return QDBusReply<QDBusObjectPath>(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusReply<SystemdUnitInfoList> SystemdUserManager::listUnits()
|
||||||
|
{
|
||||||
|
QDBusInterface manager(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
QStringLiteral("/org/freedesktop/systemd1"),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Manager"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
const QDBusMessage reply = manager.call(QStringLiteral("ListUnits"));
|
||||||
|
return QDBusReply<SystemdUnitInfoList>(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<uint> SystemdUserManager::scopePids(const QDBusObjectPath &unitPath)
|
||||||
|
{
|
||||||
|
QDBusInterface unit(QStringLiteral("org.freedesktop.systemd1"),
|
||||||
|
unitPath.path(),
|
||||||
|
QStringLiteral("org.freedesktop.systemd1.Unit"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
|
||||||
|
const QDBusMessage reply = unit.call(QStringLiteral("GetProcesses"));
|
||||||
|
if (reply.type() != QDBusMessage::ReplyMessage || reply.arguments().isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<uint> pids;
|
||||||
|
const QDBusArgument arg = reply.arguments().first().value<QDBusArgument>();
|
||||||
|
arg.beginArray();
|
||||||
|
while (!arg.atEnd()) {
|
||||||
|
arg.beginStructure();
|
||||||
|
QString cgroupPath;
|
||||||
|
uint pid = 0;
|
||||||
|
QString cmdline;
|
||||||
|
arg >> cgroupPath >> pid >> cmdline;
|
||||||
|
arg.endStructure();
|
||||||
|
if (pid > 0) {
|
||||||
|
pids.push_back(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg.endArray();
|
||||||
|
return pids;
|
||||||
|
}
|
||||||
69
src/gamecenter/systemdusermanager.h
Normal file
69
src/gamecenter/systemdusermanager.h
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDBusError>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QDBusReply>
|
||||||
|
#include <QList>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
struct SystemdProperty {
|
||||||
|
QString name;
|
||||||
|
QVariant value;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(SystemdProperty)
|
||||||
|
|
||||||
|
using SystemdProperties = QList<SystemdProperty>;
|
||||||
|
Q_DECLARE_METATYPE(SystemdProperties)
|
||||||
|
|
||||||
|
struct SystemdAuxUnit {
|
||||||
|
QString name;
|
||||||
|
SystemdProperties properties;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(SystemdAuxUnit)
|
||||||
|
|
||||||
|
using SystemdAuxUnits = QList<SystemdAuxUnit>;
|
||||||
|
Q_DECLARE_METATYPE(SystemdAuxUnits)
|
||||||
|
|
||||||
|
struct SystemdUnitInfo {
|
||||||
|
QString name;
|
||||||
|
QString description;
|
||||||
|
QString loadState;
|
||||||
|
QString activeState;
|
||||||
|
QString subState;
|
||||||
|
QString following;
|
||||||
|
QDBusObjectPath objectPath;
|
||||||
|
uint jobId = 0;
|
||||||
|
QString jobType;
|
||||||
|
QDBusObjectPath jobPath;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(SystemdUnitInfo)
|
||||||
|
|
||||||
|
using SystemdUnitInfoList = QList<SystemdUnitInfo>;
|
||||||
|
Q_DECLARE_METATYPE(SystemdUnitInfoList)
|
||||||
|
|
||||||
|
class SystemdUserManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SystemdUserManager(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool isAvailable() const;
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> startTransientScope(const QString &unitName, const QList<uint> &pids, const QString &description);
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> stopUnit(const QString &unitName, const QString &mode = QStringLiteral("replace"));
|
||||||
|
|
||||||
|
QDBusReply<QDBusObjectPath> getUnit(const QString &unitName);
|
||||||
|
|
||||||
|
QDBusReply<SystemdUnitInfoList> listUnits();
|
||||||
|
QList<uint> scopePids(const QDBusObjectPath &unitPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensureTypesRegistered();
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue