diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6436cccf973da5351150f5e1999d4869f822518e..fda15aaf390332092cf8bae72d81fc4f2a085f64 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,7 +21,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.19)
 
 if(APPLE)
   project(Jami)
@@ -75,6 +75,37 @@ if(NOT MSVC)
   set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb")
 endif()
 
+include(${PROJECT_SOURCE_DIR}/extras/build/cmake/contrib_tools.cmake)
+set(EXTRA_PATCHES_DIR ${PROJECT_SOURCE_DIR}/extras/patches)
+
+list(APPEND QWINDOWKIT_OPTIONS
+  QWINDOWKIT_BUILD_WIDGETS OFF
+  QWINDOWKIT_INSTALL OFF
+  QWINDOWKIT_BUILD_STATIC ON
+)
+
+if(WIN32)
+  list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0002-workaround-right-margin.patch)
+  list(APPEND QWINDOWKIT_OPTIONS QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS OFF)
+endif()
+
+# qmsetup uses the wrong package dir on Fedora at least.
+check_redhat_based(IS_REDHAT_BASED)
+if(IS_REDHAT_BASED)
+  list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0001-fix-fedora-fc-build.patch)
+  set(qmsetup_cmake_path ${CMAKE_BINARY_DIR}/_install/lib64/cmake/qmsetup)
+endif()
+
+# qwindowkit (frameless window)
+add_fetch_content(
+  TARGET qwindowkit
+  URL https://github.com/stdware/qwindowkit.git
+  BRANCH 79b1f3110754f9c21af2d7dacbd07b1a9dbaf6ef
+  PATCHES ${QWINDOWKIT_PATCHES}
+  OPTIONS ${QWINDOWKIT_OPTIONS}
+)
+list(APPEND CLIENT_INCLUDE_DIRS ${QWindowKit_BINARY_DIR}/include)
+list(APPEND CLIENT_LIBS QWindowKit::Quick)
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
@@ -102,7 +133,7 @@ if(QT6_VER AND QT6_PATH)
   find_package(QT NAMES Qt6 REQUIRED
     PATHS ${QT6_PATH} NO_DEFAULT_PATH)
 else()
-  message(STATUS "Looking for Qt 6" ${CMAKE_PREFIX_PATH})
+  message(STATUS "Looking for Qt 6 in ${CMAKE_PREFIX_PATH}")
   find_package(QT NAMES Qt6 REQUIRED)
 endif()
 if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR})
diff --git a/extras/build/cmake/contrib_tools.cmake b/extras/build/cmake/contrib_tools.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..8759c4518e30e24a323ed91f31fc0ef150b628c4
--- /dev/null
+++ b/extras/build/cmake/contrib_tools.cmake
@@ -0,0 +1,84 @@
+# Copyright (C) 2024 Savoir-faire Linux Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+
+include(FetchContent)
+include(CMakeParseArguments)
+
+# Helper function to check if the current distribution is Red Hat-based
+function(check_redhat_based IS_REDHAT_BASED)
+  set(${IS_REDHAT_BASED} FALSE PARENT_SCOPE)
+  # Check for the existence of /etc/os-release
+  if(EXISTS "/etc/os-release")
+    # Read the content of the file
+    file(READ "/etc/os-release" OS_RELEASE_CONTENT)
+      # Check if the distribution is Fedora or Red Hat-based
+      string(REGEX MATCH "ID=fedora|ID_LIKE=\"rhel fedora\"" MATCH_RESULT "${OS_RELEASE_CONTENT}")
+      if(MATCH_RESULT)
+        set(${IS_REDHAT_BASED} TRUE PARENT_SCOPE)
+        message(STATUS "Running on a Red Hat-based distribution (Fedora, RHEL, CentOS, etc.)")
+      else()
+        message(STATUS "Not a Red Hat-based distribution")
+      endif()
+  else()
+    message(STATUS "Cannot determine the distribution type: /etc/os-release not found")
+  endif()
+endfunction()
+
+# Helper function to add external content with patches and options.
+# Parameters:
+#   TARGET: Name of the target to create
+#   URL: URL of the git repository
+#   BRANCH: Branch to checkout
+#   PATCHES: List of patch files to apply
+#   OPTIONS: List of options to set prior to calling FetchContent_MakeAvailable
+function(add_fetch_content)
+  # Parse function arguments
+  set(oneValueArgs TARGET URL BRANCH)
+  set(multiValueArgs PATCHES OPTIONS)
+  cmake_parse_arguments(PARSE_ARGV 0 AFCWP "" "${oneValueArgs}" "${multiValueArgs}")
+
+  # Create a string for the patch command
+  set(patch_cmd "")
+  # If patches is not empty, start the command with "git apply"
+  if(NOT "${AFCWP_PATCHES}" STREQUAL "")
+    set(patch_cmd git apply)
+  endif()
+  foreach(patch_file IN LISTS AFCWP_PATCHES)
+    list(APPEND patch_cmd "${patch_file}")
+  endforeach()
+
+  # Declare the external content
+  FetchContent_Declare(
+    ${AFCWP_TARGET}
+    GIT_REPOSITORY ${AFCWP_URL}
+    GIT_TAG ${AFCWP_BRANCH}
+    PATCH_COMMAND ${patch_cmd}
+    UPDATE_DISCONNECTED 1
+  )
+
+  # Apply options
+  list(LENGTH AFCWP_OPTIONS options_length)
+  math(EXPR max_idx "${options_length} - 1")
+  foreach(idx RANGE 0 ${max_idx} 2)
+    list(GET AFCWP_OPTIONS ${idx} key)
+    math(EXPR value_idx "${idx} + 1")
+    list(GET AFCWP_OPTIONS ${value_idx} value)
+    set(${key} ${value} CACHE STRING "${key}" FORCE)
+  endforeach()
+
+  # Make the content available
+  FetchContent_MakeAvailable(${AFCWP_TARGET})
+endfunction()
diff --git a/extras/build/docker/Dockerfile.client-qt-gnulinux b/extras/build/docker/Dockerfile.client-qt-gnulinux
index 2e0910d59b16660430d3eceeedecd44b2b95f8a4..44a86858bce6483101480bf402e5267f2dbacb3b 100644
--- a/extras/build/docker/Dockerfile.client-qt-gnulinux
+++ b/extras/build/docker/Dockerfile.client-qt-gnulinux
@@ -66,4 +66,9 @@ RUN apt-get install -y -o Acquire::Retries=10 \
         libssl-dev
 RUN apt-get install -y pandoc \
         googletest \
