summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-06-19 01:38:40 +0200
committerNicolas Werner <nicolas.werner@hotmail.de>2023-06-19 01:38:40 +0200
commitce1a64bc19ffc21e115bdf2587bb053d7a417f3e (patch)
treea195d127228218772a535448de642f0bb7b6d053 /src
parentRemove explicit link styling (diff)
downloadnheko-ce1a64bc19ffc21e115bdf2587bb053d7a417f3e.tar.xz
Move to automatic type registration
Diffstat (limited to 'src')
-rw-r--r--src/AliasEditModel.h4
-rw-r--r--src/CacheCryptoStructs.h3
-rw-r--r--src/Clipboard.h4
-rw-r--r--src/LoginPage.h8
-rw-r--r--src/MainWindow.cpp215
-rw-r--r--src/MainWindow.h25
-rw-r--r--src/PowerlevelsEditModels.h4
-rw-r--r--src/ReadReceiptsModel.h4
-rw-r--r--src/RegisterPage.h2
-rw-r--r--src/RoomDirectoryModel.h2
-rw-r--r--src/UserDirectoryModel.h2
-rw-r--r--src/UserSettingsPage.h24
-rw-r--r--src/encryption/Olm.h3
-rw-r--r--src/encryption/SelfVerificationStatus.cpp5
-rw-r--r--src/encryption/SelfVerificationStatus.h4
-rw-r--r--src/encryption/VerificationManager.cpp1
-rw-r--r--src/encryption/VerificationManager.h27
-rw-r--r--src/main.cpp2
-rw-r--r--src/timeline/CommunitiesModel.cpp1
-rw-r--r--src/timeline/CommunitiesModel.h28
-rw-r--r--src/timeline/DelegateChooser.h2
-rw-r--r--src/timeline/InputBar.h6
-rw-r--r--src/timeline/PresenceEmitter.h27
-rw-r--r--src/timeline/RoomlistModel.cpp2
-rw-r--r--src/timeline/RoomlistModel.h26
-rw-r--r--src/timeline/TimelineFilter.h2
-rw-r--r--src/timeline/TimelineModel.h5
-rw-r--r--src/timeline/TimelineViewManager.cpp30
-rw-r--r--src/timeline/TimelineViewManager.h25
-rw-r--r--src/ui/HiddenEvents.h2
-rw-r--r--src/ui/MxcAnimatedImage.h1
-rw-r--r--src/ui/MxcMediaProxy.h3
-rw-r--r--src/ui/NhekoCursorShape.h2
-rw-r--r--src/ui/NhekoDropArea.h1
-rw-r--r--src/ui/NhekoEventObserver.cpp60
-rw-r--r--src/ui/NhekoEventObserver.h27
-rw-r--r--src/ui/NhekoGlobalObject.h4
-rw-r--r--src/ui/RoomSummary.h4
-rw-r--r--src/ui/UIA.h22
-rw-r--r--src/ui/UserProfile.h2
-rw-r--r--src/voip/CallManager.cpp21
-rw-r--r--src/voip/CallManager.h7
-rw-r--r--src/voip/WebRTCSession.cpp40
-rw-r--r--src/voip/WebRTCSession.h2
44 files changed, 391 insertions, 300 deletions
diff --git a/src/AliasEditModel.h b/src/AliasEditModel.h
index 2263659b..04de5016 100644
--- a/src/AliasEditModel.h
+++ b/src/AliasEditModel.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QAbstractListModel>
+#include <QQmlEngine>
 #include <QVector>
 
 #include <mtx/events/canonical_alias.hpp>
