a-la-karte/src/gamelauncher.cpp
Marco Allegretti 747b02035a alakarte: Initial import
Initial release of A-La-Karte, a unified game launcher for KDE Plasma.

Includes the QML UI, platform importers, AppStream metadata, icons,

and developer documentation.
2026-01-18 13:13:07 +01:00

202 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();
}