diff --git a/LICENSE b/LICENSE
index abd492e9f5eb09fcef3daec62e4ec1f5bc6cef48..bed53b89c7bad9e0246db317e064b94b8f8801bc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -61,4 +61,4 @@ The Free Software Foundation may publish revised and/or new versions of the GNU
 
 Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
 
-If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
\ No newline at end of file
+If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
diff --git a/README.md b/README.md
index 73c517d7213e6ea7f437b9222c520e894727ea3c..558d39c8b32cfa8a43d3662ff3b8e9e776cadf2c 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
 
 ## About
 
-Launch [Design Studio](https://www.qt.io/ui-design-tools) projects in your Android device. The viewer that helps you do with [Qt for Android](https://doc.qt.io/qt-6/android.html).
+Launch [Qt Design Studio](https://www.qt.io/ui-design-tools) projects in your Android device. The viewer that helps you do with [Qt for Android](https://doc.qt.io/qt-6/android.html).
 
-Qt UI Viewer works with minimum Android 33.
+Qt UI Viewer works with minimum Android 11 (API level 30).
 
 ## Getting the App
 
@@ -72,7 +72,9 @@ cmake \
     -DCMAKE_TOOLCHAIN_FILE=<qt-android-path>/lib/cmake/Qt6/qt.toolchain.cmake \
     -DANDROID_SDK_ROOT=<android-sdk-path> \
     -DANDROID_NDK_ROOT=<android-sdk-path>/ndk/<ndk-version> \
-    -DANDROID_OPENSSL_PATH=<openssl-path>
+    -DANDROID_OPENSSL_PATH=<openssl-path> \
+    -DGOOGLE_PLAY_APP_VERSION=30 \ ## see the 'Versioning' section
+    -DBUILD_EXAMPLES=OFF
 
 cmake --build build
 ```
@@ -94,6 +96,24 @@ xunit-viewer -r output.junit.xml
 
 It'll create a `report.html` file that can be opened in a web browser. For more information about `xunit-viewer` please visit the [GitHub repo](https://github.com/lukejpreston/xunit-viewer).
 
+## Versioning
+
+[android:versionCode](https://developer.android.com/guide/topics/manifest/manifest-element#vcode) is an integer value that should be incremented for each new release and it's required by Google Play Store. It's used to determine if the app should be updated or not.
+
+During the development this value can be set to any arbitrary integer. But for the release it should be incremented. The value can be controlled by setting the CMake variable called `GOOGLE_PLAY_APP_VERSION` during the CMake configuration step.
+
+It's easy for an individual developer to forget to increment it before the release. To prevent this, a GitLab pipeline is used to automatically increment this value for each new release. The pipeline is triggered by creating a new tag. The tag should be in the format of `vX.Y.Z` where `X`, `Y`, and `Z` are integers. Then the new value is determined by calculating the following formula:
+
+```text
+android:versionCode = <total_number_of_tags> + 11
+```
+
+The value `11` is coming from the times where `GOOGLE_PLAY_APP_VERSION` was increased without creating a tag, so we have an offset in between the tag count and the actual value. From now on the value should be incremented by creating a new tag.
+
+There's no `AndroidManifest.xml` file in the repository. `AndroidManifest.xml.in` file is used to generate the `AndroidManifest.xml` file during the configuration step. The `versionCode` value is set to the `GOOGLE_PLAY_APP_VERSION` and the `versionName` value is set to the output of the `git describe --always --tags` command.
+
+Both values can be seen in the `AndroidManifest.xml` file after the CMake configuration step is completed.
+
 ## 3rd Party Libraries/Components
 
 * [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp.git)
diff --git a/cicd/stages/build.yml b/cicd/stages/build.yml
index a4dd1e13d2ac9ca4631107bc664f52dcd5919ab2..0398e849f46a365a83414d1a15d1a70263d0af4f 100644
--- a/cicd/stages/build.yml
+++ b/cicd/stages/build.yml
@@ -18,6 +18,14 @@
   - popd
 
 .build-android-apps: &build-android-apps
+  - export GOOGLE_PLAY_APP_VERSION=$(( $(git tag --list | wc -l) +11 ))
+  # where is this magic "$(git tag --list | wc -l) +11" coming from?
+  # "git tag --list | wc -l" counts the number of tags in the repository.
+  # The "+11" is a magic number which is the last GOOGLE_PLAY_APP_VERSION
+  # that was released before starting to add tags to the repository. After
+  # GOOGLE_PLAY_APP_VERSION=11, the tags are added to the repository, and
+  # the GOOGLE_PLAY_APP_VERSION is incremented by 1 for each new tag.
+  - 'echo "Google Play App Version: ${GOOGLE_PLAY_APP_VERSION}"'
   - |
     cmake \
     -S . \
@@ -30,6 +38,7 @@
     -DQT_HOST_PATH=${DOCKER_ENV_QT_PATH_LINUX_GCC_64} \
     -DCMAKE_INSTALL_PREFIX=${QDS_CI_JOB_QT_ANDROID_PATH} \
     -DANDROID_OPENSSL_PATH=${QDS_CI_JOB_OPENSSL_PATH} \
+    -DGOOGLE_PLAY_APP_VERSION=${GOOGLE_PLAY_APP_VERSION} \
     -DBUILD_EXAMPLES=OFF
   - cmake --build ${QDS_CI_JOB_BUILD_PATH} --target aab
 
diff --git a/cicd/stages/test.yml b/cicd/stages/test.yml
index 9fa8a66e3087c6c47bfbe81f2a1925f1bfed11f6..ceb5c5cd5744095169d1efbb33fa8624e118060c 100644
--- a/cicd/stages/test.yml
+++ b/cicd/stages/test.yml
@@ -26,9 +26,9 @@ test-x86_64:
       counter=0
       while [ -z "$(adb shell pidof -s io.qt.qtdesignviewer.test)" ]; do
         echo "Waiting for test to start"
-        sleep 0.1
+        sleep 1
         counter=$((counter+1))
-        if [ $counter -gt 180 ]; then
+        if [ $counter -gt 20 ]; then
           echo "Test did not start in time"
           exit 1
         fi
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5d4948f30840a9d54e2063105f198e09c60b1d7f..558b1d963015e50e0bd2bcd7f41a201bd50147d0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -9,13 +9,14 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network WebSockets)
 qt_add_executable(${PROJECT_NAME}
     backend/importdummy.qml
     backend/main.cpp
+    backend/logger.h
     backend/backend.cpp backend/backend.h
     backend/serviceconnector.cpp backend/serviceconnector.h
     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/dsconnector/tcpdatatypes.h
     backend/qrscanner.cpp backend/qrscanner.h
     ui/main.qml
     ../3rdparty/zxing-cpp/example/ZXingQtReader.h
@@ -27,6 +28,7 @@ qt_add_resources(${PROJECT_NAME} "images"
         ui/content/images/appicon.png
         ui/content/images/closed_eye.png
         ui/content/images/open_eye.png
+        ui/content/images/container.png
 )
 
 qt_add_resources(${PROJECT_NAME} "qml"
@@ -54,10 +56,10 @@ qt_add_library(qtuiviewerlib OBJECT
     EXCLUDE_FROM_ALL
     backend/projectmanager.cpp backend/projectmanager.h
     backend/serviceconnector.cpp backend/serviceconnector.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/dsconnector/tcpdatatypes.h
 )
 
 target_link_libraries(qtuiviewerlib PRIVATE
@@ -86,6 +88,6 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_EXTRA_LIBS
 )
 
 # this needs to be increased with every new release
-set(GOOGLE_PLAY_APP_VERSION 27)
+set(GOOGLE_PLAY_APP_VERSION 31)
 # CMAKE_VAR_GIT_VERSION (coming from the top-level CMakeLists.txt) and GOOGLE_PLAY_APP_VERSION replaced in the following file
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml.in  ${CMAKE_CURRENT_SOURCE_DIR}/android/AndroidManifest.xml)
diff --git a/src/android/AndroidManifest.xml.in b/src/android/AndroidManifest.xml.in
index eec6de3c45a664ddbdb5f10c8e5be853af9ffae2..1736aebbf2b33fefe8b9dd1d9a52140fba8ee5ef 100644
--- a/src/android/AndroidManifest.xml.in
+++ b/src/android/AndroidManifest.xml.in
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtuiviewer"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer"
     android:installLocation="auto" android:versionCode="@GOOGLE_PLAY_APP_VERSION@"
     android:versionName="@CMAKE_VAR_GIT_VERSION@">
     <!-- %%INSERT_PERMISSIONS -->
diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp
index e7bf5bf89f9de9f382d2789b24d3bb173eaa815e..7d0c60267de5594c7f3071a5f9c4e3cbe163ef5d 100644
--- a/src/backend/backend.cpp
+++ b/src/backend/backend.cpp
@@ -24,7 +24,6 @@
 ****************************************************************************/
 
 #include "backend.h"
-#include "backend/constants.h"
 
 #include <QDesktopServices>
 #include <QEventLoop>
@@ -37,9 +36,11 @@
 #include <QSysInfo>
 #include <QTimer>
 
-// 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");
+#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");
 
 Backend::Backend(QObject *parent)
     : QObject(parent)
@@ -70,15 +71,19 @@ Backend::Backend(QObject *parent)
                 }
             });
 
-    const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
+    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) {
+            QMetaObject::invokeMethod(m_designStudioManager.get(),
+                                      "sendProjectLogs",
+                                      Qt::QueuedConnection,
+                                      Q_ARG(QString, m_lastProjectSenderId),
+                                      Q_ARG(QString, msg));
+        }
+    });
 
-    // Get the unique device ID
-    auto context = QNativeInterface::QAndroidApplication::context();
-    // auto contentResolver = context.callMethod<QtJniTypes::ContentResolver>("getContentResolver");
-    // auto androidId = QtJniTypes::Secure::callStaticMethod<jstring>("getString",
-    //                                                                contentResolver,
-    //                                                                QStringLiteral("android_id"));
-    // const QByteArray serial = androidId.toString().toLatin1();
+    const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
 
     qDebug() << "Qt Design Viewer";
     qDebug() << "System information:";
@@ -97,11 +102,34 @@ Backend::Backend(QObject *parent)
     qDebug() << "-- Product version: " << QSysInfo::productVersion();
     qDebug() << "-- Build ABI: " << QSysInfo::buildAbi();
     qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture();
-    // qDebug() << "-- Unique device ID: " << serial;
+    qDebug() << "-- Device serial: " << getDeviceSerial();
+    qDebug() << "-- Device unique ID: " << getDeviceUuid();
 
     qDebug() << "Thread id backend:" << QThread::currentThreadId();
 }
 
+// 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();
+}
+
 QString Backend::buildInfo() const
 {
 #ifdef QT_DEBUG
@@ -116,32 +144,6 @@ QString Backend::buildInfo() const
             + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()};
 }
 
-void Backend::setAutoScaleProject(const bool &enabled)
-{
-    QSettings().setValue(Constants::Settings::ProjectManager::AutoScale, enabled);
-    if (enabled) {
-        qDebug() << "Auto scale project is enabled";
-    } else {
-        qDebug() << "Auto scale project is disabled";
-    }
-}
-
-void Backend::setUserHash(const QString &userHash)
-{
-    QSettings().setValue(Constants::Settings::Backend::UserHash, userHash);
-    emit userHashChanged();
-}
-
-bool Backend::autoScaleProject()
-{
-    return QSettings().value(Constants::Settings::ProjectManager::AutoScale, true).toBool();
-}
-
-QString Backend::userHash()
-{
-    return QSettings().value(Constants::Settings::Backend::UserHash).toString();
-}
-
 void Backend::updatePopup(const QString &text, bool indeterminate)
 {
     emit popupTextChanged(text);
@@ -151,22 +153,31 @@ void Backend::updatePopup(const QString &text, bool indeterminate)
 
 void Backend::initializeProjectManager()
 {
-    m_projectManager.reset(new ProjectManager);
-    connect(m_projectManager.get(), &ProjectManager::closingProject, this, [&] {
-        emit popupClose();
-        m_projectManager.reset();
-    });
+    m_projectManager.reset(new ProjectManager(this, m_settings.autoScaleProject()));
+    connect(
+        m_projectManager.get(),
+        &ProjectManager::closingProject,
+        this,
+        [&] {
+            emit popupClose();
+            m_projectManager.reset();
+            QMetaObject::invokeMethod(m_designStudioManager.get(),
+                                      "sendProjectStopped",
+                                      Qt::QueuedConnection,
+                                      Q_ARG(QString, m_lastProjectSenderId));
+        },
+        Qt::QueuedConnection);
 }
 
 void Backend::initDesignStudioManager()
 {
     connect(&m_dsConnectorThread, &QThread::started, [this] {
         qDebug() << "Design Studio Manager thread started";
-        m_designStudioManager.reset(new DesignStudioManager);
+        m_designStudioManager.reset(new DesignStudioManager(nullptr, false, getDeviceUuid()));
 
         // signals goes from DS Manager to UI
         connect(m_designStudioManager.get(),
-                &DesignStudioManager::registrationPinRequested,
+                &DesignStudioManager::pairingPinRequested,
                 this,
                 &Backend::pinRequested);
 
@@ -175,6 +186,31 @@ void Backend::initDesignStudioManager()
                 this,
                 &Backend::runDsProject);
 
+        connect(m_designStudioManager.get(),
+                &DesignStudioManager::designStudioConnected,
+                this,
+                [this](const QString &id, const QString &ipAddr) {
+                    emit connectedChanged(true, ipAddr);
+                });
+        connect(m_designStudioManager.get(),
+                &DesignStudioManager::designStudioDisconnected,
+                this,
+                [this](const QString &id, const QString &ipAddr) {
+                    if (id == m_lastProjectSenderId && m_projectManager) {
+                        m_projectManager->stopProject();
+                    }
+                    emit connectedChanged(false, ipAddr);
+                });
+
+        connect(m_designStudioManager.get(),
+                &DesignStudioManager::projectStopRequested,
+                this,
+                [this](const QString &id) {
+                    if (m_projectManager) {
+                        m_projectManager->stopProject();
+                    }
+                });
+
         // signals goes from UI to DS Manager
         connect(this,
                 &Backend::enterPin,
@@ -193,17 +229,39 @@ void Backend::initDesignStudioManager()
             &Backend::downloadProgress);
 }
 
-void Backend::runDsProject(const QByteArray &projectData)
+void Backend::connectDesignStudio(const QString &ipAddr)
+{
+    QMetaObject::invokeMethod(m_designStudioManager.get(),
+                              "designStudioFound",
+                              Qt::QueuedConnection,
+                              Q_ARG(QString, ipAddr),
+                              Q_ARG(QString, ""),
+                              Q_ARG(bool, true));
+    emit popupOpen("Connecting in the background...", 1500);
+}
+
+void Backend::runDsProject(const QString &id, const QByteArray &projectData)
 {
+    // we'll use this to notify the correct DS when the project started/stopped
+    m_lastProjectSenderId = id;
+    QMetaObject::invokeMethod(m_designStudioManager.get(),
+                              "sendProjectRunning",
+                              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))
+    if (!m_projectManager->runProject(projectPath)) {
         qCritical() << "Could not run project. Please check the logs for more information.";
-    else {
+        QMetaObject::invokeMethod(m_designStudioManager.get(),
+                                  "sendProjectStopped",
+                                  Qt::QueuedConnection,
+                                  Q_ARG(QString, id));
+    } else {
         m_projectManager->showAppWindow();
     }
 
@@ -290,7 +348,7 @@ void Backend::runUserProject(const QString &projectName, const QString &password
 
     // fetch the project list to check if the project is cached
     const std::optional<QJsonArray> projectList = m_serviceConnector.fetchUserProjectList(
-        userHash());
+        m_settings.userHash());
 
     if (projectList == std::nullopt) {
         qCritical()
@@ -314,7 +372,7 @@ void Backend::runUserProject(const QString &projectName, const QString &password
         qDebug("Project is not cached. Downloading...");
         updatePopup("Project is not cached. Downloading...", false);
         const std::optional<QByteArray> projectData
-            = m_serviceConnector.fetchUserProject(userHash(), projectName, password);
+            = m_serviceConnector.fetchUserProject(m_settings.userHash(), projectName, password);
 
         if (projectData == std::nullopt) {
             qCritical()
@@ -371,7 +429,7 @@ void Backend::runOnlineProject(const QString &url)
 
 void Backend::updateUserProjectList()
 {
-    const QString userHash = Backend::userHash();
+    const QString userHash = m_settings.userHash();
 
     if (userHash.isEmpty()) {
         return;
@@ -432,7 +490,7 @@ void Backend::parseDesignViewerUrl(const QUrl &url)
         emit urlUpdated(url.toString());
     } else if (url.scheme() == "qtdesignviewer") {
         qDebug() << "Registering user from QR code";
-        setUserHash(url.host());
+        m_settings.setUserHash(url.host());
         updateUserProjectList();
     } else if (url.scheme() == "qtdesignstudio") {
         qDebug() << "Connecting to Design Studio from QR code";
diff --git a/src/backend/backend.h b/src/backend/backend.h
index 38cc89c56b0be17d88d896964f5113b2086e8ea2..1351df8f5dbf22987259fef7797957275c9f7d41 100644
--- a/src/backend/backend.h
+++ b/src/backend/backend.h
@@ -29,11 +29,13 @@
 #include <QThread>
 #include <QTimer>
 
+#include <memory>
+
 #include "dsconnector/dsmanager.h"
 #include "projectmanager.h"
 #include "qrscanner.h"
 #include "serviceconnector.h"
-#include <memory>
+#include "settings.h"
 
 class Backend : public QObject
 {
@@ -43,31 +45,30 @@ class Backend : public QObject
 public:
     explicit Backend(QObject *parent = nullptr);
 
-    void setLogs(const QString &logs)
-    {
-        m_logs = logs;
-        emit logsChanged(m_logs);
-    }
-
     QJsonArray projectList() const { return m_projectList; }
 
 private:
     // UI data
-    QString m_logs;
     QJsonArray m_projectList;
 
     // Other members
     ServiceConnector m_serviceConnector;
-    QThread m_dsConnectorThread;
     std::unique_ptr<ProjectManager> m_projectManager;
-    std::unique_ptr<DesignStudioManager> m_designStudioManager;
     std::unique_ptr<QrScanner> m_qrScanner;
 
+    // DS Connector
+    QThread m_dsConnectorThread;
+    std::unique_ptr<DesignStudioManager> m_designStudioManager;
+    QString m_lastProjectSenderId;
+
     // Settings
     QTimer m_projectListUpdateTimer;
+    Settings m_settings;
 
     // member functions
     void updatePopup(const QString &text, bool indeterminate = true);
+    QByteArray getDeviceSerial();
+    QString getDeviceUuid();
 
 signals:
     // UI signals - Home page
@@ -75,19 +76,17 @@ signals:
     void urlUpdated(QString);
     void userHashChanged();
 
-    // UI signals - Logs page
-    void logsChanged(QString);
-
     // UI signals - Popup
     void downloadProgress(float);
     void popupProgressIndeterminateChanged(bool indeterminate);
     void popupTextChanged(QString text);
-    void popupOpen();
+    void popupOpen(const QString &text = {}, int timeout = 0);
     void popupClose();
 
     // UI signals - from DS Manager page
     void pinRequested(const QString &id);
     void pinPopupOpen();
+    void connectedChanged(bool connected, const QString &ipAddr);
 
     // UI signals - from UI
     void enterPin(const QString &deviceId, const QString &pin);
@@ -100,21 +99,14 @@ public slots:
     void runOnlineProject(const QString &url);
     void runUserProject(const QString &projectName, const QString &password);
     void runDemoProject(const QString &projectName);
-    void runDsProject(const QByteArray &projectData);
+    void runDsProject(const QString &id, const QByteArray &projectData);
     void clearDemoCaches();
 
     void initDesignStudioManager();
+    void connectDesignStudio(const QString &ipAddr);
 
     void parseDesignViewerUrl(const QUrl &url);
 
-    // settings - setters
-    void setAutoScaleProject(const bool &enabled);
-    void setUserHash(const QString &userHash);
-
-    // settings - getters
-    bool autoScaleProject();
-    QString userHash();
-
     void popupInterrupted();
 
 private slots:
diff --git a/src/backend/constants.h b/src/backend/constants.h
index 540341e6de378d4917a39edf4a11913861340e48..dfbf2615b24afc89ebe01404612c4b8eb3b03985 100644
--- a/src/backend/constants.h
+++ b/src/backend/constants.h
@@ -30,17 +30,6 @@
 
 namespace Constants {
 
-// Settings keys
-namespace Settings {
-namespace ProjectManager {
-const static QString AutoScale = "project/autoScale";
-} // namespace ProjectManager
-
-namespace Backend {
-const static QString UserHash = "user/hash";
-} // namespace Backend
-} // namespace Settings
-
 // Path Constants
 namespace Paths {
 const static QString WritableLocation = QStandardPaths::writableLocation(
@@ -49,6 +38,7 @@ const static QString ProjectCachePath = WritableLocation + "/projectCache";
 const static QString DemoProjectsPath = WritableLocation + "/demoProjects";
 const static QString ConfigPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
 const static QString ConfigPathDsManager = ConfigPath + "/dsmanager.json";
+const static QString ConfigPathSettings = ConfigPath + "/settings.json";
 } // namespace Paths
 
 } // namespace Constants
diff --git a/src/backend/dsconnector/ds.cpp b/src/backend/dsconnector/ds.cpp
index a3ca3e950cdf39cb01e0e777faab308edb4389eb..5379af82881f6be0f603f5c7cda6552d916790f0 100644
--- a/src/backend/dsconnector/ds.cpp
+++ b/src/backend/dsconnector/ds.cpp
@@ -34,39 +34,39 @@
 #include <QSysInfo>
 #include <QTimer>
 
-#include "tcpdatatypes.h"
-
 DesignStudio::DesignStudio(const QString &ipv4Addr,
                            const quint16 port,
                            const QString &designStudioId,
                            const QString &deviceId,
+                           const bool skipPairing,
                            QObject *parent)
     : QObject(parent)
     , m_ipv4Addr(ipv4Addr)
     , m_port(port)
     , m_url("ws://" + m_ipv4Addr + ":" + QString::number(m_port))
-    , m_designStudioId(designStudioId)
-    , m_deviceId(deviceId)
+    , m_id(designStudioId)
+    , m_deviceUuid(deviceId)
+    , m_skipPairing(skipPairing)
 {
     initPingPong();
     initSocket();
+    qDebug() << "Design Studio" << m_id << "skip pairing" << m_skipPairing;
 }
 
 void DesignStudio::initPingPong()
 {
     connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
         m_socket.ping();
-        m_pongTimer.start(5000);
+        m_pongTimer.start(15000);
     });
 
     connect(&m_socket, &QWebSocket::pong, this, [this](quint64 elapsedTime, const QByteArray &) {
-        qDebug() << "Pong received from Design Studio" << m_designStudioId << "in" << elapsedTime
-                 << "ms";
+        qDebug() << "Pong received from Design Studio" << m_id << "in" << elapsedTime << "ms";
         m_pongTimer.stop();
     });
 
     connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
-        qDebug() << "Design Studio" << m_designStudioId << "is not responding. Reconnecting.";
+        qDebug() << "Design Studio" << m_id << "is not responding. Reconnecting.";
         m_socket.close();
     });
 }
@@ -84,15 +84,16 @@ void DesignStudio::initSocket()
 
     connect(&m_socket, &QWebSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) {
         if (state == QAbstractSocket::ConnectedState) {
-            qDebug() << "Connected to Design Studio" << m_designStudioId;
+            qDebug() << "Connected to Design Studio" << m_id;
             m_socketWasConnected = true;
-            m_pingTimer.start(10000);
+            m_pingTimer.start(15000);
             m_pongTimer.stop();
         } else if (state == QAbstractSocket::UnconnectedState && m_socketWasConnected) {
-            qDebug() << "Disconnected from Design Studio" << m_designStudioId;
+            qDebug() << "Disconnected from Design Studio" << m_id;
             m_socketWasConnected = false;
             m_pingTimer.stop();
             m_pongTimer.stop();
+            emit disconnected(m_id);
         }
     });
 
@@ -104,6 +105,11 @@ QString DesignStudio::ipv4Addr() const
     return m_ipv4Addr;
 }
 
+bool DesignStudio::skipPairing() const
+{
+    return m_skipPairing;
+}
+
 quint16 DesignStudio::port() const
 {
     return m_port;
@@ -114,19 +120,14 @@ QUrl DesignStudio::url() const
     return m_url;
 }
 
-QString DesignStudio::designStudioId() const
-{
-    return m_designStudioId;
-}
-
-QString DesignStudio::deviceId() const
+QString DesignStudio::id() const
 {
-    return m_deviceId;
+    return m_id;
 }
 
-bool DesignStudio::isRegistered() const
+bool DesignStudio::isPaired() const
 {
-    return !m_deviceId.isEmpty();
+    return m_skipPairing || m_paired;
 }
 
 bool DesignStudio::connected() const
@@ -142,6 +143,16 @@ void DesignStudio::setIpAddress(const QString &ipv4Addr)
     m_socket.open(m_url);
 }
 
+void DesignStudio::setSkipPairing(const bool skipPairing)
+{
+    m_skipPairing = skipPairing;
+}
+
+void DesignStudio::setDesignStudioId(const QString &designStudioId)
+{
+    m_id = designStudioId;
+}
+
 void DesignStudio::sendDeviceInfo()
 {
     const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
@@ -152,18 +163,19 @@ void DesignStudio::sendDeviceInfo()
     deviceInfo["os"] = QSysInfo::prettyProductName();
     deviceInfo["osVersion"] = QSysInfo::productVersion();
     deviceInfo["architecture"] = QSysInfo::currentCpuArchitecture();
-    deviceInfo["deviceId"] = m_deviceId;
+    deviceInfo["deviceId"] = m_deviceUuid;
     deviceInfo["appVersion"] = QString(CMAKE_VAR_GIT_VERSION);
+    deviceInfo["skipPairing"] = m_skipPairing;
 
     qDebug() << "Sending device info to Design Studio" << deviceInfo;
 
     sendData(PackageToDesignStudio::deviceInfo, deviceInfo);
 }
 
-void DesignStudio::sendRegistrationPin(const QString &pin)
+void DesignStudio::sendPairingPin(const QString &pin)
 {
-    qDebug() << "Sending registration PIN to Design Studio with pin" << pin;
-    sendData(PackageToDesignStudio::registrationPin, pin);
+    qDebug() << "Sending pairing PIN to Design Studio with pin" << pin;
+    sendData(PackageToDesignStudio::pairingPin, pin);
 }
 
 void DesignStudio::sendData(const QLatin1String &dataType, const QJsonValue &data)
@@ -194,35 +206,36 @@ void DesignStudio::processTextMessage(const QString &message)
     const QString dataType = jsonObj.value("dataType").toString();
 
     if (dataType == PackageFromDesignStudio::designStudioReady) {
-        m_designStudioId = jsonObj.value("data").toString();
-        qDebug() << "Design Studio" << m_designStudioId << "is waiting for the info.";
-        sendDeviceInfo();
-    } else if (dataType == PackageFromDesignStudio::pinVerificationRequested) {
+        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.";
+            emit idChanged(newDesignStudioId, m_ipv4Addr);
+        } else {
+            qDebug() << "Design Studio" << m_id << "is waiting for the info.";
+            sendDeviceInfo();
+        }
+    } else if (dataType == PackageFromDesignStudio::pinRequested) {
         qDebug() << "Registration PIN requested by Design Studio";
-        emit registrationPinRequested(m_designStudioId);
-    } else if (dataType == PackageFromDesignStudio::pinVerificationFailed) {
+        emit pinRequested(m_id);
+    } else if (dataType == PackageFromDesignStudio::pinFailed) {
         qDebug() << "Registration PIN verification failed";
-        emit pinVerificationFailed(m_designStudioId);
-    } else if (dataType == PackageFromDesignStudio::deviceIdReceived) {
-        if (!m_deviceId.isEmpty()) {
-            qDebug() << "Registration ID already set. Ignoring new registration ID";
-            return;
-        }
-
-        m_deviceId = jsonObj.value("data").toString();
-        qDebug() << "Registration successful. Received ID:" << m_deviceId;
-        emit registrationSucceeded(m_designStudioId);
-        emit dsOnline(m_designStudioId, m_ipv4Addr);
+        emit pinFailed(m_id);
     } else if (dataType == PackageFromDesignStudio::deviceIdDeclined) {
-        qDebug() << "Design Studio unregistered";
-        m_deviceId.clear();
-        emit unregistered(m_designStudioId);
+        qDebug() << "Design Studio unpaired the device";
+        m_paired = false;
+        emit unpaired(m_id);
     } else if (dataType == PackageFromDesignStudio::deviceIdAccepted) {
         qDebug() << "Design Studio accepted the device ID. Connection alive.";
-        emit dsOnline(m_designStudioId, m_ipv4Addr);
+        m_paired = true;
+        m_skipPairing = false;
+        emit paired(m_id);
     } else if (dataType == PackageFromDesignStudio::projectData) {
         qDebug() << "Project is expected";
         emit projectIncoming();
+    } else if (dataType == PackageFromDesignStudio::stopRunningProject) {
+        qDebug() << "Stop running project requested";
+        emit projectStopRequested(m_id);
     } else {
         qDebug() << "Unkown JSON message";
     }
@@ -232,5 +245,20 @@ 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(data);
+    emit projectReceived(m_id, data);
+}
+
+void DesignStudio::sendProjectRunning()
+{
+    sendData(PackageToDesignStudio::projectRunning);
+}
+
+void DesignStudio::sendProjectStopped()
+{
+    sendData(PackageToDesignStudio::projectStopped);
+}
+
+void DesignStudio::sendProjectLogs(const QString &logs)
+{
+    sendData(PackageToDesignStudio::projectLogs, logs);
 }
diff --git a/src/backend/dsconnector/ds.h b/src/backend/dsconnector/ds.h
index 6c39361e5b41b2f6b2921df38f4b6777d68b883b..bb461375465952fee1e10926be92c746aba2795f 100644
--- a/src/backend/dsconnector/ds.h
+++ b/src/backend/dsconnector/ds.h
@@ -31,30 +31,59 @@
 #include <qjsonvalue.h>
 #include <qlatin1stringview.h>
 
+#include <QLatin1String>
+
+namespace PackageFromDesignStudio {
+using namespace Qt::Literals;
+constexpr auto designStudioReady = "designStudioReady"_L1;
+constexpr auto pinRequested = "pinRequested"_L1;
+constexpr auto pinFailed = "pinVerificationFailed"_L1;
+constexpr auto deviceIdDeclined = "deviceIdDeclined"_L1;
+constexpr auto deviceIdAccepted = "deviceIdAccepted"_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 pairingPin = "registrationPinResponse"_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(const QString &ipv4Addr,
                  const quint16 port,
-                 const QString &designStudioId = QString(),
-                 const QString &deviceId = QString(),
+                 const QString &id = QString(),
+                 const QString &deviceUuid = QString(),
+                 const bool skipPairing = false,
                  QObject *parent = nullptr);
 
     // Getters
     QString ipv4Addr() const;
-    QString designStudioId() const;
     quint16 port() const;
+    QString id() const;
     QUrl url() const;
-    QString deviceId() const;
-    bool isRegistered() const;
+    bool skipPairing() const;
+    bool isPaired() const;
     bool connected() const;
 
     // Setters
     void setIpAddress(const QString &ipv4Addr);
+    void setDesignStudioId(const QString &designStudioId);
+    void setSkipPairing(const bool skipPairing);
 
     // Send data
-    void sendRegistrationPin(const QString &pin);
+    void sendPairingPin(const QString &pin);
+    void sendDeviceInfo();
+    void sendProjectRunning();
+    void sendProjectStopped();
+    void sendProjectLogs(const QString &logs);
 
 private:
     // Network
@@ -67,14 +96,17 @@ private:
     QTimer m_pongTimer;
 
     // DS data
-    QString m_designStudioId;
-    QString m_deviceId;
+    QString m_id;
+    QString m_deviceUuid;
+
+    // Pairing
+    bool m_paired = false;
+    bool m_skipPairing = false;
 
     // Settings
     constexpr static int m_reconnectTimeout = 5000;
 
     // DS comm
-    void sendDeviceInfo();
     void sendData(const QLatin1String &dataType, const QJsonValue &data = QJsonValue());
 
     // Internal
@@ -86,14 +118,17 @@ private slots:
     void processBinaryMessage(const QByteArray &data);
 
 signals:
-    // DS signals
-    void registrationPinRequested(const QString &id);
-    void pinVerificationFailed(const QString &id);
-    void registrationSucceeded(const QString &id);
-    void unregistered(const QString &id);
+    // socket signals
+    void disconnected(const QString &id);
 
-    void dsOnline(const QString &id, const QString &ipv4Addr);
+    // DS signals
+    void idChanged(const QString &id, const QString &ipv4Addr);
+    void pinRequested(const QString &id);
+    void pinFailed(const QString &id);
+    void paired(const QString &id);
+    void unpaired(const QString &id);
 
     void projectIncoming();
-    void projectReceived(const QByteArray &data);
+    void projectReceived(const QString &id, const QByteArray &data);
+    void projectStopRequested(const QString &id);
 };
diff --git a/src/backend/dsconnector/dsdiscovery.h b/src/backend/dsconnector/dsdiscovery.h
index 369e924ba4f437c4dec6a5cea5161fe1fcb26c87..9ddedaca3e4f35be65351acb8365147f987e1992 100644
--- a/src/backend/dsconnector/dsdiscovery.h
+++ b/src/backend/dsconnector/dsdiscovery.h
@@ -38,7 +38,9 @@ private slots:
     void onReadyRead();
 
 signals:
-    void designStudioFound(const QString &ipv4Addr, const QString &id);
+    void designStudioFound(const QString &ipv4Addr,
+                           const QString &id,
+                           const bool skipPairing = false);
 
 private:
     QUdpSocket m_udpSocket;
diff --git a/src/backend/dsconnector/dsmanager.cpp b/src/backend/dsconnector/dsmanager.cpp
index ebbfe214059dc2c14e72c07678fb971a51698494..9f753b230df7acdb3dd7acf35002c427bc796b9b 100644
--- a/src/backend/dsconnector/dsmanager.cpp
+++ b/src/backend/dsconnector/dsmanager.cpp
@@ -38,22 +38,29 @@
 #include <qjsonobject.h>
 #include <qobject.h>
 
-DesignStudioManager::DesignStudioManager(QObject *parent)
+DesignStudioManager::DesignStudioManager(QObject *parent,
+                                         const bool enableDiscovery,
+                                         const QString &deviceUuid)
     : QObject(parent)
+    , m_deviceUuid(deviceUuid)
 {
-    initializeRegisteredDesignStudios();
+    qDebug() << ">>> THREAD ID _ dsmanger:" << QThread::currentThreadId();
+    initializePairedDesignStudios();
 
-    m_discovery = std::make_unique<DesignStudioDiscovery>();
-    connect(m_discovery.get(),
-            &DesignStudioDiscovery::designStudioFound,
-            this,
-            &DesignStudioManager::designStudioFound);
+    if (enableDiscovery) {
+        qDebug() << "Starting Design Studio discovery";
+        m_discovery = std::make_unique<DesignStudioDiscovery>();
+        connect(m_discovery.get(),
+                &DesignStudioDiscovery::designStudioFound,
+                this,
+                &DesignStudioManager::designStudioFound);
+    }
 
     qDebug() << "Design Studio Manager initialized";
     qDebug() << "Thread id dsmanger:" << QThread::currentThreadId();
 }
 
-void DesignStudioManager::initializeRegisteredDesignStudios()
+void DesignStudioManager::initializePairedDesignStudios()
 {
     QFile file(Constants::Paths::ConfigPathDsManager);
 
@@ -77,56 +84,71 @@ void DesignStudioManager::initializeRegisteredDesignStudios()
         QJsonObject dsObj = ds.toObject();
         initDesignStudio(dsObj["ipv4Addr"].toString(),
                          dsObj["designStudioId"].toString(),
-                         dsObj["deviceId"].toString());
+                         dsObj["skipPairing"].toBool());
     }
 }
 
 void DesignStudioManager::initDesignStudio(const QString &ipv4Addr,
                                            const QString &designStudioId,
-                                           const QString &deviceId)
+                                           const bool skipPairing)
 {
     qDebug() << "Initializing Design Studio" << designStudioId << "with IPv4 address" << ipv4Addr;
-    auto ds = std::make_unique<DesignStudio>(ipv4Addr, 40000, designStudioId, deviceId);
+    auto ds
+        = std::make_unique<DesignStudio>(ipv4Addr, 40000, designStudioId, m_deviceUuid, skipPairing);
 
-    connect(ds.get(),
-            &DesignStudio::registrationSucceeded,
-            this,
-            &DesignStudioManager::designStudioRegistered);
+    connect(ds.get(), &DesignStudio::paired, this, &DesignStudioManager::designStudioPaired);
 
-    connect(ds.get(),
-            &DesignStudio::unregistered,
-            this,
-            &DesignStudioManager::designStudioUnregistered);
+    connect(ds.get(), &DesignStudio::unpaired, this, &DesignStudioManager::designStudioUnpaired);
 
-    connect(ds.get(),
-            &DesignStudio::registrationPinRequested,
-            this,
-            &DesignStudioManager::registrationPinRequested);
+    connect(ds.get(), &DesignStudio::pinRequested, this, &DesignStudioManager::pairingPinRequested);
+
+    connect(ds.get(), &DesignStudio::idChanged, this, &DesignStudioManager::dsIdChanged);
 
     connect(ds.get(), &DesignStudio::projectReceived, this, &DesignStudioManager::projectReceived);
 
+    connect(ds.get(), &DesignStudio::disconnected, this, &DesignStudioManager::dsDisconnected);
+
+    connect(ds.get(),
+            &DesignStudio::projectStopRequested,
+            this,
+            &DesignStudioManager::projectStopRequested);
+
     m_designStudios.push_back(std::move(ds));
 }
 
-void DesignStudioManager::designStudioFound(const QString &ipv4Addr, const QString &id)
+void DesignStudioManager::designStudioFound(const QString &ipv4Addr,
+                                            const QString &id,
+                                            const bool skipPairing)
 {
     qDebug() << "Design Studio found with IPv4 address" << ipv4Addr << "and ID" << id;
     // check if the Design Studio is already in the list
 
     for (const auto &ds : m_designStudios) {
-        if (ds->designStudioId() == id) {
+        if (ds->id() == id) {
             if (ds->ipv4Addr() != ipv4Addr) {
                 qDebug() << "Design Studio" << id << "changed IP address from" << ds->ipv4Addr()
                          << "to" << ipv4Addr;
                 ds->setIpAddress(ipv4Addr);
+                updateConfigFile();
             } else {
                 qDebug() << "Design Studio" << id << "already in the list";
             }
             return;
         }
+
+        if (ds->ipv4Addr() == ipv4Addr && ds->connected()) {
+            qDebug() << "Design Studio with IP address" << ipv4Addr << "already in the list with ID"
+                     << ds->id() << "and connected";
+            if (skipPairing) {
+                qDebug() << "Forcing pairing with Design Studio" << id;
+                ds->setSkipPairing(skipPairing);
+                ds->sendDeviceInfo();
+            }
+            return;
+        }
     }
 
-    initDesignStudio(ipv4Addr, id);
+    initDesignStudio(ipv4Addr, id, skipPairing);
 }
 
 void DesignStudioManager::updateConfigFile()
@@ -141,14 +163,14 @@ void DesignStudioManager::updateConfigFile()
     QJsonArray dsArray;
 
     for (const auto &d : m_designStudios) {
-        if (!d->isRegistered()) {
+        if (!d->isPaired()) {
             continue;
         }
 
         QJsonObject dsObj;
         dsObj["ipv4Addr"] = d->ipv4Addr();
-        dsObj["designStudioId"] = d->designStudioId();
-        dsObj["deviceId"] = d->deviceId();
+        dsObj["id"] = d->id();
+        dsObj["skipPairing"] = d->skipPairing();
         dsArray.append(dsObj);
     }
 
@@ -156,16 +178,79 @@ void DesignStudioManager::updateConfigFile()
     file.write(QJsonDocument(rootObj).toJson());
 }
 
-void DesignStudioManager::designStudioRegistered(const QString &designStudioId)
+void DesignStudioManager::dsIdChanged(const QString &id, const QString &ipv4Addr)
 {
-    qDebug() << "Design Studio" << designStudioId << "registered. Updating settings.";
+    /* that can happen because of the following reasons;
+        1. The IP address is pointing a different Design Studio (can happen because of DHCP or manual change)
+        2. The Design Studio changed its ID (can happen because of a reinstall)
+        3. Design Studio is initialized only with IP address and ID is received later
+    */
+
+    for (const auto &ds : m_designStudios) {
+        // case 1 and 3
+        if (ds->ipv4Addr() == ipv4Addr && ds->id() != id) {
+            qDebug() << "Design Studio" << id << "changed ID to" << id << "with IP address"
+                     << ipv4Addr;
+            ds->setDesignStudioId(id);
+            ds->sendDeviceInfo();
+            break;
+        }
+
+        // case 2
+        if (ds->id() == id) {
+            qDebug() << "Design Studio" << id << "changed IP address from" << ds->ipv4Addr() << "to"
+                     << ipv4Addr;
+
+            // if there's another DS with the same IP address, remove it
+            m_designStudios.erase(std::remove_if(m_designStudios.begin(),
+                                                 m_designStudios.end(),
+                                                 [&](const auto &d) {
+                                                     return d->ipv4Addr() == ipv4Addr;
+                                                 }),
+                                  m_designStudios.end());
+            ds->setIpAddress(ipv4Addr);
+            ds->sendDeviceInfo();
+            break;
+        }
+    }
+
     updateConfigFile();
 }
 
-void DesignStudioManager::designStudioUnregistered(const QString &designStudioId)
+void DesignStudioManager::designStudioPaired(const QString &id)
 {
-    qDebug() << "Design Studio" << designStudioId << "unregistered. Removing from settings.";
+    qDebug() << "Design Studio" << id << "paired. Updating settings.";
     updateConfigFile();
+    auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) {
+        return d->id() == id;
+    });
+    if (ds == m_designStudios.end()) {
+        qCritical() << "Design Studio" << id << "not found in the list. Something went wrong.";
+        return;
+    }
+
+    if (!(*ds)->connected()) {
+        qWarning() << "Design Studio" << id
+                   << "is not connected. Not notifying UI about the connection update.";
+        return;
+    }
+
+    emit designStudioConnected(id, (*ds)->ipv4Addr());
+}
+
+void DesignStudioManager::designStudioUnpaired(const QString &id)
+{
+    qDebug() << "Design Studio" << id << "unpaired. Removing from settings.";
+    updateConfigFile();
+    auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) {
+        return d->id() == id;
+    });
+    if (ds == m_designStudios.end()) {
+        qCritical() << "Design Studio" << id << "not found in the list. Something went wrong.";
+        return;
+    }
+
+    emit designStudioDisconnected(id, (*ds)->ipv4Addr());
 }
 
 void DesignStudioManager::enterPin(const QString &id, const QString &pin)
@@ -173,7 +258,7 @@ void DesignStudioManager::enterPin(const QString &id, const QString &pin)
     DesignStudio *ds = nullptr;
 
     for (const auto &d : m_designStudios) {
-        if (d->designStudioId() == id) {
+        if (d->id() == id) {
             ds = d.get();
             break;
         }
@@ -183,5 +268,48 @@ void DesignStudioManager::enterPin(const QString &id, const QString &pin)
         return;
     }
 
-    ds->sendRegistrationPin(pin);
+    ds->sendPairingPin(pin);
+}
+
+void DesignStudioManager::dsDisconnected(const QString &id)
+{
+    qDebug() << "Design Studio" << id << "disconnected";
+    auto ds = std::find_if(m_designStudios.begin(), m_designStudios.end(), [&](const auto &d) {
+        return d->id() == id;
+    });
+
+    if (!(*ds)->isPaired()) {
+        qDebug() << "Design Studio" << id
+                 << "is not paired. Not notifying UI about the disconnection.";
+        return;
+    }
+
+    emit designStudioDisconnected(id, (*ds)->ipv4Addr());
+}
+
+void DesignStudioManager::sendProjectRunning(const QString &id)
+{
+    for (const auto &ds : m_designStudios) {
+        if (ds->id() == id) {
+            ds->sendProjectRunning();
+        }
+    }
+}
+
+void DesignStudioManager::sendProjectStopped(const QString &id)
+{
+    for (const auto &ds : m_designStudios) {
+        if (ds->id() == id) {
+            ds->sendProjectStopped();
+        }
+    }
+}
+
+void DesignStudioManager::sendProjectLogs(const QString &id, const QString &logs)
+{
+    for (const auto &ds : m_designStudios) {
+        if (ds->id() == id) {
+            ds->sendProjectLogs(logs);
+        }
+    }
 }
diff --git a/src/backend/dsconnector/dsmanager.h b/src/backend/dsconnector/dsmanager.h
index 824996d3b29d965e1428ceed7a0cb153e3729a0d..48bc17ca1a91a5c4c64337e9036777bf870842b4 100644
--- a/src/backend/dsconnector/dsmanager.h
+++ b/src/backend/dsconnector/dsmanager.h
@@ -37,15 +37,23 @@ class DesignStudioManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit DesignStudioManager(QObject *parent = nullptr);
+    explicit DesignStudioManager(QObject *parent = nullptr,
+                                 const bool enableDiscovery = false,
+                                 const QString &deviceUuid = {});
 
 public slots:
+    void sendProjectRunning(const QString &id);
+    void sendProjectStopped(const QString &id);
+    void sendProjectLogs(const QString &id, const QString &logs);
+
     void enterPin(const QString &id, const QString &pin);
-    void designStudioFound(const QString &ipv4Addr, const QString &id);
+    void designStudioFound(const QString &ipv4Addr,
+                           const QString &id = {},
+                           const bool skipPairing = false);
 
 private slots:
-    void designStudioRegistered(const QString &id);
-    void designStudioUnregistered(const QString &id);
+    void designStudioPaired(const QString &id);
+    void designStudioUnpaired(const QString &id);
 
 private:
     // Discovery object
@@ -54,16 +62,24 @@ private:
     // Discovered Design Studio instances
     std::vector<std::unique_ptr<DesignStudio>> m_designStudios;
 
-    void initializeRegisteredDesignStudios();
+    // other members
+    const QString m_deviceUuid;
+
+    void dsIdChanged(const QString &id, const QString &ipv4Addr);
+    void initializePairedDesignStudios();
     void initDesignStudio(const QString &ipv4Addr,
-                          const QString &designStudioId,
-                          const QString &deviceId = QString());
+                          const QString &designStudioId = {},
+                          const bool skipPairing = false);
+    void dsDisconnected(const QString &id);
 
     void updateConfigFile();
 
 signals:
-    void registrationPinRequested(const QString &id);
-    void projectReceived(const QByteArray &project);
+    void pairingPinRequested(const QString &id);
+    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 projectStopRequested(const QString &id);
 };
 
 #endif // DSCONNECTOR_H
diff --git a/src/backend/logger.h b/src/backend/logger.h
new file mode 100644
index 0000000000000000000000000000000000000000..23efa645ee64e7a3dc730478e450d59ab9f6af3f
--- /dev/null
+++ b/src/backend/logger.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** 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 <QMessageBox>
+#include <QObject>
+
+#include <android/log.h>
+
+class Logger : public QObject
+{
+    Q_OBJECT
+public:
+    Logger(const Logger &) = delete;
+    Logger &operator=(const Logger &) = delete;
+
+    static Logger &instance()
+    {
+        static Logger m_instance;
+        return m_instance;
+    }
+
+    void log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+    {
+        QString logPrefix, logSuffix, newLog;
+
+        QByteArray localMsg = msg.toLocal8Bit();
+        const char *file = context.file ? context.file : "";
+        const char *function = context.function ? context.function : "";
+
+        switch (type) {
+        case QtDebugMsg:
+            logPrefix = QStringLiteral("Debug: ");
+            break;
+        case QtInfoMsg:
+            logPrefix = QStringLiteral("Info: ");
+            break;
+        case QtWarningMsg:
+            logPrefix = QStringLiteral("Warning: ");
+            break;
+        case QtCriticalMsg:
+            logPrefix = QStringLiteral("Critical: ");
+            break;
+        case QtFatalMsg:
+            logPrefix = QStringLiteral("Fatal: ");
+            logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function);
+            break;
+        }
+
+        newLog += logPrefix + localMsg + logSuffix + "\n";
+        __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog));
+
+        if (type == QtCriticalMsg || type == QtFatalMsg) {
+            QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
+            msgBox.exec();
+        }
+
+        emit logMessage(type, newLog);
+    }
+
+private:
+    Logger()
+    {
+        qInstallMessageHandler(
+            [](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
+                Logger::instance().log(type, context, msg);
+            });
+    }
+
+signals:
+    void logMessage(QtMsgType type, QString &msg);
+};
diff --git a/src/backend/main.cpp b/src/backend/main.cpp
index 7619e75137a8765252e4e947d36bee7afd6ddb02..1ea995334438c772c0a3d0a1c4bfbf3cfe0163d1 100644
--- a/src/backend/main.cpp
+++ b/src/backend/main.cpp
@@ -26,58 +26,15 @@
 #include <android/log.h>
 
 #include <QApplication>
-#include <QMessageBox>
 #include <QQmlContext>
 
 #include "backend.h"
-
-static Backend *backend;
-static QString appLogs;
-
-void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
-{
-    QString logPrefix, logSuffix, newLog;
-
-    QByteArray localMsg = msg.toLocal8Bit();
-    const char *file = context.file ? context.file : "";
-    const char *function = context.function ? context.function : "";
-
-    switch (type) {
-    case QtDebugMsg:
-        logPrefix = QStringLiteral("Debug: ");
-        break;
-    case QtInfoMsg:
-        logPrefix = QStringLiteral("Info: ");
-        break;
-    case QtWarningMsg:
-        logPrefix = QStringLiteral("Warning: ");
-        break;
-    case QtCriticalMsg:
-        logPrefix = QStringLiteral("Critical: ");
-        break;
-    case QtFatalMsg:
-        logPrefix = QStringLiteral("Fatal: ");
-        logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function);
-        break;
-    }
-
-    newLog += logPrefix + localMsg + logSuffix + "\n";
-    __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog));
-
-    if (type != QtDebugMsg) {
-        if (backend)
-            backend->setLogs(appLogs += newLog);
-
-        if (type == QtCriticalMsg || type == QtFatalMsg) {
-            QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
-            msgBox.exec();
-        }
-    }
-}
+#include "logger.h"
 
 int main(int argc, char *argv[])
 {
-    qInstallMessageHandler(messageHandler);
+    // qInstallMessageHandler(messageHandler);
+    Logger::instance();
 
     qDebug() << "Starting Qt Design Viewer";
 
@@ -86,10 +43,10 @@ int main(int argc, char *argv[])
     QApplication::setApplicationName(QStringLiteral("Qt Design Viewer"));
     QApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__));
 
-    backend = new Backend();
+    Backend backend;
 
     QQuickView view;
-    view.engine()->rootContext()->setContextProperty("backend", backend);
+    view.engine()->rootContext()->setContextProperty("backend", &backend);
     view.setSource(QUrl(QStringLiteral("qrc:/ui/main.qml")));
     view.setResizeMode(QQuickView::SizeRootObjectToView);
     view.showMaximized();
diff --git a/src/backend/projectmanager.cpp b/src/backend/projectmanager.cpp
index dbae903a6e3f54dea836e0526fe792119edef1d1..a70c9032223bf098207ef3317af91fd266e19334 100644
--- a/src/backend/projectmanager.cpp
+++ b/src/backend/projectmanager.cpp
@@ -49,10 +49,9 @@
 
 #include "constants.h"
 
-ProjectManager::ProjectManager(QObject *parent)
+ProjectManager::ProjectManager(QObject *parent, bool autoScaleProject)
     : QObject(parent)
-    , m_autoScaleProject(
-          QSettings().value(Constants::Settings::ProjectManager::AutoScale, true).toBool())
+    , m_autoScaleProject(autoScaleProject)
 {
     qDebug() << "ProjectManager created.";
     qDebug() << "Project cache path: " << Constants::Paths::ProjectCachePath;
@@ -592,3 +591,9 @@ void ProjectManager::hideAppWindow()
     qDebug("Hiding the QML app window");
     m_quickWindow->hide();
 }
+
+void ProjectManager::stopProject()
+{
+    qDebug("Stopping the QML app window");
+    m_quickWindow->close();
+}
diff --git a/src/backend/projectmanager.h b/src/backend/projectmanager.h
index 354ba59c2efedeb1fd53f8a99b68bf1d0f679d21..4767ce4a92d0208a51ecf1319496229f2a234892 100644
--- a/src/backend/projectmanager.h
+++ b/src/backend/projectmanager.h
@@ -37,11 +37,12 @@ class ProjectManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit ProjectManager(QObject *parent = nullptr);
+    explicit ProjectManager(QObject *parent = nullptr, bool autoScaleProject = false);
     ~ProjectManager();
 
     QString unpackProject(const QByteArray &project, bool extractZip = false);
     bool runProject(const QString &projectPath);
+    void stopProject();
 
     bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo);
     bool isProjectCached(const QJsonObject &projectInfo);
@@ -69,6 +70,7 @@ private:
     QScopedPointer<QQmlEngine> m_qmlEngine;
     QScopedPointer<QQmlComponent> m_qmlComponent;
     QScopedPointer<QQuickWindow> m_quickWindow;
+    QJSValue m_consoleObject;
 
     // Member functions
     QString findFile(const QString &dir, const QString &filter);
diff --git a/src/backend/settings.cpp b/src/backend/settings.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..73299cdd54d926e59df0be102f85cdc393293c59
--- /dev/null
+++ b/src/backend/settings.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** 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 "settings.h"
+
+#include <QFile>
+#include <QJsonDocument>
+#include <QStandardPaths>
+
+#include "constants.h"
+
+/*
+    Example of a settings.json file:
+    {
+        "autoScale": true
+        "userHash": "1234567890",
+        "deviceUuid": "1234567890"
+    }
+*/
+
+Settings::Settings()
+    : m_settingsPath(Constants::Paths::ConfigPathSettings)
+{
+    qDebug() << "Settings path:" << m_settingsPath;
+    loadSettings();
+}
+
+void Settings::loadSettings()
+{
+    QFile file(m_settingsPath);
+    if (file.open(QIODevice::ReadOnly)) {
+        QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
+        m_settings = doc.object();
+        return;
+    }
+
+    qWarning() << "Failed to load settings from" << m_settingsPath;
+    m_settings.insert("projectManager", QJsonObject{{"autoScale", true}});
+    m_settings.insert("backend", QJsonObject{{"userHash", ""}, {"deviceUuid", ""}});
+
+    if (!file.exists()) {
+        qDebug() << "Settings file not found. Creating a new one.";
+        saveSettings();
+    }
+}
+
+bool Settings::saveSettings()
+{
+    QFile file(m_settingsPath);
+    if (file.open(QIODevice::WriteOnly)) {
+        QJsonDocument doc(m_settings);
+        file.write(doc.toJson());
+        return true;
+    }
+
+    qWarning() << "Failed to save settings to" << m_settingsPath;
+    return false;
+}
+
+void Settings::setAutoScaleProject(const bool &enabled)
+{
+    m_settings["autoScale"] = enabled;
+    saveSettings();
+}
+
+void Settings::setUserHash(const QString &userHash)
+{
+    m_settings["userHash"] = userHash;
+    saveSettings();
+}
+
+void Settings::setDeviceUuid(const QString &deviceUuid)
+{
+    m_settings["deviceUuid"] = deviceUuid;
+    saveSettings();
+}
+
+QString Settings::userHash()
+{
+    return m_settings["userHash"].toString();
+}
+
+QString Settings::deviceUuid()
+{
+    return m_settings["deviceUuid"].toString();
+}
+
+bool Settings::autoScaleProject()
+{
+    return m_settings["autoScale"].toBool();
+}
diff --git a/src/backend/dsconnector/tcpdatatypes.h b/src/backend/settings.h
similarity index 59%
rename from src/backend/dsconnector/tcpdatatypes.h
rename to src/backend/settings.h
index 5001db5a7c651c54eaa6170eaf5e29d4776d5ae7..1a122b0a78d27e63a0ede9e6b25b8bf386ecd684 100644
--- a/src/backend/dsconnector/tcpdatatypes.h
+++ b/src/backend/settings.h
@@ -3,7 +3,7 @@
 ** Copyright (C) 2024 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
-** This file is part of the Qt Design Tooling
+** 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
@@ -22,24 +22,27 @@
 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
 **
 ****************************************************************************/
-
 #pragma once
 
-#include <QLatin1String>
+#include <QJsonObject>
+
+class Settings
+{
+public:
+    Settings();
+
+    void setAutoScaleProject(const bool &enabled);
+    void setUserHash(const QString &userHash);
+    void setDeviceUuid(const QString &deviceUuid);
+
+    bool autoScaleProject();
+    QString userHash();
+    QString deviceUuid();
 
-namespace PackageFromDesignStudio {
-using namespace Qt::Literals;
-constexpr auto designStudioReady = "designStudioReady"_L1;
-constexpr auto pinVerificationRequested = "pinRequested"_L1;
-constexpr auto pinVerificationFailed = "pinVerificationFailed"_L1;
-constexpr auto deviceIdReceived = "deviceIdReceived"_L1;
-constexpr auto deviceIdDeclined = "deviceIdDeclined"_L1;
-constexpr auto deviceIdAccepted = "deviceIdAccepted"_L1;
-constexpr auto projectData = "projectData"_L1;
-}; // namespace PackageFromDesignStudio
+private:
+    QJsonObject m_settings;
+    const QString m_settingsPath;
 
-namespace PackageToDesignStudio {
-using namespace Qt::Literals;
-constexpr auto deviceInfo = "deviceInfo"_L1;
-constexpr auto registrationPin = "registrationPinResponse"_L1;
-}; // namespace PackageToDesignStudio
+    void loadSettings();
+    bool saveSettings();
+};
diff --git a/src/ui/HomePage.qml b/src/ui/HomePage.qml
index 9e15965b92beb21522133d050ff71fe5b8dee276..c7c8e88f382c1a57ff381f6b0f5c3779c460e42a 100644
--- a/src/ui/HomePage.qml
+++ b/src/ui/HomePage.qml
@@ -9,207 +9,117 @@ Item {
     ColumnLayout {
         anchors.fill: parent
 
-        GridLayout{
-            id: gridLayout
+        Item {
+            id: item2
+            Layout.preferredWidth: 10
+            Layout.preferredHeight: 3
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+        }
 
-            Item {
-                id: item2
-                Layout.preferredWidth: 10
-                Layout.preferredHeight: 3
-                Layout.fillWidth: true
-                Layout.fillHeight: true
-            }
+        Text {
+            id: qrCodeStatus
+            text: qsTr("Scan the QR code to start pairing with Qt Design Studio")
+            font.pixelSize: 12
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            wrapMode: Text.WordWrap
+            Layout.fillWidth: true
+        }
 
-            Text {
-                id: qrCodeStatus
-                text: {
-                    if (backend.userHash() === '') {
-                        return qsTr("No user is registered. To access your shared projects:\n\n1. Open the project with Design Studio (4.4 or later).\n2. Select File -> Share Application Online.\n3. Scan the QR code.")
-                    }
+        Item {
+            Layout.preferredWidth: 10
+            Layout.preferredHeight: 10
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+        }
 
-                    return qsTr("User registration is completed.\nScan a new QR code to access the shared project from a different user.");
-                }
-                font.pixelSize: 12
-                horizontalAlignment: Text.AlignHCenter
-                verticalAlignment: Text.AlignVCenter
-                wrapMode: Text.WordWrap
-                Layout.fillWidth: true
-                Connections {
-                    target: backend
-                    function onUserHashChanged(){
-                        qrCodeStatus.text = qsTr("User registration is completed.\nScan a new QR code to access the shared project from a different user.");
-                    }
-                }
-            }
+        Rectangle {
+            id: sep1
+            height: 2
+            color: 'black'
+            Layout.fillWidth: true
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+        }
 
-            Button {
-                id: scanQrCode
-                text: qsTr("Scan QR code")
-                onClicked: backend.scanQrCode()
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-            }
 
-            Rectangle {
-                id: sep1
-                height: 2
-                color: 'black'
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-            }
+        Button {
+            id: scanQrCode
+            implicitHeight: 122
+            implicitWidth: 120
+            topInset: 0
+            bottomInset: 0
+            text: qsTr("Scan QR code")
+            leftPadding: 0
+            rightPadding: 0
+            icon.color: "#00a31d1d"
+            icon.width: scanQrCode.implicitHeight
+            icon.height: scanQrCode.implicitWidth
+            icon.cache: false
+            icon.source: "content/images/container.png"
+            display: AbstractButton.IconOnly
+            onClicked: backend.scanQrCode()
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+        }
 
-            Rectangle {
-                id: sep2
-                height: 2
-                color: 'black'
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-            }
 
-            Item {
-                id: item4
-                Layout.preferredWidth: 10
-                Layout.preferredHeight: 10
-                Layout.fillWidth: true
-                Layout.fillHeight: true
-            }
+        Rectangle {
+            id: sep2
+            height: 2
+            color: 'black'
+            Layout.fillWidth: true
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
         }
 
         Item {
-            id: item6
+            id: item4
             Layout.preferredWidth: 10
             Layout.preferredHeight: 10
             Layout.fillWidth: true
             Layout.fillHeight: true
         }
 
+
         ColumnLayout {
-            id: column2
             Layout.fillWidth: true
 
-            ComboBox {
-                id: projectList
+            Text {
+                text: qsTr("Or enter the Qt Design Studio IP address manually")
+                font.pixelSize: 12
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                wrapMode: Text.WordWrap
                 Layout.fillWidth: true
-                enabled: true
-                textRole: "appName"
-                model: backend.projectList
-                onModelChanged: {
-                    if (backend.projectList.length > 0) {
-                        projectList.displayText = backend.projectList[0].appName;
-                        projectList.enabled = true;
-                        downloadUserProject.enabled = true;
-                    } else {
-                        projectList.displayText = qsTr("No project is available");
-                        projectList.enabled = false;
-                        downloadUserProject.enabled = false;
-                    }
-                }
-                onCurrentIndexChanged: {
-                    displayText = model[currentIndex].appName;
-                }
-                displayText: qsTr("Scan QR code to access your projects");
-                currentIndex: -1
-                Layout.preferredHeight: 50
             }
 
             TextField {
-                id: password
-                property bool showText: false
+                id: ipAddress
                 Layout.fillWidth: true
                 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-                placeholderText: qsTr("Project password")
-                echoMode: showText ? TextInput.Normal : TextInput.Password
-
-
-                MouseArea {
-                    id: mouseArea
-                    anchors.right: parent.right
-                    height: parent.height
-                    width: height
-
-                    Image {
-                        id: eyeIcon
-                        anchors.fill: parent
-                        anchors.margins: 15
-                        anchors.leftMargin: 10
-                        anchors.rightMargin: 10
-                        source: "content/images/closed_eye.png"
-                    }
-
-                    onClicked: {
-                        if(password.showText) {
-                            eyeIcon.source = "content/images/closed_eye.png"
-                        } else {
-                            eyeIcon.source = "content/images/open_eye.png"
-                        }
-
-                        password.showText = !password.showText;
-                    }
-                }
+                placeholderText: qsTr("IP Address")
+                text: "10.0.2.2"
+                validator: RegularExpressionValidator { regularExpression: /^(\d{1,3}\.){3}\d{1,3}$/ }
             }
 
             Button {
                 id: downloadUserProject
-                text: qsTr("Run Project")
+                text: qsTr("Connect Design Studio")
                 onClicked: {
-                    backend.runUserProject(projectList.displayText, password.text);
+                    backend.connectDesignStudio(ipAddress.text)
                 }
-                enabled: false
+                enabled: true
                 Layout.fillWidth: true
                 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
             }
         }
 
         Item {
-            id: item3
             Layout.fillHeight: true
             Layout.fillWidth: true
             Layout.preferredHeight: 10
             Layout.preferredWidth: 10
         }
 
-        ColumnLayout {
-            id: column
-            Layout.fillWidth: true
-            visible: false
-
-            Text {
-                id: downloadInstructions
-                text: qsTr("Enter project URL and then click the button below")
-                font.pixelSize: 12
-                horizontalAlignment: Text.AlignHCenter
-                verticalAlignment: Text.AlignVCenter
-                wrapMode: Text.WordWrap
-                Layout.fillWidth: true
-            }
-
-            TextField {
-                id: urlTextField
-                horizontalAlignment: Text.AlignHCenter
-                placeholderText: qsTr("Enter URL")
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-            }
-
-            RowLayout {
-                id: rowLayout
-                Button {
-                    id: downloadButton
-                    text: qsTr("Download and Run Project")
-                    onClicked: backend.runOnlineProject(urlTextField.text)
-                    enabled: urlTextField.text !== ''
-                    Layout.fillWidth: true
-                    Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-                }
-            }
-
-            Connections {
-                target: backend
-                function onUrlUpdated(newUrl){
-                    urlTextField.text = newUrl;
-                }
-            }
-        }
     }
     states: [
         State {
diff --git a/src/ui/SettingsPage.qml b/src/ui/SettingsPage.qml
index 79cc624e858702a5767c9acbd319aafec2de7e63..3c40707240c0732fc604360e1a767583766dd041 100644
--- a/src/ui/SettingsPage.qml
+++ b/src/ui/SettingsPage.qml
@@ -5,6 +5,30 @@ import QtQuick.Layouts
 Item {
     id: settingsPage
 
+    ColumnLayout {
+        anchors.fill: parent
+        Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+
+        Item {
+            width: 200
+            height: 200
+            Layout.preferredHeight: 10
+            Layout.fillWidth: true
+        }
+
+        Label {
+            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+            horizontalAlignment: "AlignHCenter"
+            text: backend.buildInfo()
+        }
+
+
+        Item {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+        }
+    }
+
     ColumnLayout {
         anchors.fill: parent
 
@@ -43,5 +67,7 @@ Item {
             Layout.preferredHeight: 10
             Layout.preferredWidth: 10
         }
+
+
     }
 }
diff --git a/src/ui/content/images/container.png b/src/ui/content/images/container.png
new file mode 100644
index 0000000000000000000000000000000000000000..a650d4461ee5151f7ba9d4cd99573de071046496
Binary files /dev/null and b/src/ui/content/images/container.png differ
diff --git a/src/ui/main.qml b/src/ui/main.qml
index 5d00d420ebce5231a8a7836751d514643989df20..e847d0a50ea07b5cb3346d4aea12f5826c6410d2 100644
--- a/src/ui/main.qml
+++ b/src/ui/main.qml
@@ -15,117 +15,121 @@ Rectangle {
     M.Material.accent: M.Material.Blue
     M.Material.primary: M.Material.Blue
 
-    Image {
-        id: qdsicon1
-        width: 204
-        height: 173
-        anchors.top: parent.top
-        source: "content/images/appicon.png"
-        anchors.horizontalCenter: parent.horizontalCenter
-        fillMode: Image.PreserveAspectFit
+    ColumnLayout{
+        anchors.fill: parent
+        spacing: 4
+        Layout.alignment: Qt.AlignCenter
 
-        Text {
-            id: qdvLabel
-            text: qsTr("Qt UI Viewer")
-            anchors.top: parent.bottom
-            font.pixelSize: 12
-            anchors.topMargin: -22
-            anchors.horizontalCenter: parent.horizontalCenter
+        Rectangle{
+            id: statusBar
+            Layout.fillWidth: true
+            height: 20
+            color: "#f4a71d"
+            property bool connected: false
+            Text {
+                id: statusText
+                text: qsTr("Qt Design Studio is disconnected.")
+                font.pixelSize: 12
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                wrapMode: Text.WordWrap
+                anchors.fill: parent
+                height: 20
+            }
+            Connections{
+                target: backend
+                function onConnectedChanged(isConnected, ip){
+                    if(!isConnected){
+                        statusBar.color = "#f4a71d"
+                        statusText.text = "Qt Design Studio is disconnected.";
+                    } else {
+                        statusBar.color = "#05c940"
+                        statusText.text = "Qt Design Studio is connected to " + ip;
+                    }
+                }
+            }
         }
-    }
 
-    Popup {
-        property bool popupCloseReceived : false
-        id: popup
-        anchors.centerIn: parent
-        width: 300
-        height: 100
-        modal: true
-        focus: true
-        closePolicy: Popup.CloseOnEscape
+        Image {
+            id: qdsicon1
+            source: "content/images/appicon.png"
+            Layout.fillWidth: true
+            Layout.preferredHeight: 173
+            fillMode: Image.PreserveAspectFit
 
-        onClosed: {
-            if (!popupCloseReceived) {
-                backend.popupInterrupted();
+            Text {
+                id: qdvLabel
+                text: qsTr("Qt UI Viewer")
+                anchors.top: parent.bottom
+                font.pixelSize: 12
+                anchors.topMargin: -22
+                anchors.horizontalCenter: parent.horizontalCenter
             }
         }
 
-        ColumnLayout {
-            anchors.fill: parent
-            Text {
-                id: popupText
-            }
-            Item {
-                id: name
-            }
-            ProgressBar {
-                id: popupProgressBar
+        StackLayout {
+            id: stackLayout
+            Layout.margins: 15
+
+            HomePage {
+                id: homePage
                 Layout.fillWidth: true
-                to: 100
             }
-        }
 
-        Connections {
-            target: backend
-            function onPopupOpen() {
-                popup.open()
-                popup.popupCloseReceived = false
-            }
-            function onPopupClose() {
-                popup.popupCloseReceived = true
-                popup.close()
-            }
-            function onPopupTextChanged(text) {
-                popupText.text = text
-            }
-            function onPopupProgressIndeterminateChanged(status) {
-                popupProgressBar.indeterminate = status
-            }
-            function onDownloadProgress(progress) {
-                popupProgressBar.value = progress
+            ExamplesPage {
+                id: examplesPage
+                Layout.fillWidth: true
             }
-        }
-    }
 
-    StackLayout {
-        id: stackLayout
-        anchors.left: parent.left
-        anchors.right: parent.right
-        anchors.bottomMargin: 10
-        anchors.top: qdsicon1.bottom
-        anchors.bottom: root.bottom
-        anchors.rightMargin: 20
-        anchors.leftMargin: 20
-        anchors.topMargin: 10
+            Logs {
+                id: logsPage
+                Layout.fillWidth: true
+            }
 
-        HomePage {
-            id: homePage
-            Layout.fillWidth: true
-        }
+            DSManagement {
+                id: dsManagementPage
+                Layout.fillWidth: true
+            }
 
-        ExamplesPage {
-            id: examplesPage
-            Layout.fillWidth: true
-        }
+            SettingsPage {
+                id: settingsPage
+                Layout.fillWidth: true
+            }
 
-        Logs {
-            id: logsPage
-            Layout.fillWidth: true
+            AboutHeader {
+                id: headerPage
+                Layout.fillWidth: true
+            }
         }
 
-        DSManagement {
-            id: dsManagementPage
+        TabBar {
+            id: tabBar
             Layout.fillWidth: true
-        }
 
-        SettingsPage {
-            id: settingsPage
-            Layout.fillWidth: true
-        }
+            TabButton {
+                id: home
+                text: qsTr("Home")
+                Layout.fillWidth: true
+                checked: true
+                checkable: true
+                autoExclusive: true
+                onClicked: {
+                    stackLayout.currentIndex = 0
+                    drawer.close()
+                }
+            }
 
-        AboutHeader {
-            id: headerPage
-            Layout.fillWidth: true
+            TabButton {
+                id: settings
+                text: qsTr("Settings")
+                Layout.fillWidth: true
+                checkable: true
+                autoExclusive: true
+                onClicked: {
+                    stackLayout.currentIndex = 4
+                    drawer.close()
+                }
+            }
         }
     }
 
@@ -157,112 +161,6 @@ Rectangle {
         }
     ]
 
-
-    Drawer {
-        id: drawer
-        width: 150
-        height: root.height
-
-        ScrollView {
-            id: scrollview
-            anchors.fill: parent
-            padding: 0
-
-            ColumnLayout {
-                id: column
-                width: Math.max(implicitWidth, drawer.availableWidth)
-                height: Math.max(implicitHeight, drawer.availableHeight)
-
-                TabButton {
-                    id: home
-                    text: qsTr("Home")
-                    Layout.fillWidth: true
-                    checked: true
-                    checkable: true
-                    autoExclusive: true
-                    onClicked: {
-                        stackLayout.currentIndex = 0
-                        drawer.close()
-                    }
-                }
-
-                TabButton {
-                    id: examples
-                    text: qsTr("Examples")
-                    Layout.fillWidth: true
-                    checkable: true
-                    autoExclusive: true
-                    onClicked: {
-                        stackLayout.currentIndex = 1
-                        drawer.close()
-                    }
-                }
-
-                TabButton {
-                    id: logs
-                    text: qsTr("Logs")
-                    Layout.fillWidth: true
-                    checkable: true
-                    autoExclusive: true
-                    onClicked: {
-                        stackLayout.currentIndex = 2
-                        drawer.close()
-                    }
-                }
-
-                TabButton {
-                    id: dsManagement
-                    text: qsTr("Design Studio")
-                    Layout.fillWidth: true
-                    checkable: true
-                    autoExclusive: true
-                    visible: false
-                    onClicked: {
-                        stackLayout.currentIndex = 3
-                        drawer.close()
-                    }
-                }
-
-                TabButton {
-                    id: settings
-                    text: qsTr("Settings")
-                    Layout.fillWidth: true
-                    checkable: true
-                    autoExclusive: true
-                    onClicked: {
-                        stackLayout.currentIndex = 4
-                        drawer.close()
-                    }
-                }
-
-                TabButton {
-                    id: about
-                    text: qsTr("About")
-                    Layout.fillWidth: true
-                    checkable: true
-                    autoExclusive: true
-                    onClicked: {
-                        stackLayout.currentIndex = 5
-                        drawer.close()
-                    }
-                }
-            }
-        }
-     }
-
-    Button {
-        id: menuButon
-        x: 8
-        y: 8
-        text: qsTr("Menu")
-        Connections {
-            target: menuButon
-            function onClicked(){
-                drawer.open();
-            }
-        }
-    }
-
     Popup {
         property bool popupCloseReceived : false
         id: popup
@@ -283,6 +181,7 @@ Rectangle {
             anchors.fill: parent
             Text {
                 id: popupText
+                wrapMode: Text.WordWrap
             }
             Item {
                 id: name
@@ -294,10 +193,26 @@ Rectangle {
             }
         }
 
+        Timer {
+            id: closeTimer
+            interval: 500
+            running: false
+            repeat: false
+            onTriggered: {
+                popup.close()
+            }
+        }
+
         Connections {
             target: backend
-            function onPopupOpen() {
+            function onPopupOpen(text, timeout) {
                 popup.open()
+                popupText.text = text
+                popupProgressBar.value = 0
+                closeTimer.interval = timeout
+                if (timeout > 0) {
+                    closeTimer.start()
+                }
                 popup.popupCloseReceived = false
             }
             function onPopupClose() {
diff --git a/tests/mock.h b/tests/mock.h
index f7eb895c7154d5b7db6840aac91cc3fcb4c751f0..428bd4fff89d7febeac009b4284bf85219d178c4 100644
--- a/tests/mock.h
+++ b/tests/mock.h
@@ -34,8 +34,6 @@
 #include <QWebSocketServer>
 #include <qtypes.h>
 
-#include "backend/dsconnector/tcpdatatypes.h"
-
 #define UDP_PORT 53452
 
 class DesignStudioMock : public QObject
diff --git a/tests/tst_designstudio.cpp b/tests/tst_designstudio.cpp
index b22b4e632e4b6a0d50c3f88d0034d29a981fa842..e36bfc7b136897c929aa4c78ac3e140e046e8639 100644
--- a/tests/tst_designstudio.cpp
+++ b/tests/tst_designstudio.cpp
@@ -34,7 +34,6 @@
 #include <QWebSocketServer>
 
 #include "backend/dsconnector/ds.h"
-#include "backend/dsconnector/tcpdatatypes.h"
 
 QJsonObject createPackage(const QLatin1String &type, const QString &data)
 {
@@ -47,7 +46,7 @@ QJsonObject createPackage(const QLatin1String &type, const QString &data)
 void TestDesignStudio::initTestCase()
 {
     qDebug() << "Initialize TestDesignStudio";
-    ds = new DesignStudio("localhost", 40000, "id", "deviceId");
+    ds = new DesignStudio("localhost", 40000, "id");
     QVERIFY(ds != nullptr);
 }
 
@@ -58,7 +57,7 @@ void TestDesignStudio::testIpv4Addr()
 
 void TestDesignStudio::testId()
 {
-    QCOMPARE(ds->designStudioId(), QString("id"));
+    QCOMPARE(ds->id(), QString("id"));
 }
 
 void TestDesignStudio::testPort()
@@ -71,9 +70,11 @@ void TestDesignStudio::testUrl()
     QCOMPARE(ds->url(), QUrl("ws://localhost:40000"));
 }
 
-void TestDesignStudio::testDeviceId()
+void TestDesignStudio::testSkipPairing()
 {
-    QCOMPARE(ds->deviceId(), QString("deviceId"));
+    QVERIFY(!ds->skipPairing());
+    ds->setSkipPairing(true);
+    QVERIFY(ds->skipPairing());
 }
 
 void TestDesignStudio::testConnected()
@@ -103,14 +104,14 @@ void TestDesignStudio::testSendRegistrationPin()
         QJsonObject obj = QJsonDocument::fromJson(message.toUtf8()).object();
         QVERIFY(obj.contains("dataType"));
         QVERIFY(obj.contains("data"));
-        QVERIFY(obj.value("dataType").toString() == PackageToDesignStudio::registrationPin);
+        QVERIFY(obj.value("dataType").toString() == PackageToDesignStudio::pairingPin);
         QVERIFY(obj.value("data").toString() == "1234");
     });
 
     QSignalSpy spy(socket, &QWebSocket::textMessageReceived);
     QVERIFY(spy.isValid());
 
-    ds.sendRegistrationPin("1234");
+    ds.sendPairingPin("1234");
     QTRY_COMPARE(spy.count(), 1);
     QTest::qWait(1000);
 }
@@ -146,7 +147,7 @@ void TestDesignStudio::testSendDeviceInfo()
     QVERIFY(spy.isValid());
 
     const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::designStudioReady,
-                                                     "data");
+                                                     ds.id());
     socket->sendTextMessage(QJsonDocument(dsReadyPackage).toJson(QJsonDocument::Compact));
 
     QTRY_COMPARE(spy.count(), 1);
@@ -164,11 +165,11 @@ void TestDesignStudio::testRegistrationPinRequestedSignal()
     QWebSocket *socket = server.nextPendingConnection();
     QVERIFY(socket != nullptr);
 
-    QSignalSpy spy(&ds, &DesignStudio::registrationPinRequested);
+    QSignalSpy spy(&ds, &DesignStudio::pinRequested);
     QVERIFY(spy.isValid());
 
-    const QJsonObject pinRequestedPackage
-        = createPackage(PackageFromDesignStudio::pinVerificationRequested, "data");
+    const QJsonObject pinRequestedPackage = createPackage(PackageFromDesignStudio::pinRequested,
+                                                          "data");
     socket->sendTextMessage(QJsonDocument(pinRequestedPackage).toJson(QJsonDocument::Compact));
 
     QTRY_COMPARE(spy.count(), 1);
@@ -185,38 +186,17 @@ void TestDesignStudio::testPinVerificationFailedSignal()
     QWebSocket *socket = server.nextPendingConnection();
     QVERIFY(socket != nullptr);
 
-    QSignalSpy spy(&ds, &DesignStudio::pinVerificationFailed);
+    QSignalSpy spy(&ds, &DesignStudio::pinFailed);
     QVERIFY(spy.isValid());
 
     const QJsonObject pinVerificationFailedPackage
-        = createPackage(PackageFromDesignStudio::pinVerificationFailed, "data");
+        = createPackage(PackageFromDesignStudio::pinFailed, "data");
     socket->sendTextMessage(
         QJsonDocument(pinVerificationFailedPackage).toJson(QJsonDocument::Compact));
 
     QTRY_COMPARE(spy.count(), 1);
 }
 
-void TestDesignStudio::testRegistrationSuccessfulSignal()
-{
-    QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
-    QVERIFY(server.listen(QHostAddress::LocalHost, 40007));
-
-    DesignStudio ds("localhost", 40007, "id");
-    QTRY_VERIFY(ds.connected());
-
-    QWebSocket *socket = server.nextPendingConnection();
-    QVERIFY(socket != nullptr);
-
-    QSignalSpy spy(&ds, &DesignStudio::registrationSucceeded);
-    QVERIFY(spy.isValid());
-
-    const QJsonObject deviceIdReceivedPackage
-        = createPackage(PackageFromDesignStudio::deviceIdReceived, "data");
-    socket->sendTextMessage(QJsonDocument(deviceIdReceivedPackage).toJson(QJsonDocument::Compact));
-
-    QTRY_COMPARE(spy.count(), 1);
-}
-
 void TestDesignStudio::testUnregisteredSignal()
 {
     QWebSocketServer server("TestDesignStudio", QWebSocketServer::NonSecureMode);
@@ -228,7 +208,7 @@ void TestDesignStudio::testUnregisteredSignal()
     QWebSocket *socket = server.nextPendingConnection();
     QVERIFY(socket != nullptr);
 
-    QSignalSpy spy(&ds, &DesignStudio::unregistered);
+    QSignalSpy spy(&ds, &DesignStudio::unpaired);
     QVERIFY(spy.isValid());
 
     const QJsonObject deviceIdDeclinedPackage
@@ -249,7 +229,7 @@ void TestDesignStudio::testConnectionAliveSignal()
     QWebSocket *socket = server.nextPendingConnection();
     QVERIFY(socket != nullptr);
 
-    QSignalSpy spy(&ds, &DesignStudio::dsOnline);
+    QSignalSpy spy(&ds, &DesignStudio::paired);
     QVERIFY(spy.isValid());
 
     const QJsonObject dsReadyPackage = createPackage(PackageFromDesignStudio::deviceIdAccepted,
diff --git a/tests/tst_designstudio.h b/tests/tst_designstudio.h
index 518d12ee9d17c50d5b0fefc85c888f5775226524..7573c6b1751cb30fb29e86ae4b1ce1d635dc6d91 100644
--- a/tests/tst_designstudio.h
+++ b/tests/tst_designstudio.h
@@ -32,6 +32,7 @@ class TestDesignStudio : public QObject
     Q_OBJECT
 private:
     DesignStudio *ds;
+    QString m_uuid;
 
 private slots:
     // init
@@ -42,7 +43,7 @@ private slots:
     void testId();
     void testPort();
     void testUrl();
-    void testDeviceId();
+    void testSkipPairing();
 
     // test status check
     void testConnected();
@@ -54,7 +55,6 @@ private slots:
     // test signals
     void testRegistrationPinRequestedSignal();
     void testPinVerificationFailedSignal();
-    void testRegistrationSuccessfulSignal();
     void testUnregisteredSignal();
     void testConnectionAliveSignal();
     void testProjectIncomingSignal();