summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/install.sh33
-rwxr-xr-x.ci/linux/deploy.sh2
-rwxr-xr-x.ci/script.sh8
-rw-r--r--.travis.yml105
-rw-r--r--CMakeLists.txt13
-rw-r--r--deps/CMakeLists.txt4
-rw-r--r--deps/cmake/Json.cmake2
-rw-r--r--src/Cache.cpp30
-rw-r--r--src/Cache.h85
-rw-r--r--src/ChatPage.cpp10
-rw-r--r--src/ChatPage.h12
-rw-r--r--src/Olm.cpp7
-rw-r--r--src/Utils.cpp67
-rw-r--r--src/Utils.h17
-rw-r--r--src/timeline/TimelineModel.cpp157
-rw-r--r--src/timeline/TimelineViewManager.cpp8
-rw-r--r--src/timeline/TimelineViewManager.h8
17 files changed, 284 insertions, 284 deletions
diff --git a/.ci/install.sh b/.ci/install.sh
index 57e73e03..776d4d23 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -3,14 +3,6 @@
 set -ex
 
 if [ "$TRAVIS_OS_NAME" = "osx" ]; then
-    brew update
-
-    # uninstall packages, that would get upgraded by upgrading cmake (and we don't need)
-    brew uninstall --force cgal node sfcgal postgis
-
-    brew install qt5 lmdb clang-format ninja libsodium cmark
-    brew upgrade boost cmake icu4c || true
-
     brew tap nlohmann/json
     brew install --with-cmake nlohmann_json
 
@@ -25,11 +17,11 @@ fi
 
 
 if [ "$TRAVIS_OS_NAME" = "linux" ]; then
+    sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${CC}" 10
+    sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX}" 10
 
-    if [ -z "$QT_VERSION" ]; then
-        QT_VERSION="592"
-        QT_PKG="59"
-    fi
+    sudo update-alternatives --set gcc "/usr/bin/${CC}"
+    sudo update-alternatives --set g++ "/usr/bin/${CXX}"
 
     wget https://cmake.org/files/v3.15/cmake-3.15.5-Linux-x86_64.sh
     sudo sh cmake-3.15.5-Linux-x86_64.sh  --skip-license  --prefix=/usr/local
@@ -40,21 +32,4 @@ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
       tar xfz libsodium-1.0.17.tar.gz
       cd libsodium-1.0.17/
       ./configure && make && sudo make install )
-
-    sudo add-apt-repository -y ppa:beineri/opt-qt${QT_VERSION}-trusty
-    # needed for git-lfs, otherwise the follow apt update fails.
-    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 6B05F25D762E3157
-
-    # needed for mongodb repository: https://github.com/travis-ci/travis-ci/issues/9037
-    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
-
-    sudo apt update -qq
-    sudo apt install -qq -y \
-        qt${QT_PKG}base \
-        qt${QT_PKG}tools \
-        qt${QT_PKG}svg \
-        qt${QT_PKG}multimedia \
-        qt${QT_PKG}quickcontrols2 \
-        qt${QT_PKG}graphicaleffects \
-        liblmdb-dev
 fi
diff --git a/.ci/linux/deploy.sh b/.ci/linux/deploy.sh
index 524d72d5..ff8ef6ca 100755
--- a/.ci/linux/deploy.sh
+++ b/.ci/linux/deploy.sh
@@ -36,7 +36,7 @@ unset LD_LIBRARY_PATH
 
 ARCH=$(uname -m)
 export ARCH
-LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:$LD_LIBRARY_PATH
+LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:/usr/local/lib/:$LD_LIBRARY_PATH
 export LD_LIBRARY_PATH
 
 for res in ./linuxdeployqt*.AppImage
diff --git a/.ci/script.sh b/.ci/script.sh
index 06536278..7e9a8d81 100755
--- a/.ci/script.sh
+++ b/.ci/script.sh
@@ -3,17 +3,9 @@
 set -ex
 
 if [ "$TRAVIS_OS_NAME" = "linux" ]; then
-    export CC=${C_COMPILER}
-    export CXX=${CXX_COMPILER}
     # make build use all available cores
     export CMAKE_BUILD_PARALLEL_LEVEL=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l)
 
-    sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${C_COMPILER}" 10
-    sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX_COMPILER}" 10
-
-    sudo update-alternatives --set gcc "/usr/bin/${C_COMPILER}"
-    sudo update-alternatives --set g++ "/usr/bin/${CXX_COMPILER}"
-
     export PATH="/usr/local/bin/:${PATH}"
     cmake --version
 fi
diff --git a/.travis.yml b/.travis.yml
index 3abb8bfd..1e2bc5b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
 language: cpp
 sudo: required
-dist: trusty
+dist: xenial
 
 notifications:
   webhooks:
@@ -19,61 +19,108 @@ matrix:
     include:
         - os: osx
           compiler: clang
-          # Use the default osx image, because that one is actually tested to work with homebrew and probably the oldest supported version
-          #          osx_image: xcode9
+          # C++17 support
+          osx_image: xcode10.2
           env:
-              - DEPLOYMENT=1
-              - USE_BUNDLED_BOOST=0
-              - USE_BUNDLED_CMARK=0
-              - USE_BUNDLED_JSON=0
-              - MTX_STATIC=1
+            - DEPLOYMENT=1
+            - USE_BUNDLED_BOOST=0
+            - USE_BUNDLED_CMARK=0
+            - USE_BUNDLED_JSON=0
+            - MTX_STATIC=1
+          addons:
+            homebrew:
+              taps: nlohmann/json
+              packages:
+                - boost
+                - clang-format
+                - cmake
+                - cmark
+                - icu4c
+                - libsodium
+                - lmdb
+                - ninja
+                - openssl
+                - qt5
         - os: linux
-          compiler: gcc
+          compiler: gcc-7
           env:
-              - CXX_COMPILER=g++-5
-              - C_COMPILER=gcc-5
-              - QT_VERSION="-5.10.1"
-              - QT_PKG=510
+              - CXX=g++-7
+              - CC=gcc-7
+              - QT_PKG=512
               - DEPLOYMENT=1
               - USE_BUNDLED_BOOST=1
               - USE_BUNDLED_CMARK=1
               - USE_BUNDLED_JSON=1
           addons:
               apt:
