diff --git a/src/app.cpp b/src/app.cpp index 72bc0c3..4016dc2 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -18,7 +18,9 @@ #include #include #include +#include #include +#include #include App *App::s_instance = nullptr; @@ -142,6 +144,12 @@ App::App(QObject *parent) } } }); + + if (m_config->autoImportOnStartup()) { + QTimer::singleShot(0, this, [this]() { + importAllGames(); + }); + } } App *App::instance() @@ -215,18 +223,17 @@ void App::importAllGames() if (m_importing) return; - const bool anyEnabled = m_config->importSteam() || m_config->importLutris() || m_config->importHeroic() || m_config->importDesktop() - || m_config->importBottles() || m_config->importFlatpak() || m_config->importItch() || m_config->importLegendary() || m_config->importRetroArch(); + const bool doSteam = m_config->importSteam(); + const bool doLutris = m_config->importLutris(); + const bool doHeroic = m_config->importHeroic(); + const bool doDesktop = m_config->importDesktop(); + const bool doBottles = m_config->importBottles(); + const bool doFlatpak = m_config->importFlatpak(); + const bool doItch = m_config->importItch(); + const bool doLegendary = m_config->importLegendary(); + const bool doRetroArch = m_config->importRetroArch(); - const bool doSteam = m_config->importSteam() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Steam")); - const bool doLutris = m_config->importLutris() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Lutris")); - const bool doHeroic = m_config->importHeroic() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Heroic")); - const bool doDesktop = m_config->importDesktop() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Desktop")); - const bool doBottles = m_config->importBottles() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Bottles")); - const bool doFlatpak = m_config->importFlatpak() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Flatpak")); - const bool doItch = m_config->importItch() && !m_gameModel->hasPlatformPrefix(QStringLiteral("itch.io")); - const bool doLegendary = m_config->importLegendary() && !m_gameModel->hasPlatformPrefix(QStringLiteral("Legendary")); - const bool doRetroArch = m_config->importRetroArch() && !m_gameModel->hasPlatformPrefix(QStringLiteral("RetroArch")); + const bool anyEnabled = doSteam || doLutris || doHeroic || doDesktop || doBottles || doFlatpak || doItch || doLegendary || doRetroArch; if (!anyEnabled) { setImportStatus(tr("No import sources enabled")); @@ -234,17 +241,55 @@ void App::importAllGames() return; } - if (!(doSteam || doLutris || doHeroic || doDesktop || doBottles || doFlatpak || doItch || doLegendary || doRetroArch)) { - setImportStatus(tr("All enabled sources already imported")); - Q_EMIT importCompleted(0); - return; - } + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); setImporting(true); setImportStatus(tr("Importing games...")); - [[maybe_unused]] auto future = QtConcurrent::run([this, doSteam, doLutris, doHeroic, doDesktop, doBottles, doFlatpak, doItch, doLegendary, doRetroArch]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, + doSteam, + doLutris, + doHeroic, + doDesktop, + doBottles, + doFlatpak, + doItch, + doLegendary, + doRetroArch, + existingIds]() { int totalCount = 0; + QSet seenIds = existingIds; + + const auto keepNewGames = [&seenIds](const QList &games) { + QList result; + result.reserve(games.size()); + + for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + result.append(game); + } + + return result; + }; // Import from Steam if (doSteam) { @@ -256,7 +301,7 @@ void App::importAllGames() Qt::QueuedConnection); SteamImporter steamImporter; - QList steamGames = steamImporter.importGames(); + QList steamGames = keepNewGames(steamImporter.importGames()); for (Game *game : steamGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -291,7 +336,7 @@ void App::importAllGames() Qt::QueuedConnection); LutrisImporter lutrisImporter; - QList lutrisGames = lutrisImporter.importGames(); + QList lutrisGames = keepNewGames(lutrisImporter.importGames()); for (Game *game : lutrisGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -326,7 +371,7 @@ void App::importAllGames() Qt::QueuedConnection); HeroicImporter heroicImporter; - QList heroicGames = heroicImporter.importGames(); + QList heroicGames = keepNewGames(heroicImporter.importGames()); for (Game *game : heroicGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -361,7 +406,7 @@ void App::importAllGames() Qt::QueuedConnection); DesktopImporter desktopImporter; - QList desktopGames = desktopImporter.importGames(); + QList desktopGames = keepNewGames(desktopImporter.importGames()); for (Game *game : desktopGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -396,7 +441,7 @@ void App::importAllGames() Qt::QueuedConnection); BottlesImporter bottlesImporter; - QList bottlesGames = bottlesImporter.importGames(); + QList bottlesGames = keepNewGames(bottlesImporter.importGames()); for (Game *game : bottlesGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -431,7 +476,7 @@ void App::importAllGames() Qt::QueuedConnection); FlatpakImporter flatpakImporter; - QList flatpakGames = flatpakImporter.importGames(); + QList flatpakGames = keepNewGames(flatpakImporter.importGames()); for (Game *game : flatpakGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -466,7 +511,7 @@ void App::importAllGames() Qt::QueuedConnection); ItchImporter itchImporter; - QList itchGames = itchImporter.importGames(); + QList itchGames = keepNewGames(itchImporter.importGames()); for (Game *game : itchGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -501,7 +546,7 @@ void App::importAllGames() Qt::QueuedConnection); LegendaryImporter legendaryImporter; - QList legendaryGames = legendaryImporter.importGames(); + QList legendaryGames = keepNewGames(legendaryImporter.importGames()); for (Game *game : legendaryGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -536,7 +581,7 @@ void App::importAllGames() Qt::QueuedConnection); RetroArchImporter retroArchImporter; - QList retroArchGames = retroArchImporter.importGames(); + QList retroArchGames = keepNewGames(retroArchImporter.importGames()); for (Game *game : retroArchGames) { game->moveToThread(this->thread()); game->setParent(nullptr); @@ -565,7 +610,7 @@ void App::importAllGames() QMetaObject::invokeMethod( this, [this, totalCount]() { - setImportStatus(tr("Import complete: %1 games found").arg(totalCount)); + setImportStatus(tr("Import complete: %1 new games found").arg(totalCount)); setImporting(false); saveLibrary(); Q_EMIT importCompleted(totalCount); @@ -579,28 +624,57 @@ void App::importFromSteam() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Steam library...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { SteamImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Steam import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Steam import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -611,28 +685,57 @@ void App::importFromLutris() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Lutris library...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { LutrisImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Lutris import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Lutris import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -643,28 +746,57 @@ void App::importFromHeroic() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Heroic library...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { HeroicImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Heroic import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Heroic import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -675,28 +807,57 @@ void App::importFromDesktop() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning desktop entries...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { DesktopImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Desktop import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Desktop import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -707,28 +868,57 @@ void App::importFromBottles() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Bottles...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { BottlesImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Bottles import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Bottles import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -739,28 +929,57 @@ void App::importFromFlatpak() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Flatpak games...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { FlatpakImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Flatpak import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Flatpak import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -771,28 +990,57 @@ void App::importFromItch() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning itch.io library...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { ItchImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("itch.io import complete: %1 games found").arg(games.count())); + setImportStatus(tr("itch.io import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -803,28 +1051,57 @@ void App::importFromLegendary() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning Legendary library...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { LegendaryImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("Legendary import complete: %1 games found").arg(games.count())); + setImportStatus(tr("Legendary import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); @@ -835,28 +1112,57 @@ void App::importFromRetroArch() if (m_importing) return; + const QSet existingIds = [this]() { + QSet ids; + const QList games = m_gameModel->allGames(); + for (Game *game : games) { + if (game) { + ids.insert(game->id()); + } + } + return ids; + }(); + setImporting(true); setImportStatus(tr("Scanning RetroArch playlists...")); - [[maybe_unused]] auto future = QtConcurrent::run([this]() { + [[maybe_unused]] auto future = QtConcurrent::run([this, existingIds]() { RetroArchImporter importer; QList games = importer.importGames(); + QSet seenIds = existingIds; + QList newGames; + newGames.reserve(games.size()); for (Game *game : games) { + if (!game) { + continue; + } + + const QString id = game->id(); + if (id.isEmpty() || seenIds.contains(id)) { + delete game; + continue; + } + + seenIds.insert(id); + newGames.append(game); + } + + for (Game *game : newGames) { game->moveToThread(this->thread()); game->setParent(nullptr); } QMetaObject::invokeMethod( this, - [this, games]() { - for (Game *game : games) { + [this, newGames]() { + for (Game *game : newGames) { m_gameModel->addGame(game); } - setImportStatus(tr("RetroArch import complete: %1 games found").arg(games.count())); + setImportStatus(tr("RetroArch import complete: %1 new games found").arg(newGames.count())); setImporting(false); saveLibrary(); - Q_EMIT importCompleted(games.count()); + Q_EMIT importCompleted(newGames.count()); }, Qt::QueuedConnection); }); diff --git a/src/desktopimporter.cpp b/src/desktopimporter.cpp index 30200c6..0e65f7d 100644 --- a/src/desktopimporter.cpp +++ b/src/desktopimporter.cpp @@ -40,7 +40,13 @@ QStringList DesktopImporter::getDesktopFilePaths() const // Add common system locations appDirs << QStringLiteral("/usr/share/applications"); appDirs << QStringLiteral("/usr/local/share/applications"); - appDirs << expandPath(QStringLiteral("~/.local/share/applications")); + + const QString xdgDataHome = qEnvironmentVariable("XDG_DATA_HOME"); + if (!xdgDataHome.isEmpty()) { + appDirs << QDir(xdgDataHome).absoluteFilePath(QStringLiteral("applications")); + } else { + appDirs << expandPath(QStringLiteral("~/.local/share/applications")); + } // Flatpak export directories appDirs << expandPath(QStringLiteral("~/.local/share/flatpak/exports/share/applications")); diff --git a/src/game.cpp b/src/game.cpp index 32993c3..964222a 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -185,6 +185,134 @@ void Game::setLaunchPrefixPath(const QString &path) } } +QString Game::activeLaunchProfile() const +{ + return m_activeLaunchProfile; +} + +void Game::setActiveLaunchProfile(const QString &profileId) +{ + const QString normalized = profileId.trimmed().isEmpty() ? QStringLiteral("default") : profileId.trimmed(); + if (m_activeLaunchProfile != normalized) { + m_activeLaunchProfile = normalized; + Q_EMIT activeLaunchProfileChanged(); + } +} + +QVariantMap Game::launchProfileConfig(const QString &profileId) const +{ + const QString id = profileId.trimmed(); + if (id.isEmpty() || id == QLatin1String("default")) { + return {}; + } + + const QVariant value = m_launchProfiles.value(id); + if (!value.isValid()) { + return {}; + } + + return value.toMap(); +} + +void Game::setLaunchProfileConfig(const QString &profileId, const QVariantMap &config) +{ + const QString id = profileId.trimmed(); + if (id.isEmpty() || id == QLatin1String("default")) { + return; + } + + QVariantMap cleaned; + + const QString runner = config.value(QStringLiteral("runner")).toString().trimmed(); + if (!runner.isEmpty()) { + cleaned.insert(QStringLiteral("runner"), runner); + } + + const QString runnerPath = config.value(QStringLiteral("runnerPath")).toString().trimmed(); + if (!runnerPath.isEmpty()) { + cleaned.insert(QStringLiteral("runnerPath"), runnerPath); + } + + const QString prefixPath = config.value(QStringLiteral("prefixPath")).toString().trimmed(); + if (!prefixPath.isEmpty()) { + cleaned.insert(QStringLiteral("prefixPath"), prefixPath); + } + + const QVariantMap env = config.value(QStringLiteral("env")).toMap(); + if (!env.isEmpty()) { + cleaned.insert(QStringLiteral("env"), env); + } + + if (cleaned.isEmpty()) { + m_launchProfiles.remove(id); + } else { + m_launchProfiles.insert(id, cleaned); + } +} + +QVariantMap Game::effectiveLaunchConfig() const +{ + return effectiveLaunchConfigForProfile(m_activeLaunchProfile); +} + +QVariantMap Game::effectiveLaunchConfigForProfile(const QString &profileId) const +{ + QVariantMap result; + + if (!m_launchRunner.isEmpty()) { + result.insert(QStringLiteral("runner"), m_launchRunner); + } + if (!m_launchRunnerPath.isEmpty()) { + result.insert(QStringLiteral("runnerPath"), m_launchRunnerPath); + } + if (!m_launchPrefixPath.isEmpty()) { + result.insert(QStringLiteral("prefixPath"), m_launchPrefixPath); + } + if (!m_launchEnv.isEmpty()) { + result.insert(QStringLiteral("env"), m_launchEnv); + } + + const QString id = profileId.trimmed(); + if (id.isEmpty() || id == QLatin1String("default")) { + return result; + } + + const QVariantMap profileConfig = launchProfileConfig(id); + if (profileConfig.isEmpty()) { + return result; + } + + const QString runner = profileConfig.value(QStringLiteral("runner")).toString().trimmed(); + if (!runner.isEmpty()) { + result.insert(QStringLiteral("runner"), runner); + } + + const QString runnerPath = profileConfig.value(QStringLiteral("runnerPath")).toString().trimmed(); + if (!runnerPath.isEmpty()) { + result.insert(QStringLiteral("runnerPath"), runnerPath); + } + + const QString prefixPath = profileConfig.value(QStringLiteral("prefixPath")).toString().trimmed(); + if (!prefixPath.isEmpty()) { + result.insert(QStringLiteral("prefixPath"), prefixPath); + } + + const QVariantMap profileEnv = profileConfig.value(QStringLiteral("env")).toMap(); + if (!profileEnv.isEmpty()) { + QVariantMap mergedEnv; + const QVariantMap baseEnv = result.value(QStringLiteral("env")).toMap(); + for (auto it = baseEnv.constBegin(); it != baseEnv.constEnd(); ++it) { + mergedEnv.insert(it.key(), it.value()); + } + for (auto it = profileEnv.constBegin(); it != profileEnv.constEnd(); ++it) { + mergedEnv.insert(it.key(), it.value()); + } + result.insert(QStringLiteral("env"), mergedEnv); + } + + return result; +} + QString Game::platform() const { return m_platform; @@ -323,7 +451,8 @@ QJsonObject Game::toJson() const obj[QStringLiteral("hidden")] = m_hidden; obj[QStringLiteral("installed")] = m_installed; - const bool hasLaunchConfig = !m_launchEnv.isEmpty() || !m_launchRunner.isEmpty() || !m_launchRunnerPath.isEmpty() || !m_launchPrefixPath.isEmpty(); + const bool hasLaunchConfig = !m_launchEnv.isEmpty() || !m_launchRunner.isEmpty() || !m_launchRunnerPath.isEmpty() || !m_launchPrefixPath.isEmpty() + || !m_launchProfiles.isEmpty() || (!m_activeLaunchProfile.isEmpty() && m_activeLaunchProfile != QLatin1String("default")); if (hasLaunchConfig) { QJsonObject launchObj; @@ -345,6 +474,57 @@ QJsonObject Game::toJson() const launchObj.insert(QStringLiteral("prefixPath"), m_launchPrefixPath); } + if (!m_activeLaunchProfile.isEmpty() && m_activeLaunchProfile != QLatin1String("default")) { + launchObj.insert(QStringLiteral("activeProfile"), m_activeLaunchProfile); + } + + if (!m_launchProfiles.isEmpty()) { + QJsonObject profilesObj; + for (auto it = m_launchProfiles.constBegin(); it != m_launchProfiles.constEnd(); ++it) { + const QString profileId = it.key(); + if (profileId.isEmpty() || profileId == QLatin1String("default")) { + continue; + } + + const QVariantMap profileConfig = it.value().toMap(); + if (profileConfig.isEmpty()) { + continue; + } + + QJsonObject profileObj; + + const QString runner = profileConfig.value(QStringLiteral("runner")).toString(); + if (!runner.isEmpty()) { + profileObj.insert(QStringLiteral("runner"), runner); + } + const QString runnerPath = profileConfig.value(QStringLiteral("runnerPath")).toString(); + if (!runnerPath.isEmpty()) { + profileObj.insert(QStringLiteral("runnerPath"), runnerPath); + } + const QString prefixPath = profileConfig.value(QStringLiteral("prefixPath")).toString(); + if (!prefixPath.isEmpty()) { + profileObj.insert(QStringLiteral("prefixPath"), prefixPath); + } + + const QVariantMap env = profileConfig.value(QStringLiteral("env")).toMap(); + if (!env.isEmpty()) { + QJsonObject envObj; + for (auto envIt = env.constBegin(); envIt != env.constEnd(); ++envIt) { + envObj.insert(envIt.key(), QJsonValue::fromVariant(envIt.value())); + } + profileObj.insert(QStringLiteral("env"), envObj); + } + + if (!profileObj.isEmpty()) { + profilesObj.insert(profileId, profileObj); + } + } + + if (!profilesObj.isEmpty()) { + launchObj.insert(QStringLiteral("profiles"), profilesObj); + } + } + obj.insert(QStringLiteral("launch"), launchObj); } @@ -393,6 +573,7 @@ Game *Game::fromJson(const QJsonObject &json, QObject *parent) QString runner; QString runnerPath; QString prefixPath; + QString activeProfile; const QJsonValue launchValue = json.value(QStringLiteral("launch")); if (launchValue.isObject()) { const QJsonObject launchObj = launchValue.toObject(); @@ -404,6 +585,51 @@ Game *Game::fromJson(const QJsonObject &json, QObject *parent) runner = launchObj.value(QStringLiteral("runner")).toString(); runnerPath = launchObj.value(QStringLiteral("runnerPath")).toString(); prefixPath = launchObj.value(QStringLiteral("prefixPath")).toString(); + + activeProfile = launchObj.value(QStringLiteral("activeProfile")).toString(); + + const QJsonValue profilesValue = launchObj.value(QStringLiteral("profiles")); + if (profilesValue.isObject()) { + const QJsonObject profilesObj = profilesValue.toObject(); + for (auto it = profilesObj.constBegin(); it != profilesObj.constEnd(); ++it) { + if (!it.value().isObject()) { + continue; + } + + const QString profileId = it.key(); + if (profileId.isEmpty() || profileId == QLatin1String("default")) { + continue; + } + + const QJsonObject profileObj = it.value().toObject(); + QVariantMap profileConfig; + + const QString pRunner = profileObj.value(QStringLiteral("runner")).toString(); + if (!pRunner.isEmpty()) { + profileConfig.insert(QStringLiteral("runner"), pRunner); + } + const QString pRunnerPath = profileObj.value(QStringLiteral("runnerPath")).toString(); + if (!pRunnerPath.isEmpty()) { + profileConfig.insert(QStringLiteral("runnerPath"), pRunnerPath); + } + const QString pPrefixPath = profileObj.value(QStringLiteral("prefixPath")).toString(); + if (!pPrefixPath.isEmpty()) { + profileConfig.insert(QStringLiteral("prefixPath"), pPrefixPath); + } + + const QJsonValue pEnvValue = profileObj.value(QStringLiteral("env")); + if (pEnvValue.isObject()) { + const QVariantMap pEnv = pEnvValue.toObject().toVariantMap(); + if (!pEnv.isEmpty()) { + profileConfig.insert(QStringLiteral("env"), pEnv); + } + } + + if (!profileConfig.isEmpty()) { + game->setLaunchProfileConfig(profileId, profileConfig); + } + } + } } const QJsonValue legacyEnvValue = json.value(QStringLiteral("launchEnv")); @@ -435,6 +661,10 @@ Game *Game::fromJson(const QJsonObject &json, QObject *parent) game->setLaunchPrefixPath(prefixPath); } + if (!activeProfile.isEmpty()) { + game->setActiveLaunchProfile(activeProfile); + } + return game; } diff --git a/src/game.h b/src/game.h index 05304b8..00ce69f 100644 --- a/src/game.h +++ b/src/game.h @@ -30,6 +30,7 @@ class Game : public QObject Q_PROPERTY(QString launchRunner READ launchRunner WRITE setLaunchRunner NOTIFY launchRunnerChanged) Q_PROPERTY(QString launchRunnerPath READ launchRunnerPath WRITE setLaunchRunnerPath NOTIFY launchRunnerPathChanged) Q_PROPERTY(QString launchPrefixPath READ launchPrefixPath WRITE setLaunchPrefixPath NOTIFY launchPrefixPathChanged) + Q_PROPERTY(QString activeLaunchProfile READ activeLaunchProfile WRITE setActiveLaunchProfile NOTIFY activeLaunchProfileChanged) Q_PROPERTY(QString platform READ platform WRITE setPlatform NOTIFY platformChanged) Q_PROPERTY(QString platformId READ platformId WRITE setPlatformId NOTIFY platformIdChanged) Q_PROPERTY(QDateTime dateAdded READ dateAdded WRITE setDateAdded NOTIFY dateAddedChanged) @@ -82,6 +83,14 @@ public: QString launchPrefixPath() const; void setLaunchPrefixPath(const QString &path); + QString activeLaunchProfile() const; + void setActiveLaunchProfile(const QString &profileId); + + Q_INVOKABLE QVariantMap launchProfileConfig(const QString &profileId) const; + Q_INVOKABLE void setLaunchProfileConfig(const QString &profileId, const QVariantMap &config); + QVariantMap effectiveLaunchConfigForProfile(const QString &profileId) const; + QVariantMap effectiveLaunchConfig() const; + QString platform() const; void setPlatform(const QString &platform); @@ -129,6 +138,7 @@ Q_SIGNALS: void launchRunnerChanged(); void launchRunnerPathChanged(); void launchPrefixPathChanged(); + void activeLaunchProfileChanged(); void platformChanged(); void platformIdChanged(); void dateAddedChanged(); @@ -153,6 +163,8 @@ private: QString m_launchRunner; QString m_launchRunnerPath; QString m_launchPrefixPath; + QString m_activeLaunchProfile = QStringLiteral("default"); + QVariantMap m_launchProfiles; QString m_platform; QString m_platformId; QDateTime m_dateAdded; diff --git a/src/gamelauncher.cpp b/src/gamelauncher.cpp index eeb9c5b..53cd5e4 100644 --- a/src/gamelauncher.cpp +++ b/src/gamelauncher.cpp @@ -3,6 +3,8 @@ #include "gamelauncher.h" #include "app.h" +#include "gamepadmanager.h" +#include "inputmanager.h" #include #include @@ -108,6 +110,33 @@ static QString discoverDefaultProtonExecutable() return cached; } +static QString profileIdForCurrentUiMode() +{ + const Config *config = App::instance() ? App::instance()->config() : nullptr; + if (!config) { + return QStringLiteral("default"); + } + + if (config->uiMode() == Config::Couch) { + return QStringLiteral("couch"); + } + + if (config->uiMode() == Config::Auto) { + GamepadManager *pad = GamepadManager::instance(); + InputManager *input = InputManager::instance(); + + if (pad && pad->connected()) { + const bool activeGamepad = input && input->activeInput() == InputManager::Gamepad; + const bool noKeyboardMouse = input && !input->hasSeenKeyboardMouse(); + if (activeGamepad || noKeyboardMouse) { + return QStringLiteral("couch"); + } + } + } + + return QStringLiteral("default"); +} + GameLauncher::GameLauncher(QObject *parent) : QObject(parent) { @@ -153,10 +182,12 @@ QVariantMap GameLauncher::resolveLaunchInfo(Game *game) const return info; } - const QString runner = game->launchRunner().trimmed(); - const QString runnerPath = game->launchRunnerPath().trimmed(); - const QString prefixPath = game->launchPrefixPath().trimmed(); - const QVariantMap launchEnv = game->launchEnv(); + const QString profileId = profileIdForCurrentUiMode(); + const QVariantMap effectiveLaunchConfig = game->effectiveLaunchConfigForProfile(profileId); + const QString runner = effectiveLaunchConfig.value(QStringLiteral("runner")).toString().trimmed(); + const QString runnerPath = effectiveLaunchConfig.value(QStringLiteral("runnerPath")).toString().trimmed(); + const QString prefixPath = effectiveLaunchConfig.value(QStringLiteral("prefixPath")).toString().trimmed(); + const QVariantMap launchEnv = effectiveLaunchConfig.value(QStringLiteral("env")).toMap(); const bool hasLaunchOverrides = !runner.isEmpty() || !runnerPath.isEmpty() || !prefixPath.isEmpty() || !launchEnv.isEmpty(); info.insert(QStringLiteral("runner"), runner); @@ -297,10 +328,12 @@ void GameLauncher::launchGame(Game *game) return; } - const QString runner = game->launchRunner().trimmed(); - const QString runnerPath = game->launchRunnerPath().trimmed(); - const QString prefixPath = game->launchPrefixPath().trimmed(); - const QVariantMap launchEnv = game->launchEnv(); + const QString profileId = profileIdForCurrentUiMode(); + const QVariantMap effectiveLaunchConfig = game->effectiveLaunchConfigForProfile(profileId); + const QString runner = effectiveLaunchConfig.value(QStringLiteral("runner")).toString().trimmed(); + const QString runnerPath = effectiveLaunchConfig.value(QStringLiteral("runnerPath")).toString().trimmed(); + const QString prefixPath = effectiveLaunchConfig.value(QStringLiteral("prefixPath")).toString().trimmed(); + const QVariantMap launchEnv = effectiveLaunchConfig.value(QStringLiteral("env")).toMap(); const bool hasLaunchOverrides = !runner.isEmpty() || !runnerPath.isEmpty() || !prefixPath.isEmpty() || !launchEnv.isEmpty(); // Check if already running diff --git a/src/main.cpp b/src/main.cpp index 53e7e25..e88ad48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -16,6 +18,7 @@ #include #include "alakarte-version.h" +#include "app.h" int main(int argc, char *argv[]) { @@ -47,15 +50,54 @@ int main(int argc, char *argv[]) QCommandLineParser parser; aboutData.setupCommandLine(&parser); + + QCommandLineOption importAllAndExitOption(QStringLiteral("import-all-and-exit"), i18n("Import all enabled sources and exit")); + QCommandLineOption importDesktopAndExitOption(QStringLiteral("import-desktop-and-exit"), i18n("Import desktop entries and exit")); + QCommandLineOption startupAndExitOption(QStringLiteral("startup-and-exit"), i18n("Start without UI and exit after startup auto-import (if enabled)")); + parser.addOption(importAllAndExitOption); + parser.addOption(importDesktopAndExitOption); + parser.addOption(startupAndExitOption); + parser.process(app); aboutData.processCommandLine(&parser); - KDBusService service(KDBusService::Unique); - if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) { QQuickStyle::setStyle(QStringLiteral("org.kde.desktop")); } + if (parser.isSet(importAllAndExitOption) || parser.isSet(importDesktopAndExitOption) || parser.isSet(startupAndExitOption)) { + App *alakarteApp = App::instance(); + + QObject::connect(alakarteApp, &App::importCompleted, &app, [&app](int count) { + QTextStream(stdout) << count << Qt::endl; + app.exit(0); + }); + QObject::connect(alakarteApp, &App::importError, &app, [&app](const QString &error) { + QTextStream(stderr) << error << Qt::endl; + app.exit(1); + }); + + if (parser.isSet(importAllAndExitOption)) { + alakarteApp->importAllGames(); + } else if (parser.isSet(importDesktopAndExitOption)) { + alakarteApp->importFromDesktop(); + } else { + if (!alakarteApp->config() || !alakarteApp->config()->autoImportOnStartup()) { + QTextStream(stdout) << 0 << Qt::endl; + return 0; + } + + QTimer::singleShot(60000, &app, [&app]() { + QTextStream(stderr) << QStringLiteral("Timed out waiting for auto-import") << Qt::endl; + app.exit(2); + }); + } + + return app.exec(); + } + + KDBusService service(KDBusService::Unique); + QQmlApplicationEngine engine; engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); diff --git a/src/qml/GameDetailsSheet.qml b/src/qml/GameDetailsSheet.qml index 9f0c68d..f3a553d 100644 --- a/src/qml/GameDetailsSheet.qml +++ b/src/qml/GameDetailsSheet.qml @@ -12,7 +12,18 @@ import org.kde.alakarte Kirigami.OverlaySheet { id: detailsSheet - property var game + property var game: null + property int lastNonCouchUiMode: Config.Auto + + readonly property bool effectiveCouchMode: { + if (App.config.uiMode === Config.Couch) return true + if (App.config.uiMode !== Config.Auto) return false + if (GamepadManager.connected) { + if (InputManager.activeInput === InputManager.Gamepad) return true + if (!InputManager.hasSeenKeyboardMouse) return true + } + return false + } readonly property var screenshotsModel: game ? App.mediaManager.screenshotsModel(game) : null @@ -418,6 +429,24 @@ Kirigami.OverlaySheet { onClicked: detailsSheet.editRequested() } + QQC2.Button { + icon.name: "view-fullscreen" + text: detailsSheet.effectiveCouchMode ? i18n("Couch mode: On") : i18n("Couch mode: Off") + display: detailsSheet.useCompactLayout ? QQC2.AbstractButton.TextUnderIcon : QQC2.AbstractButton.TextBesideIcon + onClicked: { + if (detailsSheet.effectiveCouchMode) { + if (detailsSheet.lastNonCouchUiMode === Config.Auto) { + App.config.uiMode = Config.Desktop + } else { + App.config.uiMode = detailsSheet.lastNonCouchUiMode + } + } else { + detailsSheet.lastNonCouchUiMode = App.config.uiMode + App.config.uiMode = Config.Couch + } + } + } + QQC2.Button { icon.name: "dialog-information" text: i18n("Diagnostics") diff --git a/src/qml/GameEditDialog.qml b/src/qml/GameEditDialog.qml index 4729aa4..8069b1c 100644 --- a/src/qml/GameEditDialog.qml +++ b/src/qml/GameEditDialog.qml @@ -14,6 +14,7 @@ Kirigami.Dialog { property var game: null property bool isEditing: game !== null + property string editProfileId: "default" ListModel { id: envModel @@ -49,10 +50,19 @@ Kirigami.Dialog { game.developer = developerField.text.trim() game.launchCommand = executableField.text.trim() game.workingDirectory = workingDirField.text.trim() - game.launchEnv = dialog.envModelToMap() - game.launchRunner = runnerValue - game.launchRunnerPath = runnerPathValue - game.launchPrefixPath = prefixPathValue + if (dialog.editProfileId === "couch") { + game.setLaunchProfileConfig("couch", { + "runner": runnerValue, + "runnerPath": runnerPathValue, + "prefixPath": prefixPathValue, + "env": dialog.envModelToMap() + }) + } else { + game.launchEnv = dialog.envModelToMap() + game.launchRunner = runnerValue + game.launchRunnerPath = runnerPathValue + game.launchPrefixPath = prefixPathValue + } if (selectedCoverPath !== "") { App.setCoverFromFile(game, selectedCoverPath) } @@ -86,12 +96,15 @@ Kirigami.Dialog { property string selectedCoverPath: "" - readonly property bool anyMenuOpen: runnerCombo && runnerCombo.popup && runnerCombo.popup.visible + readonly property bool anyMenuOpen: (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) || (profileCombo && profileCombo.popup && profileCombo.popup.visible) function closeCurrentMenu() { if (runnerCombo && runnerCombo.popup && runnerCombo.popup.visible) { runnerCombo.popup.close() } + if (profileCombo && profileCombo.popup && profileCombo.popup.visible) { + profileCombo.popup.close() + } } function isDescendant(item, ancestor) { @@ -181,35 +194,77 @@ Kirigami.Dialog { return 0 } - function loadFields() { - selectedCoverPath = "" + function profileIdFromIndex(idx) { + if (idx === 1) return "couch" + return "default" + } + + function profileIndexFromId(profileId) { + if ((profileId || "").trim() === "couch") return 1 + return 0 + } + + function loadProfileFields() { envModel.clear() - if (isEditing && game) { - nameField.text = game.name || "" - developerField.text = game.developer || "" - executableField.text = game.launchCommand || "" - workingDirField.text = game.workingDirectory || "" - runnerCombo.currentIndex = dialog.runnerToIndex(game.launchRunner) - runnerPathField.text = game.launchRunnerPath || "" - prefixPathField.text = game.launchPrefixPath || "" + if (!isEditing || !game) { + runnerCombo.currentIndex = 0 + runnerPathField.text = "" + prefixPathField.text = "" + return + } - let env = game.launchEnv || ({}) + if (dialog.editProfileId === "couch") { + let cfg = game.launchProfileConfig("couch") || ({}) + runnerCombo.currentIndex = dialog.runnerToIndex(cfg.runner) + runnerPathField.text = cfg.runnerPath || "" + prefixPathField.text = cfg.prefixPath || "" + let env = cfg.env || ({}) let keys = Object.keys(env) keys.sort() for (let i = 0; i < keys.length; i++) { let k = keys[i] envModel.append({ key: k, value: String(env[k]) }) } + } else { + runnerCombo.currentIndex = dialog.runnerToIndex(game.launchRunner) + runnerPathField.text = game.launchRunnerPath || "" + prefixPathField.text = game.launchPrefixPath || "" + let env = game.launchEnv || ({}) + let keys = Object.keys(env) + keys.sort() + for (let i = 0; i < keys.length; i++) { + let k = keys[i] + envModel.append({ key: k, value: String(env[k]) }) + } + } + } + + function loadFields() { + selectedCoverPath = "" + if (isEditing && game) { + nameField.text = game.name || "" + developerField.text = game.developer || "" + executableField.text = game.launchCommand || "" + workingDirField.text = game.workingDirectory || "" + + dialog.editProfileId = (App.config.uiMode === Config.Couch + || (App.config.uiMode === Config.Auto + && GamepadManager.connected + && (InputManager.activeInput === InputManager.Gamepad || !InputManager.hasSeenKeyboardMouse))) + ? "couch" + : "default" + profileCombo.currentIndex = dialog.profileIndexFromId(dialog.editProfileId) + dialog.loadProfileFields() } else { nameField.text = "" developerField.text = "" executableField.text = "" workingDirField.text = "" - runnerCombo.currentIndex = 0 - runnerPathField.text = "" - prefixPathField.text = "" + dialog.editProfileId = "default" + profileCombo.currentIndex = 0 + dialog.loadProfileFields() } } @@ -325,6 +380,35 @@ Kirigami.Dialog { title: i18n("Compatibility") } + Item { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + visible: isEditing + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: Kirigami.Units.largeSpacing + + QQC2.Label { + text: i18n("Profile") + Layout.alignment: Qt.AlignVCenter + } + + QQC2.ComboBox { + id: profileCombo + Layout.fillWidth: true + model: [i18n("Default"), i18n("Couch")] + onCurrentIndexChanged: if (dialog.isEditing) { + dialog.editProfileId = dialog.profileIdFromIndex(currentIndex) + dialog.loadProfileFields() + } + } + } + } + + FormCard.FormDelegateSeparator { visible: isEditing } + Item { Layout.fillWidth: true Layout.margins: Kirigami.Units.largeSpacing diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 702dd4d..9ad4d4a 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -1183,7 +1183,7 @@ Kirigami.ApplicationWindow { secondary: "steam" resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg") } - enabled: !App.importing && App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importSteam && App.gameModel.count >= 0 onClicked: App.importFromSteam() } @@ -1197,7 +1197,7 @@ Kirigami.ApplicationWindow { primary: "lutris" secondary: "applications-games" } - enabled: !App.importing && App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importLutris && App.gameModel.count >= 0 onClicked: App.importFromLutris() } @@ -1211,7 +1211,7 @@ Kirigami.ApplicationWindow { primary: "com.heroicgameslauncher.hgl" secondary: "applications-games" } - enabled: !App.importing && App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importHeroic && App.gameModel.count >= 0 onClicked: App.importFromHeroic() } @@ -1225,7 +1225,7 @@ Kirigami.ApplicationWindow { primary: "user-desktop" secondary: "computer" } - enabled: !App.importing && App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importDesktop && App.gameModel.count >= 0 onClicked: App.importFromDesktop() } @@ -1239,7 +1239,7 @@ Kirigami.ApplicationWindow { primary: "com.usebottles.bottles" secondary: "application-x-executable" } - enabled: !App.importing && App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importBottles && App.gameModel.count >= 0 onClicked: App.importFromBottles() } @@ -1253,7 +1253,7 @@ Kirigami.ApplicationWindow { primary: "flatpak-discover" secondary: "applications-games" } - enabled: !App.importing && App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importFlatpak && App.gameModel.count >= 0 onClicked: App.importFromFlatpak() } @@ -1268,7 +1268,7 @@ Kirigami.ApplicationWindow { secondary: "itch" resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg") } - enabled: !App.importing && App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importItch && App.gameModel.count >= 0 onClicked: App.importFromItch() } @@ -1282,7 +1282,7 @@ Kirigami.ApplicationWindow { primary: "legendary" secondary: "applications-games" } - enabled: !App.importing && App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importLegendary && App.gameModel.count >= 0 onClicked: App.importFromLegendary() } @@ -1297,7 +1297,7 @@ Kirigami.ApplicationWindow { secondary: "retroarch" resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg") } - enabled: !App.importing && App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch") && App.gameModel.count >= 0 + enabled: !App.importing && App.config.importRetroArch && App.gameModel.count >= 0 onClicked: App.importFromRetroArch() } } @@ -1309,15 +1309,15 @@ Kirigami.ApplicationWindow { QQC2.Button { text: i18n("Import All") icon.name: "document-import" - enabled: !App.importing && App.gameModel.count >= 0 && ((App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam")) - || (App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris")) - || (App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic")) - || (App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop")) - || (App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles")) - || (App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak")) - || (App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io")) - || (App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary")) - || (App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch"))) + enabled: !App.importing && App.gameModel.count >= 0 && (App.config.importSteam + || App.config.importLutris + || App.config.importHeroic + || App.config.importDesktop + || App.config.importBottles + || App.config.importFlatpak + || App.config.importItch + || App.config.importLegendary + || App.config.importRetroArch) onClicked: App.importAllGames() } diff --git a/src/qml/SettingsPage.qml b/src/qml/SettingsPage.qml index 419747b..0c831a4 100644 --- a/src/qml/SettingsPage.qml +++ b/src/qml/SettingsPage.qml @@ -643,15 +643,15 @@ FormCard.FormHeader { text: i18n("Import All Games") description: i18n("Scan all enabled sources") icon.name: "document-import" - enabled: !App.importing && App.gameModel.count >= 0 && ((App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam")) - || (App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris")) - || (App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic")) - || (App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop")) - || (App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles")) - || (App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak")) - || (App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io")) - || (App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary")) - || (App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch"))) + enabled: !App.importing && App.gameModel.count >= 0 && (App.config.importSteam + || App.config.importLutris + || App.config.importHeroic + || App.config.importDesktop + || App.config.importBottles + || App.config.importFlatpak + || App.config.importItch + || App.config.importLegendary + || App.config.importRetroArch) onClicked: App.importAllGames() }