mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 09:13:09 +00:00
203 lines
5 KiB
C++
203 lines
5 KiB
C++
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
|
// SPDX-FileCopyrightText: 2024 A-La-Karte Contributors
|
||
|
|
|
||
|
|
#include "gamelauncher.h"
|
||
|
|
#include "app.h"
|
||
|
|
|
||
|
|
#include <QCoreApplication>
|
||
|
|
#include <QDateTime>
|
||
|
|
#include <QDesktopServices>
|
||
|
|
#include <QTimer>
|
||
|
|
#include <QUrl>
|
||
|
|
|
||
|
|
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);
|
||
|
|
delete process;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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<int, QProcess::ExitStatus>::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<QProcess *>(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<QProcess *>(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();
|
||
|
|
}
|