Compare commits

...

13 commits

11 changed files with 892 additions and 420 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,8 @@
#pragma once
#include <atomic>
#include <QHash>
#include <QJsonObject>
#include <QObject>
@ -36,6 +38,8 @@ public:
static App *instance();
static App *create(QQmlEngine *engine, QJSEngine *scriptEngine);
~App() override;
GameModel *gameModel() const;
GameLauncher *launcher() const;
RunnerManagerClient *runnerManager() const;
@ -90,6 +94,8 @@ private:
QString m_importStatus;
QHash<QString, QJsonObject> m_removedGames;
std::atomic_bool m_shuttingDown{false};
void setImporting(bool importing);
void setImportStatus(const QString &status);
};

View file

@ -3,6 +3,7 @@
#include "gamecenterdaemon.h"
#include <QCoreApplication>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
@ -878,6 +879,48 @@ GameCenterDaemon::GameCenterDaemon(QObject *parent)
{
}
GameCenterDaemon::~GameCenterDaemon()
{
prepareForShutdown();
}
void GameCenterDaemon::prepareForShutdown()
{
for (auto it = m_sessions.begin(); it != m_sessions.end(); ++it) {
if (it.value().scanner) {
it.value().scanner->cancel();
}
QPointer<QProcess> proc = it.value().process;
it.value().process = nullptr;
if (proc) {
proc->disconnect(this);
connect(proc, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), proc, &QObject::deleteLater);
if (proc->state() != QProcess::NotRunning) {
proc->setParent(nullptr);
} else {
proc->deleteLater();
}
}
if (it.value().scanner) {
it.value().scanner->disconnect(this);
it.value().scanner->deleteLater();
it.value().scanner = nullptr;
}
}
for (auto it = m_unitPathWatchers.begin(); it != m_unitPathWatchers.end(); ++it) {
if (QObject *watcher = it.value()) {
watcher->deleteLater();
}
}
m_unitPathWatchers.clear();
m_unitPathToSessionId.clear();
m_unitNameToSessionId.clear();
}
bool GameCenterDaemon::init()
{
QDBusConnection bus = QDBusConnection::sessionBus();
@ -897,6 +940,10 @@ bool GameCenterDaemon::init()
recoverExistingSessions();
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &GameCenterDaemon::prepareForShutdown);
}
return true;
}
@ -1232,7 +1279,7 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
}
process->start(program, args);
if (!process->waitForStarted()) {
if (!process->waitForStarted(5000)) {
const QVariantMap ctx = {
{QStringLiteral("command"), command},
{QStringLiteral("program"), program},

View file

@ -23,6 +23,7 @@ class GameCenterDaemon : public QObject
public:
explicit GameCenterDaemon(QObject *parent = nullptr);
~GameCenterDaemon() override;
bool init();
@ -76,6 +77,8 @@ private:
void handleSystemdUnitPropertiesChanged(const QDBusObjectPath &unitPath, const QVariantMap &changedProperties);
void removeSessionInternal(const QString &sessionId, const QString &finalState);
void prepareForShutdown();
private Q_SLOTS:
void handleSystemdUnitNew(const QString &unitName, const QDBusObjectPath &unitPath);
void handleSystemdUnitRemoved(const QString &unitName, const QDBusObjectPath &unitPath);

View file

@ -15,6 +15,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
#include <QStandardPaths>
#include <memory>
@ -87,14 +88,6 @@ static QString gamepadTypeToString(SDL_GamepadType t)
return QStringLiteral("unknown");
}
}
static QVariantMap unwrapVariantMap(QVariant v)
{
if (v.canConvert<QVariantMap>()) {
return v.toMap();
}
return {};
}
}
QVariantMap InputDaemon::Profile::toVariantMap() const
@ -273,7 +266,10 @@ void InputDaemon::loadProfiles()
bool InputDaemon::saveProfiles() const
{
const QString path = profilesPath();
QDir().mkpath(QFileInfo(path).absolutePath());
const QString dirPath = QFileInfo(path).absolutePath();
if (!QDir().mkpath(dirPath)) {
return false;
}
QJsonArray profiles;
for (const Profile &p : m_profiles) {
@ -289,13 +285,17 @@ bool InputDaemon::saveProfiles() const
root.insert(QStringLiteral("profiles"), profiles);
root.insert(QStringLiteral("assignments"), assignments);
QFile f(path);
QSaveFile f(path);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
f.write(QJsonDocument(root).toJson());
return true;
const QByteArray payload = QJsonDocument(root).toJson();
if (f.write(payload) != payload.size()) {
return false;
}
return f.commit();
}
InputDaemon::Profile InputDaemon::profileById(const QString &profileId) const

View file

@ -98,7 +98,7 @@ QList<Game *> LutrisImporter::importGames()
QProcess process;
process.start(lutrisPath, {QStringLiteral("-lo"), QStringLiteral("--json")});
if (process.waitForFinished(30000)) {
if (process.waitForStarted(5000) && process.waitForFinished(30000)) {
QByteArray output = process.readAllStandardOutput();
QJsonDocument doc = QJsonDocument::fromJson(output);
@ -139,6 +139,14 @@ QList<Game *> LutrisImporter::importGames()
Q_EMIT importProgress(current, total);
}
}
} else {
if (process.state() != QProcess::NotRunning) {
process.terminate();
if (!process.waitForFinished(3000)) {
process.kill();
process.waitForFinished(3000);
}
}
}
}

