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