-        libgtest-dev
+        libgtest-dev \
+        wget
+
+# Install a recent version of CMake
+ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
+RUN /opt/install-cmake.sh
\ No newline at end of file
diff --git a/extras/patches/0001-fix-fedora-fc-build.patch b/extras/patches/0001-fix-fedora-fc-build.patch
new file mode 100644
index 0000000000000000000000000000000000000000..268069f6dbff0d7de0d9e8c7423160cee15260d9
--- /dev/null
+++ b/extras/patches/0001-fix-fedora-fc-build.patch
@@ -0,0 +1,25 @@
+From 161d28abb6784115ad71fcb6977e112e9d5756d4 Mon Sep 17 00:00:00 2001
+From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+Date: Tue, 23 Jan 2024 15:38:34 -0500
+Subject: [PATCH] fix-fedora-fc-build
+
+---
+ CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 0fb89c8..3a6ad6d 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -65,7 +65,7 @@ if(NOT TARGET qmsetup::library)
+     )
+ 
+     # Find package again
+-    find_package(qmsetup REQUIRED PATHS ${_package_path})
++    find_package(qmsetup REQUIRED PATHS ${_package_path} ${qmsetup_cmake_path})
+ 
+     # Update import path
+     set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE)
+-- 
+2.34.1
+
diff --git a/extras/patches/0002-workaround-right-margin.patch b/extras/patches/0002-workaround-right-margin.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4ebe4837284a51e4cb2ffefb3cc1503f6dcf4602
--- /dev/null
+++ b/extras/patches/0002-workaround-right-margin.patch
@@ -0,0 +1,34 @@
+From ca2be6466c150d1b82a646d97b27df35b45d90f1 Mon Sep 17 00:00:00 2001
+From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+Date: Tue, 9 Jan 2024 15:25:19 -0500
+Subject: [PATCH] workaround right margin
+
+---
+ src/core/contexts/win32windowcontext.cpp | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
+index 3f6623e..9ee7752 100644
+--- a/src/core/contexts/win32windowcontext.cpp
++++ b/src/core/contexts/win32windowcontext.cpp
+@@ -402,6 +402,17 @@ namespace QWK {
+                     return true;
+                 }
+             }
++
++#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
++            if (msg->message == WM_MOVE || msg->message == WM_SIZE ||
++                (msg->message == WM_IME_SETCONTEXT && (GetForegroundWindow() == msg->hwnd))) {
++                static const auto flags = SWP_FRAMECHANGED | SWP_NOMOVE |
++                                          SWP_NOSIZE | SWP_NOZORDER |
++                                          SWP_NOOWNERZORDER;
++                SetWindowPos(msg->hwnd, NULL, 0, 0, 0, 0, flags);
++            }
++#endif
++
+             return false;
+         }
+
+--
+2.7.4
+
diff --git a/resources/icons/window-bar_close.svg b/resources/icons/window-bar_close.svg
new file mode 100644
index 0000000000000000000000000000000000000000..103d04ef4b3d621ee7b9ac6b8cc7028bd52a4298
--- /dev/null
+++ b/resources/icons/window-bar_close.svg
@@ -0,0 +1,15 @@
+<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10.88" height="10.88"
+  viewBox="0 0 10.88 10.88">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: none;
+        stroke: white;
+        stroke-miterlimit: 10;
+        stroke-width: 1.25px;
+      }
+    </style>
+  </defs>
+  <line class="cls-1" x1="0.44" y1="0.44" x2="10.44" y2="10.44" />
+  <line class="cls-1" x1="0.44" y1="10.44" x2="10.44" y2="0.44" />
+</svg>
\ No newline at end of file
diff --git a/resources/icons/window-bar_fullscreen.svg b/resources/icons/window-bar_fullscreen.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fff0898c53a3b70c10f4ef59d88cfe0b8816e338
--- /dev/null
+++ b/resources/icons/window-bar_fullscreen.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1594017175519"
+    class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1933"
+    xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+    <defs>
+        <style type="text/css"></style>
+    </defs>
+    <path
+        d="M874.666667 128h-170.666667a21.333333 21.333333 0 0 0 0 42.666667h119.168l-176.917333 176.917333a21.333333 21.333333 0 1 0 30.165333 30.165333L853.333333 200.832V320a21.333333 21.333333 0 0 0 42.666667 0V149.333333a21.333333 21.333333 0 0 0-21.333333-21.333333zM347.584 646.250667L170.666667 823.168V704a21.333333 21.333333 0 0 0-42.666667 0v170.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h170.666667a21.333333 21.333333 0 0 0 0-42.666667H200.832l176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333-30.165333zM874.666667 682.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v119.168l-176.917333-176.917333a21.333333 21.333333 0 0 0-30.165333 30.165333L823.168 853.333333H704a21.333333 21.333333 0 0 0 0 42.666667h170.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-170.666667a21.333333 21.333333 0 0 0-21.333333-21.333333zM200.832 170.666667H320a21.333333 21.333333 0 0 0 0-42.666667H149.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v170.666667a21.333333 21.333333 0 0 0 42.666667 0V200.832l176.917333 176.917333a21.333333 21.333333 0 0 0 30.165333-30.165333z"
+        fill="#ffffff" p-id="1934"></path>
+</svg>
\ No newline at end of file
diff --git a/resources/icons/window-bar_maximize.svg b/resources/icons/window-bar_maximize.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a50c90958a6d74083449c0253b7c9ae98ef45916
--- /dev/null
+++ b/resources/icons/window-bar_maximize.svg
@@ -0,0 +1,12 @@
+<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: none;
+        stroke: white;
+        stroke-miterlimit: 10;
+      }
+    </style>
+  </defs>
+  <rect class="cls-1" x="0.5" y="0.5" width="9" height="9" />
+</svg>
\ No newline at end of file
diff --git a/resources/icons/window-bar_minimize.svg b/resources/icons/window-bar_minimize.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e4e4bfdd99cb4a7b417719ce47ef4c67e15daeae
--- /dev/null
+++ b/resources/icons/window-bar_minimize.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
+	y="0px" viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
+	<style type="text/css">
+		.st0 {
+			fill: white;
+		}
+	</style>
+	<rect y="4.5" class="st0" width="10" height="1" />
+</svg>
\ No newline at end of file
diff --git a/resources/icons/window-bar_restore.svg b/resources/icons/window-bar_restore.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bb6e245cbed5b9a904b96a3ec8a110025b98ce6c
--- /dev/null
+++ b/resources/icons/window-bar_restore.svg
@@ -0,0 +1,16 @@
+<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+  <defs>
+    <style>
+      .cls-1 {
+        fill: none;
+        stroke: white;
+        stroke-miterlimit: 10;
+      }
+    </style>
+  </defs>
+  <rect class="cls-1" x="0.5" y="2.5" width="9" height="9" />
+  <line class="cls-1" x1="2.5" y1="2.5" x2="2.5" y2="0.5" />
+  <line class="cls-1" x1="12" y1="0.5" x2="2" y2="0.5" />
+  <line class="cls-1" x1="11.5" y1="10" x2="11.5" />
+  <line class="cls-1" x1="10" y1="9.5" x2="12" y2="9.5" />
+</svg>
\ No newline at end of file
diff --git a/src/app/LayoutManager.qml b/src/app/LayoutManager.qml
index a9ac2a6c0088ad7fc94dd3f4e46ca306d1f5b96c..f36b2b685f127889d42dff3599716eebafb3771e 100644
--- a/src/app/LayoutManager.qml
+++ b/src/app/LayoutManager.qml
@@ -73,17 +73,26 @@ QtObject {
     function saveWindowSettings() {
         // If closed-to-tray or minimized or fullscreen, save the cached windowedVisibility
         // value instead.
-        if (isHidden || isFullScreen) {
-            AppSettingsManager.setValue(Settings.WindowState, priv.windowedVisibility)
-        } else {
-            AppSettingsManager.setValue(Settings.WindowState, visibility)
-        }
+        const visibilityToSave = isHidden || isFullScreen ? priv.windowedVisibility : visibility;
 
         // Likewise, don't save fullscreen geometry.
         const geometry = isFullScreen ?
                            priv.windowedGeometry :
                            Qt.rect(appWindow.x, appWindow.y,
-                                   appWindow.width, appWindow.height)
+                                   appWindow.width, appWindow.height);
+
+        // QWK: Account for the frameless window's offset.
+        if (appWindow.useFrameless) {
+            if (Qt.platform.os.toString() !== "osx") {
+                // Add [7, 30, 0, 0] on Windows and GNU/Linux.
+                geometry.x += 7;
+                geometry.y += 30;
+            }
+        }
+
+        console.debug("Saving window: " + JSON.stringify(geometry) + " " + visibilityToSave);
+
+        AppSettingsManager.setValue(Settings.WindowState, visibilityToSave)
         AppSettingsManager.setValue(Settings.WindowGeometry, geometry)
     }
 
