mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
Replace blocking waitForStarted with async started/errorOccurred handlers
This commit is contained in:
parent
90bb30416c
commit
56ab147a85
1 changed files with 138 additions and 89 deletions
|
|
@ -1319,64 +1319,91 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
|
|||
process->setProcessEnvironment(env);
|
||||
}
|
||||
|
||||
process->start(program, args);
|
||||
if (!process->waitForStarted(5000)) {
|
||||
const QVariantMap ctx = {
|
||||
{QStringLiteral("command"), command},
|
||||
{QStringLiteral("program"), program},
|
||||
{QStringLiteral("args"), args},
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("workingDirectory"), workingDirectory},
|
||||
{QStringLiteral("envOverrides"), envOverrides},
|
||||
{QStringLiteral("error"), process->errorString()},
|
||||
};
|
||||
process->deleteLater();
|
||||
failLaunch(QStringLiteral("failed to start process"), ctx);
|
||||
return {};
|
||||
}
|
||||
|
||||
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 = {
|
||||
{QStringLiteral("command"), command},
|
||||
{QStringLiteral("program"), program},
|
||||
{QStringLiteral("args"), args},
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("workingDirectory"), workingDirectory},
|
||||
{QStringLiteral("envOverrides"), envOverrides},
|
||||
{QStringLiteral("unit"), unitName},
|
||||
{QStringLiteral("error"), startReply.error().message()},
|
||||
};
|
||||
process->kill();
|
||||
process->deleteLater();
|
||||
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);
|
||||
|
||||
watchSystemdUnit(sessionId, unitName, unitPath);
|
||||
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},
|
||||
{QStringLiteral("args"), args},
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("workingDirectory"), workingDirectory},
|
||||
{QStringLiteral("envOverrides"), envOverrides},
|
||||
{QStringLiteral("error"), proc ? proc->errorString() : QString()},
|
||||
};
|
||||
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);
|
||||
});
|
||||
|
||||
Q_EMIT SessionAdded(sessionToVariantMap(session, QStringLiteral("Running")));
|
||||
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 QDBusReply<QDBusObjectPath> startReply = m_systemd.startTransientScope(unitName, pids, description);
|
||||
if (!startReply.isValid()) {
|
||||
const QVariantMap ctx = {
|
||||
{QStringLiteral("command"), command},
|
||||
{QStringLiteral("program"), program},
|
||||
{QStringLiteral("args"), args},
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("workingDirectory"), workingDirectory},
|
||||
{QStringLiteral("envOverrides"), envOverrides},
|
||||
{QStringLiteral("unit"), unitName},
|
||||
{QStringLiteral("error"), startReply.error().message()},
|
||||
};
|
||||
process->kill();
|
||||
process->deleteLater();
|
||||
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();
|
||||
}
|
||||
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(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,57 +1538,74 @@ 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);
|
||||
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);
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const QDBusReply<QDBusObjectPath> scopeReply = m_systemd.startTransientScope(unitName, {static_cast<uint>(bootstrap->processId())}, description);
|
||||
if (!scopeReply.isValid()) {
|
||||
const QVariantMap ctx = {
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("provider"), provider},
|
||||
{QStringLiteral("command"), command},
|
||||
{QStringLiteral("unit"), unitName},
|
||||
{QStringLiteral("error"), scopeReply.error().message()},
|
||||
};
|
||||
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 {};
|
||||
}
|
||||
|
||||
session.unitName = unitName;
|
||||
const QDBusReply<QDBusObjectPath> getUnitReply = m_systemd.getUnit(unitName);
|
||||
if (getUnitReply.isValid()) {
|
||||
session.unitPath = getUnitReply.value();
|
||||
}
|
||||
|
||||
m_sessions[sessionId] = session;
|
||||
|
||||
watchSystemdUnit(sessionId, session.unitName, session.unitPath);
|
||||
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 = {
|
||||
{QStringLiteral("gameId"), gameId},
|
||||
{QStringLiteral("provider"), provider},
|
||||
{QStringLiteral("command"), command},
|
||||
{QStringLiteral("unit"), unitName},
|
||||
{QStringLiteral("error"), scopeReply.error().message()},
|
||||
};
|
||||
bootstrap->kill();
|
||||
bootstrap->deleteLater();
|
||||
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);
|
||||
}
|
||||
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()) {
|
||||
it.value().unitPath = getUnitReply.value();
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue