From 60ad95bb7b8ac6d754b7becce1de90b283ef34cc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 7 Jul 2018 16:30:26 -0700 Subject: [PATCH] Start work on launching a stream from QML and rip out remaining QtWidgets --- app/app.pro | 3 -- app/backend/computermanager.h | 4 +-- app/backend/nvhttp.h | 5 +++ app/gui/AppView.qml | 6 ++-- app/gui/appmodel.cpp | 8 +++++ app/gui/appmodel.h | 3 ++ app/gui/computermodel.cpp | 20 +++++++++++ app/gui/computermodel.h | 3 ++ app/main.cpp | 2 ++ app/streaming/session.cpp | 62 +++++++++++++---------------------- app/streaming/session.hpp | 22 +++++++++---- 11 files changed, 83 insertions(+), 55 deletions(-) diff --git a/app/app.pro b/app/app.pro index e28c3c84..748a0e6b 100644 --- a/app/app.pro +++ b/app/app.pro @@ -1,9 +1,6 @@ QT += core quick network CONFIG += c++11 -# TODO: Rid ourselves of QtWidgets -QT += widgets - TARGET = moonlight-qt TEMPLATE = app diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 359073d4..74d62b52 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -327,9 +327,7 @@ private: break; } } catch (const GfeHttpResponseException& e) { - emit pairingCompleted(m_Computer, - QString::fromLocal8Bit(e.getStatusMessage()) + - " (Error " + QString::number(e.getStatusCode())); + emit pairingCompleted(m_Computer, e.toQString()); } } diff --git a/app/backend/nvhttp.h b/app/backend/nvhttp.h index f179302d..4730d66b 100644 --- a/app/backend/nvhttp.h +++ b/app/backend/nvhttp.h @@ -52,6 +52,11 @@ public: return m_StatusCode; } + QString toQString() const + { + return m_StatusMessage + " (Error " + QString::number(m_StatusCode) + ")"; + } + private: int m_StatusCode; QString m_StatusMessage; diff --git a/app/gui/AppView.qml b/app/gui/AppView.qml index 70c73d5e..5953b1ed 100644 --- a/app/gui/AppView.qml +++ b/app/gui/AppView.qml @@ -7,6 +7,7 @@ import ComputerManager 1.0 GridView { property int computerIndex + property AppModel appModel : createModel() anchors.fill: parent anchors.leftMargin: 5 @@ -33,7 +34,7 @@ GridView { return model } - model: createModel() + model: appModel delegate: Item { width: 200; height: 300; @@ -64,7 +65,8 @@ GridView { MouseArea { anchors.fill: parent onClicked: { - parent.GridView.view.currentIndex = index + var session = appModel.createSessionForGame(index); + session.exec(); } } } diff --git a/app/gui/appmodel.cpp b/app/gui/appmodel.cpp index 5bf5d88a..989d09dc 100644 --- a/app/gui/appmodel.cpp +++ b/app/gui/appmodel.cpp @@ -19,6 +19,14 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex) m_CurrentGameId = m_Computer->currentGameId; } +Session* AppModel::createSessionForApp(int appIndex) +{ + Q_ASSERT(appIndex < m_Apps.count()); + NvApp app = m_Apps.at(appIndex); + + return new Session(m_Computer, app); +} + int AppModel::rowCount(const QModelIndex &parent) const { // For list models only the root node (an invalid parent) should return the list's size. For all diff --git a/app/gui/appmodel.h b/app/gui/appmodel.h index a74de1b0..87e9bcce 100644 --- a/app/gui/appmodel.h +++ b/app/gui/appmodel.h @@ -2,6 +2,7 @@ #include "backend/boxartmanager.h" #include "backend/computermanager.h" +#include "streaming/session.hpp" #include @@ -22,6 +23,8 @@ public: // Must be called before any QAbstractListModel functions Q_INVOKABLE void initialize(ComputerManager* computerManager, int computerIndex); + Q_INVOKABLE Session* createSessionForApp(int appIndex); + QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent) const override; diff --git a/app/gui/computermodel.cpp b/app/gui/computermodel.cpp index 2f13ddab..f1dcb29c 100644 --- a/app/gui/computermodel.cpp +++ b/app/gui/computermodel.cpp @@ -78,6 +78,26 @@ QHash ComputerModel::roleNames() const return names; } +Session* ComputerModel::createSessionForCurrentGame(int computerIndex) +{ + Q_ASSERT(computerIndex < m_Computers.count()); + + NvComputer* computer = m_Computers[computerIndex]; + + // We must currently be streaming a game to use this function + Q_ASSERT(computer->currentGameId != 0); + + for (NvApp& app : computer->appList) { + if (app.id == computer->currentGameId) { + return new Session(computer, app); + } + } + + // We have a current running app but it's not in our app list + Q_ASSERT(false); + return nullptr; +} + void ComputerModel::deleteComputer(int computerIndex) { Q_ASSERT(computerIndex < m_Computers.count()); diff --git a/app/gui/computermodel.h b/app/gui/computermodel.h index d1db61dd..2b076cfe 100644 --- a/app/gui/computermodel.h +++ b/app/gui/computermodel.h @@ -1,4 +1,5 @@ #include "backend/computermanager.h" +#include "streaming/session.hpp" #include @@ -33,6 +34,8 @@ public: Q_INVOKABLE bool wakeComputer(int computerIndex); + Q_INVOKABLE Session* createSessionForCurrentGame(int computerIndex); + signals: void pairingCompleted(QVariant error); diff --git a/app/main.cpp b/app/main.cpp index eeb4ecf3..ee2fd06b 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -3,6 +3,7 @@ #include "gui/computermodel.h" #include "gui/appmodel.h" +#include "streaming/session.hpp" // Don't let SDL hook our main function, since Qt is already // doing the same thing @@ -30,6 +31,7 @@ int main(int argc, char *argv[]) // Register our C++ types for QML qmlRegisterType("ComputerModel", 1, 0, "ComputerModel"); qmlRegisterType("AppModel", 1, 0, "AppModel"); + qmlRegisterUncreatableType("Session", 1, 0, "Session", "Session cannot be created from QML"); qmlRegisterSingletonType("ComputerManager", 1, 0, "ComputerManager", [](QQmlEngine*, QJSEngine*) -> QObject* { diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 35ba4b0c..2b71f949 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -35,28 +34,19 @@ Session* Session::s_ActiveSession; void Session::clStageStarting(int stage) { - char buffer[512]; - sprintf(buffer, "Starting %s...", LiGetStageName(stage)); - // We know this is called on the same thread as LiStartConnection() // which happens to be the main thread, so it's cool to interact // with the GUI in these callbacks. - s_ActiveSession->m_ProgressBox.setText(buffer); + emit s_ActiveSession->stageStarting(QString::fromLocal8Bit(LiGetStageName(stage))); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } void Session::clStageFailed(int stage, long errorCode) { - s_ActiveSession->m_ProgressBox.close(); - - // TODO: Open error dialog - char buffer[512]; - sprintf(buffer, "Failed %s with error: %ld", - LiGetStageName(stage), errorCode); - // We know this is called on the same thread as LiStartConnection() // which happens to be the main thread, so it's cool to interact // with the GUI in these callbacks. + emit s_ActiveSession->stageFailed(QString::fromLocal8Bit(LiGetStageName(stage)), errorCode); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } @@ -87,8 +77,7 @@ void Session::clLogMessage(const char* format, ...) Session::Session(NvComputer* computer, NvApp& app) : m_Computer(computer), - m_App(app), - m_ProgressBox(nullptr) + m_App(app) { StreamingPreferences prefs; @@ -138,13 +127,7 @@ Session::Session(NvComputer* computer, NvApp& app) } } -QString Session::checkForFatalValidationError() -{ - // Nothing here yet - return nullptr; -} - -QStringList Session::checkForAdvisoryValidationError() +bool Session::validateLaunch() { NvHTTP http(m_Computer->activeAddress); QStringList warningList; @@ -155,12 +138,12 @@ QStringList Session::checkForAdvisoryValidationError() // Check that the app supports HDR if (!m_App.hdrSupported) { - warningList.append(m_App.name + " doesn't support HDR10."); + emit displayLaunchWarning(m_App.name + " doesn't support HDR10."); } // Check that the server GPU supports HDR else if (!(m_Computer->serverCodecModeSupport & 0x200)) { - warningList.append("Your host PC GPU doesn't support HDR streaming. " - "A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming."); + emit displayLaunchWarning("Your host PC GPU doesn't support HDR streaming. " + "A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming."); } else { // TODO: Also validate client decoder and display capabilites @@ -183,11 +166,21 @@ QStringList Session::checkForAdvisoryValidationError() // TODO: Validate HEVC support based on decoder caps } - return warningList; + // Always allow the launch to proceed for now + return true; } void Session::exec() { + // Check for validation errors/warnings and emit + // signals for them, if appropriate + if (!validateLaunch()) { + return; + } + + // Manually pump the UI thread for the view + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + // We're now active s_ActiveSession = this; @@ -195,21 +188,13 @@ void Session::exec() StreamingPreferences prefs; SdlInputHandler inputHandler(prefs.multiController); - m_ProgressBox.setStandardButtons(QMessageBox::Cancel); - m_ProgressBox.setText("Launching "+m_App.name+"..."); - m_ProgressBox.open(); - - // Ensure the progress box is immediately visible - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - - NvHTTP http(m_Computer->activeAddress); - // The UI should have ensured the old game was already quit // if we decide to stream a different game. Q_ASSERT(m_Computer->currentGameId == 0 || m_Computer->currentGameId == m_App.id); try { + NvHTTP http(m_Computer->activeAddress); if (m_Computer->currentGameId != 0) { http.resumeApp(&m_StreamConfig); } @@ -220,12 +205,10 @@ void Session::exec() inputHandler.getAttachedGamepadMask()); } } catch (const GfeHttpResponseException& e) { - m_ProgressBox.close(); - // TODO: display error dialog + emit displayLaunchError(e.toQString()); return; } - QByteArray hostnameStr = m_Computer->activeAddress.toLatin1(); QByteArray siAppVersion = m_Computer->appVersion.toLatin1(); @@ -251,9 +234,8 @@ void Session::exec() return; } - // Before we get into our SDL loop, close the message box used to - // display progress - m_ProgressBox.close(); + // Pump the message loop to update the UI + emit connectionStarted(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); SDL_Window* wnd = SDL_CreateWindow("SDL Test Window", 0, 0, 1280, 720, SDL_WINDOW_INPUT_GRABBED); diff --git a/app/streaming/session.hpp b/app/streaming/session.hpp index e35bb96d..052a3add 100644 --- a/app/streaming/session.hpp +++ b/app/streaming/session.hpp @@ -5,20 +5,29 @@ #include "backend/computermanager.h" #include "input.hpp" -#include - -class Session +class Session : public QObject { + Q_OBJECT + public: explicit Session(NvComputer* computer, NvApp& app); - QString checkForFatalValidationError(); + Q_INVOKABLE void exec(); - QStringList checkForAdvisoryValidationError(); +signals: + void stageStarting(QString stage); - void exec(); + void stageFailed(QString stage, long errorCode); + + void connectionStarted(); + + void displayLaunchError(QString text); + + void displayLaunchWarning(QString text); private: + bool validateLaunch(); + static void clStageStarting(int stage); @@ -54,7 +63,6 @@ private: STREAM_CONFIGURATION m_StreamConfig; NvComputer* m_Computer; NvApp m_App; - QMessageBox m_ProgressBox; static SDL_AudioDeviceID s_AudioDevice; static OpusMSDecoder* s_OpusDecoder;