diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index 2d0bdf3f..4b1970f0 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -1,7 +1,6 @@ #include "computermanager.h" #include "nvhttp.h" #include "settings/streamingpreferences.h" -#include "streaming/session.h" #include #include @@ -479,11 +478,6 @@ void ComputerManager::quitRunningApp(NvComputer* computer) QThreadPool::globalInstance()->start(quit); } -void ComputerManager::quitRunningApp(Session *session) -{ - quitRunningApp(session->getComputer()); -} - void ComputerManager::stopPollingAsync() { QWriteLocker lock(&m_Lock); diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 2be9a95a..18f6621a 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -14,8 +14,6 @@ #include #include -class Session; - class MdnsPendingComputer : public QObject { Q_OBJECT @@ -74,7 +72,6 @@ public: void pairHost(NvComputer* computer, QString pin); void quitRunningApp(NvComputer* computer); - Q_INVOKABLE void quitRunningApp(Session* session); QVector getComputers(); diff --git a/app/backend/nvhttp.cpp b/app/backend/nvhttp.cpp index 248862f3..3cb713ea 100644 --- a/app/backend/nvhttp.cpp +++ b/app/backend/nvhttp.cpp @@ -57,13 +57,8 @@ NvHTTP::getCurrentGame(QString serverInfo) // GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer // has the semantics that its name would indicate. To contain the effects of this change as much // as possible, we'll force the current game to zero if the server isn't in a streaming session. - // - // However, current game info must be available also in other states than just _SERVER_BUSY as - // it is required for quitting currently running app. Quitting app occurs at end of stream if - // configured so. At that point the server state may be in some other state than _SERVER_BUSY - // for a short while, but that must not prevent quitting of the app. QString serverState = getXmlString(serverInfo, "state"); - if (serverState != nullptr && !serverState.endsWith("_SERVER_AVAILABLE")) + if (serverState != nullptr && serverState.endsWith("_SERVER_BUSY")) { return getXmlString(serverInfo, "currentgame").toInt(); } diff --git a/app/gui/StreamSegue.qml b/app/gui/StreamSegue.qml index 20a34e5b..2edc941d 100644 --- a/app/gui/StreamSegue.qml +++ b/app/gui/StreamSegue.qml @@ -12,7 +12,6 @@ Item { property string appName property string stageText : "Starting " + appName + "..." property bool quitAfter : false - property bool sessionLaunched : false anchors.fill: parent @@ -61,17 +60,27 @@ Item { toast.visible = true } - function streamingFinished() { + function quitStarting() + { + // Avoid the push transition animation + var component = Qt.createComponent("QuitSegue.qml") + stackView.replace(stackView.currentItem, component.createObject(stackView, {"appName": appName}), StackView.Immediate) + + // Show the Qt window again to show quit segue + window.visible = true + } + + function sessionFinished() + { if (quitAfter) { - window.visible = false Qt.quit() } else { - // Show the Qt window again after streaming - window.visible = true - // Exit this view stackView.pop() + // Show the Qt window again after streaming + window.visible = true + // Display any launch errors. We do this after // the Qt UI is visible again to prevent losing // focus on the dialog which would impact gamepad @@ -91,12 +100,6 @@ Item { onVisibleChanged: { if (visible) { - // Prevent session restart after execution returns from QuitSegue - if (sessionLaunched) { - return - } - sessionLaunched = true - // Hide the toolbar before we start loading toolBar.visible = false @@ -114,21 +117,11 @@ Item { session.connectionStarted.connect(connectionStarted) session.displayLaunchError.connect(displayLaunchError) session.displayLaunchWarning.connect(displayLaunchWarning) + session.quitStarting.connect(quitStarting) + session.sessionFinished.connect(sessionFinished) // Run the streaming session to completion - session.exec(Screen.virtualX, Screen.virtualY); - - if (!errorDialog.text && session.shouldQuitAppAfter()) { - // Show the Qt window again to show quit segue - window.visible = true - var component = Qt.createComponent("QuitSegue.qml") - stackView.push(component.createObject(stackView, {"appName": appName})) - // Quit app - ComputerManager.quitAppCompleted.connect(streamingFinished) - ComputerManager.quitRunningApp(session) - } else { - streamingFinished() - } + session.exec(Screen.virtualX, Screen.virtualY) } else if (!quitAfter) { // Show the toolbar again when we become hidden diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index d591b39e..5a3f316d 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -275,16 +275,6 @@ int Session::getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection return caps; } -NvComputer *Session::getComputer() const -{ - return m_Computer; -} - -bool Session::shouldQuitAppAfter() const -{ - return m_Preferences->quitAppAfter; -} - Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences) : m_Preferences(preferences ? preferences : new StreamingPreferences(this)), m_Computer(computer), @@ -303,6 +293,14 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere { } +Session::~Session() +{ + // Acquire session semaphore to ensure all cleanup is done before the destructor returns + // and the object is deallocated. + s_ActiveSessionSemaphore.acquire(); + s_ActiveSessionSemaphore.release(); +} + void Session::initialize() { qInfo() << "Server GPU:" << m_Computer->gpuModel; @@ -561,15 +559,48 @@ bool Session::validateLaunch() class DeferredSessionCleanupTask : public QRunnable { - void run() override - { - // Finish cleanup of the connection state - LiStopConnection(); +public: + DeferredSessionCleanupTask(Session* session) : + m_Session(session) {} +private: + virtual ~DeferredSessionCleanupTask() override + { // Allow another session to start now that we're cleaned up Session::s_ActiveSession = nullptr; Session::s_ActiveSessionSemaphore.release(); } + + void run() override + { + // Notify the UI + if (!m_Session->m_Preferences->quitAppAfter) { + emit m_Session->sessionFinished(); + } + else { + emit m_Session->quitStarting(); + } + + // Finish cleanup of the connection state + LiStopConnection(); + + // Perform a best-effort app quit + if (m_Session->m_Preferences->quitAppAfter) { + NvHTTP http(m_Session->m_Computer->activeAddress); + + // Logging is already done inside NvHTTP + try { + http.quitApp(); + } catch (const GfeHttpResponseException&) { + } catch (const QtNetworkReplyException&) { + } + + // Session is finished now + emit m_Session->sessionFinished(); + } + } + + Session* m_Session; }; void Session::getWindowDimensions(int& x, int& y, @@ -1162,6 +1193,6 @@ DispatchDeferredCleanup: // Cleanup can take a while, so dispatch it to a worker thread. // When it is complete, it will release our s_ActiveSessionSemaphore // reference. - QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask()); + QThreadPool::globalInstance()->start(new DeferredSessionCleanupTask(this)); } diff --git a/app/streaming/session.h b/app/streaming/session.h index 63dfb49d..965fc4ec 100644 --- a/app/streaming/session.h +++ b/app/streaming/session.h @@ -20,6 +20,8 @@ class Session : public QObject public: explicit Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences = nullptr); + virtual ~Session(); + Q_INVOKABLE void exec(int displayOriginX, int displayOriginY); static @@ -30,10 +32,6 @@ public: int getDecoderCapabilities(StreamingPreferences::VideoDecoderSelection vds, int videoFormat, int width, int height, int frameRate); - NvComputer* getComputer() const; - - Q_INVOKABLE bool shouldQuitAppAfter() const; - signals: void stageStarting(QString stage); @@ -45,6 +43,10 @@ signals: void displayLaunchWarning(QString text); + void quitStarting(); + + void sessionFinished(); + private: void initialize();