@@ -29,6 +30,9 @@ signals:
 class AliasEditingModel final : public QAbstractListModel
 {
     Q_OBJECT
+    QML_ELEMENT
+    QML_UNCREATABLE("Please use editAliases to create the models")
+
     Q_PROPERTY(bool canAdvertize READ canAdvertize CONSTANT)
 
 public:
diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h
index 96fc35ec..2a5b895f 100644
--- a/src/CacheCryptoStructs.h
+++ b/src/CacheCryptoStructs.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 
 #include <map>
 #include <mutex>
@@ -16,6 +17,8 @@
 
 namespace crypto {
 Q_NAMESPACE
+QML_NAMED_ELEMENT(Crypto)
+
 //! How much a participant is trusted.
 enum Trust
 {
diff --git a/src/Clipboard.h b/src/Clipboard.h
index bad9fd10..8bf89c22 100644
--- a/src/Clipboard.h
+++ b/src/Clipboard.h
@@ -5,11 +5,15 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 #include <QString>
 
 class Clipboard final : public QObject
 {
     Q_OBJECT
+    QML_ELEMENT
+    QML_SINGLETON
+
     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
 
 public:
diff --git a/src/LoginPage.h b/src/LoginPage.h
index f20ba0c6..e3d0d2e0 100644
--- a/src/LoginPage.h
+++ b/src/LoginPage.h
@@ -5,13 +5,10 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 #include <QVariantList>
 
-namespace mtx {
-namespace responses {
-struct Login;
-}
-}
+#include <mtx/responses/login.hpp>
 
 struct SSOProvider
 {
@@ -33,6 +30,7 @@ public:
 class LoginPage : public QObject
 {
     Q_OBJECT
+    QML_ELEMENT
 
     Q_PROPERTY(QString mxid READ mxid WRITE setMxid NOTIFY matrixIdChanged)
     Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 51b23e0f..d06171de 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -25,21 +25,15 @@
 #include "InviteesModel.h"
 #include "JdenticonProvider.h"
 #include "Logging.h"
-#include "LoginPage.h"
 #include "MainWindow.h"
 #include "MatrixClient.h"
 #include "MemberList.h"
 #include "MxcImageProvider.h"
 #include "PowerlevelsEditModels.h"
-#include "ReadReceiptsModel.h"
-#include "RegisterPage.h"
-#include "RoomDirectoryModel.h"
-#include "RoomsModel.h"
 #include "SingleImagePackModel.h"
 #include "TrayIcon.h"
 #include "UserDirectoryModel.h"
 #include "UserSettingsPage.h"
-#include "UsersModel.h"
 #include "Utils.h"
 #include "dock/Dock.h"
 #include "emoji/Provider.h"
@@ -48,12 +42,6 @@
 #include "timeline/DelegateChooser.h"
 #include "timeline/TimelineFilter.h"
 #include "timeline/TimelineViewManager.h"
-#include "ui/HiddenEvents.h"
-#include "ui/MxcAnimatedImage.h"
-#include "ui/MxcMediaProxy.h"
-#include "ui/NhekoCursorShape.h"
-#include "ui/NhekoDropArea.h"
-#include "ui/NhekoEventObserver.h"
 #include "ui/NhekoGlobalObject.h"
 #include "ui/RoomSummary.h"
 #include "ui/UIA.h"
@@ -83,7 +71,7 @@ MainWindow::MainWindow(QWindow *parent)
     registerQmlTypes();
 
     setColor(Theme::paletteFromTheme(userSettings_->theme()).window().color());
-    setSource(QUrl(QStringLiteral("qrc:///qml/Root.qml")));
+    setSource(QUrl(QStringLiteral("qrc:///resources/qml/Root.qml")));
 
     trayIcon_ = new TrayIcon(QStringLiteral(":/logos/nheko.svg"), this);
 
@@ -132,156 +120,57 @@ MainWindow::MainWindow(QWindow *parent)
 void
 MainWindow::registerQmlTypes()
 {
-    qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "MtxEvent",
-                                     QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(
-      olm::staticMetaObject, "im.nheko", 1, 0, "Olm", QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(crypto::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "Crypto",
-                                     QStringLiteral("Can't instantiate enum!"));
-    qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "VerificationStatus",
-                                     QStringLiteral("Can't instantiate enum!"));
-
-    qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
-    qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
-    qmlRegisterType<NhekoDropArea>("im.nheko", 1, 0, "NhekoDropArea");
-    qmlRegisterType<NhekoCursorShape>("im.nheko", 1, 0, "CursorShape");
-    qmlRegisterType<NhekoEventObserver>("im.nheko", 1, 0, "EventObserver");
-    qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
-    qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
-    qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
-    qmlRegisterType<UserDirectoryModel>("im.nheko", 1, 0, "UserDirectoryModel");
-    qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
-    qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
-    qmlRegisterType<HiddenEvents>("im.nheko", 1, 0, "HiddenEvents");
-    qmlRegisterType<TimelineFilter>("im.nheko", 1, 0, "TimelineFilter");
-    qmlRegisterUncreatableType<RoomSummary>(
-      "im.nheko",
-      1,
-      0,
-      "RoomSummary",
-      QStringLiteral("Please use joinRoom to create a room summary."));
-    qmlRegisterUncreatableType<AliasEditingModel>(
-      "im.nheko",
-      1,
-      0,
-      "AliasEditingModel",
-      QStringLiteral("Please use editAliases to create the models"));
-
-    qmlRegisterUncreatableType<PowerlevelEditingModels>(
-      "im.nheko",
-      1,
-      0,
-      "PowerlevelEditingModels",
-      QStringLiteral("Please use editPowerlevels to create the models"));
-    qmlRegisterUncreatableType<DeviceVerificationFlow>(
-      "im.nheko",
-      1,
-      0,
-      "DeviceVerificationFlow",
-      QStringLiteral("Can't create verification flow from QML!"));
-    qmlRegisterUncreatableType<UserProfile>(
-      "im.nheko",
-      1,
-      0,
-      "UserProfileModel",
-      QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<MemberList>(
-      "im.nheko",
-      1,
-      0,
-      "MemberList",
-      QStringLiteral("MemberList needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<RoomSettings>(
-      "im.nheko",
-      1,
-      0,
-      "RoomSettingsModel",
-      QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<TimelineModel>(
-      "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<ImagePackListModel>(
-      "im.nheko",
-      1,
-      0,
-      "ImagePackListModel",
-      QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<SingleImagePackModel>(
-      "im.nheko",
-      1,
-      0,
-      "SingleImagePackModel",
-      QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<InviteesModel>(
-      "im.nheko",
-      1,
-      0,
-      "InviteesModel",
-      QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
-    qmlRegisterUncreatableType<ReadReceiptsProxy>(
-      "im.nheko",
-      1,
-      0,
-      "ReadReceiptsProxy",
-      QStringLiteral("ReadReceiptsProxy needs to be instantiated on the C++ side"));
-
-    qmlRegisterSingletonType<Clipboard>(
-      "im.nheko", 1, 0, "Clipboard", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new Clipboard();
-      });
-    qmlRegisterSingletonType<Nheko>(
-      "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new Nheko();
-      });
-    qmlRegisterSingletonType<UserSettingsModel>(
-      "im.nheko", 1, 0, "UserSettingsModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          return new UserSettingsModel();
-      });
-
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", userSettings_.data());
-
-    qmlRegisterUncreatableType<FilteredCommunitiesModel>(
-      "im.nheko",
-      1,
-      0,
-      "FilteredCommunitiesModel",
-      QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
-
-    qmlRegisterUncreatableType<MediaUpload>(
-      "im.nheko", 1, 0, "MediaUpload", QStringLiteral("MediaUploads can not be created in Qml"));
-    qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
-                                     "im.nheko.EmojiModel",
-                                     1,
-                                     0,
-                                     "EmojiCategory",
-                                     QStringLiteral("Error: Only enums"));
-
-    qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
-
-    qmlRegisterSingletonType<SelfVerificationStatus>(
-      "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          auto ptr = new SelfVerificationStatus();
-          QObject::connect(ChatPage::instance(),
-                           &ChatPage::initializeEmptyViews,
-                           ptr,
-                           &SelfVerificationStatus::invalidate);
-          return ptr;
-      });
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "MainWindow", this);
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "UIA", UIA::instance());
-    qmlRegisterSingletonInstance(
-      "im.nheko", 1, 0, "CallManager", ChatPage::instance()->callManager());
+    // qmlRegisterUncreatableType<DeviceVerificationFlow>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "DeviceVerificationFlow",
+    //   QStringLiteral("Can't create verification flow from QML!"));
+    // qmlRegisterUncreatableType<UserProfile>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "UserProfileModel",
+    //   QStringLiteral("UserProfile needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<MemberList>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "MemberList",
+    //   QStringLiteral("MemberList needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<RoomSettings>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "RoomSettingsModel",
+    //   QStringLiteral("Room Settings needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<TimelineModel>(
+    //   "im.nheko", 1, 0, "Room", QStringLiteral("Room needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<ImagePackListModel>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "ImagePackListModel",
+    //   QStringLiteral("ImagePackListModel needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<SingleImagePackModel>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "SingleImagePackModel",
+    //   QStringLiteral("SingleImagePackModel needs to be instantiated on the C++ side"));
+    // qmlRegisterUncreatableType<InviteesModel>(
+    //   "im.nheko",
+    //   1,
+    //   0,
+    //   "InviteesModel",
+    //   QStringLiteral("InviteesModel needs to be instantiated on the C++ side"));
+
+    // qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,
+    //                                  "im.nheko.EmojiModel",
+    //                                  1,
+    //                                  0,
+    //                                  "EmojiCategory",
+    //                                  QStringLiteral("Error: Only enums"));
 
     imgProvider = new MxcImageProvider();
     engine()->addImageProvider(QStringLiteral("MxcImage"), imgProvider);
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 0a5f9433..20e81efc 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -50,14 +50,35 @@ public:
     bool eventFilter(QObject *obj, QEvent *event) override;
 };
 
-class MainWindow final : public QQuickView
+class MainWindow : public QQuickView
 {
     Q_OBJECT
+    QML_ELEMENT
+    QML_SINGLETON
 
 public:
-    explicit MainWindow(QWindow *parent = nullptr);
+    explicit MainWindow(QWindow *parent);
 
     static MainWindow *instance() { return instance_; }
+    static MainWindow *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
+    }
+
     void saveCurrentWindowSize();
 
     void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
diff --git a/src/PowerlevelsEditModels.h b/src/PowerlevelsEditModels.h
index fe9735d3..c9d262d8 100644
--- a/src/PowerlevelsEditModels.h
+++ b/src/PowerlevelsEditModels.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QAbstractListModel>
+#include <QQmlEngine>
 #include <QSortFilterProxyModel>
 
 #include <mtx/events/power_levels.hpp>
@@ -196,6 +197,9 @@ class PowerlevelEditingModels final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_UNCREATABLE("Please use editPowerlevels to create the models")
+
     Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT)
     Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT)
     Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces CONSTANT)
diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h
index b870061a..56f67509 100644
--- a/src/ReadReceiptsModel.h
+++ b/src/ReadReceiptsModel.h
@@ -8,6 +8,7 @@
 #include <QAbstractListModel>
 #include <QDateTime>
 #include <QObject>
+#include <QQmlEngine>
 #include <QSortFilterProxyModel>
 #include <QString>
 
@@ -54,6 +55,9 @@ class ReadReceiptsProxy final : public QSortFilterProxyModel
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_UNCREATABLE("")
+
     Q_PROPERTY(QString eventId READ eventId CONSTANT)
     Q_PROPERTY(QString roomId READ roomId CONSTANT)
 
diff --git a/src/RegisterPage.h b/src/RegisterPage.h
index 7c58b40c..dcf61489 100644
--- a/src/RegisterPage.h
+++ b/src/RegisterPage.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 #include <QString>
 
 #include <mtx/user_interactive.hpp>
@@ -13,6 +14,7 @@
 class RegisterPage : public QObject
 {
     Q_OBJECT
+    QML_ELEMENT
 
     Q_PROPERTY(QString error READ error NOTIFY errorChanged)
     Q_PROPERTY(QString hsError READ hsError NOTIFY hsErrorChanged)
diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h
index 8a367e2e..a5103112 100644
--- a/src/RoomDirectoryModel.h
+++ b/src/RoomDirectoryModel.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QAbstractListModel>
+#include <QQmlEngine>
 #include <QString>
 #include <string>
 #include <vector>
@@ -32,6 +33,7 @@ signals:
 class RoomDirectoryModel : public QAbstractListModel
 {
     Q_OBJECT
+    QML_ELEMENT
 
     Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged)
     Q_PROPERTY(
diff --git a/src/UserDirectoryModel.h b/src/UserDirectoryModel.h
index f0416ecf..ffa9ae93 100644
--- a/src/UserDirectoryModel.h
+++ b/src/UserDirectoryModel.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QAbstractListModel>
+#include <QQmlEngine>
 #include <QString>
 #include <string>
 #include <vector>
@@ -26,6 +27,7 @@ signals:
 class UserDirectoryModel : public QAbstractListModel
 {
     Q_OBJECT
+    QML_ELEMENT
 
     Q_PROPERTY(bool searchingUsers READ searchingUsers NOTIFY searchingUsersChanged)
 
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 657a362d..301a1b67 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -6,6 +6,7 @@
 
 #include <QAbstractListModel>
 #include <QProcessEnvironment>
+#include <QQmlEngine>
 #include <QSettings>
 #include <QSharedPointer>
 
@@ -23,6 +24,8 @@ class QVBoxLayout;
 class UserSettings final : public QObject
 {
     Q_OBJECT
+    QML_NAMED_ELEMENT(Settings)
+    QML_SINGLETON
 
     Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
     Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight
@@ -131,6 +134,24 @@ class UserSettings final : public QObject
 public:
     static QSharedPointer<UserSettings> instance();
     static void initialize(std::optional<QString> profile);
+    static UserSettings *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance());
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance()->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance().get(), QJSEngine::CppOwnership);
+        return instance().get();
+    }
 
     QSettings *qsettings() { return &settings; }
 
@@ -431,9 +452,10 @@ private:
     static QSharedPointer<UserSettings> instance_;
 };
 
-class UserSettingsModel final : public QAbstractListModel
+class UserSettingsModel : public QAbstractListModel
 {
     Q_OBJECT
+    QML_ELEMENT
 
     enum Indices
     {
diff --git a/src/encryption/Olm.h b/src/encryption/Olm.h
index f0e51070..726b9590 100644
--- a/src/encryption/Olm.h
+++ b/src/encryption/Olm.h
@@ -9,10 +9,13 @@
 #include <mtx/events/encrypted.hpp>
 #include <mtxclient/crypto/client.hpp>
 
+#include <QQmlEngine>
+
 #include <CacheCryptoStructs.h>
 
 namespace olm {
 Q_NAMESPACE
+QML_NAMED_ELEMENT(Olm)
 
 enum DecryptionErrorCode
 {
diff --git a/src/encryption/SelfVerificationStatus.cpp b/src/encryption/SelfVerificationStatus.cpp
index 6fa737d4..d9d3d787 100644
--- a/src/encryption/SelfVerificationStatus.cpp
+++ b/src/encryption/SelfVerificationStatus.cpp
@@ -29,6 +29,11 @@ SelfVerificationStatus::SelfVerificationStatus(QObject *o)
                 Qt::UniqueConnection);
         cache::client()->markUserKeysOutOfDate({http::client()->user_id().to_string()});
     });
+
+    connect(ChatPage::instance(),
+            &ChatPage::initializeEmptyViews,
+            this,
+            &SelfVerificationStatus::invalidate);
 }
 
 void
diff --git a/src/encryption/SelfVerificationStatus.h b/src/encryption/SelfVerificationStatus.h
index ea790c8b..c65fffd0 100644
--- a/src/encryption/SelfVerificationStatus.h
+++ b/src/encryption/SelfVerificationStatus.h
@@ -5,11 +5,15 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 
 class SelfVerificationStatus final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_SINGLETON
+
     Q_PROPERTY(Status status READ status NOTIFY statusChanged)
     Q_PROPERTY(bool hasSSSS READ hasSSSS NOTIFY hasSSSSChanged)
 
diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp
index 802a8177..d1248755 100644
--- a/src/encryption/VerificationManager.cpp
+++ b/src/encryption/VerificationManager.cpp
@@ -15,6 +15,7 @@ VerificationManager::VerificationManager(TimelineViewManager *o)
   : QObject(o)
   , rooms_(o->rooms())
 {
+    instance_ = this;
 }
 
 static bool
diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h
index 7b32bc98..cdc8af30 100644
--- a/src/encryption/VerificationManager.h
+++ b/src/encryption/VerificationManager.h
@@ -6,6 +6,7 @@
 
 #include <QHash>
 #include <QObject>
+#include <QQmlEngine>
 #include <QSharedPointer>
 
 #include <mtx/events.hpp>
@@ -21,8 +22,30 @@ class VerificationManager final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_SINGLETON
+
 public:
-    VerificationManager(TimelineViewManager *o = nullptr);
+    VerificationManager(TimelineViewManager *o);
+
+    static VerificationManager *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
+    }
 
     Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
     void verifyUser(QString userid);
@@ -45,4 +68,6 @@ private:
     QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList;
     bool isInitialSync_ = false;
     RoomlistModel *rooms_;
+
+    inline static VerificationManager *instance_ = nullptr;
 };
diff --git a/src/main.cpp b/src/main.cpp
index da67ca43..07397d62 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -346,7 +346,7 @@ main(int argc, char *argv[])
                            QStringLiteral(":/translations")))
         app.installTranslator(&appTranslator);
 
