From 98f060ef4585e8a9cb10b65b1370ffd7dac36dea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io>
Date: Mon, 28 Oct 2024 11:49:48 +0000
Subject: [PATCH] QDS-13458 Move server to device

---
 3rdparty/qtquickdesigner-components     |   2 +-
 CMakeLists.txt                          |  17 +-
 src/CMakeLists.txt                      |   2 -
 src/HomePage.qml                        |  12 +-
 src/Main.qml                            |   6 +-
 src/backend/backend.cpp                 | 146 +++++++--------
 src/backend/backend.h                   |  13 +-
 src/backend/dsconnector/ds.cpp          | 139 ++++----------
 src/backend/dsconnector/ds.h            |  55 +-----
 src/backend/dsconnector/dsdiscovery.cpp |  85 ---------
 src/backend/dsconnector/dsdiscovery.h   |  44 -----
 src/backend/dsconnector/dsmanager.cpp   | 204 +++++++--------------
 src/backend/dsconnector/dsmanager.h     |  54 ++----
 src/backend/logger.h                    |  32 +---
 src/backend/main.cpp                    |   2 -
 src/backend/projectmanager.cpp          |  83 ++++-----
 src/backend/projectmanager.h            |  26 +--
 src/backend/qrscanner.cpp               |  26 +--
 src/backend/qrscanner.h                 |  26 +--
 src/backend/settings.cpp                |  26 +--
 src/backend/settings.h                  |  27 +--
 tests/CMakeLists.txt                    |   3 -
 tests/main.cpp                          |  30 +--
 tests/mock.h                            |  97 ----------
 tests/tst_designstudio.cpp              | 234 ------------------------
 tests/tst_designstudio.h                |  62 -------
 tests/tst_dsdiscovery.cpp               |  67 -------
 tests/tst_dsdiscovery.h                 |  41 -----
 tests/tst_settings.cpp                  |  26 +--
 tests/tst_settings.h                    |  26 +--
 30 files changed, 283 insertions(+), 1330 deletions(-)
 delete mode 100644 src/backend/dsconnector/dsdiscovery.cpp
 delete mode 100644 src/backend/dsconnector/dsdiscovery.h
 delete mode 100644 tests/mock.h
 delete mode 100644 tests/tst_designstudio.cpp
 delete mode 100644 tests/tst_designstudio.h
 delete mode 100644 tests/tst_dsdiscovery.cpp
 delete mode 100644 tests/tst_dsdiscovery.h

