shift-shell/components/gamingshellplugin/gamelauncherprovider.h
Marco Allegretti b42ef44e23 Add MangoHud overlay, FPS cap, and game pinning
Inject MangoHud when launching desktop and command-line games.
MANGOHUD_CONFIG is built per-launch via a QProcess instance so
env vars are isolated to each child process — qputenv is not used.

Global overlay toggle and FPS cap (Off/30/40/60) are stored as
properties on GameLauncherProvider and reflected in the quick
settings panel. Per-game overrides stored in plasmamobilerc under
[GamingPerGame/<storageId>] take precedence over the globals at
launch time.

Games can be pinned to the top of the grid. The pinned set is
persisted in plasmamobilerc [GamingPinned] and restored on start.
applyFilter() uses stable_sort so pinned games float to the top
while alphabetical order is preserved within each group.
2026-04-25 09:48:25 +02:00

149 lines
5.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2
#pragma once
#include <QAbstractListModel>
#include <QDateTime>
#include <QList>
#include <QSet>
#include <QString>
#include <QTimer>
#include <QVariantMap>
#include <qqmlregistration.h>
#include <KConfigWatcher>
#include <KSharedConfig>
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,
InstalledRole,
PinnedRole,
};
Q_ENUM(Roles)
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> 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;
bool installed = true;
};
void loadDesktopGames();
void loadSteamGames();
void loadFlatpakGames();
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<GameEntry> m_allGames;
QList<GameEntry> 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<QString> m_pinnedGames;
bool m_launchPending = false;
QString m_pendingLaunchName;
QString m_lastLaunchError;
QTimer m_pendingLaunchTimer;
KConfigWatcher::Ptr m_configWatcher;
};