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
CONFIG += c++11
# TODO: Rid ourselves of QtWidgets
QT += widgets
TARGET = moonlight-qt
TEMPLATE = app

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -2,6 +2,7 @@
#include "backend/boxartmanager.h"
#include "backend/computermanager.h"
#include "streaming/session.hpp"
#include <QAbstractListModel>
@ -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;

View File

@ -78,6 +78,26 @@ QHash<int, QByteArray> 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());

View File

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

View File

@ -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>("ComputerModel", 1, 0, "ComputerModel");
qmlRegisterType<AppModel>("AppModel", 1, 0, "AppModel");
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
"ComputerManager",
[](QQmlEngine*, QJSEngine*) -> QObject* {

View File

@ -5,7 +5,6 @@
#include <SDL.h>
#include <QRandomGenerator>
#include <QMessageBox>
#include <QtEndian>
#include <QCoreApplication>
@ -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);

View File

@ -5,20 +5,29 @@
#include "backend/computermanager.h"
#include "input.hpp"
#include <QMessageBox>
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;