diff --git a/3rdparty/qtquickdesigner-components b/3rdparty/qtquickdesigner-components
index 20397e2..34b97d4 160000
--- a/3rdparty/qtquickdesigner-components
+++ b/3rdparty/qtquickdesigner-components
@@ -1 +1 @@
-Subproject commit 20397e26370ff073125fd19f77c7ad013276b5bd
+Subproject commit 34b97d40d86d7c243cb6bf8245d717397ef25268
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2d1ca30..bf02083 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,13 +7,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-if(NOT EXISTS ${ANDROID_OPENSSL_PATH})
-    message(WARNING "Cannot find OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
-    message(FATAL_ERROR "Please set ANDROID_OPENSSL_PATH to the path of OpenSSL for Android.")
-endif()
-
-message(STATUS "Found OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
-
 find_package(
     QT NAMES Qt6
 )
@@ -26,6 +19,16 @@ if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION)
     message(FATAL_ERROR "Minimum supported Qt version: ${QT_MINIMUM_VERSION}")
 endif()
 
+if(ANDROID)
+    if(NOT EXISTS ${ANDROID_OPENSSL_PATH})
+        message(WARNING "Cannot find OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
+        message(FATAL_ERROR "Please set ANDROID_OPENSSL_PATH to the path of OpenSSL for Android.")
+    endif()
+    message(STATUS "Found OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
+else()
+    message(STATUS "Building for Desktop")
+endif()
+
 qt_standard_project_setup(REQUIRES ${QT_MINIMUM_VERSION})
 
 set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_INSTALL_PREFIX})
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e071432..a1ad4cd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,7 +14,6 @@ qt_add_executable(${PROJECT_NAME}
     backend/projectmanager.cpp backend/projectmanager.h
     backend/settings.cpp backend/settings.h
     backend/dsconnector/ds.cpp backend/dsconnector/ds.h
-    backend/dsconnector/dsdiscovery.cpp backend/dsconnector/dsdiscovery.h
     backend/dsconnector/dsmanager.cpp backend/dsconnector/dsmanager.h
     backend/qrscanner.cpp backend/qrscanner.h
     ../3rdparty/zxing-cpp/example/ZXingQtReader.h
@@ -61,7 +60,6 @@ qt_add_library(qtuiviewerlib OBJECT
     backend/projectmanager.cpp backend/projectmanager.h
     backend/settings.cpp backend/settings.h
     backend/dsconnector/ds.cpp backend/dsconnector/ds.h
-    backend/dsconnector/dsdiscovery.cpp backend/dsconnector/dsdiscovery.h
     backend/dsconnector/dsmanager.cpp backend/dsconnector/dsmanager.h
 )
 
diff --git a/src/HomePage.qml b/src/HomePage.qml
index d6e328b..c24cf59 100644
--- a/src/HomePage.qml
+++ b/src/HomePage.qml
@@ -18,8 +18,8 @@ Flickable {
         target: Qt.inputMethod
 
         function onKeyboardRectangleChanged() {
-            let mapPositon = optionLayout2.mapToItem(root, ipAddress.x, ipAddress.y)
-            root.contentY = mapPositon.y
+            let mapPositon = optionLayout2.mapToItem(root, ipAddress.x, ipAddress.y);
+            root.contentY = mapPositon.y;
         }
     }
 
@@ -101,11 +101,7 @@ Flickable {
                     }
 
                     Repeater {
-                        model: [
-                            qsTr("Open Qt Design Studio"),
-                            qsTr("Click Play > Manage Run Targets"),
-                            qsTr("In Manage Run Targets window click Add run target button.")
-                        ]
+                        model: [qsTr("Open Qt Design Studio"), qsTr("Click Play > Manage Run Targets"), qsTr("In Manage Run Targets window click Add run target button.")]
 
                         delegate: RowLayout {
                             Layout.fillWidth: true
@@ -234,7 +230,7 @@ Flickable {
                                 target: backend
 
                                 function onConnectedChanged(isConnected, ip) {
-                                    ipAddress.text = ip
+                                    ipAddress.text = ip;
                                 }
                             }
                         }
diff --git a/src/Main.qml b/src/Main.qml
index 0baf1ff..e29a59f 100644
--- a/src/Main.qml
+++ b/src/Main.qml
@@ -53,16 +53,16 @@ Rectangle {
                     font.pixelSize: Constants.smTextSize
                     wrapMode: Text.WordWrap
 
-                    text: root.connected ? qsTr("Qt Design Studio is connected to " + root.ip)
+                    text: root.connected ? qsTr("Qt Design Studio is connected")
                                          : qsTr("Qt Design Studio is disconnected.")
                 }
             }
 
             Connections {
                 target: backend
-                function onConnectedChanged(isConnected, ip) {
+                function onConnectedChanged(isConnected) {
+                    console.log("Connected changed to", isConnected)
                     root.connected = isConnected
-                    root.ip = ip
                 }
             }
         }
diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp
index 13b7d22..c9b6b2b 100644
--- a/src/backend/backend.cpp
+++ b/src/backend/backend.cpp
@@ -29,26 +29,35 @@
 
 #include "logger.h"
 
-Q_DECLARE_JNI_CLASS(Secure, "android/provider/Settings$Secure");
-Q_DECLARE_JNI_CLASS(String, "java/lang/String");
-Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver");
+#ifdef QT_DEBUG
+#define buildType "Debug"
+#else
+#define buildType "Release"
+#endif
 
 Backend::Backend(QObject *parent)
     : QObject(parent)
 {
-    // This will allow us to open the app with the QR code
-    QDesktopServices::setUrlHandler("qtdesignviewer", this, "parseDesignViewerUrl");
+    // register the `qtdesignstudio` url handler to the Android system
     QDesktopServices::setUrlHandler("qtdesignstudio", this, "parseDesignViewerUrl");
-    QDesktopServices::setUrlHandler("https", 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) {
+        if (m_projectManager && m_projectManager->isRunning()) {
             QMetaObject::invokeMethod(m_dsManager.get(),
                                       "sendProjectLogs",
                                       Qt::QueuedConnection,
@@ -76,50 +85,28 @@ Backend::Backend(QObject *parent)
     qDebug() << "-- Product version: " << QSysInfo::productVersion();
     qDebug() << "-- Build ABI: " << QSysInfo::buildAbi();
     qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture();
-    qDebug() << "-- Device serial: " << getDeviceSerial();
-    qDebug() << "-- Device unique ID: " << getDeviceUuid();
+    qDebug() << "-- Device unique ID: " << m_settings.deviceUuid();
 }
 
 Backend::~Backend()
 {
     m_dsManagerThread.quit();
+    m_projectManagerThread.quit();
     m_dsManagerThread.wait();
-}
-
-// this function returns the device serial number (which is really unique)
-// but may not be reliable on all devices. Instead we use the device UUID.
-QByteArray Backend::getDeviceSerial()
-{
-    auto context = QNativeInterface::QAndroidApplication::context();
-    auto contentResolver = context.callMethod<QtJniTypes::ContentResolver>("getContentResolver");
-    auto androidId = QtJniTypes::Secure::callStaticMethod<jstring>("getString",
-                                                                   contentResolver,
-                                                                   QStringLiteral("android_id"));
-    return androidId.toString().toLatin1();
-}
-
-QString Backend::getDeviceUuid()
-{
-    if (m_settings.deviceUuid().isEmpty()) {
-        qDebug() << "Device UUID not found. Generating a new one.";
-        m_settings.setDeviceUuid(QUuid::createUuid().toString(QUuid::WithoutBraces));
-    }
-
-    return m_settings.deviceUuid();
+    m_projectManagerThread.wait();
 }
 
 QString Backend::buildInfo() const
 {
-#ifdef QT_DEBUG
-    const QString buildType = "Debug";
-#else
-    const QString buildType = "Release";
-#endif
-    return {QCoreApplication::applicationVersion() + "\nTechnology Preview - "
-            + QString(CMAKE_VAR_GIT_VERSION) + "\nQt " + QString(QT_VERSION_STR) + " - " + buildType
-            + " Build" + "\nQt Quick Components " + QString(CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION)
-            + "\nZXing-Cpp: " + QString(CMAKE_VAR_ZXING_VERSION)
-            + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()};
+    // clang-format off
+    return {
+        QCoreApplication::applicationVersion() +
+        "\nTechnology Preview - "+ 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::updatePopup(const QString &text, bool indeterminate)
@@ -129,51 +116,61 @@ void Backend::updatePopup(const QString &text, bool indeterminate)
     QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
 }
 
-void Backend::initializeProjectManager()
+void Backend::initProjectManager()
 {
-    m_projectManager.reset(new ProjectManager(this, m_settings.autoScaleProject()));
+    m_projectManager.reset(new ProjectManager(this));
 
-    connect(m_projectManager.get(), &QObject::destroyed, this, [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...");
 }
 
 void Backend::initDsManager()
 {
     qDebug() << "Design Studio Manager thread started. Initializing Design Studio Manager";
-    m_dsManager.reset(new DesignStudioManager(nullptr, false, getDeviceUuid()));
+    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, ipAddr);
-            });
+            [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 && m_projectManager) {
-                    m_projectManager->stopProject();
+                if (id == m_lastProjectSenderId) {
+                    QMetaObject::invokeMethod(m_projectManager.get(),
+                                              "stopProject",
+                                              Qt::QueuedConnection);
                 }
-                emit connectedChanged(false, ipAddr);
             });
 
+    connect(m_dsManager.get(), &DesignStudioManager::allDesignStudiosDisconnected, this, [this] {
+        qDebug() << "All Design Studios disconnected";
+        emit connectedChanged(false);
+    });
+
+    connect(m_dsManager.get(), &DesignStudioManager::projectIncoming, this, [this] {
+        emit popupOpen("Receiving project...");
+    });
+
     connect(m_dsManager.get(),
             &DesignStudioManager::projectStopRequested,
             this,
             [this](const QString &id) {
-                if (m_projectManager) {
-                    m_projectManager->stopProject();
-                }
+                QMetaObject::invokeMethod(m_projectManager.get(),
+                                          "stopProject",
+                                          Qt::QueuedConnection);
             });
 
-    m_dsManager->initialize();
+    m_dsManager->init();
     qDebug() << "Design Studio Manager initialized";
 }
 
@@ -189,6 +186,9 @@ void Backend::connectDesignStudio(const QString &ipAddr)
 
 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(),
@@ -196,19 +196,11 @@ void Backend::runProject(const QString &id, const QByteArray &projectData)
                               Qt::QueuedConnection,
                               Q_ARG(QString, id));
 
-    initializeProjectManager();
-    emit popupOpen();
-    updatePopup("Unpacking project...");
-    QString projectPath = m_projectManager->unpackProject(projectData);
-    updatePopup("Running project...");
-
-    if (!m_projectManager->runProject(projectPath)) {
-        qCritical() << "Could not run project. Please check the logs for more information.";
-        m_projectManager.reset();
-    } else {
-        m_projectManager->showAppWindow();
-    }
-
+    QMetaObject::invokeMethod(m_projectManager.get(),
+                              "runProject",
+                              Qt::QueuedConnection,
+                              Q_ARG(QByteArray, projectData),
+                              Q_ARG(bool, autoScaleProject()));
     emit popupClose();
 }
 
@@ -228,18 +220,18 @@ void Backend::scanQrCode()
 
 void Backend::parseDesignViewerUrl(const QUrl &url)
 {
-    if (url.scheme() == "qtdesignstudio") {
-        if (url.host().isEmpty()) {
-            qWarning() << "No Design Studio IP address found in the QR code. URL:"
-                       << url.toString();
-            return;
-        }
-
-        connectDesignStudio(url.host());
-    } else {
+    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()
diff --git a/src/backend/backend.h b/src/backend/backend.h
index 297cc52..0908b56 100644
--- a/src/backend/backend.h
+++ b/src/backend/backend.h
@@ -41,13 +41,14 @@ public:
     ~Backend();
 
 private:
-    // Other members
     QScopedPointer<ProjectManager> m_projectManager;
-    QScopedPointer<QrScanner> m_qrScanner;
+    QThread m_projectManagerThread;
 
-    // DS Connector
     QScopedPointer<DesignStudioManager> m_dsManager;
     QThread m_dsManagerThread;
+
+    QScopedPointer<QrScanner> m_qrScanner;
+
     QString m_lastProjectSenderId;
 
     // Settings
@@ -55,11 +56,9 @@ private:
 
     // member functions
     void updatePopup(const QString &text, bool indeterminate = true);
-    QByteArray getDeviceSerial();
-    QString getDeviceUuid();
 
     void initDsManager();
-    void initializeProjectManager();
+    void initProjectManager();
 
     void runProject(const QString &id, const QByteArray &projectData);
 
@@ -71,7 +70,7 @@ signals:
     void popupClose();
 
     // UI signals - from DS Manager page
-    void connectedChanged(bool connected, const QString &ipAddr);
+    void connectedChanged(bool connected);
 
     // UI signals - from UI
     void connectToDesignStudio(const QString &url);
diff --git a/src/backend/dsconnector/ds.cpp b/src/backend/dsconnector/ds.cpp
index 86bb1fa..c95ec8c 100644
--- a/src/backend/dsconnector/ds.cpp
+++ b/src/backend/dsconnector/ds.cpp
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "ds.h"
 
@@ -29,17 +7,10 @@
 #include <QJsonObject>
 #include <QScreen>
 
-DesignStudio::DesignStudio(const QString &ipv4Addr,
-                           const quint16 port,
-                           const QString &designStudioId,
-                           const QString &deviceId,
-                           QObject *parent)
+DesignStudio::DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent)
     : QObject(parent)
-    , m_ipv4Addr(ipv4Addr)
-    , m_port(port)
-    , m_url("ws://" + m_ipv4Addr + ":" + QString::number(m_port))
-    , m_id(designStudioId)
-    , m_deviceUuid(deviceId)
+    , m_deviceUuid(m_deviceUuid)
+    , m_socket(socket)
 {
     initPingPong();
     initSocket();
@@ -48,71 +19,46 @@ DesignStudio::DesignStudio(const QString &ipv4Addr,
 void DesignStudio::initPingPong()
 {
     connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
-        m_socket.ping();
+        m_socket->ping();
         m_pongTimer.start(15000);
     });
 
-    connect(&m_socket, &QWebSocket::pong, this, [this](quint64 elapsedTime, const QByteArray &) {
-        m_pongTimer.stop();
-    });
+    connect(m_socket.data(),
+            &QWebSocket::pong,
+            this,
+            [this](quint64 elapsedTime, const QByteArray &) { m_pongTimer.stop(); });
 
     connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
         qDebug() << "Design Studio" << m_id << "is not responding. Reconnecting.";
-        m_socket.close();
+        m_socket->close();
     });
 }
 
 void DesignStudio::initSocket()
 {
-    connect(&m_socket, &QWebSocket::textMessageReceived, this, &DesignStudio::processTextMessage);
-    connect(&m_socket,
-            &QWebSocket::binaryMessageReceived,
-            this,
-            &DesignStudio::processBinaryMessage);
-    connect(&m_socket, &QWebSocket::disconnected, this, [this]() {
-        QTimer::singleShot(m_reconnectTimeout, this, [this]() { m_socket.open(m_url); });
-    });
+    m_pingTimer.start(15000);
+    m_pongTimer.stop();
 
-    connect(&m_socket, &QWebSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) {
-        if (state == QAbstractSocket::ConnectedState) {
-            qDebug() << "Connected to Design Studio" << m_id;
-            m_socketWasConnected = true;
-            m_pingTimer.start(15000);
-            m_pongTimer.stop();
-            emit connected(m_id);
-        } else if (state == QAbstractSocket::UnconnectedState && m_socketWasConnected) {
-            qDebug() << "Disconnected from Design Studio" << m_id;
-            m_socketWasConnected = false;
-            m_pingTimer.stop();
-            m_pongTimer.stop();
-            emit disconnected(m_id);
-        }
+    connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
+        qDebug() << "Design Studio" << m_id << "disconnected";
+        m_pingTimer.stop();
+        m_pongTimer.stop();
+        emit disconnected(m_id);
     });
-}
-
-void DesignStudio::connectToDesignStudio()
-{
-    m_socket.open(m_url);
-}
 
