From 9a20e7cbea82f31acd1976879b36b0e3a15e216e Mon Sep 17 00:00:00 2001 From: Burak Hancerli <burak.hancerli@qt.io> Date: Fri, 17 Nov 2023 13:35:22 +0100 Subject: [PATCH] add: network connection between DS and DV --- src/backend.cpp | 90 +++++++++++++++++++++++++-- src/backend.h | 7 ++- src/dsConnector.cpp | 108 ++++++++++++++++++++++++-------- src/dsConnector.h | 29 +++++++-- src/projectManager.cpp | 138 +++++++++++++++++++++-------------------- ui/Network.qml | 30 ++++++++- 6 files changed, 294 insertions(+), 108 deletions(-) diff --git a/src/backend.cpp b/src/backend.cpp index a1ebd29..8a1fcca 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -80,12 +80,6 @@ void Backend::initialize() this, &Backend::popupTextChanged); - connect(&m_designStudioConnector, - &DesignStudioConnector::networkStatusUpdated, - this, - &Backend::networkUpdated); - m_designStudioConnector.initialize(); - qDebug("Initialization complete"); } @@ -103,6 +97,90 @@ void Backend::updatePopup(const QString &text, bool indeterminate) QEventLoop().processEvents(QEventLoop::AllEvents, 100); } +bool Backend::connectDesignStudio() +{ + if (m_designStudioConnector) { + qDebug() << "Design Studio Connector is already initialized"; + return true; + } + + qDebug() << "Initializing Design Studio Connector"; + + connect( + &m_dsConnectorThread, + &QThread::started, + this, + [&] { + m_designStudioConnector.reset(new DesignStudioConnector); + connect(m_designStudioConnector.data(), + &DesignStudioConnector::networkStatusUpdated, + this, + &Backend::networkUpdated); + + connect(m_designStudioConnector.data(), + &DesignStudioConnector::projectReceived, + &m_projectManager, + [this](const QByteArray &projectData) { + qDebug() << "Project received from Design Studio"; + emit popupOpen(); + updatePopup("Unpacking project..."); + qDebug() << "Project data size: " << projectData.size(); + const QString projectPath = m_projectManager.unpackProject(projectData); + + if (projectPath.isEmpty()) { + showWarning("Could not unpack project. Please check the logs for more " + "information."); + emit popupClose(); + return; + } + + qDebug() << "Project unpacked to " << projectPath; + updatePopup("Running project..."); + + if (!m_projectManager.runProject(projectPath, "UntitledProject35")) { + showWarning("Could not run project. Please check the logs for more " + "information."); + } else { + m_projectManager.showAppWindow(); + } + emit popupClose(); + }); + if (!m_designStudioConnector->initialize()) { + showWarning("Could initialize server. Please check the logs for more information."); + m_designStudioConnector.reset(); + } + }, + Qt::DirectConnection); + + m_dsConnectorThread.start(); + + qDebug() << "Design Studio Connector is initialized"; + return true; +} + +void Backend::disconnectDesignStudio() +{ + if (!m_designStudioConnector) { + qDebug() << "Design Studio Connector is not initialized"; + return; + } + + qDebug() << "Disconnecting from Design Studio"; + + connect( + &m_dsConnectorThread, + &QThread::finished, + this, + [&] { + qDebug() << "Design Studio Connector is disconnected"; + m_designStudioConnector.reset(); + }, + Qt::DirectConnection); + + m_dsConnectorThread.quit(); + m_dsConnectorThread.wait(); +} + void Backend::runDemoProject(const QString &projectName) { qDebug() << "Checking if demo project is cached for " << projectName; diff --git a/src/backend.h b/src/backend.h index 6227665..9f3d7c6 100644 --- a/src/backend.h +++ b/src/backend.h @@ -26,6 +26,8 @@ #ifndef DV_ANDROID_H #define DV_ANDROID_H +#include <QThread> + #include "dsConnector.h" #include "projectManager.h" #include "serviceConnector.h" @@ -58,7 +60,8 @@ private: QString m_userHash; ServiceConnector m_serviceConnector; ProjectManager m_projectManager; - DesignStudioConnector m_designStudioConnector; + QScopedPointer<DesignStudioConnector> m_designStudioConnector; + QThread m_dsConnectorThread; // member functions void showWarning(const QString &message); @@ -83,6 +86,8 @@ public slots: void runDemoProject(const QString &projectName); void clearDemoCaches(); void registerUser(const QUrl &url); + bool connectDesignStudio(); + void disconnectDesignStudio(); }; #endif // DV_ANDROID_H diff --git a/src/dsConnector.cpp b/src/dsConnector.cpp index 726555f..93785da 100644 --- a/src/dsConnector.cpp +++ b/src/dsConnector.cpp @@ -27,57 +27,111 @@ #include <QNetworkInterface> -DesignStudioConnector::DesignStudioConnector(QObject *parent) - : QObject(parent) - , m_socket(this) - , m_broadcastTimer(this) -{} +void DesignStudioConnector::receiveProject() +{ + qDebug() << "Reading data from Design Studio"; + emit networkStatusUpdated("Reading data from Design Studio"); + QByteArray data = m_tcpSocket->readAll(); + qDebug() << "Data from Design Studio:" << data; + // if (data.startsWith(":qmlrc_project_starts:")) { + // qDebug() << "Received qmlrc_project"; + // emit networkStatusUpdated("Received qmlrc_project"); + // m_projectData.append(data.mid(21, data.size() - 21)); + // m_receivingData = true; + // } else if (data.contains(":qmlrc_project_ends:")) { + // qDebug() << "Received qmlrc_project_ends"; + // emit networkStatusUpdated("Received qmlrc_project_ends"); + // m_projectData.append(data.mid(0, data.size() - 20)); + // emit projectReceived(m_projectData); + // m_projectData.clear(); + // m_receivingData = false; + // } else if (m_receivingData) { + // qDebug() << "Received data:" << data; + // emit networkStatusUpdated("Receiving project data"); + // m_projectData.append(data); + // } else { + // qDebug() << "Received unknown data:" << data; + // emit networkStatusUpdated("Received unknown data"); + // } + m_projectData.append(data); +} -bool DesignStudioConnector::initialize() +bool DesignStudioConnector::initTcpServer() { - // const bool retVal = m_socket.bind(39000); + const bool retVal = m_tcpServer.listen(QHostAddress::Any, m_tcpPort); - // if (!retVal) { - // qDebug() << "Failed to bind socket"; - // emit networkStatusUpdated("Failed to bind socket on port 39000"); - // return false; - // } + if (!retVal) { + qDebug() << "Failed to listen on port " << m_tcpPort; + emit networkStatusUpdated("Failed to bind on port " + QString::number(m_tcpPort)); + return false; + } + + qDebug() << "Listening on port " << m_tcpPort; + connect(&m_tcpServer, &QTcpServer::newConnection, this, [this]() { + qDebug() << "New connection from Design Studio"; + emit networkStatusUpdated("Design Studio is connected.\n Waiting for project..."); + m_tcpSocket.reset(m_tcpServer.nextPendingConnection()); + m_broadcastTimer.stop(); + + connect(m_tcpSocket.data(), &QTcpSocket::disconnected, this, [this]() { + qDebug() << "Disconnected from Design Studio"; + emit networkStatusUpdated("\nLocal IP: " + m_ipv4Addr + + "\nWaiting for Design Studio to connect..."); + m_broadcastTimer.start(); + m_receivingData = false; + emit projectReceived(m_projectData); + m_projectData.clear(); + }); + + connect(m_tcpSocket.data(), + &QTcpSocket::readyRead, + this, + &DesignStudioConnector::receiveProject); + }); + + return true; +} +void DesignStudioConnector::initBroadcast() +{ // get ipv4 address - QString ipv4Addr; const QList<QHostAddress> list = QNetworkInterface::allAddresses(); for (const QHostAddress &address : list) { if (address.protocol() == QAbstractSocket::IPv4Protocol && address != QHostAddress::LocalHost) { qDebug() << "Found IPv4 address:" << address.toString(); - ipv4Addr = address.toString(); + m_ipv4Addr = address.toString(); break; } } - // connect(&m_socket, &QUdpSocket::readyRead, this, &DesignStudioConnector::readPendingDatagrams); - qDebug() << "Advertising from" << ipv4Addr; - emit networkStatusUpdated("\nLocal IP: " + ipv4Addr + qDebug() << "Advertising from" << m_ipv4Addr; + emit networkStatusUpdated("\nLocal IP: " + m_ipv4Addr + "\nWaiting for Design Studio to connect..."); // Broadcast a message to the network in each 2 seconds // to let the Design Studio know that we are here connect(&m_broadcastTimer, &QTimer::timeout, this, [this]() { - QByteArray datagram = "Qt Design Viewer"; - m_socket.writeDatagram(datagram.data(), datagram.size(), QHostAddress::Broadcast, 39000); + QByteArray datagram = "qt_ui_viewer_at:" + m_ipv4Addr.toUtf8() + ":" + + QByteArray::number(m_tcpPort); + + m_udpSocket.writeDatagram(datagram.data(), + datagram.size(), + QHostAddress::Broadcast, + m_udpPort); + qDebug() << "Broadcasting datagram:" << datagram; }); + m_broadcastTimer.setSingleShot(false); m_broadcastTimer.start(2000); - return true; } -void DesignStudioConnector::readPendingDatagrams() +bool DesignStudioConnector::initialize() { - while (m_socket.hasPendingDatagrams()) { - QByteArray datagram; - datagram.resize(m_socket.pendingDatagramSize()); - m_socket.readDatagram(datagram.data(), datagram.size()); - qDebug() << "Received datagram:" << datagram; - } + if (!initTcpServer()) + return false; + + initBroadcast(); + return true; } diff --git a/src/dsConnector.h b/src/dsConnector.h index 5857e38..5e577cc 100644 --- a/src/dsConnector.h +++ b/src/dsConnector.h @@ -27,6 +27,8 @@ #define DSCONNECTOR_H #include <QObject> +#include <QTcpServer> +#include <QTcpSocket> #include <QTimer> #include <QUdpSocket> @@ -34,18 +36,35 @@ class DesignStudioConnector : public QObject { Q_OBJECT public: - explicit DesignStudioConnector(QObject *parent = nullptr); + explicit DesignStudioConnector(QObject *parent = nullptr) + : QObject(parent) + {} bool initialize(); -private slots: - void readPendingDatagrams(); - private: - QUdpSocket m_socket; + // Tcp connection members + QTcpServer m_tcpServer; + QScopedPointer<QTcpSocket> m_tcpSocket; + const quint32 m_tcpPort = 40000; + + // Udp connection members + QUdpSocket m_udpSocket; QTimer m_broadcastTimer; + QString m_ipv4Addr; + const quint32 m_udpPort = 39000; + + // Other members + QByteArray m_projectData; + bool m_receivingData; + + // Member functions + bool initTcpServer(); + void initBroadcast(); + void receiveProject(); signals: void networkStatusUpdated(QString); + void projectReceived(QByteArray); }; #endif // DSCONNECTOR_H diff --git a/src/projectManager.cpp b/src/projectManager.cpp index fc0249d..f9c25f1 100644 --- a/src/projectManager.cpp +++ b/src/projectManager.cpp @@ -213,11 +213,16 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFilePath); QFile file(mainQmlUrl.path()); - // QFile file(mainQmlUrl.path().prepend(":")); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Could not open mainQmlfile for reading! " << file.fileName() << ": " - << file.errorString(); - return false; + qWarning() << "Could not open mainQmlfile for reading! " << file.fileName() << ": " + << file.errorString(); + qWarning() << "Trying to open it as a resource file."; + file.setFileName(mainQmlUrl.path().prepend(":")); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Could not open mainQmlfile for reading! " << file.fileName() << ": " + << file.errorString(); + return false; + } } qDebug() << "Looking for qtquickcontrols2File in " << projectPath; @@ -226,75 +231,76 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje if (!qtquickcontrols2File.isEmpty()) { qDebug() << "Found qtquickcontrols2File: " << qtquickcontrols2File; qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1()); - } + } - qDebug() << "Initializing the qmlEngine"; - m_qmlEngine.reset(new QQmlEngine); - m_qmlEngine->clearComponentCache(); + qDebug() << "Initializing the qmlEngine"; + m_qmlEngine.reset(new QQmlEngine); + m_qmlEngine->clearComponentCache(); - qDebug("Adding import paths"); - for (const QString &importPath : importPaths) { - qDebug() << "-- Import path: " << importPath; - m_qmlEngine->addImportPath(importPath); - } + qDebug("Adding import paths"); + for (const QString &importPath : importPaths) { + qDebug() << "-- Import path: " << importPath; + m_qmlEngine->addImportPath(importPath); + } - QObject::connect(m_qmlEngine.data(), - &QQmlEngine::warnings, - this, - [&](const QList<QQmlError> &warnings) { - for (const auto &warning : warnings) { - qWarning() << warning.toString(); - } - }); - - emit projectStateChanged("Loading project..."); - QEventLoop().processEvents(QEventLoop::AllEvents, 1000); - - qDebug() << "Loading mainQmlUrl: " << mainQmlUrl.toString(); - m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.data())); - m_qmlComponent->loadUrl(mainQmlUrl); - - qDebug() << "Waiting for qmlComponent to load"; - while (m_qmlComponent->isLoading()) - QCoreApplication::processEvents(); - - qDebug() << "Checking if m_qmlComponent is ready"; - if (!m_qmlComponent->isReady()) { - qCritical() << "m_qmlComponent is not ready. Reason:" << m_qmlComponent->errorString(); - return false; - } - qDebug() << "Creating top level object"; - QObject *topLevel = m_qmlComponent->create(); - if (!topLevel && m_qmlComponent->isError()) { - qCritical() << "Error while creating Qml m_qmlComponent:" << m_qmlComponent->errorString(); - return false; - } + QObject::connect(m_qmlEngine.data(), + &QQmlEngine::warnings, + this, + [&](const QList<QQmlError> &warnings) { + for (const auto &warning : warnings) { + qWarning() << warning.toString(); + } + }); + + emit projectStateChanged("Loading project..."); + QEventLoop().processEvents(QEventLoop::AllEvents, 1000); + + qDebug() << "Loading mainQmlUrl: " << mainQmlUrl.toString(); + m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.data())); + m_qmlComponent->loadUrl(mainQmlUrl); + + qDebug() << "Waiting for qmlComponent to load"; + while (m_qmlComponent->isLoading()) + QCoreApplication::processEvents(); + + qDebug() << "Checking if m_qmlComponent is ready"; + if (!m_qmlComponent->isReady()) { + qCritical() << "m_qmlComponent is not ready. Reason:" << m_qmlComponent->errorString(); + return false; + } + qDebug() << "Creating top level object"; + QObject *topLevel = m_qmlComponent->create(); + if (!topLevel && m_qmlComponent->isError()) { + qCritical() << "Error while creating Qml m_qmlComponent:" + << m_qmlComponent->errorString(); + return false; + } - emit projectStateChanged("Setting up the quickWindow..."); - QEventLoop().processEvents(QEventLoop::AllEvents, 1000); + emit projectStateChanged("Setting up the quickWindow..."); + QEventLoop().processEvents(QEventLoop::AllEvents, 1000); - qDebug() << "Setting up the quickWindow"; - m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel)); - if (m_quickWindow) { - qDebug() << "Running with incubator controller"; - m_qmlEngine->setIncubationController(m_quickWindow->incubationController()); - } else { - qWarning() << "Top level object is not a QQuickWindow. Trying QQuickView..."; + qDebug() << "Setting up the quickWindow"; + m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel)); + if (m_quickWindow) { + qDebug() << "Running with incubator controller"; + m_qmlEngine->setIncubationController(m_quickWindow->incubationController()); + } else { + qWarning() << "Top level object is not a QQuickWindow. Trying QQuickView..."; - QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel); - if (!contentItem) { - qCritical() << "Top level object cannot be casted to QQuickItem. Aborting."; - return false; - } + QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel); + if (!contentItem) { + qCritical() << "Top level object cannot be casted to QQuickItem. Aborting."; + return false; + } - qDebug() << "Initializing QQuickView"; - QQuickView *view = new QQuickView(m_qmlEngine.data(), nullptr); - m_quickWindow.reset(view); - view->setContent(mainQmlUrl, m_qmlComponent.data(), contentItem); - view->setResizeMode(QQuickView::SizeViewToRootObject); - m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height())); - } - return true; + qDebug() << "Initializing QQuickView"; + QQuickView *view = new QQuickView(m_qmlEngine.data(), nullptr); + m_quickWindow.reset(view); + view->setContent(mainQmlUrl, m_qmlComponent.data(), contentItem); + view->setResizeMode(QQuickView::SizeViewToRootObject); + m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height())); + } + return true; } bool ProjectManager::cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo) diff --git a/ui/Network.qml b/ui/Network.qml index 88b3130..e2ec0f4 100644 --- a/ui/Network.qml +++ b/ui/Network.qml @@ -22,17 +22,18 @@ Item { id: infoHeader Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter horizontalAlignment: "AlignHCenter" - text: qsTr("Design Studio connection") + text: qsTr("Design Studio Connection Menu") } Label { - id: status + id: statusLabel Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter horizontalAlignment: "AlignHCenter" + text: "Click the button below to start\nadvertising this device on the network" Connections { target: backend function onNetworkUpdated(newStatus){ - status.text = newStatus + statusLabel.text = newStatus } } } @@ -42,5 +43,28 @@ Item { Layout.fillHeight: true Layout.fillWidth: true } + + Button { + property bool isRunning: false + id: startConnection + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.fillWidth: true + height: 100 + text: qsTr("Start Connection") + onClicked: { + if (isRunning) { + backend.disconnectDesignStudio() + isRunning = false + text = qsTr("Start Connection") + statusLabel.text = "Click the button below to start\nadvertising this device on the network" + } else { + let retval = backend.connectDesignStudio() + if (retval) { + text = qsTr("Stop Connection") + isRunning = true + } + } + } + } } } -- GitLab