mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-06-17 14:11:33 +00:00
Refactor ComputerManager and BoxArtManager to get code out of headers and split NvComputer into its own file
This commit is contained in:
@@ -92,6 +92,7 @@ macx {
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
backend/identitymanager.cpp \
|
backend/identitymanager.cpp \
|
||||||
|
backend/nvcomputer.cpp \
|
||||||
backend/nvhttp.cpp \
|
backend/nvhttp.cpp \
|
||||||
backend/nvpairingmanager.cpp \
|
backend/nvpairingmanager.cpp \
|
||||||
backend/computermanager.cpp \
|
backend/computermanager.cpp \
|
||||||
@@ -109,6 +110,7 @@ SOURCES += \
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
utils.h \
|
utils.h \
|
||||||
backend/identitymanager.h \
|
backend/identitymanager.h \
|
||||||
|
backend/nvcomputer.h \
|
||||||
backend/nvhttp.h \
|
backend/nvhttp.h \
|
||||||
backend/nvpairingmanager.h \
|
backend/nvpairingmanager.h \
|
||||||
backend/computermanager.h \
|
backend/computermanager.h \
|
||||||
|
|||||||
@@ -36,6 +36,39 @@ BoxArtManager::getFilePathForBoxArt(NvComputer* computer, int appId)
|
|||||||
return dir.filePath(QString::number(appId) + ".png");
|
return dir.filePath(QString::number(appId) + ".png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NetworkBoxArtLoadTask : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
NetworkBoxArtLoadTask(BoxArtManager* boxArtManager, NvComputer* computer, NvApp& app)
|
||||||
|
: m_Bam(boxArtManager),
|
||||||
|
m_Computer(computer),
|
||||||
|
m_App(app)
|
||||||
|
{
|
||||||
|
connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QUrl)),
|
||||||
|
boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QUrl)));
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void boxArtFetchCompleted(NvComputer* computer, NvApp app, QUrl image);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
QUrl image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id);
|
||||||
|
if (image.isEmpty()) {
|
||||||
|
// Give it another shot if it fails once
|
||||||
|
image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id);
|
||||||
|
}
|
||||||
|
emit boxArtFetchCompleted(m_Computer, m_App, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxArtManager* m_Bam;
|
||||||
|
NvComputer* m_Computer;
|
||||||
|
NvApp m_App;
|
||||||
|
};
|
||||||
|
|
||||||
QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
QUrl BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app)
|
||||||
{
|
{
|
||||||
// Try to open the cached file
|
// Try to open the cached file
|
||||||
@@ -81,3 +114,5 @@ QUrl BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId)
|
|||||||
|
|
||||||
return QUrl();
|
return QUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "boxartmanager.moc"
|
||||||
|
|||||||
@@ -38,36 +38,3 @@ private:
|
|||||||
QDir m_BoxArtDir;
|
QDir m_BoxArtDir;
|
||||||
QThreadPool m_ThreadPool;
|
QThreadPool m_ThreadPool;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NetworkBoxArtLoadTask : public QObject, public QRunnable
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
NetworkBoxArtLoadTask(BoxArtManager* boxArtManager, NvComputer* computer, NvApp& app)
|
|
||||||
: m_Bam(boxArtManager),
|
|
||||||
m_Computer(computer),
|
|
||||||
m_App(app)
|
|
||||||
{
|
|
||||||
connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QUrl)),
|
|
||||||
boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QUrl)));
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void boxArtFetchCompleted(NvComputer* computer, NvApp app, QUrl image);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
QUrl image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id);
|
|
||||||
if (image.isEmpty()) {
|
|
||||||
// Give it another shot if it fails once
|
|
||||||
image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id);
|
|
||||||
}
|
|
||||||
emit boxArtFetchCompleted(m_Computer, m_App, image);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxArtManager* m_Bam;
|
|
||||||
NvComputer* m_Computer;
|
|
||||||
NvApp m_App;
|
|
||||||
};
|
|
||||||
|
|||||||
+319
-276
@@ -3,282 +3,141 @@
|
|||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QUdpSocket>
|
|
||||||
#include <QHostInfo>
|
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
|
|
||||||
#define SER_HOSTS "hosts"
|
#define SER_HOSTS "hosts"
|
||||||
#define SER_NAME "hostname"
|
|
||||||
#define SER_UUID "uuid"
|
|
||||||
#define SER_MAC "mac"
|
|
||||||
#define SER_LOCALADDR "localaddress"
|
|
||||||
#define SER_REMOTEADDR "remoteaddress"
|
|
||||||
#define SER_MANUALADDR "manualaddress"
|
|
||||||
#define SER_APPLIST "apps"
|
|
||||||
|
|
||||||
#define SER_APPNAME "name"
|
class PcMonitorThread : public QThread
|
||||||
#define SER_APPID "id"
|
|
||||||
#define SER_APPHDR "hdr"
|
|
||||||
|
|
||||||
NvComputer::NvComputer(QSettings& settings)
|
|
||||||
{
|
{
|
||||||
this->name = settings.value(SER_NAME).toString();
|
Q_OBJECT
|
||||||
this->uuid = settings.value(SER_UUID).toString();
|
|
||||||
this->macAddress = settings.value(SER_MAC).toByteArray();
|
|
||||||
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
|
||||||
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
|
||||||
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
|
||||||
|
|
||||||
int appCount = settings.beginReadArray(SER_APPLIST);
|
#define TRIES_BEFORE_OFFLINING 2
|
||||||
for (int i = 0; i < appCount; i++) {
|
#define POLLS_PER_APPLIST_FETCH 10
|
||||||
NvApp app;
|
|
||||||
|
|
||||||
settings.setArrayIndex(i);
|
public:
|
||||||
|
PcMonitorThread(NvComputer* computer)
|
||||||
app.name = settings.value(SER_APPNAME).toString();
|
: m_Computer(computer)
|
||||||
app.id = settings.value(SER_APPID).toInt();
|
{
|
||||||
app.hdrSupported = settings.value(SER_APPHDR).toBool();
|
setObjectName("Polling thread for " + computer->name);
|
||||||
|
|
||||||
this->appList.append(app);
|
|
||||||
}
|
}
|
||||||
settings.endArray();
|
|
||||||
sortAppList();
|
|
||||||
|
|
||||||
this->activeAddress = nullptr;
|
private:
|
||||||
this->currentGameId = 0;
|
bool tryPollComputer(QString address, bool& changed)
|
||||||
this->pairState = PS_UNKNOWN;
|
{
|
||||||
this->state = CS_UNKNOWN;
|
NvHTTP http(address);
|
||||||
this->gfeVersion = nullptr;
|
|
||||||
this->appVersion = nullptr;
|
|
||||||
this->maxLumaPixelsHEVC = 0;
|
|
||||||
this->serverCodecModeSupport = 0;
|
|
||||||
this->pendingQuit = false;
|
|
||||||
this->gpuModel = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
QString serverInfo;
|
||||||
NvComputer::serialize(QSettings& settings)
|
try {
|
||||||
{
|
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::NONE);
|
||||||
QReadLocker lock(&this->lock);
|
} catch (...) {
|
||||||
|
return false;
|
||||||
settings.setValue(SER_NAME, name);
|
|
||||||
settings.setValue(SER_UUID, uuid);
|
|
||||||
settings.setValue(SER_MAC, macAddress);
|
|
||||||
settings.setValue(SER_LOCALADDR, localAddress);
|
|
||||||
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
|
||||||
settings.setValue(SER_MANUALADDR, manualAddress);
|
|
||||||
|
|
||||||
// Avoid deleting an existing applist if we couldn't get one
|
|
||||||
if (!appList.isEmpty()) {
|
|
||||||
settings.remove(SER_APPLIST);
|
|
||||||
settings.beginWriteArray(SER_APPLIST);
|
|
||||||
for (int i = 0; i < appList.count(); i++) {
|
|
||||||
settings.setArrayIndex(i);
|
|
||||||
|
|
||||||
settings.setValue(SER_APPNAME, appList[i].name);
|
|
||||||
settings.setValue(SER_APPID, appList[i].id);
|
|
||||||
settings.setValue(SER_APPHDR, appList[i].hdrSupported);
|
|
||||||
}
|
}
|
||||||
settings.endArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NvComputer::sortAppList()
|
NvComputer newState(address, serverInfo);
|
||||||
{
|
|
||||||
std::stable_sort(appList.begin(), appList.end(), [](const NvApp& app1, const NvApp& app2) {
|
|
||||||
return app1.name.toLower() < app2.name.toLower();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer::NvComputer(QString address, QString serverInfo)
|
// Ensure the machine that responded is the one we intended to contact
|
||||||
{
|
if (m_Computer->uuid != newState.uuid) {
|
||||||
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
qInfo() << "Found unexpected PC " << newState.name << " looking for " << m_Computer->name;
|
||||||
if (this->name.isEmpty()) {
|
return false;
|
||||||
this->name = "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
this->uuid = NvHTTP::getXmlString(serverInfo, "uniqueid");
|
|
||||||
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
|
|
||||||
if (newMacString != "00:00:00:00:00:00") {
|
|
||||||
QStringList macOctets = newMacString.split(':');
|
|
||||||
for (QString macOctet : macOctets) {
|
|
||||||
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString codecSupport = NvHTTP::getXmlString(serverInfo, "ServerCodecModeSupport");
|
changed = m_Computer->update(newState);
|
||||||
if (!codecSupport.isEmpty()) {
|
|
||||||
this->serverCodecModeSupport = codecSupport.toInt();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this->serverCodecModeSupport = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString maxLumaPixelsHEVC = NvHTTP::getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
|
||||||
if (!maxLumaPixelsHEVC.isEmpty()) {
|
|
||||||
this->maxLumaPixelsHEVC = maxLumaPixelsHEVC.toInt();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this->maxLumaPixelsHEVC = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->displayModes = NvHTTP::getDisplayModeList(serverInfo);
|
|
||||||
std::stable_sort(this->displayModes.begin(), this->displayModes.end(),
|
|
||||||
[](const NvDisplayMode& mode1, const NvDisplayMode& mode2) {
|
|
||||||
return mode1.width * mode1.height * mode1.refreshRate <
|
|
||||||
mode2.width * mode2.height * mode2.refreshRate;
|
|
||||||
});
|
|
||||||
|
|
||||||
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
|
|
||||||
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
|
||||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
|
||||||
PS_PAIRED : PS_NOT_PAIRED;
|
|
||||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
|
||||||
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
|
||||||
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
|
||||||
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
|
|
||||||
this->activeAddress = address;
|
|
||||||
this->state = NvComputer::CS_ONLINE;
|
|
||||||
this->pendingQuit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NvComputer::wake()
|
|
||||||
{
|
|
||||||
if (state == NvComputer::CS_ONLINE) {
|
|
||||||
qWarning() << name << "is already online";
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (macAddress.isEmpty()) {
|
bool updateAppList(bool& changed)
|
||||||
qWarning() << name << "has no MAC address stored";
|
{
|
||||||
return false;
|
Q_ASSERT(m_Computer->activeAddress != nullptr);
|
||||||
}
|
|
||||||
|
|
||||||
const quint16 WOL_PORTS[] = {
|
NvHTTP http(m_Computer->activeAddress);
|
||||||
7, 9, // Standard WOL ports
|
|
||||||
47998, 47999, 48000, // Ports opened by GFE
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the WoL payload
|
QVector<NvApp> appList;
|
||||||
QByteArray wolPayload;
|
|
||||||
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
wolPayload.append(macAddress);
|
|
||||||
}
|
|
||||||
Q_ASSERT(wolPayload.count() == 102);
|
|
||||||
|
|
||||||
// Add the addresses that we know this host to be
|
try {
|
||||||
// and broadcast addresses for this link just in
|
appList = http.getAppList();
|
||||||
// case the host has timed out in ARP entries.
|
if (appList.isEmpty()) {
|
||||||
QVector<QString> addressList = uniqueAddresses();
|
return false;
|
||||||
addressList.append("255.255.255.255");
|
}
|
||||||
|
} catch (...) {
|
||||||
// Try all unique address strings or host names
|
return false;
|
||||||
bool success = false;
|
|
||||||
for (QString& addressString : addressList) {
|
|
||||||
QHostInfo hostInfo = QHostInfo::fromName(addressString);
|
|
||||||
|
|
||||||
if (hostInfo.error() != QHostInfo::NoError) {
|
|
||||||
qWarning() << "Error resolving" << addressString << ":" << hostInfo.errorString();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try all IP addresses that this string resolves to
|
QWriteLocker lock(&m_Computer->lock);
|
||||||
for (QHostAddress& address : hostInfo.addresses()) {
|
if (m_Computer->appList != appList) {
|
||||||
QUdpSocket sock;
|
m_Computer->appList = appList;
|
||||||
|
m_Computer->sortAppList();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Bind to any address on the correct protocol
|
return true;
|
||||||
if (sock.bind(address.protocol() == QUdpSocket::IPv4Protocol ?
|
}
|
||||||
QHostAddress::AnyIPv4 : QHostAddress::AnyIPv6)) {
|
|
||||||
|
|
||||||
// Send to all ports
|
void run() override
|
||||||
for (quint16 port : WOL_PORTS) {
|
{
|
||||||
if (sock.writeDatagram(wolPayload, address, port)) {
|
// Always fetch the applist the first time
|
||||||
qInfo().nospace().noquote() << "Send WoL packet to " << name << " via " << address.toString() << ":" << port;
|
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
||||||
success = true;
|
while (!isInterruptionRequested()) {
|
||||||
|
bool stateChanged = false;
|
||||||
|
bool online = false;
|
||||||
|
bool wasOnline = m_Computer->state == NvComputer::CS_ONLINE;
|
||||||
|
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
||||||
|
for (auto& address : m_Computer->uniqueAddresses()) {
|
||||||
|
if (isInterruptionRequested()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryPollComputer(address, stateChanged)) {
|
||||||
|
if (!wasOnline) {
|
||||||
|
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
|
||||||
|
}
|
||||||
|
online = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
// Check if we failed after all retry attempts
|
||||||
}
|
// Note: we don't need to acquire the read lock here,
|
||||||
|
// because we're on the writing thread.
|
||||||
QVector<QString> NvComputer::uniqueAddresses()
|
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
||||||
{
|
qInfo() << m_Computer->name << "is now offline";
|
||||||
QVector<QString> uniqueAddressList;
|
m_Computer->state = NvComputer::CS_OFFLINE;
|
||||||
|
stateChanged = true;
|
||||||
// Start with addresses correctly ordered
|
|
||||||
uniqueAddressList.append(activeAddress);
|
|
||||||
uniqueAddressList.append(localAddress);
|
|
||||||
uniqueAddressList.append(remoteAddress);
|
|
||||||
uniqueAddressList.append(manualAddress);
|
|
||||||
|
|
||||||
// Prune duplicates (always giving precedence to the first)
|
|
||||||
for (int i = 0; i < uniqueAddressList.count(); i++) {
|
|
||||||
if (uniqueAddressList[i].isEmpty()) {
|
|
||||||
uniqueAddressList.remove(i);
|
|
||||||
i--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (int j = i + 1; j < uniqueAddressList.count(); j++) {
|
|
||||||
if (uniqueAddressList[i] == uniqueAddressList[j]) {
|
|
||||||
// Always remove the later occurrence
|
|
||||||
uniqueAddressList.remove(j);
|
|
||||||
j--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
||||||
|
pollsSinceLastAppListFetch++;
|
||||||
|
if (m_Computer->state == NvComputer::CS_ONLINE &&
|
||||||
|
m_Computer->pairState == NvComputer::PS_PAIRED &&
|
||||||
|
(m_Computer->appList.isEmpty() || pollsSinceLastAppListFetch >= POLLS_PER_APPLIST_FETCH)) {
|
||||||
|
// Notify prior to the app list poll since it may take a while, and we don't
|
||||||
|
// want to delay onlining of a machine, especially if we already have a cached list.
|
||||||
|
if (stateChanged) {
|
||||||
|
emit computerStateChanged(m_Computer);
|
||||||
|
stateChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateAppList(stateChanged)) {
|
||||||
|
pollsSinceLastAppListFetch = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateChanged) {
|
||||||
|
// Tell anyone listening that we've changed state
|
||||||
|
emit computerStateChanged(m_Computer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a bit to poll again
|
||||||
|
QThread::sleep(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must have at least 1 address
|
signals:
|
||||||
Q_ASSERT(!uniqueAddressList.isEmpty());
|
void computerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
return uniqueAddressList;
|
private:
|
||||||
}
|
NvComputer* m_Computer;
|
||||||
|
};
|
||||||
bool NvComputer::update(NvComputer& that)
|
|
||||||
{
|
|
||||||
bool changed = false;
|
|
||||||
|
|
||||||
// Lock us for write and them for read
|
|
||||||
QWriteLocker thisLock(&this->lock);
|
|
||||||
QReadLocker thatLock(&that.lock);
|
|
||||||
|
|
||||||
// UUID may not change or we're talking to a new PC
|
|
||||||
Q_ASSERT(this->uuid == that.uuid);
|
|
||||||
|
|
||||||
#define ASSIGN_IF_CHANGED(field) \
|
|
||||||
if (this->field != that.field) { \
|
|
||||||
this->field = that.field; \
|
|
||||||
changed = true; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ASSIGN_IF_CHANGED_AND_NONEMPTY(field) \
|
|
||||||
if (!that.field.isEmpty() && \
|
|
||||||
this->field != that.field) { \
|
|
||||||
this->field = that.field; \
|
|
||||||
changed = true; \
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSIGN_IF_CHANGED(name);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
|
|
||||||
ASSIGN_IF_CHANGED(pairState);
|
|
||||||
ASSIGN_IF_CHANGED(serverCodecModeSupport);
|
|
||||||
ASSIGN_IF_CHANGED(currentGameId);
|
|
||||||
ASSIGN_IF_CHANGED(activeAddress);
|
|
||||||
ASSIGN_IF_CHANGED(state);
|
|
||||||
ASSIGN_IF_CHANGED(gfeVersion);
|
|
||||||
ASSIGN_IF_CHANGED(appVersion);
|
|
||||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
|
||||||
ASSIGN_IF_CHANGED(gpuModel);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComputerManager::ComputerManager(QObject *parent)
|
ComputerManager::ComputerManager(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -396,6 +255,25 @@ void ComputerManager::startPolling()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must hold m_Lock for write
|
||||||
|
void ComputerManager::startPollingComputer(NvComputer* computer)
|
||||||
|
{
|
||||||
|
if (m_PollingRef == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_PollThreads.contains(computer->uuid)) {
|
||||||
|
Q_ASSERT(m_PollThreads[computer->uuid]->isRunning());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PcMonitorThread* thread = new PcMonitorThread(computer);
|
||||||
|
connect(thread, SIGNAL(computerStateChanged(NvComputer*)),
|
||||||
|
this, SLOT(handleComputerStateChanged(NvComputer*)));
|
||||||
|
m_PollThreads[computer->uuid] = thread;
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||||
const QHostAddress& address)
|
const QHostAddress& address)
|
||||||
{
|
{
|
||||||
@@ -407,6 +285,19 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
|||||||
computer->deleteLater();
|
computer->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComputerManager::handleComputerStateChanged(NvComputer* computer)
|
||||||
|
{
|
||||||
|
emit computerStateChanged(computer);
|
||||||
|
|
||||||
|
if (computer->pendingQuit && computer->currentGameId == 0) {
|
||||||
|
computer->pendingQuit = false;
|
||||||
|
emit quitAppCompleted(QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updated hosts to QSettings
|
||||||
|
saveHosts();
|
||||||
|
}
|
||||||
|
|
||||||
QVector<NvComputer*> ComputerManager::getComputers()
|
QVector<NvComputer*> ComputerManager::getComputers()
|
||||||
{
|
{
|
||||||
QReadLocker lock(&m_Lock);
|
QReadLocker lock(&m_Lock);
|
||||||
@@ -469,6 +360,53 @@ void ComputerManager::deleteHost(NvComputer* computer)
|
|||||||
QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer));
|
QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PendingPairingTask : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
|
||||||
|
: m_Computer(computer),
|
||||||
|
m_Pin(pin)
|
||||||
|
{
|
||||||
|
connect(this, &PendingPairingTask::pairingCompleted,
|
||||||
|
computerManager, &ComputerManager::pairingCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void pairingCompleted(NvComputer* computer, QString error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
NvPairingManager pairingManager(m_Computer->activeAddress);
|
||||||
|
|
||||||
|
try {
|
||||||
|
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin);
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case NvPairingManager::PairState::PIN_WRONG:
|
||||||
|
emit pairingCompleted(m_Computer, "The PIN from the PC didn't match. Please try again.");
|
||||||
|
break;
|
||||||
|
case NvPairingManager::PairState::FAILED:
|
||||||
|
emit pairingCompleted(m_Computer, "Pairing failed. Please try again.");
|
||||||
|
break;
|
||||||
|
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
|
||||||
|
emit pairingCompleted(m_Computer, "Another pairing attempt is already in progress.");
|
||||||
|
break;
|
||||||
|
case NvPairingManager::PairState::PAIRED:
|
||||||
|
emit pairingCompleted(m_Computer, nullptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (const GfeHttpResponseException& e) {
|
||||||
|
emit pairingCompleted(m_Computer, e.toQString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NvComputer* m_Computer;
|
||||||
|
QString m_Pin;
|
||||||
|
};
|
||||||
|
|
||||||
void ComputerManager::pairHost(NvComputer* computer, QString pin)
|
void ComputerManager::pairHost(NvComputer* computer, QString pin)
|
||||||
{
|
{
|
||||||
// Punt to a worker thread to avoid stalling the
|
// Punt to a worker thread to avoid stalling the
|
||||||
@@ -477,6 +415,49 @@ void ComputerManager::pairHost(NvComputer* computer, QString pin)
|
|||||||
QThreadPool::globalInstance()->start(pairing);
|
QThreadPool::globalInstance()->start(pairing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PendingQuitTask : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PendingQuitTask(ComputerManager* computerManager, NvComputer* computer)
|
||||||
|
: m_Computer(computer)
|
||||||
|
{
|
||||||
|
connect(this, &PendingQuitTask::quitAppFailed,
|
||||||
|
computerManager, &ComputerManager::quitAppCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void quitAppFailed(QString error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
NvHTTP http(m_Computer->activeAddress);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (m_Computer->currentGameId != 0) {
|
||||||
|
http.quitApp();
|
||||||
|
}
|
||||||
|
} catch (const GfeHttpResponseException& e) {
|
||||||
|
{
|
||||||
|
QWriteLocker lock(&m_Computer->lock);
|
||||||
|
m_Computer->pendingQuit = false;
|
||||||
|
}
|
||||||
|
if (e.getStatusCode() == 599) {
|
||||||
|
// 599 is a special code we make a custom message for
|
||||||
|
emit quitAppFailed("The running game wasn't started by this PC. "
|
||||||
|
"You must quit the game on the host PC manually or use the device that originally started the game.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit quitAppFailed(e.toQString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NvComputer* m_Computer;
|
||||||
|
};
|
||||||
|
|
||||||
void ComputerManager::quitRunningApp(NvComputer* computer)
|
void ComputerManager::quitRunningApp(NvComputer* computer)
|
||||||
{
|
{
|
||||||
QWriteLocker lock(&computer->lock);
|
QWriteLocker lock(&computer->lock);
|
||||||
@@ -524,6 +505,101 @@ void ComputerManager::stopPollingAsync()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PendingAddTask : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PendingAddTask(ComputerManager* computerManager, QString address, bool mdns)
|
||||||
|
: m_ComputerManager(computerManager),
|
||||||
|
m_Address(address),
|
||||||
|
m_Mdns(mdns)
|
||||||
|
{
|
||||||
|
connect(this, &PendingAddTask::computerAddCompleted,
|
||||||
|
computerManager, &ComputerManager::computerAddCompleted);
|
||||||
|
connect(this, &PendingAddTask::computerStateChanged,
|
||||||
|
computerManager, &ComputerManager::handleComputerStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void computerAddCompleted(QVariant success);
|
||||||
|
|
||||||
|
void computerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
NvHTTP http(m_Address);
|
||||||
|
|
||||||
|
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
||||||
|
|
||||||
|
QString serverInfo;
|
||||||
|
try {
|
||||||
|
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::VERBOSE);
|
||||||
|
} catch (...) {
|
||||||
|
if (!m_Mdns) {
|
||||||
|
emit computerAddCompleted(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvComputer* newComputer = new NvComputer(m_Address, serverInfo);
|
||||||
|
|
||||||
|
// Update addresses depending on the context
|
||||||
|
if (m_Mdns) {
|
||||||
|
newComputer->localAddress = m_Address;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newComputer->manualAddress = m_Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this PC already exists
|
||||||
|
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||||
|
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid];
|
||||||
|
if (existingComputer != nullptr) {
|
||||||
|
// Fold it into the existing PC
|
||||||
|
bool changed = existingComputer->update(*newComputer);
|
||||||
|
delete newComputer;
|
||||||
|
|
||||||
|
// Drop the lock before notifying
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
// For non-mDNS clients, let them know it succeeded
|
||||||
|
if (!m_Mdns) {
|
||||||
|
emit computerAddCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell our client if something changed
|
||||||
|
if (changed) {
|
||||||
|
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
||||||
|
emit computerStateChanged(existingComputer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Store this in our active sets
|
||||||
|
m_ComputerManager->m_KnownHosts[newComputer->uuid] = newComputer;
|
||||||
|
|
||||||
|
// Start polling if enabled (write lock required)
|
||||||
|
m_ComputerManager->startPollingComputer(newComputer);
|
||||||
|
|
||||||
|
// Drop the lock before notifying
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
// For non-mDNS clients, let them know it succeeded
|
||||||
|
if (!m_Mdns) {
|
||||||
|
emit computerAddCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell our client about this new PC
|
||||||
|
emit computerStateChanged(newComputer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputerManager* m_ComputerManager;
|
||||||
|
QString m_Address;
|
||||||
|
bool m_Mdns;
|
||||||
|
};
|
||||||
|
|
||||||
void ComputerManager::addNewHost(QString address, bool mdns)
|
void ComputerManager::addNewHost(QString address, bool mdns)
|
||||||
{
|
{
|
||||||
// Punt to a worker thread to avoid stalling the
|
// Punt to a worker thread to avoid stalling the
|
||||||
@@ -532,37 +608,4 @@ void ComputerManager::addNewHost(QString address, bool mdns)
|
|||||||
QThreadPool::globalInstance()->start(addTask);
|
QThreadPool::globalInstance()->start(addTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
#include "computermanager.moc"
|
||||||
ComputerManager::handleComputerStateChanged(NvComputer* computer)
|
|
||||||
{
|
|
||||||
emit computerStateChanged(computer);
|
|
||||||
|
|
||||||
if (computer->pendingQuit && computer->currentGameId == 0) {
|
|
||||||
computer->pendingQuit = false;
|
|
||||||
emit quitAppCompleted(QVariant());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save updated hosts to QSettings
|
|
||||||
saveHosts();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must hold m_Lock for write
|
|
||||||
void
|
|
||||||
ComputerManager::startPollingComputer(NvComputer* computer)
|
|
||||||
{
|
|
||||||
if (m_PollingRef == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_PollThreads.contains(computer->uuid)) {
|
|
||||||
Q_ASSERT(m_PollThreads[computer->uuid]->isRunning());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PcMonitorThread* thread = new PcMonitorThread(computer);
|
|
||||||
connect(thread, SIGNAL(computerStateChanged(NvComputer*)),
|
|
||||||
this, SLOT(handleComputerStateChanged(NvComputer*)));
|
|
||||||
m_PollThreads[computer->uuid] = thread;
|
|
||||||
thread->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "nvhttp.h"
|
|
||||||
|
#include "nvcomputer.h"
|
||||||
#include "nvpairingmanager.h"
|
#include "nvpairingmanager.h"
|
||||||
|
|
||||||
#include <qmdnsengine/server.h>
|
#include <qmdnsengine/server.h>
|
||||||
@@ -13,207 +14,6 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
|
|
||||||
class NvComputer
|
|
||||||
{
|
|
||||||
friend class PcMonitorThread;
|
|
||||||
friend class ComputerManager;
|
|
||||||
friend class PendingQuitTask;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void sortAppList();
|
|
||||||
|
|
||||||
bool pendingQuit;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit NvComputer(QString address, QString serverInfo);
|
|
||||||
|
|
||||||
explicit NvComputer(QSettings& settings);
|
|
||||||
|
|
||||||
bool
|
|
||||||
update(NvComputer& that);
|
|
||||||
|
|
||||||
bool
|
|
||||||
wake();
|
|
||||||
|
|
||||||
QVector<QString>
|
|
||||||
uniqueAddresses();
|
|
||||||
|
|
||||||
void
|
|
||||||
serialize(QSettings& settings);
|
|
||||||
|
|
||||||
enum PairState
|
|
||||||
{
|
|
||||||
PS_UNKNOWN,
|
|
||||||
PS_PAIRED,
|
|
||||||
PS_NOT_PAIRED
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ComputerState
|
|
||||||
{
|
|
||||||
CS_UNKNOWN,
|
|
||||||
CS_ONLINE,
|
|
||||||
CS_OFFLINE
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ephemeral traits
|
|
||||||
ComputerState state;
|
|
||||||
PairState pairState;
|
|
||||||
QString activeAddress;
|
|
||||||
int currentGameId;
|
|
||||||
QString gfeVersion;
|
|
||||||
QString appVersion;
|
|
||||||
QVector<NvDisplayMode> displayModes;
|
|
||||||
int maxLumaPixelsHEVC;
|
|
||||||
int serverCodecModeSupport;
|
|
||||||
QString gpuModel;
|
|
||||||
|
|
||||||
// Persisted traits
|
|
||||||
QString localAddress;
|
|
||||||
QString remoteAddress;
|
|
||||||
QString manualAddress;
|
|
||||||
QByteArray macAddress;
|
|
||||||
QString name;
|
|
||||||
QString uuid;
|
|
||||||
QVector<NvApp> appList;
|
|
||||||
|
|
||||||
// Synchronization
|
|
||||||
QReadWriteLock lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: MOC isn't finding Q_OBJECT properly when this is confined
|
|
||||||
// to computermanager.cpp as it should be.
|
|
||||||
class PcMonitorThread : public QThread
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
#define TRIES_BEFORE_OFFLINING 2
|
|
||||||
#define POLLS_PER_APPLIST_FETCH 10
|
|
||||||
|
|
||||||
public:
|
|
||||||
PcMonitorThread(NvComputer* computer)
|
|
||||||
: m_Computer(computer)
|
|
||||||
{
|
|
||||||
setObjectName("Polling thread for " + computer->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool tryPollComputer(QString address, bool& changed)
|
|
||||||
{
|
|
||||||
NvHTTP http(address);
|
|
||||||
|
|
||||||
QString serverInfo;
|
|
||||||
try {
|
|
||||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::NONE);
|
|
||||||
} catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer newState(address, serverInfo);
|
|
||||||
|
|
||||||
// Ensure the machine that responded is the one we intended to contact
|
|
||||||
if (m_Computer->uuid != newState.uuid) {
|
|
||||||
qInfo() << "Found unexpected PC " << newState.name << " looking for " << m_Computer->name;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
changed = m_Computer->update(newState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool updateAppList(bool& changed)
|
|
||||||
{
|
|
||||||
Q_ASSERT(m_Computer->activeAddress != nullptr);
|
|
||||||
|
|
||||||
NvHTTP http(m_Computer->activeAddress);
|
|
||||||
|
|
||||||
QVector<NvApp> appList;
|
|
||||||
|
|
||||||
try {
|
|
||||||
appList = http.getAppList();
|
|
||||||
if (appList.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QWriteLocker lock(&m_Computer->lock);
|
|
||||||
if (m_Computer->appList != appList) {
|
|
||||||
m_Computer->appList = appList;
|
|
||||||
m_Computer->sortAppList();
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
// Always fetch the applist the first time
|
|
||||||
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
|
||||||
while (!isInterruptionRequested()) {
|
|
||||||
bool stateChanged = false;
|
|
||||||
bool online = false;
|
|
||||||
bool wasOnline = m_Computer->state == NvComputer::CS_ONLINE;
|
|
||||||
for (int i = 0; i < TRIES_BEFORE_OFFLINING && !online; i++) {
|
|
||||||
for (auto& address : m_Computer->uniqueAddresses()) {
|
|
||||||
if (isInterruptionRequested()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tryPollComputer(address, stateChanged)) {
|
|
||||||
if (!wasOnline) {
|
|
||||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress;
|
|
||||||
}
|
|
||||||
online = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we failed after all retry attempts
|
|
||||||
// Note: we don't need to acquire the read lock here,
|
|
||||||
// because we're on the writing thread.
|
|
||||||
if (!online && m_Computer->state != NvComputer::CS_OFFLINE) {
|
|
||||||
qInfo() << m_Computer->name << "is now offline";
|
|
||||||
m_Computer->state = NvComputer::CS_OFFLINE;
|
|
||||||
stateChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the applist if it's empty or it's been long enough that we need to refresh
|
|
||||||
pollsSinceLastAppListFetch++;
|
|
||||||
if (m_Computer->state == NvComputer::CS_ONLINE &&
|
|
||||||
m_Computer->pairState == NvComputer::PS_PAIRED &&
|
|
||||||
(m_Computer->appList.isEmpty() || pollsSinceLastAppListFetch >= POLLS_PER_APPLIST_FETCH)) {
|
|
||||||
// Notify prior to the app list poll since it may take a while, and we don't
|
|
||||||
// want to delay onlining of a machine, especially if we already have a cached list.
|
|
||||||
if (stateChanged) {
|
|
||||||
emit computerStateChanged(m_Computer);
|
|
||||||
stateChanged = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateAppList(stateChanged)) {
|
|
||||||
pollsSinceLastAppListFetch = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateChanged) {
|
|
||||||
// Tell anyone listening that we've changed state
|
|
||||||
emit computerStateChanged(m_Computer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a bit to poll again
|
|
||||||
QThread::sleep(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void computerStateChanged(NvComputer* computer);
|
|
||||||
|
|
||||||
private:
|
|
||||||
NvComputer* m_Computer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MdnsPendingComputer : public QObject
|
class MdnsPendingComputer : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -306,188 +106,3 @@ private:
|
|||||||
QMdnsEngine::Cache m_MdnsCache;
|
QMdnsEngine::Cache m_MdnsCache;
|
||||||
QVector<MdnsPendingComputer*> m_PendingResolution;
|
QVector<MdnsPendingComputer*> m_PendingResolution;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PendingPairingTask : public QObject, public QRunnable
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
|
|
||||||
: m_Computer(computer),
|
|
||||||
m_Pin(pin)
|
|
||||||
{
|
|
||||||
connect(this, &PendingPairingTask::pairingCompleted,
|
|
||||||
computerManager, &ComputerManager::pairingCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void pairingCompleted(NvComputer* computer, QString error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
NvPairingManager pairingManager(m_Computer->activeAddress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin);
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case NvPairingManager::PairState::PIN_WRONG:
|
|
||||||
emit pairingCompleted(m_Computer, "The PIN from the PC didn't match. Please try again.");
|
|
||||||
break;
|
|
||||||
case NvPairingManager::PairState::FAILED:
|
|
||||||
emit pairingCompleted(m_Computer, "Pairing failed. Please try again.");
|
|
||||||
break;
|
|
||||||
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
|
|
||||||
emit pairingCompleted(m_Computer, "Another pairing attempt is already in progress.");
|
|
||||||
break;
|
|
||||||
case NvPairingManager::PairState::PAIRED:
|
|
||||||
emit pairingCompleted(m_Computer, nullptr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (const GfeHttpResponseException& e) {
|
|
||||||
emit pairingCompleted(m_Computer, e.toQString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer* m_Computer;
|
|
||||||
QString m_Pin;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PendingQuitTask : public QObject, public QRunnable
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PendingQuitTask(ComputerManager* computerManager, NvComputer* computer)
|
|
||||||
: m_Computer(computer)
|
|
||||||
{
|
|
||||||
connect(this, &PendingQuitTask::quitAppFailed,
|
|
||||||
computerManager, &ComputerManager::quitAppCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void quitAppFailed(QString error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
NvHTTP http(m_Computer->activeAddress);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (m_Computer->currentGameId != 0) {
|
|
||||||
http.quitApp();
|
|
||||||
}
|
|
||||||
} catch (const GfeHttpResponseException& e) {
|
|
||||||
{
|
|
||||||
QWriteLocker lock(&m_Computer->lock);
|
|
||||||
m_Computer->pendingQuit = false;
|
|
||||||
}
|
|
||||||
if (e.getStatusCode() == 599) {
|
|
||||||
// 599 is a special code we make a custom message for
|
|
||||||
emit quitAppFailed("The running game wasn't started by this PC. "
|
|
||||||
"You must quit the game on the host PC manually or use the device that originally started the game.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
emit quitAppFailed(e.toQString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer* m_Computer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PendingAddTask : public QObject, public QRunnable
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PendingAddTask(ComputerManager* computerManager, QString address, bool mdns)
|
|
||||||
: m_ComputerManager(computerManager),
|
|
||||||
m_Address(address),
|
|
||||||
m_Mdns(mdns)
|
|
||||||
{
|
|
||||||
connect(this, &PendingAddTask::computerAddCompleted,
|
|
||||||
computerManager, &ComputerManager::computerAddCompleted);
|
|
||||||
connect(this, &PendingAddTask::computerStateChanged,
|
|
||||||
computerManager, &ComputerManager::handleComputerStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void computerAddCompleted(QVariant success);
|
|
||||||
|
|
||||||
void computerStateChanged(NvComputer* computer);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
NvHTTP http(m_Address);
|
|
||||||
|
|
||||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
|
||||||
|
|
||||||
QString serverInfo;
|
|
||||||
try {
|
|
||||||
serverInfo = http.getServerInfo(NvHTTP::NvLogLevel::VERBOSE);
|
|
||||||
} catch (...) {
|
|
||||||
if (!m_Mdns) {
|
|
||||||
emit computerAddCompleted(false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvComputer* newComputer = new NvComputer(m_Address, serverInfo);
|
|
||||||
|
|
||||||
// Update addresses depending on the context
|
|
||||||
if (m_Mdns) {
|
|
||||||
newComputer->localAddress = m_Address;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newComputer->manualAddress = m_Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this PC already exists
|
|
||||||
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
|
||||||
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts[newComputer->uuid];
|
|
||||||
if (existingComputer != nullptr) {
|
|
||||||
// Fold it into the existing PC
|
|
||||||
bool changed = existingComputer->update(*newComputer);
|
|
||||||
delete newComputer;
|
|
||||||
|
|
||||||
// Drop the lock before notifying
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
// For non-mDNS clients, let them know it succeeded
|
|
||||||
if (!m_Mdns) {
|
|
||||||
emit computerAddCompleted(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell our client if something changed
|
|
||||||
if (changed) {
|
|
||||||
qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress;
|
|
||||||
emit computerStateChanged(existingComputer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Store this in our active sets
|
|
||||||
m_ComputerManager->m_KnownHosts[newComputer->uuid] = newComputer;
|
|
||||||
|
|
||||||
// Start polling if enabled (write lock required)
|
|
||||||
m_ComputerManager->startPollingComputer(newComputer);
|
|
||||||
|
|
||||||
// Drop the lock before notifying
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
// For non-mDNS clients, let them know it succeeded
|
|
||||||
if (!m_Mdns) {
|
|
||||||
emit computerAddCompleted(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell our client about this new PC
|
|
||||||
emit computerStateChanged(newComputer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComputerManager* m_ComputerManager;
|
|
||||||
QString m_Address;
|
|
||||||
bool m_Mdns;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
#include "nvcomputer.h"
|
||||||
|
|
||||||
|
#include <QUdpSocket>
|
||||||
|
#include <QHostInfo>
|
||||||
|
|
||||||
|
#define SER_NAME "hostname"
|
||||||
|
#define SER_UUID "uuid"
|
||||||
|
#define SER_MAC "mac"
|
||||||
|
#define SER_LOCALADDR "localaddress"
|
||||||
|
#define SER_REMOTEADDR "remoteaddress"
|
||||||
|
#define SER_MANUALADDR "manualaddress"
|
||||||
|
#define SER_APPLIST "apps"
|
||||||
|
|
||||||
|
#define SER_APPNAME "name"
|
||||||
|
#define SER_APPID "id"
|
||||||
|
#define SER_APPHDR "hdr"
|
||||||
|
|
||||||
|
NvComputer::NvComputer(QSettings& settings)
|
||||||
|
{
|
||||||
|
this->name = settings.value(SER_NAME).toString();
|
||||||
|
this->uuid = settings.value(SER_UUID).toString();
|
||||||
|
this->macAddress = settings.value(SER_MAC).toByteArray();
|
||||||
|
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
||||||
|
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
||||||
|
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
||||||
|
|
||||||
|
int appCount = settings.beginReadArray(SER_APPLIST);
|
||||||
|
for (int i = 0; i < appCount; i++) {
|
||||||
|
NvApp app;
|
||||||
|
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
|
||||||
|
app.name = settings.value(SER_APPNAME).toString();
|
||||||
|
app.id = settings.value(SER_APPID).toInt();
|
||||||
|
app.hdrSupported = settings.value(SER_APPHDR).toBool();
|
||||||
|
|
||||||
|
this->appList.append(app);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
sortAppList();
|
||||||
|
|
||||||
|
this->activeAddress = nullptr;
|
||||||
|
this->currentGameId = 0;
|
||||||
|
this->pairState = PS_UNKNOWN;
|
||||||
|
this->state = CS_UNKNOWN;
|
||||||
|
this->gfeVersion = nullptr;
|
||||||
|
this->appVersion = nullptr;
|
||||||
|
this->maxLumaPixelsHEVC = 0;
|
||||||
|
this->serverCodecModeSupport = 0;
|
||||||
|
this->pendingQuit = false;
|
||||||
|
this->gpuModel = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NvComputer::serialize(QSettings& settings)
|
||||||
|
{
|
||||||
|
QReadLocker lock(&this->lock);
|
||||||
|
|
||||||
|
settings.setValue(SER_NAME, name);
|
||||||
|
settings.setValue(SER_UUID, uuid);
|
||||||
|
settings.setValue(SER_MAC, macAddress);
|
||||||
|
settings.setValue(SER_LOCALADDR, localAddress);
|
||||||
|
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
||||||
|
settings.setValue(SER_MANUALADDR, manualAddress);
|
||||||
|
|
||||||
|
// Avoid deleting an existing applist if we couldn't get one
|
||||||
|
if (!appList.isEmpty()) {
|
||||||
|
settings.remove(SER_APPLIST);
|
||||||
|
settings.beginWriteArray(SER_APPLIST);
|
||||||
|
for (int i = 0; i < appList.count(); i++) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
|
||||||
|
settings.setValue(SER_APPNAME, appList[i].name);
|
||||||
|
settings.setValue(SER_APPID, appList[i].id);
|
||||||
|
settings.setValue(SER_APPHDR, appList[i].hdrSupported);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NvComputer::sortAppList()
|
||||||
|
{
|
||||||
|
std::stable_sort(appList.begin(), appList.end(), [](const NvApp& app1, const NvApp& app2) {
|
||||||
|
return app1.name.toLower() < app2.name.toLower();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NvComputer::NvComputer(QString address, QString serverInfo)
|
||||||
|
{
|
||||||
|
this->name = NvHTTP::getXmlString(serverInfo, "hostname");
|
||||||
|
if (this->name.isEmpty()) {
|
||||||
|
this->name = "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
this->uuid = NvHTTP::getXmlString(serverInfo, "uniqueid");
|
||||||
|
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
|
||||||
|
if (newMacString != "00:00:00:00:00:00") {
|
||||||
|
QStringList macOctets = newMacString.split(':');
|
||||||
|
for (QString macOctet : macOctets) {
|
||||||
|
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString codecSupport = NvHTTP::getXmlString(serverInfo, "ServerCodecModeSupport");
|
||||||
|
if (!codecSupport.isEmpty()) {
|
||||||
|
this->serverCodecModeSupport = codecSupport.toInt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->serverCodecModeSupport = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString maxLumaPixelsHEVC = NvHTTP::getXmlString(serverInfo, "MaxLumaPixelsHEVC");
|
||||||
|
if (!maxLumaPixelsHEVC.isEmpty()) {
|
||||||
|
this->maxLumaPixelsHEVC = maxLumaPixelsHEVC.toInt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->maxLumaPixelsHEVC = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->displayModes = NvHTTP::getDisplayModeList(serverInfo);
|
||||||
|
std::stable_sort(this->displayModes.begin(), this->displayModes.end(),
|
||||||
|
[](const NvDisplayMode& mode1, const NvDisplayMode& mode2) {
|
||||||
|
return mode1.width * mode1.height * mode1.refreshRate <
|
||||||
|
mode2.width * mode2.height * mode2.refreshRate;
|
||||||
|
});
|
||||||
|
|
||||||
|
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
|
||||||
|
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
||||||
|
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||||
|
PS_PAIRED : PS_NOT_PAIRED;
|
||||||
|
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||||
|
this->appVersion = NvHTTP::getXmlString(serverInfo, "appversion");
|
||||||
|
this->gfeVersion = NvHTTP::getXmlString(serverInfo, "GfeVersion");
|
||||||
|
this->gpuModel = NvHTTP::getXmlString(serverInfo, "gputype");
|
||||||
|
this->activeAddress = address;
|
||||||
|
this->state = NvComputer::CS_ONLINE;
|
||||||
|
this->pendingQuit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NvComputer::wake()
|
||||||
|
{
|
||||||
|
if (state == NvComputer::CS_ONLINE) {
|
||||||
|
qWarning() << name << "is already online";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macAddress.isEmpty()) {
|
||||||
|
qWarning() << name << "has no MAC address stored";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quint16 WOL_PORTS[] = {
|
||||||
|
7, 9, // Standard WOL ports
|
||||||
|
47998, 47999, 48000, // Ports opened by GFE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the WoL payload
|
||||||
|
QByteArray wolPayload;
|
||||||
|
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
wolPayload.append(macAddress);
|
||||||
|
}
|
||||||
|
Q_ASSERT(wolPayload.count() == 102);
|
||||||
|
|
||||||
|
// Add the addresses that we know this host to be
|
||||||
|
// and broadcast addresses for this link just in
|
||||||
|
// case the host has timed out in ARP entries.
|
||||||
|
QVector<QString> addressList = uniqueAddresses();
|
||||||
|
addressList.append("255.255.255.255");
|
||||||
|
|
||||||
|
// Try all unique address strings or host names
|
||||||
|
bool success = false;
|
||||||
|
for (QString& addressString : addressList) {
|
||||||
|
QHostInfo hostInfo = QHostInfo::fromName(addressString);
|
||||||
|
|
||||||
|
if (hostInfo.error() != QHostInfo::NoError) {
|
||||||
|
qWarning() << "Error resolving" << addressString << ":" << hostInfo.errorString();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try all IP addresses that this string resolves to
|
||||||
|
for (QHostAddress& address : hostInfo.addresses()) {
|
||||||
|
QUdpSocket sock;
|
||||||
|
|
||||||
|
// Bind to any address on the correct protocol
|
||||||
|
if (sock.bind(address.protocol() == QUdpSocket::IPv4Protocol ?
|
||||||
|
QHostAddress::AnyIPv4 : QHostAddress::AnyIPv6)) {
|
||||||
|
|
||||||
|
// Send to all ports
|
||||||
|
for (quint16 port : WOL_PORTS) {
|
||||||
|
if (sock.writeDatagram(wolPayload, address, port)) {
|
||||||
|
qInfo().nospace().noquote() << "Send WoL packet to " << name << " via " << address.toString() << ":" << port;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QString> NvComputer::uniqueAddresses()
|
||||||
|
{
|
||||||
|
QVector<QString> uniqueAddressList;
|
||||||
|
|
||||||
|
// Start with addresses correctly ordered
|
||||||
|
uniqueAddressList.append(activeAddress);
|
||||||
|
uniqueAddressList.append(localAddress);
|
||||||
|
uniqueAddressList.append(remoteAddress);
|
||||||
|
uniqueAddressList.append(manualAddress);
|
||||||
|
|
||||||
|
// Prune duplicates (always giving precedence to the first)
|
||||||
|
for (int i = 0; i < uniqueAddressList.count(); i++) {
|
||||||
|
if (uniqueAddressList[i].isEmpty()) {
|
||||||
|
uniqueAddressList.remove(i);
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int j = i + 1; j < uniqueAddressList.count(); j++) {
|
||||||
|
if (uniqueAddressList[i] == uniqueAddressList[j]) {
|
||||||
|
// Always remove the later occurrence
|
||||||
|
uniqueAddressList.remove(j);
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must have at least 1 address
|
||||||
|
Q_ASSERT(!uniqueAddressList.isEmpty());
|
||||||
|
|
||||||
|
return uniqueAddressList;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NvComputer::update(NvComputer& that)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
// Lock us for write and them for read
|
||||||
|
QWriteLocker thisLock(&this->lock);
|
||||||
|
QReadLocker thatLock(&that.lock);
|
||||||
|
|
||||||
|
// UUID may not change or we're talking to a new PC
|
||||||
|
Q_ASSERT(this->uuid == that.uuid);
|
||||||
|
|
||||||
|
#define ASSIGN_IF_CHANGED(field) \
|
||||||
|
if (this->field != that.field) { \
|
||||||
|
this->field = that.field; \
|
||||||
|
changed = true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSIGN_IF_CHANGED_AND_NONEMPTY(field) \
|
||||||
|
if (!that.field.isEmpty() && \
|
||||||
|
this->field != that.field) { \
|
||||||
|
this->field = that.field; \
|
||||||
|
changed = true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSIGN_IF_CHANGED(name);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
|
||||||
|
ASSIGN_IF_CHANGED(pairState);
|
||||||
|
ASSIGN_IF_CHANGED(serverCodecModeSupport);
|
||||||
|
ASSIGN_IF_CHANGED(currentGameId);
|
||||||
|
ASSIGN_IF_CHANGED(activeAddress);
|
||||||
|
ASSIGN_IF_CHANGED(state);
|
||||||
|
ASSIGN_IF_CHANGED(gfeVersion);
|
||||||
|
ASSIGN_IF_CHANGED(appVersion);
|
||||||
|
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||||
|
ASSIGN_IF_CHANGED(gpuModel);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(appList);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(displayModes);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nvhttp.h"
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
#include <QReadWriteLock>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QRunnable>
|
||||||
|
|
||||||
|
class NvComputer
|
||||||
|
{
|
||||||
|
friend class PcMonitorThread;
|
||||||
|
friend class ComputerManager;
|
||||||
|
friend class PendingQuitTask;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void sortAppList();
|
||||||
|
|
||||||
|
bool pendingQuit;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NvComputer(QString address, QString serverInfo);
|
||||||
|
|
||||||
|
explicit NvComputer(QSettings& settings);
|
||||||
|
|
||||||
|
bool
|
||||||
|
update(NvComputer& that);
|
||||||
|
|
||||||
|
bool
|
||||||
|
wake();
|
||||||
|
|
||||||
|
QVector<QString>
|
||||||
|
uniqueAddresses();
|
||||||
|
|
||||||
|
void
|
||||||
|
serialize(QSettings& settings);
|
||||||
|
|
||||||
|
enum PairState
|
||||||
|
{
|
||||||
|
PS_UNKNOWN,
|
||||||
|
PS_PAIRED,
|
||||||
|
PS_NOT_PAIRED
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ComputerState
|
||||||
|
{
|
||||||
|
CS_UNKNOWN,
|
||||||
|
CS_ONLINE,
|
||||||
|
CS_OFFLINE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ephemeral traits
|
||||||
|
ComputerState state;
|
||||||
|
PairState pairState;
|
||||||
|
QString activeAddress;
|
||||||
|
int currentGameId;
|
||||||
|
QString gfeVersion;
|
||||||
|
QString appVersion;
|
||||||
|
QVector<NvDisplayMode> displayModes;
|
||||||
|
int maxLumaPixelsHEVC;
|
||||||
|
int serverCodecModeSupport;
|
||||||
|
QString gpuModel;
|
||||||
|
|
||||||
|
// Persisted traits
|
||||||
|
QString localAddress;
|
||||||
|
QString remoteAddress;
|
||||||
|
QString manualAddress;
|
||||||
|
QByteArray macAddress;
|
||||||
|
QString name;
|
||||||
|
QString uuid;
|
||||||
|
QVector<NvApp> appList;
|
||||||
|
|
||||||
|
// Synchronization
|
||||||
|
QReadWriteLock lock;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user