// SPDX-FileCopyrightText: 2026 Marco Allegretti // SPDX-License-Identifier: EUPL-1.2 #pragma once #include #include #include #include #include #include #include #include #include #include class GameLauncherProvider : public QAbstractListModel { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(int count READ count NOTIFY countChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged) Q_PROPERTY(QString sourceFilter READ sourceFilter WRITE setSourceFilter NOTIFY sourceFilterChanged) Q_PROPERTY(bool overlayEnabled READ overlayEnabled WRITE setOverlayEnabled NOTIFY overlayEnabledChanged) Q_PROPERTY(bool mangohudAvailable READ mangohudAvailable NOTIFY mangohudAvailableChanged) Q_PROPERTY(int fpsLimit READ fpsLimit WRITE setFpsLimit NOTIFY fpsLimitChanged) Q_PROPERTY(bool launchPending READ launchPending NOTIFY launchPendingChanged) Q_PROPERTY(QString pendingLaunchName READ pendingLaunchName NOTIFY launchPendingChanged) Q_PROPERTY(QString lastLaunchError READ lastLaunchError NOTIFY lastLaunchErrorChanged) public: explicit GameLauncherProvider(QObject *parent = nullptr); enum Roles { NameRole = Qt::UserRole + 1, IconRole, SourceRole, // "desktop", "waydroid", "steam", "flatpak" StorageIdRole, // .desktop file name or launch URI LaunchCommandRole, LaunchMethodRole, ArtworkRole, // path to banner/grid image (empty if none) LastPlayedTextRole, PinnedRole, }; Q_ENUM(Roles) int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; int count() const; bool loading() const; QString filterString() const; void setFilterString(const QString &filter); QString sourceFilter() const; void setSourceFilter(const QString &source); bool overlayEnabled() const; void setOverlayEnabled(bool enabled); bool mangohudAvailable() const; int fpsLimit() const; void setFpsLimit(int limit); bool launchPending() const; QString pendingLaunchName() const; QString lastLaunchError() const; Q_INVOKABLE void refresh(); Q_INVOKABLE void launch(int index); Q_INVOKABLE void launchByStorageId(const QString &storageId); Q_INVOKABLE QVariantMap gameDetails(const QString &storageId) const; Q_INVOKABLE bool openSourceApp(const QString &source); Q_INVOKABLE void clearLastPlayed(const QString &storageId); Q_INVOKABLE QVariantList recentGames(int limit = 5) const; Q_INVOKABLE void clearPendingLaunch(); Q_INVOKABLE void clearLastLaunchError(); Q_INVOKABLE void togglePin(const QString &storageId); Q_INVOKABLE int perGameFpsLimit(const QString &storageId) const; Q_INVOKABLE void setPerGameFpsLimit(const QString &storageId, int limit); Q_INVOKABLE int perGameOverlayState(const QString &storageId) const; Q_INVOKABLE void setPerGameOverlayState(const QString &storageId, int state); Q_SIGNALS: void countChanged(); void loadingChanged(); void filterStringChanged(); void sourceFilterChanged(); void overlayEnabledChanged(); void mangohudAvailableChanged(); void fpsLimitChanged(); void launchPendingChanged(); void lastLaunchErrorChanged(); void gameLaunched(const QString &name); void gameLaunchFailed(const QString &name, const QString &error); void recentGamesChanged(); private: struct GameEntry { QString name; QString icon; QString source; QString storageId; QString launchCommand; QString artwork; QDateTime lastPlayed; }; void loadDesktopGames(); void loadSteamGames(); void loadLutrisGames(); void loadHeroicGames(); void deduplicateGames(); void loadRecentTimestamps(); void saveRecentTimestamp(const QString &storageId, const QDateTime &when); void applyFilter(); void launchEntry(GameEntry &entry); bool launchWithMangohud(const QString &program, const QStringList &args, bool overlayEnabled, int fpsLimit, qint64 *pid = nullptr); QString launchMethodForEntry(const GameEntry &entry) const; QString formatLastPlayed(const QDateTime &when) const; // Returns the current m_allGames index for the storage id. // Callers must re-lookup after any mutation that can rebuild or reorder the list. int findEntryIndexByStorageId(const QString &storageId) const; void markLaunchSucceeded(const QString &storageId, const QString &name); void markLaunchFailed(const QString &name, const QString &error); void setPendingLaunch(const QString &name); void loadPinnedGames(); QList m_allGames; QList m_games; // filtered view QString m_filterString; QString m_sourceFilter; // empty = all, or a specific source such as "desktop" or "waydroid" KSharedConfigPtr m_config; bool m_loading = false; bool m_overlayEnabled = false; int m_fpsLimit = 0; bool m_mangohudAvailable = false; QString m_mangohudPath; QSet m_pinnedGames; bool m_launchPending = false; QString m_pendingLaunchName; QString m_lastLaunchError; QTimer m_pendingLaunchTimer; KConfigWatcher::Ptr m_configWatcher; };