-                  sources: ["ubuntu-toolchain-r-test"]
-                  packages: ["g++-5", "ninja-build"]
+                  sources: 
+                    - ubuntu-toolchain-r-test
+                    - sourceline: 'ppa:beineri/opt-qt-5.12.6-xenial'
+                  packages: 
+                    - g++-7 
+                    - ninja-build
+                    - qt512base
+                    - qt512tools
+                    - qt512svg
+                    - qt512multimedia
+                    - qt512quickcontrols2
+                    - qt512graphicaleffects
+                    - liblmdb-dev
+                    - libgl1-mesa-dev # needed for missing gl.h
         - os: linux
-          compiler: gcc
+          compiler: gcc-8
           env:
-              - CXX_COMPILER=g++-8
-              - C_COMPILER=gcc-8
-              - QT_VERSION=592
+              - CXX=g++-8
+              - CC=gcc-8
               - QT_PKG=59
               - USE_BUNDLED_BOOST=1
               - USE_BUNDLED_CMARK=1
               - USE_BUNDLED_JSON=1
           addons:
               apt:
-                  sources: ["ubuntu-toolchain-r-test"]
-                  packages: ["g++-8", "ninja-build"]
+                  sources: 
+                    - ubuntu-toolchain-r-test
+                    - sourceline: 'ppa:beineri/opt-qt597-xenial'
+                  packages: 
+                    - g++-8 
+                    - ninja-build
+                    - qt59base
+                    - qt59tools
+                    - qt59svg
+                    - qt59multimedia
+                    - qt59quickcontrols2
+                    - qt59graphicaleffects
+                    - liblmdb-dev
+                    - libgl1-mesa-dev # needed for missing gl.h
         - os: linux
-          compiler: clang
+          compiler: clang-6
           env:
-              - CXX_COMPILER=clang++-5.0
-              - C_COMPILER=clang-5.0
-              - QT_VERSION=592
+              - CXX=clang++-6.0
+              - CC=clang-6.0
               - QT_PKG=59
               - USE_BUNDLED_BOOST=1
               - USE_BUNDLED_CMARK=1
               - USE_BUNDLED_JSON=1
           addons:
               apt:
-                  sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-5.0"]
-                  packages: ["clang-5.0", "g++-7", "ninja-build"]
+                  sources: 
+                    - ubuntu-toolchain-r-test
+                    - llvm-toolchain-xenial-6.0
+                    - sourceline: 'ppa:beineri/opt-qt597-xenial'
+                  packages: 
+                    - clang++-6.0
+                    - g++-7 
+                    - ninja-build
+                    - qt59base
+                    - qt59tools
+                    - qt59svg
+                    - qt59multimedia
+                    - qt59quickcontrols2
+                    - qt59graphicaleffects
+                    - liblmdb-dev
+                    - libgl1-mesa-dev # needed for missing gl.h
 
 before_install:
-    - export CXX=${CXX_COMPILER}
-    - export CC=${C_COMPILER}
     # Use TRAVIS_TAG if defined, or the short commit SHA otherwise
     - export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)}
 install:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 67a1dfb0..39dadc64 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,9 +5,6 @@ option(ASAN "Compile with address sanitizers" OFF)
 
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
 
-add_definitions(-DBOOST_MPL_LIMIT_LIST_SIZE=30)
-add_definitions(-DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS)
-
 include(GNUInstallDirs)
 
 # Include Qt basic functions
@@ -15,8 +12,8 @@ include(QtCommon)
 
 project(nheko LANGUAGES C CXX)
 set(CPACK_PACKAGE_VERSION_MAJOR "0")
-set(CPACK_PACKAGE_VERSION_MINOR "6")
-set(CPACK_PACKAGE_VERSION_PATCH "4")
+set(CPACK_PACKAGE_VERSION_MINOR "7")
+set(CPACK_PACKAGE_VERSION_PATCH "0")
 set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR})
 set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR})
 set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
@@ -27,7 +24,7 @@ fix_project_version()
 
 # Set additional project information
 set(COMPANY "Nheko")
-set(COPYRIGHT "Copyright (c) 2018 Nheko Contributors")
+set(COPYRIGHT "Copyright (c) 2019 Nheko Contributors")
 set(IDENTIFIER "com.github.mujx.nheko")
 
 add_project_meta(META_FILES_TO_INCLUDE)
@@ -91,7 +88,7 @@ if (NOT MSVC)
     set(CMAKE_C_COMPILER gcc)
 endif(NOT MSVC)
 
-set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 if(NOT MSVC)
@@ -106,7 +103,7 @@ if(NOT MSVC)
         -fsized-deallocation \
         -fdiagnostics-color=always \
         -Wunreachable-code \
-	-std=c++14"
+	-std=c++17"
     )
     if (NOT CMAKE_COMPILER_IS_GNUCXX)
         # -Wshadow is buggy and broken in GCC, so do not enable it.
diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt
index fbe9275f..cf42b7b5 100644
--- a/deps/CMakeLists.txt
+++ b/deps/CMakeLists.txt
@@ -46,10 +46,10 @@ set(BOOST_SHA256
 
 set(
   MTXCLIENT_URL
-  https://github.com/Nheko-Reborn/mtxclient/archive/1945952864ef87a6afeb57b0beb80756d0647381.zip
+  https://github.com/Nheko-Reborn/mtxclient/archive/fd5208d85ecc9e077fe0fde5424ba225849cccf7.zip
   )
 set(MTXCLIENT_HASH
-    727cd145c0c1e9c168aaeded3b7cc9801c63b4da5e8cd0a4782982d08770816e)
+    24ef9d5562ed6d83d6c28f6c8de559487029f3bdc35d9fe8e5799283ff999513)
 set(
   TWEENY_URL
   https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz
diff --git a/deps/cmake/Json.cmake b/deps/cmake/Json.cmake
index 3b63550e..c5e66cea 100644
--- a/deps/cmake/Json.cmake
+++ b/deps/cmake/Json.cmake
@@ -13,7 +13,7 @@ ExternalProject_Add(
         -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
 
   BUILD_COMMAND ${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/json 
-  INSTALL_COMMAND make install
+  INSTALL_COMMAND ${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/json --target install
 )
 
 list(APPEND THIRD_PARTY_DEPS Json)
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 083dbe89..d3aec9db 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -17,6 +17,7 @@
 
 #include <limits>
 #include <stdexcept>
+#include <variant>
 
 #include <QByteArray>
 #include <QCoreApplication>
@@ -26,7 +27,6 @@
 #include <QSettings>
 #include <QStandardPaths>
 
-#include <boost/variant.hpp>
 #include <mtx/responses/common.hpp>
 
 #include "Cache.h"
@@ -395,7 +395,7 @@ Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr
         txn.commit();
 }
 
-boost::optional<mtx::crypto::OlmSessionPtr>
+std::optional<mtx::crypto::OlmSessionPtr>
 Cache::getOlmSession(const std::string &curve25519, const std::string &session_id)
 {
         using namespace mtx::crypto;
@@ -413,7 +413,7 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i
                 return unpickle<SessionObject>(data, SECRET);
         }
 
-        return boost::none;
+        return std::nullopt;
 }
 
 std::vector<std::string>
@@ -967,8 +967,8 @@ Cache::saveState(const mtx::responses::Sync &res)
                 bool has_new_tags = false;
                 for (const auto &evt : room.second.account_data.events) {
                         // for now only fetch tag events
-                        if (evt.type() == typeid(Event<account_data::Tag>)) {
-                                auto tags_evt = boost::get<Event<account_data::Tag>>(evt);
+                        if (std::holds_alternative<Event<account_data::Tag>>(evt)) {
+                                auto tags_evt = std::get<Event<account_data::Tag>>(evt);
                                 has_new_tags  = true;
                                 for (const auto &tag : tags_evt.content.tags) {
                                         updatedInfo.tags.push_back(tag.first);
@@ -1049,19 +1049,17 @@ Cache::saveInvite(lmdb::txn &txn,
         using namespace mtx::events::state;
 
         for (const auto &e : room.invite_state) {
-                if (boost::get<StrippedEvent<Member>>(&e) != nullptr) {
-                        auto msg = boost::get<StrippedEvent<Member>>(e);
+                if (auto msg = std::get_if<StrippedEvent<Member>>(&e)) {
+                        auto display_name = msg->content.display_name.empty()
+                                              ? msg->state_key
+                                              : msg->content.display_name;
 
-                        auto display_name = msg.content.display_name.empty()
-                                              ? msg.state_key
-                                              : msg.content.display_name;
-
-                        MemberInfo tmp{display_name, msg.content.avatar_url};
+                        MemberInfo tmp{display_name, msg->content.avatar_url};
 
                         lmdb::dbi_put(
-                          txn, membersdb, lmdb::val(msg.state_key), lmdb::val(json(tmp).dump()));
+                          txn, membersdb, lmdb::val(msg->state_key), lmdb::val(json(tmp).dump()));
                 } else {
-                        boost::apply_visitor(
+                        std::visit(
                           [&txn, &statesdb](auto msg) {
                                   bool res = lmdb::dbi_put(txn,
                                                            statesdb,
@@ -1122,7 +1120,7 @@ Cache::roomsWithTagUpdates(const mtx::responses::Sync &res)
         for (const auto &room : res.rooms.join) {
                 bool hasUpdates = false;
                 for (const auto &evt : room.second.account_data.events) {
-                        if (evt.type() == typeid(Event<account_data::Tag>)) {
+                        if (std::holds_alternative<Event<account_data::Tag>>(evt)) {
                                 hasUpdates = true;
                         }
                 }
@@ -1940,7 +1938,7 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
                 if (isStateEvent(e))
                         continue;
 
-                if (boost::get<RedactionEvent<msg::Redaction>>(&e) != nullptr)
+                if (std::holds_alternative<RedactionEvent<msg::Redaction>>(e))
                         continue;
 
                 json obj = json::object();
diff --git a/src/Cache.h b/src/Cache.h
index f5e1cfa0..878ac9ce 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -17,7 +17,8 @@
 
 #pragma once
 
-#include <boost/optional.hpp>
+#include <mutex>
+#include <optional>
 
 #include <QDateTime>
 #include <QDir>
@@ -25,11 +26,11 @@
 #include <QString>
 
 #include <lmdb++.h>
+#include <nlohmann/json.hpp>
+
 #include <mtx/events/join_rules.hpp>
 #include <mtx/responses.hpp>
 #include <mtxclient/crypto/client.hpp>
-#include <mutex>
-#include <nlohmann/json.hpp>
 
 #include "Logging.h"
 #include "MatrixClient.h"
@@ -453,8 +454,8 @@ public:
         //
         void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
         std::vector<std::string> getOlmSessions(const std::string &curve25519);
-        boost::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
-                                                                  const std::string &session_id);
+        std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
+                                                                const std::string &session_id);
 
         void saveOlmAccount(const std::string &pickled);
         std::string restoreOlmAccount();
@@ -517,52 +518,50 @@ private:
                 using namespace mtx::events;
                 using namespace mtx::events::state;
 
-                if (boost::get<StateEvent<Member>>(&event) != nullptr) {
-                        const auto e = boost::get<StateEvent<Member>>(event);
-
-                        switch (e.content.membership) {
+                if (auto e = std::get_if<StateEvent<Member>>(&event); e != nullptr) {
+                        switch (e->content.membership) {
                         //
                         // We only keep users with invite or join membership.
                         //
                         case Membership::Invite:
                         case Membership::Join: {
-                                auto display_name = e.content.display_name.empty()
-                                                      ? e.state_key
-                                                      : e.content.display_name;
+                                auto display_name = e->content.display_name.empty()
+                                                      ? e->state_key
+                                                      : e->content.display_name;
 
                                 // Lightweight representation of a member.
-                                MemberInfo tmp{display_name, e.content.avatar_url};
+                                MemberInfo tmp{display_name, e->content.avatar_url};
 
                                 lmdb::dbi_put(txn,
                                               membersdb,
-                                              lmdb::val(e.state_key),
+                                              lmdb::val(e->state_key),
                                               lmdb::val(json(tmp).dump()));
 
                                 insertDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e.state_key),
+                                                  QString::fromStdString(e->state_key),
                                                   QString::fromStdString(display_name));
 
                                 insertAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e.state_key),
-                                                QString::fromStdString(e.content.avatar_url));
+                                                QString::fromStdString(e->state_key),
+                                                QString::fromStdString(e->content.avatar_url));
 
                                 break;
                         }
                         default: {
                                 lmdb::dbi_del(
-                                  txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
+                                  txn, membersdb, lmdb::val(e->state_key), lmdb::val(""));
 
                                 removeDisplayName(QString::fromStdString(room_id),
-                                                  QString::fromStdString(e.state_key));
+                                                  QString::fromStdString(e->state_key));
                                 removeAvatarUrl(QString::fromStdString(room_id),
-                                                QString::fromStdString(e.state_key));
+                                                QString::fromStdString(e->state_key));
 
                                 break;
                         }
                         }
 
                         return;
-                } else if (boost::get<StateEvent<Encryption>>(&event) != nullptr) {
+                } else if (std::holds_alternative<StateEvent<Encryption>>(event)) {
                         setEncryptedRoom(txn, room_id);
                         return;
                 }
@@ -570,7 +569,7 @@ private:
                 if (!isStateEvent(event))
                         return;
 
-                boost::apply_visitor(
+                std::visit(
                   [&txn, &statesdb](auto e) {
                           lmdb::dbi_put(
                             txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
@@ -584,17 +583,17 @@ private:
                 using namespace mtx::events;
                 using namespace mtx::events::state;
 
-                return boost::get<StateEvent<Aliases>>(&e) != nullptr ||
-                       boost::get<StateEvent<state::Avatar>>(&e) != nullptr ||
-                       boost::get<StateEvent<CanonicalAlias>>(&e) != nullptr ||
-                       boost::get<StateEvent<Create>>(&e) != nullptr ||
-                       boost::get<StateEvent<GuestAccess>>(&e) != nullptr ||
-                       boost::get<StateEvent<HistoryVisibility>>(&e) != nullptr ||
-                       boost::get<StateEvent<JoinRules>>(&e) != nullptr ||
-                       boost::get<StateEvent<Name>>(&e) != nullptr ||
-                       boost::get<StateEvent<Member>>(&e) != nullptr ||
-                       boost::get<StateEvent<PowerLevels>>(&e) != nullptr ||
-                       boost::get<StateEvent<Topic>>(&e) != nullptr;
+                return std::holds_alternative<StateEvent<Aliases>>(e) ||
+                       std::holds_alternative<StateEvent<state::Avatar>>(e) ||
+                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+                       std::holds_alternative<StateEvent<Create>>(e) ||
+                       std::holds_alternative<StateEvent<GuestAccess>>(e) ||
+                       std::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
+                       std::holds_alternative<StateEvent<JoinRules>>(e) ||
+                       std::holds_alternative<StateEvent<Name>>(e) ||
+                       std::holds_alternative<StateEvent<Member>>(e) ||
+                       std::holds_alternative<StateEvent<PowerLevels>>(e) ||
+                       std::holds_alternative<StateEvent<Topic>>(e);
         }
 
         template<class T>
@@ -603,11 +602,11 @@ private:
                 using namespace mtx::events;
                 using namespace mtx::events::state;
 
-                return boost::get<StateEvent<state::Avatar>>(&e) != nullptr ||
-                       boost::get<StateEvent<CanonicalAlias>>(&e) != nullptr ||
-                       boost::get<StateEvent<Name>>(&e) != nullptr ||
-                       boost::get<StateEvent<Member>>(&e) != nullptr ||
-                       boost::get<StateEvent<Topic>>(&e) != nullptr;
+                return std::holds_alternative<StateEvent<state::Avatar>>(e) ||
+                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+                       std::holds_alternative<StateEvent<Name>>(e) ||
+                       std::holds_alternative<StateEvent<Member>>(e) ||
+                       std::holds_alternative<StateEvent<Topic>>(e);
         }
 
         bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
@@ -615,11 +614,11 @@ private:
                 using namespace mtx::events;
                 using namespace mtx::events::state;
 
-                return boost::get<StrippedEvent<state::Avatar>>(&e) != nullptr ||
-                       boost::get<StrippedEvent<CanonicalAlias>>(&e) != nullptr ||
-                       boost::get<StrippedEvent<Name>>(&e) != nullptr ||
-                       boost::get<StrippedEvent<Member>>(&e) != nullptr ||
-                       boost::get<StrippedEvent<Topic>>(&e) != nullptr;
+                return std::holds_alternative<StrippedEvent<state::Avatar>>(e) ||
+                       std::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
+                       std::holds_alternative<StrippedEvent<Name>>(e) ||
+                       std::holds_alternative<StrippedEvent<Member>>(e) ||
+                       std::holds_alternative<StrippedEvent<Topic>>(e);
         }
 
         void saveInvites(lmdb::txn &txn,
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 35d262ac..c496acab 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -54,7 +54,7 @@ constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
 constexpr int RETRY_TIMEOUT               = 5'000;
 constexpr size_t MAX_ONETIME_KEYS         = 50;
 
-Q_DECLARE_METATYPE(boost::optional<mtx::crypto::EncryptedFile>)
+Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
 
 ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
   : QWidget(parent)
@@ -64,8 +64,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
 {
         setObjectName("chatPage");
 
-        qRegisterMetaType<boost::optional<mtx::crypto::EncryptedFile>>(
-          "boost::optional<mtx::crypto::EncryptedFile>");
+        qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>(
+          "std::optional<mtx::crypto::EncryptedFile>");
 
         topLayout_ = new QHBoxLayout(this);
         topLayout_->setSpacing(0);
@@ -318,7 +318,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
 
                   auto bin     = dev->peek(dev->size());
                   auto payload = std::string(bin.data(), bin.size());
-                  boost::optional<mtx::crypto::EncryptedFile> encryptedFile;
+                  std::optional<mtx::crypto::EncryptedFile> encryptedFile;
                   if (cache::client()->isRoomEncrypted(current_room_.toStdString())) {
                           mtx::crypto::BinaryBuf buf;
                           std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
@@ -371,7 +371,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                 this,
                 [this](QString roomid,
                        QString filename,
-                       boost::optional<mtx::crypto::EncryptedFile> encryptedFile,
+                       std::optional<mtx::crypto::EncryptedFile> encryptedFile,
                        QString url,
                        QString mimeClass,
                        QString mime,
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 20e156af..6ca30b3d 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -18,8 +18,9 @@
 #pragma once
 
 #include <atomic>
-#include <boost/optional.hpp>
-#include <boost/variant.hpp>
+#include <optional>
+#include <variant>
+
 #include <mtx/common.hpp>
 #include <mtx/responses.hpp>
 
@@ -98,7 +99,7 @@ signals:
         void uploadFailed(const QString &msg);
         void mediaUploaded(const QString &roomid,
                            const QString &filename,
-                           const boost::optional<mtx::crypto::EncryptedFile> &file,
+                           const std::optional<mtx::crypto::EncryptedFile> &file,
                            const QString &url,
                            const QString &mimeClass,
                            const QString &mime,
@@ -252,9 +253,8 @@ ChatPage::getMemberships(const std::vector<Collection> &collection) const
         using Member = mtx::events::StateEvent<mtx::events::state::Member>;
 
         for (const auto &event : collection) {
-                if (boost::get<Member>(event) != nullptr) {
-                        auto member = boost::get<Member>(event);
-                        memberships.emplace(member.state_key, member);
+                if (auto member = std::get_if<Member>(event)) {
+                        memberships.emplace(member->state_key, *member);
                 }
         }
 
diff --git a/src/Olm.cpp b/src/Olm.cpp
index c1598570..9c1a25df 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -1,4 +1,4 @@
-#include <boost/variant.hpp>
+#include <variant>
 
 #include "Olm.h"
 
@@ -289,14 +289,13 @@ request_keys(const std::string &room_id, const std::string &event_id)
                           return;
                   }
 
-                  if (boost::get<EncryptedEvent<msg::Encrypted>>(&res) == nullptr) {
+                  if (!std::holds_alternative<EncryptedEvent<msg::Encrypted>>(res)) {
                           nhlog::net()->info(
                             "retrieved event is not encrypted: {} from {}", event_id, room_id);
                           return;
                   }
 
-                  olm::send_key_request_for(room_id,
-                                            boost::get<EncryptedEvent<msg::Encrypted>>(res));
+                  olm::send_key_request_for(room_id, std::get<EncryptedEvent<msg::Encrypted>>(res));
           });
 }
 
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 8f9e0643..3d69162f 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -9,9 +9,10 @@
 #include <QSettings>
 #include <QTextDocument>
 #include <QXmlStreamReader>
+
 #include <cmath>
+#include <variant>
 
-#include <boost/variant.hpp>
 #include <cmark.h>
 
 #include "Config.h"
@@ -122,34 +123,33 @@ utils::getMessageDescription(const TimelineEvent &event,
         using Video     = mtx::events::RoomEvent<mtx::events::msg::Video>;
         using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
 
-        if (boost::get<Audio>(&event) != nullptr) {
+        if (std::holds_alternative<Audio>(event)) {
                 return createDescriptionInfo<Audio>(event, localUser, room_id);
-        } else if (boost::get<Emote>(&event) != nullptr) {
+        } else if (std::holds_alternative<Emote>(event)) {
                 return createDescriptionInfo<Emote>(event, localUser, room_id);
-        } else if (boost::get<File>(&event) != nullptr) {
+        } else if (std::holds_alternative<File>(event)) {
                 return createDescriptionInfo<File>(event, localUser, room_id);
-        } else if (boost::get<Image>(&event) != nullptr) {
+        } else if (std::holds_alternative<Image>(event)) {
                 return createDescriptionInfo<Image>(event, localUser, room_id);
-        } else if (boost::get<Notice>(&event) != nullptr) {
+        } else if (std::holds_alternative<Notice>(event)) {
                 return createDescriptionInfo<Notice>(event, localUser, room_id);
-        } else if (boost::get<Text>(&event) != nullptr) {
+        } else if (std::holds_alternative<Text>(event)) {
                 return createDescriptionInfo<Text>(event, localUser, room_id);
-        } else if (boost::get<Video>(&event) != nullptr) {
+        } else if (std::holds_alternative<Video>(event)) {
                 return createDescriptionInfo<Video>(event, localUser, room_id);
-        } else if (boost::get<mtx::events::Sticker>(&event) != nullptr) {
+        } else if (std::holds_alternative<mtx::events::Sticker>(event)) {
                 return createDescriptionInfo<mtx::events::Sticker>(event, localUser, room_id);
-        } else if (boost::get<Encrypted>(&event) != nullptr) {
-                const auto msg    = boost::get<Encrypted>(event);
-                const auto sender = QString::fromStdString(msg.sender);
+        } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {
+                const auto sender = QString::fromStdString(msg->sender);
 
                 const auto username = Cache::displayName(room_id, sender);
-                const auto ts       = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
+                const auto ts       = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts);
 
                 DescInfo info;
                 info.userid    = sender;
                 info.body      = QString(" %1").arg(messageDescription<Encrypted>());
                 info.timestamp = utils::descriptiveTime(ts);
-                info.event_id  = QString::fromStdString(msg.event_id);
+                info.event_id  = QString::fromStdString(msg->event_id);
                 info.datetime  = ts;
 
                 return info;
@@ -217,30 +217,25 @@ utils::levenshtein_distance(const std::string &s1, const std::string &s2)
 }
 
 QString
-utils::event_body(const mtx::events::collections::TimelineEvents &event)
+utils::event_body(const mtx::events::collections::TimelineEvents &e)
 {
         using namespace mtx::events;
-        using namespace mtx::events::msg;
-
-        if (boost::get<RoomEvent<Audio>>(&event) != nullptr) {
-                return message_body<RoomEvent<Audio>>(event);
-        } else if (boost::get<RoomEvent<Emote>>(&event) != nullptr) {
-                return message_body<RoomEvent<Emote>>(event);
-        } else if (boost::get<RoomEvent<File>>(&event) != nullptr) {
-                return message_body<RoomEvent<File>>(event);
-        } else if (boost::get<RoomEvent<Image>>(&event) != nullptr) {
-                return message_body<RoomEvent<Image>>(event);
-        } else if (boost::get<RoomEvent<Notice>>(&event) != nullptr) {
-                return message_body<RoomEvent<Notice>>(event);
-        } else if (boost::get<Sticker>(&event) != nullptr) {
-                return message_body<Sticker>(event);
-        } else if (boost::get<RoomEvent<Text>>(&event) != nullptr) {
-                return message_body<RoomEvent<Text>>(event);
-        } else if (boost::get<RoomEvent<Video>>(&event) != nullptr) {
-                return message_body<RoomEvent<Video>>(event);
-        }
-
-        return QString();
+        if (auto ev = std::get_if<RoomEvent<msg::Audio>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::Emote>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::File>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::Image>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::Notice>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::Text>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+        if (auto ev = std::get_if<RoomEvent<msg::Video>>(&e); ev != nullptr)
+                return QString::fromStdString(ev->content.body);
+
+        return "";
 }
 
 QPixmap
diff --git a/src/Utils.h b/src/Utils.h
index bdb51844..6c0cf0dd 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <boost/variant.hpp>
+#include <variant>
 
 #include "Cache.h"
 #include "RoomInfoListItem.h"
@@ -165,7 +165,7 @@ template<class T, class Event>
 DescInfo
 createDescriptionInfo(const Event &event, const QString &localUser, const QString &room_id)
 {
-        const auto msg    = boost::get<T>(event);
+        const auto msg    = std::get<T>(event);
         const auto sender = QString::fromStdString(msg.sender);
 
         const auto username = Cache::displayName(room_id, sender);
@@ -200,25 +200,25 @@ erase_if(ContainerT &items, const PredicateT &predicate)
 inline uint64_t
 event_timestamp(const mtx::events::collections::TimelineEvents &event)
 {
-        return boost::apply_visitor([](auto msg) { return msg.origin_server_ts; }, event);
+        return std::visit([](auto msg) { return msg.origin_server_ts; }, event);
 }
 
 inline nlohmann::json
 serialize_event(const mtx::events::collections::TimelineEvents &event)
 {
-        return boost::apply_visitor([](auto msg) { return json(msg); }, event);
+        return std::visit([](auto msg) { return json(msg); }, event);
 }
 
 inline mtx::events::EventType
 event_type(const mtx::events::collections::TimelineEvents &event)
 {
-        return boost::apply_visitor([](auto msg) { return msg.type; }, event);
+        return std::visit([](auto msg) { return msg.type; }, event);
 }
 
 inline std::string
 event_id(const mtx::events::collections::TimelineEvents &event)
 {
-        return boost::apply_visitor([](auto msg) { return msg.event_id; }, event);
+        return std::visit([](auto msg) { return msg.event_id; }, event);
 }
 
 inline QString
@@ -230,15 +230,14 @@ eventId(const mtx::events::collections::TimelineEvents &event)
 inline QString
 event_sender(const mtx::events::collections::TimelineEvents &event)
 {
-        return boost::apply_visitor([](auto msg) { return QString::fromStdString(msg.sender); },
-                                    event);
+        return std::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
 }
 
 template<class T>
 QString
 message_body(const mtx::events::collections::TimelineEvents &event)
 {
-        return QString::fromStdString(boost::get<T>(event).content.body);
+        return QString::fromStdString(std::get<T>(event).content.body);
 }
 
 //! Calculate the Levenshtein distance between two strings with character skipping.
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index e49fcf57..1e069d50 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -117,17 +117,17 @@ eventFormattedBody(const mtx::events::RoomEvent<T> &e)
 }
 
 template<class T>
-boost::optional<mtx::crypto::EncryptedFile>
+std::optional<mtx::crypto::EncryptedFile>
 eventEncryptionInfo(const mtx::events::Event<T> &)
 {
-        return boost::none;
+        return std::nullopt;
 }
 
 template<class T>
 auto
 eventEncryptionInfo(const mtx::events::RoomEvent<T> &e) -> std::enable_if_t<
-  std::is_same<decltype(e.content.file), boost::optional<mtx::crypto::EncryptedFile>>::value,
-  boost::optional<mtx::crypto::EncryptedFile>>
+  std::is_same<decltype(e.content.file), std::optional<mtx::crypto::EncryptedFile>>::value,
+  std::optional<mtx::crypto::EncryptedFile>>
 {
         return e.content.file;
 }
@@ -407,7 +407,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 }
                 eventOrder[idx] = event_id;
                 auto ev         = events.value(txn_id);
-                ev              = boost::apply_visitor(
+                ev              = std::visit(
                   [event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
                           auto eventCopy     = e;
                           eventCopy.event_id = event_id.toStdString();
@@ -483,29 +483,30 @@ TimelineModel::data(const QModelIndex &index, int role) const
 
         mtx::events::collections::TimelineEvents event = events.value(id);
 
-        if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
+        if (auto e =
+              std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
                 event = decryptEvent(*e).event;
         }
 
         switch (role) {
         case Section: {
-                QDateTime date = boost::apply_visitor(
-                  [](const auto &e) -> QDateTime { return eventTimestamp(e); }, event);
+                QDateTime date =
+                  std::visit([](const auto &e) -> QDateTime { return eventTimestamp(e); }, event);
                 date.setTime(QTime());
 
                 QString userId =
-                  boost::apply_visitor([](const auto &e) -> QString { return senderId(e); }, event);
+                  std::visit([](const auto &e) -> QString { return senderId(e); }, event);
 
                 for (int r = index.row() - 1; r > 0; r--) {
                         auto tempEv        = events.value(eventOrder[r]);
-                        QDateTime prevDate = boost::apply_visitor(
+                        QDateTime prevDate = std::visit(
                           [](const auto &e) -> QDateTime { return eventTimestamp(e); }, tempEv);
                         prevDate.setTime(QTime());
                         if (prevDate != date)
                                 return QString("%2 %1").arg(date.toMSecsSinceEpoch()).arg(userId);
 
-                        QString prevUserId = boost::apply_visitor(
-                          [](const auto &e) -> QString { return senderId(e); }, tempEv);
+                        QString prevUserId =
+                          std::visit([](const auto &e) -> QString { return senderId(e); }, tempEv);
                         if (userId != prevUserId)
                                 break;
                 }
@@ -513,62 +514,61 @@ TimelineModel::data(const QModelIndex &index, int role) const
                 return QString("%1").arg(userId);
         }
         case UserId:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return senderId(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return senderId(e); }, event));
         case UserName:
-                return QVariant(displayName(boost::apply_visitor(
-                  [](const auto &e) -> QString { return senderId(e); }, event)));
+                return QVariant(displayName(
+                  std::visit([](const auto &e) -> QString { return senderId(e); }, event)));
 
         case Timestamp:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QDateTime { return eventTimestamp(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QDateTime { return eventTimestamp(e); }, event));
         case Type:
-                return QVariant(boost::apply_visitor(
+                return QVariant(std::visit(
                   [](const auto &e) -> qml_mtx_events::EventType { return toRoomEventType(e); },
                   event));
         case Body:
-                return QVariant(utils::replaceEmoji(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventBody(e); }, event)));
+                return QVariant(utils::replaceEmoji(
+                  std::visit([](const auto &e) -> QString { return eventBody(e); }, event)));
         case FormattedBody:
                 return QVariant(
                   utils::replaceEmoji(
-                    utils::linkifyMessage(boost::apply_visitor(
+                    utils::linkifyMessage(std::visit(
                       [](const auto &e) -> QString { return eventFormattedBody(e); }, event)))
                     .remove("<mx-reply>")
                     .remove("</mx-reply>"));
         case Url:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventUrl(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventUrl(e); }, event));
         case ThumbnailUrl:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventThumbnailUrl(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventThumbnailUrl(e); }, event));
         case Filename:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventFilename(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventFilename(e); }, event));
         case Filesize:
-                return QVariant(boost::apply_visitor(
+                return QVariant(std::visit(
                   [](const auto &e) -> QString {
                           return utils::humanReadableFileSize(eventFilesize(e));
                   },
                   event));
         case MimeType:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventMimeType(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventMimeType(e); }, event));
         case Height:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> qulonglong { return eventHeight(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> qulonglong { return eventHeight(e); }, event));
         case Width:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> qulonglong { return eventWidth(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> qulonglong { return eventWidth(e); }, event));
         case ProportionalHeight:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> double { return eventPropHeight(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> double { return eventPropHeight(e); }, event));
         case Id:
                 return id;
         case State:
                 // only show read receipts for messages not from us
-                if (boost::apply_visitor([](const auto &e) -> QString { return senderId(e); },
-                                         event)
+                if (std::visit([](const auto &e) -> QString { return senderId(e); }, event)
                       .toStdString() != http::client()->user_id().to_string())
                         return qml_mtx_events::Empty;
                 else if (failed.contains(id))
@@ -582,20 +582,20 @@ TimelineModel::data(const QModelIndex &index, int role) const
                         return qml_mtx_events::Received;
         case IsEncrypted: {
                 auto tempEvent = events[id];
-                return boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
-                         &tempEvent) != nullptr;
+                return std::holds_alternative<
+                  mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(tempEvent);
         }
         case ReplyTo: {
-                QString evId = boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventRelatesTo(e); }, event);
+                QString evId =
+                  std::visit([](const auto &e) -> QString { return eventRelatesTo(e); }, event);
                 return QVariant(evId);
         }
         case RoomName:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventRoomName(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventRoomName(e); }, event));
         case RoomTopic:
-                return QVariant(boost::apply_visitor(
-                  [](const auto &e) -> QString { return eventRoomTopic(e); }, event));
+                return QVariant(
+                  std::visit([](const auto &e) -> QString { return eventRoomTopic(e); }, event));
         default:
                 return QVariant();
         }
@@ -646,13 +646,12 @@ TimelineModel::updateLastMessage()
 {
         for (auto it = eventOrder.rbegin(); it != eventOrder.rend(); ++it) {
                 auto event = events.value(*it);
-                if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+                if (auto e = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
                       &event)) {
                         event = decryptEvent(*e).event;
                 }
 
-                if (!boost::apply_visitor([](const auto &e) -> bool { return isMessage(e); },
-                                          event))
+                if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, event))
                         continue;
 
                 auto description = utils::getMessageDescription(
@@ -668,8 +667,7 @@ TimelineModel::internalAddEvents(
 {
         std::vector<QString> ids;
         for (auto e : timeline) {
-                QString id =
-                  boost::apply_visitor([](const auto &e) -> QString { return eventId(e); }, e);
+                QString id = std::visit([](const auto &e) -> QString { return eventId(e); }, e);
 
                 if (this->events.contains(id)) {
                         this->events.insert(id, e);
@@ -679,12 +677,12 @@ TimelineModel::internalAddEvents(
                 }
 
                 if (auto redaction =
-                      boost::get<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
+                      std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
                         QString redacts = QString::fromStdString(redaction->redacts);
                         auto redacted   = std::find(eventOrder.begin(), eventOrder.end(), redacts);
 
                         if (redacted != eventOrder.end()) {
-                                auto redactedEvent = boost::apply_visitor(
+                                auto redactedEvent = std::visit(
                                   [](const auto &ev)
                                     -> mtx::events::RoomEvent<mtx::events::msg::Redacted> {
                                           mtx::events::RoomEvent<mtx::events::msg::Redacted>
@@ -707,11 +705,11 @@ TimelineModel::internalAddEvents(
                 }
 
                 if (auto event =
-                      boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&e)) {
+                      std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&e)) {
                         e = decryptEvent(*event).event;
                 }
-                auto encInfo = boost::apply_visitor(
-                  [](const auto &ev) -> boost::optional<mtx::crypto::EncryptedFile> {
+                auto encInfo = std::visit(
+                  [](const auto &ev) -> std::optional<mtx::crypto::EncryptedFile> {
                           return eventEncryptionInfo(ev);
                   },
                   e);
@@ -947,11 +945,12 @@ void
 TimelineModel::replyAction(QString id)
 {
         auto event = events.value(id);
-        if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
+        if (auto e =
+              std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
                 event = decryptEvent(*e).event;
         }
 
-        RelatedInfo related = boost::apply_visitor(
+        RelatedInfo related = std::visit(
           [](const auto &ev) -> RelatedInfo {
                   RelatedInfo related_   = {};
                   related_.quoted_user   = QString::fromStdString(ev.sender);
@@ -959,10 +958,10 @@ TimelineModel::replyAction(QString id)
                   return related_;
           },
           event);
-        related.type        = mtx::events::getMessageType(boost::apply_visitor(
-          [](const auto &e) -> std::string { return eventMsgType(e); }, event));
-        related.quoted_body = boost::apply_visitor(
-          [](const auto &e) -> QString { return eventFormattedBody(e); }, event);
+        related.type = mtx::events::getMessageType(
+          std::visit([](const auto &e) -> std::string { return eventMsgType(e); }, event));
+        related.quoted_body =
+          std::visit([](const auto &e) -> QString { return eventFormattedBody(e); }, event);
         related.quoted_body.remove(QRegularExpression(
           "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
         nhlog::ui()->debug("after replacement: {}", related.quoted_body.toStdString());
@@ -1396,7 +1395,7 @@ TimelineModel::processOnePendingMessage()
         QString txn_id_qstr = pending.first();
 
         auto event = events.value(txn_id_qstr);
-        boost::apply_visitor(SendMessageVisitor{txn_id_qstr, this}, event);
+        std::visit(SendMessageVisitor{txn_id_qstr, this}, event);
 }
 
 void
@@ -1405,7 +1404,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
         internalAddEvents({event});
 
         QString txn_id_qstr =
-          boost::apply_visitor([](const auto &e) -> QString { return eventId(e); }, event);
+          std::visit([](const auto &e) -> QString { return eventId(e); }, event);
         beginInsertRows(QModelIndex(),
                         static_cast<int>(this->eventOrder.size()),
                         static_cast<int>(this->eventOrder.size()));
@@ -1423,22 +1422,22 @@ TimelineModel::saveMedia(QString eventId) const
 {
         mtx::events::collections::TimelineEvents event = events.value(eventId);
 
-        if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
+        if (auto e =
+              std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
                 event = decryptEvent(*e).event;
         }
 
-        QString mxcUrl =
-          boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event);
+        QString mxcUrl = std::visit([](const auto &e) -> QString { return eventUrl(e); }, event);
         QString originalFilename =
-          boost::apply_visitor([](const auto &e) -> QString { return eventFilename(e); }, event);
+          std::visit([](const auto &e) -> QString { return eventFilename(e); }, event);
         QString mimeType =
-          boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event);
+          std::visit([](const auto &e) -> QString { return eventMimeType(e); }, event);
 
-        using EncF = boost::optional<mtx::crypto::EncryptedFile>;
+        using EncF = std::optional<mtx::crypto::EncryptedFile>;
         EncF encryptionInfo =
-          boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
+          std::visit([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
 
-        qml_mtx_events::EventType eventType = boost::apply_visitor(
+        qml_mtx_events::EventType eventType = std::visit(
           [](const auto &e) -> qml_mtx_events::EventType { return toRoomEventType(e); }, event);
 
         QString dialogTitle;
@@ -1500,18 +1499,18 @@ TimelineModel::cacheMedia(QString eventId)
 {
         mtx::events::collections::TimelineEvents event = events.value(eventId);
 
-        if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
+        if (auto e =
+              std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
                 event = decryptEvent(*e).event;
         }
 
-        QString mxcUrl =
-          boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event);
+        QString mxcUrl = std::visit([](const auto &e) -> QString { return eventUrl(e); }, event);
         QString mimeType =
-          boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event);
+          std::visit([](const auto &e) -> QString { return eventMimeType(e); }, event);
 
-        using EncF = boost::optional<mtx::crypto::EncryptedFile>;
+        using EncF = std::optional<mtx::crypto::EncryptedFile>;
         EncF encryptionInfo =
-          boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
+          std::visit([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
 
         // If the message is a link to a non mxcUrl, don't download it
         if (!mxcUrl.startsWith("mxc://")) {
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 6e18d111..74e09a33 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -222,7 +222,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
 void
 TimelineViewManager::queueImageMessage(const QString &roomid,
                                        const QString &filename,
-                                       const boost::optional<mtx::crypto::EncryptedFile> &file,
+                                       const std::optional<mtx::crypto::EncryptedFile> &file,
                                        const QString &url,
                                        const QString &mime,
                                        uint64_t dsize,
@@ -243,7 +243,7 @@ void
 TimelineViewManager::queueFileMessage(
   const QString &roomid,
   const QString &filename,
-  const boost::optional<mtx::crypto::EncryptedFile> &encryptedFile,
+  const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
   const QString &url,
   const QString &mime,
   uint64_t dsize)
@@ -260,7 +260,7 @@ TimelineViewManager::queueFileMessage(
 void
 TimelineViewManager::queueAudioMessage(const QString &roomid,
                                        const QString &filename,
-                                       const boost::optional<mtx::crypto::EncryptedFile> &file,
+                                       const std::optional<mtx::crypto::EncryptedFile> &file,
                                        const QString &url,
                                        const QString &mime,
                                        uint64_t dsize)
@@ -277,7 +277,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
 void
 TimelineViewManager::queueVideoMessage(const QString &roomid,
                                        const QString &filename,
-                                       const boost::optional<mtx::crypto::EncryptedFile> &file,
+                                       const std::optional<mtx::crypto::EncryptedFile> &file,
                                        const QString &url,
                                        const QString &mime,
                                        uint64_t dsize)
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 9e8de616..587aa14e 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -56,26 +56,26 @@ public slots:
         void queueEmoteMessage(const QString &msg);
         void queueImageMessage(const QString &roomid,
                                const QString &filename,
-                               const boost::optional<mtx::crypto::EncryptedFile> &file,
+                               const std::optional<mtx::crypto::EncryptedFile> &file,
                                const QString &url,
                                const QString &mime,
                                uint64_t dsize,
                                const QSize &dimensions);
         void queueFileMessage(const QString &roomid,
                               const QString &filename,
-                              const boost::optional<mtx::crypto::EncryptedFile> &file,
+                              const std::optional<mtx::crypto::EncryptedFile> &file,
                               const QString &url,
                               const QString &mime,
                               uint64_t dsize);
         void queueAudioMessage(const QString &roomid,
                                const QString &filename,
-                               const boost::optional<mtx::crypto::EncryptedFile> &file,
+                               const std::optional<mtx::crypto::EncryptedFile> &file,
                                const QString &url,
                                const QString &mime,
                                uint64_t dsize);
         void queueVideoMessage(const QString &roomid,
                                const QString &filename,
-                               const boost::optional<mtx::crypto::EncryptedFile> &file,
+                               const std::optional<mtx::crypto::EncryptedFile> &file,
                                const QString &url,
                                const QString &mime,
                                uint64_t dsize);