-void DesignStudio::disconnectFromDesignStudio()
-{
-    m_socket.close();
+    connect(m_socket.data(),
+            &QWebSocket::textMessageReceived,
+            this,
+            &DesignStudio::processTextMessage);
+    connect(m_socket.data(),
+            &QWebSocket::binaryMessageReceived,
+            this,
+            &DesignStudio::processBinaryMessage);
 }
 
 QString DesignStudio::ipv4Addr() const
 {
-    return m_ipv4Addr;
-}
-
-quint16 DesignStudio::port() const
-{
-    return m_port;
-}
-
-QUrl DesignStudio::url() const
-{
-    return m_url;
+    return m_socket->peerAddress().toString();
 }
 
 QString DesignStudio::id() const
@@ -120,24 +66,6 @@ QString DesignStudio::id() const
     return m_id;
 }
 
-bool DesignStudio::isConnected() const
-{
-    return m_socket.state() == QAbstractSocket::ConnectedState;
-}
-
-void DesignStudio::setIpAddress(const QString &ipv4Addr)
-{
-    m_socket.close();
-    m_ipv4Addr = ipv4Addr;
-    m_url = QUrl("ws://" + m_ipv4Addr + ":" + QString::number(m_port));
-    m_socket.open(m_url);
-}
-
-void DesignStudio::setDesignStudioId(const QString &designStudioId)
-{
-    m_id = designStudioId;
-}
-
 void DesignStudio::sendDeviceInfo()
 {
     const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
@@ -162,7 +90,7 @@ void DesignStudio::sendData(const QLatin1String &dataType, const QJsonValue &dat
     message["dataType"] = dataType;
     message["data"] = data;
 
-    m_socket.sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
+    m_socket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
 }
 
 void DesignStudio::processTextMessage(const QString &message)
@@ -185,13 +113,10 @@ void DesignStudio::processTextMessage(const QString &message)
 
     if (dataType == PackageFromDesignStudio::designStudioReady) {
         const QString newDesignStudioId = jsonObj.value("data").toString();
-        if (m_id != newDesignStudioId) {
-            qDebug() << "Design Studio" << m_id << "changed ID to" << newDesignStudioId
-                     << "with IP address" << m_ipv4Addr << "Notifying ds manager.";
-            m_id = newDesignStudioId;
-            emit idReceived(newDesignStudioId, m_ipv4Addr);
-        }
+        qDebug() << "Design Studio ready with ID" << newDesignStudioId;
+        m_id = newDesignStudioId;
         sendDeviceInfo();
+        emit idReceived(newDesignStudioId, ipv4Addr());
     } else if (dataType == PackageFromDesignStudio::projectData) {
         qDebug() << "Project is expected";
         emit projectIncoming();
@@ -203,9 +128,9 @@ void DesignStudio::processTextMessage(const QString &message)
     }
 }
 
+// this function should only be triggered when the device is receiving a qmlrc project
 void DesignStudio::processBinaryMessage(const QByteArray &data)
 {
-    // this slot is only triggered when we're receiving a qmlrc project
     qDebug() << "Binary message received (most probably a project)";
     emit projectReceived(m_id, data);
 }
diff --git a/src/backend/dsconnector/ds.h b/src/backend/dsconnector/ds.h
index a4cc12a..316b9cd 100644
--- a/src/backend/dsconnector/ds.h
+++ b/src/backend/dsconnector/ds.h
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #pragma once
 
