diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp
index c3cc9996..bfa12c6e 100644
--- a/app/backend/computermanager.cpp
+++ b/app/backend/computermanager.cpp
@@ -51,6 +51,7 @@ NvComputer::NvComputer(QSettings& settings)
this->appVersion = nullptr;
this->maxLumaPixelsHEVC = 0;
this->serverCodecModeSupport = 0;
+ this->pendingQuit = false;
}
void
@@ -135,6 +136,7 @@ NvComputer::NvComputer(QString address, QString serverInfo)
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
this->activeAddress = address;
this->state = NvComputer::CS_ONLINE;
+ this->pendingQuit = false;
}
bool NvComputer::wake()
@@ -508,7 +510,7 @@ ComputerManager::handleComputerStateChanged(NvComputer* computer)
if (computer->pendingQuit && computer->currentGameId == 0) {
computer->pendingQuit = false;
- emit quitAppCompleted(nullptr);
+ emit quitAppCompleted(QVariant());
}
// Save updated hosts to QSettings
diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h
index 06aae502..0c824edc 100644
--- a/app/backend/computermanager.h
+++ b/app/backend/computermanager.h
@@ -272,7 +272,7 @@ signals:
void computerAddCompleted(QVariant success);
- void quitAppCompleted(QString error);
+ void quitAppCompleted(QVariant error);
private slots:
void handleComputerStateChanged(NvComputer* computer);
diff --git a/app/gui/AppView.qml b/app/gui/AppView.qml
index e029932c..d4f268a1 100644
--- a/app/gui/AppView.qml
+++ b/app/gui/AppView.qml
@@ -1,4 +1,5 @@
import QtQuick 2.9
+import QtQuick.Dialogs 1.3
import QtQuick.Controls 2.2
import AppModel 1.0
@@ -50,12 +51,6 @@ GridView {
delegate: Item {
width: 200; height: 300;
- Component.onCompleted: {
- if (model.running) {
- appNameText.text = "Running - " + appNameText.text
- }
- }
-
Image {
id: appIcon
anchors.horizontalCenter: parent.horizontalCenter;
@@ -68,7 +63,15 @@ GridView {
Text {
id: appNameText
- text: model.name
+ text: {
+ if (model.running) {
+ return "Running - " + model.name
+ }
+ else {
+ return model.name
+ }
+ }
+
color: "white"
width: parent.width
@@ -80,16 +83,86 @@ GridView {
elide: Text.ElideRight
}
+ function launchOrResumeSelectedApp()
+ {
+ var runningIndex = appModel.getRunningAppIndex()
+ if (runningIndex >= 0 && runningIndex !== index) {
+ quitAppDialog.appName = appModel.getRunningAppName()
+ quitAppDialog.segueToStream = true
+ quitAppDialog.open()
+ return
+ }
+
+ var component = Qt.createComponent("StreamSegue.qml")
+ var segue = component.createObject(stackView)
+ segue.appName = model.name
+ segue.session = appModel.createSessionForApp(index)
+ stackView.push(segue)
+ }
+
+ MessageDialog {
+ id: quitAppDialog
+ modality:Qt.WindowModal
+ property string appName : "";
+ property bool segueToStream : false
+ text:"Are you sure you want to quit " + appName +"? Any unsaved progress will be lost."
+ standardButtons: StandardButton.Yes | StandardButton.No
+ onYes: {
+ var component = Qt.createComponent("QuitSegue.qml")
+ var segue = component.createObject(stackView)
+ segue.appName = appName
+ if (segueToStream) {
+ // Store the session and app name if we're going to stream after
+ // successfully quitting the old app.
+ segue.nextAppName = model.name
+ segue.nextSession = appModel.createSessionForApp(index)
+ }
+ else {
+ segue.nextAppName = null
+ segue.nextSession = null
+ }
+
+ stackView.push(segue)
+
+ // Trigger the quit after pushing the quit segue on screen
+ appModel.quitRunningApp()
+ }
+ }
+
MouseArea {
anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
- // TODO: Check if a different game is running
+ if (mouse.button === Qt.LeftButton) {
+ // Nothing is running or this app is running
+ launchOrResumeSelectedApp()
+ }
+ else {
+ // Right click
+ appContextMenu.open()
+ }
+ }
+ }
- var component = Qt.createComponent("StreamSegue.qml")
- var segue = component.createObject(stackView)
- segue.appname = model.name
- segue.session = appModel.createSessionForApp(index)
- stackView.push(segue)
+ Menu {
+ id: appContextMenu
+ MenuItem {
+ text: model.running ? "Resume Game" : "Launch Game"
+ onTriggered: {
+ appContextMenu.close()
+ launchOrResumeSelectedApp()
+ }
+ height: visible ? implicitHeight : 0
+ }
+ MenuItem {
+ text: "Quit Game"
+ onTriggered: {
+ quitAppDialog.appName = appModel.getRunningAppName()
+ quitAppDialog.segueToStream = false
+ quitAppDialog.open()
+ }
+ visible: model.running
+ height: visible ? implicitHeight : 0
}
}
}
diff --git a/app/gui/QuitSegue.qml b/app/gui/QuitSegue.qml
new file mode 100644
index 00000000..5ef6bcc5
--- /dev/null
+++ b/app/gui/QuitSegue.qml
@@ -0,0 +1,86 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.2
+import QtQuick.Dialogs 1.3
+
+import ComputerManager 1.0
+import Session 1.0
+
+Item {
+ property string appName
+ property Session nextSession : null
+ property string nextAppName : ""
+
+ property string stageText : "Quitting " + appName + "..."
+
+ anchors.fill: parent
+
+ // The StackView will trigger a visibility change when
+ // we're pushed onto it, causing our onVisibleChanged
+ // routine to run, but only if we start as invisible
+ visible: false
+
+ function quitAppCompleted(error)
+ {
+ // Display a failed dialog if we got an error
+ if (error !== undefined) {
+ errorDialog.text = error
+ errorDialog.open()
+ }
+
+ // Exit this view
+ stackView.pop()
+
+ // If we're supposed to launch another game after this, do so now
+ if (error === undefined && nextSession !== null) {
+ var component = Qt.createComponent("StreamSegue.qml")
+ var segue = component.createObject(stackView)
+ segue.appName = nextAppName
+ segue.session = nextSession
+ stackView.push(segue)
+ }
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ // Connect the quit completion signal
+ ComputerManager.quitAppCompleted.connect(quitAppCompleted)
+
+ // We must be polling or we won't get the quitAppCompleted() callback
+ ComputerManager.startPolling()
+ }
+ else {
+ // Disconnect the signal
+ ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
+
+ // Stop polling when this view is not on top
+ ComputerManager.stopPollingAsync()
+ }
+ }
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 5
+
+ BusyIndicator {
+ id: stageSpinner
+ }
+
+ Label {
+ id: stageLabel
+ height: stageSpinner.height
+ text: stageText
+ font.pointSize: 20
+ verticalAlignment: Text.AlignVCenter
+
+ wrapMode: Text.Wrap
+ color: "white"
+ }
+ }
+
+ MessageDialog {
+ id: errorDialog
+ modality:Qt.WindowModal
+ icon: StandardIcon.Critical
+ standardButtons: StandardButton.Ok
+ }
+}
diff --git a/app/gui/StreamSegue.qml b/app/gui/StreamSegue.qml
index f6ce90ec..02b38711 100644
--- a/app/gui/StreamSegue.qml
+++ b/app/gui/StreamSegue.qml
@@ -6,8 +6,8 @@ import Session 1.0
Item {
property Session session
- property string appname
- property string stageText : "Starting " + appname + "..."
+ property string appName
+ property string stageText : "Starting " + appName + "..."
anchors.fill: parent
diff --git a/app/qml.qrc b/app/qml.qrc
index 7e42f481..8d59d670 100644
--- a/app/qml.qrc
+++ b/app/qml.qrc
@@ -6,5 +6,6 @@
gui/SettingsView.qml
gui/StreamSegue.qml
gui/GamepadMapper.qml
+ gui/QuitSegue.qml