Start work on launching a stream from QML and rip out remaining QtWidgets

This commit is contained in:
Cameron Gutman 2018-07-07 16:30:26 -07:00
parent d5cc07f107
commit 60ad95bb7b
11 changed files with 83 additions and 55 deletions

View File

@ -1,9 +1,6 @@
QT += core quick network QT += core quick network
CONFIG += c++11 CONFIG += c++11
# TODO: Rid ourselves of QtWidgets
QT += widgets
TARGET = moonlight-qt TARGET = moonlight-qt
TEMPLATE = app TEMPLATE = app

View File

@ -327,9 +327,7 @@ private:
break; break;
} }
} catch (const GfeHttpResponseException& e) { } catch (const GfeHttpResponseException& e) {
emit pairingCompleted(m_Computer, emit pairingCompleted(m_Computer, e.toQString());
QString::fromLocal8Bit(e.getStatusMessage()) +
" (Error " + QString::number(e.getStatusCode()));
} }
} }

View File

@ -52,6 +52,11 @@ public:
return m_StatusCode; return m_StatusCode;
} }
QString toQString() const
{
return m_StatusMessage + " (Error " + QString::number(m_StatusCode) + ")";
}
private: private:
int m_StatusCode; int m_StatusCode;
QString m_StatusMessage; QString m_StatusMessage;

View File

@ -7,6 +7,7 @@ import ComputerManager 1.0
GridView { GridView {
property int computerIndex property int computerIndex
property AppModel appModel : createModel()
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 5 anchors.leftMargin: 5
@ -33,7 +34,7 @@ GridView {
return model return model
} }
model: createModel() model: appModel
delegate: Item { delegate: Item {
width: 200; height: 300; width: 200; height: 300;
@ -64,7 +65,8 @@ GridView {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
parent.GridView.view.currentIndex = index var session = appModel.createSessionForGame(index);
session.exec();
} }
} }
} }

View File

@ -19,6 +19,14 @@ void AppModel::initialize(ComputerManager* computerManager, int computerIndex)
m_CurrentGameId = m_Computer->currentGameId; 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 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 // For list models only the root node (an invalid parent) should return the list's size. For all

View File

@ -2,6 +2,7 @@
#include "backend/boxartmanager.h" #include "backend/boxartmanager.h"
#include "backend/computermanager.h" #include "backend/computermanager.h"
#include "streaming/session.hpp"
#include <QAbstractListModel> #include <QAbstractListModel>
@ -22,6 +23,8 @@ public:
// 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);
Q_INVOKABLE Session* createSessionForApp(int appIndex);
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;

View File