@@ -48,26 +26,11 @@ class DesignStudio : public QObject
 {
     Q_OBJECT
 public:
-    DesignStudio(const QString &ipv4Addr,
-                 const quint16 port,
-                 const QString &id = QString(),
-                 const QString &deviceUuid = QString(),
-                 QObject *parent = nullptr);
-
-    // Socket ops
-    void connectToDesignStudio();
-    void disconnectFromDesignStudio();
+    DesignStudio(QWebSocket *socket, const QString &m_deviceUuid, QObject *parent = nullptr);
 
     // Getters
     QString ipv4Addr() const;
-    quint16 port() const;
     QString id() const;
-    QUrl url() const;
-    bool isConnected() const;
-
-    // Setters
-    void setIpAddress(const QString &ipv4Addr);
-    void setDesignStudioId(const QString &designStudioId);
 
     // Send data
     void sendDeviceInfo();
@@ -77,11 +40,9 @@ public:
 
 private:
     // Network
-    QWebSocket m_socket;
-    QString m_ipv4Addr;
-    quint16 m_port;
-    QUrl m_url;
-    bool m_socketWasConnected = false;
+    QScopedPointer<QWebSocket> m_socket;
+    bool m_socketWasConnected;
+
     QTimer m_pingTimer;
     QTimer m_pongTimer;
 
@@ -89,9 +50,6 @@ private:
     QString m_id;
     QString m_deviceUuid;
 
-    // Settings
-    constexpr static int m_reconnectTimeout = 5000;
-
     // DS comm
     void sendData(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
 
@@ -110,7 +68,6 @@ signals:
 
     // DS signals
     void idReceived(const QString &id, const QString &ipv4Addr);
-
     void projectIncoming();
     void projectReceived(const QString &id, const QByteArray &data);
     void projectStopRequested(const QString &id);
diff --git a/src/backend/dsconnector/dsdiscovery.cpp b/src/backend/dsconnector/dsdiscovery.cpp
deleted file mode 100644
index 09c7c6c..0000000
--- a/src/backend/dsconnector/dsdiscovery.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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 "dsdiscovery.h"
-
-#include <QNetworkDatagram>
-#include <QNetworkInterface>
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonParseError>
-
-DesignStudioDiscovery::DesignStudioDiscovery(QObject *parent)
-    : QObject(parent)
-{
-    const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
-    for (const QNetworkInterface &interface : interfaces) {
-        if (interface.flags().testFlag(QNetworkInterface::IsUp)
-            && interface.flags().testFlag(QNetworkInterface::IsRunning)
-            && !interface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
-            for (const QNetworkAddressEntry &entry : interface.addressEntries()) {
-                if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
-                    const bool retVal = m_udpSocket.bind(QHostAddress::AnyIPv4,
-                                                         53452,
-                                                         QUdpSocket::ShareAddress);
-                    if (!retVal) {
-                        qWarning() << "UDP:: Failed to bind to port 53452";
-                    }
-                }
-            }
-        }
-    }
-
-    qDebug() << "UDP:: Listening on" << m_udpSocket.localAddress() << "port"
-             << m_udpSocket.localPort();
-    connect(&m_udpSocket, &QUdpSocket::readyRead, this, &DesignStudioDiscovery::onReadyRead);
-}
-
-void DesignStudioDiscovery::onReadyRead()
-{
-    while (m_udpSocket.hasPendingDatagrams()) {
-        const QNetworkDatagram datagram = m_udpSocket.receiveDatagram();
-        const QString message = datagram.data();
-        const QString ipv4Addr = datagram.senderAddress().toString();
-
-        QJsonParseError jsonError;
-        const QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toUtf8(), &jsonError);
-
-        if (jsonError.error != QJsonParseError::NoError) {
-            qDebug() << "UDP:: Failed to parse JSON message:" << jsonError.errorString();
-            continue;
-        }
-
-        const QJsonObject jsonObj = jsonDoc.object();
-
-        if (!jsonObj.contains("name") && !jsonObj.contains("id")) {
-            qDebug() << "UDP:: Invalid JSON message:" << jsonObj;
-            continue;
-        }
-
-        emit designStudioFound(ipv4Addr, jsonObj.value("id").toString());
-    }
-}
diff --git a/src/backend/dsconnector/dsdiscovery.h b/src/backend/dsconnector/dsdiscovery.h
deleted file mode 100644
index f3756c6..0000000
--- a/src/backend/dsconnector/dsdiscovery.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
-
-#pragma once
-
-#include <QUdpSocket>
-
-class DesignStudioDiscovery : public QObject
-{
-    Q_OBJECT
-public:
-    explicit DesignStudioDiscovery(QObject *parent = nullptr);
-
-private slots:
-    void onReadyRead();
-
-signals:
-    void designStudioFound(const QString &ipv4Addr, const QString &id);
-
-private:
-    QUdpSocket m_udpSocket;
-};
diff --git a/src/backend/dsconnector/dsmanager.cpp b/src/backend/dsconnector/dsmanager.cpp
index 777b85c..c0a4c32 100644
--- a/src/backend/dsconnector/dsmanager.cpp
+++ b/src/backend/dsconnector/dsmanager.cpp
@@ -1,48 +1,21 @@
-/****************************************************************************
-**
-** 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "dsmanager.h"
-#include "backend/dsconnector/ds.h"
+#include "ds.h"
 
-#include <QFile>
 #include <QJsonArray>
 #include <QJsonDocument>
 #include <QJsonObject>
-#include <QStandardPaths>
+#include <QUdpSocket>
 
-DesignStudioManager::DesignStudioManager(QObject *parent,
-                                         const bool enableDiscovery,
-                                         const QString &deviceUuid)
+DesignStudioManager::DesignStudioManager(const QString &deviceUuid, QObject *parent)
     : QObject(parent)
     , m_deviceUuid(deviceUuid)
-    , m_enableDiscovery(enableDiscovery)
-    , m_settingsPath(
-          QStandardPaths::writableLocation(QStandardPaths::ConfigLocation).append("/dsmanager.json"))
+    , m_webSocketServer("DesignStudio", QWebSocketServer::NonSecureMode)
 {}
 
-void DesignStudioManager::initialize()
+void DesignStudioManager::init()
 {
     static bool initialized = false;
     if (initialized) {
@@ -50,132 +23,97 @@ void DesignStudioManager::initialize()
     }
     initialized = true;
 
-    initLastKnownDs();
-
-    qDebug() << "Device UUID:" << m_deviceUuid;
-    if (m_enableDiscovery) {
-        qDebug() << "Starting Design Studio discovery";
-        m_discovery = std::make_unique<DesignStudioDiscovery>();
-        connect(m_discovery.get(),
-                &DesignStudioDiscovery::designStudioFound,
-                this,
-                &DesignStudioManager::designStudioFound);
-    }
+    m_webSocketServer.listen(QHostAddress::Any, 40000);
+    connect(&m_webSocketServer,
+            &QWebSocketServer::newConnection,
+            this,
+            &DesignStudioManager::incomingConnection);
+    qDebug() << "TCP server listening on port 40000";
+
+    m_discoveryTimer.setInterval(10000);
+    connect(&m_discoveryTimer, &QTimer::timeout, [this]() {
+        QJsonObject message;
+        message["name"] = "__qtuiviewer__";
+        message["id"] = m_deviceUuid;
+        QByteArray datagram = QJsonDocument(message).toJson(QJsonDocument::Compact);
+
+        QUdpSocket udpSocket;
+        const int port = 53452;
+        udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, port);
+        udpSocket.writeDatagram(datagram, QHostAddress::Broadcast, port);
+    });
 }
 
-void DesignStudioManager::initLastKnownDs()
+void DesignStudioManager::incomingConnection()
 {
-    QFile file(m_settingsPath);
-
-    if (!file.exists()) {
-        qDebug() << "Settings file not found.";
+    auto *socket = m_webSocketServer.nextPendingConnection();
+    if (!socket) {
         return;
     }
 
-    file.open(QIODevice::ReadOnly);
-    QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
-    file.close();
-
-    if (!doc.isObject()) {
-        qDebug() << "Settings file is not a JSON object.";
-        return;
-    }
-
-    QJsonObject obj = doc.object();
-    QJsonArray dsArray = obj["designStudios"].toArray();
-    for (const auto &ds : dsArray) {
-        QJsonObject dsObj = ds.toObject();
-        initDesignStudio(dsObj["ipv4Addr"].toString(), dsObj["designStudioId"].toString());
-    }
-}
-
-void DesignStudioManager::initDesignStudio(const QString &ipv4Addr, const QString &designStudioId)
-{
-    if (m_designStudio) {
-        if (m_designStudio->ipv4Addr() == ipv4Addr && m_designStudio->isConnected()) {
-            qDebug() << "Design Studio" << ipv4Addr << "is already connected";
-            return;
-        }
-    }
-
-    emit designStudioDisconnected(designStudioId, ipv4Addr);
-
-    qDebug() << "Initializing Design Studio" << designStudioId << "with IPv4 address" << ipv4Addr;
-    m_designStudio.reset(new DesignStudio(ipv4Addr, 40000, designStudioId, m_deviceUuid));
+    auto designStudio = QSharedPointer<DesignStudio>(new DesignStudio(socket, m_deviceUuid, this),
+                                                     &QObject::deleteLater);
+    connect(designStudio.data(), &DesignStudio::disconnected, this, [this, designStudio]() {
+        m_designStudios.removeOne(designStudio);
+        qDebug() << "Remaining Design Studios:" << m_designStudios.size();
+        if (m_designStudios.isEmpty())
+            emit allDesignStudiosDisconnected();
+    });
 
-    connect(m_designStudio.get(),
-            &DesignStudio::idReceived,
-            this,
-            [this](const QString &, const QString &) { updateConfigFile(); });
+    connect(designStudio.data(), &DesignStudio::projectIncoming, this, [this]() {
+        emit projectIncoming();
+    });
 
-    connect(m_designStudio.get(),
+    connect(designStudio.data(),
             &DesignStudio::projectReceived,
             this,
-            &DesignStudioManager::projectReceived);
-
-    connect(m_designStudio.get(), &DesignStudio::connected, this, [this](const QString &id) {
-        emit designStudioConnected(id, m_designStudio->ipv4Addr());
-    });
-
-    connect(m_designStudio.get(), &DesignStudio::disconnected, this, [this](const QString &id) {
-        emit designStudioDisconnected(id, m_designStudio->ipv4Addr());
-    });
+            [this, &designStudio](const QString &id, const QByteArray &project) {
+                emit projectReceived(id, project);
+            });
 
-    connect(m_designStudio.get(),
+    connect(designStudio.data(),
             &DesignStudio::projectStopRequested,
             this,
-            &DesignStudioManager::projectStopRequested);
+            [this](const QString &id) { emit projectStopRequested(id); });
 
-    m_designStudio->connectToDesignStudio();
-    updateConfigFile();
+    m_designStudios.append(designStudio);
+    emit designStudioConnected(designStudio->id(), designStudio->ipv4Addr());
 }
 
-void DesignStudioManager::designStudioFound(const QString &ipv4Addr, const QString &id)
+void DesignStudioManager::sendProjectRunning(const QString &id)
 {
-    qDebug() << "Design Studio found with IPv4 address" << ipv4Addr << "and ID" << id;
-
-    // check if the Design Studio is already in the list
-    if (m_designStudio->id() == id && m_designStudio->ipv4Addr() != ipv4Addr) {
-        qDebug() << "Design Studio" << id << "changed IP address from" << m_designStudio->ipv4Addr()
-                 << "to" << ipv4Addr;
-        m_designStudio->setIpAddress(ipv4Addr);
-        updateConfigFile();
+    for (const auto &designStudio : m_designStudios) {
+        if (designStudio->id() == id) {
+            designStudio->sendProjectRunning();
+        }
     }
 }
 
-void DesignStudioManager::updateConfigFile()
+void DesignStudioManager::sendProjectStopped(const QString &id)
 {
-    QFile file(m_settingsPath);
-    if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
-        qDebug() << "Failed to open settings file for writing";
-        return;
+    for (const auto &designStudio : m_designStudios) {
+        if (designStudio->id() == id) {
+            designStudio->sendProjectStopped();
+        }
     }
-
-    QJsonObject rootObj;
-    QJsonArray dsArray;
-    dsArray.append(
-        QJsonObject{{"ipv4Addr", m_designStudio->ipv4Addr()}, {"id", m_designStudio->id()}});
-
-    rootObj["designStudios"] = dsArray;
-    file.write(QJsonDocument(rootObj).toJson());
 }
 
-void DesignStudioManager::sendProjectRunning(const QString &)
+void DesignStudioManager::sendProjectLogs(const QString &id, const QString &logs)
 {
-    m_designStudio->sendProjectRunning();
-}
-
-void DesignStudioManager::sendProjectStopped(const QString &)
-{
-    m_designStudio->sendProjectStopped();
+    for (const auto &designStudio : m_designStudios) {
+        if (designStudio->id() == id) {
+            designStudio->sendProjectLogs(logs);
+        }
+    }
 }
 
-void DesignStudioManager::sendProjectLogs(const QString &, const QString &logs)
+QString DesignStudioManager::getDesignStudioIp(const QString &id) const
 {
-    m_designStudio->sendProjectLogs(logs);
-}
+    for (const auto &designStudio : m_designStudios) {
+        if (designStudio->id() == id) {
+            return designStudio->ipv4Addr();
+        }
+    }
 
-QString DesignStudioManager::getDesignStudioIp(const QString &) const
-{
-    return m_designStudio ? m_designStudio->ipv4Addr() : QString();
+    return {};
 }
diff --git a/src/backend/dsconnector/dsmanager.h b/src/backend/dsconnector/dsmanager.h
index 913d372..27e964b 100644
--- a/src/backend/dsconnector/dsmanager.h
+++ b/src/backend/dsconnector/dsmanager.h
@@ -1,72 +1,44 @@
-/****************************************************************************
-**
-** 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.
-**
-****************************************************************************/
-
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 #pragma once
 
 #include <QObject>
