Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • alcroito/qt-ui-viewer
  • design-studio/design-viewer/qt-ui-viewer
2 results
Show changes
Showing
with 606 additions and 232 deletions
src/android/res/mipmap-xhdpi/ic_launcher_round.webp

4.84 KiB

src/android/res/mipmap-xxhdpi/app_icon.png

13 KiB

src/android/res/mipmap-xxhdpi/ic_launcher.webp

3.79 KiB

src/android/res/mipmap-xxhdpi/ic_launcher_round.webp

7.01 KiB

src/android/res/mipmap-xxxhdpi/app_icon.png

30 KiB

src/android/res/mipmap-xxxhdpi/ic_launcher.webp

4.65 KiB

src/android/res/mipmap-xxxhdpi/ic_launcher_round.webp

9.29 KiB

<!--
~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">Qt UI Viewer</string>
</resources>
......@@ -26,6 +26,11 @@
#include "backend.h"
#include <QDesktopServices>
#include <QNetworkInterface>
#ifdef Q_OS_ANDROID
#include <QJniObject>
#endif
#include "logger.h"
......@@ -35,6 +40,8 @@
#define buildType "Release"
#endif
#define FLAG_KEEP_SCREEN_ON 0x00000080
Backend::Backend(QObject *parent)
: QObject(parent)
{
......@@ -57,15 +64,30 @@ Backend::Backend(QObject *parent)
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_projectManager->isRunning()) {
if (m_projectManager && !m_lastSessionId.isEmpty()
&& m_lastSessionId == m_projectManager->sessionId()) {
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectLogs",
&DesignStudioManager::sendProjectLogs,
Qt::QueuedConnection,
Q_ARG(QString, m_lastProjectSenderId),
Q_ARG(QString, msg));
m_lastProjectSenderId,
msg);
}
});
connect(qApp,
&QGuiApplication::applicationStateChanged,
this,
[this](Qt::ApplicationState state) {
qDebug() << "Application state changed to" << state;
if (state == Qt::ApplicationState::ApplicationSuspended) {
if (m_projectManager && !m_projectManager->sessionId().isEmpty())
QMetaObject::invokeMethod(m_projectManager.get(),
&ProjectManager::stopProject);
m_dsManager->disconnectAllDesignStudios();
popupClose();
}
});
const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
qDebug() << "Qt Design Viewer";
......@@ -86,6 +108,8 @@ Backend::Backend(QObject *parent)
qDebug() << "-- Build ABI: " << QSysInfo::buildAbi();
qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture();
qDebug() << "-- Device unique ID: " << m_settings.deviceUuid();
setAndroidScreenOn(keepScreenOn());
}
Backend::~Backend()
......@@ -101,7 +125,7 @@ QString Backend::buildInfo() const
// clang-format off
return {
QCoreApplication::applicationVersion() +
"\nTechnology Preview - "+ CMAKE_VAR_GIT_VERSION +
"\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 +
......@@ -109,10 +133,19 @@ QString Backend::buildInfo() const
// clang-format on
}
void Backend::updatePopup(const QString &text, bool indeterminate)
void Backend::updatePopupText(const QString &text, int timeout)
{
emit popupTextChanged(text);
emit popupProgressIndeterminateChanged(indeterminate);
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);
}
......@@ -120,20 +153,28 @@ void Backend::initProjectManager()
{
m_projectManager.reset(new ProjectManager(this));
connect(m_projectManager.get(), &ProjectManager::closingProject, this, [this] {
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectStopped",
Qt::QueuedConnection,
Q_ARG(QString, m_lastProjectSenderId));
});
updatePopup("Initializing Project Manager...");
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(),
&DesignStudioManager::sendProjectStopped,
Qt::QueuedConnection,
m_lastProjectSenderId);
});
}
void Backend::initDsManager()
{
qDebug() << "Design Studio Manager thread started. Initializing Design Studio Manager";
m_dsManager.reset(new DesignStudioManager(m_settings.deviceUuid(), this));
m_dsManager.reset(new DesignStudioManager(m_settings.deviceUuid()));
connect(m_dsManager.get(), &DesignStudioManager::projectReceived, this, &Backend::runProject);
......@@ -146,10 +187,9 @@ void Backend::initDsManager()
this,
[this](const QString &id, const QString &ipAddr) {
if (id == m_lastProjectSenderId) {
QMetaObject::invokeMethod(m_projectManager.get(),
"stopProject",
Qt::QueuedConnection);
QMetaObject::invokeMethod(m_projectManager.get(), &ProjectManager::stopProject);
}
emit popupClose();
});
connect(m_dsManager.get(), &DesignStudioManager::allDesignStudiosDisconnected, this, [this] {
......@@ -157,51 +197,71 @@ void Backend::initDsManager()
emit connectedChanged(false);
});
connect(m_dsManager.get(), &DesignStudioManager::projectIncoming, this, [this] {
emit popupOpen("Receiving project...");
});
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(),
&ProjectManager::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",
&ProjectManager::stopProject,
Qt::QueuedConnection);
QMetaObject::invokeMethod(m_dsManager.get(),
&DesignStudioManager::sendProjectStopped,
Qt::QueuedConnection,
m_lastProjectSenderId);
});
m_dsManager->init();
qDebug() << "Design Studio Manager initialized";
}
void Backend::connectDesignStudio(const QString &ipAddr)
{
QMetaObject::invokeMethod(m_dsManager.get(),
"initDesignStudio",
Qt::QueuedConnection,
Q_ARG(QString, ipAddr),
Q_ARG(QString, ""));
emit popupOpen("Connecting in the background...", 1500);
}
void Backend::runProject(const QString &id, const QByteArray &projectData)
{
emit popupOpen();
updatePopup("Running project...");
// we'll use this to notify the correct DS when the project started/stopped
m_lastProjectSenderId = id;
QMetaObject::invokeMethod(m_dsManager.get(),
"sendProjectRunning",
Qt::QueuedConnection,
Q_ARG(QString, id));
emit updatePopupText("Running project...");
QTimer::singleShot(1000, [this, id, projectData] {
bool retVal;
QMetaObject::invokeMethod(m_projectManager.get(),
&ProjectManager::runProject,
Q_RETURN_ARG(bool, retVal),
projectData,
autoScaleProject(),
m_lastSessionId);
if (!retVal)
QMetaObject::invokeMethod(m_dsManager.get(),
&DesignStudioManager::sendProjectStopped,
id);
else {
QMetaObject::invokeMethod(m_projectManager.get(), &ProjectManager::showAppWindow);
QMetaObject::invokeMethod(m_dsManager.get(),
&DesignStudioManager::sendProjectStarted,
id);
}
QMetaObject::invokeMethod(m_projectManager.get(),
"runProject",
Qt::QueuedConnection,
Q_ARG(QByteArray, projectData),
Q_ARG(bool, autoScaleProject()));
emit popupClose();
emit popupClose();
});
}
void Backend::scanQrCode()
......@@ -231,7 +291,7 @@ void Backend::parseDesignViewerUrl(const QUrl &url)
return;
}
connectDesignStudio(url.host());
// connectDesignStudio(url.host());
}
void Backend::popupInterrupted()
......@@ -249,7 +309,49 @@ 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;
}
......@@ -26,6 +26,7 @@
#ifndef DV_ANDROID_H
#define DV_ANDROID_H
#include <QJsonArray>
#include <QThread>
#include "dsconnector/dsmanager.h"
......@@ -50,23 +51,27 @@ private:
QScopedPointer<QrScanner> m_qrScanner;
QString m_lastProjectSenderId;
QString m_lastSessionId;
// Settings
Settings m_settings;
// member functions
void updatePopup(const QString &text, bool indeterminate = true);
void updatePopupText(const QString &text, int timeout = 0);
void updatePopupProgress(const int progress);
void initDsManager();
void initProjectManager();
void setAndroidScreenOn(bool on);
void runProject(const QString &id, const QByteArray &projectData);
signals:
// UI signals - Popup
void popupProgressIndeterminateChanged(bool indeterminate);
void popupTextChanged(QString text);
void popupOpen(const QString &text = {}, int timeout = 0);
void popupChangeText(QString text, int timeout = 0);
void popupChangeProgress(int progress);
void popupOpen();
void popupClose();
// UI signals - from DS Manager page
......@@ -78,13 +83,16 @@ signals:
public slots:
QString buildInfo() const;
void scanQrCode();
void connectDesignStudio(const QString &ipAddr);
void parseDesignViewerUrl(const QUrl &url);
void popupInterrupted();
QJsonArray getIpAddresses() const;
bool autoScaleProject() const;
void setAutoScaleProject(bool autoScaleProject);
bool keepScreenOn() const;
void setKeepScreenOn(bool keepScreenOn);
QString lastDesignStudioIp() const;
};
......
......@@ -7,55 +7,123 @@
#include <QJsonObject>
#include <QScreen>
DesignStudio::DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent)
namespace PackageFromDesignStudio {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageFromDesignStudio
namespace PackageToDesignStudio {
using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1;
constexpr auto projectStarting = "projectStarting"_L1;
constexpr auto projectStarted = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageToDesignStudio
DesignStudio::DesignStudio(QWebSocket *socket, const QString &deviceID, QObject *parent)
: QObject(parent)
, m_deviceUuid(m_deviceUuid)
, m_deviceID(deviceID)
, m_socket(socket)
{
initPingPong();
initSocket();
startPingPong();
m_projectStallTimer.setInterval(5000);
m_projectStallTimer.setSingleShot(true);
connect(&m_projectStallTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Project is stalled. Closing the connection.";
m_socket->close();
m_socket->abort();
});
m_speedCalculator.setInterval(1000);
m_speedCalculator.setSingleShot(false);
connect(&m_speedCalculator, &QTimer::timeout, this, [this]() {
quint64 elapsedTime = m_projectNotificationTime.msecsTo(QTime::currentTime()) / 1000;
int receiveSpeed = elapsedTime > 0 ? m_projectData.size() / elapsedTime / 1024 : 0;
qDebug() << "Receive speed" << receiveSpeed << "KB/s (" << receiveSpeed / 1024 << "MB/s )"
<< "percentage" << m_lastPercentage << "%";
});
}
void DesignStudio::initPingPong()
{
m_pingTimer.setInterval(1000);
m_pongTimer.setInterval(30000);
m_pongTimer.setSingleShot(true);
m_pingTimer.setSingleShot(true);
connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
m_socket->ping();
m_pongTimer.start(15000);
startPingPong();
});
connect(m_socket.data(),
&QWebSocket::pong,
this,
[this](quint64 elapsedTime, const QByteArray &) { m_pongTimer.stop(); });
[this](quint64 elapsedTime, const QByteArray &) {
if (elapsedTime > 1000)
qWarning() << "Design Studio pong is too slow:" << elapsedTime
<< "ms. Newtork issue?";
else if (elapsedTime > 500)
qWarning() << "Design Studio pong is slow:" << elapsedTime << "ms";
m_pongTimer.stop();
m_pingTimer.start();
});
connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Design Studio" << m_id << "is not responding. Reconnecting.";
qDebug() << "Design Studio" << m_designStudioID
<< "is not responding. Closing the connection.";
m_socket->close();
m_socket->abort();
});
}
void DesignStudio::initSocket()
void DesignStudio::stopPingPong()
{
m_pingTimer.start(15000);
m_pingTimer.stop();
m_pongTimer.stop();
}
void DesignStudio::startPingPong()
{
m_pingTimer.start();
}
void DesignStudio::initSocket()
{
connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
qDebug() << "Design Studio" << m_id << "disconnected";
qDebug() << "Design Studio" << m_designStudioID << "disconnected";
m_pingTimer.stop();
m_pongTimer.stop();
emit disconnected(m_id);
m_projectStallTimer.stop();
m_speedCalculator.stop();
m_projectData.clear();
emit disconnected(m_designStudioID);
});
connect(m_socket.data(),
&QWebSocket::textMessageReceived,
this,
&DesignStudio::processTextMessage);
connect(m_socket.data(),
&QWebSocket::binaryMessageReceived,
this,
&DesignStudio::processBinaryMessage);
}
void DesignStudio::disconnect()
{
m_socket->close();
m_socket->abort();
}
QString DesignStudio::ipv4Addr() const
{
return m_socket->peerAddress().toString();
......@@ -63,7 +131,7 @@ QString DesignStudio::ipv4Addr() const
QString DesignStudio::id() const
{
return m_id;
return m_designStudioID;
}
void DesignStudio::sendDeviceInfo()
......@@ -76,7 +144,7 @@ void DesignStudio::sendDeviceInfo()
deviceInfo["os"] = QSysInfo::prettyProductName();
deviceInfo["osVersion"] = QSysInfo::productVersion();
deviceInfo["architecture"] = QSysInfo::currentCpuArchitecture();
deviceInfo["deviceId"] = m_deviceUuid;
deviceInfo["deviceId"] = m_deviceID;
deviceInfo["appVersion"] = QString(CMAKE_VAR_GIT_VERSION);
qDebug() << "Sending device info to Design Studio" << deviceInfo;
......@@ -110,34 +178,78 @@ void DesignStudio::processTextMessage(const QString &message)
}
const QString dataType = jsonObj.value("dataType").toString();
const QJsonObject data = jsonObj.value("data").toObject();
if (dataType == PackageFromDesignStudio::designStudioReady) {
const QString newDesignStudioId = jsonObj.value("data").toString();
qDebug() << "Design Studio ready with ID" << newDesignStudioId;
m_id = newDesignStudioId;
const QString newDesignStudioId = data["designStudioID"].toString();
const int commVersion = data["commVersion"].toInt();
qDebug() << "Design Studio ready with ID" << newDesignStudioId << "and comm version"
<< commVersion;
m_designStudioID = newDesignStudioId;
sendDeviceInfo();
emit idReceived(newDesignStudioId, ipv4Addr());
} else if (dataType == PackageFromDesignStudio::projectData) {
qDebug() << "Project is expected";
emit projectIncoming();
m_incomingProjectSize = data["projectSize"].toInt();
const QString qtVersion = data["qtVersion"].toString();
auto qcompare = QString::compare(qtVersion, QT_VERSION_STR, Qt::CaseInsensitive);
if (qcompare != 0) {
qDebug() << "Qt version mismatch. Expected" << QT_VERSION_STR " or lower, but got"
<< qtVersion << ". Project may not work correctly.";
emit projectVersionMismatch(QT_VERSION_STR, qtVersion);
}
qDebug() << "Project is expected with size" << m_incomingProjectSize << "and Qt version"
<< qtVersion;
m_projectData.clear();
m_projectNotificationTime = QTime::currentTime();
m_speedCalculator.start();
m_projectStallTimer.start();
stopPingPong();
emit projectIncoming(m_designStudioID, m_incomingProjectSize);
} else if (dataType == PackageFromDesignStudio::stopRunningProject) {
qDebug() << "Stop running project requested";
emit projectStopRequested(m_id);
m_projectData.clear();
m_speedCalculator.stop();
m_projectStallTimer.stop();
emit projectStopRequested(m_designStudioID);
} else {
qDebug() << "Unkown JSON message";
qDebug() << "Unkown JSON message type:" << dataType;
}
}
// this function should only be triggered when the device is receiving a qmlrc project
void DesignStudio::processBinaryMessage(const QByteArray &data)
{
qDebug() << "Binary message received (most probably a project)";
emit projectReceived(m_id, data);
m_projectStallTimer.start();
m_projectData.append(data);
const bool isProjectComplete = m_projectData.size() == m_incomingProjectSize;
int percentage = m_projectData.size() * 100 / m_incomingProjectSize;
if (m_lastPercentage != percentage) {
emit projectIncomingProgress(m_designStudioID, percentage);
m_lastPercentage = percentage;
sendData(PackageToDesignStudio::projectReceivingProgress, percentage);
}
if (isProjectComplete) {
startPingPong();
sendProjectStarting();
emit projectReceived(m_designStudioID, m_projectData);
m_speedCalculator.stop();
m_projectStallTimer.stop();
}
}
void DesignStudio::sendProjectStarting()
{
sendData(PackageToDesignStudio::projectStarting);
}
void DesignStudio::sendProjectRunning()
void DesignStudio::sendProjectStarted()
{
sendData(PackageToDesignStudio::projectRunning);
sendData(PackageToDesignStudio::projectStarted);
}
void DesignStudio::sendProjectStopped()
......
......@@ -7,26 +7,14 @@
#include <QTimer>
#include <QWebSocket>
namespace PackageFromDesignStudio {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageFromDesignStudio
namespace PackageToDesignStudio {
using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectRunning = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageToDesignStudio
class DesignStudio : public QObject
{
Q_OBJECT
public:
DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent = nullptr);
DesignStudio(QWebSocket *socket, const QString &deviceID, QObject *parent = nullptr);
// Connection
void disconnect();
// Getters
QString ipv4Addr() const;
......@@ -34,7 +22,8 @@ public:
// Send data
void sendDeviceInfo();
void sendProjectRunning();
void sendProjectStarting();
void sendProjectStarted();
void sendProjectStopped();
void sendProjectLogs(const QString &logs);
......@@ -47,14 +36,24 @@ private:
QTimer m_pongTimer;
// DS data
QString m_id;
QString m_deviceUuid;
QString m_designStudioID;
QString m_deviceID;
// project
QByteArray m_projectData;
int m_incomingProjectSize;
int m_lastPercentage;
QTime m_projectNotificationTime;
QTimer m_speedCalculator;
QTimer m_projectStallTimer;
// DS comm
void sendData(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
// Internal
void initPingPong();
void startPingPong();
void stopPingPong();
void initSocket();
private slots:
......@@ -68,7 +67,9 @@ signals:
// DS signals
void idReceived(const QString &id, const QString &ipv4Addr);
void projectIncoming();
void projectVersionMismatch(const QString &version, const QString &got);
void projectIncoming(const QString &id, const int projectSize);
void projectIncomingProgress(const QString &id, const int percentage);
void projectReceived(const QString &id, const QByteArray &data);
void projectStopRequested(const QString &id);
};
......@@ -9,26 +9,32 @@
#include <QJsonObject>
#include <QUdpSocket>
DesignStudioManager::DesignStudioManager(const QString &deviceUuid, QObject *parent)
DesignStudioManager::DesignStudioManager(const QString &deviceUuid,
const quint16 port,
QObject *parent)
: QObject(parent)
, m_port(port)
, m_deviceUuid(deviceUuid)
, m_webSocketServer("DesignStudio", QWebSocketServer::NonSecureMode)
{}
void DesignStudioManager::init()
bool DesignStudioManager::init()
{
static bool initialized = false;
if (initialized) {
return;
if (m_webSocketServer.isListening()) {
qWarning() << "TCP server is already running";
return false;
}
if (!m_webSocketServer.listen(QHostAddress::Any, m_port)) {
qWarning() << "Failed to start TCP server on port" << m_port;
return false;
}
initialized = true;
m_webSocketServer.listen(QHostAddress::Any, 40000);
connect(&m_webSocketServer,
&QWebSocketServer::newConnection,
this,
&DesignStudioManager::incomingConnection);
qDebug() << "TCP server listening on port 40000";
qDebug() << "TCP server listening on port" << m_webSocketServer.serverPort();
m_discoveryTimer.setInterval(10000);
connect(&m_discoveryTimer, &QTimer::timeout, [this]() {
......@@ -39,9 +45,30 @@ void DesignStudioManager::init()
QUdpSocket udpSocket;
const int port = 53452;
#ifdef Q_OS_ANDROID
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, port);
#endif
udpSocket.writeDatagram(datagram, QHostAddress::Broadcast, port);
udpSocket.writeDatagram(datagram, QHostAddress::AnyIPv4, port);
udpSocket.writeDatagram(datagram, QHostAddress::AnyIPv6, port);
udpSocket.writeDatagram(datagram, QHostAddress::Any, port);
// this is useful for Android emulator
udpSocket.writeDatagram(datagram, QHostAddress("10.0.2.2"), port);
});
m_discoveryTimer.start();
return true;
}
QString DesignStudioManager::webSocketServerName() const
{
return m_webSocketServer.serverName();
}
int DesignStudioManager::webSocketServerPort() const
{
return m_webSocketServer.serverPort();
}
void DesignStudioManager::incomingConnection()
......@@ -55,65 +82,100 @@ void DesignStudioManager::incomingConnection()
&QObject::deleteLater);
connect(designStudio.data(), &DesignStudio::disconnected, this, [this, designStudio]() {
m_designStudios.removeOne(designStudio);
emit designStudioDisconnected(designStudio->id(), designStudio->ipv4Addr());
qDebug() << "Remaining Design Studios:" << m_designStudios.size();
if (m_designStudios.isEmpty())
emit allDesignStudiosDisconnected();
});
connect(designStudio.data(), &DesignStudio::projectIncoming, this, [this]() {
emit projectIncoming();
});
connect(designStudio.data(),
&DesignStudio::projectIncoming,
this,
&DesignStudioManager::projectIncoming);
connect(designStudio.data(),
&DesignStudio::projectIncomingProgress,
this,
&DesignStudioManager::projectIncomingProgress);
connect(designStudio.data(),
&DesignStudio::projectVersionMismatch,
this,
&DesignStudioManager::projectVersionMismatch);
connect(designStudio.data(),
&DesignStudio::projectReceived,
this,
[this, &designStudio](const QString &id, const QByteArray &project) {
emit projectReceived(id, project);
});
&DesignStudioManager::projectReceived);
connect(designStudio.data(),
&DesignStudio::projectStopRequested,
this,
[this](const QString &id) { emit projectStopRequested(id); });
&DesignStudioManager::projectStopRequested);
m_designStudios.append(designStudio);
emit designStudioConnected(designStudio->id(), designStudio->ipv4Addr());
}
void DesignStudioManager::sendProjectRunning(const QString &id)
void DesignStudioManager::sendProjectStarting(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
designStudio->sendProjectRunning();
}
if (designStudio->id() == id)
designStudio->sendProjectStarting();
}
}
void DesignStudioManager::sendProjectStarted(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id)
designStudio->sendProjectStarted();
}
}
void DesignStudioManager::sendProjectStopped(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
designStudio->sendProjectStopped();
}
}
}
void DesignStudioManager::sendProjectLogs(const QString &id, const QString &logs)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
designStudio->sendProjectLogs(logs);
}
}
}
QString DesignStudioManager::getDesignStudioIp(const QString &id) const
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
if (designStudio->id() == id)
return designStudio->ipv4Addr();
}
}
return {};
}
bool DesignStudioManager::disconnectDesignStudio(const QString &id)
{
for (const auto &designStudio : m_designStudios) {
if (designStudio->id() == id) {
qDebug() << "Disconnecting Design Studio" << id;
designStudio->disconnect();
return true;
}
}
qWarning() << "Design Studio" << id << "not found for disconnection";
return false;
}
void DesignStudioManager::disconnectAllDesignStudios()
{
qDebug() << "Disconnecting all Design Studios";
for (const auto &designStudio : m_designStudios)
designStudio->disconnect();
}
......@@ -11,20 +11,28 @@ class DesignStudioManager : public QObject
{
Q_OBJECT
public:
explicit DesignStudioManager(const QString &deviceUuid, QObject *parent = nullptr);
explicit DesignStudioManager(const QString &deviceUuid,
const quint16 port = 40000,
QObject *parent = nullptr);
void init();
bool init();
QString webSocketServerName() const;
int webSocketServerPort() const;
public slots:
void sendProjectRunning(const QString &id);
void sendProjectStarting(const QString &id);
void sendProjectStarted(const QString &id);
void sendProjectStopped(const QString &id);
void sendProjectLogs(const QString &id, const QString &logs);
QString getDesignStudioIp(const QString &id) const;
bool disconnectDesignStudio(const QString &id);
void disconnectAllDesignStudios();
private:
// Network
QWebSocketServer m_webSocketServer;
QTimer m_discoveryTimer;
const quint16 m_port;
// Connected Design Studios
QList<QSharedPointer<DesignStudio>> m_designStudios;
......@@ -35,7 +43,9 @@ private:
void incomingConnection();
signals:
void projectIncoming();
void projectVersionMismatch(const QString &expected, const QString &got);
void projectIncoming(const QString &id, const int &projectSize);
void projectIncomingProgress(const QString &id, const int percentage);
void projectReceived(const QString &id, const QByteArray &project);
void designStudioConnected(const QString &id, const QString &ipAddr);
void designStudioDisconnected(const QString &id, const QString &ipAddr);
......
// Hack to force the qml plugins to be linked statically
import QtQuick
import QtQuick.Controls
import QtQml
import QtQml.Models
import QtQml.StateMachine
import QtQuick3D
import QtQuick3D.AssetUtils
import QtQuick3D.Effects
import QtQuick3D.Helpers
import QtQuick3D.ParticleEffects
import QtQuick3D.Particles3D
import QtQuick.VirtualKeyboard
import QtQuick.Studio.Application
import QtQuick.Studio.Components
import QtQuick.Studio.Effects
import QtQuick.Studio.EventSimulator
import QtQuick.Studio.EventSystem
import QtQuick.Studio.LogicHelper
import QtQuick.Studio.MultiText
import QtQuick.Studio.Utils
import QtQuick.Studio.DesignEffects
import QtQuickUltralite.Extras
import QtQuickUltralite.Layers
import FlowView
import Qt.labs.folderlistmodel
import QtWebSockets
ApplicationWindow {
visible: true
width: 640
height: 480
}
......@@ -7,6 +7,18 @@
#ifdef Q_OS_ANDROID
#include <android/log.h>
#else
enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT,
};
#endif
class Logger : public QObject
......@@ -30,38 +42,45 @@ public:
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
enum android_LogPriority logPriority;
switch (type) {
case QtDebugMsg:
logPrefix = QStringLiteral("Debug: ");
logPriority = ANDROID_LOG_DEBUG;
break;
case QtInfoMsg:
logPrefix = QStringLiteral("Info: ");
logPriority = ANDROID_LOG_INFO;
break;
case QtWarningMsg:
logPrefix = QStringLiteral("Warning: ");
logPriority = ANDROID_LOG_WARN;
break;
case QtCriticalMsg:
logPrefix = QStringLiteral("Critical: ");
logPriority = ANDROID_LOG_ERROR;
break;
case QtFatalMsg:
logPrefix = QStringLiteral("Fatal: ");
logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function);
logPriority = ANDROID_LOG_FATAL;
break;
}
newLog += logPrefix + localMsg + logSuffix + "\n";
#ifdef Q_OS_ANDROID
__android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog));
__android_log_print(logPriority, "Qt_UI_Viewer", "%s", qPrintable(newLog));
#else
fprintf(stderr, "%s", qPrintable(newLog));
#endif
emit logMessage(type, newLog);
if (type == QtCriticalMsg || type == QtFatalMsg) {
QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
msgBox.exec();
}
emit logMessage(type, newLog);
}
private:
......
......@@ -33,8 +33,7 @@
int main(int argc, char *argv[])
{
Logger::instance();
qDebug() << "Starting Qt Design Viewer";
qputenv("QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT", "1");
QApplication app(argc, argv);
QApplication::setOrganizationName("Qt");
......@@ -47,7 +46,13 @@ int main(int argc, char *argv[])
view.engine()->rootContext()->setContextProperty("backend", &backend);
view.setSource(QUrl(QStringLiteral("qrc:/qt/qml/AndroidUI/Main.qml")));
view.setResizeMode(QQuickView::SizeRootObjectToView);
#ifdef Q_OS_ANDROID
view.showMaximized();
#else
QApplication::setWindowIcon(QIcon(QStringLiteral(":/images/appicon.svg")));
view.show();
#endif
return app.exec();
}
......@@ -32,6 +32,7 @@
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QResource>
#include <QStandardPaths>
#include <QTemporaryDir>
ProjectManager::ProjectManager(QObject *parent)
......@@ -40,50 +41,106 @@ ProjectManager::ProjectManager(QObject *parent)
ProjectManager::~ProjectManager()
{
qDebug() << "ProjectManager destroyed. Cleaning up resources.";
cleanupResources();
unregisterResource(m_projectData, m_projectPath);
}
void ProjectManager::cleanupResources()
bool ProjectManager::unregisterResource(const QByteArray &data, const QString &path)
{
const uchar *data;
if (m_projectData.size()) {
qDebug() << "Unregistering the previous data from QRC system. Path: " << m_projectPath
<< " Size: " << m_projectData.size() << " bytes.";
data = reinterpret_cast<const uchar *>(m_projectData.data());
if (!QResource::unregisterResource(data, m_projectPath)) {
const uchar *charPtr;
if (data.size()) {
qDebug() << "Unregistering the previous data from QRC system. Path: " << path
<< " Size: " << data.size() << " bytes.";
charPtr = reinterpret_cast<const uchar *>(data.data());
if (!QResource::unregisterResource(charPtr, path)) {
qCritical() << "Cannot unregister the previous resource data.";
return false;
}
}
return true;
}
QString ProjectManager::unpackProject(const QByteArray &project)
bool ProjectManager::registerResource(const QByteArray &data, const QString &path)
{
cleanupResources();
if (!QDir(path).removeRecursively()) {
qWarning() << "Could not remove resource path: " << path;
return false;
}
m_projectData = project;
const uchar *data;
data = reinterpret_cast<const uchar *>(m_projectData.data());
qDebug() << "Registering resource data. Size: " << m_projectData.size();
qDebug() << "Registering resource data. Size: " << data.size();
const uchar *resourceData = reinterpret_cast<const uchar *>(data.data());
if (!QResource::registerResource(resourceData, path)) {
qCritical() << "Cannot register the resource data.";
return false;
}
const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
m_projectPath = resourcePath;
return true;
}
bool ProjectManager::copyResourceToFs(const QString &sourcePath, const QString &destPath)
{
QDir tmpDir(destPath);
if (tmpDir.exists()) {
if (!tmpDir.removeRecursively()) {
qCritical() << "Could not remove tmp dir: " << destPath;
return false;
}
}
if (!tmpDir.mkpath(destPath)) {
qCritical() << "Could not create tmp dir: " << destPath;
return false;
}
QDirIterator it(sourcePath,
QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
const QString filePath = it.filePath();
const QString relativePath = it.filePath().mid(sourcePath.length());
const QString targetPath = destPath + relativePath;
qDebug() << "Copying file: " << filePath << " to " << targetPath;
if (it.fileInfo().isDir()) {
QDir().mkpath(targetPath);
} else {
QFile file(filePath);
if (!file.copy(targetPath)) {
qCritical() << "Could not copy file: " << filePath << " to " << targetPath;
return false;
}
}
}
if (!QDir(resourcePath).removeRecursively()) {
qWarning() << "Could not remove resource path: " << resourcePath;
return true;
}
QString ProjectManager::unpackProject(const QByteArray &project)
{
if (!unregisterResource(m_projectData, m_projectPath)) {
qCritical() << "Could not unregister previous resource: " << m_projectPath;
return {};
}
if (!QResource::registerResource(data, resourcePath)) {
qCritical() << "Can not load the resource data.";
const QString resourcePath{":/" + QString::number(QRandomGenerator::global()->generate())};
if (!registerResource(project, resourcePath)) {
qCritical() << "Could not register resource: " << resourcePath;
return {};
}
m_projectData = project;
m_projectPath = resourcePath;
return QString(":") + resourcePath;
return resourcePath;
}
QString ProjectManager::findFile(const QString &dir, const QString &filter)
QString ProjectManager::findFile(const QString &dir, const QString &filter, const bool recursive)
{
QDirIterator it(dir, {filter}, QDir::Files);
QDirIterator it(dir,
{filter},
QDir::Files,
recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
return it.next();
}
......@@ -144,14 +201,12 @@ bool ProjectManager::isQt6Project(const QString &qmlProjectFileContent)
return qt6ProjectMatch.hasMatch();
}
bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleProject)
bool ProjectManager::runProjectInternal(const QByteArray &project)
{
m_autoScaleProject = autoScaleProject;
const QString projectPath = unpackProject(project);
qDebug() << "Project location: " << projectPath;
const QString qmlProjectFile = findFile(projectPath, "*.qmlproject");
const QString qmlProjectFile = findFile(projectPath, "*.qmlproject", false);
if (qmlProjectFile.isEmpty()) {
qCritical() << "No \"*.qmlproject\" found in \"" << projectPath << "\".";
return false;
......@@ -165,14 +220,14 @@ bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleP
if (mainQmlFilePath.isEmpty())
return false;
const QString qtquickcontrols2File = findFile(projectPath, "qtquickcontrols2.conf");
const QString qtquickcontrols2File = findFile(projectPath, "qtquickcontrols2.conf", false);
if (!qtquickcontrols2File.isEmpty()) {
qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1());
}
const QStringList importPaths = getImportPaths(projectPath, qmlProjectFileContent);
if (!isQt6Project(qmlProjectFileContent)) {
qWarning() << "This is not a Qt6 project.\nQt5 projects might work, but they are not "
qWarning() << "This is not a Qt6 project. Qt5 projects might work, but they are not "
"officially supported.";
}
......@@ -200,15 +255,6 @@ bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleP
m_qmlEngine->addImportPath(importPath);
}
QObject::connect(m_qmlEngine.data(),
&QQmlEngine::warnings,
this,
[&](const QList<QQmlError> &warnings) {
for (const auto &warning : warnings) {
qWarning() << warning.toString();
}
});
qDebug() << "Loading mainQmlUrl: " << mainQmlUrl.toString();
m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.data()));
......@@ -232,6 +278,8 @@ bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleP
qDebug() << "Setting up the quickWindow";
m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel));
m_quickWindow->setVisible(false);
if (m_quickWindow) {
qDebug() << "Running with incubator controller";
m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
......@@ -251,10 +299,25 @@ bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleP
view->setResizeMode(QQuickView::SizeViewToRootObject);
m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height()));
}
showAppWindow();
return true;
}
bool ProjectManager::runProject(const QByteArray &project,
const bool autoScaleProject,
const QString &sessionId)
{
m_autoScaleProject = autoScaleProject;
m_sessionId = sessionId;
const bool retVal = runProjectInternal(project);
if (!retVal)
m_sessionId.clear();
return retVal;
}
void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation)
{
if (!m_autoScaleProject)
......@@ -274,7 +337,7 @@ void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation)
<< m_quickWindow->width() << Qt::endl
<< "-- Child size: " << childItem->height() << " x " << childItem->width() << Qt::endl
<< "-- Child pos: " << childItem->x() << ", " << childItem->y() << Qt::endl
<< "-- Child scale: " << childItem->scale();
<< "-- Child scale: " << childItem->scale() << Qt::endl;
const QSizeF newContentSize = childItem->size().scaled(screenGeometry.size().toSizeF(),
Qt::AspectRatioMode::KeepAspectRatio);
......@@ -299,15 +362,17 @@ void ProjectManager::showAppWindow()
qDebug("Initializing and showing the QML app window");
QScreen *screen = QGuiApplication::primaryScreen();
connect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow);
connect(screen, &QScreen::orientationChanged, this, [this](Qt::ScreenOrientation orientation) {
orientateWindow(orientation);
});
orientateWindow(screen->orientation());
connect(m_quickWindow.data(), &QQuickWindow::closing, this, [this, screen]() {
qDebug() << "QML app window is closing";
disconnect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow);
// this signal is connected to the lambda in the backend
// which will reset the project manager
emit closingProject();
disconnect(screen, &QScreen::orientationChanged, this, nullptr);
emit closingProject(m_sessionId);
m_sessionId.clear();
});
m_quickWindow->setFlags(Qt::Window | Qt::WindowStaysOnTopHint);
......@@ -316,14 +381,19 @@ void ProjectManager::showAppWindow()
void ProjectManager::stopProject()
{
if (!m_quickWindow || !m_quickWindow->isVisible())
if (!m_quickWindow)
return;
qDebug("Stopping the QML app window");
m_quickWindow->close();
m_qmlComponent.reset();
m_qmlEngine.reset();
m_quickWindow.reset();
m_sessionId.clear();
}
bool ProjectManager::isRunning()
QString ProjectManager::sessionId() const
{
return m_quickWindow && m_quickWindow->isVisible();
return m_sessionId;
}
......@@ -34,16 +34,22 @@ public:
explicit ProjectManager(QObject *parent = nullptr);
~ProjectManager();
bool isRunning();
QString sessionId() const;
public slots:
bool runProject(const QByteArray &project, const bool autoScaleProject);
bool runProject(const QByteArray &project,
const bool autoScaleProject,
const QString &sessionId);
void stopProject();
void showAppWindow();
private:
friend class TestProjectManager;
// Member variables
QByteArray m_projectData;
QString m_projectPath;
QString m_sessionId;
bool m_autoScaleProject;
// Qml related members
......@@ -52,21 +58,22 @@ private:
QScopedPointer<QQuickWindow> m_quickWindow;
// resource management
void cleanupResources();
bool registerResource(const QByteArray &data, const QString &path);
bool unregisterResource(const QByteArray &data, const QString &path);
bool copyResourceToFs(const QString &sourcePath, const QString &targetPath);
QString unpackProject(const QByteArray &project);
// project parsers
QString findFile(const QString &dir, const QString &filter);
// project management
bool runProjectInternal(const QByteArray &project);
QString findFile(const QString &dir, const QString &filter, const bool recursive = true);
QString readQmlProjectFile(const QString &qmlProjectFilePath);
QString getMainQmlFile(const QString &projectPath, const QString &qmlProjectFileContent);
QStringList getImportPaths(const QString &projectPath, const QString &qmlProjectFileContent);
bool isQt6Project(const QString &qmlProjectFileContent);
void parseQmlProjectFile(const QString &fileName, QString *mainFile, QStringList *importPaths);
// window management
void orientateWindow(Qt::ScreenOrientation orientation);
void showAppWindow();
signals:
void closingProject();
void closingProject(const QString &sessionId);
};
......@@ -83,22 +83,22 @@ void QrScanner::openCamera()
QtConcurrent::run([=] {
if (!m_captureSession)
return QList<Result>();
return QList<Barcode>();
QElapsedTimer timer;
timer.start();
QList<Result> results = ReadBarcodes(frame.toImage(), readerOptions);
auto results = ReadBarcodes(frame.toImage(), readerOptions);
qDebug() << "Barcode detection took" << timer.elapsed() << "ms";
if (!results.size())
m_processing = 0;
return results;
}).then([=](const QList<Result> &results) {
}).then([=](const QList<Barcode> &results) {
if (results.isEmpty() || !m_captureSession)
return;
Result result = results.first();
auto result = results.first();
qDebug() << "Text: " << result.text();
qDebug() << "Format: " << result.format();
......