diff --git a/.gitignore b/.gitignore
index 8cfcb49b84b4ee0548da301001095035bab41d74..7a0b17171673cd37d632f38d4c4d9102d573fc7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,7 +23,7 @@ build-local/
 *.vcxproj
 *.vcxproj.filters
 *qmlcache.qrc
-
+qml_without_webengine.qrc
 .deploy.stamp
 
 # auto-gen files
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8f12eeb4ab2c88c8655c14d279cfa07446792cbf..ed4ecddc27f14d31fa1d99e3d9fd78e19af67511 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,12 +42,22 @@ set(QT_MODULES
     Concurrent
     Core
     Core5Compat
-    WebEngineCore
-    WebEngineQuick
-    WebChannel
-    WebEngineWidgets
     Multimedia
 )
+
+if(NOT DEFINED WITH_WEBENGINE)
+    set(WITH_WEBENGINE true)
+endif()
+
+if(WITH_WEBENGINE)
+    list(APPEND QT_MODULES
+        WebEngineCore
+        WebEngineQuick
+        WebChannel
+        WebEngineWidgets
+    )
+endif()
+
 find_package(Qt6 COMPONENTS ${QT_MODULES} REQUIRED)
 foreach(MODULE ${QT_MODULES})
   list(APPEND QT_LIBS "Qt::${MODULE}")
@@ -57,8 +67,23 @@ set(SRC_DIR ${PROJECT_SOURCE_DIR}/src)
 set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS})
 set(CMAKE_INSTALL_RPATH_USE_LINK_PATH true)
 
+if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0")
+    include(FindPython3)
+    find_package (Python3 COMPONENTS Interpreter)
+    set(PYTHON_EXEC ${Python3_EXECUTABLE})
+else()
+    include(FindPythonInterp)
+    set(PYTHON_EXEC ${PYTHON_EXECUTABLE})
+endif()
+
 set(QML_RESOURCES ${PROJECT_SOURCE_DIR}/resources.qrc)