+#include <QWebSocketServer>
 
 #include "ds.h"
-#include "dsdiscovery.h"
 
 class DesignStudioManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit DesignStudioManager(QObject *parent = nullptr,
-                                 const bool enableDiscovery = false,
-                                 const QString &deviceUuid = {});
+    explicit DesignStudioManager(const QString &deviceUuid, QObject *parent = nullptr);
 
-    void initialize();
+    void init();
 
 public slots:
     void sendProjectRunning(const QString &id);
     void sendProjectStopped(const QString &id);
     void sendProjectLogs(const QString &id, const QString &logs);
-    void initDesignStudio(const QString &ipv4Addr, const QString &designStudioId = {});
     QString getDesignStudioIp(const QString &id) const;
 
 private:
-    // Discovery object
-    std::unique_ptr<DesignStudioDiscovery> m_discovery;
-    const bool m_enableDiscovery;
+    // Network
+    QWebSocketServer m_webSocketServer;
+    QTimer m_discoveryTimer;
 
-    // Active Design Studio. Only one can be active at a time.
-    std::unique_ptr<DesignStudio> m_designStudio;
+    // Connected Design Studios
+    QList<QSharedPointer<DesignStudio>> m_designStudios;
 
     // other members
     const QString m_deviceUuid;
-    const QString m_settingsPath;
 
