Add support for hiding games

Fixes #255
This commit is contained in:
Cameron Gutman 2020-08-01 21:06:01 -07:00
parent 9385d62c89
commit 539bf0cb30
10 changed files with 239 additions and 47 deletions

View File

@ -68,12 +68,7 @@ private:
} }
QWriteLocker lock(&m_Computer->lock); QWriteLocker lock(&m_Computer->lock);
if (m_Computer->appList != appList) { changed = m_Computer->updateAppList(appList);
m_Computer->appList = appList;
m_Computer->sortAppList();
changed = true;
}
return true; return true;
} }
@ -433,6 +428,15 @@ void ComputerManager::renameHost(NvComputer* computer, QString name)
handleComputerStateChanged(computer); handleComputerStateChanged(computer);
} }
void ComputerManager::clientSideAttributeUpdated(NvComputer* computer)
{
// Persist the change
saveHosts();
// Notify the UI of the state change
handleComputerStateChanged(computer);
}
void ComputerManager::handleAboutToQuit() void ComputerManager::handleAboutToQuit()
{ {
QWriteLocker lock(&m_Lock); QWriteLocker lock(&m_Lock);

View File

@ -181,6 +181,8 @@ public:
void renameHost(NvComputer* computer, QString name); void renameHost(NvComputer* computer, QString name);
void clientSideAttributeUpdated(NvComputer* computer);
signals: signals:
void computerStateChanged(NvComputer* computer); void computerStateChanged(NvComputer* computer);

View File

@ -4,6 +4,7 @@
#define SER_APPID "id" #define SER_APPID "id"
#define SER_APPHDR "hdr" #define SER_APPHDR "hdr"
#define SER_APPCOLLECTOR "appcollector" #define SER_APPCOLLECTOR "appcollector"
#define SER_HIDDEN "hidden"
NvApp::NvApp(QSettings& settings) NvApp::NvApp(QSettings& settings)
{ {
@ -11,6 +12,7 @@ NvApp::NvApp(QSettings& settings)
id = settings.value(SER_APPID).toInt(); id = settings.value(SER_APPID).toInt();
hdrSupported = settings.value(SER_APPHDR).toBool(); hdrSupported = settings.value(SER_APPHDR).toBool();
isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool(); isAppCollectorGame = settings.value(SER_APPCOLLECTOR).toBool();
hidden = settings.value(SER_HIDDEN).toBool();
} }
void NvApp::serialize(QSettings& settings) const void NvApp::serialize(QSettings& settings) const
@ -19,4 +21,5 @@ void NvApp::serialize(QSettings& settings) const
settings.setValue(SER_APPID, id); settings.setValue(SER_APPID, id);
settings.setValue(SER_APPHDR, hdrSupported); settings.setValue(SER_APPHDR, hdrSupported);
settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame); settings.setValue(SER_APPCOLLECTOR, isAppCollectorGame);
settings.setValue(SER_HIDDEN, hidden);
} }

View File

@ -10,7 +10,16 @@ public:
bool operator==(const NvApp& other) const bool operator==(const NvApp& other) const
{ {
return id == other.id; return id == other.id &&
name == other.name &&
hdrSupported == other.hdrSupported &&
isAppCollectorGame == other.isAppCollectorGame &&
hidden == other.hidden;
}
bool operator!=(const NvApp& other) const
{
return !operator==(other);
} }
bool isInitialized() bool isInitialized()
@ -25,6 +34,7 @@ public:
QString name; QString name;
bool hdrSupported = false; bool hdrSupported = false;
bool isAppCollectorGame = false; bool isAppCollectorGame = false;
bool hidden = false;
}; };
Q_DECLARE_METATYPE(NvApp) Q_DECLARE_METATYPE(NvApp)

View File

@ -306,6 +306,25 @@ bool NvComputer::isReachableOverVpn()
} }
} }
bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
if (appList == newAppList) {
return false;
}
// Propagate client-side attributes to the new app list
for (const NvApp& existingApp : appList) {
for (NvApp& newApp : newAppList) {
if (existingApp.id == newApp.id) {
newApp.hidden = existingApp.hidden;
}
}
}
appList = newAppList;
sortAppList();
return true;
}
QVector<QString> NvComputer::uniqueAddresses() const QVector<QString> NvComputer::uniqueAddresses() const
{ {
QVector<QString> uniqueAddressList; QVector<QString> uniqueAddressList;
@ -389,7 +408,12 @@ bool NvComputer::update(NvComputer& that)
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC); ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
ASSIGN_IF_CHANGED(gpuModel); ASSIGN_IF_CHANGED(gpuModel);
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert); ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes); ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
if (!that.appList.isEmpty()) {
// updateAppList() handles merging client-side attributes
updateAppList(that.appList);
}
return changed; return changed;
} }

View File