-    MainWindow w;
+    MainWindow w(nullptr);
     // QQuickView w;
 
     // Move the MainWindow to the center
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp
index b04fd7a9..3c09d747 100644
--- a/src/timeline/CommunitiesModel.cpp
+++ b/src/timeline/CommunitiesModel.cpp
@@ -22,6 +22,7 @@ CommunitiesModel::CommunitiesModel(QObject *parent)
   , hiddenTagIds_{UserSettings::instance()->hiddenTags()}
   , mutedTagIds_{UserSettings::instance()->mutedTags()}
 {
+    instance_ = this;
 }
 
 QHash<int, QByteArray>
diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h
index a90fa6a2..d0841f4b 100644
--- a/src/timeline/CommunitiesModel.h
+++ b/src/timeline/CommunitiesModel.h
@@ -6,6 +6,7 @@
 
 #include <QAbstractListModel>
 #include <QHash>
+#include <QQmlEngine>
 #include <QSortFilterProxyModel>
 #include <QString>
 #include <QStringList>
@@ -21,6 +22,8 @@ class CommunitiesModel;
 class FilteredCommunitiesModel final : public QSortFilterProxyModel
 {
     Q_OBJECT
+    QML_ELEMENT
+    QML_UNCREATABLE("Use Communities.filtered() to create a FilteredCommunitiesModel")
 
 public:
     explicit FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent = nullptr);