View file

@ -115,7 +115,10 @@ Kirigami.Dialog {
readonly property bool anyConfirmOpen: !!(deletePrefixConfirmDialog && deletePrefixConfirmDialog.opened)
readonly property bool anyMenuOpen: !!(runnerCombo && runnerCombo.popup && runnerCombo.popup.visible)
readonly property bool anyMenuOpen: {
if (!runnerCombo || !runnerCombo.popup) return false
return runnerCombo.popup.visible === true
}
function currentConfirmDialog() {
if (deletePrefixConfirmDialog && deletePrefixConfirmDialog.opened) return deletePrefixConfirmDialog
@ -133,7 +136,7 @@ Kirigami.Dialog {
}
function closeCurrentMenu() {
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) {
if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible === true) {
runnerCombo.popup.close()
}
}

View file

@ -328,8 +328,6 @@ Kirigami.ApplicationWindow {
footer: Item {
implicitWidth: root.width
anchors.left: parent ? parent.left : undefined
anchors.right: parent ? parent.right : undefined
implicitHeight: footerBar.implicitHeight
height: implicitHeight

View file

@ -22,6 +22,7 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProcess>
#include <QSaveFile>
#include <QSet>
#include <QStandardPaths>
#include <QTimer>
@ -201,7 +202,10 @@ void RunnerManagerDaemon::loadRegistry()
bool RunnerManagerDaemon::saveRegistry() const
{
const QString path = registryPath();
QDir().mkpath(QFileInfo(path).absolutePath());
const QString dirPath = QFileInfo(path).absolutePath();
if (!QDir().mkpath(dirPath)) {
return false;
}
QJsonArray arr;
@ -221,13 +225,17 @@ bool RunnerManagerDaemon::saveRegistry() const
QJsonObject root;
root.insert(QStringLiteral("runners"), arr);
QFile f(path);
QSaveFile f(path);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
f.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
return true;
const QByteArray payload = QJsonDocument(root).toJson(QJsonDocument::Indented);
if (f.write(payload) != payload.size()) {
return false;
}
return f.commit();
}
QString RunnerManagerDaemon::gameProfilesPath() const
@ -267,7 +275,10 @@ void RunnerManagerDaemon::loadGameProfiles()
bool RunnerManagerDaemon::saveGameProfiles() const
{
const QString path = gameProfilesPath();
QDir().mkpath(QFileInfo(path).absolutePath());
const QString dirPath = QFileInfo(path).absolutePath();
if (!QDir().mkpath(dirPath)) {
return false;
}
QJsonArray arr;
for (auto it = m_gameProfiles.constBegin(); it != m_gameProfiles.constEnd(); ++it) {
@ -281,13 +292,17 @@ bool RunnerManagerDaemon::saveGameProfiles() const
QJsonObject root;
root.insert(QStringLiteral("profiles"), arr);
QFile f(path);
QSaveFile f(path);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
f.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
return true;
const QByteArray payload = QJsonDocument(root).toJson(QJsonDocument::Indented);
if (f.write(payload) != payload.size()) {
return false;
}
return f.commit();
}
QVariantMap RunnerManagerDaemon::gameProfileForGameId(const QString &gameId) const

View file

@ -152,15 +152,23 @@ void RunnerManagerClient::shutdownSpawnedRunnerDaemon()
return;
}
if (m_runnerdProcess->state() == QProcess::NotRunning) {
QProcess *p = m_runnerdProcess;
const auto state = p->state();
if (state == QProcess::NotRunning) {
return;
}
m_runnerdProcess->terminate();
if (!m_runnerdProcess->waitForFinished(1000)) {
m_runnerdProcess->kill();
m_runnerdProcess->waitForFinished(1000);
// Avoid our finished() handler nulling the pointer while we're shutting down.
p->disconnect(this);
p->terminate();
if (!p->waitForFinished(3000)) {
p->kill();
p->waitForFinished(3000);
}
p->deleteLater();
m_runnerdProcess = nullptr;
}
void RunnerManagerClient::ensureRunnerDaemon()

View file

@ -8,11 +8,11 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QSaveFile>
#include <QStandardPaths>
#include <QUrlQuery>
@ -297,16 +297,28 @@ void SteamGridDB::onImageDownloaded()
QString coversPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/covers");
QDir dir(coversPath);
if (!dir.exists()) {
dir.mkpath(coversPath);
if (!dir.mkpath(coversPath)) {
Q_EMIT fetchError(game, QStringLiteral("Failed to create covers directory"));
m_processedGames++;
Q_EMIT fetchProgress(m_processedGames, m_totalGames);
processNextGame();
return;
}
}
QString fileName = game->id() + QStringLiteral(".jpg");
QString filePath = coversPath + QStringLiteral("/") + fileName;
QFile file(filePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(reply->readAll());
file.close();
const QByteArray payload = reply->readAll();
QSaveFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
if (file.write(payload) != payload.size() || !file.commit()) {
Q_EMIT fetchError(game, QStringLiteral("Failed to save cover image"));
m_processedGames++;
Q_EMIT fetchProgress(m_processedGames, m_totalGames);
processNextGame();
return;
}
QUrl localUrl = QUrl::fromLocalFile(filePath);
game->setCoverUrl(localUrl);