diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index 91b15d11..f430669f 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #define SER_HOSTS "hosts" #define SER_NAME "hostname" @@ -331,27 +332,54 @@ QVector ComputerManager::getComputers() return QVector::fromList(m_KnownHosts.values()); } -void ComputerManager::deleteHost(NvComputer* computer) +class DeferredHostDeletionTask : public QRunnable { - QWriteLocker lock(&m_Lock); +public: + DeferredHostDeletionTask(ComputerManager* cm, NvComputer* computer) + : m_Computer(computer), + m_ComputerManager(cm) {} - QThread* pollingThread = m_PollThreads[computer->uuid]; - if (pollingThread != nullptr) { - pollingThread->requestInterruption(); + void run() + { + QWriteLocker lock(&m_ComputerManager->m_Lock); - // We must wait here because we're going to delete computer - // and we can't do that out from underneath the poller. - pollingThread->wait(); + QThread* pollingThread = m_ComputerManager->m_PollThreads[m_Computer->uuid]; + if (pollingThread != nullptr) { + pollingThread->requestInterruption(); - // The thread is safe to delete now - Q_ASSERT(pollingThread->isFinished()); - delete pollingThread; + // We must wait here because we're going to delete computer + // and we can't do that out from underneath the poller. + pollingThread->wait(); + + // The thread is safe to delete now + Q_ASSERT(pollingThread->isFinished()); + delete pollingThread; + } + + m_ComputerManager->m_PollThreads.remove(m_Computer->uuid); + m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid); + + delete m_Computer; } - m_PollThreads.remove(computer->uuid); - m_KnownHosts.remove(computer->uuid); +private: + NvComputer* m_Computer; + ComputerManager* m_ComputerManager; +}; - delete computer; +void ComputerManager::deleteHost(NvComputer* computer) +{ + // Punt to a worker thread to avoid stalling the + // UI while waiting for the polling thread to die + QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer)); +} + +void ComputerManager::pairHost(NvComputer* computer, QString pin) +{ + // Punt to a worker thread to avoid stalling the + // UI while waiting for pairing to complete + PendingPairingTask* pairing = new PendingPairingTask(this, computer, pin); + QThreadPool::globalInstance()->start(pairing); } void ComputerManager::stopPollingAsync() diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 517dff3a..388ae258 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -1,5 +1,6 @@ #pragma once #include "nvhttp.h" +#include "nvpairingmanager.h" #include #include @@ -10,6 +11,7 @@ #include #include #include +#include class NvComputer { @@ -239,6 +241,8 @@ class ComputerManager : public QObject { Q_OBJECT + friend class DeferredHostDeletionTask; + public: explicit ComputerManager(QObject *parent = nullptr); @@ -248,6 +252,8 @@ public: Q_INVOKABLE bool addNewHost(QString address, bool mdns); + void pairHost(NvComputer* computer, QString pin); + QVector getComputers(); // computer is deleted inside this call @@ -256,6 +262,8 @@ public: signals: void computerStateChanged(NvComputer* computer); + void pairingCompleted(NvComputer* computer, QString error); + private slots: void handleComputerStateChanged(NvComputer* computer); @@ -275,3 +283,54 @@ private: QMdnsEngine::Cache m_MdnsCache; QVector m_PendingResolution; }; + +class PendingPairingTask : public QObject, public QRunnable +{ + Q_OBJECT + +public: + PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin) + : m_ComputerManager(computerManager), + 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, + QString::fromLocal8Bit(e.getStatusMessage()) + + " (Error " + QString::number(e.getStatusCode())); + } + } + + ComputerManager* m_ComputerManager; + NvComputer* m_Computer; + QString m_Pin; +}; diff --git a/app/backend/nvhttp.cpp b/app/backend/nvhttp.cpp index e8721c91..ebcc9248 100644 --- a/app/backend/nvhttp.cpp +++ b/app/backend/nvhttp.cpp @@ -25,9 +25,8 @@ NvHTTP::NvHTTP(QString address) : } QVector -NvHTTP::getServerVersionQuad(QString serverInfo) +NvHTTP::parseQuad(QString quad) { - QString quad = getXmlString(serverInfo, "appversion"); QStringList parts = quad.split("."); QVector ret; diff --git a/app/backend/nvhttp.h b/app/backend/nvhttp.h index 717cf728..f179302d 100644 --- a/app/backend/nvhttp.h +++ b/app/backend/nvhttp.h @@ -91,7 +91,7 @@ public: static QVector - getServerVersionQuad(QString serverInfo); + parseQuad(QString quad); void quitApp(); diff --git a/app/backend/nvpairingmanager.cpp b/app/backend/nvpairingmanager.cpp index 76073b88..4a6e14c3 100644 --- a/app/backend/nvpairingmanager.cpp +++ b/app/backend/nvpairingmanager.cpp @@ -42,12 +42,6 @@ NvPairingManager::~NvPairingManager() EVP_PKEY_free(m_PrivateKey); } -QString -NvPairingManager::generatePinString() -{ - return QString::asprintf("%04d", QRandomGenerator::global()->bounded(10000)); -} - QByteArray NvPairingManager::generateRandomBytes(int length) { @@ -167,9 +161,9 @@ NvPairingManager::saltPin(QByteArray salt, QString pin) } NvPairingManager::PairState -NvPairingManager::pair(QString serverInfo, QString pin) +NvPairingManager::pair(QString appVersion, QString pin) { - int serverMajorVersion = NvHTTP::getServerVersionQuad(serverInfo).at(0); + int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0); qDebug() << "Pairing with server generation: " << serverMajorVersion; QCryptographicHash::Algorithm hashAlgo; diff --git a/app/backend/nvpairingmanager.h b/app/backend/nvpairingmanager.h index 9600dffb..20b2cc34 100644 --- a/app/backend/nvpairingmanager.h +++ b/app/backend/nvpairingmanager.h @@ -23,11 +23,8 @@ public: ~NvPairingManager(); - QString - generatePinString(); - PairState - pair(QString serverInfo, QString pin); + pair(QString appVersion, QString pin); private: QByteArray diff --git a/app/gui/PcView.qml b/app/gui/PcView.qml index 7efb221e..0bdfc435 100644 --- a/app/gui/PcView.qml +++ b/app/gui/PcView.qml @@ -8,6 +8,8 @@ import ComputerModel 1.0 import ComputerManager 1.0 GridView { + property ComputerModel computerModel : createModel() + anchors.fill: parent anchors.leftMargin: 5 anchors.topMargin: 5 @@ -26,10 +28,26 @@ GridView { ComputerManager.stopPollingAsync() } + function pairingComplete(error) + { + // Close the PIN dialog + pairDialog.close() + + // Start polling again + ComputerManager.startPolling() + + // Display a failed dialog if we got an error + if (error !== null) { + pairingFailedDialog.text = error + pairingFailedDialog.open() + } + } + function createModel() { var model = Qt.createQmlObject('import ComputerModel 1.0; ComputerModel {}', parent, '') model.initialize(ComputerManager) + model.pairingCompleted.connect(pairingComplete) return model } @@ -114,12 +132,20 @@ GridView { else { if (!model.busy) { var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4) + + // Stop polling, since pairing may make GFE unresponsive + ComputerManager.stopPollingAsync() + + // Kick off pairing in the background + computerModel.pairComputer(index, pin) + + // Display the pairing dialog pairDialog.pin = pin - // TODO: initiate pairing request pairDialog.open() } else { // cannot pair while something is streaming or attempting to pair + pairingFailedDialog.text = "This PC is currently busy. Make sure to quit any running games and try again." pairingFailedDialog.open() } } @@ -135,8 +161,7 @@ GridView { id: pairingFailedDialog // don't allow edits to the rest of the window while open modality:Qt.WindowModal - - text:"This PC is busy: make sure no game is streaming, and try again" + icon: StandardIcon.Critical standardButtons: StandardButton.Ok } diff --git a/app/gui/computermodel.cpp b/app/gui/computermodel.cpp index 975ecf40..09689621 100644 --- a/app/gui/computermodel.cpp +++ b/app/gui/computermodel.cpp @@ -1,5 +1,4 @@ #include "computermodel.h" -#include "backend/nvpairingmanager.h" ComputerModel::ComputerModel(QObject* object) : QAbstractListModel(object) {} @@ -9,6 +8,8 @@ void ComputerModel::initialize(ComputerManager* computerManager) m_ComputerManager = computerManager; connect(m_ComputerManager, &ComputerManager::computerStateChanged, this, &ComputerModel::handleComputerStateChanged); + connect(m_ComputerManager, &ComputerManager::pairingCompleted, + this, &ComputerModel::handlePairingCompleted); m_Computers = m_ComputerManager->getComputers(); } @@ -92,6 +93,18 @@ void ComputerModel::deleteComputer(int computerIndex) endRemoveRows(); } +void ComputerModel::pairComputer(int computerIndex, QString pin) +{ + Q_ASSERT(computerIndex < m_Computers.count()); + + m_ComputerManager->pairHost(m_Computers[computerIndex], pin); +} + +void ComputerModel::handlePairingCompleted(NvComputer*, QString error) +{ + emit pairingCompleted(error.isNull() ? QVariant() : error); +} + void ComputerModel::handleComputerStateChanged(NvComputer* computer) { // If this is an existing computer, we can report the data changed diff --git a/app/gui/computermodel.h b/app/gui/computermodel.h index a152a6b7..a173dd17 100644 --- a/app/gui/computermodel.h +++ b/app/gui/computermodel.h @@ -29,9 +29,16 @@ public: Q_INVOKABLE void deleteComputer(int computerIndex); + Q_INVOKABLE void pairComputer(int computerIndex, QString pin); + +signals: + void pairingCompleted(QVariant error); + private slots: void handleComputerStateChanged(NvComputer* computer); + void handlePairingCompleted(NvComputer* computer, QString error); + private: QVector m_Computers; ComputerManager* m_ComputerManager;