@@ -111,6 +120,8 @@ QtObject {
         const visibilityStr = AppSettingsManager.getValue(Settings.WindowState)
         var visibilitySetting = parseInt(visibilityStr)
 
+        console.debug("Restoring window: " + JSON.stringify(geometry) + " " + visibilitySetting)
+
         // We should never restore a hidden or fullscreen state here. Default to normal
         // windowed state in such a case. This shouldn't happen.
         if (visibilitySetting === Window.Hidden || visibilitySetting === Window.FullScreen) {
diff --git a/src/app/MainApplicationWindow.qml b/src/app/MainApplicationWindow.qml
index 06d38ce4f28eeab3d07a79f7dd025d8bde5ded67..31b9dee728b6f1a04cf74f0f8bcc24e6f0c41087 100644
--- a/src/app/MainApplicationWindow.qml
+++ b/src/app/MainApplicationWindow.qml
@@ -24,29 +24,30 @@ import QtQuick.Window
 import QtQuick.Controls
 import QtQuick.Layouts
 import Qt5Compat.GraphicalEffects
+
 import net.jami.Models 1.1
 import net.jami.Adapters 1.1
 import net.jami.Enums 1.1
 import net.jami.Helpers 1.1
 import net.jami.Constants 1.1
+
 import "mainview"
 import "mainview/components"
 import "wizardview"
 import "commoncomponents"
 
+import QWindowKit
+
 ApplicationWindow {
-    id: root
+    id: appWindow
 
     property bool isRTL: UtilsAdapter.isRTL
 
     LayoutMirroring.enabled: isRTL
     LayoutMirroring.childrenInherit: isRTL
 
-    enum LoadedSource {
-        MainView,
-        AccountMigrationView,
-        None
-    }
+    // This needs to be set from the start.
+    readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
 
     onActiveFocusItemChanged: {
         focusOverlay.margin = -5;
@@ -70,7 +71,7 @@ ApplicationWindow {
         sourceComponent: GenericErrorsRow {
             id: genericError
             text: CurrentAccount.enabled ? JamiStrings.noNetworkConnectivity : JamiStrings.disabledAccount
-            height: visible? JamiTheme.chatViewHeaderPreferredHeight : 0
+            height: visible? JamiTheme.qwkTitleBarHeight : 0
         }
     }
 
@@ -88,36 +89,30 @@ ApplicationWindow {
         border.color: JamiTheme.tintedBlue
     }
 
-    property ApplicationWindow appWindow: root
-    property LayoutManager layoutManager: LayoutManager {
-        appContainer: appContainer
+    // Used to manage full screen mode and save/restore window geometry.
+    LayoutManager {
+        id: layoutManager
+        appContainer: fullscreenContainer
     }
-    property ViewManager viewManager: ViewManager {
-    }
-    property ViewCoordinator viewCoordinator: ViewCoordinator {
-        viewManager: root.viewManager
-    }
-
-    property bool windowSettingsLoaded: false
-    property bool allowVisibleWindow: true
 
-    function checkLoadedSource() {
-        var sourceString = mainApplicationLoader.source.toString();
-        if (sourceString === JamiQmlUtils.mainViewLoadPath)
-            return MainApplicationWindow.LoadedSource.MainView;
-        return MainApplicationWindow.LoadedSource.None;
+    // Used to manage dynamic view loading and unloading.
+    ViewManager {
+        id: viewManager
     }
 
-    function startClient() {
-        setMainLoaderSource(JamiQmlUtils.mainViewLoadPath);
+    // Used to manage the view stack and the current view.
+    ViewCoordinator {
+        id: viewCoordinator
     }
 
-    function setMainLoaderSource(source) {
-        if (checkLoadedSource() === MainApplicationWindow.LoadedSource.MainView) {
-            cleanupMainView();
-        }
-        mainApplicationLoader.setSource(source);
-    }
+    // Used to prevent the window from being visible until the
+    // window geometry has been restored and the view stack has
+    // been loaded.
+    property bool windowSettingsLoaded: false
+
+    // This setting can be used to block a loading Jami instance
+    // from showNormal() and showMaximized() when starting minimized.
+    property bool allowVisibleWindow: true
 
     function cleanupMainView() {
         // Save the main view window size if loading anything else.
@@ -139,88 +134,148 @@ ApplicationWindow {
 
     title: JamiStrings.appTitle
 
-    visible: mainApplicationLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow
-
-    // To facilitate reparenting of the callview during
-    // fullscreen mode, we need QQuickItem based object.
-    Item {
-        id: appContainer
+    visible: mainViewLoader.status === Loader.Ready && windowSettingsLoaded && allowVisibleWindow
 
-        anchors.fill: parent
-    }
+    Connections {
+        id: connectionMigrationEnded
 
-    Loader {
-        id: mainApplicationLoader
+        target: CurrentAccountToMigrate
 
-        anchors.fill: parent
-        z: -1
+        function onAccountNeedsMigration(accountId) {
+            viewCoordinator.present("AccountMigrationView");
+        }
 
-        asynchronous: true
-        visible: status == Loader.Ready
+        function onAllMigrationsFinished() {
+            viewCoordinator.dismiss("AccountMigrationView");
+            viewCoordinator.present("WelcomePage");
+        }
+    }
 
-        Connections {
-            id: connectionMigrationEnded
+    function initMainView(view) {
+        console.info("Initializing main view");
+
+        // Main window, load any valid app settings, and allow the
+        // layoutManager to handle as much as possible.
+        layoutManager.restoreWindowSettings();
+
+        // QWK: setup
+        if (useFrameless) {
+            windowAgent.setTitleBar(titleBar);
+            // Now register the system buttons (non-macOS).
+            if (sysBtnsLoader.item) {
+                const sysBtns = sysBtnsLoader.item;
+                windowAgent.setSystemButton(WindowAgent.Minimize, sysBtns.minButton);
+                windowAgent.setSystemButton(WindowAgent.Maximize, sysBtns.maxButton);
+                windowAgent.setSystemButton(WindowAgent.Close, sysBtns.closeButton);
+            }
+        }
 
-            target: CurrentAccountToMigrate
+        // Set the viewCoordinator's root item.
+        viewCoordinator.init(view);
 
-            function onAccountNeedsMigration(accountId) {
+        // Navigate to something.
+        if (UtilsAdapter.getAccountListSize() > 0) {
+            // Already have an account.
+            if (CurrentAccountToMigrate.accountToMigrateListSize > 0)
+                // Do we need to migrate any accounts?
                 viewCoordinator.present("AccountMigrationView");
-            }
+            else
+                // Okay now just start the client normally.
+                viewCoordinator.present("WelcomePage");
+        } else {
+            // No account, so start the wizard.
+            viewCoordinator.present("WizardView");
+        }
+
+        // Set up the event filter for macOS.
+        if (Qt.platform.os.toString() === "osx") {
+            MainApplication.setEventFilter();
+        }
 
-            function onAllMigrationsFinished() {
-                viewCoordinator.dismiss("AccountMigrationView");
-                startClient();
+        // Quiet check for updates on start if set to.
+        if (Qt.platform.os.toString() === "windows") {
+            if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) {
+                AppVersionManager.checkForUpdates(true);
+                AppVersionManager.setAutoUpdateCheck(true);
             }
         }
 
-        // Set `visible = false` when loading a new QML file.
-        onSourceChanged: windowSettingsLoaded = false
+        // Handle a start URI if set as start option.
+        MainApplication.handleUriAction();
 
-        onLoaded: {
-            if (UtilsAdapter.getAccountListSize() === 0) {
-                layoutManager.restoreWindowSettings();
-                if (!viewCoordinator.rootView)
-                    // Set the viewCoordinator's root item.
-                    viewCoordinator.init(item);
-                viewCoordinator.present("WizardView");
-            } else {
-                // Main window, load any valid app settings, and allow the
-                // layoutManager to handle as much as possible.
-                layoutManager.restoreWindowSettings();
-
-                // Present the welcome view once the viewCoordinator is setup.
-                viewCoordinator.initialized.connect(function () {
-                        viewCoordinator.preload("SidePanel");
-                        viewCoordinator.preload("SettingsSidePanel");
-                        viewCoordinator.present("WelcomePage");
-                        viewCoordinator.preload("ConversationView");
-                    });
-                if (!viewCoordinator.rootView)
-                    // Set the viewCoordinator's root item.
-                    viewCoordinator.init(item);
-                if (CurrentAccountToMigrate.accountToMigrateListSize > 0)
-                    viewCoordinator.present("AccountMigrationView");
-            }
-            if (Qt.platform.os.toString() === "osx") {
-                MainApplication.setEventFilter();
-            }
+        // This will allow visible to become true if not starting minimized.
+        windowSettingsLoaded = true;
+    }
+
+    Component.onCompleted: {
+        // QWK: setup
+        if (useFrameless) {
+            windowAgent.setup(appWindow);
+        }
 
-            // This will trigger `visible = true`.
-            windowSettingsLoaded = true;
+        mainViewLoader.active = true;
 
-            // Quiet check for updates on start if set to.
-            if (Qt.platform.os.toString() === "windows") {
-                if (UtilsAdapter.getAppValue(Settings.AutoUpdate)) {
-                    AppVersionManager.checkForUpdates(true);
-                    AppVersionManager.setAutoUpdateCheck(true);
-                }
-            }
+        // Dbus error handler for Linux.
+        if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx")
+            DBusErrorHandler.setActive(true);
+    }
+
+    Loader {
+        id: mainViewLoader
+        active: false
+        source: "qrc:/mainview/MainView.qml"
+        anchors.fill: parent
+        onLoaded: initMainView(item)
+    }
 
-            // Handle a start URI if set as start option.
-            MainApplication.handleUriAction();
+    // Use this as a parent for fullscreen items.
+    Item {
+        id: fullscreenContainer
+        anchors.fill: parent
+    }
+
+    // QWK: Provide spacing for widgets that may be occluded by the system buttons.
+    QtObject {
+        id: qwkSystemButtonSpacing
+        readonly property bool isMacOS: Qt.platform.os.toString() === "osx"
+        readonly property bool isFullscreen: layoutManager.isFullScreen
+        // macOS buttons are on the left.
+        readonly property real left: useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0
+        // Windows and Linux buttons are on the right.
+        readonly property real right: useFrameless && !isMacOS && !isFullscreen ? sysBtnsLoader.width + 24 : 0
+    }
+
+    // QWK: Window Title bar
+    Item {
+        id: titleBar
+        height: JamiTheme.qwkTitleBarHeight
+        anchors {
+            top: parent.top
+            right: parent.right
+            left: parent.left
+        }
+
+        // On Windows and Linux, use custom system buttons.
+        Loader {
+            id: sysBtnsLoader
+            active: Qt.platform.os.toString() !== "osx" && useFrameless
+            height: titleBar.height
+            anchors {
+                top: parent.top
+                right: parent.right
+                // Note: leave these margins, they prevent image scaling artifacts
+                topMargin: 1
+                rightMargin: 1
+            }
+            source: "qrc:/commoncomponents/QWKSystemButtonGroup.qml"
         }
     }
 
+    // QWK: Main interop component.
+    WindowAgent {
+        id: windowAgent
+    }
+
     Connections {
         target: LRCInstance
 
@@ -339,11 +394,5 @@ ApplicationWindow {
         }
     }
 
-    onClosing: root.close()
-
-    Component.onCompleted: {
-        startClient();
-        if (Qt.platform.os.toString() !== "windows" && Qt.platform.os.toString() !== "osx")
-            DBusErrorHandler.setActive(true);
-    }
+    onClosing: appWindow.close()
 }
diff --git a/src/app/ViewCoordinator.qml b/src/app/ViewCoordinator.qml
index 1e0bcdb208b825cd61f6176bf6794e5bb7fc038d..4bd71e7510d01793c8b6fdb6e5f21ebaca32f9c4 100644
--- a/src/app/ViewCoordinator.qml
+++ b/src/app/ViewCoordinator.qml
@@ -24,14 +24,6 @@ import "commoncomponents"
 QtObject {
     id: root
 
-    required property QtObject viewManager
-
-    signal initialized
-
-    function requestAppWindowWizardView() {
-        viewCoordinator.present("WizardView");
-    }
-
     // A map of view names to file paths for QML files that define each view.
     property variant resources: {
         "SidePanel": "mainview/components/SidePanel.qml",
@@ -47,12 +39,44 @@ QtObject {
     // The `main` view of the application window.
     property StackView rootView
 
-    property var currentViewName: rootView && rootView.currentItem && rootView.currentItem.objectName || null
+    readonly property Item currentView: rootView && rootView.currentItem || null
+    readonly property var currentViewName: currentView && currentView.objectName || null
+    readonly property bool isDualPane: currentView && currentView instanceof DualPaneView
+    readonly property bool isInSinglePaneMode: !isDualPane || currentView.isSinglePane
+    readonly property bool isRTL: Qt.application.layoutDirection === Qt.RightToLeft
+    // A list of the current visible views. This could be a single view or two views in
+    // dual pane mode. The list is ordered [minor, major] where major is the view on the
+    // right side when not in RTL and should represent the main or content-type view.
+    readonly property var visibleViews: {
+        if (!currentView)
+            return []
+        if (isDualPane) {
+            if (isInSinglePaneMode)
+                return [currentView.rightPaneItem]
+            return [currentView.leftPaneItem, currentView.rightPaneItem]
+        }
+        return [currentView]
+    }
+    // Aggregate this info and expose it as a single string for convenience.
+    // JSON indented by 2 spaces.
+    readonly property string currentViewInfo: {
+        var info = {
+            currentViewName: currentViewName,
+            isDualPane: isDualPane,
+            isInSinglePaneMode: isInSinglePaneMode,
+            visibleViews: visibleViews.map(function(view) {
+                return view && view.objectName || null;
+            }),
+            visibleViewWidths: visibleViews.map(function(view) {
+                return view && view.width || null;
+            }),
+        };
+        return JSON.stringify(info, null, 2);
+    }
 
     function init(mainStackView) {
         rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls
                                       StackView { anchors.fill: parent }`, mainStackView);
-        initialized();
     }
 
     function deinit() {
@@ -171,6 +195,8 @@ QtObject {
                 var objectName = view ? view.objectName : obj.objectName;
                 if (!viewManager.destroyView(resources[objectName])) {
                     print("could not destroy view:", objectName);
+                } else {
+                    print("destroyed view:", objectName);
                 }
             } else
                 view.dismissed();
@@ -197,8 +223,20 @@ QtObject {
         }
     }
 
-    function getView(viewName) {
-        return viewManager.getView(viewName);
+    function getView(viewName, forceCreate = false) {
+        // If the view is already loaded, return it.
+        var view = viewManager.getView(viewName);
+        if (view)
+            return view;
+        if (!forceCreate)
+            return null;
+        // Otherwise, create it.
+        view = viewManager.createView(resources[viewName], null);
+        if (!view) {
+            console.log("Failed to load view: " + viewName);
+            return null;
+        }
+        return view;
     }
 
     // Load a view without presenting it.
diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index ac2f57974e7bb2a55294db22e314820e4130c014..a31fd941d81f2e5aaf8a9d2a7a786e1d12c70c35 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -33,6 +33,14 @@
 extern const QString defaultDownloadPath;
 
 // clang-format off
+
+// Define USE_FRAMELESS_WINDOW_DEFAULT based on the platform
+#ifdef Q_OS_LINUX
+#define USE_FRAMELESS_WINDOW_DEFAULT false
+#else
+#define USE_FRAMELESS_WINDOW_DEFAULT true
+#endif
+
 // Common key-value pairs for both APPSTORE and non-APPSTORE builds
 #define COMMON_KEYS \
     X(MinimizeOnClose, false) \
@@ -66,7 +74,8 @@ extern const QString defaultDownloadPath;
     X(ChatViewEnterIsNewLine, false) \
     X(ShowSendOption, false) \
     X(EnablePtt, false) \
-    X(PttKeys, 32)
+    X(PttKeys, 32) \
+    X(UseFramelessWindow, USE_FRAMELESS_WINDOW_DEFAULT)
 #ifdef APPSTORE
 #define KEYS COMMON_KEYS
 #else
diff --git a/src/app/commoncomponents/BaseView.qml b/src/app/commoncomponents/BaseView.qml
index e0ddaac849a07f9fda103af955af1f44fccf59e6..86f74e45759c21d93ac68602e24b7a768f19c076 100644
--- a/src/app/commoncomponents/BaseView.qml
+++ b/src/app/commoncomponents/BaseView.qml
@@ -35,10 +35,12 @@ Rectangle {
     signal dismissed
 
     Component.onCompleted: {
+        console.debug("Created", objectName);
         if (managed)
             presented();
     }
     Component.onDestruction: {
+        console.debug("Destroyed", objectName);
         if (managed)
             dismissed();
     }
diff --git a/src/app/commoncomponents/QWKButton.qml b/src/app/commoncomponents/QWKButton.qml
new file mode 100644
index 0000000000000000000000000000000000000000..4b8c2e9eff9599f35c12f34e126209e5ff7240a6
--- /dev/null
+++ b/src/app/commoncomponents/QWKButton.qml
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+import QtQuick.Controls
+import Qt5Compat.GraphicalEffects
+
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+Button {
+    id: control
+
+    width: height * 0.9156626506024096
+    leftInset: 0
+    topInset: 0
+    rightInset: 0
+    bottomInset: 0
+    padding: 0
+
+    function calculateLuminance(color) {
+        return 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
+    }
+
+    property alias source: image.source
+    contentItem: Item {
+        Image {
+            id: image
+            anchors.centerIn: parent
+            mipmap: true
+            width: 12
+            height: 12
+            layer.enabled: true
+            layer.effect: ColorOverlay {
+                color: {
+                    // We may force this color to be white, when the background is dark.
+                    var backgroundIsDark = calculateLuminance(control.background.color) > 0.25;
+                    // This includes when we are in a call (which has a dark background).
+                    backgroundIsDark = backgroundIsDark || CurrentConversation.hasCall;
+                    return backgroundIsDark ? "white" : JamiTheme.primaryForegroundColor;
+                }
+            }
+        }
+    }
+
+    property color baseColor: {
+        // Avoid transparent if the background is dark i.e. in a call.
+        if (CurrentConversation.hasCall)
+            return Qt.rgba(1, 1, 1, 0.5);
+        return JamiTheme.darkTheme ? Qt.rgba(1, 1, 1, 0.15) : Qt.rgba(0, 0, 0, 0.15);
+    }
+    readonly property color pressedColor: {
+        const darker = Qt.darker(baseColor, 1.3);
+        return Qt.rgba(darker.r, darker.g, darker.b, baseColor.a * 1.3);
+    }
+    background: Rectangle {
+        color: {
+            if (!control.enabled)
+                return "gray";
+            if (control.pressed)
+                return control.pressedColor;
+            if (control.hovered)
+                return control.baseColor;
+            return "transparent";
+        }
+        Behavior on color { ColorAnimation { duration: 100 } }
+    }
+}
diff --git a/src/app/commoncomponents/QWKSetParentHitTestVisible.qml b/src/app/commoncomponents/QWKSetParentHitTestVisible.qml
new file mode 100644
index 0000000000000000000000000000000000000000..e8ce4683382ed9c2b86c4012f80f583bb47f09fd
--- /dev/null
+++ b/src/app/commoncomponents/QWKSetParentHitTestVisible.qml
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+
+Item {
+    // Wait for it's parent to be created, then set it to be hit test visible.
+    // This avoids having to edit Component.onCompleted of the parent.
+    // Note: this is experimental. TBD if this is a good way to do this.
+    // This technique makes it clear and simple to implement, but may have
+    // side effects beyond just adding a dummy item component.
+    // Best alternatives:
+    // - Wrap the parent in a custom component that is hit test visible.
+    // - Edit the parent's Component.onCompleted to set it to be hit test visible.
+    Component.onCompleted: Qt.callLater(function() {
+        if (appWindow && appWindow.useFrameless)
+            windowAgent.setHitTestVisible(parent, true);
+    });
+
+    // Likewise, wait for it's parent to be destroyed, then set it to be hit test invisible.
+    Component.onDestruction: {
+        if (appWindow && appWindow.useFrameless)
+            windowAgent.setHitTestVisible(parent, false);
+    }
+}
diff --git a/src/app/commoncomponents/QWKSystemButtonGroup.qml b/src/app/commoncomponents/QWKSystemButtonGroup.qml
new file mode 100644
index 0000000000000000000000000000000000000000..e2426750109891c674d1a8f249d94440f0366ed8
--- /dev/null
+++ b/src/app/commoncomponents/QWKSystemButtonGroup.qml
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+import QtQuick.Controls
+
+import net.jami.Constants 1.1
+
+import QWindowKit
+
+Row {
+    id: root
+
+    property alias minButton: minButton
+    property alias maxButton: maxButton
+    property alias closeButton: closeButton
+
+    component SystemButton : QWKButton {
+        height: parent.height
+    }
+
+    visible: appWindow.visibility !== Window.FullScreen
+
+    SystemButton {
+        id: minButton
+        source: JamiResources.window_bar_minimize_svg
+        onClicked: appWindow.showMinimized()
+    }
+
+    SystemButton {
+        id: maxButton
+        source: appWindow.visibility === Window.Maximized ?
+                    JamiResources.window_bar_restore_svg :
+                    JamiResources.window_bar_maximize_svg
+        onClicked: appWindow.visibility === Window.Maximized ?
+                       appWindow.showNormal() :
+                       appWindow.showMaximized()
+    }
+
+    SystemButton {
+        id: closeButton
+        source: JamiResources.window_bar_close_svg
+        baseColor: "#e81123"
+        onClicked: appWindow.close()
+    }
+}
diff --git a/src/app/commoncomponents/SidePanelBase.qml b/src/app/commoncomponents/SidePanelBase.qml
index 353c4c762b02d9e3f5fce9ce373b2fa040d2ac6d..0ad6da949a0a15702defadc6ad0b7d50b737f91b 100644
--- a/src/app/commoncomponents/SidePanelBase.qml
+++ b/src/app/commoncomponents/SidePanelBase.qml
@@ -1,10 +1,53 @@
+/*
+ * Copyright (C) 2022-2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 import QtQuick
+import QtQuick.Controls
 
-Rectangle {
+Page {
     id: root
 
     anchors.fill: parent
 
+    property color color: "transparent"
+
+    // QWK: Title bar spacing for macOS and single pane mode.
+    // Not using topMargin here on purpose, to make is simple to
+    // keep the theme coloring without wrapping components that
+    // derive from SidePanelBase.
+    header: Rectangle {
+        id: titleBarSpacer
+        height: {
+            if (!appWindow.useFrameless)
+                return 0;
+            var extraHeight = 0;
+            if (Qt.platform.os.toString() === "osx")
+                extraHeight = 24;
+            else if (viewCoordinator.isInSinglePaneMode)
+                extraHeight = titleBar.height;
+            return extraHeight;
+        }
+        color: root.color
+    }
+
+    background: Rectangle {
+        color: root.color
+    }
+
     // Override these if needed.
     property var select: function () {}
     property var deselect: function () {}
diff --git a/src/app/constant/JamiQmlUtils.qml b/src/app/constant/JamiQmlUtils.qml
index 28f16f8bb14417aae0773355249a6715fcd5fefc..eb272fb931cbdb2d9aa99f7d3474153b3ff72558 100644
--- a/src/app/constant/JamiQmlUtils.qml
+++ b/src/app/constant/JamiQmlUtils.qml
@@ -24,9 +24,6 @@ import net.jami.Enums 1.1
 
 Item {
     property string qmlFilePrefix: "file:/"
-
-    readonly property string mainViewLoadPath: "qrc:/mainview/MainView.qml"
-    readonly property string wizardViewLoadPath: "qrc:/wizardview/WizardView.qml"
     readonly property string base64StringTitle: "data:image/png;base64,"
 
     property var accountCreationInputParaObject: ({})
@@ -81,8 +78,7 @@ Item {
         function onDonationCampaignSettingsChanged() {
             // Changing any of the donation campaign settings will trigger a recompute
             // of the banner visibility.
-            updateIsDonationBannerVisible();
-        }
+            updateIsDonationBannerVisible(); }
     }
 
     function updateIsDonationBannerVisible() {
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index b72253e07a93bc768e67c8c8c3eaa7f2275f8abc..611564a2275cdd8d6be0e7bbaad3e0710b2c5b1f 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -477,6 +477,7 @@ Item {
     property string enableNotifications: qsTr("Enable notifications")
     property string showNotifications: qsTr("Show notifications")
     property string keepMinimized: qsTr("Minimize on close")
+    property string useNativeWindowFrame: qsTr("Use native window frame (requires restart)")
     property string tipRunStartup: qsTr("Run at system startup")
     property string runStartup: qsTr("Launch at startup")
     property string downloadFolder: qsTr("Choose download directory")
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 05a6c9521b83438f29523bc3efcb58b0bf83b68f..e976bb7de59dde975130a69941a70256cf291e39 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -474,7 +474,6 @@ Item {
     // MessageWebView
     property real chatViewHairLineSize: 1
     property real chatViewMaximumWidth: 900
-    property real chatViewHeaderPreferredHeight: 50
     property real chatViewFooterPreferredHeight: 35
     property real chatViewFooterMaximumHeight: 315
     property real chatViewFooterRowSpacing: 4
@@ -710,4 +709,7 @@ Item {
     property color chatSettingButtonBackgroundColor: darkTheme ? "#303030" : "#F0EFEF"
     property color chatSettingButtonBorderColor: darkTheme ? "#03B9E9" : "#005699"
     property color chatSettingButtonTextColor: textColor
+
+    // QWK
+    property real qwkTitleBarHeight: 50
 }
diff --git a/src/app/mainapplication.cpp b/src/app/mainapplication.cpp
index c118d1a3ee4bea26b0760ba490a76a397d4d9db0..144bfbc62edd4ae68e9c10060c085d496e377a64 100644
--- a/src/app/mainapplication.cpp
+++ b/src/app/mainapplication.cpp
@@ -27,6 +27,8 @@
 #include "systemtray.h"
 #include "videoprovider.h"
 
+#include <QWKQuick/qwkquickglobal.h>
+
 #include <QAction>
 #include <QCommandLineParser>
 #include <QCoreApplication>
@@ -138,7 +140,6 @@ ScreenInfo::onPhysicalDotsPerInchChanged()
 
 MainApplication::MainApplication(int& argc, char** argv)
     : QApplication(argc, argv)
-    , isCleanupped(false)
 {
     const char* qtVersion = qVersion();
     if (strncmp(qtVersion, QT_VERSION_STR, strnlen(qtVersion, sizeof qtVersion)) != 0) {
@@ -166,8 +167,6 @@ MainApplication::MainApplication(int& argc, char** argv)
     // the logging features.
     qInstallMessageHandler(messageHandler);
 
-    QObject::connect(this, &QApplication::aboutToQuit, this, &MainApplication::cleanup);
-
     qCInfo(app_) << "Using Qt runtime version:" << qtVersion;
 }
 
@@ -184,6 +183,8 @@ MainApplication::init()
     // performing unnecessary tasks, like initializing the webengine.
     engine_.reset(new QQmlApplicationEngine(this));
 
+    QWK::registerTypes(engine_.get());
+
     connectivityMonitor_ = new ConnectivityMonitor(this);
     settingsManager_ = new AppSettingsManager(this);
     systemTray_ = new SystemTray(settingsManager_, this);
@@ -466,17 +467,6 @@ MainApplication::initSystray()
     systemTray_->show();
 }
 
-void
-MainApplication::cleanup()
-{
-    // In Qt 6.5, QApplication::exit(0) will signal aboutToQuit, and aboutToQuit is connected to cleanup
-    // TODO: delete cleanup.
-    if (!isCleanupped) {
-        isCleanupped = true;
-        QApplication::exit(0);
-    }
-}
-
 void
 MainApplication::setEventFilter()
 {
diff --git a/src/app/mainapplication.h b/src/app/mainapplication.h
index 79f218aedd179c9437e42ccd3c81c9860d7c96e0..5c039e882f733bc0d5584f0621116fa9ea80c3fb 100644
--- a/src/app/mainapplication.h
+++ b/src/app/mainapplication.h
@@ -108,7 +108,6 @@ private:
     void setApplicationFont();
     void initQmlLayer();
     void initSystray();
-    void cleanup();
 
 private:
     std::map<Option, QVariant> runOptions_;
@@ -123,6 +122,4 @@ private:
     AppSettingsManager* settingsManager_;
 
     ScreenInfo screenInfo_;
-
-    bool isCleanupped;
 };
diff --git a/src/app/mainview/ConversationView.qml b/src/app/mainview/ConversationView.qml
index 7d96ce6b24624d17f6e1f39ae883514be4b3c6e0..9b8b7f0da4899cf532e4358fcba54b40c40ffdad 100644
--- a/src/app/mainview/ConversationView.qml
+++ b/src/app/mainview/ConversationView.qml
@@ -45,9 +45,11 @@ ListSelectionView {
 
     color: JamiTheme.transparentColor
 
-    leftPaneItem: viewCoordinator.getView("SidePanel")
+    leftPaneItem: viewCoordinator.getView("SidePanel", true)
 
     rightPaneItem: StackLayout {
+        objectName: "ConversationLayout"
+
         currentIndex: !CurrentConversation.hasCall ? 0 : 1
         onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer
 
diff --git a/src/app/mainview/MainView.qml b/src/app/mainview/MainView.qml
index 93ef1380c590c579b5300764863746832a6b7001..ed71fe399cd4a8ed550dd78428fc56f38edeee17 100644
--- a/src/app/mainview/MainView.qml
+++ b/src/app/mainview/MainView.qml
@@ -36,13 +36,8 @@ import "js/keyboardshortcuttablecreation.js" as KeyboardShortcutTableCreation
 
 Rectangle {
     id: mainView
-
     objectName: "mainView"
 
-    // To calculate tab bar bottom border hidden rect left margin.
-    property int tabBarLeftMargin: 8
-    property int tabButtonShrinkSize: 8
-
     property string currentConvId: CurrentConversation.id
     onCurrentConvIdChanged: {
         if (currentConvId !== '') {
diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml
index eff5ab59eaf13221821ab28dbb392a1011e223cf..6e7860488e172da1682e6e0d028b8bc60d0be99c 100644
--- a/src/app/mainview/components/ChatView.qml
+++ b/src/app/mainview/components/ChatView.qml
@@ -115,8 +115,8 @@ Rectangle {
 
             Layout.alignment: Qt.AlignHCenter
             Layout.fillWidth: true
-            Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
-            Layout.maximumHeight: JamiTheme.chatViewHeaderPreferredHeight
+            Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
+            Layout.maximumHeight: JamiTheme.qwkTitleBarHeight
             Layout.minimumWidth: JamiTheme.mainViewPaneMinWidth
 
             DropArea {
@@ -180,14 +180,14 @@ Rectangle {
         ConversationErrorsRow {
             id: errorRect
             Layout.fillWidth: true
-            Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
+            Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
             visible: false
         }
 
         NotificationArea {
             id: notificationArea
             Layout.fillWidth: true
-            Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
+            Layout.preferredHeight: JamiTheme.qwkTitleBarHeight
             visible: CurrentConversation.activeCalls.length > 0 && !root.inCallView
         }
 
diff --git a/src/app/mainview/components/ChatViewHeader.qml b/src/app/mainview/components/ChatViewHeader.qml
index b2b9232036267a136f3a3e58eb2d1c6cda098d28..d33ab24cc6c72cf6d2ecdc4d0b2f3e26d955f293 100644
--- a/src/app/mainview/components/ChatViewHeader.qml
+++ b/src/app/mainview/components/ChatViewHeader.qml
@@ -46,7 +46,7 @@ Rectangle {
         }
     }
 
-    property bool interactionButtonsVisibility: {
+    readonly property bool interactionButtonsVisibility: {
         if (CurrentConversation.inCall)
             return false;
         if (LRCInstance.currentAccountType === Profile.Type.SIP)
@@ -74,16 +74,17 @@ Rectangle {
         id: messagingHeaderRectRowLayout
 
         anchors.fill: parent
-        anchors.rightMargin: 8
+        // QWK: spacing
+        anchors.leftMargin: qwkSystemButtonSpacing.left
+        anchors.rightMargin: 10 + qwkSystemButtonSpacing.right
         spacing: 16
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: backToWelcomeViewButton
 
             Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
             Layout.leftMargin: 8
 
-            //preferredSize: 24
             mirror: UtilsAdapter.isRTL
 
             source: JamiResources.back_24dp_svg
@@ -106,10 +107,11 @@ Rectangle {
 
             color: JamiTheme.transparentColor
 
-            ColumnLayout {
+            ColumnLayout { QWKSetParentHitTestVisible {}
                 id: userNameOrIdColumnLayout
+                objectName: "userNameOrIdColumnLayout"
 
-                anchors.fill: parent
+                height: parent.height
 
                 spacing: 0
 
@@ -144,63 +146,30 @@ Rectangle {
             }
         }
 
-        Searchbar {
-            id: rowSearchBar
-
-            reductionEnabled: true
-
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-            Layout.preferredHeight: 30
-            Layout.preferredWidth: 40 + (isOpen ? JamiTheme.searchbarSize : 0)
-            colorSearchBar: JamiTheme.backgroundColor
-
-            hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius
-
-            Behavior on Layout.preferredWidth  {
-                NumberAnimation {
-                    duration: 150
-                }
-            }
-
-            visible: root.swarmDetailsVisibility
-
-            onSearchBarTextChanged: function (text) {
-                MessagesAdapter.searchbarPrompt = text;
-            }
-
-            onSearchClicked: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel)
-
-            Shortcut {
-                sequence: "Ctrl+Shift+F"
-                context: Qt.ApplicationShortcut
-                enabled: rowSearchBar.visible
-                onActivated: {
-                    rowSearchBar.openSearchBar();
-                }
-            }
-        }
-
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: startAAudioCallButton
 
-            visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
+            visible: interactionButtonsVisibility &&
+                     (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
             source: JamiResources.place_audiocall_24dp_svg
             toolTipText: JamiStrings.placeAudioCall
 
             onClicked: CallAdapter.placeAudioOnlyCall()
         }
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: startAVideoCallButton
 
-            visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
+            visible: interactionButtonsVisibility &&
+                     CurrentAccount.videoEnabled_Video &&
+                     (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
             source: JamiResources.videocam_24dp_svg
             toolTipText: JamiStrings.placeVideoCall
 
             onClicked: CallAdapter.placeCall()
         }
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: addParticipantsButton
 
             checkable: true
@@ -212,7 +181,7 @@ Rectangle {
             onClicked: extrasPanel.switchToPanel(ChatView.AddMemberPanel)
         }
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: selectPluginButton
 
             visible: PluginAdapter.chatHandlersListCount && interactionButtonsVisibility
@@ -222,7 +191,7 @@ Rectangle {
             onClicked: pluginSelector()
         }
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: sendContactRequestButton
             objectName: "sendContactRequestButton"
 
@@ -230,16 +199,39 @@ Rectangle {
             source: JamiResources.add_people_24dp_svg
             toolTipText: JamiStrings.addToConversations
 
-            onClicked: CurrentConversation.isBanned ? MessagesAdapter.unbanConversation(CurrentConversation.id) : MessagesAdapter.sendConversationRequest()
+            onClicked: CurrentConversation.isBanned ?
+                           MessagesAdapter.unbanConversation(CurrentConversation.id) :
+                           MessagesAdapter.sendConversationRequest()
+        }
+
+        JamiPushButton { QWKSetParentHitTestVisible {}
+            id: searchMessagesButton
+            objectName: "searchMessagesButton"
+
+            checkable: true
+            checked: extrasPanel.isOpen(ChatView.MessagesResearchPanel)
+            visible: root.swarmDetailsVisibility
+            source:  JamiResources.ic_baseline_search_24dp_svg
+            toolTipText: JamiStrings.search
+
+            onClicked: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel)
+
+            Shortcut {
+                sequence: "Ctrl+Shift+F"
+                context: Qt.ApplicationShortcut
+                enabled: parent.visible
+                onActivated: extrasPanel.switchToPanel(ChatView.MessagesResearchPanel)
+            }
         }
 
-        JamiPushButton {
+        JamiPushButton { QWKSetParentHitTestVisible {}
             id: detailsButton
             objectName: "detailsButton"
 
             checkable: true
             checked: extrasPanel.isOpen(ChatView.SwarmDetailsPanel)
-            visible: interactionButtonsVisibility && (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request
+            visible: interactionButtonsVisibility &&
+                     (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request
             source: JamiResources.swarm_details_panel_svg
             toolTipText: JamiStrings.details
 
diff --git a/src/app/mainview/components/MainOverlay.qml b/src/app/mainview/components/MainOverlay.qml
index 07545b99ec32a60997cef5237f984ed0466c70f4..aabf85e5421c815c2e4437cb213d3ef776758511 100644
--- a/src/app/mainview/components/MainOverlay.qml
+++ b/src/app/mainview/components/MainOverlay.qml
@@ -127,9 +127,12 @@ Item {
         id: overlayUpperPartRect
 
         anchors.top: parent.top
-
-        width: parent.width
         height: 50
+        anchors.left: parent.left
+        anchors.right: parent.right
+        // QWK: spacing
+        anchors.leftMargin: qwkSystemButtonSpacing.left
+        anchors.rightMargin: qwkSystemButtonSpacing.right
 
         RowLayout {
             anchors.fill: parent
diff --git a/src/app/mainview/components/MessagesResearchPanel.qml b/src/app/mainview/components/MessagesResearchPanel.qml
index 5bb556e25ed5d1b5b90690f1a2151ad0f7860c2f..cbe1195e910ca65b4523472c27180573fe537b5b 100644
--- a/src/app/mainview/components/MessagesResearchPanel.qml
+++ b/src/app/mainview/components/MessagesResearchPanel.qml
@@ -26,13 +26,33 @@ import net.jami.Constants 1.1
 import "../../commoncomponents"
 import "../../settingsview/components"
 
-Rectangle {
+Page {
     id: root
 
-    color: JamiTheme.chatviewBgColor
+    header: Item {
+        height: 45
+        Searchbar {
+            onVisibleChanged: {
+                if (visible) {
+                    clearText();
+                    forceActiveFocus();
+                }
+            }
+            anchors.fill: parent
+            anchors.margins: 5
+            onSearchBarTextChanged: function (text) {
+                MessagesAdapter.searchbarPrompt = text;
+            }
+        }
+    }
+
+    background: Rectangle {
+        color: JamiTheme.backgroundColor
+    }
 
     ColumnLayout {
         anchors.fill: parent
+        anchors.topMargin: 10
 
         TabBar {
             id: researchTabBar
@@ -88,7 +108,7 @@ Rectangle {
         Rectangle {
             id: view
 
-            color: JamiTheme.chatviewBgColor
+            color: JamiTheme.backgroundColor
             Layout.fillWidth: true
             Layout.fillHeight: true
 
diff --git a/src/app/mainview/components/NewSwarmPage.qml b/src/app/mainview/components/NewSwarmPage.qml
index e1df3410ffefb08bb3bb21f3fb1c8a8099188e02..3549998089eb90fb2a0f184fd40d638e36a92b46 100644
--- a/src/app/mainview/components/NewSwarmPage.qml
+++ b/src/app/mainview/components/NewSwarmPage.qml
@@ -41,9 +41,11 @@ DualPaneView {
     splitViewStateKey: "Main"
     inhibits: ["ConversationView"]
 
-    leftPaneItem: viewCoordinator.getView("SidePanel")
+    leftPaneItem: viewCoordinator.getView("SidePanel", true)
     rightPaneItem: Rectangle {
         id: root
+        objectName: "NewSwarmLayout"
+
         color: JamiTheme.chatviewBgColor
 
         anchors.fill: parent
diff --git a/src/app/mainview/components/Searchbar.qml b/src/app/mainview/components/Searchbar.qml
index c48ee7b317edc59afee9bd14b6b022be88288e7a..5ee100890b0c3bc7c93c38f1ac27d7226f7be200 100644
--- a/src/app/mainview/components/Searchbar.qml
+++ b/src/app/mainview/components/Searchbar.qml
@@ -26,31 +26,19 @@ Rectangle {
 
     signal searchBarTextChanged(string text)
     signal returnPressedWhileSearching
-    signal searchClicked
 
-    property bool reductionEnabled: false
     property alias textContent: textArea.text
     property alias placeHolderText: textArea.placeholderText
-
     property real hoverButtonRadius: JamiTheme.chatViewHeaderButtonRadius
-
-    property var colorSearchBar: JamiTheme.secondaryBackgroundColor
-
     property string currentConversationId: CurrentConversation.id
 
-    property bool isOpen: reductionEnabled ? extrasPanel.isOpen(ChatView.MessagesResearchPanel) : true
-    onIsOpenChanged: {
-        if (isOpen)
-            textArea.forceActiveFocus();
-    }
-
     function clearText() {
         textArea.clear();
         textArea.forceActiveFocus();
     }
 
     radius: JamiTheme.primaryRadius
-    color: isOpen ? colorSearchBar : "transparent"
+    color: JamiTheme.secondaryBackgroundColor
 
     onFocusChanged: {
         if (focus) {
@@ -64,32 +52,14 @@ Rectangle {
         lineEditObj: textArea
     }
 
-    PushButton {
+    ResponsiveImage {
         id: startSearch
 
         anchors.verticalCenter: root.verticalCenter
         anchors.left: root.left
         anchors.leftMargin: 10
-        hoverEnabled: reductionEnabled
-        enabled: reductionEnabled
-        radius: hoverButtonRadius
-        hoveredColor: JamiTheme.hoveredButtonColor
         source: JamiResources.ic_baseline_search_24dp_svg
-        toolTipText: JamiStrings.search
-        normalColor: JamiTheme.primaryBackgroundColor
-        imageColor: {
-            if (reductionEnabled) {
-                if (hovered) {
-                    JamiTheme.chatviewButtonColor;
-                } else {
-                    JamiTheme.chatViewFooterImgColor;
-                }
-            } else {
-                JamiTheme.chatviewButtonColor;
-            }
-        }
-
-        onClicked: root.searchClicked()
+        color: JamiTheme.chatviewButtonColor
     }
 
     Rectangle {
@@ -100,21 +70,7 @@ Rectangle {
         anchors.right: root.right
         anchors.verticalCenter: root.verticalCenter
         color: "transparent"
-
-        opacity: isOpen
-        visible: opacity
-        Behavior on opacity  {
-            NumberAnimation {
-                duration: 150
-            }
-        }
-
-        width: isOpen ? JamiTheme.searchbarSize : 0
-        Behavior on width  {
-            NumberAnimation {
-                duration: 150
-            }
-        }
+        width: JamiTheme.searchbarSize
 
         TextField {
             id: textArea
diff --git a/src/app/mainview/components/SidePanel.qml b/src/app/mainview/components/SidePanel.qml
index a3dafb77a4bf87e6343121c06ec3375b55f69d06..0037f7ebd270139dcdc643e6dbca4e3d0aafb8e3 100644
--- a/src/app/mainview/components/SidePanel.qml
+++ b/src/app/mainview/components/SidePanel.qml
@@ -173,7 +173,7 @@ SidePanelBase {
             color: JamiTheme.backgroundColor
         }
 
-        header: AccountComboBox {
+        header: AccountComboBox { QWKSetParentHitTestVisible {}
             id: accountComboBox
             Shortcut {
                 sequence: "Ctrl+J"
diff --git a/src/app/mainview/components/WelcomePage.qml b/src/app/mainview/components/WelcomePage.qml
index b62b4eaaec3d88276bbdd79e90e6affca6e9fd9d..352b400408c9af6d629d74a2b5ad4b6526359b92 100644
--- a/src/app/mainview/components/WelcomePage.qml
+++ b/src/app/mainview/components/WelcomePage.qml
@@ -35,7 +35,7 @@ ListSelectionView {
     color: JamiTheme.secondaryBackgroundColor
 
     onPresented: LRCInstance.deselectConversation()
-    leftPaneItem: viewCoordinator.getView("SidePanel")
+    leftPaneItem: viewCoordinator.getView("SidePanel", true)
 
     property variant uiCustomization: CurrentAccount.uiCustomization
 
@@ -120,6 +120,8 @@ ListSelectionView {
 
     rightPaneItem: JamiFlickable {
         id: root
+        objectName: "WelcomeLayout"
+
         anchors.fill: parent
         property int thresholdSize: 700
         property int thresholdHeight: 570
diff --git a/src/app/settingsview/SettingsSidePanel.qml b/src/app/settingsview/SettingsSidePanel.qml
index fd26c07b0c40c1bbdfb445f4f31f7eccc360f6fc..1f5cf88e3702220c236bf2a8bae7e8d41304e4ab 100644
--- a/src/app/settingsview/SettingsSidePanel.qml
+++ b/src/app/settingsview/SettingsSidePanel.qml
@@ -263,7 +263,13 @@ SidePanelBase {
 
         background: null
 
-        header: AccountComboBox {
+        header: AccountComboBox { QWKSetParentHitTestVisible {}
+            id: accountComboBox
+            Shortcut {
+                sequence: "Ctrl+J"
+                context: Qt.ApplicationShortcut
+                onActivated: accountComboBox.togglePopup()
+            }
         }
 
         ListView {
diff --git a/src/app/settingsview/SettingsView.qml b/src/app/settingsview/SettingsView.qml
index e6a6ef8483295e1efbdce5db067a6b22496470c5..4b2225e97e647b63076f199ac8ca37672b1c09af 100644
--- a/src/app/settingsview/SettingsView.qml
+++ b/src/app/settingsview/SettingsView.qml
@@ -56,7 +56,7 @@ ListSelectionView {
     splitViewStateKey: "Main"
     inhibits: ["ConversationView"]
 
-    leftPaneItem: viewCoordinator.getView("SettingsSidePanel")
+    leftPaneItem: viewCoordinator.getView("SettingsSidePanel", true)
 
     Component.onCompleted: {
         leftPaneItem.updateModel();
@@ -76,7 +76,7 @@ ListSelectionView {
         // Currently needed when changing the show link preview setting.
         CurrentConversation.reloadInteractions();
         if (UtilsAdapter.getAccountListSize() === 0) {
-            viewCoordinator.requestAppWindowWizardView();
+            viewCoordinator.present("WizardView");
         } else {
             AccountAdapter.changeAccount(0);
         }
@@ -85,8 +85,7 @@ ListSelectionView {
     property int selectedMenu: index
 
     rightPaneItem: StackView {
-        id: settingsView
-        objectName: "settingsView"
+        objectName: "SettingsLayout"
 
         property var currentIndex: selectedMenu !== -1 ? selectedMenu : 0
         anchors.fill: parent
diff --git a/src/app/settingsview/components/AppearanceSettingsPage.qml b/src/app/settingsview/components/AppearanceSettingsPage.qml
index c341cbf485cfb97b2924ca102fbbedf2ca8fde7f..198b70214bf173239a9974e886338764fe46afc2 100644
--- a/src/app/settingsview/components/AppearanceSettingsPage.qml
+++ b/src/app/settingsview/components/AppearanceSettingsPage.qml
@@ -237,6 +237,15 @@ SettingsPageBase {
             }
         }
 
+        ToggleSwitch {
+            id: useNativeWindowFrameCheckBox
+            Layout.fillWidth: true
+
+            checked: !UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
+            labelText: JamiStrings.useNativeWindowFrame
+            onSwitchToggled: UtilsAdapter.setAppValue(Settings.Key.UseFramelessWindow, !checked)
+        }
+
         MaterialButton {
             id: defaultSettings
 
diff --git a/src/app/settingsview/components/SettingsHeader.qml b/src/app/settingsview/components/SettingsHeader.qml
index ec686c80322e830a56254db33fa0aaab93274388..9b203db2c5f223e4647fd928fd012c55f933fdb3 100644
--- a/src/app/settingsview/components/SettingsHeader.qml
+++ b/src/app/settingsview/components/SettingsHeader.qml
@@ -29,7 +29,7 @@ RowLayout {
     signal backArrowClicked
     spacing: 10
 
-    BackButton {
+    BackButton { QWKSetParentHitTestVisible {}
         id: backToSettingsMenuButton
 
         Layout.preferredWidth: JamiTheme.preferredFieldHeight
diff --git a/src/app/settingsview/components/SettingsPageBase.qml b/src/app/settingsview/components/SettingsPageBase.qml
index 37af8fcf2ea242328ff2bdbbd6e459e1dc5b1dc9..75c4386602eec28408b4e34a3debdaff0c262064 100644
--- a/src/app/settingsview/components/SettingsPageBase.qml
+++ b/src/app/settingsview/components/SettingsPageBase.qml
@@ -25,11 +25,13 @@ import "../../commoncomponents"
 
 JamiSplitView {
     id: root
+
     required property Item flickableContent
     property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize)
     property alias title: settingsPage.title
     property color backgroundColor: JamiTheme.secondaryBackgroundColor
     property alias pageContainer: settingsPage
+
     Page {
         id: settingsPage
         SplitView.maximumWidth: root.width
diff --git a/src/app/settingsview/components/SystemSettingsPage.qml b/src/app/settingsview/components/SystemSettingsPage.qml
index a598eed5c3febdb924fd757a5a95c60f6d3b4bb7..3ea004118f2b4e6b042a8e4b580f86b30250603f 100644
--- a/src/app/settingsview/components/SystemSettingsPage.qml
+++ b/src/app/settingsview/components/SystemSettingsPage.qml
@@ -108,7 +108,7 @@ SettingsPageBase {
             }
 
             ToggleSwitch {
-                id: applicationOnStartUpCheckBox
+                id: runOnStartUpCheckBox
                 Layout.fillWidth: true
 
                 checked: UtilsAdapter.checkStartupLink()
diff --git a/src/app/utilsadapter.cpp b/src/app/utilsadapter.cpp
index a9689eb729107a81043841bcfd71b1af23a462c4..d157586874810311a2c2356c64339c98141787e7 100644
--- a/src/app/utilsadapter.cpp
+++ b/src/app/utilsadapter.cpp
@@ -94,6 +94,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
         Q_EMIT chatviewPositionChanged();
     else if (key == Settings::Key::AppTheme)
         Q_EMIT appThemeChanged();
+    else if (key == Settings::Key::UseFramelessWindow)
+        Q_EMIT useFramelessWindowChanged();
 #ifndef APPSTORE
     // Any donation campaign-related keys can trigger a donation campaign check
     else if (key == Settings::Key::IsDonationVisible
diff --git a/src/app/utilsadapter.h b/src/app/utilsadapter.h
index 3361a9f712462115f378cfac451c768e212edecd..2f1b6abb7ecae093bb4511afd65ae839996b22e0 100644
--- a/src/app/utilsadapter.h
+++ b/src/app/utilsadapter.h
@@ -172,6 +172,7 @@ Q_SIGNALS:
     void showExperimentalCallSwarm();
     void changeLanguage();
     void donationCampaignSettingsChanged();
+    void useFramelessWindowChanged();
 
 private:
     QClipboard* clipboard_;
diff --git a/src/app/webengine/map/MapPosition.qml b/src/app/webengine/map/MapPosition.qml
index a5a5fe9ec004b3752b772b2516c0ebcffa8d0ac3..f349321bf7bf0b3de938b263890d69e5b8c47e2e 100644
--- a/src/app/webengine/map/MapPosition.qml
+++ b/src/app/webengine/map/MapPosition.qml
@@ -107,7 +107,7 @@ Item {
             property real windowSize: windowPreferedSize > JamiTheme.minimumMapWidth ? windowPreferedSize : JamiTheme.minimumMapWidth
             property real windowPreferedSize: root.maxWidth > root.maxHeight ? root.maxHeight / 3 : root.maxWidth / 3
             property real xPos: 0
-            property real yPos: root.isUnpin ? 0 : JamiTheme.chatViewHeaderPreferredHeight
+            property real yPos: root.isUnpin ? 0 : JamiTheme.qwkTitleBarHeight
 
             states: [
                 State {
@@ -125,7 +125,7 @@ Item {
                         target: mapObject
                         parent: parentPin
                         x: xPos
-                        y: JamiTheme.chatViewHeaderPreferredHeight
+                        y: JamiTheme.qwkTitleBarHeight
                     }
                 }
             ]
diff --git a/src/app/wizardview/WizardView.qml b/src/app/wizardview/WizardView.qml
index e4e0fe8d005a11e76542609d78d7ba73b25c78a7..2a1f9b738c0f33fc972699d84ef89d83888ff241 100644
--- a/src/app/wizardview/WizardView.qml
+++ b/src/app/wizardview/WizardView.qml
@@ -55,10 +55,7 @@ BaseView {
 
         function onCloseWizardView() {
             root.dismiss();
-            viewCoordinator.preload("SidePanel");
-            viewCoordinator.preload("SettingsSidePanel");
             viewCoordinator.present("WelcomePage");
-            viewCoordinator.preload("ConversationView");
         }
     }
 
diff --git a/src/app/wizardview/components/CreateAccountPage.qml b/src/app/wizardview/components/CreateAccountPage.qml
index 9dd84d3f6366d89a5c806615c009fdecb31f5028..7628f55e55cd4ce266e82067c42537cf7d7d3d07 100644
--- a/src/app/wizardview/components/CreateAccountPage.qml
+++ b/src/app/wizardview/components/CreateAccountPage.qml
@@ -402,7 +402,7 @@ Rectangle {
         }
     }
 
-    JamiPushButton {
+    JamiPushButton { QWKSetParentHitTestVisible {}
         id: backButton
 
         objectName: "createAccountPageBackButton"
diff --git a/src/app/wizardview/components/WelcomePage.qml b/src/app/wizardview/components/WelcomePage.qml
index 126c42e16395349e1762ba35587fabddf89fd115..6595c79df18e5915d5707d02d84d5a47bc63fd95 100644
--- a/src/app/wizardview/components/WelcomePage.qml
+++ b/src/app/wizardview/components/WelcomePage.qml
@@ -382,7 +382,7 @@ Rectangle {
         }
     }
 
-    JamiPushButton {
+    JamiPushButton { QWKSetParentHitTestVisible {}
         id: backButton
 
         objectName: "welcomePageBackButton"
diff --git a/tests/qml/main.cpp b/tests/qml/main.cpp
index 43bb414841045a6e3d29766f72f6dfa88f99d759..a604a5c729277fb55279885777619209d0a17e95 100644
--- a/tests/qml/main.cpp
+++ b/tests/qml/main.cpp
@@ -90,7 +90,7 @@ public Q_SLOTS:
 
         // Create 2 Account
         QSignalSpy accountStatusChangedSpy(&lrcInstance_->accountModel(),
-                                        &AccountModel::accountStatusChanged);
+                                           &AccountModel::accountStatusChanged);
 
         QSignalSpy accountAddedSpy(&lrcInstance_->accountModel(), &AccountModel::accountAdded);
         aliceId = lrcInstance_->accountModel().createNewAccount(profile::Type::JAMI, "Alice");
diff --git a/tests/qml/resources.qrc b/tests/qml/resources.qrc
index ca3275042cb5bc69550dcc31d0ad8177ead56f69..bc3d31a38c7e4ee261ed42b498742eed3e8684df 100644
--- a/tests/qml/resources.qrc
+++ b/tests/qml/resources.qrc
@@ -3,8 +3,22 @@
         <file>src/tst_WizardView.qml</file>
         <file>src/tst_NewSwarmPage.qml</file>
         <file>src/tst_MessageOptions.qml</file>
+        <file>src/tst_OngoingCallPage.qml</file>
+        <file>src/tst_MainView.qml</file>
+        <file>src/tst_MaterialTextEdit.qml</file>
+        <file>src/tst_ChatView.qml</file>
+        <file>src/tst_ChatViewFooter.qml</file>
+        <file>src/tst_CachedImage.qml</file>
+        <file>src/tst_CallMessageDelegate.qml</file>
+        <file>src/tst_DataTransferMessageDelegate.qml</file>
+        <file>src/tst_FilesToSendContainer.qml</file>
+        <file>src/tst_PresenceIndicator.qml</file>
+        <file>src/tst_RecordBox.qml</file>
+        <file>src/tst_SettingsSidePanel.qml</file>
+        <file>src/tst_WelcomePage.qml</file>
         <file>src/resources/gif_test.gif</file>
         <file>src/resources/gz_test.gz</file>
         <file>src/resources/png_test.png</file>
+        <file>src/TestWrapper.qml</file>
     </qresource>
 </RCC>
diff --git a/tests/qml/src/TestWrapper.qml b/tests/qml/src/TestWrapper.qml
new file mode 100644
index 0000000000000000000000000000000000000000..3d22ce6c3bef693dc149ed082872ce8240beba04
--- /dev/null
+++ b/tests/qml/src/TestWrapper.qml
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import QtQuick
+import QtQuick.Controls
+
+import "../../../src/app/"
+
+// The purpose of this component is to fake the ApplicationWindow and prevent
+// each UUT from having to manage its own top level app management objects
+// (currently ViewManager, ViewCoordinator, and ApplicationWindow).
+Item {
+    // This will be our UUT.
+    required default property var uut
+
+    // These are our fake app management objects. The caveat is that they
+    // must be maintained in sync with the actual objects in the app for now.
+    // The benefit, is that this should be the single place where we need to
+    // sync them.
+    property ViewManager viewManager: ViewManager {}
+    property ViewCoordinator viewCoordinator: ViewCoordinator {}
+    property ApplicationWindow appWindow: ApplicationWindow {
+        property bool useFrameless: false
+    }
+}
diff --git a/tests/qml/src/tst_NewSwarmPage.qml b/tests/qml/src/tst_NewSwarmPage.qml
index 3551d822f0f45ff62bd039ecb09a5f23c2077022..2cebc7dc2000a3f4eb3044696fd2a9495bfae3f5 100644
--- a/tests/qml/src/tst_NewSwarmPage.qml
+++ b/tests/qml/src/tst_NewSwarmPage.qml
@@ -39,9 +39,7 @@ ColumnLayout {
         id: uut
 
         property ViewManager viewManager: ViewManager {}
-        property ViewCoordinator viewCoordinator: ViewCoordinator {
-            viewManager: uut.viewManager
-        }
+        property ViewCoordinator viewCoordinator: ViewCoordinator {}
 
         Layout.alignment: Qt.AlignHCenter
         Layout.preferredWidth: root.width
diff --git a/tests/qml/src/tst_OngoingCallPage.qml b/tests/qml/src/tst_OngoingCallPage.qml
index f1a57532eb237be1b30804725b1b20091d25c68d..62f9a3754f9a93a35c77b2bca16cd69382603797 100644
--- a/tests/qml/src/tst_OngoingCallPage.qml
+++ b/tests/qml/src/tst_OngoingCallPage.qml
@@ -32,11 +32,8 @@ OngoingCallPage {
     height: 600
 
     property QtObject appWindow
-    property ViewManager viewManager: ViewManager {
-    }
-    property ViewCoordinator viewCoordinator: ViewCoordinator {
-        viewManager: uut.viewManager
-    }
+    property ViewManager viewManager: ViewManager {}
+    property ViewCoordinator viewCoordinator: ViewCoordinator {}
 
     TestCase {
         name: "Check basic visibility of action bar during a call"
diff --git a/tests/qml/src/tst_WelcomePage.qml b/tests/qml/src/tst_WelcomePage.qml
index f4b0908a014bdff9aac6b759b65142a2157ef219..d7cc95883cda98273236db57f0e90c7e0d52ce19 100644
--- a/tests/qml/src/tst_WelcomePage.qml
+++ b/tests/qml/src/tst_WelcomePage.qml
@@ -16,40 +16,26 @@
  */
 
 import QtQuick
+import QtQuick.Controls
+
 import QtTest
 
 import "../../../src/app/"
 import "../../../src/app/mainview/components"
 
-WelcomePage {
-    id: uut
-
-    width: 800
-    height: 600
-
-    // The appWindow, viewManager and viewCoordinator properties
-    // are required in order for the "aboutJami" button to work.
-    property Item appWindow: uut
-    property ViewManager viewManager: ViewManager {
-    }
-    property ViewCoordinator viewCoordinator: ViewCoordinator {
-        viewManager: uut.viewManager
-    }
-
-    TestCase {
-        name: "Open 'About Jami' popup"
-
-        function test_openAboutPopup() {
-            compare(viewManager.viewCount(), 0)
-
-            var aboutJamiButton = findChild(uut, "aboutJami")
-            aboutJamiButton.clicked()
+TestWrapper {
+    uut: WelcomePage {
+        TestCase {
+            name: "Open 'About Jami' popup"
 
-            compare(viewManager.viewCount(), 1)
+            function test_openAboutPopup() {
+                var aboutJamiButton = findChild(uut, "aboutJami")
+                aboutJamiButton.clicked()
 
-            var aboutJamiPopup = viewManager.getView("AboutPopUp")
-            verify(aboutJamiPopup !== null)
-            compare(aboutJamiPopup.visible, true)
+                var aboutJamiPopup = viewManager.getView("AboutPopUp")
+                verify(aboutJamiPopup !== null, "About Jami popup should be created")
+                compare(aboutJamiPopup.visible, true, "About Jami popup should be visible")
+            }
         }
     }
-}
\ No newline at end of file
+}