From 56ab147a85539c24341056c788a4bb06fa3226dd Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Mon, 23 Mar 2026 13:20:13 +0100 Subject: [PATCH] Replace blocking waitForStarted with async started/errorOccurred handlers --- src/gamecenter/gamecenterdaemon.cpp | 227 +++++++++++++++++----------- 1 file changed, 138 insertions(+), 89 deletions(-) diff --git a/src/gamecenter/gamecenterdaemon.cpp b/src/gamecenter/gamecenterdaemon.cpp index b335e4f..3e5be3a 100644 --- a/src/gamecenter/gamecenterdaemon.cpp +++ b/src/gamecenter/gamecenterdaemon.cpp @@ -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 pids = {static_cast(process->processId())}; const QString description = gameId.isEmpty() ? QStringLiteral("A-La-Karte game") : QStringLiteral("A-La-Karte game %1").arg(gameId); - const QDBusReply 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 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(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(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 pids = {static_cast(process->processId())}; + const QDBusReply 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 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(process->processId()); + watchSystemdUnit(sessionId, unitName, unitPath); + Q_EMIT SessionAdded(sessionToVariantMap(it.value(), QStringLiteral("Running"))); + }); connect(process, qOverload(&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(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 scopeReply = m_systemd.startTransientScope(unitName, {static_cast(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 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 scopeReply = m_systemd.startTransientScope(unitName, {static_cast(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 getUnitReply = m_systemd.getUnit(unitName); + if (getUnitReply.isValid()) { + it.value().unitPath = getUnitReply.value(); + } + watchSystemdUnit(sessionId, it.value().unitName, it.value().unitPath); + }); connect(bootstrap, qOverload(&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;