// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 A-La-Karte Contributors #include "gamelauncher.h" #include "app.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const QString kGameCenterService = QStringLiteral("org.kde.GameCenter1"); static const QString kGameCenterPath = QStringLiteral("/org/kde/GameCenter1"); static const QString kGameCenterInterface = QStringLiteral("org.kde.GameCenter1"); static const QString kRunnerService = QStringLiteral("org.kde.ALaKarte.Runner1"); static const QString kRunnerPath = QStringLiteral("/org/kde/ALaKarte/Runner1"); static const QString kRunnerInterface = QStringLiteral("org.kde.ALaKarte.Runner1"); static bool pingDaemon(QDBusConnection bus) { if (!bus.isConnected()) { return false; } QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, bus); if (!iface.isValid()) { return false; } iface.setTimeout(2000); const QDBusReply reply = iface.call(QStringLiteral("Ping")); return reply.isValid() && reply.value() == QLatin1String("ok"); } static void disconnectDaemonSignals(QDBusConnection bus, GameLauncher *launcher) { if (!bus.isConnected()) { return; } bus.disconnect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionAdded"), launcher, SLOT(onDaemonSessionAdded(QVariantMap))); bus.disconnect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionChanged"), launcher, SLOT(onDaemonSessionChanged(QVariantMap))); bus.disconnect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionRemoved"), launcher, SLOT(onDaemonSessionRemoved(QString, QVariantMap))); bus.disconnect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("LaunchFailed"), launcher, SLOT(onDaemonLaunchFailed(QVariantMap))); } static void connectDaemonSignals(QDBusConnection bus, GameLauncher *launcher) { if (!bus.isConnected()) { return; } bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionAdded"), launcher, SLOT(onDaemonSessionAdded(QVariantMap))); bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionChanged"), launcher, SLOT(onDaemonSessionChanged(QVariantMap))); bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionRemoved"), launcher, SLOT(onDaemonSessionRemoved(QString, QVariantMap))); bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("LaunchFailed"), launcher, SLOT(onDaemonLaunchFailed(QVariantMap))); } static QVariant unwrapDbusVariant(QVariant v) { if (v.canConvert()) { v = v.value().variant(); } return v; } static QVariantMap unwrapVariantMap(const QVariantMap &map) { QVariantMap out; for (auto it = map.constBegin(); it != map.constEnd(); ++it) { out.insert(it.key(), unwrapDbusVariant(it.value())); } return out; } static bool tryResolveWithRunnerManager(const QVariantMap &spec, QVariantMap &out) { QDBusConnection bus = QDBusConnection::sessionBus(); if (!bus.isConnected() || !bus.interface()) { return false; } if (!bus.interface()->isServiceRegistered(kRunnerService)) { bus.interface()->startService(kRunnerService); } QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, bus); if (!iface.isValid()) { if (bus.interface()->startService(kRunnerService).isValid()) { QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus); if (retryIface.isValid()) { retryIface.setTimeout(2000); const QDBusReply retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec); if (retryReply.isValid()) { out = unwrapVariantMap(retryReply.value()); return true; } } } return false; } iface.setTimeout(2000); const QDBusReply reply = iface.call(QStringLiteral("ResolveLaunch"), spec); if (!reply.isValid()) { if (reply.error().type() == QDBusError::ServiceUnknown) { bus.interface()->startService(kRunnerService); QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus); if (!retryIface.isValid()) { return false; } retryIface.setTimeout(2000); const QDBusReply retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec); if (!retryReply.isValid()) { return false; } out = unwrapVariantMap(retryReply.value()); return true; } return false; } out = unwrapVariantMap(reply.value()); return true; } static QStringList steamCandidateRoots(); static QString findSteamClientInstallPathFromProton(const QString &protonExe) { if (protonExe.isEmpty()) { return {}; } QDir dir(QFileInfo(protonExe).absoluteDir()); for (int i = 0; i < 10; ++i) { if (dir.exists(QStringLiteral("steamapps"))) { return dir.absolutePath(); } if (!dir.cdUp()) { break; } } for (const QString &root : steamCandidateRoots()) { if (QDir(root).exists(QStringLiteral("steamapps"))) { return root; } } return {}; } static QString discoverDefaultProtonExecutable() { static bool done = false; static QString cached; if (done) { return cached; } done = true; QStringList candidates; for (const QString &root : steamCandidateRoots()) { const QString compatTools = root + QStringLiteral("/compatibilitytools.d"); QDir compatDir(compatTools); if (compatDir.exists()) { const QStringList toolDirs = compatDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &tool : toolDirs) { const QString protonPath = compatTools + QLatin1Char('/') + tool + QStringLiteral("/proton"); if (QFileInfo::exists(protonPath)) { candidates.append(protonPath); } } } const QString common = root + QStringLiteral("/steamapps/common"); QDir commonDir(common); if (commonDir.exists()) { const QStringList dirs = commonDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &d : dirs) { if (!d.contains(QStringLiteral("Proton"), Qt::CaseInsensitive)) { continue; } const QString protonPath = common + QLatin1Char('/') + d + QStringLiteral("/proton"); if (QFileInfo::exists(protonPath)) { candidates.append(protonPath); } } } } candidates.removeDuplicates(); candidates.sort(); for (const QString &c : candidates) { if (c.contains(QStringLiteral("Proton - Experimental"))) { cached = c; return cached; } } if (!candidates.isEmpty()) { cached = candidates.constLast(); } return cached; } static QStringList steamCandidateRoots() { const QString home = QDir::homePath(); return { home + QStringLiteral("/.steam/root"), home + QStringLiteral("/.steam/steam"), home + QStringLiteral("/.local/share/Steam"), home + QStringLiteral("/.var/app/com.valvesoftware.Steam/data/Steam"), home + QStringLiteral("/.var/app/com.valvesoftware.Steam/.local/share/Steam"), }; } GameLauncher::GameLauncher(QObject *parent) : QObject(parent) , m_daemonAvailable(false) , m_usingSystemBus(false) { if (auto *app = qobject_cast(parent)) { connect(app->gameModel(), &GameModel::countChanged, this, [this]() { QTimer::singleShot(0, this, [this]() { applyRunningStateToLibrary(); }); }); } QTimer::singleShot(0, this, [this]() { checkDaemonAvailability(); syncDaemonSessions(); }); } GameLauncher::~GameLauncher() { m_daemonGameToSession.clear(); m_daemonSessionToGame.clear(); } bool GameLauncher::hasRunningGames() const { return !m_daemonGameToSession.isEmpty(); } QVariantMap GameLauncher::resolveLaunchInfo(Game *game) const { QVariantMap info; info.insert(QStringLiteral("ok"), false); if (!game) { info.insert(QStringLiteral("error"), tr("No game selected")); return info; } info.insert(QStringLiteral("gameId"), game->id()); info.insert(QStringLiteral("gameName"), game->name()); info.insert(QStringLiteral("displayName"), game->name()); info.insert(QStringLiteral("isRunning"), m_daemonGameToSession.contains(game->id())); // Detect provider from platform const QString platform = game->platform().toLower(); if (platform == QLatin1String("steam")) { info.insert(QStringLiteral("provider"), QStringLiteral("steam")); } else if (platform == QLatin1String("lutris")) { info.insert(QStringLiteral("provider"), QStringLiteral("lutris")); } else { info.insert(QStringLiteral("provider"), QStringLiteral("manual")); } const QString command = game->launchCommand(); info.insert(QStringLiteral("launchCommand"), command); if (command.isEmpty()) { info.insert(QStringLiteral("error"), tr("No launch command configured")); return info; } const QString runner = game->launchRunner().trimmed(); const QString runnerId = game->launchRunnerId().trimmed(); const QString runnerPath = game->launchRunnerPath().trimmed(); const QString prefixPath = game->launchPrefixPath().trimmed(); const QVariantMap launchEnv = game->launchEnv(); const bool hasLaunchOverrides = !runner.isEmpty() || !runnerId.isEmpty() || !runnerPath.isEmpty() || !prefixPath.isEmpty() || !launchEnv.isEmpty(); info.insert(QStringLiteral("runner"), runner); info.insert(QStringLiteral("runnerId"), runnerId); info.insert(QStringLiteral("runnerPath"), runnerPath); info.insert(QStringLiteral("prefixPath"), prefixPath); info.insert(QStringLiteral("workingDirectory"), game->workingDirectory()); info.insert(QStringLiteral("hasLaunchOverrides"), hasLaunchOverrides); QVariantMap envOverrides; for (auto it = launchEnv.constBegin(); it != launchEnv.constEnd(); ++it) { const QString key = it.key(); if (key.isEmpty() || key.contains(QLatin1Char('='))) { continue; } envOverrides.insert(key, it.value().toString()); } info.insert(QStringLiteral("envOverrides"), envOverrides); if (!hasLaunchOverrides && command.startsWith(QLatin1String("steam://"))) { info.insert(QStringLiteral("ok"), true); info.insert(QStringLiteral("launchType"), QStringLiteral("url")); info.insert(QStringLiteral("url"), command); info.insert(QStringLiteral("provider"), QStringLiteral("steam")); return info; } if (!hasLaunchOverrides && command.startsWith(QLatin1String("lutris "))) { info.insert(QStringLiteral("ok"), true); info.insert(QStringLiteral("launchType"), QStringLiteral("url")); info.insert(QStringLiteral("url"), command.mid(7)); info.insert(QStringLiteral("provider"), QStringLiteral("lutris")); return info; } QStringList parts; if (command.startsWith(QLatin1String("steam://"))) { parts = {QStringLiteral("xdg-open"), command}; } else if (command.startsWith(QLatin1String("lutris "))) { parts = {QStringLiteral("xdg-open"), command.mid(7)}; } else { parts = QProcess::splitCommand(command); } if (parts.isEmpty()) { info.insert(QStringLiteral("error"), tr("Invalid launch command")); return info; } const QString program = parts.takeFirst(); info.insert(QStringLiteral("program"), program); info.insert(QStringLiteral("args"), parts); QString finalProgram = program; QStringList finalArgs = parts; QVariantMap effectiveEnv = envOverrides; if ((runner == QLatin1String("wine") || runner == QLatin1String("proton")) && (program == QLatin1String("xdg-open"))) { info.insert(QStringLiteral("error"), tr("This runner cannot be used with URL-based launch commands")); return info; } if (!runnerId.isEmpty() || runner == QLatin1String("wine") || runner == QLatin1String("proton")) { QVariantMap runnerSpec = { {QStringLiteral("runnerId"), runnerId}, {QStringLiteral("runner"), runner}, {QStringLiteral("runnerPath"), runnerPath}, {QStringLiteral("gameId"), game->id()}, {QStringLiteral("prefixPath"), prefixPath}, {QStringLiteral("program"), program}, {QStringLiteral("args"), parts}, {QStringLiteral("envOverrides"), envOverrides}, }; QVariantMap resolved; if (tryResolveWithRunnerManager(runnerSpec, resolved)) { if (!resolved.value(QStringLiteral("ok")).toBool()) { info.insert(QStringLiteral("error"), resolved.value(QStringLiteral("error")).toString()); return info; } info.insert(QStringLiteral("finalProgram"), resolved.value(QStringLiteral("finalProgram")).toString()); info.insert(QStringLiteral("finalArgs"), resolved.value(QStringLiteral("finalArgs")).toStringList()); info.insert(QStringLiteral("effectiveEnv"), resolved.value(QStringLiteral("effectiveEnv")).toMap()); const QString resolvedPrefixPath = resolved.value(QStringLiteral("resolvedPrefixPath")).toString(); if (!resolvedPrefixPath.isEmpty()) { info.insert(QStringLiteral("resolvedPrefixPath"), resolvedPrefixPath); } const QString resolvedSteamInstallPath = resolved.value(QStringLiteral("resolvedSteamInstallPath")).toString(); if (!resolvedSteamInstallPath.isEmpty()) { info.insert(QStringLiteral("resolvedSteamInstallPath"), resolvedSteamInstallPath); } info.insert(QStringLiteral("ok"), true); return info; } else if (!runnerId.isEmpty()) { info.insert(QStringLiteral("error"), tr("Runner service is not available")); return info; } } if (runner == QLatin1String("custom")) { if (runnerPath.isEmpty()) { info.insert(QStringLiteral("error"), tr("Custom runner is enabled but no runner path is configured")); return info; } finalProgram = runnerPath; finalArgs = {program}; finalArgs.append(parts); } else if (runner == QLatin1String("wine")) { QString wineExe = runnerPath; if (wineExe.isEmpty()) { wineExe = QStandardPaths::findExecutable(QStringLiteral("wine")); } if (wineExe.isEmpty()) { info.insert(QStringLiteral("error"), tr("Wine runner is enabled but Wine was not found")); return info; } QString winePrefix = prefixPath; if (winePrefix.isEmpty()) { winePrefix = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/prefixes/") + game->id(); } effectiveEnv.insert(QStringLiteral("WINEPREFIX"), winePrefix); info.insert(QStringLiteral("resolvedPrefixPath"), winePrefix); finalProgram = wineExe; finalArgs = {program}; finalArgs.append(parts); } else if (runner == QLatin1String("proton")) { QString protonExe = runnerPath; if (protonExe.isEmpty()) { protonExe = discoverDefaultProtonExecutable(); } if (protonExe.isEmpty()) { info.insert(QStringLiteral("error"), tr("Proton runner is enabled but no Proton installation was found")); return info; } const QString steamInstallPath = findSteamClientInstallPathFromProton(protonExe); info.insert(QStringLiteral("resolvedSteamInstallPath"), steamInstallPath); QString compatDataPath = prefixPath; if (compatDataPath.isEmpty()) { compatDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/prefixes/") + game->id(); } effectiveEnv.insert(QStringLiteral("STEAM_COMPAT_DATA_PATH"), compatDataPath); info.insert(QStringLiteral("resolvedPrefixPath"), compatDataPath); if (!steamInstallPath.isEmpty()) { effectiveEnv.insert(QStringLiteral("STEAM_COMPAT_CLIENT_INSTALL_PATH"), steamInstallPath); } finalProgram = protonExe; finalArgs = {QStringLiteral("run"), program}; finalArgs.append(parts); } info.insert(QStringLiteral("finalProgram"), finalProgram); info.insert(QStringLiteral("finalArgs"), finalArgs); info.insert(QStringLiteral("effectiveEnv"), effectiveEnv); info.insert(QStringLiteral("ok"), true); return info; } void GameLauncher::launchGame(Game *game) { if (!game) { return; } // Check if already running if (m_daemonGameToSession.contains(game->id())) { Q_EMIT gameError(game, tr("Game is already running")); return; } const QVariantMap info = resolveLaunchInfo(game); if (!info.value(QStringLiteral("ok")).toBool()) { Q_EMIT gameError(game, info.value(QStringLiteral("error")).toString()); return; } const QString launchCommand = info.value(QStringLiteral("launchCommand")).toString(); const QString launchType = info.value(QStringLiteral("launchType")).toString(); const QString provider = info.value(QStringLiteral("provider")).toString(); const QString runner = info.value(QStringLiteral("runner")).toString(); const QString runnerId = info.value(QStringLiteral("runnerId")).toString(); const QString runnerPath = info.value(QStringLiteral("runnerPath")).toString(); const QString prefixPath = info.value(QStringLiteral("prefixPath")).toString(); const QVariantMap envOverrides = info.value(QStringLiteral("envOverrides")).toMap(); if (runner == QLatin1String("proton") || runner == QLatin1String("wine")) { const QString resolvedPrefixPath = info.value(QStringLiteral("resolvedPrefixPath")).toString(); if (!resolvedPrefixPath.isEmpty()) { QDir().mkpath(resolvedPrefixPath); } } const QString finalProgram = info.value(QStringLiteral("finalProgram")).toString(); const QStringList finalArgs = info.value(QStringLiteral("finalArgs")).toStringList(); const QString workingDirectory = info.value(QStringLiteral("workingDirectory")).toString(); const QVariantMap effectiveEnv = info.value(QStringLiteral("effectiveEnv")).toMap(); // Always try daemon first — for all launch types { QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus()); QVariantMap launchSpec = { {QStringLiteral("command"), launchCommand}, {QStringLiteral("gameId"), game->id()}, {QStringLiteral("displayName"), game->name()}, {QStringLiteral("provider"), provider}, {QStringLiteral("origin"), QStringLiteral("ui")}, }; if (!runnerId.isEmpty()) { launchSpec.insert(QStringLiteral("runnerId"), runnerId); } if (!runner.isEmpty()) { launchSpec.insert(QStringLiteral("runner"), runner); } if (!runnerPath.isEmpty()) { launchSpec.insert(QStringLiteral("runnerPath"), runnerPath); } if (!prefixPath.isEmpty()) { launchSpec.insert(QStringLiteral("prefixPath"), prefixPath); } launchSpec.insert(QStringLiteral("requestedProgram"), info.value(QStringLiteral("program")).toString()); launchSpec.insert(QStringLiteral("requestedArgs"), info.value(QStringLiteral("args")).toStringList()); if (!envOverrides.isEmpty()) { launchSpec.insert(QStringLiteral("requestedEnvOverrides"), envOverrides); } if (!finalProgram.isEmpty()) { launchSpec.insert(QStringLiteral("program"), finalProgram); launchSpec.insert(QStringLiteral("args"), finalArgs); } if (!effectiveEnv.isEmpty()) { launchSpec.insert(QStringLiteral("envOverrides"), effectiveEnv); } if (!workingDirectory.isEmpty()) { launchSpec.insert(QStringLiteral("workingDirectory"), workingDirectory); } const QDBusReply reply = iface.call(QStringLiteral("Launch"), launchSpec); if (reply.isValid() && !reply.value().isEmpty()) { m_daemonGameToSession.insert(game->id(), reply.value()); m_daemonSessionToGame.insert(reply.value(), game->id()); game->setRunning(true); game->setLastPlayed(QDateTime::currentDateTime()); Q_EMIT gameStarted(game); Q_EMIT runningGamesChanged(); if (App::instance()->config()->exitAfterLaunch()) { QTimer::singleShot(500, qApp, &QCoreApplication::quit); } return; } const QString launchError = reply.isValid() ? QString() : reply.error().message(); // Daemon call failed — fallback for URL-type commands only if (launchType == QLatin1String("url")) { const QString url = info.value(QStringLiteral("url")).toString(); if (!url.isEmpty()) { QDesktopServices::openUrl(QUrl(url)); game->setLastPlayed(QDateTime::currentDateTime()); Q_EMIT gameStarted(game); return; } } // No fallback for non-URL commands — emit error if (!launchError.isEmpty()) { Q_EMIT gameError(game, tr("Game Center launch failed: %1").arg(launchError)); } else { Q_EMIT gameError(game, tr("Game Center daemon is not available")); } } } void GameLauncher::stopGame(Game *game) { if (!game) { return; } QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus()); iface.call(QStringLiteral("StopByGameId"), game->id()); } bool GameLauncher::isGameRunning(Game *game) const { return game && m_daemonGameToSession.contains(game->id()); } void GameLauncher::onDaemonSessionAdded(const QVariantMap &session) { const QVariantMap s = unwrapVariantMap(session); const QString sessionId = s.value(QStringLiteral("sessionId")).toString(); const QString gameId = s.value(QStringLiteral("gameId")).toString(); if (sessionId.isEmpty() || gameId.isEmpty()) { return; } if (m_daemonSessionToGame.contains(sessionId)) { return; } m_daemonGameToSession.insert(gameId, sessionId); m_daemonSessionToGame.insert(sessionId, gameId); Game *game = App::instance()->gameModel()->gameById(gameId); if (game) { game->setRunning(true); } Q_EMIT runningGamesChanged(); applyRunningStateToLibrary(); } void GameLauncher::onDaemonSessionChanged(const QVariantMap &session) { const QVariantMap s = unwrapVariantMap(session); const QString sessionId = s.value(QStringLiteral("sessionId")).toString(); const QString gameId = s.value(QStringLiteral("gameId")).toString(); if (sessionId.isEmpty() || gameId.isEmpty()) { return; } m_daemonGameToSession.insert(gameId, sessionId); m_daemonSessionToGame.insert(sessionId, gameId); const QString state = s.value(QStringLiteral("state")).toString(); if (state == QLatin1String("Exited")) { return; } Game *game = App::instance()->gameModel()->gameById(gameId); if (game) { game->setRunning(true); } Q_EMIT runningGamesChanged(); applyRunningStateToLibrary(); } void GameLauncher::onDaemonSessionRemoved(const QString &sessionId, const QVariantMap &finalState) { if (sessionId.isEmpty()) { return; } QString gameId = m_daemonSessionToGame.take(sessionId); if (!gameId.isEmpty()) { m_daemonGameToSession.remove(gameId); } else { const QVariantMap fs = unwrapVariantMap(finalState); gameId = fs.value(QStringLiteral("gameId")).toString(); if (!gameId.isEmpty()) { m_daemonGameToSession.remove(gameId); } } Game *game = !gameId.isEmpty() ? App::instance()->gameModel()->gameById(gameId) : nullptr; if (game) { game->setRunning(false); const QVariantMap fs = unwrapVariantMap(finalState); Q_EMIT gameStopped(game, fs.value(QStringLiteral("exitCode")).toInt()); } Q_EMIT runningGamesChanged(); applyRunningStateToLibrary(); } void GameLauncher::onDaemonLaunchFailed(const QVariantMap &error) { const QVariantMap e = unwrapVariantMap(error); QVariantMap context; { QVariant ctx = unwrapDbusVariant(e.value(QStringLiteral("context"))); if (ctx.canConvert()) { context = ctx.toMap(); } else if (ctx.canConvert()) { context = qdbus_cast(ctx.value()); } } QString gameId = unwrapDbusVariant(context.value(QStringLiteral("gameId"))).toString(); if (gameId.isEmpty()) { gameId = e.value(QStringLiteral("gameId")).toString(); } if (gameId.isEmpty()) { return; } const QString message = e.value(QStringLiteral("error")).toString(); if (message.isEmpty()) { return; } QTimer::singleShot(100, this, [this, gameId, message]() { if (m_daemonGameToSession.contains(gameId)) { return; } Game *game = App::instance()->gameModel()->gameById(gameId); if (!game) { return; } Q_EMIT gameError(game, message); }); } void GameLauncher::syncDaemonSessions() { QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus()); const QDBusReply reply = iface.call(QStringLiteral("ListSessions")); if (!reply.isValid()) { return; } const QVariantList list = reply.value(); for (const QVariant &v : list) { QVariantMap map; if (v.canConvert()) { map = v.toMap(); } else if (v.canConvert()) { map = qdbus_cast(v.value()); } if (!map.isEmpty()) { onDaemonSessionAdded(map); } } applyRunningStateToLibrary(); } void GameLauncher::applyRunningStateToLibrary() { const QList games = App::instance()->gameModel()->allGames(); for (Game *game : games) { if (!game) { continue; } const bool shouldBeRunning = m_daemonGameToSession.contains(game->id()); game->setRunning(shouldBeRunning); } } void GameLauncher::checkDaemonAvailability() { const bool systemAvailable = pingDaemon(QDBusConnection::systemBus()); const bool sessionAvailable = systemAvailable ? false : pingDaemon(QDBusConnection::sessionBus()); const bool available = systemAvailable || sessionAvailable; const bool useSystemBus = systemAvailable; disconnectDaemonSignals(QDBusConnection::systemBus(), this); disconnectDaemonSignals(QDBusConnection::sessionBus(), this); if (available) { connectDaemonSignals(useSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus(), this); } const bool busChanged = m_usingSystemBus != useSystemBus; if (busChanged) { m_daemonGameToSession.clear(); m_daemonSessionToGame.clear(); Q_EMIT runningGamesChanged(); applyRunningStateToLibrary(); } m_usingSystemBus = useSystemBus; if (available != m_daemonAvailable) { m_daemonAvailable = available; Q_EMIT daemonAvailableChanged(); } } bool GameLauncher::daemonAvailable() const { return m_daemonAvailable; } void GameLauncher::retryDaemonConnection() { checkDaemonAvailability(); if (m_daemonAvailable) { syncDaemonSessions(); } }