Replace blocking waitForStarted with async started/errorOccurred handlers

This commit is contained in:
Marco Allegretti 2026-03-23 13:20:13 +01:00
parent 90bb30416c
commit 56ab147a85

View file

@ -1319,8 +1319,25 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
process->setProcessEnvironment(env);
}
process->start(program, args);
if (!process->waitForStarted(5000)) {
const QString description = gameId.isEmpty() ? QStringLiteral("A-La-Karte game") : QStringLiteral("A-La-Karte game %1").arg(gameId);
Session session;
session.sessionId = sessionId;
session.gameId = gameId;
session.displayName = displayName;
session.unitName = unitName;
session.provider = QStringLiteral("manual");
session.startTime = QDateTime::currentDateTimeUtc();
m_sessions.insert(sessionId, session);
connect(process,
&QProcess::errorOccurred,
this,
[this, sessionId, command, program, args, gameId, workingDirectory, envOverrides](QProcess::ProcessError error) {
if (error != QProcess::FailedToStart) {
return;
}
auto *proc = qobject_cast<QProcess *>(sender());
const QVariantMap ctx = {
{QStringLiteral("command"), command},
{QStringLiteral("program"), program},
@ -1328,16 +1345,25 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
{QStringLiteral("gameId"), gameId},
{QStringLiteral("workingDirectory"), workingDirectory},
{QStringLiteral("envOverrides"), envOverrides},
{QStringLiteral("error"), process->errorString()},
{QStringLiteral("error"), proc ? proc->errorString() : QString()},
};
process->deleteLater();
failLaunch(QStringLiteral("failed to start process"), ctx);
return {};
const auto it = m_sessions.find(sessionId);
if (it != m_sessions.end()) {
QVariantMap finalState = sessionToVariantMap(it.value(), QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.erase(it);
}
if (proc) {
proc->deleteLater();
}
failLaunch(QStringLiteral("failed to start process"), ctx);
});
connect(process,
&QProcess::started,
this,
[this, sessionId, unitName, description, process, command, program, args, gameId, workingDirectory, envOverrides]() {
const QList<uint> pids = {static_cast<uint>(process->processId())};
const QString description = gameId.isEmpty() ? QStringLiteral("A-La-Karte game") : QStringLiteral("A-La-Karte game %1").arg(gameId);
const QDBusReply<QDBusObjectPath> startReply = m_systemd.startTransientScope(unitName, pids, description);
if (!startReply.isValid()) {
const QVariantMap ctx = {
@ -1352,31 +1378,32 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
};
process->kill();
process->deleteLater();
failLaunch(QStringLiteral("failed to create transient scope"), ctx);
return {};
const auto it = m_sessions.find(sessionId);
if (it != m_sessions.end()) {
QVariantMap finalState = sessionToVariantMap(it.value(), QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.erase(it);
}
failLaunch(QStringLiteral("failed to create transient scope"), ctx);
return;
}
QDBusObjectPath unitPath;
const QDBusReply<QDBusObjectPath> getUnitReply = m_systemd.getUnit(unitName);
if (getUnitReply.isValid()) {
unitPath = getUnitReply.value();
}
Session session;
session.sessionId = sessionId;
session.gameId = gameId;
session.displayName = displayName;
session.unitName = unitName;
session.unitPath = unitPath;
session.provider = QStringLiteral("manual");
session.startTime = QDateTime::currentDateTimeUtc();
session.process = process;
session.mainPid = static_cast<uint>(process->processId());
m_sessions.insert(sessionId, session);
auto it = m_sessions.find(sessionId);
if (it == m_sessions.end()) {
process->kill();
process->deleteLater();
return;
}
it.value().unitPath = unitPath;
it.value().process = process;
it.value().mainPid = static_cast<uint>(process->processId());
watchSystemdUnit(sessionId, unitName, unitPath);
Q_EMIT SessionAdded(sessionToVariantMap(session, QStringLiteral("Running")));
Q_EMIT SessionAdded(sessionToVariantMap(it.value(), QStringLiteral("Running")));
});
connect(process, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [this, sessionId](int exitCode, QProcess::ExitStatus exitStatus) {
const auto it = m_sessions.find(sessionId);
@ -1392,6 +1419,7 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
}
});
process->start(program, args);
return sessionId;
}
@ -1497,7 +1525,9 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
return {};
}
// Create session in Launching state
const QString unitName = ensureScopeUnitName(QStringLiteral("alakarte-game-%1").arg(sessionId));
const QString description = gameId.isEmpty() ? QStringLiteral("A-La-Karte game") : QStringLiteral("A-La-Karte game %1").arg(gameId);
Session session;
session.sessionId = sessionId;
session.gameId = gameId;
@ -1508,30 +1538,36 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
Q_EMIT SessionAdded(sessionToVariantMap(session, QStringLiteral("Launching")));
// Start bootstrap process (e.g. steam -applaunch or lutris ...)
auto *bootstrap = new QProcess(this);
bootstrap->start(bootstrapProgram, bootstrapArgs);
if (!bootstrap->waitForStarted(5000)) {
connect(bootstrap, &QProcess::errorOccurred, this, [this, sessionId, gameId, provider, command](QProcess::ProcessError error) {
if (error != QProcess::FailedToStart) {
return;
}
auto *proc = qobject_cast<QProcess *>(sender());
const QVariantMap ctx = {
{QStringLiteral("gameId"), gameId},
{QStringLiteral("provider"), provider},
{QStringLiteral("command"), command},
{QStringLiteral("error"), bootstrap->errorString()},
{QStringLiteral("error"), proc ? proc->errorString() : QString()},
};
bootstrap->deleteLater();
QVariantMap finalState = sessionToVariantMap(session, QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.remove(sessionId);
failLaunch(QStringLiteral("failed to start bootstrap process"), ctx);
return {};
const auto it = m_sessions.find(sessionId);
if (it != m_sessions.end()) {
if (it.value().scanner) {
it.value().scanner->cancel();
it.value().scanner->deleteLater();
}
QVariantMap finalState = sessionToVariantMap(it.value(), QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.erase(it);
}
if (proc) {
proc->deleteLater();
}
failLaunch(QStringLiteral("failed to start bootstrap process"), ctx);
});
session.process = bootstrap;
const QString unitName = ensureScopeUnitName(QStringLiteral("alakarte-game-%1").arg(sessionId));
const QString description = session.gameId.isEmpty() ? QStringLiteral("A-La-Karte game") : QStringLiteral("A-La-Karte game %1").arg(session.gameId);
connect(bootstrap, &QProcess::started, this, [this, sessionId, bootstrap, unitName, description, gameId, provider, command]() {
const QDBusReply<QDBusObjectPath> scopeReply = m_systemd.startTransientScope(unitName, {static_cast<uint>(bootstrap->processId())}, description);
if (!scopeReply.isValid()) {
const QVariantMap ctx = {
@ -1543,22 +1579,33 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
};
bootstrap->kill();
bootstrap->deleteLater();
QVariantMap finalState = sessionToVariantMap(session, QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.remove(sessionId);
failLaunch(QStringLiteral("failed to create transient scope"), ctx);
return {};
const auto it = m_sessions.find(sessionId);
if (it != m_sessions.end()) {
if (it.value().scanner) {
it.value().scanner->cancel();
it.value().scanner->deleteLater();
}
session.unitName = unitName;
QVariantMap finalState = sessionToVariantMap(it.value(), QStringLiteral("Failed"));
Q_EMIT SessionRemoved(sessionId, finalState);
m_sessions.erase(it);
}
failLaunch(QStringLiteral("failed to create transient scope"), ctx);
return;
}
auto it = m_sessions.find(sessionId);
if (it == m_sessions.end()) {
bootstrap->kill();
bootstrap->deleteLater();
return;
}
it.value().process = bootstrap;
it.value().unitName = unitName;
const QDBusReply<QDBusObjectPath> getUnitReply = m_systemd.getUnit(unitName);
if (getUnitReply.isValid()) {
session.unitPath = getUnitReply.value();
it.value().unitPath = getUnitReply.value();
}
m_sessions[sessionId] = session;
watchSystemdUnit(sessionId, session.unitName, session.unitPath);
watchSystemdUnit(sessionId, it.value().unitName, it.value().unitPath);
});
connect(bootstrap, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [this, sessionId](int, QProcess::ExitStatus) {
const auto it = m_sessions.find(sessionId);
@ -1574,6 +1621,8 @@ QString GameCenterDaemon::launchMonitored(const QVariantMap &launchSpec, const Q
}
});
bootstrap->start(bootstrapProgram, bootstrapArgs);
// Start polling for game PID
auto *scanner = new ProcessScanner(this);
m_sessions[sessionId].scanner = scanner;