/**************************************************************************** ** ** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Design Viewer of the Qt Toolkit. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "backend.h" #include <QDesktopServices> #include <QNetworkInterface> #ifdef Q_OS_ANDROID #include <QJniObject> #endif #include "logger.h" #ifdef QT_DEBUG #define buildType "Debug" #else #define buildType "Release" #endif #define FLAG_KEEP_SCREEN_ON 0x00000080 Backend::Backend(QObject *parent) : QObject(parent) { // register the `qtdesignstudio` url handler to the Android system QDesktopServices::setUrlHandler("qtdesignstudio", this, "parseDesignViewerUrl"); if (m_settings.deviceUuid().isEmpty()) { qDebug() << "Device UUID not found. Generating a new one."; m_settings.setDeviceUuid(QUuid::createUuid().toString(QUuid::WithoutBraces)); } m_dsManagerThread.setParent(this); connect(&m_dsManagerThread, &QThread::started, this, &Backend::initDsManager); m_dsManagerThread.start(); m_projectManagerThread.setParent(this); connect(&m_projectManagerThread, &QThread::started, this, &Backend::initProjectManager); m_projectManagerThread.start(); connect(&Logger::instance(), &Logger::logMessage, this, [this](QtMsgType type, QString &msg) { // if we have any active project running, then reroute // all the logs to the dsmanager with the last project sender id if (m_projectManager && !m_lastSessionId.isEmpty() && m_lastSessionId == m_projectManager->sessionId()) { QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectLogs", Qt::QueuedConnection, Q_ARG(QString, m_lastProjectSenderId), Q_ARG(QString, msg)); } }); const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); qDebug() << "Qt Design Viewer"; qDebug() << "System information:"; qDebug() << "-- Qt version: " << QT_VERSION_STR; qDebug() << "-- OpenSSL support: " << QVariant(QSslSocket::supportsSsl()).toString(); qDebug() << "-- Screen height: " << QString::number(screenGeometry.height()); qDebug() << "-- Screen width: " << QString::number(screenGeometry.width()); qDebug() << "-- OS: " << QSysInfo::prettyProductName(); qDebug() << "-- OS version: " << QSysInfo::productVersion(); qDebug() << "-- Architecture: " << QSysInfo::currentCpuArchitecture(); qDebug() << "-- Boot ID: " << QSysInfo::bootUniqueId(); qDebug() << "-- Kernel type: " << QSysInfo::kernelType(); qDebug() << "-- Kernel version: " << QSysInfo::kernelVersion(); qDebug() << "-- Machine unique ID: " << QSysInfo::machineUniqueId(); qDebug() << "-- Product type: " << QSysInfo::productType(); qDebug() << "-- Product version: " << QSysInfo::productVersion(); qDebug() << "-- Build ABI: " << QSysInfo::buildAbi(); qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture(); qDebug() << "-- Device unique ID: " << m_settings.deviceUuid(); setAndroidScreenOn(keepScreenOn()); } Backend::~Backend() { m_dsManagerThread.quit(); m_projectManagerThread.quit(); m_dsManagerThread.wait(); m_projectManagerThread.wait(); } QString Backend::buildInfo() const { // clang-format off return { QCoreApplication::applicationVersion() + "\n"+ CMAKE_VAR_GIT_VERSION + "\nQt " + QT_VERSION_STR + " - " + buildType + " Build" + "\nQt Quick Components " + CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION + "\nZXing-Cpp: " + CMAKE_VAR_ZXING_VERSION + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()}; // clang-format on } void Backend::updatePopupText(const QString &text, int timeout) { emit popupOpen(); emit popupChangeText(text, timeout); emit popupProgressIndeterminateChanged(true); QEventLoop().processEvents(QEventLoop::AllEvents, 1000); } void Backend::updatePopupProgress(const int progress) { emit popupOpen(); emit popupProgressIndeterminateChanged(false); emit popupChangeProgress(progress); QEventLoop().processEvents(QEventLoop::AllEvents, 1000); } void Backend::initProjectManager() { m_projectManager.reset(new ProjectManager(this)); connect(m_projectManager.get(), &ProjectManager::closingProject, this, [this](const QString &sessionId) { // if seesion ids are same, it's most likely the project was running // from the leftover session (DS connected, project started, // DS disconnected without stopping the project) // so we'll stop the project and do not send any signals to the DS if (sessionId != m_lastSessionId) return; QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Qt::QueuedConnection, Q_ARG(QString, m_lastProjectSenderId)); }); } void Backend::initDsManager() { qDebug() << "Design Studio Manager thread started. Initializing Design Studio Manager"; m_dsManager.reset(new DesignStudioManager(m_settings.deviceUuid(), this)); connect(m_dsManager.get(), &DesignStudioManager::projectReceived, this, &Backend::runProject); connect(m_dsManager.get(), &DesignStudioManager::designStudioConnected, this, [this](const QString &id, const QString &ipAddr) { emit connectedChanged(true); }); connect(m_dsManager.get(), &DesignStudioManager::designStudioDisconnected, this, [this](const QString &id, const QString &ipAddr) { if (id == m_lastProjectSenderId) { QMetaObject::invokeMethod(m_projectManager.get(), "stopProject"); } emit popupClose(); }); connect(m_dsManager.get(), &DesignStudioManager::allDesignStudiosDisconnected, this, [this] { qDebug() << "All Design Studios disconnected"; emit connectedChanged(false); }); connect(m_dsManager.get(), &DesignStudioManager::projectIncoming, this, [this](const QString &id, const int projectSize) { qDebug() << "Project incoming with size" << projectSize; emit updatePopupText("Receiving project..."); // we'll use this to notify the correct DS when the project started/stopped m_lastProjectSenderId = id; m_lastSessionId = QUuid::createUuid().toString(QUuid::WithoutBraces); QMetaObject::invokeMethod(m_projectManager.get(), "stopProject", Qt::QueuedConnection); }); connect(m_dsManager.get(), &DesignStudioManager::projectIncomingProgress, this, [this](const QString &id, const int percentage) { updatePopupProgress(percentage); }); connect(m_dsManager.get(), &DesignStudioManager::projectStopRequested, this, [this](const QString &id) { qDebug() << "Project stop requested"; emit popupClose(); QMetaObject::invokeMethod(m_projectManager.get(), "stopProject", Qt::QueuedConnection); QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Qt::QueuedConnection, Q_ARG(QString, m_lastProjectSenderId)); }); m_dsManager->init(); qDebug() << "Design Studio Manager initialized"; } void Backend::connectDesignStudio(const QString &ipAddr) { QMetaObject::invokeMethod(m_dsManager.get(), "initDesignStudio", Q_ARG(QString, ipAddr), Q_ARG(QString, "")); emit updatePopupText("Connecting in the background...", 1500); } void Backend::runProject(const QString &id, const QByteArray &projectData) { emit updatePopupText("Running project..."); QTimer::singleShot(1000, [this, id, projectData] { bool retVal; QMetaObject::invokeMethod(m_projectManager.get(), "runProject", Q_RETURN_ARG(bool, retVal), Q_ARG(QByteArray, projectData), Q_ARG(bool, autoScaleProject()), Q_ARG(QString, m_lastSessionId)); if (!retVal) QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectStopped", Q_ARG(QString, id)); else QMetaObject::invokeMethod(m_dsManager.get(), "sendProjectRunning", Q_ARG(QString, id)); emit popupClose(); }); } void Backend::scanQrCode() { m_qrScanner.reset(new QrScanner); connect(m_qrScanner.get(), &QrScanner::qrCodeScanned, this, [&](const QString &qrCode) { qDebug() << "QR code scanned:" << qrCode; m_qrScanner.reset(); parseDesignViewerUrl(QUrl(qrCode)); }); connect(m_qrScanner.get(), &QrScanner::windowClosed, this, [&]() { m_qrScanner.reset(); }); m_qrScanner->scanQrCode(); } void Backend::parseDesignViewerUrl(const QUrl &url) { if (url.scheme() != "qtdesignstudio") { qWarning() << "Unknown QR code format"; qWarning() << "URL:" << url.toString(); return; } if (url.host().isEmpty()) { qWarning() << "No Design Studio IP address found in the QR code. URL:" << url.toString(); return; } connectDesignStudio(url.host()); } void Backend::popupInterrupted() { qDebug() << "Popup closed prematurely. Interrupting active downloads (if any)"; } bool Backend::autoScaleProject() const { return m_settings.autoScaleProject(); } void Backend::setAutoScaleProject(bool autoScaleProject) { m_settings.setAutoScaleProject(autoScaleProject); } bool Backend::keepScreenOn() const { return m_settings.keepScreenOn(); } void Backend::setKeepScreenOn(bool keepScreenOn) { m_settings.setKeepScreenOn(keepScreenOn); setAndroidScreenOn(keepScreenOn); } void Backend::setAndroidScreenOn(bool on) { #ifdef Q_OS_ANDROID QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() { QJniObject activity = QNativeInterface::QAndroidApplication::context(); QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); on ? window.callMethod<void>("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON) : window.callMethod<void>("clearFlags", "(I)V", FLAG_KEEP_SCREEN_ON); }); #else Q_UNUSED(on); #endif } QString Backend::lastDesignStudioIp() const { return m_dsManager ? m_dsManager->getDesignStudioIp({}) : QString(); } QJsonArray Backend::getIpAddresses() const { QNetworkInterface networkInterface; QJsonArray ipAddresses; for (const auto &interface : networkInterface.allInterfaces()) { for (const auto &entry : interface.addressEntries()) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { ipAddresses.append( QJsonObject{{"interface", interface.name()}, {"ip", entry.ip().toString()}}); } } } return ipAddresses; }