@ -16,6 +16,8 @@ class NvComputer
private: private:
void sortAppList(); void sortAppList();
bool updateAppList(QVector<NvApp> newAppList);
bool pendingQuit; bool pendingQuit;
public: public:

View File

@ -9,6 +9,7 @@ CenteredGridView {
property int computerIndex property int computerIndex
property AppModel appModel : createModel() property AppModel appModel : createModel()
property bool activated property bool activated
property bool showHiddenGames
id: appGrid id: appGrid
focus: true focus: true
@ -48,7 +49,7 @@ CenteredGridView {
function createModel() function createModel()
{ {
var model = Qt.createQmlObject('import AppModel 1.0; AppModel {}', parent, '') var model = Qt.createQmlObject('import AppModel 1.0; AppModel {}', parent, '')
model.initialize(ComputerManager, computerIndex) model.initialize(ComputerManager, computerIndex, showHiddenGames)
return model return model
} }
@ -58,6 +59,9 @@ CenteredGridView {
width: 220; height: 287; width: 220; height: 287;
grid: appGrid grid: appGrid
// Dim the app if it's hidden
opacity: model.hidden ? 0.4 : 1.0
Image { Image {
property bool isPlaceholder: false property bool isPlaceholder: false
@ -165,8 +169,8 @@ CenteredGridView {
function launchOrResumeSelectedApp() function launchOrResumeSelectedApp()
{ {
var runningIndex = appModel.getRunningAppIndex() var runningId = appModel.getRunningAppId()
if (runningIndex >= 0 && runningIndex !== index) { if (runningId !== 0 && runningId !== model.appid) {
quitAppDialog.appName = appModel.getRunningAppName() quitAppDialog.appName = appModel.getRunningAppName()
quitAppDialog.segueToStream = true quitAppDialog.segueToStream = true
quitAppDialog.nextAppName = model.name quitAppDialog.nextAppName = model.name
@ -190,6 +194,25 @@ CenteredGridView {
} }
} }
onPressAndHold: {
// popup() ensures the menu appears under the mouse cursor
if (appContextMenu.popup) {
appContextMenu.popup()
}
else {
// Qt 5.9 doesn't have popup()
appContextMenu.open()
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton;
onClicked: {
parent.onPressAndHold()
}
}
Keys.onReturnPressed: { Keys.onReturnPressed: {
// Open the app context menu if activated via the gamepad or keyboard // Open the app context menu if activated via the gamepad or keyboard
// for running games. If the game isn't running, the above onClicked // for running games. If the game isn't running, the above onClicked
@ -228,6 +251,14 @@ CenteredGridView {
onTriggered: doQuitGame() onTriggered: doQuitGame()
visible: model.running visible: model.running
} }
NavigableMenuItem {
parentMenu: appContextMenu
checkable: true
checked: model.hidden
text: "Hide Game"
onTriggered: appModel.setAppHidden(model.index, !model.hidden)
visible: !model.running || model.hidden
}
} }
} }

View File

@ -151,6 +151,26 @@ CenteredGridView {
NavigableMenu { NavigableMenu {
id: pcContextMenu id: pcContextMenu
NavigableMenuItem {
parentMenu: pcContextMenu
text: "View Apps"
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: "View Hidden Apps"
onTriggered: {
var component = Qt.createComponent("AppView.qml")
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name, "showHiddenGames": true})
stackView.push(appView)
}
visible: model.online && model.paired
}
NavigableMenuItem { NavigableMenuItem {
parentMenu: pcContextMenu parentMenu: pcContextMenu
text: "Delete PC" text: "Delete PC"

View File

@ -7,7 +7,7 @@ AppModel::AppModel(QObject *parent)
this, &AppModel::handleBoxArtLoaded); this, &AppModel::handleBoxArtLoaded);
} }
void AppModel::initialize(ComputerManager* computerManager, int computerIndex) void AppModel::initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames)
{ {
m_ComputerManager = computerManager; m_ComputerManager = computerManager;
connect(m_ComputerManager, &ComputerManager::computerStateChanged, connect(m_ComputerManager, &ComputerManager::computerStateChanged,
@ -15,29 +15,23 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
Q_ASSERT(computerIndex < m_ComputerManager->getComputers().count()); Q_ASSERT(computerIndex < m_ComputerManager->getComputers().count());
m_Computer = m_ComputerManager->getComputers().at(computerIndex); m_Computer = m_ComputerManager->getComputers().at(computerIndex);
m_Apps = m_Computer->appList;
m_CurrentGameId = m_Computer->currentGameId; m_CurrentGameId = m_Computer->currentGameId;
m_ShowHiddenGames = showHiddenGames;
updateAppList(m_Computer->appList);
} }
int AppModel::getRunningAppIndex() int AppModel::getRunningAppId()
{ {
if (m_CurrentGameId != 0) { return m_CurrentGameId;
for (int i = 0; i < m_Apps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) {
return i;
}
}
}
return -1;
} }
QString AppModel::getRunningAppName() QString AppModel::getRunningAppName()
{ {
if (m_CurrentGameId != 0) { if (m_CurrentGameId != 0) {
for (int i = 0; i < m_Apps.count(); i++) { for (int i = 0; i < m_AllApps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) { if (m_AllApps[i].id == m_CurrentGameId) {
return m_Apps[i].name; return m_AllApps[i].name;
} }
} }
} }
@ -47,8 +41,8 @@ QString AppModel::getRunningAppName()
Session* AppModel::createSessionForApp(int appIndex) Session* AppModel::createSessionForApp(int appIndex)
{ {
Q_ASSERT(appIndex < m_Apps.count()); Q_ASSERT(appIndex < m_VisibleApps.count());
NvApp app = m_Apps.at(appIndex); NvApp app = m_VisibleApps.at(appIndex);
return new Session(m_Computer, app); return new Session(m_Computer, app);
} }
@ -60,7 +54,7 @@ int AppModel::rowCount(const QModelIndex &parent) const
if (parent.isValid()) if (parent.isValid())
return 0; return 0;
return m_Apps.count(); return m_VisibleApps.count();
} }
QVariant AppModel::data(const QModelIndex &index, int role) const QVariant AppModel::data(const QModelIndex &index, int role) const
@ -68,8 +62,8 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
Q_ASSERT(index.row() < m_Apps.count()); Q_ASSERT(index.row() < m_VisibleApps.count());
NvApp app = m_Apps.at(index.row()); NvApp app = m_VisibleApps.at(index.row());
switch (role) switch (role)
{ {
@ -80,6 +74,10 @@ QVariant AppModel::data(const QModelIndex &index, int role) const
case BoxArtRole: case BoxArtRole:
// FIXME: const-correctness // FIXME: const-correctness
return const_cast<BoxArtManager&>(m_BoxArtManager).loadBoxArt(m_Computer, app); return const_cast<BoxArtManager&>(m_BoxArtManager).loadBoxArt(m_Computer, app);
case HiddenRole:
return app.hidden;
case AppIdRole:
return app.id;
default: default:
return QVariant(); return QVariant();
} }
@ -92,6 +90,8 @@ QHash<int, QByteArray> AppModel::roleNames() const
names[NameRole] = "name"; names[NameRole] = "name";
names[RunningRole] = "running"; names[RunningRole] = "running";
names[BoxArtRole] = "boxart"; names[BoxArtRole] = "boxart";
names[HiddenRole] = "hidden";
names[AppIdRole] = "appid";
return names; return names;
} }
@ -101,6 +101,98 @@ void AppModel::quitRunningApp()
m_ComputerManager->quitRunningApp(m_Computer); m_ComputerManager->quitRunningApp(m_Computer);
} }
QVector<NvApp> AppModel::getVisibleApps(const QVector<NvApp>& appList)
{
QVector<NvApp> visibleApps;
for (const NvApp& app : appList) {
if (m_ShowHiddenGames || !app.hidden) {
visibleApps.append(app);
}
}
return visibleApps;
}
void AppModel::updateAppList(QVector<NvApp> newList)
{
m_AllApps = newList;
QVector<NvApp> newVisibleList = getVisibleApps(newList);
// Process removals and updates first
for (int i = 0; i < m_VisibleApps.count(); i++) {
const NvApp& existingApp = m_VisibleApps.at(i);
bool found = false;
for (const NvApp& newApp : newVisibleList) {
if (existingApp.id == newApp.id) {
// If the data changed, update it in our list
if (existingApp != newApp) {
m_VisibleApps.replace(i, newApp);
emit dataChanged(createIndex(i, 0), createIndex(i, 0));
}
found = true;
break;
}
}
if (!found) {
beginRemoveRows(QModelIndex(), i, i);
m_VisibleApps.removeAt(i);
endRemoveRows();
i--;
}
}
// Process additions now
for (const NvApp& newApp : newVisibleList) {
int insertionIndex = m_VisibleApps.size();
bool found = false;
for (int i = 0; i < m_VisibleApps.count(); i++) {
const NvApp& existingApp = m_VisibleApps.at(i);
if (existingApp.id == newApp.id) {
found = true;
break;
}
else if (existingApp.name.toLower() > newApp.name.toLower()) {
insertionIndex = i;
break;
}
}
if (!found) {
beginInsertRows(QModelIndex(), insertionIndex, insertionIndex);
m_VisibleApps.insert(insertionIndex, newApp);
endInsertRows();
}
}
Q_ASSERT(newVisibleList == m_VisibleApps);
}
void AppModel::setAppHidden(int appIndex, bool hidden)
{
Q_ASSERT(appIndex < m_VisibleApps.count());
int appId = m_VisibleApps.at(appIndex).id;
{
QWriteLocker lock(&m_Computer->lock);
for (NvApp& app : m_Computer->appList) {
if (app.id == appId) {
app.hidden = hidden;
break;
}
}
}
m_ComputerManager->clientSideAttributeUpdated(m_Computer);
}
void AppModel::handleComputerStateChanged(NvComputer* computer) void AppModel::handleComputerStateChanged(NvComputer* computer)
{ {
// Ignore updates for computers that aren't ours // Ignore updates for computers that aren't ours
@ -119,20 +211,15 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
// First, process additions/removals from the app list. This // First, process additions/removals from the app list. This
// is required because the new game may now be running, so // is required because the new game may now be running, so
// we can't check that first. // we can't check that first.
if (computer->appList != m_Apps) { if (computer->appList != m_AllApps) {
// Just reset the whole thing if the list changes updateAppList(computer->appList);
beginResetModel();
m_Apps = computer->appList;
m_CurrentGameId = computer->currentGameId;
endResetModel();
return;
} }
// Finally, process changes to the active app // Finally, process changes to the active app
if (computer->currentGameId != m_CurrentGameId) { if (computer->currentGameId != m_CurrentGameId) {
// First, invalidate the running state of newly running game // First, invalidate the running state of newly running game
for (int i = 0; i < m_Apps.count(); i++) { for (int i = 0; i < m_VisibleApps.count(); i++) {
if (m_Apps[i].id == computer->currentGameId) { if (m_VisibleApps[i].id == computer->currentGameId) {
emit dataChanged(createIndex(i, 0), emit dataChanged(createIndex(i, 0),
createIndex(i, 0), createIndex(i, 0),
QVector<int>() << RunningRole); QVector<int>() << RunningRole);
@ -142,8 +229,8 @@ void AppModel::handleComputerStateChanged(NvComputer* computer)
// Next, invalidate the running state of the old game (if it exists) // Next, invalidate the running state of the old game (if it exists)
if (m_CurrentGameId != 0) { if (m_CurrentGameId != 0) {
for (int i = 0; i < m_Apps.count(); i++) { for (int i = 0; i < m_VisibleApps.count(); i++) {
if (m_Apps[i].id == m_CurrentGameId) { if (m_VisibleApps[i].id == m_CurrentGameId) {
emit dataChanged(createIndex(i, 0), emit dataChanged(createIndex(i, 0),
createIndex(i, 0), createIndex(i, 0),
QVector<int>() << RunningRole); QVector<int>() << RunningRole);
@ -161,7 +248,7 @@ void AppModel::handleBoxArtLoaded(NvComputer* computer, NvApp app, QUrl /* image
{ {
Q_ASSERT(computer == m_Computer); Q_ASSERT(computer == m_Computer);
int index = m_Apps.indexOf(app); int index = m_VisibleApps.indexOf(app);
// Make sure we're not delivering a callback to an app that's already been removed // Make sure we're not delivering a callback to an app that's already been removed
if (index >= 0) { if (index >= 0) {

View File

@ -14,23 +14,27 @@ class AppModel : public QAbstractListModel
{ {
NameRole = Qt::UserRole, NameRole = Qt::UserRole,
RunningRole, RunningRole,
BoxArtRole BoxArtRole,
HiddenRole,
AppIdRole,
}; };
public: public:
explicit AppModel(QObject *parent = nullptr); explicit AppModel(QObject *parent = nullptr);
// Must be called before any QAbstractListModel functions // Must be called before any QAbstractListModel functions
Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex); Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex, bool showHiddenGames);
Q_INVOKABLE Session* createSessionForApp(int appIndex); Q_INVOKABLE Session* createSessionForApp(int appIndex);
Q_INVOKABLE int getRunningAppIndex(); Q_INVOKABLE int getRunningAppId();
Q_INVOKABLE QString getRunningAppName(); Q_INVOKABLE QString getRunningAppName();
Q_INVOKABLE void quitRunningApp(); Q_INVOKABLE void quitRunningApp();
Q_INVOKABLE void setAppHidden(int appIndex, bool hidden);
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override;
@ -46,9 +50,14 @@ signals:
void computerLost(); void computerLost();
private: private:
void updateAppList(QVector<NvApp> newList);
QVector<NvApp> getVisibleApps(const QVector<NvApp>& appList);
NvComputer* m_Computer; NvComputer* m_Computer;
BoxArtManager m_BoxArtManager; BoxArtManager m_BoxArtManager;
ComputerManager* m_ComputerManager; ComputerManager* m_ComputerManager;
QVector<NvApp> m_Apps; QVector<NvApp> m_VisibleApps, m_AllApps;
int m_CurrentGameId; int m_CurrentGameId;
bool m_ShowHiddenGames;
}; };