-set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml.qrc)
+if(WITH_WEBENGINE)
+    set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml.qrc)
+else()
+    execute_process(COMMAND ${PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-qrc-without-webengine.py
+        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+    set(QML_RESOURCES_QML ${PROJECT_SOURCE_DIR}/qml_without_webengine.qrc)
+endif()
 
 if (APPLE)
     include(FetchContent)
@@ -78,14 +103,6 @@ file(GLOB_RECURSE
     RES_FILES CONFIGURE_DEPENDS
     ${PROJECT_SOURCE_DIR}/resources/*
 )
-if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0")
-    include(FindPython3)
-    find_package (Python3 COMPONENTS Interpreter)
-    set(PYTHON_EXEC ${Python3_EXECUTABLE})
-else()
-    include(FindPythonInterp)
-    set(PYTHON_EXEC ${PYTHON_EXECUTABLE})
-endif()
 execute_process(
     COMMAND ${PYTHON_EXEC} ${PROJECT_SOURCE_DIR}/gen-resources.py
     WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
@@ -137,7 +154,6 @@ set(COMMON_SOURCES
     ${SRC_DIR}/currentconversation.cpp
     ${SRC_DIR}/currentaccount.cpp
     ${SRC_DIR}/videodevices.cpp
-    ${SRC_DIR}/previewengine.cpp
     ${SRC_DIR}/videoprovider.cpp
     ${SRC_DIR}/callparticipantsmodel.cpp
 )
@@ -194,11 +210,19 @@ set(COMMON_HEADERS
     ${SRC_DIR}/currentconversation.h
     ${SRC_DIR}/currentaccount.h
     ${SRC_DIR}/videodevices.h
-    ${SRC_DIR}/previewengine.h
     ${SRC_DIR}/videoprovider.h
     ${SRC_DIR}/callparticipantsmodel.h
 )
 
+if(WITH_WEBENGINE)
+    list(APPEND COMMON_SOURCES
+                ${SRC_DIR}/previewengine.cpp)
+    add_definitions(-DWITH_WEBENGINE)
+else()
+    list(APPEND COMMON_SOURCES
+                ${SRC_DIR}/nowebengine/previewengine.cpp)
+endif()
+
 # For libavutil/avframe.
 set(LIBJAMI_CONTRIB_DIR "${PROJECT_SOURCE_DIR}/../daemon/contrib")
 find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h
diff --git a/qml.qrc b/qml.qrc
index 81ef94d691d88421dda336c99b0ae50e7fe3ce59..1e587633c1dcba6e73ce62790d546e10d6b22577 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -30,6 +30,7 @@
         <file>src/commoncomponents/PresenceIndicator.qml</file>
         <file>src/commoncomponents/DaemonReconnectPopup.qml</file>
         <file>src/commoncomponents/SpinningAnimation.qml</file>
+        <file>src/commoncomponents/MediaPreviewBase.qml</file>
         <file>src/settingsview/SettingsView.qml</file>
         <file>src/settingsview/components/ChatviewSettings.qml</file>
         <file>src/settingsview/components/FileTransferSettings.qml</file>
diff --git a/scripts/gen-qrc-without-webengine.py b/scripts/gen-qrc-without-webengine.py
new file mode 100755
index 0000000000000000000000000000000000000000..7cce660a83b5073117f5da507d9973b00a9f29c7
--- /dev/null
+++ b/scripts/gen-qrc-without-webengine.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2022 Savoir-faire Linux Inc.
+#
+# Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+#
+# 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.
+
+with open('qml_without_webengine.qrc', 'w') as outfile:
+  with open('qml.qrc', 'r') as infile:
+    line = infile.readline()
+    while line:
+      if 'EmojiPicker.qml' in line:
+        outfile.write('\t<file>src/nowebengine/EmojiPicker.qml</file>\n')
+      elif 'MediaPreviewBase.qml' in line:
+        outfile.write('\t<file>src/nowebengine/MediaPreviewBase.qml</file>\n')
+      else:
+        outfile.write(line)
+      line = infile.readline()
diff --git a/src/commoncomponents/DataTransferMessageDelegate.qml b/src/commoncomponents/DataTransferMessageDelegate.qml
index ca5913efc4672aae0e238604602479009b36ac82..c451e730d1ba5fdb01a00d455b6a45deb27f4aac 100644
--- a/src/commoncomponents/DataTransferMessageDelegate.qml
+++ b/src/commoncomponents/DataTransferMessageDelegate.qml
@@ -22,7 +22,6 @@ import QtQuick
 import QtQuick.Controls
 import QtQuick.Layouts
 import Qt5Compat.GraphicalEffects
-import QtWebEngine
 
 import net.jami.Models 1.1
 import net.jami.Constants 1.1
@@ -41,7 +40,7 @@ Loader {
     sourceComponent: {
         if (Status === Interaction.Status.TRANSFER_FINISHED) {
             mediaInfo = MessagesAdapter.getMediaInfo(Body)
-            if (Object.keys(mediaInfo).length !== 0)
+            if (Object.keys(mediaInfo).length !== 0 && WITH_WEBENGINE)
                 return localMediaMsgComp
         }
         return dataTransferMsgComp
@@ -264,48 +263,10 @@ Loader {
                     }
                     Component {
                         id: avComp
-                        WebEngineView {
-                            id: wev
-                            anchors.right: isOutgoing ? parent.right : undefined
-                            readonly property real minSize: 192
-                            readonly property real maxSize: 256
-                            readonly property real aspectRatio: 1 / .75
-                            readonly property real adjustedWidth: Math.min(maxSize,
-                                                                  Math.max(minSize,
-                                                                           innerContent.width - senderMargin))
-                            width: isFullScreen ? parent.width : adjustedWidth
-                            height: mediaInfo.isVideo ?
-                                        isFullScreen ?
-                                            parent.height :
-                                            Math.ceil(adjustedWidth / aspectRatio) :
-                                        54
-                            onContextMenuRequested: function(request) {
-                                request.accepted = true
-                            }
-                            settings.fullScreenSupportEnabled: mediaInfo.isVideo
-                            settings.javascriptCanOpenWindows: false
-                            Component.onCompleted: loadHtml(mediaInfo.html, 'file://')
-                            layer.enabled: !isFullScreen
-                            layer.effect: OpacityMask {
-                                maskSource: MessageBubble {
-                                    out: isOutgoing
-                                    type: seq
-                                    width: wev.width
-                                    height: wev.height
-                                    radius: msgRadius
-                                }
-                            }
-                            onFullScreenRequested: function(request) {
-                                if (request.toggleOn) {
-                                    layoutManager.pushFullScreenItem(
-                                                this,
-                                                localMediaCompLoader,
-                                                null,
-                                                function() { wev.fullScreenCancelled() })
-                                } else if (!request.toggleOn) {
-                                    layoutManager.removeFullScreenItem(this)
-                                }
-                                request.accept()
+                        Loader {
+                            Component.onCompleted: {
+                                var qml = WITH_WEBENGINE ? "qrc:/src/commoncomponents/MediaPreviewBase.qml" : "qrc:/src/nowebengine/MediaPreviewBase.qml"
+                                setSource( qml, { isVideo: mediaInfo.isVideo, html:mediaInfo.html } )
                             }
                         }
                     }
diff --git a/src/commoncomponents/MediaPreviewBase.qml b/src/commoncomponents/MediaPreviewBase.qml
new file mode 100644
index 0000000000000000000000000000000000000000..1192638c66026ccc5d5b676f530815904163903a
--- /dev/null
+++ b/src/commoncomponents/MediaPreviewBase.qml
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
+ * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ * 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 QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+import QtWebEngine
+
+import net.jami.Models 1.1
+import net.jami.Constants 1.1
+import net.jami.Adapters 1.1
+
+WebEngineView {
+    id: wev
+    property bool isVideo
+    property string html
+    readonly property real minSize: 192
+    readonly property real maxSize: 256
+    readonly property real aspectRatio: 1 / .75
+    readonly property real adjustedWidth: Math.min(maxSize,
+                                                   Math.max(minSize,
+                                                            innerContent.width - senderMargin))
+    anchors.right: isOutgoing ? parent.right : undefined
+    width: isFullScreen ? parent.width : adjustedWidth
+    height: isVideo ?
+                isFullScreen ?
+                    parent.height :
+                    Math.ceil(adjustedWidth / aspectRatio) :
+    54
+    onContextMenuRequested: function(request) {
+        request.accepted = true
+    }
+    settings.fullScreenSupportEnabled: isVideo
+    settings.javascriptCanOpenWindows: false
+    Component.onCompleted: loadHtml(html, 'file://')
+    layer.enabled: !isFullScreen
+    layer.effect: OpacityMask {
+        maskSource: MessageBubble {
+            out: isOutgoing
+            type: seq
+            width: wev.width
+            height: wev.height
+            radius: msgRadius
+        }
+    }
+    onFullScreenRequested: function(request) {
+        if (request.toggleOn) {
+            layoutManager.pushFullScreenItem(
+                        this,
+                        localMediaCompLoader,
+                        null,
+                        function() { wev.fullScreenCancelled() })
+        } else if (!request.toggleOn) {
+            layoutManager.removeFullScreenItem(this)
+        }
+        request.accept()
+    }
+}
diff --git a/src/main.cpp b/src/main.cpp
index 1d6aae0d5efdfced15d2351ef10ef9fc1e8f6c91..f886cdb98f04d8d3fe82a4d7e36da66834e995fc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -24,8 +24,11 @@
 
 #include <QCryptographicHash>
 #include <QApplication>
+#include <QtQuick>
+#ifdef WITH_WEBENGINE
 #include <QtWebEngineCore>
 #include <QtWebEngineQuick>
+#endif
 #if defined(HAS_VULKAN) && !defined(Q_OS_LINUX)
 #include <QVulkanInstance>
 #endif
@@ -56,9 +59,11 @@ parseInputArgument(int& argc, char* argv[], QList<char*> argsToParse)
     return newArgv;
 }
 
+#ifdef WITH_WEBENGINE
 // Qt WebEngine Chromium Flags
 static char disableWebSecurity[] {"--disable-web-security"};
 static char singleProcess[] {"--single-process"};
+#endif
 
 int
 main(int argc, char* argv[])
@@ -88,8 +93,10 @@ main(int argc, char* argv[])
      */
     unsetenv("QT_STYLE_OVERRIDE");
 #endif
+#ifdef WITH_WEBENGINE
     qtWebEngineChromiumFlags << disableWebSecurity;
     qtWebEngineChromiumFlags << singleProcess;
+#endif
 
     QApplication::setApplicationName("Jami");
     QApplication::setOrganizationDomain("jami.net");
diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp
index 104671ceac7fe14cdc7fea06c0d1c7572539350b..7528966cc6772e8f479e83c6413817fa6e8e11ed 100644
--- a/src/mainapplication.cpp
+++ b/src/mainapplication.cpp
@@ -193,6 +193,11 @@ MainApplication::init()
     auto startMinimizedSetting = settingsManager_->getValue(Settings::Key::StartMinimized).toBool();
     // The presence of start URI should override the startMinimized setting for this instance.
     set_startMinimized(startMinimizedSetting && runOptions_[Option::StartUri].isNull());
+#ifdef WITH_WEBENGINE
+    engine_.get()->rootContext()->setContextProperty("WITH_WEBENGINE", QVariant(true));
+#else
+    engine_.get()->rootContext()->setContextProperty("WITH_WEBENGINE", QVariant(false));
+#endif
 
     initQmlLayer();
 
diff --git a/src/mainview/components/ChatViewFooter.qml b/src/mainview/components/ChatViewFooter.qml
index 7085b5dd4a1314dad82ca5698e91696823abce3a..5a241b180d139ffcb323b797eff2bef003eceded 100644
--- a/src/mainview/components/ChatViewFooter.qml
+++ b/src/mainview/components/ChatViewFooter.qml
@@ -24,7 +24,6 @@ import net.jami.Constants 1.1
 import net.jami.Adapters 1.1
 
 import "../../commoncomponents"
-import "../../commoncomponents/emojipicker"
 
 Rectangle {
     id: root
@@ -90,10 +89,19 @@ Rectangle {
         visible: false
     }
 
-    EmojiPicker {
-        id: emojiPicker
+    Loader {
+        id: empjiLoader
+        source: WITH_WEBENGINE ? "qrc:/src/commoncomponents/emojipicker/EmojiPicker.qml" : "qrc:/src/nowebengine/EmojiPicker.qml"
 
-        onEmojiIsPicked: messageBar.textAreaObj.insertText(content)
+        function openEmojiPicker() {
+            item.openEmojiPicker()
+        }
+        Connections {
+            target: empjiLoader.item
+            function onEmojiIsPicked(content) {
+                messageBar.textAreaObj.insertText(content)
+            }
+        }
     }
 
     JamiFileDialog {
@@ -126,20 +134,20 @@ Rectangle {
             onEmojiButtonClicked: {
                 JamiQmlUtils.updateMessageBarButtonsPoints()
 
-                emojiPicker.parent = JamiQmlUtils.mainViewRectObj
+                empjiLoader.parent = JamiQmlUtils.mainViewRectObj
 
-                emojiPicker.x = Qt.binding(function() {
+                empjiLoader.x = Qt.binding(function() {
                     var buttonX = JamiQmlUtils.emojiPickerButtonInMainViewPoint.x +
                             JamiQmlUtils.emojiPickerButtonObj.width
-                    return buttonX - emojiPicker.width
+                    return buttonX - empjiLoader.width
                 })
-                emojiPicker.y = Qt.binding(function() {
+                empjiLoader.y = Qt.binding(function() {
                     var buttonY = JamiQmlUtils.audioRecordMessageButtonInMainViewPoint.y
-                    return buttonY - emojiPicker.height - messageBar.marginSize
+                    return buttonY - empjiLoader.height - messageBar.marginSize
                             - JamiTheme.chatViewHairLineSize
                 })
 
-                emojiPicker.openEmojiPicker()
+                empjiLoader.openEmojiPicker()
             }
             onSendFileButtonClicked: jamiFileDialog.open()
             onSendMessageButtonClicked: {
diff --git a/src/mainview/components/MessageBar.qml b/src/mainview/components/MessageBar.qml
index 6a65e197d10a3cea1164d9130f529b2f2c01658c..c9d5ac8931dbc042e3aac68fa19ef613743b0aad 100644
--- a/src/mainview/components/MessageBar.qml
+++ b/src/mainview/components/MessageBar.qml
@@ -157,6 +157,7 @@ ColumnLayout {
 
         PushButton {
             id: emojiButton
+            visible: WITH_WEBENGINE
 
             Layout.alignment: Qt.AlignVCenter
             Layout.rightMargin: sendMessageButton.visible ? 0 : marginSize
diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp
index 948d508c28f65f29f5d7754bd90d3790519271b2..ddea15a6283345719a744349908197d0a3a1a8d6 100644
--- a/src/messagesadapter.cpp
+++ b/src/messagesadapter.cpp
@@ -61,10 +61,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
     });
 
     connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady);
-    connect(previewEngine_,
-            &PreviewEngine::linkifyReady,
-            this,
-            &MessagesAdapter::onMessageLinkified);
+    connect(previewEngine_, &PreviewEngine::linkified, this, &MessagesAdapter::onMessageLinkified);
 }
 
 void
diff --git a/src/nowebengine/EmojiPicker.qml b/src/nowebengine/EmojiPicker.qml
new file mode 100644
index 0000000000000000000000000000000000000000..2fafa2cb63e5e7bf536d9b23d1daf6648a392323
--- /dev/null
+++ b/src/nowebengine/EmojiPicker.qml
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ * 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
+
+Rectangle {
+    id: root
+
+    signal emojiIsPicked(string content)
+    function openEmojiPicker() {}
+    function closeEmojiPicker() {}
+}
diff --git a/src/nowebengine/MediaPreviewBase.qml b/src/nowebengine/MediaPreviewBase.qml
new file mode 100644
index 0000000000000000000000000000000000000000..e7095f29574f2fa0d932a2dfffeda5a6636413e8
--- /dev/null
+++ b/src/nowebengine/MediaPreviewBase.qml
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ * 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 QtQuick.Layouts
+
+Rectangle {
+    property bool isVideo
+    property string html
+}
diff --git a/src/nowebengine/previewengine.cpp b/src/nowebengine/previewengine.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b6f165a1f3a7be91191aaaae913f4cf989c33cbc
--- /dev/null
+++ b/src/nowebengine/previewengine.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ * 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/>.
+ */
+
+#include "previewengine.h"
+
+struct PreviewEngine::Impl : public QObject
+{
+    Impl(PreviewEngine&)
+        : QObject(nullptr)
+    {}
+};
+
+PreviewEngine::PreviewEngine(QObject* parent)
+    : QObject(parent)
+    , pimpl_(std::make_unique<Impl>(*this))
+{}
+
+PreviewEngine::~PreviewEngine() {}
+
+void
+PreviewEngine::parseMessage(const QString&, const QString&, bool)
+{}
+
+void
+PreviewEngine::log(const QString&)
+{}
+
+#include "moc_previewengine.cpp"
+#include "previewengine.moc"
diff --git a/src/previewengine.cpp b/src/previewengine.cpp
index b5061c7314674e340042a1f7661c214c0d0d1896..84ae798854ca080361e03d8a3a3372772f5d71c7 100644
--- a/src/previewengine.cpp
+++ b/src/previewengine.cpp
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
  * Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
  * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
@@ -23,65 +23,78 @@
 #include <QWebEngineProfile>
 #include <QWebEngineSettings>
 
-PreviewEngine::PreviewEngine(QObject* parent)
-    : QWebEnginePage(parent)
-    , pimpl_(new PreviewEnginePrivate(this))
+#include <QtWebChannel>
+#include <QWebEnginePage>
+
+struct PreviewEngine::Impl : public QWebEnginePage
 {
-    QWebEngineProfile* profile = QWebEngineProfile::defaultProfile();
+public:
+    PreviewEngine& parent_;
+    QWebChannel* channel_;
 
-    QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
-    dataDir.cdUp();
-    auto cachePath = dataDir.absolutePath() + "/jami";
-    profile->setCachePath(cachePath);
-    profile->setPersistentStoragePath(cachePath);
-    profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
-    profile->setHttpCacheType(QWebEngineProfile::NoCache);
+    Impl(PreviewEngine& parent)
+        : QWebEnginePage((QObject*) nullptr)
+        , parent_(parent)
+    {
+        QWebEngineProfile* profile = QWebEngineProfile::defaultProfile();
 
-    settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
-    settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
-    settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
-    settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
-    settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
-    settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
+        QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
+        dataDir.cdUp();
+        auto cachePath = dataDir.absolutePath() + "/jami";
+        profile->setCachePath(cachePath);
+        profile->setPersistentStoragePath(cachePath);
+        profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
+        profile->setHttpCacheType(QWebEngineProfile::NoCache);
 
-    channel_ = new QWebChannel(this);
-    channel_->registerObject(QStringLiteral("jsbridge"), pimpl_);
+        settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+        settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::LinksIncludedInFocusChain, false);
+        settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
+        settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+        settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
+        settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
 
-    setWebChannel(channel_);
-    runJavaScript(Utils::QByteArrayFromFile(":/linkify.js"), QWebEngineScript::MainWorld);
-    runJavaScript(Utils::QByteArrayFromFile(":/linkify-string.js"), QWebEngineScript::MainWorld);
-    runJavaScript(Utils::QByteArrayFromFile(":/qwebchannel.js"), QWebEngineScript::MainWorld);
-    runJavaScript(Utils::QByteArrayFromFile(":/previewInfo.js"), QWebEngineScript::MainWorld);
-    runJavaScript(Utils::QByteArrayFromFile(":/misc/previewInterop.js"),
-                  QWebEngineScript::MainWorld);
-}
+        channel_ = new QWebChannel(this);
+        channel_->registerObject(QStringLiteral("jsbridge"), &parent_);
+
+        setWebChannel(channel_);
+        runJavaScript(Utils::QByteArrayFromFile(":/linkify.js"), QWebEngineScript::MainWorld);
+        runJavaScript(Utils::QByteArrayFromFile(":/linkify-string.js"), QWebEngineScript::MainWorld);
+        runJavaScript(Utils::QByteArrayFromFile(":/qwebchannel.js"), QWebEngineScript::MainWorld);
+        runJavaScript(Utils::QByteArrayFromFile(":/previewInfo.js"), QWebEngineScript::MainWorld);
+        runJavaScript(Utils::QByteArrayFromFile(":/misc/previewInterop.js"),
+                      QWebEngineScript::MainWorld);
+    }
+
+    void parseMessage(const QString& messageId, const QString& msg, bool showPreview)
+    {
+        runJavaScript(QString("parseMessage(`%1`, `%2`, %3)")
+                          .arg(messageId, msg, showPreview ? "true" : "false"));
+    }
+};
+
+PreviewEngine::PreviewEngine(QObject* parent)
+    : QObject(parent)
+    , pimpl_(std::make_unique<Impl>(*this))
+{}
+
+PreviewEngine::~PreviewEngine() {}
 
 void
 PreviewEngine::parseMessage(const QString& messageId, const QString& msg, bool showPreview)
 {
-    runJavaScript(
-        QString("parseMessage(`%1`, `%2`, %3)").arg(messageId, msg, showPreview ? "true" : "false"));
+    pimpl_->parseMessage(messageId, msg, showPreview);
 }
 
 void
-PreviewEnginePrivate::log(const QString& str)
+PreviewEngine::log(const QString& str)
 {
     qDebug() << str;
 }
 
-void
-PreviewEnginePrivate::infoReady(const QString& messageId, const QVariantMap& info)
-{
-    Q_EMIT parent_->infoReady(messageId, info);
-}
-
-void
-PreviewEnginePrivate::linkifyReady(const QString& messageId, const QString& linkified)
-{
-    Q_EMIT parent_->linkifyReady(messageId, linkified);
-}
+#include "moc_previewengine.cpp"
+#include "previewengine.moc"
diff --git a/src/previewengine.h b/src/previewengine.h
index ea57f296a25c49dcc6f8754a37510330bc23e0d4..c929b21b8e31f76c2e09d7fe0ee5a192034e76d8 100644
--- a/src/previewengine.h
+++ b/src/previewengine.h
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
  * Author: Trevor Tabah <trevor.tabah@savoirfairelinux.com>
  * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
@@ -20,42 +20,25 @@
 #pragma once
 
 #include "utils.h"
+#include <QObject>
 
-#include <QtWebChannel>
-#include <QWebEnginePage>
-
-class PreviewEngine;
-
-class PreviewEnginePrivate : public QObject
+class PreviewEngine : public QObject
 {
     Q_OBJECT
+    Q_DISABLE_COPY(PreviewEngine)
 public:
-    explicit PreviewEnginePrivate(PreviewEngine* parent)
-        : parent_(parent)
-    {}
-
-    Q_INVOKABLE void infoReady(const QString& messageId, const QVariantMap& info);
-    Q_INVOKABLE void linkifyReady(const QString& messageId, const QString& linkified);
-    Q_INVOKABLE void log(const QString& str);
-
-private:
-    PreviewEngine* parent_;
-};
-
-class PreviewEngine : public QWebEnginePage
-{
-    Q_OBJECT
-public:
-    explicit PreviewEngine(QObject* parent = nullptr);
-    ~PreviewEngine() = default;
+    PreviewEngine(QObject* parent = nullptr);
+    ~PreviewEngine();
 
     void parseMessage(const QString& messageId, const QString& msg, bool showPreview);
 
+    Q_INVOKABLE void log(const QString& str);
+
 Q_SIGNALS:
-    void infoReady(const QString& messageId, const QVariantMap& info);
-    void linkifyReady(const QString& messageId, const QString& linkified);
+    Q_INVOKABLE void infoReady(const QString& messageId, const QVariantMap& info);
+    Q_INVOKABLE void linkified(const QString& messageId, const QString& linkified);
 
 private:
-    QWebChannel* channel_;
-    PreviewEnginePrivate* pimpl_;
+    struct Impl;
+    std::unique_ptr<Impl> pimpl_;
 };
diff --git a/src/settingsview/components/ChatviewSettings.qml b/src/settingsview/components/ChatviewSettings.qml
index 65105700b5f0e1bf99ea24e582ffd90a7e98441d..da5c90926701f51c6df9b6de25509602910f4d0c 100644
--- a/src/settingsview/components/ChatviewSettings.qml
+++ b/src/settingsview/components/ChatviewSettings.qml
@@ -62,6 +62,7 @@ ColumnLayout {
 
     ToggleSwitch {
         id: displayImagesCheckbox
+        visible: WITH_WEBENGINE
 
         Layout.fillWidth: true
         Layout.leftMargin: JamiTheme.preferredMarginSize