@ -78,6 +78,26 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
return names; 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) void ComputerModel::deleteComputer(int computerIndex)
{ {
Q_ASSERT(computerIndex < m_Computers.count()); Q_ASSERT(computerIndex < m_Computers.count());

View File

@ -1,4 +1,5 @@
#include "backend/computermanager.h" #include "backend/computermanager.h"
#include "streaming/session.hpp"
#include <QAbstractListModel> #include <QAbstractListModel>
@ -33,6 +34,8 @@ public:
Q_INVOKABLE bool wakeComputer(int computerIndex); Q_INVOKABLE bool wakeComputer(int computerIndex);
Q_INVOKABLE Session* createSessionForCurrentGame(int computerIndex);
signals: signals:
void pairingCompleted(QVariant error); void pairingCompleted(QVariant error);

View File

@ -3,6 +3,7 @@
#include "gui/computermodel.h" #include "gui/computermodel.h"
#include "gui/appmodel.h" #include "gui/appmodel.h"
#include "streaming/session.hpp"
// Don't let SDL hook our main function, since Qt is already // Don't let SDL hook our main function, since Qt is already
// doing the same thing // doing the same thing
@ -30,6 +31,7 @@ int main(int argc, char *argv[])
// Register our C++ types for QML // Register our C++ types for QML
qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel"); qmlRegisterType<ComputerModel>("ComputerModel", 1, 0, "ComputerModel");
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel"); qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0, qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
"ComputerManager", "ComputerManager",
[](QQmlEngine*, QJSEngine*) -> QObject* { [](QQmlEngine*, QJSEngine*) -> QObject* {

View File

@ -5,7 +5,6 @@
#include <SDL.h> #include <SDL.h>
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QMessageBox>
#include <QtEndian> #include <QtEndian>
#include <QCoreApplication> #include <QCoreApplication>
@ -35,28 +34,19 @@ Session* Session::s_ActiveSession;
void Session::clStageStarting(int stage) 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() // We know this is called on the same thread as LiStartConnection()
// which happens to be the main thread, so it's cool to interact // which happens to be the main thread, so it's cool to interact
// with the GUI in these callbacks. // with the GUI in these callbacks.
s_ActiveSession->m_ProgressBox.setText(buffer); emit s_ActiveSession->stageStarting(QString::fromLocal8Bit(LiGetStageName(stage)));
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
} }
void Session::clStageFailed(int stage, long errorCode) 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() // We know this is called on the same thread as LiStartConnection()
// which happens to be the main thread, so it's cool to interact // which happens to be the main thread, so it's cool to interact
// with the GUI in these callbacks. // with the GUI in these callbacks.
emit s_ActiveSession->stageFailed(QString::fromLocal8Bit(LiGetStageName(stage)), errorCode);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
} }
@ -87,8 +77,7 @@ void Session::clLogMessage(const char* format, ...)
Session::Session(NvComputer* computer, NvApp& app) Session::Session(NvComputer* computer, NvApp& app)
: m_Computer(computer), : m_Computer(computer),
m_App(app), m_App(app)
m_ProgressBox(nullptr)
{ {
StreamingPreferences prefs; StreamingPreferences prefs;
@ -138,13 +127,7 @@ Session::Session(NvComputer* computer, NvApp& app)
} }
} }
QString Session::checkForFatalValidationError() bool Session::validateLaunch()
{
// Nothing here yet
return nullptr;
}
QStringList Session::checkForAdvisoryValidationError()
{ {
NvHTTP http(m_Computer->activeAddress); NvHTTP http(m_Computer->activeAddress);
QStringList warningList; QStringList warningList;
@ -155,12 +138,12 @@ QStringList Session::checkForAdvisoryValidationError()
// Check that the app supports HDR // Check that the app supports HDR
if (!m_App.hdrSupported) { 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 // Check that the server GPU supports HDR
else if (!(m_Computer->serverCodecModeSupport & 0x200)) { else if (!(m_Computer->serverCodecModeSupport & 0x200)) {
warningList.append("Your host PC GPU doesn't support 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."); "A GeForce GTX 1000-series (Pascal) or later GPU is required for HDR streaming.");
} }
else { else {
// TODO: Also validate client decoder and display capabilites // TODO: Also validate client decoder and display capabilites
@ -183,11 +166,21 @@ QStringList Session::checkForAdvisoryValidationError()
// TODO: Validate HEVC support based on decoder caps // TODO: Validate HEVC support based on decoder caps
} }
return warningList; // Always allow the launch to proceed for now
return true;
} }
void Session::exec() 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 // We're now active
s_ActiveSession = this; s_ActiveSession = this;
@ -195,21 +188,13 @@ void Session::exec()
StreamingPreferences prefs; StreamingPreferences prefs;
SdlInputHandler inputHandler(prefs.multiController); 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 // The UI should have ensured the old game was already quit
// if we decide to stream a different game. // if we decide to stream a different game.
Q_ASSERT(m_Computer->currentGameId == 0 || Q_ASSERT(m_Computer->currentGameId == 0 ||
m_Computer->currentGameId == m_App.id); m_Computer->currentGameId == m_App.id);
try { try {
NvHTTP http(m_Computer->activeAddress);
if (m_Computer->currentGameId != 0) { if (m_Computer->currentGameId != 0) {
http.resumeApp(&m_StreamConfig); http.resumeApp(&m_StreamConfig);
} }
@ -220,12 +205,10 @@ void Session::exec()
inputHandler.getAttachedGamepadMask()); inputHandler.getAttachedGamepadMask());
} }
} catch (const GfeHttpResponseException& e) { } catch (const GfeHttpResponseException& e) {
m_ProgressBox.close(); emit displayLaunchError(e.toQString());
// TODO: display error dialog
return; return;
} }
QByteArray hostnameStr = m_Computer->activeAddress.toLatin1(); QByteArray hostnameStr = m_Computer->activeAddress.toLatin1();
QByteArray siAppVersion = m_Computer->appVersion.toLatin1(); QByteArray siAppVersion = m_Computer->appVersion.toLatin1();
@ -251,9 +234,8 @@ void Session::exec()
return; return;
} }
// Before we get into our SDL loop, close the message box used to // Pump the message loop to update the UI
// display progress emit connectionStarted();
m_ProgressBox.close();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
SDL_Window* wnd = SDL_CreateWindow("SDL Test Window", 0, 0, 1280, 720, SDL_WINDOW_INPUT_GRABBED); SDL_Window* wnd = SDL_CreateWindow("SDL Test Window", 0, 0, 1280, 720, SDL_WINDOW_INPUT_GRABBED);

View File

@ -5,20 +5,29 @@
#include "backend/computermanager.h" #include "backend/computermanager.h"
#include "input.hpp" #include "input.hpp"
#include <QMessageBox> class Session : public QObject
class Session
{ {
Q_OBJECT
public: public:
explicit Session(NvComputer* computer, NvApp& app); 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: private:
bool validateLaunch();
static static
void clStageStarting(int stage); void clStageStarting(int stage);
@ -54,7 +63,6 @@ private:
STREAM_CONFIGURATION m_StreamConfig; STREAM_CONFIGURATION m_StreamConfig;
NvComputer* m_Computer; NvComputer* m_Computer;
NvApp m_App; NvApp m_App;
QMessageBox m_ProgressBox;
static SDL_AudioDeviceID s_AudioDevice; static SDL_AudioDeviceID s_AudioDevice;
static OpusMSDecoder* s_OpusDecoder; static OpusMSDecoder* s_OpusDecoder;