// 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 GameLauncher::GameLauncher(QObject *parent) : QObject(parent) { } GameLauncher::~GameLauncher() { // Cleanup all running processes for (QProcess *process : m_runningGames.values()) { process->disconnect(); process->terminate(); process->waitForFinished(3000); process->deleteLater(); } m_runningGames.clear(); m_processToGame.clear(); } bool GameLauncher::hasRunningGames() const { return !m_runningGames.isEmpty(); } void GameLauncher::launchGame(Game *game) { if (!game) { return; } QString command = game->launchCommand(); if (command.isEmpty()) { Q_EMIT gameError(game, tr("No launch command configured")); return; } // Check if already running if (m_runningGames.contains(game->id())) { Q_EMIT gameError(game, tr("Game is already running")); return; } // Handle Steam URLs if (command.startsWith(QLatin1String("steam://"))) { QDesktopServices::openUrl(QUrl(command)); game->setLastPlayed(QDateTime::currentDateTime()); Q_EMIT gameStarted(game); return; } // Handle Lutris URLs if (command.startsWith(QLatin1String("lutris "))) { QString lutrisCommand = command.mid(7); // Remove "lutris " QDesktopServices::openUrl(QUrl(lutrisCommand)); game->setLastPlayed(QDateTime::currentDateTime()); Q_EMIT gameStarted(game); return; } // Start process for other commands QProcess *process = new QProcess(this); // Set working directory if available if (!game->workingDirectory().isEmpty()) { process->setWorkingDirectory(game->workingDirectory()); } connect(process, QOverload::of(&QProcess::finished), this, &GameLauncher::onProcessFinished); connect(process, &QProcess::errorOccurred, this, &GameLauncher::onProcessError); m_runningGames.insert(game->id(), process); m_processToGame.insert(process, game); // Parse command and arguments QStringList parts = QProcess::splitCommand(command); if (parts.isEmpty()) { cleanupProcess(process); Q_EMIT gameError(game, tr("Invalid launch command")); return; } QString program = parts.takeFirst(); process->start(program, parts); if (!process->waitForStarted(5000)) { QString error = process->errorString(); cleanupProcess(process); Q_EMIT gameError(game, tr("Failed to start game: %1").arg(error)); return; } game->setRunning(true); game->setLastPlayed(QDateTime::currentDateTime()); Q_EMIT gameStarted(game); Q_EMIT runningGamesChanged(); // Exit after launch if configured if (App::instance()->config()->exitAfterLaunch()) { QTimer::singleShot(500, qApp, &QCoreApplication::quit); } } void GameLauncher::stopGame(Game *game) { if (!game || !m_runningGames.contains(game->id())) { return; } QProcess *process = m_runningGames.value(game->id()); process->terminate(); if (!process->waitForFinished(5000)) { process->kill(); } } bool GameLauncher::isGameRunning(Game *game) const { return game && m_runningGames.contains(game->id()); } void GameLauncher::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) QProcess *process = qobject_cast(sender()); if (!process) { return; } Game *game = m_processToGame.value(process); if (game) { game->setRunning(false); Q_EMIT gameStopped(game, exitCode); } cleanupProcess(process); Q_EMIT runningGamesChanged(); } void GameLauncher::onProcessError(QProcess::ProcessError error) { QProcess *process = qobject_cast(sender()); if (!process) { return; } Game *game = m_processToGame.value(process); QString errorMessage; switch (error) { case QProcess::FailedToStart: errorMessage = tr("Failed to start the game process"); break; case QProcess::Crashed: errorMessage = tr("Game process crashed"); break; case QProcess::Timedout: errorMessage = tr("Game process timed out"); break; case QProcess::WriteError: errorMessage = tr("Failed to write to game process"); break; case QProcess::ReadError: errorMessage = tr("Failed to read from game process"); break; default: errorMessage = tr("Unknown error occurred"); break; } if (game) { game->setRunning(false); Q_EMIT gameError(game, errorMessage); } cleanupProcess(process); Q_EMIT runningGamesChanged(); } void GameLauncher::cleanupProcess(QProcess *process) { if (!process) { return; } Game *game = m_processToGame.take(process); if (game) { m_runningGames.remove(game->id()); } process->deleteLater(); }