@@ -73,6 +76,9 @@ public:
 class CommunitiesModel final : public QAbstractListModel
 {
     Q_OBJECT
+    QML_NAMED_ELEMENT(Communities)
+    QML_SINGLETON
+
     Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
                  currentTagIdChanged RESET resetCurrentTagId)
     Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
@@ -149,6 +155,26 @@ public:
     };
 
     CommunitiesModel(QObject *parent = nullptr);
+
+    static CommunitiesModel *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
+    }
+
     QHash<int, QByteArray> roleNames() const override;
     int rowCount(const QModelIndex &parent = QModelIndex()) const override
     {
@@ -221,4 +247,6 @@ private:
     mtx::responses::UnreadNotifications dmUnreads{};
 
     friend class FilteredCommunitiesModel;
+
+    inline static CommunitiesModel *instance_ = nullptr;
 };
diff --git a/src/timeline/DelegateChooser.h b/src/timeline/DelegateChooser.h
index c27f2c43..ac227382 100644
--- a/src/timeline/DelegateChooser.h
+++ b/src/timeline/DelegateChooser.h
@@ -19,6 +19,7 @@ class QQmlAdaptorModel;
 class DelegateChoice : public QObject
 {
     Q_OBJECT
+    QML_ELEMENT
     Q_CLASSINFO("DefaultProperty", "delegate")
 
 public:
@@ -45,6 +46,7 @@ private:
 class DelegateChooser : public QQuickItem
 {
     Q_OBJECT
+    QML_ELEMENT
     Q_CLASSINFO("DefaultProperty", "choices")
 
 public:
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index f03e6019..3cd65524 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -7,11 +7,13 @@
 #include <QIODevice>
 #include <QImage>
 #include <QObject>
+#include <QQmlEngine>
 #include <QSize>
 #include <QStringList>
 #include <QTimer>
 #include <QUrl>
 #include <QVariantList>
+
 #include <deque>
 #include <memory>
 
@@ -43,6 +45,10 @@ enum class MarkdownOverride
 class MediaUpload final : public QObject
 {
     Q_OBJECT
+
+    QML_ELEMENT
+    QML_UNCREATABLE("")
+
     Q_PROPERTY(int mediaType READ type NOTIFY mediaTypeChanged)
     // https://stackoverflow.com/questions/33422265/pass-qimage-to-qml/68554646#68554646
     Q_PROPERTY(QUrl thumbnail READ thumbnailDataUrl NOTIFY thumbnailChanged)
diff --git a/src/timeline/PresenceEmitter.h b/src/timeline/PresenceEmitter.h
index e89fb316..09ad1301 100644
--- a/src/timeline/PresenceEmitter.h
+++ b/src/timeline/PresenceEmitter.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 
 #include <vector>
 
@@ -15,10 +16,33 @@ class PresenceEmitter final : public QObject
 {
     Q_OBJECT
 
+    QML_NAMED_ELEMENT(Presence)
+    QML_SINGLETON
+
 public:
     PresenceEmitter(QObject *p = nullptr)
       : QObject(p)
     {
+        instance_ = this;
+    }
+
+    static PresenceEmitter *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
     }
 
     void sync(const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences);
@@ -28,4 +52,7 @@ public:
 
 signals:
     void presenceChanged(QString userid);
+
+private:
+    inline static PresenceEmitter *instance_ = nullptr;
 };
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index ec41cc12..8d8d2977 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -909,6 +909,8 @@ FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *pare
   : QSortFilterProxyModel(parent)
   , roomlistmodel(model)
 {
+    instance_ = this;
+
     this->sortByImportance = UserSettings::instance()->sortByImportance();
     this->sortByAlphabet   = UserSettings::instance()->sortByAlphabet();
     setSourceModel(model);
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index c06ab67d..34bf3f9a 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -167,12 +167,36 @@ private:
 class FilteredRoomlistModel final : public QSortFilterProxyModel
 {
     Q_OBJECT
+
+    QML_NAMED_ELEMENT(Rooms)
+    QML_SINGLETON
+
     Q_PROPERTY(
       TimelineModel *currentRoom READ currentRoom NOTIFY currentRoomChanged RESET resetCurrentRoom)
     Q_PROPERTY(RoomPreview currentRoomPreview READ currentRoomPreview NOTIFY currentRoomChanged
                  RESET resetCurrentRoom)
 public:
     FilteredRoomlistModel(RoomlistModel *model, QObject *parent = nullptr);
+
+    static FilteredRoomlistModel *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
+    }
+
     bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
     bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
 
@@ -249,4 +273,6 @@ private:
     FilterBy filterType = FilterBy::Nothing;
     QStringList hiddenTags, hiddenSpaces;
     bool hideDMs = false;
+
+    inline static FilteredRoomlistModel *instance_ = nullptr;
 };
diff --git a/src/timeline/TimelineFilter.h b/src/timeline/TimelineFilter.h
index 1c92c89a..658a8c57 100644
--- a/src/timeline/TimelineFilter.h
+++ b/src/timeline/TimelineFilter.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <QQmlEngine>
 #include <QSortFilterProxyModel>
 #include <QString>
 
@@ -14,6 +15,7 @@
 class TimelineFilter : public QSortFilterProxyModel
 {
     Q_OBJECT
+    QML_ELEMENT
 
     Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
     Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index a232b4ee..fd1a4396 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -11,6 +11,7 @@
 #include <QTimer>
 #include <QVariant>
 
+#include <mtx/responses/common.hpp>
 #include <mtxclient/http/errors.hpp>
 
 #include "CacheCryptoStructs.h"
@@ -36,6 +37,7 @@ struct RelatedInfo;
 
 namespace qml_mtx_events {
 Q_NAMESPACE
+QML_NAMED_ELEMENT(MtxEvent)
 
 enum EventType
 {
@@ -193,6 +195,9 @@ class TimelineViewManager;
 class TimelineModel final : public QAbstractListModel
 {
     Q_OBJECT
+    QML_NAMED_ELEMENT(Room)
+    QML_UNCREATABLE("")
+
     Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
     Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
                  typingUsersChanged)
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index a3b91ce7..2f6553e5 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -96,29 +96,21 @@ TimelineViewManager::userColor(QString id, QColor background)
 TimelineViewManager::TimelineViewManager(CallManager *, ChatPage *parent)
   : QObject(parent)
   , rooms_(new RoomlistModel(this))
+  , frooms_(new FilteredRoomlistModel(this->rooms_))
   , communities_(new CommunitiesModel(this))
   , verificationManager_(new VerificationManager(this))
   , presenceEmitter(new PresenceEmitter(this))
 {
-    static auto self = this;
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", self);
-    qmlRegisterSingletonType<RoomlistModel>(
-      "im.nheko", 1, 0, "Rooms", [](QQmlEngine *, QJSEngine *) -> QObject * {
-          auto ptr = new FilteredRoomlistModel(self->rooms_);
-
-          connect(self->communities_,
-                  &CommunitiesModel::currentTagIdChanged,
-                  ptr,
-                  &FilteredRoomlistModel::updateFilterTag);
-          connect(self->communities_,
-                  &CommunitiesModel::hiddenTagsChanged,
-                  ptr,
-                  &FilteredRoomlistModel::updateHiddenTagsAndSpaces);
-          return ptr;
-      });
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "Communities", self->communities_);
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
-    qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
+    instance_ = this;
+
+    connect(this->communities_,
+            &CommunitiesModel::currentTagIdChanged,
+            frooms_,
+            &FilteredRoomlistModel::updateFilterTag);
+    connect(this->communities_,
+            &CommunitiesModel::hiddenTagsChanged,
+            frooms_,
+            &FilteredRoomlistModel::updateHiddenTagsAndSpaces);
 
     updateColorPalette();
 
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 303e2af2..a4bc6c41 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -34,6 +34,9 @@ class TimelineViewManager final : public QObject
 {
     Q_OBJECT
 
+    QML_NAMED_ELEMENT(TimelineManager)
+    QML_SINGLETON
+
     Q_PROPERTY(
       bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
     Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged)
@@ -41,6 +44,25 @@ class TimelineViewManager final : public QObject
 public:
     TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
 
+    static TimelineViewManager *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance_);
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance_->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance_, QJSEngine::CppOwnership);
+        return instance_;
+    }
+
     void sync(const mtx::responses::Sync &sync_);
 
     VerificationManager *verificationManager() { return verificationManager_; }
@@ -123,6 +145,7 @@ private:
     bool isConnected_   = true;
 
     RoomlistModel *rooms_          = nullptr;
+    FilteredRoomlistModel *frooms_ = nullptr;
     CommunitiesModel *communities_ = nullptr;
 
     // don't move this above the rooms_
@@ -130,4 +153,6 @@ private:
     PresenceEmitter *presenceEmitter          = nullptr;
 
     QHash<QPair<QString, quint64>, QColor> userColors;
+
+    inline static TimelineViewManager *instance_ = nullptr;
 };
diff --git a/src/ui/HiddenEvents.h b/src/ui/HiddenEvents.h
index bb68e0fa..4f0d23b4 100644
--- a/src/ui/HiddenEvents.h
+++ b/src/ui/HiddenEvents.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 #include <QString>
 #include <QVariantList>
 
@@ -13,6 +14,7 @@
 class HiddenEvents : public QObject
 {
     Q_OBJECT
+    QML_ELEMENT
     Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged REQUIRED)
     Q_PROPERTY(QVariantList hiddenEvents READ hiddenEvents NOTIFY hiddenEventsChanged)
 public:
diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h
index bc53e711..c9f89764 100644
--- a/src/ui/MxcAnimatedImage.h
+++ b/src/ui/MxcAnimatedImage.h
@@ -15,6 +15,7 @@
 class MxcAnimatedImage : public QQuickItem
 {
     Q_OBJECT
+    QML_ELEMENT
     Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
     Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
     Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged)
diff --git a/src/ui/MxcMediaProxy.h b/src/ui/MxcMediaProxy.h
index 5c2eac33..d245dcae 100644
--- a/src/ui/MxcMediaProxy.h
+++ b/src/ui/MxcMediaProxy.h
@@ -8,6 +8,7 @@
 #include <QMediaPlayer>
 #include <QObject>
 #include <QPointer>
+#include <QQuickItem>
 #include <QString>
 #include <QUrl>
 #include <QVideoSink>
@@ -21,6 +22,8 @@ class TimelineModel;
 class MxcMediaProxy : public QMediaPlayer
 {
     Q_OBJECT
+    QML_NAMED_ELEMENT(MxcMedia)
+
     Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED)
     Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
     Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
diff --git a/src/ui/NhekoCursorShape.h b/src/ui/NhekoCursorShape.h
index 84d56fad..123852f9 100644
--- a/src/ui/NhekoCursorShape.h
+++ b/src/ui/NhekoCursorShape.h
@@ -12,7 +12,7 @@
 class NhekoCursorShape : public QQuickItem
 {
     Q_OBJECT
-
+    QML_ELEMENT
     Q_PROPERTY(
       Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY cursorShapeChanged)
 
diff --git a/src/ui/NhekoDropArea.h b/src/ui/NhekoDropArea.h
index 91116844..46a02da5 100644
--- a/src/ui/NhekoDropArea.h
+++ b/src/ui/NhekoDropArea.h
@@ -7,6 +7,7 @@
 class NhekoDropArea : public QQuickItem
 {
     Q_OBJECT
+    QML_ELEMENT
     Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged)
 public:
     NhekoDropArea(QQuickItem *parent = nullptr);
diff --git a/src/ui/NhekoEventObserver.cpp b/src/ui/NhekoEventObserver.cpp
deleted file mode 100644
index 713a0733..00000000
--- a/src/ui/NhekoEventObserver.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-// SPDX-FileCopyrightText: Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "NhekoEventObserver.h"
-
-#include <QMouseEvent>
-
-#include "Logging.h"
-
-NhekoEventObserver::NhekoEventObserver(QQuickItem *parent)
-  : QQuickItem(parent)
-{
-    setFiltersChildMouseEvents(true);
-}
-
-bool
-NhekoEventObserver::childMouseEventFilter(QQuickItem * /*item*/, QEvent *event)
-{
-    // nhlog::ui()->debug("Touched {}", item->metaObject()->className());
-
-    auto setTouched = [this](bool touched) {
-        if (touched != this->wasTouched_) {
-            this->wasTouched_ = touched;
-            emit wasTouchedChanged();
-        }
-    };
-
-    // see
-    // https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quicktemplates2/qquickscrollview.cpp?id=7f29e89c26ae2babc358b1c4e6f965af6ec759f4#n471
-    switch (event->type()) {
-    case QEvent::TouchBegin:
-    case QEvent::TouchEnd:
-        setTouched(true);
-        break;
-
-    case QEvent::MouseButtonPress:
-        if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized) {
-            setTouched(false);
-        }
-        break;
-
-    case QEvent::MouseMove:
-    case QEvent::MouseButtonRelease:
-        if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized)
-            setTouched(false);
-        break;
-
-    case QEvent::HoverEnter:
-    case QEvent::HoverMove:
-    case QEvent::Wheel:
-        setTouched(false);
-        break;
-
-    default:
-        break;
-    }
-
-    return false;
-}
diff --git a/src/ui/NhekoEventObserver.h b/src/ui/NhekoEventObserver.h
deleted file mode 100644
index 63739d4a..00000000
--- a/src/ui/NhekoEventObserver.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-FileCopyrightText: Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QQuickItem>
-
-class NhekoEventObserver : public QQuickItem
-{
-    Q_OBJECT
-
-    Q_PROPERTY(bool wasTouched READ wasTouched NOTIFY wasTouchedChanged)
-
-public:
-    explicit NhekoEventObserver(QQuickItem *parent = 0);
-
-    bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
-
-private:
-    bool wasTouched() { return wasTouched_; }
-
-    bool wasTouched_ = false;
-
-signals:
-    void wasTouchedChanged();
-};
diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h
index b7a7a637..91210c54 100644
--- a/src/ui/NhekoGlobalObject.h
+++ b/src/ui/NhekoGlobalObject.h
@@ -7,6 +7,7 @@
 #include <QFontDatabase>
 #include <QObject>
 #include <QPalette>
+#include <QQmlEngine>
 #include <QWindow>
 
 #include "AliasEditModel.h"
@@ -19,6 +20,9 @@ class Nheko final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_SINGLETON
+
     Q_PROPERTY(QPalette colors READ colors NOTIFY colorsChanged)
     Q_PROPERTY(QPalette inactiveColors READ inactiveColors NOTIFY colorsChanged)
     Q_PROPERTY(Theme theme READ theme NOTIFY colorsChanged)
diff --git a/src/ui/RoomSummary.h b/src/ui/RoomSummary.h
index c02ea5d5..8225f0ae 100644
--- a/src/ui/RoomSummary.h
+++ b/src/ui/RoomSummary.h
@@ -7,6 +7,7 @@
 #include <optional>
 
 #include <QObject>
+#include <QQmlEngine>
 
 #include <mtx/responses/public_rooms.hpp>
 
@@ -25,6 +26,9 @@ class RoomSummary final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_UNCREATABLE("Please use joinRoom to create a room summary.")
+
     Q_PROPERTY(QString reason READ reason WRITE setReason NOTIFY reasonChanged)
 
     Q_PROPERTY(QString roomid READ roomid NOTIFY loaded)
diff --git a/src/ui/UIA.h b/src/ui/UIA.h
index 7d23d88e..414cb804 100644
--- a/src/ui/UIA.h
+++ b/src/ui/UIA.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <QObject>
+#include <QQmlEngine>
 
 #include <mtxclient/http/client.hpp>
 
@@ -12,10 +13,31 @@ class UIA final : public QObject
 {
     Q_OBJECT
 
+    QML_ELEMENT
+    QML_SINGLETON
+
     Q_PROPERTY(QString title READ title NOTIFY titleChanged)
 
 public:
     static UIA *instance();
+    static UIA *create(QQmlEngine *qmlEngine, QJSEngine *)
+    {
+        // The instance has to exist before it is used. We cannot replace it.
+        Q_ASSERT(instance());
+
+        // The engine has to have the same thread affinity as the singleton.
+        Q_ASSERT(qmlEngine->thread() == instance()->thread());
+
+        // There can only be one engine accessing the singleton.
+        static QJSEngine *s_engine = nullptr;
+        if (s_engine)
+            Q_ASSERT(qmlEngine == s_engine);
+        else
+            s_engine = qmlEngine;
+
+        QJSEngine::setObjectOwnership(instance(), QJSEngine::CppOwnership);
+        return instance();
+    }
 
     UIA(QObject *parent = nullptr)
       : QObject(parent)
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index a880f320..d8e06aa1 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -6,6 +6,7 @@
 
 #include <QAbstractListModel>
 #include <QObject>
+#include <QQmlEngine>
 #include <QString>
 #include <QVector>
 #include <mtx/responses.hpp>
@@ -16,6 +17,7 @@
 
 namespace verification {
 Q_NAMESPACE
+QML_NAMED_ELEMENT(VerificationStatus)
 
 enum Status
 {
diff --git a/src/voip/CallManager.cpp b/src/voip/CallManager.cpp
index feb06835..5479ba31 100644
--- a/src/voip/CallManager.cpp
+++ b/src/voip/CallManager.cpp
@@ -54,6 +54,27 @@ std::vector<std::string>
 getTurnURIs(const mtx::responses::TurnServer &turnServer);
 }
 
+CallManager *
+CallManager::create(QQmlEngine *qmlEngine, QJSEngine *)
+{
+    // The instance has to exist before it is used. We cannot replace it.
+    auto instance = ChatPage::instance()->callManager();
+    Q_ASSERT(instance);
+
+    // The engine has to have the same thread affinity as the singleton.
+    Q_ASSERT(qmlEngine->thread() == instance->thread());
+
+    // There can only be one engine accessing the singleton.
+    static QJSEngine *s_engine = nullptr;
+    if (s_engine)
+        Q_ASSERT(qmlEngine == s_engine);
+    else
+        s_engine = qmlEngine;
+
+    QJSEngine::setObjectOwnership(instance, QJSEngine::CppOwnership);
+    return instance;
+}
+
 CallManager::CallManager(QObject *parent)
   : QObject(parent)
   , session_(WebRTCSession::instance())
diff --git a/src/voip/CallManager.h b/src/voip/CallManager.h
index bbc7a903..e84b79c9 100644
--- a/src/voip/CallManager.h
+++ b/src/voip/CallManager.h
@@ -9,6 +9,7 @@
 
 #include <QMediaPlayer>
 #include <QObject>
+#include <QQmlEngine>
 #include <QString>
 #include <QStringList>
 #include <QTimer>
@@ -29,6 +30,10 @@ class QUrl;
 class CallManager final : public QObject
 {
     Q_OBJECT
+
+    QML_ELEMENT
+    QML_SINGLETON
+
     Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
     Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
     Q_PROPERTY(bool isOnCallOnOtherDevice READ isOnCallOnOtherDevice NOTIFY newCallDeviceState)
@@ -49,6 +54,8 @@ class CallManager final : public QObject
 public:
     CallManager(QObject *);
 
+    static CallManager *create(QQmlEngine *qmlEngine, QJSEngine *);
+
     bool haveCallInvite() const { return haveCallInvite_; }
     bool isOnCall() const { return (session_.state() != webrtc::State::DISCONNECTED); }
     bool isOnCallOnOtherDevice() const { return (isOnCallOnOtherDevice_ != ""); }
diff --git a/src/voip/WebRTCSession.cpp b/src/voip/WebRTCSession.cpp
index c40b39a4..ff459bf9 100644
--- a/src/voip/WebRTCSession.cpp
+++ b/src/voip/WebRTCSession.cpp
@@ -48,26 +48,26 @@ using webrtc::State;
 WebRTCSession::WebRTCSession()
   : devices_(CallDevices::instance())
 {
-    qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "CallType",
-                                     QStringLiteral("Can't instantiate enum"));
-
-    qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "ScreenShareType",
-                                     QStringLiteral("Can't instantiate enum"));
-
-    qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
-                                     "im.nheko",
-                                     1,
-                                     0,
-                                     "WebRTCState",
-                                     QStringLiteral("Can't instantiate enum"));
+    // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
+    //                                  "im.nheko",
+    //                                  1,
+    //                                  0,
+    //                                  "CallType",
+    //                                  QStringLiteral("Can't instantiate enum"));
+
+    // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
+    //                                  "im.nheko",
+    //                                  1,
+    //                                  0,
+    //                                  "ScreenShareType",
+    //                                  QStringLiteral("Can't instantiate enum"));
+
+    // qmlRegisterUncreatableMetaObject(webrtc::staticMetaObject,
+    //                                  "im.nheko",
+    //                                  1,
+    //                                  0,
+    //                                  "WebRTCState",
+    //                                  QStringLiteral("Can't instantiate enum"));
 
     connect(this, &WebRTCSession::stateChanged, this, &WebRTCSession::setState);
     init();
diff --git a/src/voip/WebRTCSession.h b/src/voip/WebRTCSession.h
index 82753372..3357bff7 100644
--- a/src/voip/WebRTCSession.h
+++ b/src/voip/WebRTCSession.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include <QObject>
+#include <QQmlEngine>
 
 #include "mtx/events/voip.hpp"
 
@@ -17,6 +18,7 @@ class QQuickItem;
 
 namespace webrtc {
 Q_NAMESPACE
+QML_NAMED_ELEMENT(Voip)
 
 enum class CallType
 {