-    void initLastKnownDs();
-    void updateConfigFile();
-    void dsIdReceived(const QString &id, const QString &ipv4Addr);
-    void designStudioFound(const QString &ipv4Addr, const QString &id);
+    void incomingConnection();
 
 signals:
+    void projectIncoming();
     void projectReceived(const QString &id, const QByteArray &project);
     void designStudioConnected(const QString &id, const QString &ipAddr);
     void designStudioDisconnected(const QString &id, const QString &ipAddr);
+    void allDesignStudiosDisconnected();
     void projectStopRequested(const QString &id);
 };
diff --git a/src/backend/logger.h b/src/backend/logger.h
index 6c233f7..6635e8f 100644
--- a/src/backend/logger.h
+++ b/src/backend/logger.h
@@ -1,33 +1,13 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #pragma once
 
 #include <QMessageBox>
 
+#ifdef Q_OS_ANDROID
 #include <android/log.h>
+#endif
 
 class Logger : public QObject
 {
@@ -70,7 +50,11 @@ public:
         }
 
         newLog += logPrefix + localMsg + logSuffix + "\n";
+#ifdef Q_OS_ANDROID
         __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog));
+#else
+        fprintf(stderr, "%s", qPrintable(newLog));
+#endif
 
         if (type == QtCriticalMsg || type == QtFatalMsg) {
             QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
diff --git a/src/backend/main.cpp b/src/backend/main.cpp
index 2141249..05bd977 100644
--- a/src/backend/main.cpp
+++ b/src/backend/main.cpp
@@ -23,8 +23,6 @@
 **
 ****************************************************************************/
 
-#include <android/log.h>
-
 #include <QApplication>
 #include <QQmlContext>
 #include <QQmlEngine>
diff --git a/src/backend/projectmanager.cpp b/src/backend/projectmanager.cpp
index 3aee233..6f2bcb1 100644
--- a/src/backend/projectmanager.cpp
+++ b/src/backend/projectmanager.cpp
@@ -33,14 +33,10 @@
 #include <QRegularExpression>
 #include <QResource>
 #include <QTemporaryDir>
-#include <QtCore/private/qzipreader_p.h>
 
-ProjectManager::ProjectManager(QObject *parent, bool autoScaleProject)
+ProjectManager::ProjectManager(QObject *parent)
     : QObject(parent)
-    , m_autoScaleProject(autoScaleProject)
-{
-    qDebug() << "ProjectManager created. Auto scale project: " << m_autoScaleProject;
-}
+{}
 
 ProjectManager::~ProjectManager()
 {
@@ -61,51 +57,28 @@ void ProjectManager::cleanupResources()
     }
 }
 
-QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip)
+QString ProjectManager::unpackProject(const QByteArray &project)
 {
-    QTemporaryDir tempDir("qmlprojector");
-    QString projectPath = tempDir.path();
-
-    if (extractZip) {
-        QDir().mkpath(projectPath);
-        QBuffer buffer;
-        buffer.setData(project);
-        buffer.open(QIODevice::ReadOnly);
-        QZipReader reader(&buffer);
-        reader.extractAll(projectPath);
-    }
-
-    qDebug() << "Initial project location: " << projectPath;
-
-    QDir projectPathDir(projectPath);
-
-    // maybe it was not a zip file so try it as resource binary
-    if (projectPathDir.isEmpty()) {
-        if (extractZip)
-            qWarning("File could not be extracted. Trying to open it as a resource file.");
-
-        cleanupResources();
-
-        m_projectData = project;
-        const uchar *data;
-        data = reinterpret_cast<const uchar *>(m_projectData.data());
-        qDebug() << "Registering resource data. Size: " << m_projectData.size();
+    cleanupResources();
 
-        const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
-        m_projectPath = resourcePath;
+    m_projectData = project;
+    const uchar *data;
+    data = reinterpret_cast<const uchar *>(m_projectData.data());
+    qDebug() << "Registering resource data. Size: " << m_projectData.size();
 
-        if (!QDir(resourcePath).removeRecursively()) {
-            qWarning() << "Could not remove resource path: " << resourcePath;
-        }
+    const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
+    m_projectPath = resourcePath;
 
-        if (!QResource::registerResource(data, resourcePath)) {
-            qCritical() << "Can not load the resource data.";
-            return {};
-        }
+    if (!QDir(resourcePath).removeRecursively()) {
+        qWarning() << "Could not remove resource path: " << resourcePath;
+    }
 
-        projectPath = ":" + resourcePath;
+    if (!QResource::registerResource(data, resourcePath)) {
+        qCritical() << "Can not load the resource data.";
+        return {};
     }
-    return projectPath;
+
+    return QString(":") + resourcePath;
 }
 
 QString ProjectManager::findFile(const QString &dir, const QString &filter)
@@ -171,8 +144,11 @@ bool ProjectManager::isQt6Project(const QString &qmlProjectFileContent)
     return qt6ProjectMatch.hasMatch();
 }
 
-bool ProjectManager::runProject(const QString &projectPath)
+bool ProjectManager::runProject(const QByteArray &project, const bool autoScaleProject)
 {
+    m_autoScaleProject = autoScaleProject;
+
+    const QString projectPath = unpackProject(project);
     qDebug() << "Project location: " << projectPath;
 
     const QString qmlProjectFile = findFile(projectPath, "*.qmlproject");
@@ -275,6 +251,7 @@ bool ProjectManager::runProject(const QString &projectPath)
         view->setResizeMode(QQuickView::SizeViewToRootObject);
         m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height()));
     }
+    showAppWindow();
     return true;
 }
 
@@ -337,14 +314,16 @@ void ProjectManager::showAppWindow()
     m_quickWindow->showMaximized();
 }
 
-void ProjectManager::hideAppWindow()
-{
-    qDebug("Hiding the QML app window");
-    m_quickWindow->hide();
-}
-
 void ProjectManager::stopProject()
 {
+    if (!m_quickWindow || !m_quickWindow->isVisible())
+        return;
+
     qDebug("Stopping the QML app window");
     m_quickWindow->close();
 }
+
+bool ProjectManager::isRunning()
+{
+    return m_quickWindow && m_quickWindow->isVisible();
+}
diff --git a/src/backend/projectmanager.h b/src/backend/projectmanager.h
index 6a1a746..cde6e95 100644
--- a/src/backend/projectmanager.h
+++ b/src/backend/projectmanager.h
@@ -31,39 +31,41 @@ class ProjectManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit ProjectManager(QObject *parent = nullptr, bool autoScaleProject = false);
+    explicit ProjectManager(QObject *parent = nullptr);
     ~ProjectManager();
 
-    QString unpackProject(const QByteArray &project, bool extractZip = false);
-    bool runProject(const QString &projectPath);
-    void stopProject();
-
-    void showAppWindow();
-    void hideAppWindow();
+    bool isRunning();
 
 public slots:
-    void orientateWindow(Qt::ScreenOrientation orientation);
+    bool runProject(const QByteArray &project, const bool autoScaleProject);
+    void stopProject();
 
 private:
     // Member variables
     QByteArray m_projectData;
     QString m_projectPath;
-    const bool m_autoScaleProject;
+    bool m_autoScaleProject;
 
     // Qml related members
     QScopedPointer<QQmlEngine> m_qmlEngine;
     QScopedPointer<QQmlComponent> m_qmlComponent;
     QScopedPointer<QQuickWindow> m_quickWindow;
 
-    // Member functions
+    // resource management
+    void cleanupResources();
+    QString unpackProject(const QByteArray &project);
+
+    // project parsers
     QString findFile(const QString &dir, const QString &filter);
     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);
-    void cleanupResources();
+
+    // window management
+    void orientateWindow(Qt::ScreenOrientation orientation);
+    void showAppWindow();
 
 signals:
     void closingProject();
diff --git a/src/backend/qrscanner.cpp b/src/backend/qrscanner.cpp
index d6c2b7f..f66ec0f 100644
--- a/src/backend/qrscanner.cpp
+++ b/src/backend/qrscanner.cpp
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "qrscanner.h"
 
diff --git a/src/backend/qrscanner.h b/src/backend/qrscanner.h
index 8931d49..3ba3cc8 100644
--- a/src/backend/qrscanner.h
+++ b/src/backend/qrscanner.h
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #pragma once
 
diff --git a/src/backend/settings.cpp b/src/backend/settings.cpp
index 3dfedfd..0ff2f5b 100644
--- a/src/backend/settings.cpp
+++ b/src/backend/settings.cpp
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "settings.h"
 
diff --git a/src/backend/settings.h b/src/backend/settings.h
index 96f034f..b88309b 100644
--- a/src/backend/settings.h
+++ b/src/backend/settings.h
@@ -1,27 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
 #pragma once
 
 #include <QJsonObject>
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b006a33..3e1c7b1 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -4,10 +4,7 @@ find_package(Qt6 REQUIRED COMPONENTS Test Core Quick Gui Multimedia WebSockets)
 
 enable_testing(true)
 qt_add_executable(${TARGET_NAME}
-    tst_designstudio.cpp tst_designstudio.h
-    tst_dsdiscovery.cpp tst_dsdiscovery.h
     tst_settings.cpp tst_settings.h
-    mock.h
     main.cpp
 )
 
diff --git a/tests/main.cpp b/tests/main.cpp
index 2947812..7076f50 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -1,34 +1,10 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include <QGuiApplication>
 #include <QTemporaryFile>
 #include <QTest>
 
-#include "tst_designstudio.h"
-#include "tst_dsdiscovery.h"
 #include "tst_settings.h"
 
 #define DEBUG_LINE qDebug() << "Debugline:" << __LINE__
@@ -90,8 +66,6 @@ int main(int argc, char *argv[])
     outputFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
     outputFile.write("<testsuites>\n");
 
-    runTest(new TestDesignStudio);
-    runTest(new TestDesignStudioDiscovery);
     runTest(new TestSettings);
 
     outputFile.write("</testsuites>\n");
diff --git a/tests/mock.h b/tests/mock.h
deleted file mode 100644
index 428bd4f..0000000
--- a/tests/mock.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
-
-#pragma once
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QObject>
-#include <QTimer>
-#include <QUdpSocket>
-#include <QWebSocket>
-#include <QWebSocketServer>
-#include <qtypes.h>
-
-#define UDP_PORT 53452
-
-class DesignStudioMock : public QObject
-{
-    Q_OBJECT
-
-private:
-    // self advertisement over udp
-    QTimer m_discoveryTimer;
-    const QString m_id;
-
-    // websocket connection to design studio
-    QWebSocketServer m_webSocketServer;
-
-public:
-    DesignStudioMock(QObject *parent = nullptr, const QString &id = "123456")
-        : QObject(parent)
-        , m_id(id)
-        , m_webSocketServer("DesignStudioMock", QWebSocketServer::NonSecureMode, this)
-
-    {
-        connect(&m_discoveryTimer, &QTimer::timeout, [this] {
-            QUdpSocket udpSocket;
-
-            QJsonObject message;
-            message["name"] = "__designstudio__";
-            message["id"] = m_id;
-            QByteArray datagram = QJsonDocument(message).toJson(QJsonDocument::Compact);
-
-            udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, UDP_PORT);
-            udpSocket.writeDatagram(datagram, QHostAddress::Broadcast, UDP_PORT);
-        });
-
-        m_discoveryTimer.setInterval(100);
-        m_discoveryTimer.start();
-
-        connect(&m_webSocketServer, &QWebSocketServer::newConnection, [this] {
-            QWebSocket *socket = m_webSocketServer.nextPendingConnection();
-            connect(socket,
-                    &QWebSocket::textMessageReceived,
-                    [this, socket](const QString &message) {
-                        QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
-                    });
-        });
-    }
-
-    void enableDiscovery(const bool enable)
-    {
-        if (enable) {
-            m_discoveryTimer.start();
-        } else {
-            m_discoveryTimer.stop();
-        }
-    }
-
-    void connectToDesignStudio();
-    void disconnectFromDesignStudio();
-
-signals:
-    void disconnected();
-};
diff --git a/tests/tst_designstudio.cpp b/tests/tst_designstudio.cpp
deleted file mode 100644
index 5e71c26..0000000
--- a/tests/tst_designstudio.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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 "tst_designstudio.h"
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QLatin1String>
-#include <QSignalSpy>
-#include <QTest>
-#include <QWebSocket>
-#include <QWebSocketServer>
-
-#include "backend/dsconnector/ds.h"
-
-QJsonObject createPackage(const QLatin1String &type, const QString &data)
-{
-    QJsonObject obj;
-    obj.insert("dataType", type.toString());
-    obj.insert("data", data);
-    return obj;
-}
-
-void TestDesignStudio::initTestCase()
-{
-    qDebug() << "Initialize TestDesignStudio";
-    ds = new DesignStudio("localhost", 40000, "id");
-    QVERIFY(ds != nullptr);
-}
-
-void TestDesignStudio::testIpv4Addr()
-{
-    QCOMPARE(ds->ipv4Addr(), QString("localhost"));
-}
-
-void TestDesignStudio::testId()
-{
-    QCOMPARE(ds->id(), QString("id"));
-}
-
-void TestDesignStudio::testPort()
-{
-    QCOMPARE(ds->port(), quint16(40000));
-}
-
-void TestDesignStudio::testUrl()
-{
-    QCOMPARE(ds->url(), QUrl("ws://localhost:40000"));
-}
-
-void TestDesignStudio::testConnected()
-{
-    QHostAddress addr = QHostAddress::LocalHost;
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(addr, 40001));
-
-    DesignStudio ds(addr.toString(), 40001, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-}
-
-void TestDesignStudio::testConnect()
-{
-    QHostAddress addr = QHostAddress::LocalHost;
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(addr, 40002));
-
-    DesignStudio ds(addr.toString(), 40002, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-}
-
-void TestDesignStudio::testDisconnect()
-{
-    QHostAddress addr = QHostAddress::LocalHost;
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(addr, 40002));
-
-    DesignStudio ds(addr.toString(), 40002, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    ds.disconnectFromDesignStudio();
-    QTRY_VERIFY(!ds.isConnected());
-}
-
-void TestDesignStudio::testSendDeviceInfo()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40003));
-
-    DesignStudio ds("localhost", 40003, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    connect(socket, &QWebSocket::textMessageReceived, [](const QString &message) {
-        qDebug() << "Received message" << message;
-
-        QJsonObject obj = QJsonDocument::fromJson(message.toUtf8()).object();
-        QVERIFY(obj.contains("dataType"));
-        QVERIFY(obj.contains("data"));
-        QVERIFY(obj.value("dataType").toString() == PackageToDesignStudio::deviceInfo);
-        QVERIFY(obj.value("data").toObject().contains("screenHeight"));
-        QVERIFY(obj.value("data").toObject().contains("screenWidth"));
-        QVERIFY(obj.value("data").toObject().contains("os"));
-        QVERIFY(obj.value("data").toObject().contains("osVersion"));
-        QVERIFY(obj.value("data").toObject().contains("architecture"));
-        QVERIFY(obj.value("data").toObject().contains("deviceId"));
-        QVERIFY(obj.value("data").toObject().contains("appVersion"));
-    });
-
-    QSignalSpy spy(socket, &QWebSocket::textMessageReceived);
-    QVERIFY(spy.isValid());
-
-    const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::designStudioReady,
-                                                     ds.id());
-    socket->sendTextMessage(QJsonDocument(dsReadyPackage).toJson(QJsonDocument::Compact));
-
-    QTRY_COMPARE(spy.count(), 1);
-    QTest::qWait(500);
-}
-
-void TestDesignStudio::testConnectedSignal()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40010));
-
-    DesignStudio ds("localhost", 40010, "id", "deviceId");
-    QSignalSpy spy(&ds, &DesignStudio::connected);
-    QSignalSpy spy2(&ds, &DesignStudio::idReceived);
-    QVERIFY(spy.isValid());
-    QVERIFY(spy2.isValid());
-
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::designStudioReady,
-                                                     "data");
-    socket->sendTextMessage(QJsonDocument(dsReadyPackage).toJson(QJsonDocument::Compact));
-
-    QTRY_COMPARE(spy.count(), 1);
-    QTRY_COMPARE(spy2.count(), 1);
-}
-
-void TestDesignStudio::testDisconnectedSignal()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40011));
-
-    DesignStudio ds("localhost", 40011, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    QSignalSpy spy(&ds, &DesignStudio::disconnected);
-    QVERIFY(spy.isValid());
-
-    ds.disconnectFromDesignStudio();
-
-    QTRY_COMPARE(spy.count(), 1);
-}
-
-void TestDesignStudio::testProjectIncomingSignal()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40011));
-
-    DesignStudio ds("localhost", 40011, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    QSignalSpy spy(&ds, &DesignStudio::projectIncoming);
-    QVERIFY(spy.isValid());
-
-    const QJsonObject projectIncomingPackage = createPackage(PackageFromDesignStudio::projectData,
-                                                             "data");
-    socket->sendTextMessage(QJsonDocument(projectIncomingPackage).toJson(QJsonDocument::Compact));
-
-    QTRY_COMPARE(spy.count(), 1);
-}
-
-void TestDesignStudio::testProjectReceivedSignal()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40012));
-
-    DesignStudio ds("localhost", 40012, "id", "deviceId");
-    ds.connectToDesignStudio();
-    QTRY_VERIFY(ds.isConnected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    QSignalSpy spy(&ds, &DesignStudio::projectReceived);
-    QVERIFY(spy.isValid());
-
-    const QString arbitraryData = "some arbitrary data";
-    socket->sendBinaryMessage(arbitraryData.toUtf8());
-
-    QTRY_COMPARE(spy.count(), 1);
-}
diff --git a/tests/tst_designstudio.h b/tests/tst_designstudio.h
deleted file mode 100644
index 3abbf5f..0000000
--- a/tests/tst_designstudio.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
-
-#pragma once
-
-#include "backend/dsconnector/ds.h"
-
-class TestDesignStudio : public QObject
-{
-    Q_OBJECT
-private:
-    DesignStudio *ds;
-    QString m_uuid;
-
-private slots:
-    // init
-    void initTestCase();
-
-    // test getters
-    void testIpv4Addr();
-    void testId();
-    void testPort();
-    void testUrl();
-
-    // test status check
-    void testConnected();
-
-    // test connect/disconnect
-    void testConnect();
-    void testDisconnect();
-
-    // test send data
-    void testSendDeviceInfo();
-
-    // test signals
-    void testConnectedSignal();
-    void testDisconnectedSignal();
-    void testProjectIncomingSignal();
-    void testProjectReceivedSignal();
-};
diff --git a/tests/tst_dsdiscovery.cpp b/tests/tst_dsdiscovery.cpp
deleted file mode 100644
index e5d1a7a..0000000
--- a/tests/tst_dsdiscovery.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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 "tst_dsdiscovery.h"
-
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QSignalSpy>
-#include <QUdpSocket>
-
-#include "backend/dsconnector/dsdiscovery.h"
-
-#define UDP_PORT 53452
-
-void TestDesignStudioDiscovery::initTestCase()
-{
-    qDebug() << "Initialize TestDesignStudioDiscovery";
-
-    connect(&m_timer, &QTimer::timeout, [] {
-        QUdpSocket udpSocket;
-
-        QJsonObject message;
-        message["name"] = "__designstudio__";
-        message["id"] = "123456";
-        QByteArray datagram = QJsonDocument(message).toJson(QJsonDocument::Compact);
-
-        udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, UDP_PORT);
-        udpSocket.writeDatagram(datagram, QHostAddress::Broadcast, UDP_PORT);
-    });
-}
-
-void TestDesignStudioDiscovery::testDesignStudioFoundSignal()
-{
-    qDebug() << "Test Design Studio Found Signal";
-
-    DesignStudioDiscovery dsDiscovery;
-    connect(&dsDiscovery, &DesignStudioDiscovery::designStudioFound, [](const QString &ipv4Addr) {
-        qDebug() << "Design Studio found at" << ipv4Addr;
-    });
-
-    m_timer.start(1000);
-
-    QSignalSpy spy(&dsDiscovery, &DesignStudioDiscovery::designStudioFound);
-}
diff --git a/tests/tst_dsdiscovery.h b/tests/tst_dsdiscovery.h
deleted file mode 100644
index d8f092a..0000000
--- a/tests/tst_dsdiscovery.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
-
-#pragma once
-
-#include <QTimer>
-
-class TestDesignStudioDiscovery : public QObject
-{
-    Q_OBJECT
-private:
-    QTimer m_timer;
-
-private slots:
-    void initTestCase();
-
-    // test signals
-    void testDesignStudioFoundSignal();
-};
diff --git a/tests/tst_settings.cpp b/tests/tst_settings.cpp
index dcb5ffc..a8922db 100644
--- a/tests/tst_settings.cpp
+++ b/tests/tst_settings.cpp
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #include "tst_settings.h"
 
diff --git a/tests/tst_settings.h b/tests/tst_settings.h
index 18ed10d..6c70c64 100644
--- a/tests/tst_settings.h
+++ b/tests/tst_settings.h
@@ -1,27 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2024 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.
-**
-****************************************************************************/
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
 
 #pragma once
 
-- 
GitLab