From cfca7157b98c9dc8e0852fe6484bc3f75008af7d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 18 Sep 2021 00:22:33 +0200 Subject: Change indentation to 4 spaces --- .clang-format | 6 +- src/AvatarProvider.cpp | 69 +- src/AvatarProvider.h | 4 +- src/BlurhashProvider.cpp | 56 +- src/BlurhashProvider.h | 48 +- src/Cache.cpp | 6424 +++++++++++++++++----------------- src/CacheCryptoStructs.h | 154 +- src/CacheStructs.h | 86 +- src/Cache_p.h | 1286 ++++--- src/CallDevices.cpp | 443 ++- src/CallDevices.h | 43 +- src/CallManager.cpp | 918 +++-- src/CallManager.h | 151 +- src/ChatPage.cpp | 2081 ++++++----- src/ChatPage.h | 281 +- src/Clipboard.cpp | 7 +- src/Clipboard.h | 12 +- src/ColorImageProvider.cpp | 26 +- src/ColorImageProvider.h | 8 +- src/CombinedImagePackModel.cpp | 84 +- src/CombinedImagePackModel.h | 58 +- src/CompletionModelRoles.h | 6 +- src/CompletionProxyModel.cpp | 188 +- src/CompletionProxyModel.h | 283 +- src/DeviceVerificationFlow.cpp | 1279 ++++--- src/DeviceVerificationFlow.h | 353 +- src/EventAccessors.cpp | 489 ++- src/EventAccessors.h | 14 +- src/ImagePackListModel.cpp | 91 +- src/ImagePackListModel.h | 46 +- src/InviteesModel.cpp | 75 +- src/InviteesModel.h | 54 +- src/JdenticonProvider.cpp | 104 +- src/JdenticonProvider.h | 75 +- src/Logging.cpp | 117 +- src/LoginPage.cpp | 795 +++-- src/LoginPage.h | 128 +- src/MainWindow.cpp | 540 ++- src/MainWindow.h | 135 +- src/MatrixClient.cpp | 30 +- src/MemberList.cpp | 122 +- src/MemberList.h | 84 +- src/MxcImageProvider.cpp | 420 ++- src/MxcImageProvider.h | 58 +- src/Olm.cpp | 2792 +++++++-------- src/Olm.h | 32 +- src/ReadReceiptsModel.cpp | 147 +- src/ReadReceiptsModel.h | 74 +- src/RegisterPage.cpp | 791 ++--- src/RegisterPage.h | 118 +- src/RoomDirectoryModel.cpp | 280 +- src/RoomDirectoryModel.h | 94 +- src/RoomsModel.cpp | 102 +- src/RoomsModel.h | 38 +- src/SSOHandler.cpp | 64 +- src/SSOHandler.h | 16 +- src/SingleImagePackModel.cpp | 478 ++- src/SingleImagePackModel.h | 131 +- src/TrayIcon.cpp | 148 +- src/TrayIcon.h | 28 +- src/UserSettingsPage.cpp | 2301 ++++++------ src/UserSettingsPage.h | 731 ++-- src/UsersModel.cpp | 72 +- src/UsersModel.h | 36 +- src/Utils.cpp | 1085 +++--- src/Utils.h | 232 +- src/WebRTCSession.cpp | 1622 +++++---- src/WebRTCSession.h | 144 +- src/WelcomePage.cpp | 98 +- src/WelcomePage.h | 14 +- src/dialogs/CreateRoom.cpp | 254 +- src/dialogs/CreateRoom.h | 28 +- src/dialogs/FallbackAuth.cpp | 84 +- src/dialogs/FallbackAuth.h | 14 +- src/dialogs/ImageOverlay.cpp | 107 +- src/dialogs/ImageOverlay.h | 24 +- src/dialogs/JoinRoom.cpp | 66 +- src/dialogs/JoinRoom.h | 16 +- src/dialogs/LeaveRoom.cpp | 54 +- src/dialogs/LeaveRoom.h | 10 +- src/dialogs/Logout.cpp | 72 +- src/dialogs/Logout.h | 10 +- src/dialogs/PreviewUploadOverlay.cpp | 283 +- src/dialogs/PreviewUploadOverlay.h | 40 +- src/dialogs/ReCaptcha.cpp | 80 +- src/dialogs/ReCaptcha.h | 14 +- src/emoji/EmojiModel.cpp | 69 +- src/emoji/EmojiModel.h | 26 +- src/emoji/MacHelper.h | 4 +- src/emoji/Provider.h | 46 +- src/main.cpp | 403 ++- src/notifications/Manager.cpp | 48 +- src/notifications/Manager.h | 82 +- src/notifications/ManagerLinux.cpp | 357 +- src/notifications/ManagerMac.cpp | 70 +- src/notifications/ManagerWin.cpp | 100 +- src/timeline/CommunitiesModel.cpp | 373 +- src/timeline/CommunitiesModel.h | 102 +- src/timeline/DelegateChooser.cpp | 115 +- src/timeline/DelegateChooser.h | 94 +- src/timeline/EventStore.cpp | 1389 ++++---- src/timeline/EventStore.h | 230 +- src/timeline/InputBar.cpp | 1224 ++++--- src/timeline/InputBar.h | 172 +- src/timeline/Permissions.cpp | 34 +- src/timeline/Permissions.h | 24 +- src/timeline/Reaction.h | 26 +- src/timeline/RoomlistModel.cpp | 1595 +++++---- src/timeline/RoomlistModel.h | 310 +- src/timeline/TimelineModel.cpp | 3135 ++++++++--------- src/timeline/TimelineModel.h | 727 ++-- src/timeline/TimelineViewManager.cpp | 967 +++-- src/timeline/TimelineViewManager.h | 174 +- src/ui/Badge.cpp | 168 +- src/ui/Badge.h | 72 +- src/ui/DropShadow.cpp | 155 +- src/ui/DropShadow.h | 20 +- src/ui/FlatButton.cpp | 653 ++-- src/ui/FlatButton.h | 255 +- src/ui/FloatingButton.cpp | 106 +- src/ui/FloatingButton.h | 14 +- src/ui/InfoMessage.cpp | 58 +- src/ui/InfoMessage.h | 52 +- src/ui/Label.cpp | 14 +- src/ui/Label.h | 22 +- src/ui/LoadingIndicator.cpp | 64 +- src/ui/LoadingIndicator.h | 30 +- src/ui/Menu.h | 16 +- src/ui/MxcAnimatedImage.cpp | 279 +- src/ui/MxcAnimatedImage.h | 124 +- src/ui/MxcMediaProxy.cpp | 209 +- src/ui/MxcMediaProxy.h | 95 +- src/ui/NhekoCursorShape.cpp | 12 +- src/ui/NhekoCursorShape.h | 16 +- src/ui/NhekoDropArea.cpp | 16 +- src/ui/NhekoDropArea.h | 30 +- src/ui/NhekoGlobalObject.cpp | 58 +- src/ui/NhekoGlobalObject.h | 66 +- src/ui/OverlayModal.cpp | 46 +- src/ui/OverlayModal.h | 26 +- src/ui/OverlayWidget.cpp | 78 +- src/ui/OverlayWidget.h | 12 +- src/ui/Painter.h | 260 +- src/ui/RaisedButton.cpp | 96 +- src/ui/RaisedButton.h | 20 +- src/ui/Ripple.cpp | 72 +- src/ui/Ripple.h | 98 +- src/ui/RippleOverlay.cpp | 46 +- src/ui/RippleOverlay.h | 38 +- src/ui/RoomSettings.cpp | 893 +++-- src/ui/RoomSettings.h | 168 +- src/ui/SnackBar.cpp | 128 +- src/ui/SnackBar.h | 100 +- src/ui/TextField.cpp | 416 +-- src/ui/TextField.h | 168 +- src/ui/TextLabel.cpp | 128 +- src/ui/TextLabel.h | 40 +- src/ui/Theme.cpp | 116 +- src/ui/Theme.h | 56 +- src/ui/ThemeManager.cpp | 56 +- src/ui/ThemeManager.h | 16 +- src/ui/ToggleButton.cpp | 178 +- src/ui/ToggleButton.h | 110 +- src/ui/UserProfile.cpp | 512 ++- src/ui/UserProfile.h | 201 +- 165 files changed, 24152 insertions(+), 24981 deletions(-) diff --git a/.clang-format b/.clang-format index 059aee19..b5e2f017 100644 --- a/.clang-format +++ b/.clang-format @@ -1,14 +1,14 @@ --- Language: Cpp -Standard: Cpp11 -AccessModifierOffset: -8 +Standard: c++17 +AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AllowShortFunctionsOnASingleLine: true BasedOnStyle: Mozilla ColumnLimit: 100 IndentCaseLabels: false -IndentWidth: 8 +IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: false PointerAlignment: Right Cpp11BracedListStyle: true diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp index b9962cef..177bf903 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp @@ -22,45 +22,44 @@ namespace AvatarProvider { void resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback) { - const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size); + const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size); - QPixmap pixmap; - if (avatarUrl.isEmpty()) { - callback(pixmap); - return; - } + QPixmap pixmap; + if (avatarUrl.isEmpty()) { + callback(pixmap); + return; + } - if (avatar_cache.find(cacheKey, &pixmap)) { - callback(pixmap); - return; - } + if (avatar_cache.find(cacheKey, &pixmap)) { + callback(pixmap); + return; + } - MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")), - QSize(size, size), - [callback, cacheKey, recv = QPointer(receiver)]( - QString, QSize, QImage img, QString) { - if (!recv) - return; + MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")), + QSize(size, size), + [callback, cacheKey, recv = QPointer(receiver)]( + QString, QSize, QImage img, QString) { + if (!recv) + return; - auto proxy = std::make_shared(); - QObject::connect(proxy.get(), - &AvatarProxy::avatarDownloaded, - recv, - [callback, cacheKey](QPixmap pm) { - if (!pm.isNull()) - avatar_cache.insert( - cacheKey, pm); - callback(pm); - }); + auto proxy = std::make_shared(); + QObject::connect(proxy.get(), + &AvatarProxy::avatarDownloaded, + recv, + [callback, cacheKey](QPixmap pm) { + if (!pm.isNull()) + avatar_cache.insert(cacheKey, pm); + callback(pm); + }); - if (img.isNull()) { - emit proxy->avatarDownloaded(QPixmap{}); - return; - } + if (img.isNull()) { + emit proxy->avatarDownloaded(QPixmap{}); + return; + } - auto pm = QPixmap::fromImage(std::move(img)); - emit proxy->avatarDownloaded(pm); - }); + auto pm = QPixmap::fromImage(std::move(img)); + emit proxy->avatarDownloaded(pm); + }); } void @@ -70,8 +69,8 @@ resolve(const QString &room_id, QObject *receiver, AvatarCallback callback) { - auto avatarUrl = cache::avatarUrl(room_id, user_id); + auto avatarUrl = cache::avatarUrl(room_id, user_id); - resolve(std::move(avatarUrl), size, receiver, callback); + resolve(std::move(avatarUrl), size, receiver, callback); } } diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h index 173a2fba..efd0f330 100644 --- a/src/AvatarProvider.h +++ b/src/AvatarProvider.h @@ -12,10 +12,10 @@ using AvatarCallback = std::function; class AvatarProxy : public QObject { - Q_OBJECT + Q_OBJECT signals: - void avatarDownloaded(QPixmap pm); + void avatarDownloaded(QPixmap pm); }; namespace AvatarProvider { diff --git a/src/BlurhashProvider.cpp b/src/BlurhashProvider.cpp index aef618a2..e905474a 100644 --- a/src/BlurhashProvider.cpp +++ b/src/BlurhashProvider.cpp @@ -13,33 +13,33 @@ void BlurhashResponse::run() { - if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) { - m_error = QStringLiteral("Blurhash needs size request"); - emit finished(); - return; - } - if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) { - m_image = QImage(m_requestedSize, QImage::Format_RGB32); - m_image.fill(QColor(0, 0, 0)); - emit finished(); - return; - } - - auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(), - m_requestedSize.width(), - m_requestedSize.height()); - if (decoded.image.empty()) { - m_error = QStringLiteral("Failed decode!"); - emit finished(); - return; - } - - QImage image(decoded.image.data(), - (int)decoded.width, - (int)decoded.height, - (int)decoded.width * 3, - QImage::Format_RGB888); - - m_image = image.copy(); + if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) { + m_error = QStringLiteral("Blurhash needs size request"); emit finished(); + return; + } + if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) { + m_image = QImage(m_requestedSize, QImage::Format_RGB32); + m_image.fill(QColor(0, 0, 0)); + emit finished(); + return; + } + + auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(), + m_requestedSize.width(), + m_requestedSize.height()); + if (decoded.image.empty()) { + m_error = QStringLiteral("Failed decode!"); + emit finished(); + return; + } + + QImage image(decoded.image.data(), + (int)decoded.width, + (int)decoded.height, + (int)decoded.width * 3, + QImage::Format_RGB888); + + m_image = image.copy(); + emit finished(); } diff --git a/src/BlurhashProvider.h b/src/BlurhashProvider.h index ee89302c..1c8351f2 100644 --- a/src/BlurhashProvider.h +++ b/src/BlurhashProvider.h @@ -15,41 +15,41 @@ class BlurhashResponse , public QRunnable { public: - BlurhashResponse(const QString &id, const QSize &requestedSize) + BlurhashResponse(const QString &id, const QSize &requestedSize) - : m_id(id) - , m_requestedSize(requestedSize) - { - setAutoDelete(false); - } + : m_id(id) + , m_requestedSize(requestedSize) + { + setAutoDelete(false); + } - QQuickTextureFactory *textureFactory() const override - { - return QQuickTextureFactory::textureFactoryForImage(m_image); - } - QString errorString() const override { return m_error; } + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + QString errorString() const override { return m_error; } - void run() override; + void run() override; - QString m_id, m_error; - QSize m_requestedSize; - QImage m_image; + QString m_id, m_error; + QSize m_requestedSize; + QImage m_image; }; class BlurhashProvider : public QObject , public QQuickAsyncImageProvider { - Q_OBJECT + Q_OBJECT public slots: - QQuickImageResponse *requestImageResponse(const QString &id, - const QSize &requestedSize) override - { - BlurhashResponse *response = new BlurhashResponse(id, requestedSize); - pool.start(response); - return response; - } + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override + { + BlurhashResponse *response = new BlurhashResponse(id, requestedSize); + pool.start(response); + return response; + } private: - QThreadPool pool; + QThreadPool pool; }; diff --git a/src/Cache.cpp b/src/Cache.cpp index 9ebc61b9..b124fe5e 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -97,55 +97,55 @@ std::unique_ptr instance_ = nullptr; struct RO_txn { - ~RO_txn() { txn.reset(); } - operator MDB_txn *() const noexcept { return txn.handle(); } - operator lmdb::txn &() noexcept { return txn; } + ~RO_txn() { txn.reset(); } + operator MDB_txn *() const noexcept { return txn.handle(); } + operator lmdb::txn &() noexcept { return txn; } - lmdb::txn &txn; + lmdb::txn &txn; }; RO_txn ro_txn(lmdb::env &env) { - thread_local lmdb::txn txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - thread_local int reuse_counter = 0; + thread_local lmdb::txn txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + thread_local int reuse_counter = 0; - if (reuse_counter >= 100 || txn.env() != env.handle()) { - txn.abort(); - txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - reuse_counter = 0; - } else if (reuse_counter > 0) { - try { - txn.renew(); - } catch (...) { - txn.abort(); - txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - reuse_counter = 0; - } + if (reuse_counter >= 100 || txn.env() != env.handle()) { + txn.abort(); + txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + reuse_counter = 0; + } else if (reuse_counter > 0) { + try { + txn.renew(); + } catch (...) { + txn.abort(); + txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + reuse_counter = 0; } - reuse_counter++; + } + reuse_counter++; - return RO_txn{txn}; + return RO_txn{txn}; } template bool containsStateUpdates(const T &e) { - return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e); + return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e); } bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - return std::holds_alternative>(e) || - std::holds_alternative>(e) || - std::holds_alternative>(e) || - std::holds_alternative>(e) || - std::holds_alternative>(e); + return std::holds_alternative>(e) || + std::holds_alternative>(e) || + std::holds_alternative>(e) || + std::holds_alternative>(e) || + std::holds_alternative>(e); } bool @@ -153,45 +153,45 @@ Cache::isHiddenEvent(lmdb::txn &txn, mtx::events::collections::TimelineEvents e, const std::string &room_id) { - using namespace mtx::events; + using namespace mtx::events; - // Always hide edits - if (mtx::accessors::relations(e).replaces()) - return true; - - if (auto encryptedEvent = std::get_if>(&e)) { - MegolmSessionIndex index; - index.room_id = room_id; - index.session_id = encryptedEvent->content.session_id; - index.sender_key = encryptedEvent->content.sender_key; - - auto result = olm::decryptEvent(index, *encryptedEvent, true); - if (!result.error) - e = result.event.value(); - } + // Always hide edits + if (mtx::accessors::relations(e).replaces()) + return true; - mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents; - hiddenEvents.hidden_event_types = { - EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; - - if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) - hiddenEvents = - std::move(std::get>(*temp) - .content); - if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) - hiddenEvents = - std::move(std::get>(*temp) - .content); - - return std::visit( - [hiddenEvents](const auto &ev) { - return std::any_of(hiddenEvents.hidden_event_types.begin(), - hiddenEvents.hidden_event_types.end(), - [ev](EventType type) { return type == ev.type; }); - }, - e); + if (auto encryptedEvent = std::get_if>(&e)) { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = encryptedEvent->content.session_id; + index.sender_key = encryptedEvent->content.sender_key; + + auto result = olm::decryptEvent(index, *encryptedEvent, true); + if (!result.error) + e = result.event.value(); + } + + mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents; + hiddenEvents.hidden_event_types = { + EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; + + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) + hiddenEvents = + std::move(std::get>(*temp) + .content); + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) + hiddenEvents = + std::move(std::get>(*temp) + .content); + + return std::visit( + [hiddenEvents](const auto &ev) { + return std::any_of(hiddenEvents.hidden_event_types.begin(), + hiddenEvents.hidden_event_types.end(), + [ev](EventType type) { return type == ev.type; }); + }, + e); } Cache::Cache(const QString &userId, QObject *parent) @@ -199,218 +199,210 @@ Cache::Cache(const QString &userId, QObject *parent) , env_{nullptr} , localUserId_{userId} { - setup(); - connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); + setup(); + connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); } void Cache::setup() { - auto settings = UserSettings::instance(); + auto settings = UserSettings::instance(); - nhlog::db()->debug("setting up cache"); + nhlog::db()->debug("setting up cache"); - // Previous location of the cache directory - auto oldCache = QString("%1/%2%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) - .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); + // Previous location of the cache directory + auto oldCache = QString("%1/%2%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) + .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); - cacheDirectory_ = QString("%1/%2%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) - .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); + cacheDirectory_ = QString("%1/%2%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) + .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) + .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); - bool isInitial = !QFile::exists(cacheDirectory_); + bool isInitial = !QFile::exists(cacheDirectory_); - // NOTE: If both cache directories exist it's better to do nothing: it - // could mean a previous migration failed or was interrupted. - bool needsMigration = isInitial && QFile::exists(oldCache); + // NOTE: If both cache directories exist it's better to do nothing: it + // could mean a previous migration failed or was interrupted. + bool needsMigration = isInitial && QFile::exists(oldCache); - if (needsMigration) { - nhlog::db()->info("found old state directory, migrating"); - if (!QDir().rename(oldCache, cacheDirectory_)) { - throw std::runtime_error(("Unable to migrate the old state directory (" + - oldCache + ") to the new location (" + - cacheDirectory_ + ")") - .toStdString() - .c_str()); - } - nhlog::db()->info("completed state migration"); + if (needsMigration) { + nhlog::db()->info("found old state directory, migrating"); + if (!QDir().rename(oldCache, cacheDirectory_)) { + throw std::runtime_error(("Unable to migrate the old state directory (" + oldCache + + ") to the new location (" + cacheDirectory_ + ")") + .toStdString() + .c_str()); } + nhlog::db()->info("completed state migration"); + } - env_ = lmdb::env::create(); - env_.set_mapsize(DB_SIZE); - env_.set_max_dbs(MAX_DBS); + env_ = lmdb::env::create(); + env_.set_mapsize(DB_SIZE); + env_.set_max_dbs(MAX_DBS); - if (isInitial) { - nhlog::db()->info("initializing LMDB"); + if (isInitial) { + nhlog::db()->info("initializing LMDB"); - if (!QDir().mkpath(cacheDirectory_)) { - throw std::runtime_error( - ("Unable to create state directory:" + cacheDirectory_) - .toStdString() - .c_str()); - } + if (!QDir().mkpath(cacheDirectory_)) { + throw std::runtime_error( + ("Unable to create state directory:" + cacheDirectory_).toStdString().c_str()); } + } - try { - // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but - // it can really mess up our database, so we shouldn't. For now, hopefully - // NOMETASYNC is fast enough. - env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC); - } catch (const lmdb::error &e) { - if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { - throw std::runtime_error("LMDB initialization failed" + - std::string(e.what())); - } + try { + // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but + // it can really mess up our database, so we shouldn't. For now, hopefully + // NOMETASYNC is fast enough. + env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC); + } catch (const lmdb::error &e) { + if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { + throw std::runtime_error("LMDB initialization failed" + std::string(e.what())); + } - nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what()); + nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what()); - QDir stateDir(cacheDirectory_); + QDir stateDir(cacheDirectory_); - for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { - if (!stateDir.remove(file)) - throw std::runtime_error( - ("Unable to delete file " + file).toStdString().c_str()); - } - env_.open(cacheDirectory_.toStdString().c_str()); + for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { + if (!stateDir.remove(file)) + throw std::runtime_error(("Unable to delete file " + file).toStdString().c_str()); } + env_.open(cacheDirectory_.toStdString().c_str()); + } - auto txn = lmdb::txn::begin(env_); - syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE); - roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); - spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT); - spacesParentsDb_ = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT); - invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE); - readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE); - notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE); - - // Device management - devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE); - deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE); - - // Session management - inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); - outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); - megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); - - // What rooms are encrypted - encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - [[maybe_unused]] auto verificationDb = getVerificationDb(txn); - [[maybe_unused]] auto userKeysDb = getUserKeysDb(txn); + auto txn = lmdb::txn::begin(env_); + syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE); + roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); + spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT); + spacesParentsDb_ = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT); + invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE); + readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE); + notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE); - txn.commit(); + // Device management + devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE); + deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE); + + // Session management + inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); + outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); + megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); - databaseReady_ = true; + // What rooms are encrypted + encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + [[maybe_unused]] auto verificationDb = getVerificationDb(txn); + [[maybe_unused]] auto userKeysDb = getUserKeysDb(txn); + + txn.commit(); + + databaseReady_ = true; } void Cache::setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) { - nhlog::db()->info("mark room {} as encrypted", room_id); + nhlog::db()->info("mark room {} as encrypted", room_id); - encryptedRooms_.put(txn, room_id, "0"); + encryptedRooms_.put(txn, room_id, "0"); } bool Cache::isRoomEncrypted(const std::string &room_id) { - std::string_view unused; + std::string_view unused; - auto txn = ro_txn(env_); - auto res = encryptedRooms_.get(txn, room_id, unused); + auto txn = ro_txn(env_); + auto res = encryptedRooms_.get(txn, room_id, unused); - return res; + return res; } std::optional Cache::roomEncryptionSettings(const std::string &room_id) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - try { - auto txn = ro_txn(env_); - auto statesdb = getStatesDb(txn, room_id); - std::string_view event; - bool res = - statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event); - - if (res) { - try { - StateEvent msg = json::parse(event); - - return msg.content; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.encryption event: {}", - e.what()); - return Encryption{}; - } - } - } catch (lmdb::error &) { + try { + auto txn = ro_txn(env_); + auto statesdb = getStatesDb(txn, room_id); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event); + + if (res) { + try { + StateEvent msg = json::parse(event); + + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.encryption event: {}", e.what()); + return Encryption{}; + } } + } catch (lmdb::error &) { + } - return std::nullopt; + return std::nullopt; } mtx::crypto::ExportedSessionKeys Cache::exportSessionKeys() { - using namespace mtx::crypto; + using namespace mtx::crypto; - ExportedSessionKeys keys; + ExportedSessionKeys keys; - auto txn = ro_txn(env_); - auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_); + auto txn = ro_txn(env_); + auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_); - std::string_view key, value; - while (cursor.get(key, value, MDB_NEXT)) { - ExportedSession exported; - MegolmSessionIndex index; + std::string_view key, value; + while (cursor.get(key, value, MDB_NEXT)) { + ExportedSession exported; + MegolmSessionIndex index; - auto saved_session = - unpickle(std::string(value), pickle_secret_); + auto saved_session = unpickle(std::string(value), pickle_secret_); - try { - index = nlohmann::json::parse(key).get(); - } catch (const nlohmann::json::exception &e) { - nhlog::db()->critical("failed to export megolm session: {}", e.what()); - continue; - } + try { + index = nlohmann::json::parse(key).get(); + } catch (const nlohmann::json::exception &e) { + nhlog::db()->critical("failed to export megolm session: {}", e.what()); + continue; + } - exported.room_id = index.room_id; - exported.sender_key = index.sender_key; - exported.session_id = index.session_id; - exported.session_key = export_session(saved_session.get(), -1); + exported.room_id = index.room_id; + exported.sender_key = index.sender_key; + exported.session_id = index.session_id; + exported.session_key = export_session(saved_session.get(), -1); - keys.sessions.push_back(exported); - } + keys.sessions.push_back(exported); + } - cursor.close(); + cursor.close(); - return keys; + return keys; } void Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) { - for (const auto &s : keys.sessions) { - MegolmSessionIndex index; - index.room_id = s.room_id; - index.session_id = s.session_id; - index.sender_key = s.sender_key; + for (const auto &s : keys.sessions) { + MegolmSessionIndex index; + index.room_id = s.room_id; + index.session_id = s.session_id; + index.sender_key = s.sender_key; - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain; - if (s.sender_claimed_keys.count("ed25519")) - data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519"); + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain; + if (s.sender_claimed_keys.count("ed25519")) + data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519"); - auto exported_session = mtx::crypto::import_session(s.session_key); + auto exported_session = mtx::crypto::import_session(s.session_key); - saveInboundMegolmSession(index, std::move(exported_session), data); - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); - } + saveInboundMegolmSession(index, std::move(exported_session), data); + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + } } // @@ -422,67 +414,64 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, mtx::crypto::InboundGroupSessionPtr session, const GroupSessionData &data) { - using namespace mtx::crypto; - const auto key = json(index).dump(); - const auto pickled = pickle(session.get(), pickle_secret_); + using namespace mtx::crypto; + const auto key = json(index).dump(); + const auto pickled = pickle(session.get(), pickle_secret_); - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - std::string_view value; - if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto oldSession = - unpickle(std::string(value), pickle_secret_); - if (olm_inbound_group_session_first_known_index(session.get()) > - olm_inbound_group_session_first_known_index(oldSession.get())) { - nhlog::crypto()->warn( - "Not storing inbound session with newer first known index"); - return; - } + std::string_view value; + if (inboundMegolmSessionDb_.get(txn, key, value)) { + auto oldSession = unpickle(std::string(value), pickle_secret_); + if (olm_inbound_group_session_first_known_index(session.get()) > + olm_inbound_group_session_first_known_index(oldSession.get())) { + nhlog::crypto()->warn("Not storing inbound session with newer first known index"); + return; } + } - inboundMegolmSessionDb_.put(txn, key, pickled); - megolmSessionDataDb_.put(txn, key, json(data).dump()); - txn.commit(); + inboundMegolmSessionDb_.put(txn, key, pickled); + megolmSessionDataDb_.put(txn, key, json(data).dump()); + txn.commit(); } mtx::crypto::InboundGroupSessionPtr Cache::getInboundMegolmSession(const MegolmSessionIndex &index) { - using namespace mtx::crypto; + using namespace mtx::crypto; - try { - auto txn = ro_txn(env_); - std::string key = json(index).dump(); - std::string_view value; - - if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto session = - unpickle(std::string(value), pickle_secret_); - return session; - } - } catch (std::exception &e) { - nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + try { + auto txn = ro_txn(env_); + std::string key = json(index).dump(); + std::string_view value; + + if (inboundMegolmSessionDb_.get(txn, key, value)) { + auto session = unpickle(std::string(value), pickle_secret_); + return session; } + } catch (std::exception &e) { + nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + } - return nullptr; + return nullptr; } bool Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) { - using namespace mtx::crypto; + using namespace mtx::crypto; - try { - auto txn = ro_txn(env_); - std::string key = json(index).dump(); - std::string_view value; + try { + auto txn = ro_txn(env_); + std::string key = json(index).dump(); + std::string_view value; - return inboundMegolmSessionDb_.get(txn, key, value); - } catch (std::exception &e) { - nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); - } + return inboundMegolmSessionDb_.get(txn, key, value); + } catch (std::exception &e) { + nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + } - return false; + return false; } void @@ -490,42 +479,42 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &ptr) { - using namespace mtx::crypto; + using namespace mtx::crypto; - if (!outboundMegolmSessionExists(room_id)) - return; + if (!outboundMegolmSessionExists(room_id)) + return; - GroupSessionData data = data_; - data.message_index = olm_outbound_group_session_message_index(ptr.get()); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(ptr.get()); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(ptr.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ptr.get()); - // Save the updated pickled data for the session. - json j; - j["session"] = pickle(ptr.get(), pickle_secret_); + // Save the updated pickled data for the session. + json j; + j["session"] = pickle(ptr.get(), pickle_secret_); - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.put(txn, room_id, j.dump()); - megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); + txn.commit(); } void Cache::dropOutboundMegolmSession(const std::string &room_id) { - using namespace mtx::crypto; + using namespace mtx::crypto; - if (!outboundMegolmSessionExists(room_id)) - return; + if (!outboundMegolmSessionExists(room_id)) + return; - { - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.del(txn, room_id); - // don't delete session data, so that we can still share the session. - txn.commit(); - } + { + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.del(txn, room_id); + // don't delete session data, so that we can still share the session. + txn.commit(); + } } void @@ -533,86 +522,86 @@ Cache::saveOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &session) { - using namespace mtx::crypto; - const auto pickled = pickle(session.get(), pickle_secret_); + using namespace mtx::crypto; + const auto pickled = pickle(session.get(), pickle_secret_); - GroupSessionData data = data_; - data.message_index = olm_outbound_group_session_message_index(session.get()); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(session.get()); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(session.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(session.get()); - json j; - j["session"] = pickled; + json j; + j["session"] = pickled; - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.put(txn, room_id, j.dump()); - megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); + txn.commit(); } bool Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept { - try { - auto txn = ro_txn(env_); - std::string_view value; - return outboundMegolmSessionDb_.get(txn, room_id, value); - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); - return false; - } + try { + auto txn = ro_txn(env_); + std::string_view value; + return outboundMegolmSessionDb_.get(txn, room_id, value); + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); + return false; + } } OutboundGroupSessionDataRef Cache::getOutboundMegolmSession(const std::string &room_id) { - try { - using namespace mtx::crypto; - - auto txn = ro_txn(env_); - std::string_view value; - outboundMegolmSessionDb_.get(txn, room_id, value); - auto obj = json::parse(value); + try { + using namespace mtx::crypto; - OutboundGroupSessionDataRef ref{}; - ref.session = unpickle(obj.at("session"), pickle_secret_); + auto txn = ro_txn(env_); + std::string_view value; + outboundMegolmSessionDb_.get(txn, room_id, value); + auto obj = json::parse(value); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(ref.session.get()); + OutboundGroupSessionDataRef ref{}; + ref.session = unpickle(obj.at("session"), pickle_secret_); - if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { - ref.data = nlohmann::json::parse(value).get(); - } + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ref.session.get()); - return ref; - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); - return {}; + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + ref.data = nlohmann::json::parse(value).get(); } + + return ref; + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); + return {}; + } } std::optional Cache::getMegolmSessionData(const MegolmSessionIndex &index) { - try { - using namespace mtx::crypto; - - auto txn = ro_txn(env_); + try { + using namespace mtx::crypto; - std::string_view value; - if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { - return nlohmann::json::parse(value).get(); - } + auto txn = ro_txn(env_); - return std::nullopt; - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what()); - return std::nullopt; + std::string_view value; + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + return nlohmann::json::parse(value).get(); } + + return std::nullopt; + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what()); + return std::nullopt; + } } // // OLM sessions. @@ -623,1013 +612,972 @@ Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session, uint64_t timestamp) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - const auto pickled = pickle(session.get(), pickle_secret_); - const auto session_id = mtx::crypto::session_id(session.get()); + const auto pickled = pickle(session.get(), pickle_secret_); + const auto session_id = mtx::crypto::session_id(session.get()); - StoredOlmSession stored_session; - stored_session.pickled_session = pickled; - stored_session.last_message_ts = timestamp; + StoredOlmSession stored_session; + stored_session.pickled_session = pickled; + stored_session.last_message_ts = timestamp; - db.put(txn, session_id, json(stored_session).dump()); + db.put(txn, session_id, json(stored_session).dump()); - txn.commit(); + txn.commit(); } std::optional Cache::getOlmSession(const std::string &curve25519, const std::string &session_id) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view pickled; - bool found = db.get(txn, session_id, pickled); + std::string_view pickled; + bool found = db.get(txn, session_id, pickled); - txn.commit(); + txn.commit(); - if (found) { - auto data = json::parse(pickled).get(); - return unpickle(data.pickled_session, pickle_secret_); - } + if (found) { + auto data = json::parse(pickled).get(); + return unpickle(data.pickled_session, pickle_secret_); + } - return std::nullopt; + return std::nullopt; } std::optional Cache::getLatestOlmSession(const std::string &curve25519) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view session_id, pickled_session; + std::string_view session_id, pickled_session; - std::optional currentNewest; + std::optional currentNewest; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, pickled_session, MDB_NEXT)) { - auto data = json::parse(pickled_session).get(); - if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) - currentNewest = data; - } - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, pickled_session, MDB_NEXT)) { + auto data = json::parse(pickled_session).get(); + if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) + currentNewest = data; + } + cursor.close(); - txn.commit(); + txn.commit(); - return currentNewest ? std::optional(unpickle(currentNewest->pickled_session, - pickle_secret_)) - : std::nullopt; + return currentNewest ? std::optional(unpickle(currentNewest->pickled_session, + pickle_secret_)) + : std::nullopt; } std::vector Cache::getOlmSessions(const std::string &curve25519) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view session_id, unused; - std::vector res; + std::string_view session_id, unused; + std::vector res; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, unused, MDB_NEXT)) - res.emplace_back(session_id); - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, unused, MDB_NEXT)) + res.emplace_back(session_id); + cursor.close(); - txn.commit(); + txn.commit(); - return res; + return res; } void Cache::saveOlmAccount(const std::string &data) { - auto txn = lmdb::txn::begin(env_); - syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data); + txn.commit(); } std::string Cache::restoreOlmAccount() { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view pickled; - syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled); + std::string_view pickled; + syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled); - return std::string(pickled.data(), pickled.size()); + return std::string(pickled.data(), pickled.size()); } void Cache::saveBackupVersion(const OnlineBackupVersion &data) { - auto txn = lmdb::txn::begin(env_); - syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump()); + txn.commit(); } void Cache::deleteBackupVersion() { - auto txn = lmdb::txn::begin(env_); - syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION); + txn.commit(); } std::optional Cache::backupVersion() { - try { - auto txn = ro_txn(env_); - std::string_view v; - syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v); + try { + auto txn = ro_txn(env_); + std::string_view v; + syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v); - return nlohmann::json::parse(v).get(); - } catch (...) { - return std::nullopt; - } + return nlohmann::json::parse(v).get(); + } catch (...) { + return std::nullopt; + } } static void fatalSecretError() { - QMessageBox::critical( - ChatPage::instance(), - QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), - QCoreApplication::translate( - "SecretStorage", - "Nheko could not connect to the secure storage to save encryption secrets to. " - "This can have multiple reasons. Check if your D-Bus service is running and " - "you have configured a service like KWallet, Gnome Secrets or the equivalent " - "for your platform. If you are having trouble, feel free to open an issue " - "here: https://github.com/Nheko-Reborn/nheko/issues")); + QMessageBox::critical( + ChatPage::instance(), + QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), + QCoreApplication::translate( + "SecretStorage", + "Nheko could not connect to the secure storage to save encryption secrets to. " + "This can have multiple reasons. Check if your D-Bus service is running and " + "you have configured a service like KWallet, Gnome Secrets or the equivalent " + "for your platform. If you are having trouble, feel free to open an issue " + "here: https://github.com/Nheko-Reborn/nheko/issues")); - QCoreApplication::exit(1); - exit(1); + QCoreApplication::exit(1); + exit(1); } void Cache::storeSecret(const std::string name, const std::string secret, bool internal) { - auto settings = UserSettings::instance(); - auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); - job->setAutoDelete(true); - job->setInsecureFallback(true); - job->setSettings(UserSettings::instance()->qsettings()); - - job->setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - job->setTextData(QString::fromStdString(secret)); - - QObject::connect( - job, - &QKeychain::WritePasswordJob::finished, - this, - [name, this](QKeychain::Job *job) { - if (job->error()) { - nhlog::db()->warn("Storing secret '{}' failed: {}", - name, - job->errorString().toStdString()); - fatalSecretError(); - } else { - // if we emit the signal directly, qtkeychain breaks and won't execute new - // jobs. You can't start a job from the finish signal of a job. - QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); - nhlog::db()->info("Storing secret '{}' successful", name); - } - }, - Qt::ConnectionType::DirectConnection); - job->start(); + auto settings = UserSettings::instance(); + auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); + job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); + + job->setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); + + job->setTextData(QString::fromStdString(secret)); + + QObject::connect( + job, + &QKeychain::WritePasswordJob::finished, + this, + [name, this](QKeychain::Job *job) { + if (job->error()) { + nhlog::db()->warn( + "Storing secret '{}' failed: {}", name, job->errorString().toStdString()); + fatalSecretError(); + } else { + // if we emit the signal directly, qtkeychain breaks and won't execute new + // jobs. You can't start a job from the finish signal of a job. + QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); + nhlog::db()->info("Storing secret '{}' successful", name); + } + }, + Qt::ConnectionType::DirectConnection); + job->start(); } void Cache::deleteSecret(const std::string name, bool internal) { - auto settings = UserSettings::instance(); - QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); + auto settings = UserSettings::instance(); + QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); + job.setAutoDelete(false); + job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); - job.setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); + job.setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! + QEventLoop loop; + job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); - emit secretChanged(name); + emit secretChanged(name); } std::optional Cache::secret(const std::string name, bool internal) { - auto settings = UserSettings::instance(); - QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); - - job.setKey( - (internal ? "nheko." : "matrix.") + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); - - const QString secret = job.textData(); - if (job.error()) { - if (job.error() == QKeychain::Error::EntryNotFound) - return std::nullopt; - nhlog::db()->error("Restoring secret '{}' failed ({}): {}", - name, - job.error(), - job.errorString().toStdString()); - - fatalSecretError(); - return std::nullopt; - } - if (secret.isEmpty()) { - nhlog::db()->debug("Restored empty secret '{}'.", name); - return std::nullopt; - } + auto settings = UserSettings::instance(); + QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); + job.setAutoDelete(false); + job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); + + job.setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); + + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! + QEventLoop loop; + job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + const QString secret = job.textData(); + if (job.error()) { + if (job.error() == QKeychain::Error::EntryNotFound) + return std::nullopt; + nhlog::db()->error("Restoring secret '{}' failed ({}): {}", + name, + job.error(), + job.errorString().toStdString()); + + fatalSecretError(); + return std::nullopt; + } + if (secret.isEmpty()) { + nhlog::db()->debug("Restored empty secret '{}'.", name); + return std::nullopt; + } - return secret.toStdString(); + return secret.toStdString(); } std::string Cache::pickleSecret() { - if (pickle_secret_.empty()) { - auto s = secret("pickle_secret", true); - if (!s) { - this->pickle_secret_ = mtx::client::utils::random_token(64, true); - storeSecret("pickle_secret", pickle_secret_, true); - } else { - this->pickle_secret_ = *s; - } + if (pickle_secret_.empty()) { + auto s = secret("pickle_secret", true); + if (!s) { + this->pickle_secret_ = mtx::client::utils::random_token(64, true); + storeSecret("pickle_secret", pickle_secret_, true); + } else { + this->pickle_secret_ = *s; } + } - return pickle_secret_; + return pickle_secret_; } void Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) { - invitesDb_.del(txn, room_id); - getInviteStatesDb(txn, room_id).drop(txn, true); - getInviteMembersDb(txn, room_id).drop(txn, true); + invitesDb_.del(txn, room_id); + getInviteStatesDb(txn, room_id).drop(txn, true); + getInviteMembersDb(txn, room_id).drop(txn, true); } void Cache::removeInvite(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - removeInvite(txn, room_id); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + removeInvite(txn, room_id); + txn.commit(); } void Cache::removeRoom(lmdb::txn &txn, const std::string &roomid) { - roomsDb_.del(txn, roomid); - getStatesDb(txn, roomid).drop(txn, true); - getAccountDataDb(txn, roomid).drop(txn, true); - getMembersDb(txn, roomid).drop(txn, true); + roomsDb_.del(txn, roomid); + getStatesDb(txn, roomid).drop(txn, true); + getAccountDataDb(txn, roomid).drop(txn, true); + getMembersDb(txn, roomid).drop(txn, true); } void Cache::removeRoom(const std::string &roomid) { - auto txn = lmdb::txn::begin(env_, nullptr, 0); - roomsDb_.del(txn, roomid); - txn.commit(); + auto txn = lmdb::txn::begin(env_, nullptr, 0); + roomsDb_.del(txn, roomid); + txn.commit(); } void Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token) { - syncStateDb_.put(txn, NEXT_BATCH_KEY, token); + syncStateDb_.put(txn, NEXT_BATCH_KEY, token); } bool Cache::isInitialized() { - if (!env_.handle()) - return false; + if (!env_.handle()) + return false; - auto txn = ro_txn(env_); - std::string_view token; + auto txn = ro_txn(env_); + std::string_view token; - bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); + bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); - return res; + return res; } std::string Cache::nextBatchToken() { - if (!env_.handle()) - throw lmdb::error("Env already closed", MDB_INVALID); + if (!env_.handle()) + throw lmdb::error("Env already closed", MDB_INVALID); - auto txn = ro_txn(env_); - std::string_view token; + auto txn = ro_txn(env_); + std::string_view token; - bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); + bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); - if (result) - return std::string(token.data(), token.size()); - else - return ""; + if (result) + return std::string(token.data(), token.size()); + else + return ""; } void Cache::deleteData() { - this->databaseReady_ = false; - // TODO: We need to remove the env_ while not accepting new requests. - lmdb::dbi_close(env_, syncStateDb_); - lmdb::dbi_close(env_, roomsDb_); - lmdb::dbi_close(env_, invitesDb_); - lmdb::dbi_close(env_, readReceiptsDb_); - lmdb::dbi_close(env_, notificationsDb_); + this->databaseReady_ = false; + // TODO: We need to remove the env_ while not accepting new requests. + lmdb::dbi_close(env_, syncStateDb_); + lmdb::dbi_close(env_, roomsDb_); + lmdb::dbi_close(env_, invitesDb_); + lmdb::dbi_close(env_, readReceiptsDb_); + lmdb::dbi_close(env_, notificationsDb_); - lmdb::dbi_close(env_, devicesDb_); - lmdb::dbi_close(env_, deviceKeysDb_); + lmdb::dbi_close(env_, devicesDb_); + lmdb::dbi_close(env_, deviceKeysDb_); - lmdb::dbi_close(env_, inboundMegolmSessionDb_); - lmdb::dbi_close(env_, outboundMegolmSessionDb_); - lmdb::dbi_close(env_, megolmSessionDataDb_); + lmdb::dbi_close(env_, inboundMegolmSessionDb_); + lmdb::dbi_close(env_, outboundMegolmSessionDb_); + lmdb::dbi_close(env_, megolmSessionDataDb_); - env_.close(); + env_.close(); - verification_storage.status.clear(); + verification_storage.status.clear(); - if (!cacheDirectory_.isEmpty()) { - QDir(cacheDirectory_).removeRecursively(); - nhlog::db()->info("deleted cache files from disk"); - } + if (!cacheDirectory_.isEmpty()) { + QDir(cacheDirectory_).removeRecursively(); + nhlog::db()->info("deleted cache files from disk"); + } - deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); - deleteSecret(mtx::secret_storage::secrets::cross_signing_master); - deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); - deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); - deleteSecret("pickle_secret", true); + deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); + deleteSecret(mtx::secret_storage::secrets::cross_signing_master); + deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); + deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); + deleteSecret("pickle_secret", true); } //! migrates db to the current format bool Cache::runMigrations() { - std::string stored_version; - { - auto txn = ro_txn(env_); - - std::string_view current_version; - bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - - if (!res) - return false; - - stored_version = std::string(current_version); - } + std::string stored_version; + { + auto txn = ro_txn(env_); - std::vector>> migrations{ - {"2020.05.01", - [this]() { - try { - auto txn = lmdb::txn::begin(env_, nullptr); - auto pending_receipts = - lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); - lmdb::dbi_drop(txn, pending_receipts, true); - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical( - "Failed to delete pending_receipts database in migration!"); - return false; - } + std::string_view current_version; + bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - nhlog::db()->info("Successfully deleted pending receipts database."); - return true; - }}, - {"2020.07.05", - [this]() { + if (!res) + return false; + + stored_version = std::string(current_version); + } + + std::vector>> migrations{ + {"2020.05.01", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto pending_receipts = lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); + lmdb::dbi_drop(txn, pending_receipts, true); + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to delete pending_receipts database in migration!"); + return false; + } + + nhlog::db()->info("Successfully deleted pending receipts database."); + return true; + }}, + {"2020.07.05", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto room_ids = getRoomIds(txn); + + for (const auto &room_id : room_ids) { try { - auto txn = lmdb::txn::begin(env_, nullptr); - auto room_ids = getRoomIds(txn); - - for (const auto &room_id : room_ids) { - try { - auto messagesDb = lmdb::dbi::open( - txn, std::string(room_id + "/messages").c_str()); - - // keep some old messages and batch token - { - auto roomsCursor = - lmdb::cursor::open(txn, messagesDb); - std::string_view ts, stored_message; - bool start = true; - mtx::responses::Timeline oldMessages; - while (roomsCursor.get(ts, - stored_message, - start ? MDB_FIRST - : MDB_NEXT)) { - start = false; - - auto j = json::parse(std::string_view( - stored_message.data(), - stored_message.size())); - - if (oldMessages.prev_batch.empty()) - oldMessages.prev_batch = - j["token"].get(); - else if (j["token"] != - oldMessages.prev_batch) - break; - - mtx::events::collections::TimelineEvent - te; - mtx::events::collections::from_json( - j["event"], te); - oldMessages.events.push_back(te.data); - } - // messages were stored in reverse order, so we - // need to reverse them - std::reverse(oldMessages.events.begin(), - oldMessages.events.end()); - // save messages using the new method - auto eventsDb = getEventsDb(txn, room_id); - saveTimelineMessages( - txn, eventsDb, room_id, oldMessages); - } - - // delete old messages db - lmdb::dbi_drop(txn, messagesDb, true); - } catch (std::exception &e) { - nhlog::db()->error( - "While migrating messages from {}, ignoring error {}", - room_id, - e.what()); - } + auto messagesDb = + lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str()); + + // keep some old messages and batch token + { + auto roomsCursor = lmdb::cursor::open(txn, messagesDb); + std::string_view ts, stored_message; + bool start = true; + mtx::responses::Timeline oldMessages; + while ( + roomsCursor.get(ts, stored_message, start ? MDB_FIRST : MDB_NEXT)) { + start = false; + + auto j = json::parse( + std::string_view(stored_message.data(), stored_message.size())); + + if (oldMessages.prev_batch.empty()) + oldMessages.prev_batch = j["token"].get(); + else if (j["token"] != oldMessages.prev_batch) + break; + + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(j["event"], te); + oldMessages.events.push_back(te.data); } - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical( - "Failed to delete messages database in migration!"); - return false; + // messages were stored in reverse order, so we + // need to reverse them + std::reverse(oldMessages.events.begin(), oldMessages.events.end()); + // save messages using the new method + auto eventsDb = getEventsDb(txn, room_id); + saveTimelineMessages(txn, eventsDb, room_id, oldMessages); + } + + // delete old messages db + lmdb::dbi_drop(txn, messagesDb, true); + } catch (std::exception &e) { + nhlog::db()->error( + "While migrating messages from {}, ignoring error {}", room_id, e.what()); } + } + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to delete messages database in migration!"); + return false; + } + + nhlog::db()->info("Successfully deleted pending receipts database."); + return true; + }}, + {"2020.10.20", + [this]() { + try { + using namespace mtx::crypto; + + auto txn = lmdb::txn::begin(env_); + + auto mainDb = lmdb::dbi::open(txn, nullptr); + + std::string_view dbName, ignored; + auto olmDbCursor = lmdb::cursor::open(txn, mainDb); + while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) { + // skip every db but olm session dbs + nhlog::db()->debug("Db {}", dbName); + if (dbName.find("olm_sessions/") != 0) + continue; + + nhlog::db()->debug("Migrating {}", dbName); + + auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str()); + + std::string_view session_id, session_value; + + std::vector> sessions; + + auto cursor = lmdb::cursor::open(txn, olmDb); + while (cursor.get(session_id, session_value, MDB_NEXT)) { + nhlog::db()->debug( + "session_id {}, session_value {}", session_id, session_value); + StoredOlmSession session; + bool invalid = false; + for (auto c : session_value) + if (!isprint(c)) { + invalid = true; + break; + } + if (invalid) + continue; - nhlog::db()->info("Successfully deleted pending receipts database."); - return true; - }}, - {"2020.10.20", - [this]() { - try { - using namespace mtx::crypto; - - auto txn = lmdb::txn::begin(env_); - - auto mainDb = lmdb::dbi::open(txn, nullptr); - - std::string_view dbName, ignored; - auto olmDbCursor = lmdb::cursor::open(txn, mainDb); - while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) { - // skip every db but olm session dbs - nhlog::db()->debug("Db {}", dbName); - if (dbName.find("olm_sessions/") != 0) - continue; - - nhlog::db()->debug("Migrating {}", dbName); - - auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str()); - - std::string_view session_id, session_value; - - std::vector> sessions; - - auto cursor = lmdb::cursor::open(txn, olmDb); - while (cursor.get(session_id, session_value, MDB_NEXT)) { - nhlog::db()->debug("session_id {}, session_value {}", - session_id, - session_value); - StoredOlmSession session; - bool invalid = false; - for (auto c : session_value) - if (!isprint(c)) { - invalid = true; - break; - } - if (invalid) - continue; - - nhlog::db()->debug("Not skipped"); - - session.pickled_session = session_value; - sessions.emplace_back(session_id, session); - } - cursor.close(); + nhlog::db()->debug("Not skipped"); - olmDb.drop(txn, true); + session.pickled_session = session_value; + sessions.emplace_back(session_id, session); + } + cursor.close(); - auto newDbName = std::string(dbName); - newDbName.erase(0, sizeof("olm_sessions") - 1); - newDbName = "olm_sessions.v2" + newDbName; + olmDb.drop(txn, true); - auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE); + auto newDbName = std::string(dbName); + newDbName.erase(0, sizeof("olm_sessions") - 1); + newDbName = "olm_sessions.v2" + newDbName; - for (const auto &[key, value] : sessions) { - // nhlog::db()->debug("{}\n{}", key, json(value).dump()); - newDb.put(txn, key, json(value).dump()); - } - } - olmDbCursor.close(); + auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE); - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical("Failed to migrate olm sessions,"); - return false; + for (const auto &[key, value] : sessions) { + // nhlog::db()->debug("{}\n{}", key, json(value).dump()); + newDb.put(txn, key, json(value).dump()); } - - nhlog::db()->info("Successfully migrated olm sessions."); - return true; - }}, - {"2021.08.22", - [this]() { + } + olmDbCursor.close(); + + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to migrate olm sessions,"); + return false; + } + + nhlog::db()->info("Successfully migrated olm sessions."); + return true; + }}, + {"2021.08.22", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto try_drop = [&txn](const std::string &dbName) { try { - auto txn = lmdb::txn::begin(env_, nullptr); - auto try_drop = [&txn](const std::string &dbName) { - try { - auto db = lmdb::dbi::open(txn, dbName.c_str()); - db.drop(txn, true); - } catch (std::exception &e) { - nhlog::db()->warn( - "Failed to drop '{}': {}", dbName, e.what()); - } - }; - - auto room_ids = getRoomIds(txn); - - for (const auto &room : room_ids) { - try_drop(room + "/state"); - try_drop(room + "/state_by_key"); - try_drop(room + "/account_data"); - try_drop(room + "/members"); - try_drop(room + "/mentions"); - try_drop(room + "/events"); - try_drop(room + "/event_order"); - try_drop(room + "/event2order"); - try_drop(room + "/msg2order"); - try_drop(room + "/order2msg"); - try_drop(room + "/pending"); - try_drop(room + "/related"); - } - - // clear db, don't delete - roomsDb_.drop(txn, false); - setNextBatchToken(txn, ""); - - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical("Failed to clear cache!"); - return false; + auto db = lmdb::dbi::open(txn, dbName.c_str()); + db.drop(txn, true); + } catch (std::exception &e) { + nhlog::db()->warn("Failed to drop '{}': {}", dbName, e.what()); } + }; + + auto room_ids = getRoomIds(txn); + + for (const auto &room : room_ids) { + try_drop(room + "/state"); + try_drop(room + "/state_by_key"); + try_drop(room + "/account_data"); + try_drop(room + "/members"); + try_drop(room + "/mentions"); + try_drop(room + "/events"); + try_drop(room + "/event_order"); + try_drop(room + "/event2order"); + try_drop(room + "/msg2order"); + try_drop(room + "/order2msg"); + try_drop(room + "/pending"); + try_drop(room + "/related"); + } + + // clear db, don't delete + roomsDb_.drop(txn, false); + setNextBatchToken(txn, ""); + + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to clear cache!"); + return false; + } + + nhlog::db()->info("Successfully cleared the cache. Will do a clean sync after startup."); + return true; + }}, + {"2021.08.31", + [this]() { + storeSecret("pickle_secret", "secret", true); + return true; + }}, + }; + + nhlog::db()->info("Running migrations, this may take a while!"); + for (const auto &[target_version, migration] : migrations) { + if (target_version > stored_version) + if (!migration()) { + nhlog::db()->critical("migration failure!"); + return false; + } + } + nhlog::db()->info("Migrations finished."); - nhlog::db()->info( - "Successfully cleared the cache. Will do a clean sync after startup."); - return true; - }}, - {"2021.08.31", - [this]() { - storeSecret("pickle_secret", "secret", true); - return true; - }}, - }; - - nhlog::db()->info("Running migrations, this may take a while!"); - for (const auto &[target_version, migration] : migrations) { - if (target_version > stored_version) - if (!migration()) { - nhlog::db()->critical("migration failure!"); - return false; - } - } - nhlog::db()->info("Migrations finished."); - - setCurrentFormat(); - return true; + setCurrentFormat(); + return true; } cache::CacheVersion Cache::formatVersion() { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view current_version; - bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); + std::string_view current_version; + bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - if (!res) - return cache::CacheVersion::Older; + if (!res) + return cache::CacheVersion::Older; - std::string stored_version(current_version.data(), current_version.size()); + std::string stored_version(current_version.data(), current_version.size()); - if (stored_version < CURRENT_CACHE_FORMAT_VERSION) - return cache::CacheVersion::Older; - else if (stored_version > CURRENT_CACHE_FORMAT_VERSION) - return cache::CacheVersion::Older; - else - return cache::CacheVersion::Current; + if (stored_version < CURRENT_CACHE_FORMAT_VERSION) + return cache::CacheVersion::Older; + else if (stored_version > CURRENT_CACHE_FORMAT_VERSION) + return cache::CacheVersion::Older; + else + return cache::CacheVersion::Current; } void Cache::setCurrentFormat() { - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION); + syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION); - txn.commit(); + txn.commit(); } CachedReceipts Cache::readReceipts(const QString &event_id, const QString &room_id) { - CachedReceipts receipts; + CachedReceipts receipts; - ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()}; - nlohmann::json json_key = receipt_key; - - try { - auto txn = ro_txn(env_); - auto key = json_key.dump(); + ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()}; + nlohmann::json json_key = receipt_key; - std::string_view value; + try { + auto txn = ro_txn(env_); + auto key = json_key.dump(); - bool res = readReceiptsDb_.get(txn, key, value); + std::string_view value; - if (res) { - auto json_response = - json::parse(std::string_view(value.data(), value.size())); - auto values = json_response.get>(); + bool res = readReceiptsDb_.get(txn, key, value); - for (const auto &v : values) - // timestamp, user_id - receipts.emplace(v.second, v.first); - } + if (res) { + auto json_response = json::parse(std::string_view(value.data(), value.size())); + auto values = json_response.get>(); - } catch (const lmdb::error &e) { - nhlog::db()->critical("readReceipts: {}", e.what()); + for (const auto &v : values) + // timestamp, user_id + receipts.emplace(v.second, v.first); } - return receipts; + } catch (const lmdb::error &e) { + nhlog::db()->critical("readReceipts: {}", e.what()); + } + + return receipts; } void Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts) { - auto user_id = this->localUserId_.toStdString(); - for (const auto &receipt : receipts) { - const auto event_id = receipt.first; - auto event_receipts = receipt.second; + auto user_id = this->localUserId_.toStdString(); + for (const auto &receipt : receipts) { + const auto event_id = receipt.first; + auto event_receipts = receipt.second; - ReadReceiptKey receipt_key{event_id, room_id}; - nlohmann::json json_key = receipt_key; + ReadReceiptKey receipt_key{event_id, room_id}; + nlohmann::json json_key = receipt_key; - try { - const auto key = json_key.dump(); + try { + const auto key = json_key.dump(); - std::string_view prev_value; + std::string_view prev_value; - bool exists = readReceiptsDb_.get(txn, key, prev_value); + bool exists = readReceiptsDb_.get(txn, key, prev_value); - std::map saved_receipts; + std::map saved_receipts; - // If an entry for the event id already exists, we would - // merge the existing receipts with the new ones. - if (exists) { - auto json_value = json::parse( - std::string_view(prev_value.data(), prev_value.size())); + // If an entry for the event id already exists, we would + // merge the existing receipts with the new ones. + if (exists) { + auto json_value = + json::parse(std::string_view(prev_value.data(), prev_value.size())); - // Retrieve the saved receipts. - saved_receipts = json_value.get>(); - } + // Retrieve the saved receipts. + saved_receipts = json_value.get>(); + } - // Append the new ones. - for (const auto &[read_by, timestamp] : event_receipts) { - if (read_by == user_id) { - emit removeNotification(QString::fromStdString(room_id), - QString::fromStdString(event_id)); - } - saved_receipts.emplace(read_by, timestamp); - } + // Append the new ones. + for (const auto &[read_by, timestamp] : event_receipts) { + if (read_by == user_id) { + emit removeNotification(QString::fromStdString(room_id), + QString::fromStdString(event_id)); + } + saved_receipts.emplace(read_by, timestamp); + } - // Save back the merged (or only the new) receipts. - nlohmann::json json_updated_value = saved_receipts; - std::string merged_receipts = json_updated_value.dump(); + // Save back the merged (or only the new) receipts. + nlohmann::json json_updated_value = saved_receipts; + std::string merged_receipts = json_updated_value.dump(); - readReceiptsDb_.put(txn, key, merged_receipts); + readReceiptsDb_.put(txn, key, merged_receipts); - } catch (const lmdb::error &e) { - nhlog::db()->critical("updateReadReceipts: {}", e.what()); - } + } catch (const lmdb::error &e) { + nhlog::db()->critical("updateReadReceipts: {}", e.what()); } + } } void Cache::calculateRoomReadStatus() { - const auto joined_rooms = joinedRooms(); + const auto joined_rooms = joinedRooms(); - std::map readStatus; + std::map readStatus; - for (const auto &room : joined_rooms) - readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room)); + for (const auto &room : joined_rooms) + readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room)); - emit roomReadStatus(readStatus); + emit roomReadStatus(readStatus); } bool Cache::calculateRoomReadStatus(const std::string &room_id) { - std::string last_event_id_, fullyReadEventId_; - { - auto txn = ro_txn(env_); - - // Get last event id on the room. - const auto last_event_id = getLastEventId(txn, room_id); - const auto localUser = utils::localUser().toStdString(); - - std::string fullyReadEventId; - if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) { - if (auto fr = std::get_if< - mtx::events::AccountDataEvent>( - &ev.value())) { - fullyReadEventId = fr->content.event_id; - } - } - - if (last_event_id.empty() || fullyReadEventId.empty()) - return true; + std::string last_event_id_, fullyReadEventId_; + { + auto txn = ro_txn(env_); - if (last_event_id == fullyReadEventId) - return false; + // Get last event id on the room. + const auto last_event_id = getLastEventId(txn, room_id); + const auto localUser = utils::localUser().toStdString(); - last_event_id_ = std::string(last_event_id); - fullyReadEventId_ = std::string(fullyReadEventId); + std::string fullyReadEventId; + if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) { + if (auto fr = + std::get_if>( + &ev.value())) { + fullyReadEventId = fr->content.event_id; + } } - // Retrieve all read receipts for that event. - return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_); + if (last_event_id.empty() || fullyReadEventId.empty()) + return true; + + if (last_event_id == fullyReadEventId) + return false; + + last_event_id_ = std::string(last_event_id); + fullyReadEventId_ = std::string(fullyReadEventId); + } + + // Retrieve all read receipts for that event. + return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_); } void Cache::saveState(const mtx::responses::Sync &res) { - using namespace mtx::events; - auto local_user_id = this->localUserId_.toStdString(); + using namespace mtx::events; + auto local_user_id = this->localUserId_.toStdString(); - auto currentBatchToken = nextBatchToken(); + auto currentBatchToken = nextBatchToken(); - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - setNextBatchToken(txn, res.next_batch); - - if (!res.account_data.events.empty()) { - auto accountDataDb = getAccountDataDb(txn, ""); - for (const auto &ev : res.account_data.events) - std::visit( - [&txn, &accountDataDb](const auto &event) { - auto j = json(event); - accountDataDb.put(txn, j["type"].get(), j.dump()); - }, - ev); - } + setNextBatchToken(txn, res.next_batch); - auto userKeyCacheDb = getUserKeysDb(txn); - - std::set spaces_with_updates; - std::set rooms_with_space_updates; - - // Save joined rooms - for (const auto &room : res.rooms.join) { - auto statesdb = getStatesDb(txn, room.first); - auto stateskeydb = getStatesKeyDb(txn, room.first); - auto membersdb = getMembersDb(txn, room.first); - auto eventsDb = getEventsDb(txn, room.first); - - saveStateEvents(txn, - statesdb, - stateskeydb, - membersdb, - eventsDb, - room.first, - room.second.state.events); - saveStateEvents(txn, - statesdb, - stateskeydb, - membersdb, - eventsDb, - room.first, - room.second.timeline.events); - - saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); - - RoomInfo updatedInfo; - updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); - updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); - updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); - updatedInfo.version = getRoomVersion(txn, statesdb).toStdString(); - updatedInfo.is_space = getRoomIsSpace(txn, statesdb); - - if (updatedInfo.is_space) { - bool space_updates = false; - for (const auto &e : room.second.state.events) - if (std::holds_alternative>(e) || - std::holds_alternative>(e)) - space_updates = true; - for (const auto &e : room.second.timeline.events) - if (std::holds_alternative>(e) || - std::holds_alternative>(e)) - space_updates = true; - - if (space_updates) - spaces_with_updates.insert(room.first); - } + if (!res.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, ""); + for (const auto &ev : res.account_data.events) + std::visit( + [&txn, &accountDataDb](const auto &event) { + auto j = json(event); + accountDataDb.put(txn, j["type"].get(), j.dump()); + }, + ev); + } - { - bool room_has_space_update = false; - for (const auto &e : room.second.state.events) { - if (auto se = std::get_if>(&e)) { - spaces_with_updates.insert(se->state_key); - room_has_space_update = true; - } - } - for (const auto &e : room.second.timeline.events) { - if (auto se = std::get_if>(&e)) { - spaces_with_updates.insert(se->state_key); - room_has_space_update = true; - } - } + auto userKeyCacheDb = getUserKeysDb(txn); - if (room_has_space_update) - rooms_with_space_updates.insert(room.first); - } + std::set spaces_with_updates; + std::set rooms_with_space_updates; - bool has_new_tags = false; - // Process the account_data associated with this room - if (!room.second.account_data.events.empty()) { - auto accountDataDb = getAccountDataDb(txn, room.first); - - for (const auto &evt : room.second.account_data.events) { - std::visit( - [&txn, &accountDataDb](const auto &event) { - auto j = json(event); - accountDataDb.put( - txn, j["type"].get(), j.dump()); - }, - evt); - - // for tag events - if (std::holds_alternative>( - evt)) { - auto tags_evt = - std::get>(evt); - has_new_tags = true; - for (const auto &tag : tags_evt.content.tags) { - updatedInfo.tags.push_back(tag.first); - } - } - if (auto fr = std::get_if>(&evt)) { - nhlog::db()->debug("Fully read: {}", fr->content.event_id); - } - } + // Save joined rooms + for (const auto &room : res.rooms.join) { + auto statesdb = getStatesDb(txn, room.first); + auto stateskeydb = getStatesKeyDb(txn, room.first); + auto membersdb = getMembersDb(txn, room.first); + auto eventsDb = getEventsDb(txn, room.first); + + saveStateEvents( + txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.state.events); + saveStateEvents( + txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.timeline.events); + + saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); + + RoomInfo updatedInfo; + updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); + updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); + updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); + updatedInfo.version = getRoomVersion(txn, statesdb).toStdString(); + updatedInfo.is_space = getRoomIsSpace(txn, statesdb); + + if (updatedInfo.is_space) { + bool space_updates = false; + for (const auto &e : room.second.state.events) + if (std::holds_alternative>(e) || + std::holds_alternative>(e)) + space_updates = true; + for (const auto &e : room.second.timeline.events) + if (std::holds_alternative>(e) || + std::holds_alternative>(e)) + space_updates = true; + + if (space_updates) + spaces_with_updates.insert(room.first); + } + + { + bool room_has_space_update = false; + for (const auto &e : room.second.state.events) { + if (auto se = std::get_if>(&e)) { + spaces_with_updates.insert(se->state_key); + room_has_space_update = true; } - if (!has_new_tags) { - // retrieve the old tags, they haven't changed - std::string_view data; - if (roomsDb_.get(txn, room.first, data)) { - try { - RoomInfo tmp = - json::parse(std::string_view(data.data(), data.size())); - updatedInfo.tags = tmp.tags; - } catch (const json::exception &e) { - nhlog::db()->warn( - "failed to parse room info: room_id ({}), {}: {}", - room.first, - std::string(data.data(), data.size()), - e.what()); - } - } + } + for (const auto &e : room.second.timeline.events) { + if (auto se = std::get_if>(&e)) { + spaces_with_updates.insert(se->state_key); + room_has_space_update = true; + } + } + + if (room_has_space_update) + rooms_with_space_updates.insert(room.first); + } + + bool has_new_tags = false; + // Process the account_data associated with this room + if (!room.second.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, room.first); + + for (const auto &evt : room.second.account_data.events) { + std::visit( + [&txn, &accountDataDb](const auto &event) { + auto j = json(event); + accountDataDb.put(txn, j["type"].get(), j.dump()); + }, + evt); + + // for tag events + if (std::holds_alternative>(evt)) { + auto tags_evt = std::get>(evt); + has_new_tags = true; + for (const auto &tag : tags_evt.content.tags) { + updatedInfo.tags.push_back(tag.first); + } + } + if (auto fr = std::get_if< + mtx::events::AccountDataEvent>(&evt)) { + nhlog::db()->debug("Fully read: {}", fr->content.event_id); + } + } + } + if (!has_new_tags) { + // retrieve the old tags, they haven't changed + std::string_view data; + if (roomsDb_.get(txn, room.first, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); + updatedInfo.tags = tmp.tags; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room.first, + std::string(data.data(), data.size()), + e.what()); } + } + } - roomsDb_.put(txn, room.first, json(updatedInfo).dump()); + roomsDb_.put(txn, room.first, json(updatedInfo).dump()); - for (const auto &e : room.second.ephemeral.events) { - if (auto receiptsEv = std::get_if< - mtx::events::EphemeralEvent>(&e)) { - Receipts receipts; + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = + std::get_if>(&e)) { + Receipts receipts; - for (const auto &[event_id, userReceipts] : - receiptsEv->content.receipts) { - for (const auto &[user_id, receipt] : userReceipts.users) { - receipts[event_id][user_id] = receipt.ts; - } - } - updateReadReceipt(txn, room.first, receipts); - } + for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + receipts[event_id][user_id] = receipt.ts; + } } - - // Clean up non-valid invites. - removeInvite(txn, room.first); + updateReadReceipt(txn, room.first, receipts); + } } - saveInvites(txn, res.rooms.invite); + // Clean up non-valid invites. + removeInvite(txn, room.first); + } - savePresence(txn, res.presence); + saveInvites(txn, res.rooms.invite); - markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); + savePresence(txn, res.presence); - removeLeftRooms(txn, res.rooms.leave); + markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); - updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates)); + removeLeftRooms(txn, res.rooms.leave); - txn.commit(); + updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates)); + + txn.commit(); - std::map readStatus; - - for (const auto &room : res.rooms.join) { - for (const auto &e : room.second.ephemeral.events) { - if (auto receiptsEv = std::get_if< - mtx::events::EphemeralEvent>(&e)) { - std::vector receipts; - - for (const auto &[event_id, userReceipts] : - receiptsEv->content.receipts) { - for (const auto &[user_id, receipt] : userReceipts.users) { - (void)receipt; - - if (user_id != local_user_id) { - receipts.push_back( - QString::fromStdString(event_id)); - break; - } - } - } - if (!receipts.empty()) - emit newReadReceipts(QString::fromStdString(room.first), - receipts); + std::map readStatus; + + for (const auto &room : res.rooms.join) { + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = + std::get_if>(&e)) { + std::vector receipts; + + for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + (void)receipt; + + if (user_id != local_user_id) { + receipts.push_back(QString::fromStdString(event_id)); + break; } + } } - readStatus.emplace(QString::fromStdString(room.first), - calculateRoomReadStatus(room.first)); + if (!receipts.empty()) + emit newReadReceipts(QString::fromStdString(room.first), receipts); + } } + readStatus.emplace(QString::fromStdString(room.first), calculateRoomReadStatus(room.first)); + } - emit roomReadStatus(readStatus); + emit roomReadStatus(readStatus); } void Cache::saveInvites(lmdb::txn &txn, const std::map &rooms) { - for (const auto &room : rooms) { - auto statesdb = getInviteStatesDb(txn, room.first); - auto membersdb = getInviteMembersDb(txn, room.first); + for (const auto &room : rooms) { + auto statesdb = getInviteStatesDb(txn, room.first); + auto membersdb = getInviteMembersDb(txn, room.first); - saveInvite(txn, statesdb, membersdb, room.second); + saveInvite(txn, statesdb, membersdb, room.second); - RoomInfo updatedInfo; - updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString(); - updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString(); - updatedInfo.avatar_url = - getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); - updatedInfo.is_space = getInviteRoomIsSpace(txn, statesdb); - updatedInfo.is_invite = true; + RoomInfo updatedInfo; + updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString(); + updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString(); + updatedInfo.avatar_url = getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); + updatedInfo.is_space = getInviteRoomIsSpace(txn, statesdb); + updatedInfo.is_invite = true; - invitesDb_.put(txn, room.first, json(updatedInfo).dump()); - } + invitesDb_.put(txn, room.first, json(updatedInfo).dump()); + } } void @@ -1638,32 +1586,29 @@ Cache::saveInvite(lmdb::txn &txn, lmdb::dbi &membersdb, const mtx::responses::InvitedRoom &room) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - for (const auto &e : room.invite_state) { - if (auto msg = std::get_if>(&e)) { - auto display_name = msg->content.display_name.empty() - ? msg->state_key - : msg->content.display_name; + for (const auto &e : room.invite_state) { + if (auto msg = std::get_if>(&e)) { + 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}; - membersdb.put(txn, msg->state_key, json(tmp).dump()); - } else { - std::visit( - [&txn, &statesdb](auto msg) { - auto j = json(msg); - bool res = - statesdb.put(txn, j["type"].get(), j.dump()); - - if (!res) - nhlog::db()->warn("couldn't save data: {}", - json(msg).dump()); - }, - e); - } + membersdb.put(txn, msg->state_key, json(tmp).dump()); + } else { + std::visit( + [&txn, &statesdb](auto msg) { + auto j = json(msg); + bool res = statesdb.put(txn, j["type"].get(), j.dump()); + + if (!res) + nhlog::db()->warn("couldn't save data: {}", json(msg).dump()); + }, + e); } + } } void @@ -1671,281 +1616,279 @@ Cache::savePresence( lmdb::txn &txn, const std::vector> &presenceUpdates) { - for (const auto &update : presenceUpdates) { - auto presenceDb = getPresenceDb(txn); + for (const auto &update : presenceUpdates) { + auto presenceDb = getPresenceDb(txn); - presenceDb.put(txn, update.sender, json(update.content).dump()); - } + presenceDb.put(txn, update.sender, json(update.content).dump()); + } } std::vector Cache::roomsWithStateUpdates(const mtx::responses::Sync &res) { - std::vector rooms; - for (const auto &room : res.rooms.join) { - bool hasUpdates = false; - for (const auto &s : room.second.state.events) { - if (containsStateUpdates(s)) { - hasUpdates = true; - break; - } - } - - for (const auto &s : room.second.timeline.events) { - if (containsStateUpdates(s)) { - hasUpdates = true; - break; - } - } + std::vector rooms; + for (const auto &room : res.rooms.join) { + bool hasUpdates = false; + for (const auto &s : room.second.state.events) { + if (containsStateUpdates(s)) { + hasUpdates = true; + break; + } + } - if (hasUpdates) - rooms.emplace_back(room.first); + for (const auto &s : room.second.timeline.events) { + if (containsStateUpdates(s)) { + hasUpdates = true; + break; + } } - for (const auto &room : res.rooms.invite) { - for (const auto &s : room.second.invite_state) { - if (containsStateUpdates(s)) { - rooms.emplace_back(room.first); - break; - } - } + if (hasUpdates) + rooms.emplace_back(room.first); + } + + for (const auto &room : res.rooms.invite) { + for (const auto &s : room.second.invite_state) { + if (containsStateUpdates(s)) { + rooms.emplace_back(room.first); + break; + } } + } - return rooms; + return rooms; } RoomInfo Cache::singleRoomInfo(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - try { - auto statesdb = getStatesDb(txn, room_id); - - std::string_view data; - - // Check if the room is joined. - if (roomsDb_.get(txn, room_id, data)) { - try { - RoomInfo tmp = json::parse(data); - tmp.member_count = getMembersDb(txn, room_id).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); - - return tmp; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room_id, - std::string(data.data(), data.size()), - e.what()); - } - } - } catch (const lmdb::error &e) { - nhlog::db()->warn( - "failed to read room info from db: room_id ({}), {}", room_id, e.what()); + try { + auto statesdb = getStatesDb(txn, room_id); + + std::string_view data; + + // Check if the room is joined. + if (roomsDb_.get(txn, room_id, data)) { + try { + RoomInfo tmp = json::parse(data); + tmp.member_count = getMembersDb(txn, room_id).size(txn); + tmp.join_rule = getRoomJoinRule(txn, statesdb); + tmp.guest_access = getRoomGuestAccess(txn, statesdb); + + return tmp; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room_id, + std::string(data.data(), data.size()), + e.what()); + } } + } catch (const lmdb::error &e) { + nhlog::db()->warn("failed to read room info from db: room_id ({}), {}", room_id, e.what()); + } - return RoomInfo(); + return RoomInfo(); } std::map Cache::getRoomInfo(const std::vector &rooms) { - std::map room_info; + std::map room_info; - // TODO This should be read only. - auto txn = lmdb::txn::begin(env_); + // TODO This should be read only. + auto txn = lmdb::txn::begin(env_); - for (const auto &room : rooms) { - std::string_view data; - auto statesdb = getStatesDb(txn, room); - - // Check if the room is joined. - if (roomsDb_.get(txn, room, data)) { - try { - RoomInfo tmp = json::parse(data); - tmp.member_count = getMembersDb(txn, room).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); - - room_info.emplace(QString::fromStdString(room), std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room, - std::string(data.data(), data.size()), - e.what()); - } - } else { - // Check if the room is an invite. - if (invitesDb_.get(txn, room, data)) { - try { - RoomInfo tmp = json::parse(std::string_view(data)); - tmp.member_count = getInviteMembersDb(txn, room).size(txn); - - room_info.emplace(QString::fromStdString(room), - std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - room, - std::string(data.data(), data.size()), - e.what()); - } - } + for (const auto &room : rooms) { + std::string_view data; + auto statesdb = getStatesDb(txn, room); + + // Check if the room is joined. + if (roomsDb_.get(txn, room, data)) { + try { + RoomInfo tmp = json::parse(data); + tmp.member_count = getMembersDb(txn, room).size(txn); + tmp.join_rule = getRoomJoinRule(txn, statesdb); + tmp.guest_access = getRoomGuestAccess(txn, statesdb); + + room_info.emplace(QString::fromStdString(room), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room, + std::string(data.data(), data.size()), + e.what()); + } + } else { + // Check if the room is an invite. + if (invitesDb_.get(txn, room, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data)); + tmp.member_count = getInviteMembersDb(txn, room).size(txn); + + room_info.emplace(QString::fromStdString(room), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + room, + std::string(data.data(), data.size()), + e.what()); } + } } + } - txn.commit(); + txn.commit(); - return room_info; + return room_info; } std::vector Cache::roomIds() { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector rooms; - std::string_view room_id, unused; + std::vector rooms; + std::string_view room_id, unused; - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - while (roomsCursor.get(room_id, unused, MDB_NEXT)) - rooms.push_back(QString::fromStdString(std::string(room_id))); + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + while (roomsCursor.get(room_id, unused, MDB_NEXT)) + rooms.push_back(QString::fromStdString(std::string(room_id))); - roomsCursor.close(); + roomsCursor.close(); - return rooms; + return rooms; } QMap Cache::getTimelineMentions() { - // TODO: Should be read-only, but getMentionsDb will attempt to create a DB - // if it doesn't exist, throwing an error. - auto txn = lmdb::txn::begin(env_, nullptr); + // TODO: Should be read-only, but getMentionsDb will attempt to create a DB + // if it doesn't exist, throwing an error. + auto txn = lmdb::txn::begin(env_, nullptr); - QMap notifs; + QMap notifs; - auto room_ids = getRoomIds(txn); + auto room_ids = getRoomIds(txn); - for (const auto &room_id : room_ids) { - auto roomNotifs = getTimelineMentionsForRoom(txn, room_id); - notifs[QString::fromStdString(room_id)] = roomNotifs; - } + for (const auto &room_id : room_ids) { + auto roomNotifs = getTimelineMentionsForRoom(txn, room_id); + notifs[QString::fromStdString(room_id)] = roomNotifs; + } - txn.commit(); + txn.commit(); - return notifs; + return notifs; } std::string Cache::previousBatchToken(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_, nullptr); - auto orderDb = getEventOrderDb(txn, room_id); + auto txn = lmdb::txn::begin(env_, nullptr); + auto orderDb = getEventOrderDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); - std::string_view indexVal, val; - if (!cursor.get(indexVal, val, MDB_FIRST)) { - return ""; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + std::string_view indexVal, val; + if (!cursor.get(indexVal, val, MDB_FIRST)) { + return ""; + } - auto j = json::parse(val); + auto j = json::parse(val); - return j.value("prev_batch", ""); + return j.value("prev_batch", ""); } Cache::Messages Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, uint64_t index, bool forward) { - // TODO(nico): Limit the messages returned by this maybe? - auto orderDb = getOrderToMessageDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); + // TODO(nico): Limit the messages returned by this maybe? + auto orderDb = getOrderToMessageDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); - Messages messages{}; + Messages messages{}; - std::string_view indexVal, event_id; + std::string_view indexVal, event_id; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (index == std::numeric_limits::max()) { - if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) { - index = lmdb::from_sv(indexVal); - } else { - messages.end_of_cache = true; - return messages; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (index == std::numeric_limits::max()) { + if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) { + index = lmdb::from_sv(indexVal); } else { - if (cursor.get(indexVal, event_id, MDB_SET)) { - index = lmdb::from_sv(indexVal); - } else { - messages.end_of_cache = true; - return messages; - } + messages.end_of_cache = true; + return messages; } + } else { + if (cursor.get(indexVal, event_id, MDB_SET)) { + index = lmdb::from_sv(indexVal); + } else { + messages.end_of_cache = true; + return messages; + } + } - int counter = 0; - - bool ret; - while ((ret = cursor.get(indexVal, - event_id, - counter == 0 ? (forward ? MDB_FIRST : MDB_LAST) - : (forward ? MDB_NEXT : MDB_PREV))) && - counter++ < BATCH_SIZE) { - std::string_view event; - bool success = eventsDb.get(txn, event_id, event); - if (!success) - continue; + int counter = 0; - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json(json::parse(event), te); - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", e.what()); - continue; - } + bool ret; + while ((ret = cursor.get(indexVal, + event_id, + counter == 0 ? (forward ? MDB_FIRST : MDB_LAST) + : (forward ? MDB_NEXT : MDB_PREV))) && + counter++ < BATCH_SIZE) { + std::string_view event; + bool success = eventsDb.get(txn, event_id, event); + if (!success) + continue; - messages.timeline.events.push_back(std::move(te.data)); + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json(json::parse(event), te); + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + continue; } - cursor.close(); - // std::reverse(timeline.events.begin(), timeline.events.end()); - messages.next_index = lmdb::from_sv(indexVal); - messages.end_of_cache = !ret; + messages.timeline.events.push_back(std::move(te.data)); + } + cursor.close(); - return messages; + // std::reverse(timeline.events.begin(), timeline.events.end()); + messages.next_index = lmdb::from_sv(indexVal); + messages.end_of_cache = !ret; + + return messages; } std::optional Cache::getEvent(const std::string &room_id, const std::string &event_id) { - auto txn = ro_txn(env_); - auto eventsDb = getEventsDb(txn, room_id); + auto txn = ro_txn(env_); + auto eventsDb = getEventsDb(txn, room_id); - std::string_view event{}; - bool success = eventsDb.get(txn, event_id, event); - if (!success) - return {}; + std::string_view event{}; + bool success = eventsDb.get(txn, event_id, event); + if (!success) + return {}; - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json(json::parse(event), te); - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", e.what()); - return std::nullopt; - } + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json(json::parse(event), te); + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + return std::nullopt; + } - return te; + return te; } void Cache::storeEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto event_json = mtx::accessors::serialize_event(event.data); - eventsDb.put(txn, event_id, event_json.dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto event_json = mtx::accessors::serialize_event(event.data); + eventsDb.put(txn, event_id, event_json.dump()); + txn.commit(); } void @@ -1953,962 +1896,944 @@ Cache::replaceEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - auto event_json = mtx::accessors::serialize_event(event.data).dump(); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto event_json = mtx::accessors::serialize_event(event.data).dump(); - { - eventsDb.del(txn, event_id); - eventsDb.put(txn, event_id, event_json); - for (auto relation : mtx::accessors::relations(event.data).relations) { - relationsDb.put(txn, relation.event_id, event_id); - } + { + eventsDb.del(txn, event_id); + eventsDb.put(txn, event_id, event_json); + for (auto relation : mtx::accessors::relations(event.data).relations) { + relationsDb.put(txn, relation.event_id, event_id); } + } - txn.commit(); + txn.commit(); } std::vector Cache::relatedEvents(const std::string &room_id, const std::string &event_id) { - auto txn = ro_txn(env_); - auto relationsDb = getRelationsDb(txn, room_id); + auto txn = ro_txn(env_); + auto relationsDb = getRelationsDb(txn, room_id); - std::vector related_ids; + std::vector related_ids; - auto related_cursor = lmdb::cursor::open(txn, relationsDb); - std::string_view related_to = event_id, related_event; - bool first = true; + auto related_cursor = lmdb::cursor::open(txn, relationsDb); + std::string_view related_to = event_id, related_event; + bool first = true; - try { - if (!related_cursor.get(related_to, related_event, MDB_SET)) - return {}; + try { + if (!related_cursor.get(related_to, related_event, MDB_SET)) + return {}; - while (related_cursor.get( - related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - if (event_id != std::string_view(related_to.data(), related_to.size())) - break; + while ( + related_cursor.get(related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + if (event_id != std::string_view(related_to.data(), related_to.size())) + break; - related_ids.emplace_back(related_event.data(), related_event.size()); - } - } catch (const lmdb::error &e) { - nhlog::db()->error("related events error: {}", e.what()); + related_ids.emplace_back(related_event.data(), related_event.size()); } + } catch (const lmdb::error &e) { + nhlog::db()->error("related events error: {}", e.what()); + } - return related_ids; + return related_ids; } size_t Cache::memberCount(const std::string &room_id) { - auto txn = ro_txn(env_); - return getMembersDb(txn, room_id).size(txn); + auto txn = ro_txn(env_); + return getMembersDb(txn, room_id).size(txn); } QMap Cache::roomInfo(bool withInvites) { - QMap result; + QMap result; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view room_id; - std::string_view room_data; + std::string_view room_id; + std::string_view room_data; - // Gather info about the joined rooms. - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(std::move(room_data)); - tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } - roomsCursor.close(); - - if (withInvites) { - // Gather info about the invites. - auto invitesCursor = lmdb::cursor::open(txn, invitesDb_); - while (invitesCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } - invitesCursor.close(); + // Gather info about the joined rooms. + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { + RoomInfo tmp = json::parse(std::move(room_data)); + tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); + } + roomsCursor.close(); + + if (withInvites) { + // Gather info about the invites. + auto invitesCursor = lmdb::cursor::open(txn, invitesDb_); + while (invitesCursor.get(room_id, room_data, MDB_NEXT)) { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); } + invitesCursor.close(); + } - return result; + return result; } std::string Cache::getLastEventId(lmdb::txn &txn, const std::string &room_id) { - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + lmdb::dbi orderDb; + try { + orderDb = getOrderToMessageDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view indexVal, val; + std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (!cursor.get(indexVal, val, MDB_LAST)) { - return {}; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (!cursor.get(indexVal, val, MDB_LAST)) { + return {}; + } - return std::string(val.data(), val.size()); + return std::string(val.data(), val.size()); } std::optional Cache::getTimelineRange(const std::string &room_id) { - auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + auto txn = ro_txn(env_); + lmdb::dbi orderDb; + try { + orderDb = getOrderToMessageDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view indexVal, val; + std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (!cursor.get(indexVal, val, MDB_LAST)) { - return {}; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (!cursor.get(indexVal, val, MDB_LAST)) { + return {}; + } - TimelineRange range{}; - range.last = lmdb::from_sv(indexVal); + TimelineRange range{}; + range.last = lmdb::from_sv(indexVal); - if (!cursor.get(indexVal, val, MDB_FIRST)) { - return {}; - } - range.first = lmdb::from_sv(indexVal); + if (!cursor.get(indexVal, val, MDB_FIRST)) { + return {}; + } + range.first = lmdb::from_sv(indexVal); - return range; + return range; } std::optional Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id) { - if (event_id.empty() || room_id.empty()) - return {}; + if (event_id.empty() || room_id.empty()) + return {}; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getMessageToOrderDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + lmdb::dbi orderDb; + try { + orderDb = getMessageToOrderDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view indexVal{event_id.data(), event_id.size()}, val; + std::string_view indexVal{event_id.data(), event_id.size()}, val; - bool success = orderDb.get(txn, indexVal, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, indexVal, val); + if (!success) { + return {}; + } - return lmdb::from_sv(val); + return lmdb::from_sv(val); } std::optional Cache::getEventIndex(const std::string &room_id, std::string_view event_id) { - if (room_id.empty() || event_id.empty()) - return {}; + if (room_id.empty() || event_id.empty()) + return {}; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getEventToOrderDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + lmdb::dbi orderDb; + try { + orderDb = getEventToOrderDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, event_id, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, event_id, val); + if (!success) { + return {}; + } - return lmdb::from_sv(val); + return lmdb::from_sv(val); } std::optional> Cache::lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id) { - if (room_id.empty() || event_id.empty()) - return {}; - - auto txn = ro_txn(env_); - - lmdb::dbi orderDb; - lmdb::dbi eventOrderDb; - lmdb::dbi timelineDb; - try { - orderDb = getEventToOrderDb(txn, room_id); - eventOrderDb = getEventOrderDb(txn, room_id); - timelineDb = getMessageToOrderDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } - - std::string_view indexVal; - - bool success = orderDb.get(txn, event_id, indexVal); - if (!success) { - return {}; - } - - try { - uint64_t prevIdx = lmdb::from_sv(indexVal); - std::string prevId{event_id}; - - auto cursor = lmdb::cursor::open(txn, eventOrderDb); - cursor.get(indexVal, MDB_SET); - while (cursor.get(indexVal, event_id, MDB_NEXT)) { - std::string evId = json::parse(event_id)["event_id"].get(); - std::string_view temp; - if (timelineDb.get(txn, evId, temp)) { - return std::pair{prevIdx, std::string(prevId)}; - } else { - prevIdx = lmdb::from_sv(indexVal); - prevId = std::move(evId); - } - } - + if (room_id.empty() || event_id.empty()) + return {}; + + auto txn = ro_txn(env_); + + lmdb::dbi orderDb; + lmdb::dbi eventOrderDb; + lmdb::dbi timelineDb; + try { + orderDb = getEventToOrderDb(txn, room_id); + eventOrderDb = getEventOrderDb(txn, room_id); + timelineDb = getMessageToOrderDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } + + std::string_view indexVal; + + bool success = orderDb.get(txn, event_id, indexVal); + if (!success) { + return {}; + } + + try { + uint64_t prevIdx = lmdb::from_sv(indexVal); + std::string prevId{event_id}; + + auto cursor = lmdb::cursor::open(txn, eventOrderDb); + cursor.get(indexVal, MDB_SET); + while (cursor.get(indexVal, event_id, MDB_NEXT)) { + std::string evId = json::parse(event_id)["event_id"].get(); + std::string_view temp; + if (timelineDb.get(txn, evId, temp)) { return std::pair{prevIdx, std::string(prevId)}; - } catch (lmdb::runtime_error &e) { - nhlog::db()->error( - "Failed to get last invisible event after {}", event_id, e.what()); - return {}; + } else { + prevIdx = lmdb::from_sv(indexVal); + prevId = std::move(evId); + } } + + return std::pair{prevIdx, std::string(prevId)}; + } catch (lmdb::runtime_error &e) { + nhlog::db()->error("Failed to get last invisible event after {}", event_id, e.what()); + return {}; + } } std::optional Cache::getArrivalIndex(const std::string &room_id, std::string_view event_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getEventToOrderDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + lmdb::dbi orderDb; + try { + orderDb = getEventToOrderDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, event_id, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, event_id, val); + if (!success) { + return {}; + } - return lmdb::from_sv(val); + return lmdb::from_sv(val); } std::optional Cache::getTimelineEventId(const std::string &room_id, uint64_t index) { - auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(txn, room_id); - } catch (lmdb::runtime_error &e) { - nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", - room_id, - e.what()); - return {}; - } + auto txn = ro_txn(env_); + lmdb::dbi orderDb; + try { + orderDb = getOrderToMessageDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error( + "Can't open db for room '{}', probably doesn't exist yet. ({})", room_id, e.what()); + return {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, lmdb::to_sv(index), val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, lmdb::to_sv(index), val); + if (!success) { + return {}; + } - return std::string(val); + return std::string(val); } QHash Cache::invites() { - QHash result; + QHash result; - auto txn = ro_txn(env_); - auto cursor = lmdb::cursor::open(txn, invitesDb_); + auto txn = ro_txn(env_); + auto cursor = lmdb::cursor::open(txn, invitesDb_); - std::string_view room_id, room_data; + std::string_view room_id, room_data; - while (cursor.get(room_id, room_data, MDB_NEXT)) { - try { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - room_id, - std::string(room_data), - e.what()); - } + while (cursor.get(room_id, room_data, MDB_NEXT)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + room_id, + std::string(room_data), + e.what()); } + } - cursor.close(); + cursor.close(); - return result; + return result; } std::optional Cache::invite(std::string_view roomid) { - std::optional result; + std::optional result; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view room_data; + std::string_view room_data; - if (invitesDb_.get(txn, roomid, room_data)) { - try { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn); - result = std::move(tmp); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - roomid, - std::string(room_data), - e.what()); - } + if (invitesDb_.get(txn, roomid, room_data)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn); + result = std::move(tmp); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + roomid, + std::string(room_data), + e.what()); } + } - return result; + return result; } QString Cache::getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); - if (res) { - try { - StateEvent msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent msg = json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.url.empty()) - return QString::fromStdString(msg.content.url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); - } + if (!msg.content.url.empty()) + return QString::fromStdString(msg.content.url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); } + } - // We don't use an avatar for group chats. - if (membersdb.size(txn) > 2) - return QString(); + // We don't use an avatar for group chats. + if (membersdb.size(txn) > 2) + return QString(); - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id; - std::string_view member_data; - std::string fallback_url; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id; + std::string_view member_data; + std::string fallback_url; - // Resolve avatar for 1-1 chats. - while (cursor.get(user_id, member_data, MDB_NEXT)) { - try { - MemberInfo m = json::parse(member_data); - if (user_id == localUserId_.toStdString()) { - fallback_url = m.avatar_url; - continue; - } + // Resolve avatar for 1-1 chats. + while (cursor.get(user_id, member_data, MDB_NEXT)) { + try { + MemberInfo m = json::parse(member_data); + if (user_id == localUserId_.toStdString()) { + fallback_url = m.avatar_url; + continue; + } - cursor.close(); - return QString::fromStdString(m.avatar_url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + cursor.close(); + return QString::fromStdString(m.avatar_url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - // Default case when there is only one member. - return QString::fromStdString(fallback_url); + // Default case when there is only one member. + return QString::fromStdString(fallback_url); } QString Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); - if (res) { - try { - StateEvent msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent msg = json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.name.empty()) - return QString::fromStdString(msg.content.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); - } + if (!msg.content.name.empty()) + return QString::fromStdString(msg.content.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); } + } - res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); + res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); - if (res) { - try { - StateEvent msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent msg = + json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.alias.empty()) - return QString::fromStdString(msg.content.alias); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", - e.what()); - } + if (!msg.content.alias.empty()) + return QString::fromStdString(msg.content.alias); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - const auto total = membersdb.size(txn); - - std::size_t ii = 0; - std::string_view user_id; - std::string_view member_data; - std::map members; + auto cursor = lmdb::cursor::open(txn, membersdb); + const auto total = membersdb.size(txn); - while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) { - try { - members.emplace(user_id, json::parse(member_data)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + std::size_t ii = 0; + std::string_view user_id; + std::string_view member_data; + std::map members; - ii++; + while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) { + try { + members.emplace(user_id, json::parse(member_data)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } - cursor.close(); + ii++; + } - if (total == 1 && !members.empty()) - return QString::fromStdString(members.begin()->second.name); + cursor.close(); - auto first_member = [&members, this]() { - for (const auto &m : members) { - if (m.first != localUserId_.toStdString()) - return QString::fromStdString(m.second.name); - } + if (total == 1 && !members.empty()) + return QString::fromStdString(members.begin()->second.name); - return localUserId_; - }(); + auto first_member = [&members, this]() { + for (const auto &m : members) { + if (m.first != localUserId_.toStdString()) + return QString::fromStdString(m.second.name); + } + + return localUserId_; + }(); - if (total == 2) - return first_member; - else if (total > 2) - return QString("%1 and %2 others").arg(first_member).arg(total - 1); + if (total == 2) + return first_member; + else if (total > 2) + return QString("%1 and %2 others").arg(first_member).arg(total - 1); - return "Empty Room"; + return "Empty Room"; } mtx::events::state::JoinRule Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event); - if (res) { - try { - StateEvent msg = json::parse(event); - return msg.content.join_rule; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what()); - } + if (res) { + try { + StateEvent msg = json::parse(event); + return msg.content.join_rule; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what()); } - return state::JoinRule::Knock; + } + return state::JoinRule::Knock; } bool Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event); - if (res) { - try { - StateEvent msg = json::parse(event); - return msg.content.guest_access == AccessState::CanJoin; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.guest_access event: {}", - e.what()); - } + if (res) { + try { + StateEvent msg = json::parse(event); + return msg.content.guest_access == AccessState::CanJoin; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.guest_access event: {}", e.what()); } - return false; + } + return false; } QString Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event); - if (res) { - try { - StateEvent msg = json::parse(event); + if (res) { + try { + StateEvent msg = json::parse(event); - if (!msg.content.topic.empty()) - return QString::fromStdString(msg.content.topic); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (!msg.content.topic.empty()) + return QString::fromStdString(msg.content.topic); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return QString(); + return QString(); } QString Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StateEvent msg = json::parse(event); + if (res) { + try { + StateEvent msg = json::parse(event); - if (!msg.content.room_version.empty()) - return QString::fromStdString(msg.content.room_version); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); - } + if (!msg.content.room_version.empty()) + return QString::fromStdString(msg.content.room_version); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); } + } - nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); - return QString("1"); + nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); + return QString("1"); } bool Cache::getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StateEvent msg = json::parse(event); + if (res) { + try { + StateEvent msg = json::parse(event); - return msg.content.type == mtx::events::state::room_type::space; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); - } + return msg.content.type == mtx::events::state::room_type::space; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); } + } - nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); - return false; + nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); + return false; } std::optional Cache::getRoomAliases(const std::string &roomid) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - auto txn = ro_txn(env_); - auto statesdb = getStatesDb(txn, roomid); + auto txn = ro_txn(env_); + auto statesdb = getStatesDb(txn, roomid); - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); - if (res) { - try { - StateEvent msg = json::parse(event); + if (res) { + try { + StateEvent msg = json::parse(event); - return msg.content; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", - e.what()); - } + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what()); } + } - return std::nullopt; + return std::nullopt; } QString Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); - if (res) { - try { - StrippedEvent msg = json::parse(event); - return QString::fromStdString(msg.content.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); - } + if (res) { + try { + StrippedEvent msg = json::parse(event); + return QString::fromStdString(msg.content.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id, member_data; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id, member_data; - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; + while (cursor.get(user_id, member_data, MDB_NEXT)) { + if (user_id == localUserId_.toStdString()) + continue; - try { - MemberInfo tmp = json::parse(member_data); - cursor.close(); + try { + MemberInfo tmp = json::parse(member_data); + cursor.close(); - return QString::fromStdString(tmp.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + return QString::fromStdString(tmp.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - return QString("Empty Room"); + return QString("Empty Room"); } QString Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); - if (res) { - try { - StrippedEvent msg = json::parse(event); - return QString::fromStdString(msg.content.url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); - } + if (res) { + try { + StrippedEvent msg = json::parse(event); + return QString::fromStdString(msg.content.url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id, member_data; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id, member_data; - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; + while (cursor.get(user_id, member_data, MDB_NEXT)) { + if (user_id == localUserId_.toStdString()) + continue; - try { - MemberInfo tmp = json::parse(member_data); - cursor.close(); + try { + MemberInfo tmp = json::parse(member_data); + cursor.close(); - return QString::fromStdString(tmp.avatar_url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + return QString::fromStdString(tmp.avatar_url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - return QString(); + return QString(); } QString Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event); + std::string_view event; + bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event); - if (res) { - try { - StrippedEvent msg = json::parse(event); - return QString::fromStdString(msg.content.topic); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (res) { + try { + StrippedEvent msg = json::parse(event); + return QString::fromStdString(msg.content.topic); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return QString(); + return QString(); } bool Cache::getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StrippedEvent msg = json::parse(event); - return msg.content.type == mtx::events::state::room_type::space; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (res) { + try { + StrippedEvent msg = json::parse(event); + return msg.content.type == mtx::events::state::room_type::space; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return false; + return false; } std::vector Cache::joinedRooms() { - auto txn = ro_txn(env_); - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + auto txn = ro_txn(env_); + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - std::string_view id, data; - std::vector room_ids; + std::string_view id, data; + std::vector room_ids; - // Gather the room ids for the joined rooms. - while (roomsCursor.get(id, data, MDB_NEXT)) - room_ids.emplace_back(id); + // Gather the room ids for the joined rooms. + while (roomsCursor.get(id, data, MDB_NEXT)) + room_ids.emplace_back(id); - roomsCursor.close(); + roomsCursor.close(); - return room_ids; + return room_ids; } std::optional Cache::getMember(const std::string &room_id, const std::string &user_id) { - if (user_id.empty() || !env_.handle()) - return std::nullopt; + if (user_id.empty() || !env_.handle()) + return std::nullopt; - try { - auto txn = ro_txn(env_); + try { + auto txn = ro_txn(env_); - auto membersdb = getMembersDb(txn, room_id); + auto membersdb = getMembersDb(txn, room_id); - std::string_view info; - if (membersdb.get(txn, user_id, info)) { - MemberInfo m = json::parse(info); - return m; - } - } catch (std::exception &e) { - nhlog::db()->warn( - "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what()); + std::string_view info; + if (membersdb.get(txn, user_id, info)) { + MemberInfo m = json::parse(info); + return m; } - return std::nullopt; + } catch (std::exception &e) { + nhlog::db()->warn( + "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what()); + } + return std::nullopt; } std::vector Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len) { - auto txn = ro_txn(env_); - auto db = getMembersDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, db); - - std::size_t currentIndex = 0; + auto txn = ro_txn(env_); + auto db = getMembersDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, db); - const auto endIndex = std::min(startIndex + len, db.size(txn)); + std::size_t currentIndex = 0; - std::vector members; + const auto endIndex = std::min(startIndex + len, db.size(txn)); - std::string_view user_id, user_data; - while (cursor.get(user_id, user_data, MDB_NEXT)) { - if (currentIndex < startIndex) { - currentIndex += 1; - continue; - } + std::vector members; - if (currentIndex >= endIndex) - break; + std::string_view user_id, user_data; + while (cursor.get(user_id, user_data, MDB_NEXT)) { + if (currentIndex < startIndex) { + currentIndex += 1; + continue; + } - try { - MemberInfo tmp = json::parse(user_data); - members.emplace_back( - RoomMember{QString::fromStdString(std::string(user_id)), - QString::fromStdString(tmp.name)}); - } catch (const json::exception &e) { - nhlog::db()->warn("{}", e.what()); - } + if (currentIndex >= endIndex) + break; - currentIndex += 1; + try { + MemberInfo tmp = json::parse(user_data); + members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), + QString::fromStdString(tmp.name)}); + } catch (const json::exception &e) { + nhlog::db()->warn("{}", e.what()); } - cursor.close(); + currentIndex += 1; + } - return members; + cursor.close(); + + return members; } std::vector Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len) { - auto txn = ro_txn(env_); - auto db = getInviteMembersDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, db); - - std::size_t currentIndex = 0; + auto txn = ro_txn(env_); + auto db = getInviteMembersDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, db); - const auto endIndex = std::min(startIndex + len, db.size(txn)); + std::size_t currentIndex = 0; - std::vector members; + const auto endIndex = std::min(startIndex + len, db.size(txn)); - std::string_view user_id, user_data; - while (cursor.get(user_id, user_data, MDB_NEXT)) { - if (currentIndex < startIndex) { - currentIndex += 1; - continue; - } + std::vector members; - if (currentIndex >= endIndex) - break; + std::string_view user_id, user_data; + while (cursor.get(user_id, user_data, MDB_NEXT)) { + if (currentIndex < startIndex) { + currentIndex += 1; + continue; + } - try { - MemberInfo tmp = json::parse(user_data); - members.emplace_back( - RoomMember{QString::fromStdString(std::string(user_id)), - QString::fromStdString(tmp.name)}); - } catch (const json::exception &e) { - nhlog::db()->warn("{}", e.what()); - } + if (currentIndex >= endIndex) + break; - currentIndex += 1; + try { + MemberInfo tmp = json::parse(user_data); + members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), + QString::fromStdString(tmp.name)}); + } catch (const json::exception &e) { + nhlog::db()->warn("{}", e.what()); } - cursor.close(); + currentIndex += 1; + } - return members; + cursor.close(); + + return members; } bool Cache::isRoomMember(const std::string &user_id, const std::string &room_id) { - try { - auto txn = ro_txn(env_); - auto db = getMembersDb(txn, room_id); + try { + auto txn = ro_txn(env_); + auto db = getMembersDb(txn, room_id); - std::string_view value; - bool res = db.get(txn, user_id, value); + std::string_view value; + bool res = db.get(txn, user_id, value); - return res; - } catch (std::exception &e) { - nhlog::db()->warn("Failed to read member membership ({}) in room ({}): {}", - user_id, - room_id, - e.what()); - } - return false; + return res; + } catch (std::exception &e) { + nhlog::db()->warn( + "Failed to read member membership ({}) in room ({}): {}", user_id, room_id, e.what()); + } + return false; } void Cache::savePendingMessage(const std::string &room_id, const mtx::events::collections::TimelineEvent &message) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); - mtx::responses::Timeline timeline; - timeline.events.push_back(message.data); - saveTimelineMessages(txn, eventsDb, room_id, timeline); + mtx::responses::Timeline timeline; + timeline.events.push_back(message.data); + saveTimelineMessages(txn, eventsDb, room_id, timeline); - auto pending = getPendingMessagesDb(txn, room_id); + auto pending = getPendingMessagesDb(txn, room_id); - int64_t now = QDateTime::currentMSecsSinceEpoch(); - pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data)); + int64_t now = QDateTime::currentMSecsSinceEpoch(); + pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data)); - txn.commit(); + txn.commit(); } std::optional Cache::firstPendingMessage(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - auto pending = getPendingMessagesDb(txn, room_id); - - { - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - auto eventsDb = getEventsDb(txn, room_id); - std::string_view event; - if (!eventsDb.get(txn, pendingTxn, event)) { - pending.del(txn, tsIgnored, pendingTxn); - continue; - } + auto txn = lmdb::txn::begin(env_); + auto pending = getPendingMessagesDb(txn, room_id); + + { + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + auto eventsDb = getEventsDb(txn, room_id); + std::string_view event; + if (!eventsDb.get(txn, pendingTxn, event)) { + pending.del(txn, tsIgnored, pendingTxn); + continue; + } + + try { + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(json::parse(event), te); - try { - mtx::events::collections::TimelineEvent te; - mtx::events::collections::from_json(json::parse(event), te); - - pendingCursor.close(); - txn.commit(); - return te; - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", - e.what()); - pending.del(txn, tsIgnored, pendingTxn); - continue; - } - } + pendingCursor.close(); + txn.commit(); + return te; + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + pending.del(txn, tsIgnored, pendingTxn); + continue; + } } + } - txn.commit(); + txn.commit(); - return std::nullopt; + return std::nullopt; } void Cache::removePendingStatus(const std::string &room_id, const std::string &txn_id) { - auto txn = lmdb::txn::begin(env_); - auto pending = getPendingMessagesDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto pending = getPendingMessagesDb(txn, room_id); - { - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) - lmdb::cursor_del(pendingCursor); - } + { + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) + lmdb::cursor_del(pendingCursor); } + } - txn.commit(); + txn.commit(); } void @@ -2917,403 +2842,398 @@ Cache::saveTimelineMessages(lmdb::txn &txn, const std::string &room_id, const mtx::responses::Timeline &res) { - if (res.events.empty()) - return; - - auto relationsDb = getRelationsDb(txn, room_id); - - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); - auto pending = getPendingMessagesDb(txn, room_id); - - if (res.limited) { - lmdb::dbi_drop(txn, orderDb, false); - lmdb::dbi_drop(txn, evToOrderDb, false); - lmdb::dbi_drop(txn, msg2orderDb, false); - lmdb::dbi_drop(txn, order2msgDb, false); - lmdb::dbi_drop(txn, pending, true); - } - - using namespace mtx::events; - using namespace mtx::events::state; - - std::string_view indexVal, val; - uint64_t index = std::numeric_limits::max() / 2; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (cursor.get(indexVal, val, MDB_LAST)) { - index = lmdb::from_sv(indexVal); - } - - uint64_t msgIndex = std::numeric_limits::max() / 2; - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - if (msgCursor.get(indexVal, val, MDB_LAST)) { - msgIndex = lmdb::from_sv(indexVal); - } - - bool first = true; - for (const auto &e : res.events) { - auto event = mtx::accessors::serialize_event(e); - auto txn_id = mtx::accessors::transaction_id(e); - - std::string event_id_val = event.value("event_id", ""); - if (event_id_val.empty()) { - nhlog::db()->error("Event without id!"); - continue; + if (res.events.empty()) + return; + + auto relationsDb = getRelationsDb(txn, room_id); + + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); + auto pending = getPendingMessagesDb(txn, room_id); + + if (res.limited) { + lmdb::dbi_drop(txn, orderDb, false); + lmdb::dbi_drop(txn, evToOrderDb, false); + lmdb::dbi_drop(txn, msg2orderDb, false); + lmdb::dbi_drop(txn, order2msgDb, false); + lmdb::dbi_drop(txn, pending, true); + } + + using namespace mtx::events; + using namespace mtx::events::state; + + std::string_view indexVal, val; + uint64_t index = std::numeric_limits::max() / 2; + auto cursor = lmdb::cursor::open(txn, orderDb); + if (cursor.get(indexVal, val, MDB_LAST)) { + index = lmdb::from_sv(indexVal); + } + + uint64_t msgIndex = std::numeric_limits::max() / 2; + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + if (msgCursor.get(indexVal, val, MDB_LAST)) { + msgIndex = lmdb::from_sv(indexVal); + } + + bool first = true; + for (const auto &e : res.events) { + auto event = mtx::accessors::serialize_event(e); + auto txn_id = mtx::accessors::transaction_id(e); + + std::string event_id_val = event.value("event_id", ""); + if (event_id_val.empty()) { + nhlog::db()->error("Event without id!"); + continue; + } + + std::string_view event_id = event_id_val; + + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + if (first && !res.prev_batch.empty()) + orderEntry["prev_batch"] = res.prev_batch; + + std::string_view txn_order; + if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) { + eventsDb.put(txn, event_id, event.dump()); + eventsDb.del(txn, txn_id); + + std::string_view msg_txn_order; + if (msg2orderDb.get(txn, txn_id, msg_txn_order)) { + order2msgDb.put(txn, msg_txn_order, event_id); + msg2orderDb.put(txn, event_id, msg_txn_order); + msg2orderDb.del(txn, txn_id); + } + + orderDb.put(txn, txn_order, orderEntry.dump()); + evToOrderDb.put(txn, event_id, txn_order); + evToOrderDb.del(txn, txn_id); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.del(txn, r.event_id, txn_id); + relationsDb.put(txn, r.event_id, event_id); + } } + } + + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) + lmdb::cursor_del(pendingCursor); + } + } else if (auto redaction = + std::get_if>(&e)) { + if (redaction->redacts.empty()) + continue; + + std::string_view oldEvent; + bool success = eventsDb.get(txn, redaction->redacts, oldEvent); + if (!success) + continue; + + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json( + json::parse(std::string_view(oldEvent.data(), oldEvent.size())), te); + // overwrite the content and add redation data + std::visit( + [redaction](auto &ev) { + ev.unsigned_data.redacted_because = *redaction; + ev.unsigned_data.redacted_by = redaction->event_id; + }, + te.data); + event = mtx::accessors::serialize_event(te.data); + event["content"].clear(); + + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + continue; + } - std::string_view event_id = event_id_val; - - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - if (first && !res.prev_batch.empty()) - orderEntry["prev_batch"] = res.prev_batch; - - std::string_view txn_order; - if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) { - eventsDb.put(txn, event_id, event.dump()); - eventsDb.del(txn, txn_id); - - std::string_view msg_txn_order; - if (msg2orderDb.get(txn, txn_id, msg_txn_order)) { - order2msgDb.put(txn, msg_txn_order, event_id); - msg2orderDb.put(txn, event_id, msg_txn_order); - msg2orderDb.del(txn, txn_id); - } - - orderDb.put(txn, txn_order, orderEntry.dump()); - evToOrderDb.put(txn, event_id, txn_order); - evToOrderDb.del(txn, txn_id); - - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.del(txn, r.event_id, txn_id); - relationsDb.put(txn, r.event_id, event_id); - } - } - } - - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - if (std::string_view(pendingTxn.data(), pendingTxn.size()) == - txn_id) - lmdb::cursor_del(pendingCursor); - } - } else if (auto redaction = - std::get_if>( - &e)) { - if (redaction->redacts.empty()) - continue; - - std::string_view oldEvent; - bool success = eventsDb.get(txn, redaction->redacts, oldEvent); - if (!success) - continue; - - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json( - json::parse(std::string_view(oldEvent.data(), oldEvent.size())), - te); - // overwrite the content and add redation data - std::visit( - [redaction](auto &ev) { - ev.unsigned_data.redacted_because = *redaction; - ev.unsigned_data.redacted_by = redaction->event_id; - }, - te.data); - event = mtx::accessors::serialize_event(te.data); - event["content"].clear(); - - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", - e.what()); - continue; - } - - eventsDb.put(txn, redaction->redacts, event.dump()); - eventsDb.put(txn, redaction->event_id, json(*redaction).dump()); - } else { - first = false; + eventsDb.put(txn, redaction->redacts, event.dump()); + eventsDb.put(txn, redaction->event_id, json(*redaction).dump()); + } else { + first = false; - // This check protects against duplicates in the timeline. If the event_id - // is already in the DB, we skip putting it (again) in ordered DBs, and only - // update the event itself and its relations. - std::string_view unused_read; - if (!evToOrderDb.get(txn, event_id, unused_read)) { - ++index; + // This check protects against duplicates in the timeline. If the event_id + // is already in the DB, we skip putting it (again) in ordered DBs, and only + // update the event itself and its relations. + std::string_view unused_read; + if (!evToOrderDb.get(txn, event_id, unused_read)) { + ++index; - nhlog::db()->debug("saving '{}'", orderEntry.dump()); + nhlog::db()->debug("saving '{}'", orderEntry.dump()); - cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND); - evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); + cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND); + evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); - // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(txn, e, room_id)) { - ++msgIndex; - msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND); + // TODO(Nico): Allow blacklisting more event types in UI + if (!isHiddenEvent(txn, e, room_id)) { + ++msgIndex; + msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND); - msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); - } - } else { - nhlog::db()->warn("duplicate event '{}'", orderEntry.dump()); - } - eventsDb.put(txn, event_id, event.dump()); - - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.put(txn, r.event_id, event_id); - } - } - } + msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); + } + } else { + nhlog::db()->warn("duplicate event '{}'", orderEntry.dump()); + } + eventsDb.put(txn, event_id, event.dump()); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.put(txn, r.event_id, event_id); + } } + } } + } } uint64_t Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); - - std::string_view indexVal, val; - uint64_t index = std::numeric_limits::max() / 2; - { - auto cursor = lmdb::cursor::open(txn, orderDb); - if (cursor.get(indexVal, val, MDB_FIRST)) { - index = lmdb::from_sv(indexVal); - } - } + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); - uint64_t msgIndex = std::numeric_limits::max() / 2; - { - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - if (msgCursor.get(indexVal, val, MDB_FIRST)) { - msgIndex = lmdb::from_sv(indexVal); - } - } + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); - if (res.chunk.empty()) { - if (orderDb.get(txn, lmdb::to_sv(index), val)) { - auto orderEntry = json::parse(val); - orderEntry["prev_batch"] = res.end; - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - txn.commit(); - } - return index; + std::string_view indexVal, val; + uint64_t index = std::numeric_limits::max() / 2; + { + auto cursor = lmdb::cursor::open(txn, orderDb); + if (cursor.get(indexVal, val, MDB_FIRST)) { + index = lmdb::from_sv(indexVal); } + } - std::string event_id_val; - for (const auto &e : res.chunk) { - if (std::holds_alternative< - mtx::events::RedactionEvent>(e)) - continue; - - auto event = mtx::accessors::serialize_event(e); - event_id_val = event["event_id"].get(); - std::string_view event_id = event_id_val; - - // This check protects against duplicates in the timeline. If the event_id is - // already in the DB, we skip putting it (again) in ordered DBs, and only update the - // event itself and its relations. - std::string_view unused_read; - if (!evToOrderDb.get(txn, event_id, unused_read)) { - --index; - - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); - - // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(txn, e, room_id)) { - --msgIndex; - order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id); - - msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); - } - } - eventsDb.put(txn, event_id, event.dump()); - - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.put(txn, r.event_id, event_id); - } - } + uint64_t msgIndex = std::numeric_limits::max() / 2; + { + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + if (msgCursor.get(indexVal, val, MDB_FIRST)) { + msgIndex = lmdb::from_sv(indexVal); + } + } + + if (res.chunk.empty()) { + if (orderDb.get(txn, lmdb::to_sv(index), val)) { + auto orderEntry = json::parse(val); + orderEntry["prev_batch"] = res.end; + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + txn.commit(); + } + return index; + } + + std::string event_id_val; + for (const auto &e : res.chunk) { + if (std::holds_alternative>(e)) + continue; + + auto event = mtx::accessors::serialize_event(e); + event_id_val = event["event_id"].get(); + std::string_view event_id = event_id_val; + + // This check protects against duplicates in the timeline. If the event_id is + // already in the DB, we skip putting it (again) in ordered DBs, and only update the + // event itself and its relations. + std::string_view unused_read; + if (!evToOrderDb.get(txn, event_id, unused_read)) { + --index; + + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); + + // TODO(Nico): Allow blacklisting more event types in UI + if (!isHiddenEvent(txn, e, room_id)) { + --msgIndex; + order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id); + + msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); + } + } + eventsDb.put(txn, event_id, event.dump()); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.put(txn, r.event_id, event_id); } + } } + } - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - orderEntry["prev_batch"] = res.end; - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + orderEntry["prev_batch"] = res.end; + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - txn.commit(); + txn.commit(); - return msgIndex; + return msgIndex; } void Cache::clearTimeline(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); - std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); - bool start = true; - bool passed_pagination_token = false; - while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { - start = false; - json obj; + std::string_view indexVal, val; + auto cursor = lmdb::cursor::open(txn, orderDb); - try { - obj = json::parse(std::string_view(val.data(), val.size())); - } catch (std::exception &) { - // workaround bug in the initial db format, where we sometimes didn't store - // json... - obj = {{"event_id", std::string(val.data(), val.size())}}; - } + bool start = true; + bool passed_pagination_token = false; + while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; + json obj; - if (passed_pagination_token) { - if (obj.count("event_id") != 0) { - std::string event_id = obj["event_id"].get(); - - if (!event_id.empty()) { - evToOrderDb.del(txn, event_id); - eventsDb.del(txn, event_id); - relationsDb.del(txn, event_id); - - std::string_view order{}; - bool exists = msg2orderDb.get(txn, event_id, order); - if (exists) { - order2msgDb.del(txn, order); - msg2orderDb.del(txn, event_id); - } - } - } - lmdb::cursor_del(cursor); - } else { - if (obj.count("prev_batch") != 0) - passed_pagination_token = true; + try { + obj = json::parse(std::string_view(val.data(), val.size())); + } catch (std::exception &) { + // workaround bug in the initial db format, where we sometimes didn't store + // json... + obj = {{"event_id", std::string(val.data(), val.size())}}; + } + + if (passed_pagination_token) { + if (obj.count("event_id") != 0) { + std::string event_id = obj["event_id"].get(); + + if (!event_id.empty()) { + evToOrderDb.del(txn, event_id); + eventsDb.del(txn, event_id); + relationsDb.del(txn, event_id); + + std::string_view order{}; + bool exists = msg2orderDb.get(txn, event_id, order); + if (exists) { + order2msgDb.del(txn, order); + msg2orderDb.del(txn, event_id); + } } + } + lmdb::cursor_del(cursor); + } else { + if (obj.count("prev_batch") != 0) + passed_pagination_token = true; } + } - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - start = true; - while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { - start = false; - - std::string_view eventId; - bool innerStart = true; - bool found = false; - while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) { - innerStart = false; - - json obj; - try { - obj = json::parse(std::string_view(eventId.data(), eventId.size())); - } catch (std::exception &) { - obj = {{"event_id", std::string(eventId.data(), eventId.size())}}; - } + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + start = true; + while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; - if (obj["event_id"] == std::string(val.data(), val.size())) { - found = true; - break; - } - } + std::string_view eventId; + bool innerStart = true; + bool found = false; + while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) { + innerStart = false; + + json obj; + try { + obj = json::parse(std::string_view(eventId.data(), eventId.size())); + } catch (std::exception &) { + obj = {{"event_id", std::string(eventId.data(), eventId.size())}}; + } - if (!found) - break; + if (obj["event_id"] == std::string(val.data(), val.size())) { + found = true; + break; + } } - do { - lmdb::cursor_del(msgCursor); - } while (msgCursor.get(indexVal, val, MDB_PREV)); + if (!found) + break; + } - cursor.close(); - msgCursor.close(); - txn.commit(); + do { + lmdb::cursor_del(msgCursor); + } while (msgCursor.get(indexVal, val, MDB_PREV)); + + cursor.close(); + msgCursor.close(); + txn.commit(); } mtx::responses::Notifications Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id) { - auto db = getMentionsDb(txn, room_id); + auto db = getMentionsDb(txn, room_id); - if (db.size(txn) == 0) { - return mtx::responses::Notifications{}; - } + if (db.size(txn) == 0) { + return mtx::responses::Notifications{}; + } - mtx::responses::Notifications notif; - std::string_view event_id, msg; + mtx::responses::Notifications notif; + std::string_view event_id, msg; - auto cursor = lmdb::cursor::open(txn, db); + auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(event_id, msg, MDB_NEXT)) { - auto obj = json::parse(msg); + while (cursor.get(event_id, msg, MDB_NEXT)) { + auto obj = json::parse(msg); - if (obj.count("event") == 0) - continue; + if (obj.count("event") == 0) + continue; - mtx::responses::Notification notification; - mtx::responses::from_json(obj, notification); + mtx::responses::Notification notification; + mtx::responses::from_json(obj, notification); - notif.notifications.push_back(notification); - } - cursor.close(); + notif.notifications.push_back(notification); + } + cursor.close(); - std::reverse(notif.notifications.begin(), notif.notifications.end()); + std::reverse(notif.notifications.begin(), notif.notifications.end()); - return notif; + return notif; } //! Add all notifications containing a user mention to the db. void Cache::saveTimelineMentions(const mtx::responses::Notifications &res) { - QMap> notifsByRoom; + QMap> notifsByRoom; - // Sort into room-specific 'buckets' - for (const auto ¬if : res.notifications) { - json val = notif; - notifsByRoom[notif.room_id].push_back(notif); - } + // Sort into room-specific 'buckets' + for (const auto ¬if : res.notifications) { + json val = notif; + notifsByRoom[notif.room_id].push_back(notif); + } - auto txn = lmdb::txn::begin(env_); - // Insert the entire set of mentions for each room at a time. - QMap>::const_iterator it = - notifsByRoom.constBegin(); - auto end = notifsByRoom.constEnd(); - while (it != end) { - nhlog::db()->debug("Storing notifications for " + it.key()); - saveTimelineMentions(txn, it.key(), std::move(it.value())); - ++it; - } + auto txn = lmdb::txn::begin(env_); + // Insert the entire set of mentions for each room at a time. + QMap>::const_iterator it = + notifsByRoom.constBegin(); + auto end = notifsByRoom.constEnd(); + while (it != end) { + nhlog::db()->debug("Storing notifications for " + it.key()); + saveTimelineMentions(txn, it.key(), std::move(it.value())); + ++it; + } - txn.commit(); + txn.commit(); } void @@ -3321,138 +3241,138 @@ Cache::saveTimelineMentions(lmdb::txn &txn, const std::string &room_id, const QList &res) { - auto db = getMentionsDb(txn, room_id); + auto db = getMentionsDb(txn, room_id); - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - for (const auto ¬if : res) { - const auto event_id = mtx::accessors::event_id(notif.event); + for (const auto ¬if : res) { + const auto event_id = mtx::accessors::event_id(notif.event); - // double check that we have the correct room_id... - if (room_id.compare(notif.room_id) != 0) { - return; - } + // double check that we have the correct room_id... + if (room_id.compare(notif.room_id) != 0) { + return; + } - json obj = notif; + json obj = notif; - db.put(txn, event_id, obj.dump()); - } + db.put(txn, event_id, obj.dump()); + } } void Cache::markSentNotification(const std::string &event_id) { - auto txn = lmdb::txn::begin(env_); - notificationsDb_.put(txn, event_id, ""); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + notificationsDb_.put(txn, event_id, ""); + txn.commit(); } void Cache::removeReadNotification(const std::string &event_id) { - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - notificationsDb_.del(txn, event_id); + notificationsDb_.del(txn, event_id); - txn.commit(); + txn.commit(); } bool Cache::isNotificationSent(const std::string &event_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view value; - bool res = notificationsDb_.get(txn, event_id, value); + std::string_view value; + bool res = notificationsDb_.get(txn, event_id, value); - return res; + return res; } std::vector Cache::getRoomIds(lmdb::txn &txn) { - auto cursor = lmdb::cursor::open(txn, roomsDb_); + auto cursor = lmdb::cursor::open(txn, roomsDb_); - std::vector rooms; + std::vector rooms; - std::string_view room_id, _unused; - while (cursor.get(room_id, _unused, MDB_NEXT)) - rooms.emplace_back(room_id); + std::string_view room_id, _unused; + while (cursor.get(room_id, _unused, MDB_NEXT)) + rooms.emplace_back(room_id); - cursor.close(); + cursor.close(); - return rooms; + return rooms; } void Cache::deleteOldMessages() { - std::string_view indexVal, val; + std::string_view indexVal, val; - auto txn = lmdb::txn::begin(env_); - auto room_ids = getRoomIds(txn); + auto txn = lmdb::txn::begin(env_); + auto room_ids = getRoomIds(txn); - for (const auto &room_id : room_ids) { - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto o2m = getOrderToMessageDb(txn, room_id); - auto m2o = getMessageToOrderDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); + for (const auto &room_id : room_ids) { + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto o2m = getOrderToMessageDb(txn, room_id); + auto m2o = getMessageToOrderDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, orderDb); - uint64_t first, last; - if (cursor.get(indexVal, val, MDB_LAST)) { - last = lmdb::from_sv(indexVal); - } else { - continue; - } - if (cursor.get(indexVal, val, MDB_FIRST)) { - first = lmdb::from_sv(indexVal); - } else { - continue; - } + uint64_t first, last; + if (cursor.get(indexVal, val, MDB_LAST)) { + last = lmdb::from_sv(indexVal); + } else { + continue; + } + if (cursor.get(indexVal, val, MDB_FIRST)) { + first = lmdb::from_sv(indexVal); + } else { + continue; + } - size_t message_count = static_cast(last - first); - if (message_count < MAX_RESTORED_MESSAGES) - continue; - - bool start = true; - while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) && - message_count-- > MAX_RESTORED_MESSAGES) { - start = false; - auto obj = json::parse(std::string_view(val.data(), val.size())); - - if (obj.count("event_id") != 0) { - std::string event_id = obj["event_id"].get(); - evToOrderDb.del(txn, event_id); - eventsDb.del(txn, event_id); - - relationsDb.del(txn, event_id); - - std::string_view order{}; - bool exists = m2o.get(txn, event_id, order); - if (exists) { - o2m.del(txn, order); - m2o.del(txn, event_id); - } - } - cursor.del(); + size_t message_count = static_cast(last - first); + if (message_count < MAX_RESTORED_MESSAGES) + continue; + + bool start = true; + while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) && + message_count-- > MAX_RESTORED_MESSAGES) { + start = false; + auto obj = json::parse(std::string_view(val.data(), val.size())); + + if (obj.count("event_id") != 0) { + std::string event_id = obj["event_id"].get(); + evToOrderDb.del(txn, event_id); + eventsDb.del(txn, event_id); + + relationsDb.del(txn, event_id); + + std::string_view order{}; + bool exists = m2o.get(txn, event_id, order); + if (exists) { + o2m.del(txn, order); + m2o.del(txn, event_id); } - cursor.close(); + } + cursor.del(); } - txn.commit(); + cursor.close(); + } + txn.commit(); } void Cache::deleteOldData() noexcept { - try { - deleteOldMessages(); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to delete old messages: {}", e.what()); - } + try { + deleteOldMessages(); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to delete old messages: {}", e.what()); + } } void @@ -3460,241 +3380,232 @@ Cache::updateSpaces(lmdb::txn &txn, const std::set &spaces_with_updates, std::set rooms_with_updates) { - if (spaces_with_updates.empty() && rooms_with_updates.empty()) - return; + if (spaces_with_updates.empty() && rooms_with_updates.empty()) + return; - for (const auto &space : spaces_with_updates) { - // delete old entries - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view sp = space, space_child = ""; - - if (cursor.get(sp, space_child, MDB_SET)) { - while (cursor.get( - sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - spacesParentsDb_.del(txn, space_child, space); - } - } - cursor.close(); - spacesChildrenDb_.del(txn, space); + for (const auto &space : spaces_with_updates) { + // delete old entries + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view sp = space, space_child = ""; + + if (cursor.get(sp, space_child, MDB_SET)) { + while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + spacesParentsDb_.del(txn, space_child, space); } + } + cursor.close(); + spacesChildrenDb_.del(txn, space); + } - for (const auto &event : - getStateEventsWithType(txn, space)) { - if (event.content.via.has_value() && event.state_key.size() > 3 && - event.state_key.at(0) == '!') { - spacesChildrenDb_.put(txn, space, event.state_key); - spacesParentsDb_.put(txn, event.state_key, space); - } - } + for (const auto &event : + getStateEventsWithType(txn, space)) { + if (event.content.via.has_value() && event.state_key.size() > 3 && + event.state_key.at(0) == '!') { + spacesChildrenDb_.put(txn, space, event.state_key); + spacesParentsDb_.put(txn, event.state_key, space); + } } + } - const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels); + const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels); - for (const auto &room : rooms_with_updates) { - for (const auto &event : - getStateEventsWithType(txn, room)) { - if (event.content.via.has_value() && event.state_key.size() > 3 && - event.state_key.at(0) == '!') { - const std::string &space = event.state_key; + for (const auto &room : rooms_with_updates) { + for (const auto &event : + getStateEventsWithType(txn, room)) { + if (event.content.via.has_value() && event.state_key.size() > 3 && + event.state_key.at(0) == '!') { + const std::string &space = event.state_key; - auto pls = - getStateEvent(txn, space); + auto pls = getStateEvent(txn, space); - if (!pls) - continue; + if (!pls) + continue; - if (pls->content.user_level(event.sender) >= - pls->content.state_level(space_event_type)) { - spacesChildrenDb_.put(txn, space, room); - spacesParentsDb_.put(txn, room, space); - } - } + if (pls->content.user_level(event.sender) >= + pls->content.state_level(space_event_type)) { + spacesChildrenDb_.put(txn, space, room); + spacesParentsDb_.put(txn, room, space); } + } } + } } QMap> Cache::spaces() { - auto txn = ro_txn(env_); - - QMap> ret; - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view space_id, space_child; - while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) { - first = false; - - if (!space_child.empty()) { - std::string_view room_data; - if (roomsDb_.get(txn, space_id, room_data)) { - RoomInfo tmp = json::parse(std::move(room_data)); - ret.insert( - QString::fromUtf8(space_id.data(), space_id.size()), tmp); - } else { - ret.insert( - QString::fromUtf8(space_id.data(), space_id.size()), - std::nullopt); - } - } + auto txn = ro_txn(env_); + + QMap> ret; + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view space_id, space_child; + while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) { + first = false; + + if (!space_child.empty()) { + std::string_view room_data; + if (roomsDb_.get(txn, space_id, room_data)) { + RoomInfo tmp = json::parse(std::move(room_data)); + ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), tmp); + } else { + ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), std::nullopt); } - cursor.close(); + } } + cursor.close(); + } - return ret; + return ret; } std::vector Cache::getParentRoomIds(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector roomids; - { - auto cursor = lmdb::cursor::open(txn, spacesParentsDb_); - bool first = true; - std::string_view sp = room_id, space_parent; - if (cursor.get(sp, space_parent, MDB_SET)) { - while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - - if (!space_parent.empty()) - roomids.emplace_back(space_parent); - } - } - cursor.close(); + std::vector roomids; + { + auto cursor = lmdb::cursor::open(txn, spacesParentsDb_); + bool first = true; + std::string_view sp = room_id, space_parent; + if (cursor.get(sp, space_parent, MDB_SET)) { + while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + + if (!space_parent.empty()) + roomids.emplace_back(space_parent); + } } + cursor.close(); + } - return roomids; + return roomids; } std::vector Cache::getChildRoomIds(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector roomids; - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view sp = room_id, space_child; - if (cursor.get(sp, space_child, MDB_SET)) { - while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - - if (!space_child.empty()) - roomids.emplace_back(space_child); - } - } - cursor.close(); + std::vector roomids; + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view sp = room_id, space_child; + if (cursor.get(sp, space_child, MDB_SET)) { + while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + + if (!space_child.empty()) + roomids.emplace_back(space_child); + } } + cursor.close(); + } - return roomids; + return roomids; } std::vector Cache::getImagePacks(const std::string &room_id, std::optional stickers) { - auto txn = ro_txn(env_); - std::vector infos; - - auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, - const std::string &source_room, - const std::string &state_key) { - if (!pack.pack || !stickers.has_value() || - (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { - ImagePackInfo info; - info.source_room = source_room; - info.state_key = state_key; - info.pack.pack = pack.pack; - - for (const auto &img : pack.images) { - if (stickers.has_value() && img.second.overrides_usage() && - (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) - continue; - - info.pack.images.insert(img); - } - - if (!info.pack.images.empty()) - infos.push_back(std::move(info)); - } - }; - - // packs from account data - if (auto accountpack = - getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) { - auto tmp = - std::get_if>( - &*accountpack); - if (tmp) - addPack(tmp->content, "", ""); - } - - // packs from rooms, that were enabled globally - if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) { - auto tmp = - std::get_if>( - &*roomPacks); - if (tmp) { - for (const auto &[room_id2, state_to_d] : tmp->content.rooms) { - // don't add stickers from this room twice - if (room_id2 == room_id) - continue; - - for (const auto &[state_id, d] : state_to_d) { - (void)d; - if (auto pack = - getStateEvent( - txn, room_id2, state_id)) - addPack(pack->content, room_id2, state_id); - } - } + auto txn = ro_txn(env_); + std::vector infos; + + auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, + const std::string &source_room, + const std::string &state_key) { + if (!pack.pack || !stickers.has_value() || + (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { + ImagePackInfo info; + info.source_room = source_room; + info.state_key = state_key; + info.pack.pack = pack.pack; + + for (const auto &img : pack.images) { + if (stickers.has_value() && img.second.overrides_usage() && + (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) + continue; + + info.pack.images.insert(img); + } + + if (!info.pack.images.empty()) + infos.push_back(std::move(info)); + } + }; + + // packs from account data + if (auto accountpack = + getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) { + auto tmp = + std::get_if>(&*accountpack); + if (tmp) + addPack(tmp->content, "", ""); + } + + // packs from rooms, that were enabled globally + if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) { + auto tmp = std::get_if>( + &*roomPacks); + if (tmp) { + for (const auto &[room_id2, state_to_d] : tmp->content.rooms) { + // don't add stickers from this room twice + if (room_id2 == room_id) + continue; + + for (const auto &[state_id, d] : state_to_d) { + (void)d; + if (auto pack = + getStateEvent(txn, room_id2, state_id)) + addPack(pack->content, room_id2, state_id); } + } } + } - // packs from current room - if (auto pack = getStateEvent(txn, room_id)) { - addPack(pack->content, room_id, ""); - } - for (const auto &pack : - getStateEventsWithType(txn, room_id)) { - addPack(pack.content, room_id, pack.state_key); - } + // packs from current room + if (auto pack = getStateEvent(txn, room_id)) { + addPack(pack->content, room_id, ""); + } + for (const auto &pack : getStateEventsWithType(txn, room_id)) { + addPack(pack.content, room_id, pack.state_key); + } - return infos; + return infos; } std::optional Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) { - auto txn = ro_txn(env_); - return getAccountData(txn, type, room_id); + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); } std::optional Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { - try { - auto db = getAccountDataDb(txn, room_id); - - std::string_view data; - if (db.get(txn, to_string(type), data)) { - mtx::responses::utils::RoomAccountDataEvents events; - json j = json::array({ - json::parse(data), - }); - mtx::responses::utils::parse_room_account_data_events(j, events); - if (events.size() == 1) - return events.front(); - } - } catch (...) { + try { + auto db = getAccountDataDb(txn, room_id); + + std::string_view data; + if (db.get(txn, to_string(type), data)) { + mtx::responses::utils::RoomAccountDataEvents events; + json j = json::array({ + json::parse(data), + }); + mtx::responses::utils::parse_room_account_data_events(j, events); + if (events.size() == 1) + return events.front(); } - return std::nullopt; + } catch (...) { + } + return std::nullopt; } bool @@ -3702,465 +3613,436 @@ Cache::hasEnoughPowerLevel(const std::vector &eventTypes const std::string &room_id, const std::string &user_id) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - auto txn = lmdb::txn::begin(env_); - auto db = getStatesDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto db = getStatesDb(txn, room_id); - int64_t min_event_level = std::numeric_limits::max(); - int64_t user_level = std::numeric_limits::min(); + int64_t min_event_level = std::numeric_limits::max(); + int64_t user_level = std::numeric_limits::min(); - std::string_view event; - bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); + std::string_view event; + bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); - if (res) { - try { - StateEvent msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent msg = json::parse(std::string_view(event.data(), event.size())); - user_level = msg.content.user_level(user_id); + user_level = msg.content.user_level(user_id); - for (const auto &ty : eventTypes) - min_event_level = - std::min(min_event_level, msg.content.state_level(to_string(ty))); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.power_levels event: {}", - e.what()); - } + for (const auto &ty : eventTypes) + min_event_level = std::min(min_event_level, msg.content.state_level(to_string(ty))); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); } + } - txn.commit(); + txn.commit(); - return user_level >= min_event_level; + return user_level >= min_event_level; } std::vector Cache::roomMembers(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector members; - std::string_view user_id, unused; + std::vector members; + std::string_view user_id, unused; - auto db = getMembersDb(txn, room_id); + auto db = getMembersDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) - members.emplace_back(user_id); - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) + members.emplace_back(user_id); + cursor.close(); - return members; + return members; } crypto::Trust Cache::roomVerificationStatus(const std::string &room_id) { - crypto::Trust trust = crypto::Verified; + crypto::Trust trust = crypto::Verified; - try { - auto txn = lmdb::txn::begin(env_); - - auto db = getMembersDb(txn, room_id); - auto keysDb = getUserKeysDb(txn); - std::vector keysToRequest; - - std::string_view user_id, unused; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) { - auto verif = verificationStatus_(std::string(user_id), txn); - if (verif.unverified_device_count) { - trust = crypto::Unverified; - if (verif.verified_devices.empty() && verif.no_keys) { - // we probably don't have the keys yet, so query them - keysToRequest.push_back(std::string(user_id)); - } - } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified) - trust = crypto::TOFU; - } + try { + auto txn = lmdb::txn::begin(env_); - if (!keysToRequest.empty()) - markUserKeysOutOfDate(txn, keysDb, keysToRequest, ""); + auto db = getMembersDb(txn, room_id); + auto keysDb = getUserKeysDb(txn); + std::vector keysToRequest; - } catch (std::exception &e) { - nhlog::db()->error( - "Failed to calculate verification status for {}: {}", room_id, e.what()); + std::string_view user_id, unused; + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) { + auto verif = verificationStatus_(std::string(user_id), txn); + if (verif.unverified_device_count) { trust = crypto::Unverified; + if (verif.verified_devices.empty() && verif.no_keys) { + // we probably don't have the keys yet, so query them + keysToRequest.push_back(std::string(user_id)); + } + } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified) + trust = crypto::TOFU; } - return trust; + if (!keysToRequest.empty()) + markUserKeysOutOfDate(txn, keysDb, keysToRequest, ""); + + } catch (std::exception &e) { + nhlog::db()->error("Failed to calculate verification status for {}: {}", room_id, e.what()); + trust = crypto::Unverified; + } + + return trust; } std::map> Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) { - std::string_view keys; + std::string_view keys; - try { - auto txn = ro_txn(env_); - std::map> members; - - auto db = getMembersDb(txn, room_id); - auto keysDb = getUserKeysDb(txn); - - std::string_view user_id, unused; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) { - auto res = keysDb.get(txn, user_id, keys); - - if (res) { - auto k = json::parse(keys).get(); - if (verified_only) { - auto verif = verificationStatus_(std::string(user_id), txn); - - if (verif.user_verified == crypto::Trust::Verified || - !verif.verified_devices.empty()) { - auto keyCopy = k; - keyCopy.device_keys.clear(); - - std::copy_if( - k.device_keys.begin(), - k.device_keys.end(), - std::inserter(keyCopy.device_keys, - keyCopy.device_keys.end()), - [&verif](const auto &key) { - auto curve25519 = key.second.keys.find( - "curve25519:" + key.first); - if (curve25519 == key.second.keys.end()) - return false; - if (auto t = - verif.verified_device_keys.find( - curve25519->second); - t == - verif.verified_device_keys.end() || - t->second != crypto::Trust::Verified) - return false; - - return key.first == - key.second.device_id && - std::find( - verif.verified_devices.begin(), - verif.verified_devices.end(), - key.first) != - verif.verified_devices.end(); - }); - - if (!keyCopy.device_keys.empty()) - members[std::string(user_id)] = - std::move(keyCopy); - } - } else { - members[std::string(user_id)] = std::move(k); - } - } else { - if (!verified_only) - members[std::string(user_id)] = {}; - } - } - cursor.close(); + try { + auto txn = ro_txn(env_); + std::map> members; - return members; - } catch (std::exception &e) { - nhlog::db()->debug("Error retrieving members: {}", e.what()); - return {}; + auto db = getMembersDb(txn, room_id); + auto keysDb = getUserKeysDb(txn); + + std::string_view user_id, unused; + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) { + auto res = keysDb.get(txn, user_id, keys); + + if (res) { + auto k = json::parse(keys).get(); + if (verified_only) { + auto verif = verificationStatus_(std::string(user_id), txn); + + if (verif.user_verified == crypto::Trust::Verified || + !verif.verified_devices.empty()) { + auto keyCopy = k; + keyCopy.device_keys.clear(); + + std::copy_if( + k.device_keys.begin(), + k.device_keys.end(), + std::inserter(keyCopy.device_keys, keyCopy.device_keys.end()), + [&verif](const auto &key) { + auto curve25519 = key.second.keys.find("curve25519:" + key.first); + if (curve25519 == key.second.keys.end()) + return false; + if (auto t = verif.verified_device_keys.find(curve25519->second); + t == verif.verified_device_keys.end() || + t->second != crypto::Trust::Verified) + return false; + + return key.first == key.second.device_id && + std::find(verif.verified_devices.begin(), + verif.verified_devices.end(), + key.first) != verif.verified_devices.end(); + }); + + if (!keyCopy.device_keys.empty()) + members[std::string(user_id)] = std::move(keyCopy); + } + } else { + members[std::string(user_id)] = std::move(k); + } + } else { + if (!verified_only) + members[std::string(user_id)] = {}; + } } + cursor.close(); + + return members; + } catch (std::exception &e) { + nhlog::db()->debug("Error retrieving members: {}", e.what()); + return {}; + } } QString Cache::displayName(const QString &room_id, const QString &user_id) { - if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); - info && !info->name.empty()) - return QString::fromStdString(info->name); + if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); + info && !info->name.empty()) + return QString::fromStdString(info->name); - return user_id; + return user_id; } std::string Cache::displayName(const std::string &room_id, const std::string &user_id) { - if (auto info = getMember(room_id, user_id); info && !info->name.empty()) - return info->name; + if (auto info = getMember(room_id, user_id); info && !info->name.empty()) + return info->name; - return user_id; + return user_id; } QString Cache::avatarUrl(const QString &room_id, const QString &user_id) { - if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); - info && !info->avatar_url.empty()) - return QString::fromStdString(info->avatar_url); + if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); + info && !info->avatar_url.empty()) + return QString::fromStdString(info->avatar_url); - return ""; + return ""; } mtx::presence::PresenceState Cache::presenceState(const std::string &user_id) { - if (user_id.empty()) - return {}; + if (user_id.empty()) + return {}; - std::string_view presenceVal; + std::string_view presenceVal; - auto txn = lmdb::txn::begin(env_); - auto db = getPresenceDb(txn); - auto res = db.get(txn, user_id, presenceVal); + auto txn = lmdb::txn::begin(env_); + auto db = getPresenceDb(txn); + auto res = db.get(txn, user_id, presenceVal); - mtx::presence::PresenceState state = mtx::presence::offline; + mtx::presence::PresenceState state = mtx::presence::offline; - if (res) { - mtx::events::presence::Presence presence = - json::parse(std::string_view(presenceVal.data(), presenceVal.size())); - state = presence.presence; - } + if (res) { + mtx::events::presence::Presence presence = + json::parse(std::string_view(presenceVal.data(), presenceVal.size())); + state = presence.presence; + } - txn.commit(); + txn.commit(); - return state; + return state; } std::string Cache::statusMessage(const std::string &user_id) { - if (user_id.empty()) - return {}; + if (user_id.empty()) + return {}; - std::string_view presenceVal; + std::string_view presenceVal; - auto txn = lmdb::txn::begin(env_); - auto db = getPresenceDb(txn); - auto res = db.get(txn, user_id, presenceVal); + auto txn = lmdb::txn::begin(env_); + auto db = getPresenceDb(txn); + auto res = db.get(txn, user_id, presenceVal); - std::string status_msg; + std::string status_msg; - if (res) { - mtx::events::presence::Presence presence = json::parse(presenceVal); - status_msg = presence.status_msg; - } + if (res) { + mtx::events::presence::Presence presence = json::parse(presenceVal); + status_msg = presence.status_msg; + } - txn.commit(); + txn.commit(); - return status_msg; + return status_msg; } void to_json(json &j, const UserKeyCache &info) { - j["device_keys"] = info.device_keys; - j["seen_device_keys"] = info.seen_device_keys; - j["seen_device_ids"] = info.seen_device_ids; - j["master_keys"] = info.master_keys; - j["master_key_changed"] = info.master_key_changed; - j["user_signing_keys"] = info.user_signing_keys; - j["self_signing_keys"] = info.self_signing_keys; - j["updated_at"] = info.updated_at; - j["last_changed"] = info.last_changed; + j["device_keys"] = info.device_keys; + j["seen_device_keys"] = info.seen_device_keys; + j["seen_device_ids"] = info.seen_device_ids; + j["master_keys"] = info.master_keys; + j["master_key_changed"] = info.master_key_changed; + j["user_signing_keys"] = info.user_signing_keys; + j["self_signing_keys"] = info.self_signing_keys; + j["updated_at"] = info.updated_at; + j["last_changed"] = info.last_changed; } void from_json(const json &j, UserKeyCache &info) { - info.device_keys = j.value("device_keys", std::map{}); - info.seen_device_keys = j.value("seen_device_keys", std::set{}); - info.seen_device_ids = j.value("seen_device_ids", std::set{}); - info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); - info.master_key_changed = j.value("master_key_changed", false); - info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); - info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); - info.updated_at = j.value("updated_at", ""); - info.last_changed = j.value("last_changed", ""); + info.device_keys = j.value("device_keys", std::map{}); + info.seen_device_keys = j.value("seen_device_keys", std::set{}); + info.seen_device_ids = j.value("seen_device_ids", std::set{}); + info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); + info.master_key_changed = j.value("master_key_changed", false); + info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.updated_at = j.value("updated_at", ""); + info.last_changed = j.value("last_changed", ""); } std::optional Cache::userKeys(const std::string &user_id) { - auto txn = ro_txn(env_); - return userKeys_(user_id, txn); + auto txn = ro_txn(env_); + return userKeys_(user_id, txn); } std::optional Cache::userKeys_(const std::string &user_id, lmdb::txn &txn) { - std::string_view keys; + std::string_view keys; - try { - auto db = getUserKeysDb(txn); - auto res = db.get(txn, user_id, keys); + try { + auto db = getUserKeysDb(txn); + auto res = db.get(txn, user_id, keys); - if (res) { - return json::parse(keys).get(); - } else { - return {}; - } - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what()); - return {}; + if (res) { + return json::parse(keys).get(); + } else { + return {}; } + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what()); + return {}; + } } void Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - auto txn = lmdb::txn::begin(env_); - auto db = getUserKeysDb(txn); + auto txn = lmdb::txn::begin(env_); + auto db = getUserKeysDb(txn); - std::map updates; - - for (const auto &[user, keys] : keyQuery.device_keys) - updates[user].device_keys = keys; - for (const auto &[user, keys] : keyQuery.master_keys) - updates[user].master_keys = keys; - for (const auto &[user, keys] : keyQuery.user_signing_keys) - updates[user].user_signing_keys = keys; - for (const auto &[user, keys] : keyQuery.self_signing_keys) - updates[user].self_signing_keys = keys; - - for (auto &[user, update] : updates) { - nhlog::db()->debug("Updated user keys: {}", user); - - auto updateToWrite = update; - - std::string_view oldKeys; - auto res = db.get(txn, user, oldKeys); - - if (res) { - updateToWrite = json::parse(oldKeys).get(); - auto last_changed = updateToWrite.last_changed; - // skip if we are tracking this and expect it to be up to date with the last - // sync token - if (!last_changed.empty() && last_changed != sync_token) { - nhlog::db()->debug("Not storing update for user {}, because " - "last_changed {}, but we fetched update for {}", - user, - last_changed, - sync_token); - continue; - } + std::map updates; + + for (const auto &[user, keys] : keyQuery.device_keys) + updates[user].device_keys = keys; + for (const auto &[user, keys] : keyQuery.master_keys) + updates[user].master_keys = keys; + for (const auto &[user, keys] : keyQuery.user_signing_keys) + updates[user].user_signing_keys = keys; + for (const auto &[user, keys] : keyQuery.self_signing_keys) + updates[user].self_signing_keys = keys; - if (!updateToWrite.master_keys.keys.empty() && - update.master_keys.keys != updateToWrite.master_keys.keys) { - nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}", - user, - updateToWrite.master_keys.keys.size(), - update.master_keys.keys.size()); - updateToWrite.master_key_changed = true; + for (auto &[user, update] : updates) { + nhlog::db()->debug("Updated user keys: {}", user); + + auto updateToWrite = update; + + std::string_view oldKeys; + auto res = db.get(txn, user, oldKeys); + + if (res) { + updateToWrite = json::parse(oldKeys).get(); + auto last_changed = updateToWrite.last_changed; + // skip if we are tracking this and expect it to be up to date with the last + // sync token + if (!last_changed.empty() && last_changed != sync_token) { + nhlog::db()->debug("Not storing update for user {}, because " + "last_changed {}, but we fetched update for {}", + user, + last_changed, + sync_token); + continue; + } + + if (!updateToWrite.master_keys.keys.empty() && + update.master_keys.keys != updateToWrite.master_keys.keys) { + nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}", + user, + updateToWrite.master_keys.keys.size(), + update.master_keys.keys.size()); + updateToWrite.master_key_changed = true; + } + + updateToWrite.master_keys = update.master_keys; + updateToWrite.self_signing_keys = update.self_signing_keys; + updateToWrite.user_signing_keys = update.user_signing_keys; + + auto oldDeviceKeys = std::move(updateToWrite.device_keys); + updateToWrite.device_keys.clear(); + + // Don't insert keys, which we have seen once already + for (const auto &[device_id, device_keys] : update.device_keys) { + if (oldDeviceKeys.count(device_id) && + oldDeviceKeys.at(device_id).keys == device_keys.keys) { + // this is safe, since the keys are the same + updateToWrite.device_keys[device_id] = device_keys; + } else { + bool keyReused = false; + for (const auto &[key_id, key] : device_keys.keys) { + (void)key_id; + if (updateToWrite.seen_device_keys.count(key)) { + nhlog::crypto()->warn( + "Key '{}' reused by ({}: {})", key, user, device_id); + keyReused = true; + break; + } + if (updateToWrite.seen_device_ids.count(device_id)) { + nhlog::crypto()->warn("device_id '{}' reused by ({})", device_id, user); + keyReused = true; + break; + } + } + + if (!keyReused && !oldDeviceKeys.count(device_id)) { + // ensure the key has a valid signature from itself + std::string device_signing_key = "ed25519:" + device_keys.device_id; + if (device_id != device_keys.device_id) { + nhlog::crypto()->warn("device {}:{} has a different device id " + "in the body: {}", + user, + device_id, + device_keys.device_id); + continue; + } + if (!device_keys.signatures.count(user) || + !device_keys.signatures.at(user).count(device_signing_key)) { + nhlog::crypto()->warn("device {}:{} has no signature", user, device_id); + continue; } - updateToWrite.master_keys = update.master_keys; - updateToWrite.self_signing_keys = update.self_signing_keys; - updateToWrite.user_signing_keys = update.user_signing_keys; - - auto oldDeviceKeys = std::move(updateToWrite.device_keys); - updateToWrite.device_keys.clear(); - - // Don't insert keys, which we have seen once already - for (const auto &[device_id, device_keys] : update.device_keys) { - if (oldDeviceKeys.count(device_id) && - oldDeviceKeys.at(device_id).keys == device_keys.keys) { - // this is safe, since the keys are the same - updateToWrite.device_keys[device_id] = device_keys; - } else { - bool keyReused = false; - for (const auto &[key_id, key] : device_keys.keys) { - (void)key_id; - if (updateToWrite.seen_device_keys.count(key)) { - nhlog::crypto()->warn( - "Key '{}' reused by ({}: {})", - key, - user, - device_id); - keyReused = true; - break; - } - if (updateToWrite.seen_device_ids.count( - device_id)) { - nhlog::crypto()->warn( - "device_id '{}' reused by ({})", - device_id, - user); - keyReused = true; - break; - } - } - - if (!keyReused && !oldDeviceKeys.count(device_id)) { - // ensure the key has a valid signature from itself - std::string device_signing_key = - "ed25519:" + device_keys.device_id; - if (device_id != device_keys.device_id) { - nhlog::crypto()->warn( - "device {}:{} has a different device id " - "in the body: {}", - user, - device_id, - device_keys.device_id); - continue; - } - if (!device_keys.signatures.count(user) || - !device_keys.signatures.at(user).count( - device_signing_key)) { - nhlog::crypto()->warn( - "device {}:{} has no signature", - user, - device_id); - continue; - } - - if (!mtx::crypto::ed25519_verify_signature( - device_keys.keys.at(device_signing_key), - json(device_keys), - device_keys.signatures.at(user).at( - device_signing_key))) { - nhlog::crypto()->warn( - "device {}:{} has an invalid signature", - user, - device_id); - continue; - } - - updateToWrite.device_keys[device_id] = device_keys; - } - } - - for (const auto &[key_id, key] : device_keys.keys) { - (void)key_id; - updateToWrite.seen_device_keys.insert(key); - } - updateToWrite.seen_device_ids.insert(device_id); + if (!mtx::crypto::ed25519_verify_signature( + device_keys.keys.at(device_signing_key), + json(device_keys), + device_keys.signatures.at(user).at(device_signing_key))) { + nhlog::crypto()->warn( + "device {}:{} has an invalid signature", user, device_id); + continue; } + + updateToWrite.device_keys[device_id] = device_keys; + } } - updateToWrite.updated_at = sync_token; - db.put(txn, user, json(updateToWrite).dump()); + + for (const auto &[key_id, key] : device_keys.keys) { + (void)key_id; + updateToWrite.seen_device_keys.insert(key); + } + updateToWrite.seen_device_ids.insert(device_id); + } } + updateToWrite.updated_at = sync_token; + db.put(txn, user, json(updateToWrite).dump()); + } - txn.commit(); + txn.commit(); - std::map tmp; - const auto local_user = utils::localUser().toStdString(); + std::map tmp; + const auto local_user = utils::localUser().toStdString(); - { - std::unique_lock lock(verification_storage.verification_storage_mtx); - for (auto &[user_id, update] : updates) { - (void)update; - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + { + std::unique_lock lock(verification_storage.verification_storage_mtx); + for (auto &[user_id, update] : updates) { + (void)update; + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } } + } - for (auto &[user_id, update] : updates) { - (void)update; - if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } - } - emit verificationStatusChanged(user_id); + for (auto &[user_id, update] : updates) { + (void)update; + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); + } } + emit verificationStatusChanged(user_id); + } } void @@ -4169,780 +4051,772 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, const std::vector &user_ids, const std::string &sync_token) { - mtx::requests::QueryKeys query; - query.token = sync_token; + mtx::requests::QueryKeys query; + query.token = sync_token; - for (const auto &user : user_ids) { - nhlog::db()->debug("Marking user keys out of date: {}", user); - - std::string_view oldKeys; - - UserKeyCache cacheEntry; - auto res = db.get(txn, user, oldKeys); - if (res) { - cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size())) - .get(); - } - cacheEntry.last_changed = sync_token; + for (const auto &user : user_ids) { + nhlog::db()->debug("Marking user keys out of date: {}", user); - db.put(txn, user, json(cacheEntry).dump()); + std::string_view oldKeys; - query.device_keys[user] = {}; + UserKeyCache cacheEntry; + auto res = db.get(txn, user, oldKeys); + if (res) { + cacheEntry = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get(); } + cacheEntry.last_changed = sync_token; + + db.put(txn, user, json(cacheEntry).dump()); - if (!query.device_keys.empty()) - http::client()->query_keys(query, - [this, sync_token](const mtx::responses::QueryKeys &keys, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to query device keys: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - return; - } + query.device_keys[user] = {}; + } - emit userKeysUpdate(sync_token, keys); - }); + if (!query.device_keys.empty()) + http::client()->query_keys( + query, + [this, sync_token](const mtx::responses::QueryKeys &keys, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + emit userKeysUpdate(sync_token, keys); + }); } void Cache::query_keys(const std::string &user_id, std::function cb) { - mtx::requests::QueryKeys req; - std::string last_changed; - { - auto txn = ro_txn(env_); - auto cache_ = userKeys_(user_id, txn); - - if (cache_.has_value()) { - if (cache_->updated_at == cache_->last_changed) { - cb(cache_.value(), {}); - return; - } else - nhlog::db()->info("Keys outdated for {}: {} vs {}", - user_id, - cache_->updated_at, - cache_->last_changed); - } else - nhlog::db()->info("No keys found for {}", user_id); - - req.device_keys[user_id] = {}; - - if (cache_) - last_changed = cache_->last_changed; - req.token = last_changed; - } - - // use context object so that we can disconnect again - QObject *context{new QObject(this)}; - QObject::connect( - this, - &Cache::verificationStatusChanged, - context, - [cb, user_id, context_ = context, this](std::string updated_user) mutable { - if (user_id == updated_user) { - context_->deleteLater(); - auto txn = ro_txn(env_); - auto keys = this->userKeys_(user_id, txn); - cb(keys.value_or(UserKeyCache{}), {}); - } - }, - Qt::QueuedConnection); + mtx::requests::QueryKeys req; + std::string last_changed; + { + auto txn = ro_txn(env_); + auto cache_ = userKeys_(user_id, txn); - http::client()->query_keys( - req, - [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - mtx::errors::to_string(err->matrix_error.errcode), - static_cast(err->status_code)); - cb({}, err); - return; - } - - emit userKeysUpdate(last_changed, res); - }); + if (cache_.has_value()) { + if (cache_->updated_at == cache_->last_changed) { + cb(cache_.value(), {}); + return; + } else + nhlog::db()->info("Keys outdated for {}: {} vs {}", + user_id, + cache_->updated_at, + cache_->last_changed); + } else + nhlog::db()->info("No keys found for {}", user_id); + + req.device_keys[user_id] = {}; + + if (cache_) + last_changed = cache_->last_changed; + req.token = last_changed; + } + + // use context object so that we can disconnect again + QObject *context{new QObject(this)}; + QObject::connect( + this, + &Cache::verificationStatusChanged, + context, + [cb, user_id, context_ = context, this](std::string updated_user) mutable { + if (user_id == updated_user) { + context_->deleteLater(); + auto txn = ro_txn(env_); + auto keys = this->userKeys_(user_id, txn); + cb(keys.value_or(UserKeyCache{}), {}); + } + }, + Qt::QueuedConnection); + + http::client()->query_keys( + req, + [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + cb({}, err); + return; + } + + emit userKeysUpdate(last_changed, res); + }); } void to_json(json &j, const VerificationCache &info) { - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, VerificationCache &info) { - info.device_verified = j.at("device_verified").get>(); - info.device_blocked = j.at("device_blocked").get>(); + info.device_verified = j.at("device_verified").get>(); + info.device_blocked = j.at("device_blocked").get>(); } void to_json(json &j, const OnlineBackupVersion &info) { - j["v"] = info.version; - j["a"] = info.algorithm; + j["v"] = info.version; + j["a"] = info.algorithm; } void from_json(const json &j, OnlineBackupVersion &info) { - info.version = j.at("v").get(); - info.algorithm = j.at("a").get(); + info.version = j.at("v").get(); + info.algorithm = j.at("a").get(); } std::optional Cache::verificationCache(const std::string &user_id, lmdb::txn &txn) { - std::string_view verifiedVal; + std::string_view verifiedVal; - auto db = getVerificationDb(txn); + auto db = getVerificationDb(txn); - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, verifiedVal); - if (res) { - verified_state = json::parse(verifiedVal); - return verified_state; - } else { - return {}; - } - } catch (std::exception &) { - return {}; + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, verifiedVal); + if (res) { + verified_state = json::parse(verifiedVal); + return verified_state; + } else { + return {}; } + } catch (std::exception &) { + return {}; + } } void Cache::markDeviceVerified(const std::string &user_id, const std::string &key) { - { - std::string_view val; - - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); - - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, val); - if (res) { - verified_state = json::parse(val); - } + { + std::string_view val; - for (const auto &device : verified_state.device_verified) - if (device == key) - return; + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); - verified_state.device_verified.insert(key); - db.put(txn, user_id, json(verified_state).dump()); - txn.commit(); - } catch (std::exception &) { - } + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, val); + if (res) { + verified_state = json::parse(val); + } + + for (const auto &device : verified_state.device_verified) + if (device == key) + return; + + verified_state.device_verified.insert(key); + db.put(txn, user_id, json(verified_state).dump()); + txn.commit(); + } catch (std::exception &) { } + } - const auto local_user = utils::localUser().toStdString(); - std::map tmp; - { - std::unique_lock lock(verification_storage.verification_storage_mtx); - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } + std::swap(tmp, verification_storage.status); } else { - emit verificationStatusChanged(user_id); + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); } + } else { + emit verificationStatusChanged(user_id); + } } void Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) { - std::string_view val; + std::string_view val; - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, val); - if (res) { - verified_state = json::parse(val); - } + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, val); + if (res) { + verified_state = json::parse(val); + } - verified_state.device_verified.erase(key); + verified_state.device_verified.erase(key); - db.put(txn, user_id, json(verified_state).dump()); - txn.commit(); - } catch (std::exception &) { - } + db.put(txn, user_id, json(verified_state).dump()); + txn.commit(); + } catch (std::exception &) { + } - const auto local_user = utils::localUser().toStdString(); - std::map tmp; - { - std::unique_lock lock(verification_storage.verification_storage_mtx); - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + const auto local_user = utils::localUser().toStdString(); + std::map tmp; + { + std::unique_lock lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } + std::swap(tmp, verification_storage.status); } else { - emit verificationStatusChanged(user_id); + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); } + } else { + emit verificationStatusChanged(user_id); + } } VerificationStatus Cache::verificationStatus(const std::string &user_id) { - auto txn = ro_txn(env_); - return verificationStatus_(user_id, txn); + auto txn = ro_txn(env_); + return verificationStatus_(user_id, txn); } VerificationStatus Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) { - std::unique_lock lock(verification_storage.verification_storage_mtx); - if (verification_storage.status.count(user_id)) - return verification_storage.status.at(user_id); + std::unique_lock lock(verification_storage.verification_storage_mtx); + if (verification_storage.status.count(user_id)) + return verification_storage.status.at(user_id); - VerificationStatus status; + VerificationStatus status; - // assume there is at least one unverified device until we have checked we have the device - // list for that user. - status.unverified_device_count = 1; - status.no_keys = true; + // assume there is at least one unverified device until we have checked we have the device + // list for that user. + status.unverified_device_count = 1; + status.no_keys = true; - if (auto verifCache = verificationCache(user_id, txn)) { - status.verified_devices = verifCache->device_verified; - } + if (auto verifCache = verificationCache(user_id, txn)) { + status.verified_devices = verifCache->device_verified; + } - const auto local_user = utils::localUser().toStdString(); + const auto local_user = utils::localUser().toStdString(); - crypto::Trust trustlevel = crypto::Trust::Unverified; - if (user_id == local_user) { - status.verified_devices.insert(http::client()->device_id()); - trustlevel = crypto::Trust::Verified; - } + crypto::Trust trustlevel = crypto::Trust::Unverified; + if (user_id == local_user) { + status.verified_devices.insert(http::client()->device_id()); + trustlevel = crypto::Trust::Verified; + } - auto verifyAtLeastOneSig = [](const auto &toVerif, - const std::map &keys, - const std::string &keyOwner) { - if (!toVerif.signatures.count(keyOwner)) - return false; + auto verifyAtLeastOneSig = [](const auto &toVerif, + const std::map &keys, + const std::string &keyOwner) { + if (!toVerif.signatures.count(keyOwner)) + return false; - for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { - if (!keys.count(key_id)) - continue; + for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { + if (!keys.count(key_id)) + continue; - if (mtx::crypto::ed25519_verify_signature( - keys.at(key_id), json(toVerif), signature)) - return true; - } - return false; - }; + if (mtx::crypto::ed25519_verify_signature(keys.at(key_id), json(toVerif), signature)) + return true; + } + return false; + }; + + auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) { + int currentVerifiedDevices = 0; + for (auto device_id : status.verified_devices) { + if (theirDeviceKeys.count(device_id)) + currentVerifiedDevices++; + } + status.unverified_device_count = + static_cast(theirDeviceKeys.size()) - currentVerifiedDevices; + }; + + try { + // for local user verify this device_key -> our master_key -> our self_signing_key + // -> our device_keys + // + // for other user verify this device_key -> our master_key -> our user_signing_key + // -> their master_key -> their self_signing_key -> their device_keys + // + // This means verifying the other user adds 2 extra steps,verifying our user_signing + // key and their master key + auto ourKeys = userKeys_(local_user, txn); + auto theirKeys = userKeys_(user_id, txn); + if (theirKeys) + status.no_keys = false; + + if (!ourKeys || !theirKeys) { + verification_storage.status[user_id] = status; + return status; + } + + // Update verified devices count to count without cross-signing + updateUnverifiedDevices(theirKeys->device_keys); - auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) { - int currentVerifiedDevices = 0; - for (auto device_id : status.verified_devices) { - if (theirDeviceKeys.count(device_id)) - currentVerifiedDevices++; - } - status.unverified_device_count = - static_cast(theirDeviceKeys.size()) - currentVerifiedDevices; - }; + { + auto &mk = ourKeys->master_keys; + std::string dev_id = "ed25519:" + http::client()->device_id(); + if (!mk.signatures.count(local_user) || !mk.signatures.at(local_user).count(dev_id) || + !mtx::crypto::ed25519_verify_signature(olm::client()->identity_keys().ed25519, + json(mk), + mk.signatures.at(local_user).at(dev_id))) { + nhlog::crypto()->debug("We have not verified our own master key"); + verification_storage.status[user_id] = status; + return status; + } + } - try { - // for local user verify this device_key -> our master_key -> our self_signing_key - // -> our device_keys - // - // for other user verify this device_key -> our master_key -> our user_signing_key - // -> their master_key -> their self_signing_key -> their device_keys - // - // This means verifying the other user adds 2 extra steps,verifying our user_signing - // key and their master key - auto ourKeys = userKeys_(local_user, txn); - auto theirKeys = userKeys_(user_id, txn); - if (theirKeys) - status.no_keys = false; - - if (!ourKeys || !theirKeys) { - verification_storage.status[user_id] = status; - return status; - } + auto master_keys = ourKeys->master_keys.keys; - // Update verified devices count to count without cross-signing - updateUnverifiedDevices(theirKeys->device_keys); - - { - auto &mk = ourKeys->master_keys; - std::string dev_id = "ed25519:" + http::client()->device_id(); - if (!mk.signatures.count(local_user) || - !mk.signatures.at(local_user).count(dev_id) || - !mtx::crypto::ed25519_verify_signature( - olm::client()->identity_keys().ed25519, - json(mk), - mk.signatures.at(local_user).at(dev_id))) { - nhlog::crypto()->debug("We have not verified our own master key"); - verification_storage.status[user_id] = status; - return status; - } - } + if (user_id != local_user) { + bool theirMasterKeyVerified = + verifyAtLeastOneSig(ourKeys->user_signing_keys, master_keys, local_user) && + verifyAtLeastOneSig( + theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user); - auto master_keys = ourKeys->master_keys.keys; - - if (user_id != local_user) { - bool theirMasterKeyVerified = - verifyAtLeastOneSig( - ourKeys->user_signing_keys, master_keys, local_user) && - verifyAtLeastOneSig( - theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user); - - if (theirMasterKeyVerified) - trustlevel = crypto::Trust::Verified; - else if (!theirKeys->master_key_changed) - trustlevel = crypto::Trust::TOFU; - else { - verification_storage.status[user_id] = status; - return status; - } + if (theirMasterKeyVerified) + trustlevel = crypto::Trust::Verified; + else if (!theirKeys->master_key_changed) + trustlevel = crypto::Trust::TOFU; + else { + verification_storage.status[user_id] = status; + return status; + } - master_keys = theirKeys->master_keys.keys; - } + master_keys = theirKeys->master_keys.keys; + } - status.user_verified = trustlevel; + status.user_verified = trustlevel; - verification_storage.status[user_id] = status; - if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) - return status; - - for (const auto &[device, device_key] : theirKeys->device_keys) { - (void)device; - try { - auto identkey = - device_key.keys.at("curve25519:" + device_key.device_id); - if (verifyAtLeastOneSig( - device_key, theirKeys->self_signing_keys.keys, user_id)) { - status.verified_devices.insert(device_key.device_id); - status.verified_device_keys[identkey] = trustlevel; - } - } catch (...) { - } - } + verification_storage.status[user_id] = status; + if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) + return status; - updateUnverifiedDevices(theirKeys->device_keys); - verification_storage.status[user_id] = status; - return status; - } catch (std::exception &e) { - nhlog::db()->error( - "Failed to calculate verification status of {}: {}", user_id, e.what()); - return status; + for (const auto &[device, device_key] : theirKeys->device_keys) { + (void)device; + try { + auto identkey = device_key.keys.at("curve25519:" + device_key.device_id); + if (verifyAtLeastOneSig(device_key, theirKeys->self_signing_keys.keys, user_id)) { + status.verified_devices.insert(device_key.device_id); + status.verified_device_keys[identkey] = trustlevel; + } + } catch (...) { + } } + + updateUnverifiedDevices(theirKeys->device_keys); + verification_storage.status[user_id] = status; + return status; + } catch (std::exception &e) { + nhlog::db()->error("Failed to calculate verification status of {}: {}", user_id, e.what()); + return status; + } } void to_json(json &j, const RoomInfo &info) { - j["name"] = info.name; - j["topic"] = info.topic; - j["avatar_url"] = info.avatar_url; - j["version"] = info.version; - j["is_invite"] = info.is_invite; - j["is_space"] = info.is_space; - j["join_rule"] = info.join_rule; - j["guest_access"] = info.guest_access; + j["name"] = info.name; + j["topic"] = info.topic; + j["avatar_url"] = info.avatar_url; + j["version"] = info.version; + j["is_invite"] = info.is_invite; + j["is_space"] = info.is_space; + j["join_rule"] = info.join_rule; + j["guest_access"] = info.guest_access; - if (info.member_count != 0) - j["member_count"] = info.member_count; + if (info.member_count != 0) + j["member_count"] = info.member_count; - if (info.tags.size() != 0) - j["tags"] = info.tags; + if (info.tags.size() != 0) + j["tags"] = info.tags; } void from_json(const json &j, RoomInfo &info) { - info.name = j.at("name"); - info.topic = j.at("topic"); - info.avatar_url = j.at("avatar_url"); - info.version = j.value( - "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString()); - info.is_invite = j.at("is_invite"); - info.is_space = j.value("is_space", false); - info.join_rule = j.at("join_rule"); - info.guest_access = j.at("guest_access"); + info.name = j.at("name"); + info.topic = j.at("topic"); + info.avatar_url = j.at("avatar_url"); + info.version = j.value( + "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString()); + info.is_invite = j.at("is_invite"); + info.is_space = j.value("is_space", false); + info.join_rule = j.at("join_rule"); + info.guest_access = j.at("guest_access"); - if (j.count("member_count")) - info.member_count = j.at("member_count"); + if (j.count("member_count")) + info.member_count = j.at("member_count"); - if (j.count("tags")) - info.tags = j.at("tags").get>(); + if (j.count("tags")) + info.tags = j.at("tags").get>(); } void to_json(json &j, const ReadReceiptKey &key) { - j = json{{"event_id", key.event_id}, {"room_id", key.room_id}}; + j = json{{"event_id", key.event_id}, {"room_id", key.room_id}}; } void from_json(const json &j, ReadReceiptKey &key) { - key.event_id = j.at("event_id").get(); - key.room_id = j.at("room_id").get(); + key.event_id = j.at("event_id").get(); + key.room_id = j.at("room_id").get(); } void to_json(json &j, const MemberInfo &info) { - j["name"] = info.name; - j["avatar_url"] = info.avatar_url; + j["name"] = info.name; + j["avatar_url"] = info.avatar_url; } void from_json(const json &j, MemberInfo &info) { - info.name = j.at("name"); - info.avatar_url = j.at("avatar_url"); + info.name = j.at("name"); + info.avatar_url = j.at("avatar_url"); } void to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg) { - obj["deviceids"] = msg.deviceids; + obj["deviceids"] = msg.deviceids; } void from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg) { - msg.deviceids = obj.at("deviceids").get(); + msg.deviceids = obj.at("deviceids").get(); } void to_json(nlohmann::json &obj, const SharedWithUsers &msg) { - obj["keys"] = msg.keys; + obj["keys"] = msg.keys; } void from_json(const nlohmann::json &obj, SharedWithUsers &msg) { - msg.keys = obj.at("keys").get>(); + msg.keys = obj.at("keys").get>(); } void to_json(nlohmann::json &obj, const GroupSessionData &msg) { - obj["message_index"] = msg.message_index; - obj["ts"] = msg.timestamp; - obj["trust"] = msg.trusted; + obj["message_index"] = msg.message_index; + obj["ts"] = msg.timestamp; + obj["trust"] = msg.trusted; - obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; - obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; + obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; + obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; - obj["currently"] = msg.currently; + obj["currently"] = msg.currently; - obj["indices"] = msg.indices; + obj["indices"] = msg.indices; } void from_json(const nlohmann::json &obj, GroupSessionData &msg) { - msg.message_index = obj.at("message_index"); - msg.timestamp = obj.value("ts", 0ULL); - msg.trusted = obj.value("trust", true); + msg.message_index = obj.at("message_index"); + msg.timestamp = obj.value("ts", 0ULL); + msg.trusted = obj.value("trust", true); - msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); - msg.forwarding_curve25519_key_chain = - obj.value("forwarding_curve25519_key_chain", std::vector{}); + msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); + msg.forwarding_curve25519_key_chain = + obj.value("forwarding_curve25519_key_chain", std::vector{}); - msg.currently = obj.value("currently", SharedWithUsers{}); + msg.currently = obj.value("currently", SharedWithUsers{}); - msg.indices = obj.value("indices", std::map()); + msg.indices = obj.value("indices", std::map()); } void to_json(nlohmann::json &obj, const DevicePublicKeys &msg) { - obj["ed25519"] = msg.ed25519; - obj["curve25519"] = msg.curve25519; + obj["ed25519"] = msg.ed25519; + obj["curve25519"] = msg.curve25519; } void from_json(const nlohmann::json &obj, DevicePublicKeys &msg) { - msg.ed25519 = obj.at("ed25519"); - msg.curve25519 = obj.at("curve25519"); + msg.ed25519 = obj.at("ed25519"); + msg.curve25519 = obj.at("curve25519"); } void to_json(nlohmann::json &obj, const MegolmSessionIndex &msg) { - obj["room_id"] = msg.room_id; - obj["session_id"] = msg.session_id; - obj["sender_key"] = msg.sender_key; + obj["room_id"] = msg.room_id; + obj["session_id"] = msg.session_id; + obj["sender_key"] = msg.sender_key; } void from_json(const nlohmann::json &obj, MegolmSessionIndex &msg) { - msg.room_id = obj.at("room_id"); - msg.session_id = obj.at("session_id"); - msg.sender_key = obj.at("sender_key"); + msg.room_id = obj.at("room_id"); + msg.session_id = obj.at("session_id"); + msg.sender_key = obj.at("sender_key"); } void to_json(nlohmann::json &obj, const StoredOlmSession &msg) { - obj["ts"] = msg.last_message_ts; - obj["s"] = msg.pickled_session; + obj["ts"] = msg.last_message_ts; + obj["s"] = msg.pickled_session; } void from_json(const nlohmann::json &obj, StoredOlmSession &msg) { - msg.last_message_ts = obj.at("ts").get(); - msg.pickled_session = obj.at("s").get(); + msg.last_message_ts = obj.at("ts").get(); + msg.pickled_session = obj.at("s").get(); } namespace cache { void init(const QString &user_id) { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType(); - instance_ = std::make_unique(user_id); + instance_ = std::make_unique(user_id); } Cache * client() { - return instance_.get(); + return instance_.get(); } std::string displayName(const std::string &room_id, const std::string &user_id) { - return instance_->displayName(room_id, user_id); + return instance_->displayName(room_id, user_id); } QString displayName(const QString &room_id, const QString &user_id) { - return instance_->displayName(room_id, user_id); + return instance_->displayName(room_id, user_id); } QString avatarUrl(const QString &room_id, const QString &user_id) { - return instance_->avatarUrl(room_id, user_id); + return instance_->avatarUrl(room_id, user_id); } mtx::presence::PresenceState presenceState(const std::string &user_id) { - if (!instance_) - return {}; - return instance_->presenceState(user_id); + if (!instance_) + return {}; + return instance_->presenceState(user_id); } std::string statusMessage(const std::string &user_id) { - return instance_->statusMessage(user_id); + return instance_->statusMessage(user_id); } // user cache stores user keys std::optional userKeys(const std::string &user_id) { - return instance_->userKeys(user_id); + return instance_->userKeys(user_id); } void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - instance_->updateUserKeys(sync_token, keyQuery); + instance_->updateUserKeys(sync_token, keyQuery); } // device & user verification cache std::optional verificationStatus(const std::string &user_id) { - return instance_->verificationStatus(user_id); + return instance_->verificationStatus(user_id); } void markDeviceVerified(const std::string &user_id, const std::string &device) { - instance_->markDeviceVerified(user_id, device); + instance_->markDeviceVerified(user_id, device); } void markDeviceUnverified(const std::string &user_id, const std::string &device) { - instance_->markDeviceUnverified(user_id, device); + instance_->markDeviceUnverified(user_id, device); } std::vector joinedRooms() { - return instance_->joinedRooms(); + return instance_->joinedRooms(); } QMap roomInfo(bool withInvites) { - return instance_->roomInfo(withInvites); + return instance_->roomInfo(withInvites); } QHash invites() { - return instance_->invites(); + return instance_->invites(); } QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - return instance_->getRoomName(txn, statesdb, membersdb); + return instance_->getRoomName(txn, statesdb, membersdb); } mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomJoinRule(txn, statesdb); + return instance_->getRoomJoinRule(txn, statesdb); } bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomGuestAccess(txn, statesdb); + return instance_->getRoomGuestAccess(txn, statesdb); } QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomTopic(txn, statesdb); + return instance_->getRoomTopic(txn, statesdb); } QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - return instance_->getRoomAvatarUrl(txn, statesdb, membersdb); + return instance_->getRoomAvatarUrl(txn, statesdb, membersdb); } std::vector getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len) { - return instance_->getMembers(room_id, startIndex, len); + return instance_->getMembers(room_id, startIndex, len); } std::vector getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len) { - return instance_->getMembersFromInvite(room_id, startIndex, len); + return instance_->getMembersFromInvite(room_id, startIndex, len); } void saveState(const mtx::responses::Sync &res) { - instance_->saveState(res); + instance_->saveState(res); } bool isInitialized() { - return instance_->isInitialized(); + return instance_->isInitialized(); } std::string nextBatchToken() { - return instance_->nextBatchToken(); + return instance_->nextBatchToken(); } void deleteData() { - instance_->deleteData(); + instance_->deleteData(); } void removeInvite(lmdb::txn &txn, const std::string &room_id) { - instance_->removeInvite(txn, room_id); + instance_->removeInvite(txn, room_id); } void removeInvite(const std::string &room_id) { - instance_->removeInvite(room_id); + instance_->removeInvite(room_id); } void removeRoom(lmdb::txn &txn, const std::string &roomid) { - instance_->removeRoom(txn, roomid); + instance_->removeRoom(txn, roomid); } void removeRoom(const std::string &roomid) { - instance_->removeRoom(roomid); + instance_->removeRoom(roomid); } void removeRoom(const QString &roomid) { - instance_->removeRoom(roomid.toStdString()); + instance_->removeRoom(roomid.toStdString()); } void setup() { - instance_->setup(); + instance_->setup(); } bool runMigrations() { - return instance_->runMigrations(); + return instance_->runMigrations(); } cache::CacheVersion formatVersion() { - return instance_->formatVersion(); + return instance_->formatVersion(); } void setCurrentFormat() { - instance_->setCurrentFormat(); + instance_->setCurrentFormat(); } std::vector roomIds() { - return instance_->roomIds(); + return instance_->roomIds(); } QMap getTimelineMentions() { - return instance_->getTimelineMentions(); + return instance_->getTimelineMentions(); } //! Retrieve all the user ids from a room. std::vector roomMembers(const std::string &room_id) { - return instance_->roomMembers(room_id); + return instance_->roomMembers(room_id); } //! Check if the given user has power leve greater than than @@ -4952,48 +4826,48 @@ hasEnoughPowerLevel(const std::vector &eventTypes, const std::string &room_id, const std::string &user_id) { - return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id); + return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id); } void updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts) { - instance_->updateReadReceipt(txn, room_id, receipts); + instance_->updateReadReceipt(txn, room_id, receipts); } UserReceipts readReceipts(const QString &event_id, const QString &room_id) { - return instance_->readReceipts(event_id, room_id); + return instance_->readReceipts(event_id, room_id); } std::optional getEventIndex(const std::string &room_id, std::string_view event_id) { - return instance_->getEventIndex(room_id, event_id); + return instance_->getEventIndex(room_id, event_id); } std::optional> lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id) { - return instance_->lastInvisibleEventAfter(room_id, event_id); + return instance_->lastInvisibleEventAfter(room_id, event_id); } RoomInfo singleRoomInfo(const std::string &room_id) { - return instance_->singleRoomInfo(room_id); + return instance_->singleRoomInfo(room_id); } std::vector roomsWithStateUpdates(const mtx::responses::Sync &res) { - return instance_->roomsWithStateUpdates(res); + return instance_->roomsWithStateUpdates(res); } std::map getRoomInfo(const std::vector &rooms) { - return instance_->getRoomInfo(rooms); + return instance_->getRoomInfo(rooms); } //! Calculates which the read status of a room. @@ -5001,74 +4875,74 @@ getRoomInfo(const std::vector &rooms) bool calculateRoomReadStatus(const std::string &room_id) { - return instance_->calculateRoomReadStatus(room_id); + return instance_->calculateRoomReadStatus(room_id); } void calculateRoomReadStatus() { - instance_->calculateRoomReadStatus(); + instance_->calculateRoomReadStatus(); } void markSentNotification(const std::string &event_id) { - instance_->markSentNotification(event_id); + instance_->markSentNotification(event_id); } //! Removes an event from the sent notifications. void removeReadNotification(const std::string &event_id) { - instance_->removeReadNotification(event_id); + instance_->removeReadNotification(event_id); } //! Check if we have sent a desktop notification for the given event id. bool isNotificationSent(const std::string &event_id) { - return instance_->isNotificationSent(event_id); + return instance_->isNotificationSent(event_id); } //! Add all notifications containing a user mention to the db. void saveTimelineMentions(const mtx::responses::Notifications &res) { - instance_->saveTimelineMentions(res); + instance_->saveTimelineMentions(res); } //! Remove old unused data. void deleteOldMessages() { - instance_->deleteOldMessages(); + instance_->deleteOldMessages(); } void deleteOldData() noexcept { - instance_->deleteOldData(); + instance_->deleteOldData(); } //! Retrieve all saved room ids. std::vector getRoomIds(lmdb::txn &txn) { - return instance_->getRoomIds(txn); + return instance_->getRoomIds(txn); } //! Mark a room that uses e2e encryption. void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) { - instance_->setEncryptedRoom(txn, room_id); + instance_->setEncryptedRoom(txn, room_id); } bool isRoomEncrypted(const std::string &room_id) { - return instance_->isRoomEncrypted(room_id); + return instance_->isRoomEncrypted(room_id); } //! Check if a user is a member of the room. bool isRoomMember(const std::string &user_id, const std::string &room_id) { - return instance_->isRoomMember(user_id, room_id); + return instance_->isRoomMember(user_id, room_id); } // @@ -5079,40 +4953,40 @@ saveOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { - instance_->saveOutboundMegolmSession(room_id, data, session); + instance_->saveOutboundMegolmSession(room_id, data, session); } OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id) { - return instance_->getOutboundMegolmSession(room_id); + return instance_->getOutboundMegolmSession(room_id); } bool outboundMegolmSessionExists(const std::string &room_id) noexcept { - return instance_->outboundMegolmSessionExists(room_id); + return instance_->outboundMegolmSessionExists(room_id); } void updateOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { - instance_->updateOutboundMegolmSession(room_id, data, session); + instance_->updateOutboundMegolmSession(room_id, data, session); } void dropOutboundMegolmSession(const std::string &room_id) { - instance_->dropOutboundMegolmSession(room_id); + instance_->dropOutboundMegolmSession(room_id); } void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) { - instance_->importSessionKeys(keys); + instance_->importSessionKeys(keys); } mtx::crypto::ExportedSessionKeys exportSessionKeys() { - return instance_->exportSessionKeys(); + return instance_->exportSessionKeys(); } // @@ -5123,22 +4997,22 @@ saveInboundMegolmSession(const MegolmSessionIndex &index, mtx::crypto::InboundGroupSessionPtr session, const GroupSessionData &data) { - instance_->saveInboundMegolmSession(index, std::move(session), data); + instance_->saveInboundMegolmSession(index, std::move(session), data); } mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index) { - return instance_->getInboundMegolmSession(index); + return instance_->getInboundMegolmSession(index); } bool inboundMegolmSessionExists(const MegolmSessionIndex &index) { - return instance_->inboundMegolmSessionExists(index); + return instance_->inboundMegolmSessionExists(index); } std::optional getMegolmSessionData(const MegolmSessionIndex &index) { - return instance_->getMegolmSessionData(index); + return instance_->getMegolmSessionData(index); } // @@ -5149,43 +5023,43 @@ saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session, uint64_t timestamp) { - instance_->saveOlmSession(curve25519, std::move(session), timestamp); + instance_->saveOlmSession(curve25519, std::move(session), timestamp); } std::vector getOlmSessions(const std::string &curve25519) { - return instance_->getOlmSessions(curve25519); + return instance_->getOlmSessions(curve25519); } std::optional getOlmSession(const std::string &curve25519, const std::string &session_id) { - return instance_->getOlmSession(curve25519, session_id); + return instance_->getOlmSession(curve25519, session_id); } std::optional getLatestOlmSession(const std::string &curve25519) { - return instance_->getLatestOlmSession(curve25519); + return instance_->getLatestOlmSession(curve25519); } void saveOlmAccount(const std::string &pickled) { - instance_->saveOlmAccount(pickled); + instance_->saveOlmAccount(pickled); } std::string restoreOlmAccount() { - return instance_->restoreOlmAccount(); + return instance_->restoreOlmAccount(); } void storeSecret(const std::string &name, const std::string &secret) { - instance_->storeSecret(name, secret); + instance_->storeSecret(name, secret); } std::optional secret(const std::string &name) { - return instance_->secret(name); + return instance_->secret(name); } } // namespace cache diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 80dd1046..b7461848 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -19,48 +19,48 @@ Q_NAMESPACE //! How much a participant is trusted. enum Trust { - Unverified, //! Device unverified or master key changed. - TOFU, //! Device is signed by the sender, but the user is not verified, but they never - //! changed the master key. - Verified, //! User was verified and has crosssigned this device or device is verified. + Unverified, //! Device unverified or master key changed. + TOFU, //! Device is signed by the sender, but the user is not verified, but they never + //! changed the master key. + Verified, //! User was verified and has crosssigned this device or device is verified. }; Q_ENUM_NS(Trust) } struct DeviceKeysToMsgIndex { - // map from device key to message_index - // Using the device id is safe because we check for reuse on device list updates - // Using the device id makes our logic much easier to read. - std::map deviceids; + // map from device key to message_index + // Using the device id is safe because we check for reuse on device list updates + // Using the device id makes our logic much easier to read. + std::map deviceids; }; struct SharedWithUsers { - // userid to keys - std::map keys; + // userid to keys + std::map keys; }; // Extra information associated with an outbound megolm session. struct GroupSessionData { - uint64_t message_index = 0; - uint64_t timestamp = 0; + uint64_t message_index = 0; + uint64_t timestamp = 0; - // If we got the session via key sharing or forwarding, we can usually trust it. - // If it came from asymmetric key backup, it is not trusted. - // TODO(Nico): What about forwards? They might come from key backup? - bool trusted = true; + // If we got the session via key sharing or forwarding, we can usually trust it. + // If it came from asymmetric key backup, it is not trusted. + // TODO(Nico): What about forwards? They might come from key backup? + bool trusted = true; - std::string sender_claimed_ed25519_key; - std::vector forwarding_curve25519_key_chain; + std::string sender_claimed_ed25519_key; + std::vector forwarding_curve25519_key_chain; - //! map from index to event_id to check for replay attacks - std::map indices; + //! map from index to event_id to check for replay attacks + std::map indices; - // who has access to this session. - // Rotate, when a user leaves the room and share, when a user gets added. - SharedWithUsers currently; + // who has access to this session. + // Rotate, when a user leaves the room and share, when a user gets added. + SharedWithUsers currently; }; void @@ -70,14 +70,14 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg); struct OutboundGroupSessionDataRef { - mtx::crypto::OutboundGroupSessionPtr session; - GroupSessionData data; + mtx::crypto::OutboundGroupSessionPtr session; + GroupSessionData data; }; struct DevicePublicKeys { - std::string ed25519; - std::string curve25519; + std::string ed25519; + std::string curve25519; }; void @@ -88,19 +88,19 @@ from_json(const nlohmann::json &obj, DevicePublicKeys &msg); //! Represents a unique megolm session identifier. struct MegolmSessionIndex { - MegolmSessionIndex() = default; - MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e) - : room_id(std::move(room_id_)) - , session_id(e.session_id) - , sender_key(e.sender_key) - {} - - //! The room in which this session exists. - std::string room_id; - //! The session_id of the megolm session. - std::string session_id; - //! The curve25519 public key of the sender. - std::string sender_key; + MegolmSessionIndex() = default; + MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e) + : room_id(std::move(room_id_)) + , session_id(e.session_id) + , sender_key(e.sender_key) + {} + + //! The room in which this session exists. + std::string room_id; + //! The session_id of the megolm session. + std::string session_id; + //! The curve25519 public key of the sender. + std::string sender_key; }; void @@ -110,8 +110,8 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg); struct StoredOlmSession { - std::uint64_t last_message_ts = 0; - std::string pickled_session; + std::uint64_t last_message_ts = 0; + std::string pickled_session; }; void to_json(nlohmann::json &obj, const StoredOlmSession &msg); @@ -121,43 +121,43 @@ from_json(const nlohmann::json &obj, StoredOlmSession &msg); //! Verification status of a single user struct VerificationStatus { - //! True, if the users master key is verified - crypto::Trust user_verified = crypto::Trust::Unverified; - //! List of all devices marked as verified - std::set verified_devices; - //! Map from sender key/curve25519 to trust status - std::map verified_device_keys; - //! Count of unverified devices - int unverified_device_count = 0; - // if the keys are not in cache - bool no_keys = false; + //! True, if the users master key is verified + crypto::Trust user_verified = crypto::Trust::Unverified; + //! List of all devices marked as verified + std::set verified_devices; + //! Map from sender key/curve25519 to trust status + std::map verified_device_keys; + //! Count of unverified devices + int unverified_device_count = 0; + // if the keys are not in cache + bool no_keys = false; }; //! In memory cache of verification status struct VerificationStorage { - //! mapping of user to verification status - std::map status; - std::mutex verification_storage_mtx; + //! mapping of user to verification status + std::map status; + std::mutex verification_storage_mtx; }; // this will store the keys of the user with whom a encrypted room is shared with struct UserKeyCache { - //! Device id to device keys - std::map device_keys; - //! cross signing keys - mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys; - //! Sync token when nheko last fetched the keys - std::string updated_at; - //! Sync token when the keys last changed. updated != last_changed means they are outdated. - std::string last_changed; - //! if the master key has ever changed - bool master_key_changed = false; - //! Device keys that were already used at least once - std::set seen_device_keys; - //! Device ids that were already used at least once - std::set seen_device_ids; + //! Device id to device keys + std::map device_keys; + //! cross signing keys + mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys; + //! Sync token when nheko last fetched the keys + std::string updated_at; + //! Sync token when the keys last changed. updated != last_changed means they are outdated. + std::string last_changed; + //! if the master key has ever changed + bool master_key_changed = false; + //! Device keys that were already used at least once + std::set seen_device_keys; + //! Device ids that were already used at least once + std::set seen_device_ids; }; void @@ -169,10 +169,10 @@ from_json(const nlohmann::json &j, UserKeyCache &info); // UserKeyCache stores only keys of users with which encrypted room is shared struct VerificationCache { - //! list of verified device_ids with device-verification - std::set device_verified; - //! list of devices the user blocks - std::set device_blocked; + //! list of verified device_ids with device-verification + std::set device_verified; + //! list of devices the user blocks + std::set device_blocked; }; void @@ -182,10 +182,10 @@ from_json(const nlohmann::json &j, VerificationCache &info); struct OnlineBackupVersion { - //! the version of the online backup currently enabled - std::string version; - //! the algorithm used by the backup - std::string algorithm; + //! the version of the online backup currently enabled + std::string version; + //! the algorithm used by the backup + std::string algorithm; }; void diff --git a/src/CacheStructs.h b/src/CacheStructs.h index 5f4d392a..e28f5b2d 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -16,23 +16,23 @@ namespace cache { enum class CacheVersion : int { - Older = -1, - Current = 0, - Newer = 1, + Older = -1, + Current = 0, + Newer = 1, }; } struct RoomMember { - QString user_id; - QString display_name; + QString user_id; + QString display_name; }; //! Used to uniquely identify a list of read receipts. struct ReadReceiptKey { - std::string event_id; - std::string room_id; + std::string event_id; + std::string room_id; }; void @@ -43,49 +43,49 @@ from_json(const nlohmann::json &j, ReadReceiptKey &key); struct DescInfo { - QString event_id; - QString userid; - QString body; - QString descriptiveTime; - uint64_t timestamp; - QDateTime datetime; + QString event_id; + QString userid; + QString body; + QString descriptiveTime; + uint64_t timestamp; + QDateTime datetime; }; inline bool operator==(const DescInfo &a, const DescInfo &b) { - return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) == - std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime); + return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) == + std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime); } inline bool operator!=(const DescInfo &a, const DescInfo &b) { - return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) != - std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime); + return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) != + std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime); } //! UI info associated with a room. struct RoomInfo { - //! The calculated name of the room. - std::string name; - //! The topic of the room. - std::string topic; - //! The calculated avatar url of the room. - std::string avatar_url; - //! The calculated version of this room set at creation time. - std::string version; - //! Whether or not the room is an invite. - bool is_invite = false; - //! Wheter or not the room is a space - bool is_space = false; - //! Total number of members in the room. - size_t member_count = 0; - //! Who can access to the room. - mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public; - bool guest_access = false; - //! The list of tags associated with this room - std::vector tags; + //! The calculated name of the room. + std::string name; + //! The topic of the room. + std::string topic; + //! The calculated avatar url of the room. + std::string avatar_url; + //! The calculated version of this room set at creation time. + std::string version; + //! Whether or not the room is an invite. + bool is_invite = false; + //! Wheter or not the room is a space + bool is_space = false; + //! Total number of members in the room. + size_t member_count = 0; + //! Who can access to the room. + mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public; + bool guest_access = false; + //! The list of tags associated with this room + std::vector tags; }; void @@ -96,8 +96,8 @@ from_json(const nlohmann::json &j, RoomInfo &info); //! Basic information per member. struct MemberInfo { - std::string name; - std::string avatar_url; + std::string name; + std::string avatar_url; }; void @@ -107,13 +107,13 @@ from_json(const nlohmann::json &j, MemberInfo &info); struct RoomSearchResult { - std::string room_id; - RoomInfo info; + std::string room_id; + RoomInfo info; }; struct ImagePackInfo { - mtx::events::msc2545::ImagePack pack; - std::string source_room; - std::string state_key; + mtx::events::msc2545::ImagePack pack; + std::string source_room; + std::string state_key; }; diff --git a/src/Cache_p.h b/src/Cache_p.h index ff2f31e5..a15010e6 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -32,694 +32,658 @@ class Cache : public QObject { - Q_OBJECT + Q_OBJECT public: - Cache(const QString &userId, QObject *parent = nullptr); - - std::string displayName(const std::string &room_id, const std::string &user_id); - QString displayName(const QString &room_id, const QString &user_id); - QString avatarUrl(const QString &room_id, const QString &user_id); - - // presence - mtx::presence::PresenceState presenceState(const std::string &user_id); - std::string statusMessage(const std::string &user_id); - - // user cache stores user keys - std::map> getMembersWithKeys( - const std::string &room_id, - bool verified_only); - void updateUserKeys(const std::string &sync_token, - const mtx::responses::QueryKeys &keyQuery); - void markUserKeysOutOfDate(lmdb::txn &txn, - lmdb::dbi &db, - const std::vector &user_ids, - const std::string &sync_token); - void query_keys(const std::string &user_id, - std::function cb); - - // device & user verification cache - std::optional userKeys(const std::string &user_id); - VerificationStatus verificationStatus(const std::string &user_id); - void markDeviceVerified(const std::string &user_id, const std::string &device); - void markDeviceUnverified(const std::string &user_id, const std::string &device); - crypto::Trust roomVerificationStatus(const std::string &room_id); - - std::vector joinedRooms(); - - QMap roomInfo(bool withInvites = true); - std::optional getRoomAliases(const std::string &roomid); - QHash invites(); - std::optional invite(std::string_view roomid); - QMap> spaces(); - - //! Calculate & return the name of the room. - QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); - //! Get room join rules - mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb); - bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb); - //! Retrieve the topic of the room if any. - QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); - //! Retrieve the room avatar's url if any. - QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); - //! Retrieve the version of the room if any. - QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb); - //! Retrieve if the room is a space - bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb); - - //! Get a specific state event - template - std::optional> getStateEvent(const std::string &room_id, - std::string_view state_key = "") - { - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - return getStateEvent(txn, room_id, state_key); - } - - //! retrieve a specific event from account data - //! pass empty room_id for global account data - std::optional getAccountData( - mtx::events::EventType type, - const std::string &room_id = ""); - - //! Retrieve member info from a room. - std::vector getMembers(const std::string &room_id, - std::size_t startIndex = 0, - std::size_t len = 30); - - std::vector getMembersFromInvite(const std::string &room_id, - std::size_t startIndex = 0, - std::size_t len = 30); - size_t memberCount(const std::string &room_id); - - void saveState(const mtx::responses::Sync &res); - bool isInitialized(); - bool isDatabaseReady() { return databaseReady_ && isInitialized(); } - - std::string nextBatchToken(); - - void deleteData(); - - void removeInvite(lmdb::txn &txn, const std::string &room_id); - void removeInvite(const std::string &room_id); - void removeRoom(lmdb::txn &txn, const std::string &roomid); - void removeRoom(const std::string &roomid); - void setup(); - - cache::CacheVersion formatVersion(); - void setCurrentFormat(); - bool runMigrations(); - - std::vector roomIds(); - QMap getTimelineMentions(); - - //! Retrieve all the user ids from a room. - std::vector roomMembers(const std::string &room_id); - - //! Check if the given user has power leve greater than than - //! lowest power level of the given events. - bool hasEnoughPowerLevel(const std::vector &eventTypes, + Cache(const QString &userId, QObject *parent = nullptr); + + std::string displayName(const std::string &room_id, const std::string &user_id); + QString displayName(const QString &room_id, const QString &user_id); + QString avatarUrl(const QString &room_id, const QString &user_id); + + // presence + mtx::presence::PresenceState presenceState(const std::string &user_id); + std::string statusMessage(const std::string &user_id); + + // user cache stores user keys + std::map> getMembersWithKeys( + const std::string &room_id, + bool verified_only); + void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); + void markUserKeysOutOfDate(lmdb::txn &txn, + lmdb::dbi &db, + const std::vector &user_ids, + const std::string &sync_token); + void query_keys(const std::string &user_id, + std::function cb); + + // device & user verification cache + std::optional userKeys(const std::string &user_id); + VerificationStatus verificationStatus(const std::string &user_id); + void markDeviceVerified(const std::string &user_id, const std::string &device); + void markDeviceUnverified(const std::string &user_id, const std::string &device); + crypto::Trust roomVerificationStatus(const std::string &room_id); + + std::vector joinedRooms(); + + QMap roomInfo(bool withInvites = true); + std::optional getRoomAliases(const std::string &roomid); + QHash invites(); + std::optional invite(std::string_view roomid); + QMap> spaces(); + + //! Calculate & return the name of the room. + QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); + //! Get room join rules + mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb); + bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb); + //! Retrieve the topic of the room if any. + QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); + //! Retrieve the room avatar's url if any. + QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); + //! Retrieve the version of the room if any. + QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb); + //! Retrieve if the room is a space + bool getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb); + + //! Get a specific state event + template + std::optional> getStateEvent(const std::string &room_id, + std::string_view state_key = "") + { + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + return getStateEvent(txn, room_id, state_key); + } + + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional getAccountData( + mtx::events::EventType type, + const std::string &room_id = ""); + + //! Retrieve member info from a room. + std::vector getMembers(const std::string &room_id, + std::size_t startIndex = 0, + std::size_t len = 30); + + std::vector getMembersFromInvite(const std::string &room_id, + std::size_t startIndex = 0, + std::size_t len = 30); + size_t memberCount(const std::string &room_id); + + void saveState(const mtx::responses::Sync &res); + bool isInitialized(); + bool isDatabaseReady() { return databaseReady_ && isInitialized(); } + + std::string nextBatchToken(); + + void deleteData(); + + void removeInvite(lmdb::txn &txn, const std::string &room_id); + void removeInvite(const std::string &room_id); + void removeRoom(lmdb::txn &txn, const std::string &roomid); + void removeRoom(const std::string &roomid); + void setup(); + + cache::CacheVersion formatVersion(); + void setCurrentFormat(); + bool runMigrations(); + + std::vector roomIds(); + QMap getTimelineMentions(); + + //! Retrieve all the user ids from a room. + std::vector roomMembers(const std::string &room_id); + + //! Check if the given user has power leve greater than than + //! lowest power level of the given events. + bool hasEnoughPowerLevel(const std::vector &eventTypes, + const std::string &room_id, + const std::string &user_id); + + //! Adds a user to the read list for the given event. + //! + //! There should be only one user id present in a receipt list per room. + //! The user id should be removed from any other lists. + using Receipts = std::map>; + void updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts); + + //! Retrieve all the read receipts for the given event id and room. + //! + //! Returns a map of user ids and the time of the read receipt in milliseconds. + using UserReceipts = std::multimap>; + UserReceipts readReceipts(const QString &event_id, const QString &room_id); + + RoomInfo singleRoomInfo(const std::string &room_id); + std::vector roomsWithStateUpdates(const mtx::responses::Sync &res); + std::map getRoomInfo(const std::vector &rooms); + + //! Calculates which the read status of a room. + //! Whether all the events in the timeline have been read. + bool calculateRoomReadStatus(const std::string &room_id); + void calculateRoomReadStatus(); + + void markSentNotification(const std::string &event_id); + //! Removes an event from the sent notifications. + void removeReadNotification(const std::string &event_id); + //! Check if we have sent a desktop notification for the given event id. + bool isNotificationSent(const std::string &event_id); + + //! Add all notifications containing a user mention to the db. + void saveTimelineMentions(const mtx::responses::Notifications &res); + + //! retrieve events in timeline and related functions + struct Messages + { + mtx::responses::Timeline timeline; + uint64_t next_index; + bool end_of_cache = false; + }; + Messages getTimelineMessages(lmdb::txn &txn, const std::string &room_id, - const std::string &user_id); - - //! Adds a user to the read list for the given event. - //! - //! There should be only one user id present in a receipt list per room. - //! The user id should be removed from any other lists. - using Receipts = std::map>; - void updateReadReceipt(lmdb::txn &txn, - const std::string &room_id, - const Receipts &receipts); - - //! Retrieve all the read receipts for the given event id and room. - //! - //! Returns a map of user ids and the time of the read receipt in milliseconds. - using UserReceipts = std::multimap>; - UserReceipts readReceipts(const QString &event_id, const QString &room_id); - - RoomInfo singleRoomInfo(const std::string &room_id); - std::vector roomsWithStateUpdates(const mtx::responses::Sync &res); - std::map getRoomInfo(const std::vector &rooms); - - //! Calculates which the read status of a room. - //! Whether all the events in the timeline have been read. - bool calculateRoomReadStatus(const std::string &room_id); - void calculateRoomReadStatus(); - - void markSentNotification(const std::string &event_id); - //! Removes an event from the sent notifications. - void removeReadNotification(const std::string &event_id); - //! Check if we have sent a desktop notification for the given event id. - bool isNotificationSent(const std::string &event_id); - - //! Add all notifications containing a user mention to the db. - void saveTimelineMentions(const mtx::responses::Notifications &res); - - //! retrieve events in timeline and related functions - struct Messages - { - mtx::responses::Timeline timeline; - uint64_t next_index; - bool end_of_cache = false; - }; - Messages getTimelineMessages(lmdb::txn &txn, - const std::string &room_id, - uint64_t index = std::numeric_limits::max(), - bool forward = false); - - std::optional getEvent( - const std::string &room_id, - const std::string &event_id); - void storeEvent(const std::string &room_id, - const std::string &event_id, - const mtx::events::collections::TimelineEvent &event); - void replaceEvent(const std::string &room_id, - const std::string &event_id, - const mtx::events::collections::TimelineEvent &event); - std::vector relatedEvents(const std::string &room_id, - const std::string &event_id); - - struct TimelineRange - { - uint64_t first, last; + uint64_t index = std::numeric_limits::max(), + bool forward = false); + + std::optional getEvent(const std::string &room_id, + const std::string &event_id); + void storeEvent(const std::string &room_id, + const std::string &event_id, + const mtx::events::collections::TimelineEvent &event); + void replaceEvent(const std::string &room_id, + const std::string &event_id, + const mtx::events::collections::TimelineEvent &event); + std::vector relatedEvents(const std::string &room_id, const std::string &event_id); + + struct TimelineRange + { + uint64_t first, last; + }; + std::optional getTimelineRange(const std::string &room_id); + std::optional getTimelineIndex(const std::string &room_id, std::string_view event_id); + std::optional getEventIndex(const std::string &room_id, std::string_view event_id); + std::optional> lastInvisibleEventAfter( + const std::string &room_id, + std::string_view event_id); + std::optional getTimelineEventId(const std::string &room_id, uint64_t index); + std::optional getArrivalIndex(const std::string &room_id, std::string_view event_id); + + std::string previousBatchToken(const std::string &room_id); + uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res); + void savePendingMessage(const std::string &room_id, + const mtx::events::collections::TimelineEvent &message); + std::optional firstPendingMessage( + const std::string &room_id); + void removePendingStatus(const std::string &room_id, const std::string &txn_id); + + //! clear timeline keeping only the latest batch + void clearTimeline(const std::string &room_id); + + //! Remove old unused data. + void deleteOldMessages(); + void deleteOldData() noexcept; + //! Retrieve all saved room ids. + std::vector getRoomIds(lmdb::txn &txn); + std::vector getParentRoomIds(const std::string &room_id); + std::vector getChildRoomIds(const std::string &room_id); + + std::vector getImagePacks(const std::string &room_id, + std::optional stickers); + + //! Mark a room that uses e2e encryption. + void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); + bool isRoomEncrypted(const std::string &room_id); + std::optional roomEncryptionSettings( + const std::string &room_id); + + //! Check if a user is a member of the room. + bool isRoomMember(const std::string &user_id, const std::string &room_id); + + // + // Outbound Megolm Sessions + // + void saveOutboundMegolmSession(const std::string &room_id, + const GroupSessionData &data, + mtx::crypto::OutboundGroupSessionPtr &session); + OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); + bool outboundMegolmSessionExists(const std::string &room_id) noexcept; + void updateOutboundMegolmSession(const std::string &room_id, + const GroupSessionData &data, + mtx::crypto::OutboundGroupSessionPtr &session); + void dropOutboundMegolmSession(const std::string &room_id); + + void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); + mtx::crypto::ExportedSessionKeys exportSessionKeys(); + + // + // Inbound Megolm Sessions + // + void saveInboundMegolmSession(const MegolmSessionIndex &index, + mtx::crypto::InboundGroupSessionPtr session, + const GroupSessionData &data); + mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index); + bool inboundMegolmSessionExists(const MegolmSessionIndex &index); + std::optional getMegolmSessionData(const MegolmSessionIndex &index); + + // + // Olm Sessions + // + void saveOlmSession(const std::string &curve25519, + mtx::crypto::OlmSessionPtr session, + uint64_t timestamp); + std::vector getOlmSessions(const std::string &curve25519); + std::optional getOlmSession(const std::string &curve25519, + const std::string &session_id); + std::optional getLatestOlmSession(const std::string &curve25519); + + void saveOlmAccount(const std::string &pickled); + std::string restoreOlmAccount(); + + void saveBackupVersion(const OnlineBackupVersion &data); + void deleteBackupVersion(); + std::optional backupVersion(); + + void storeSecret(const std::string name, const std::string secret, bool internal = false); + void deleteSecret(const std::string name, bool internal = false); + std::optional secret(const std::string name, bool internal = false); + + std::string pickleSecret(); + + template + constexpr static bool isStateEvent_ = + std::is_same_v>, + mtx::events::StateEvent().content)>>; + + static int compare_state_key(const MDB_val *a, const MDB_val *b) + { + auto get_skey = [](const MDB_val *v) { + return nlohmann::json::parse( + std::string_view(static_cast(v->mv_data), v->mv_size)) + .value("key", ""); }; - std::optional getTimelineRange(const std::string &room_id); - std::optional getTimelineIndex(const std::string &room_id, - std::string_view event_id); - std::optional getEventIndex(const std::string &room_id, - std::string_view event_id); - std::optional> lastInvisibleEventAfter( - const std::string &room_id, - std::string_view event_id); - std::optional getTimelineEventId(const std::string &room_id, uint64_t index); - std::optional getArrivalIndex(const std::string &room_id, - std::string_view event_id); - - std::string previousBatchToken(const std::string &room_id); - uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res); - void savePendingMessage(const std::string &room_id, - const mtx::events::collections::TimelineEvent &message); - std::optional firstPendingMessage( - const std::string &room_id); - void removePendingStatus(const std::string &room_id, const std::string &txn_id); - - //! clear timeline keeping only the latest batch - void clearTimeline(const std::string &room_id); - - //! Remove old unused data. - void deleteOldMessages(); - void deleteOldData() noexcept; - //! Retrieve all saved room ids. - std::vector getRoomIds(lmdb::txn &txn); - std::vector getParentRoomIds(const std::string &room_id); - std::vector getChildRoomIds(const std::string &room_id); - - std::vector getImagePacks(const std::string &room_id, - std::optional stickers); - - //! Mark a room that uses e2e encryption. - void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); - bool isRoomEncrypted(const std::string &room_id); - std::optional roomEncryptionSettings( - const std::string &room_id); - - //! Check if a user is a member of the room. - bool isRoomMember(const std::string &user_id, const std::string &room_id); - - // - // Outbound Megolm Sessions - // - void saveOutboundMegolmSession(const std::string &room_id, - const GroupSessionData &data, - mtx::crypto::OutboundGroupSessionPtr &session); - OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id); - bool outboundMegolmSessionExists(const std::string &room_id) noexcept; - void updateOutboundMegolmSession(const std::string &room_id, - const GroupSessionData &data, - mtx::crypto::OutboundGroupSessionPtr &session); - void dropOutboundMegolmSession(const std::string &room_id); - - void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys); - mtx::crypto::ExportedSessionKeys exportSessionKeys(); - - // - // Inbound Megolm Sessions - // - void saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session, - const GroupSessionData &data); - mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession( - const MegolmSessionIndex &index); - bool inboundMegolmSessionExists(const MegolmSessionIndex &index); - std::optional getMegolmSessionData(const MegolmSessionIndex &index); - - // - // Olm Sessions - // - void saveOlmSession(const std::string &curve25519, - mtx::crypto::OlmSessionPtr session, - uint64_t timestamp); - std::vector getOlmSessions(const std::string &curve25519); - std::optional getOlmSession(const std::string &curve25519, - const std::string &session_id); - std::optional getLatestOlmSession( - const std::string &curve25519); - - void saveOlmAccount(const std::string &pickled); - std::string restoreOlmAccount(); - - void saveBackupVersion(const OnlineBackupVersion &data); - void deleteBackupVersion(); - std::optional backupVersion(); - - void storeSecret(const std::string name, const std::string secret, bool internal = false); - void deleteSecret(const std::string name, bool internal = false); - std::optional secret(const std::string name, bool internal = false); - - std::string pickleSecret(); - - template - constexpr static bool isStateEvent_ = - std::is_same_v>, - mtx::events::StateEvent().content)>>; - - static int compare_state_key(const MDB_val *a, const MDB_val *b) - { - auto get_skey = [](const MDB_val *v) { - return nlohmann::json::parse( - std::string_view(static_cast(v->mv_data), - v->mv_size)) - .value("key", ""); - }; - - return get_skey(a).compare(get_skey(b)); - } + + return get_skey(a).compare(get_skey(b)); + } signals: - void newReadReceipts(const QString &room_id, const std::vector &event_ids); - void roomReadStatus(const std::map &status); - void removeNotification(const QString &room_id, const QString &event_id); - void userKeysUpdate(const std::string &sync_token, - const mtx::responses::QueryKeys &keyQuery); - void verificationStatusChanged(const std::string &userid); - void secretChanged(const std::string name); + void newReadReceipts(const QString &room_id, const std::vector &event_ids); + void roomReadStatus(const std::map &status); + void removeNotification(const QString &room_id, const QString &event_id); + void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); + void verificationStatusChanged(const std::string &userid); + void secretChanged(const std::string name); private: - //! Save an invited room. - void saveInvite(lmdb::txn &txn, + //! Save an invited room. + void saveInvite(lmdb::txn &txn, + lmdb::dbi &statesdb, + lmdb::dbi &membersdb, + const mtx::responses::InvitedRoom &room); + + //! Add a notification containing a user mention to the db. + void saveTimelineMentions(lmdb::txn &txn, + const std::string &room_id, + const QList &res); + + //! Get timeline items that a user was mentions in for a given room + mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn, + const std::string &room_id); + + QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); + QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); + QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); + bool getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db); + + std::optional getMember(const std::string &room_id, const std::string &user_id); + + std::string getLastEventId(lmdb::txn &txn, const std::string &room_id); + void saveTimelineMessages(lmdb::txn &txn, + lmdb::dbi &eventsDb, + const std::string &room_id, + const mtx::responses::Timeline &res); + + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional + getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id); + bool isHiddenEvent(lmdb::txn &txn, + mtx::events::collections::TimelineEvents e, + const std::string &room_id); + + //! Remove a room from the cache. + // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id); + template + void saveStateEvents(lmdb::txn &txn, + lmdb::dbi &statesdb, + lmdb::dbi &stateskeydb, + lmdb::dbi &membersdb, + lmdb::dbi &eventsDb, + const std::string &room_id, + const std::vector &events) + { + for (const auto &e : events) + saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e); + } + + template + void saveStateEvent(lmdb::txn &txn, lmdb::dbi &statesdb, + lmdb::dbi &stateskeydb, lmdb::dbi &membersdb, - const mtx::responses::InvitedRoom &room); - - //! Add a notification containing a user mention to the db. - void saveTimelineMentions(lmdb::txn &txn, - const std::string &room_id, - const QList &res); - - //! Get timeline items that a user was mentions in for a given room - mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn, - const std::string &room_id); - - QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); - QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); - QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); - bool getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db); - - std::optional getMember(const std::string &room_id, const std::string &user_id); - - std::string getLastEventId(lmdb::txn &txn, const std::string &room_id); - void saveTimelineMessages(lmdb::txn &txn, - lmdb::dbi &eventsDb, - const std::string &room_id, - const mtx::responses::Timeline &res); - - //! retrieve a specific event from account data - //! pass empty room_id for global account data - std::optional - getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id); - bool isHiddenEvent(lmdb::txn &txn, - mtx::events::collections::TimelineEvents e, - const std::string &room_id); - - //! Remove a room from the cache. - // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id); - template - void saveStateEvents(lmdb::txn &txn, - lmdb::dbi &statesdb, - lmdb::dbi &stateskeydb, - lmdb::dbi &membersdb, - lmdb::dbi &eventsDb, - const std::string &room_id, - const std::vector &events) - { - for (const auto &e : events) - saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e); + lmdb::dbi &eventsDb, + const std::string &room_id, + const T &event) + { + using namespace mtx::events; + using namespace mtx::events::state; + + if (auto e = std::get_if>(&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; + + // Lightweight representation of a member. + MemberInfo tmp{display_name, e->content.avatar_url}; + + membersdb.put(txn, e->state_key, json(tmp).dump()); + break; + } + default: { + membersdb.del(txn, e->state_key, ""); + break; + } + } + + return; + } else if (std::holds_alternative>(event)) { + setEncryptedRoom(txn, room_id); + return; } - template - void saveStateEvent(lmdb::txn &txn, - lmdb::dbi &statesdb, - lmdb::dbi &stateskeydb, - lmdb::dbi &membersdb, - lmdb::dbi &eventsDb, - const std::string &room_id, - const T &event) - { - using namespace mtx::events; - using namespace mtx::events::state; - - if (auto e = std::get_if>(&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; - - // Lightweight representation of a member. - MemberInfo tmp{display_name, e->content.avatar_url}; - - membersdb.put(txn, e->state_key, json(tmp).dump()); - break; - } - default: { - membersdb.del(txn, e->state_key, ""); - break; - } - } - - return; - } else if (std::holds_alternative>(event)) { - setEncryptedRoom(txn, room_id); - return; - } - - std::visit( - [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) { - if constexpr (isStateEvent_) { - eventsDb.put(txn, e.event_id, json(e).dump()); - - if (e.type != EventType::Unsupported) { - if (std::is_same_v< - std::remove_cv_t< - std::remove_reference_t>, - StateEvent>) { - if (e.type == EventType::RoomMember) - membersdb.del(txn, e.state_key, ""); - else if (e.state_key.empty()) - statesdb.del(txn, to_string(e.type)); - else - stateskeydb.del( - txn, - to_string(e.type), - json::object({ - {"key", e.state_key}, - {"id", e.event_id}, - }) - .dump()); - } else if (e.state_key.empty()) - statesdb.put( - txn, to_string(e.type), json(e).dump()); - else - stateskeydb.put( - txn, - to_string(e.type), - json::object({ - {"key", e.state_key}, - {"id", e.event_id}, - }) - .dump()); - } - } - }, - event); + std::visit( + [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) { + if constexpr (isStateEvent_) { + eventsDb.put(txn, e.event_id, json(e).dump()); + + if (e.type != EventType::Unsupported) { + if (std::is_same_v>, + StateEvent>) { + if (e.type == EventType::RoomMember) + membersdb.del(txn, e.state_key, ""); + else if (e.state_key.empty()) + statesdb.del(txn, to_string(e.type)); + else + stateskeydb.del(txn, + to_string(e.type), + json::object({ + {"key", e.state_key}, + {"id", e.event_id}, + }) + .dump()); + } else if (e.state_key.empty()) + statesdb.put(txn, to_string(e.type), json(e).dump()); + else + stateskeydb.put(txn, + to_string(e.type), + json::object({ + {"key", e.state_key}, + {"id", e.event_id}, + }) + .dump()); + } + } + }, + event); + } + + template + std::optional> getStateEvent(lmdb::txn &txn, + const std::string &room_id, + std::string_view state_key = "") + { + constexpr auto type = mtx::events::state_content_to_type; + static_assert(type != mtx::events::EventType::Unsupported, + "Not a supported type in state events."); + + if (room_id.empty()) + return std::nullopt; + const auto typeStr = to_string(type); + + std::string_view value; + if (state_key.empty()) { + auto db = getStatesDb(txn, room_id); + if (!db.get(txn, typeStr, value)) { + return std::nullopt; + } + } else { + auto db = getStatesKeyDb(txn, room_id); + std::string d = json::object({{"key", state_key}}).dump(); + std::string_view data = d; + std::string_view typeStrV = typeStr; + + auto cursor = lmdb::cursor::open(txn, db); + if (!cursor.get(typeStrV, data, MDB_GET_BOTH)) + return std::nullopt; + + try { + auto eventsDb = getEventsDb(txn, room_id); + if (!eventsDb.get(txn, json::parse(data)["id"].get(), value)) + return std::nullopt; + } catch (std::exception &e) { + return std::nullopt; + } } - template - std::optional> getStateEvent(lmdb::txn &txn, - const std::string &room_id, - std::string_view state_key = "") - { - constexpr auto type = mtx::events::state_content_to_type; - static_assert(type != mtx::events::EventType::Unsupported, - "Not a supported type in state events."); - - if (room_id.empty()) - return std::nullopt; - const auto typeStr = to_string(type); - - std::string_view value; - if (state_key.empty()) { - auto db = getStatesDb(txn, room_id); - if (!db.get(txn, typeStr, value)) { - return std::nullopt; - } - } else { - auto db = getStatesKeyDb(txn, room_id); - std::string d = json::object({{"key", state_key}}).dump(); - std::string_view data = d; - std::string_view typeStrV = typeStr; - - auto cursor = lmdb::cursor::open(txn, db); - if (!cursor.get(typeStrV, data, MDB_GET_BOTH)) - return std::nullopt; - - try { - auto eventsDb = getEventsDb(txn, room_id); - if (!eventsDb.get( - txn, json::parse(data)["id"].get(), value)) - return std::nullopt; - } catch (std::exception &e) { - return std::nullopt; - } - } - - try { - return json::parse(value).get>(); - } catch (std::exception &e) { - return std::nullopt; - } + try { + return json::parse(value).get>(); + } catch (std::exception &e) { + return std::nullopt; } + } - template - std::vector> getStateEventsWithType(lmdb::txn &txn, - const std::string &room_id) + template + std::vector> getStateEventsWithType(lmdb::txn &txn, + const std::string &room_id) - { - constexpr auto type = mtx::events::state_content_to_type; - static_assert(type != mtx::events::EventType::Unsupported, - "Not a supported type in state events."); - - if (room_id.empty()) - return {}; - - std::vector> events; - - { - auto db = getStatesKeyDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); - const auto typeStr = to_string(type); - std::string_view typeStrV = typeStr; - std::string_view data; - std::string_view value; - - auto cursor = lmdb::cursor::open(txn, db); - bool first = true; - if (cursor.get(typeStrV, data, MDB_SET)) { - while (cursor.get( - typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - - if (eventsDb.get(txn, - json::parse(data)["id"].get(), - value)) - events.push_back( - json::parse(value) - .get>()); - } - } - } + { + constexpr auto type = mtx::events::state_content_to_type; + static_assert(type != mtx::events::EventType::Unsupported, + "Not a supported type in state events."); - return events; - } - void saveInvites(lmdb::txn &txn, - const std::map &rooms); + if (room_id.empty()) + return {}; - void savePresence( - lmdb::txn &txn, - const std::vector> &presenceUpdates); + std::vector> events; - //! Sends signals for the rooms that are removed. - void removeLeftRooms(lmdb::txn &txn, - const std::map &rooms) { - for (const auto &room : rooms) { - removeRoom(txn, room.first); - - // Clean up leftover invites. - removeInvite(txn, room.first); + auto db = getStatesKeyDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); + const auto typeStr = to_string(type); + std::string_view typeStrV = typeStr; + std::string_view data; + std::string_view value; + + auto cursor = lmdb::cursor::open(txn, db); + bool first = true; + if (cursor.get(typeStrV, data, MDB_SET)) { + while (cursor.get(typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + + if (eventsDb.get(txn, json::parse(data)["id"].get(), value)) + events.push_back(json::parse(value).get>()); } + } } - void updateSpaces(lmdb::txn &txn, - const std::set &spaces_with_updates, - std::set rooms_with_updates); - - lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn) - { - return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); - } - - lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE); - } - - lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY); - } - - // inverse of EventOrderDb - lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE); - } - - lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE); - } - - lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY); - } - - lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY); - } - - lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT); - } - - lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE); - } - - lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE); - } - - lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE); - } - - lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id) - { - auto db = lmdb::dbi::open( - txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE | MDB_DUPSORT); - lmdb::dbi_set_dupsort(txn, db, compare_state_key); - return db; - } - - lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open( - txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE); - } - - lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE); - } - - lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id) - { - return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE); - } + return events; + } + void saveInvites(lmdb::txn &txn, + const std::map &rooms); - lmdb::dbi getPresenceDb(lmdb::txn &txn) - { - return lmdb::dbi::open(txn, "presence", MDB_CREATE); - } + void savePresence( + lmdb::txn &txn, + const std::vector> &presenceUpdates); - lmdb::dbi getUserKeysDb(lmdb::txn &txn) - { - return lmdb::dbi::open(txn, "user_key", MDB_CREATE); - } + //! Sends signals for the rooms that are removed. + void removeLeftRooms(lmdb::txn &txn, + const std::map &rooms) + { + for (const auto &room : rooms) { + removeRoom(txn, room.first); - lmdb::dbi getVerificationDb(lmdb::txn &txn) - { - return lmdb::dbi::open(txn, "verified", MDB_CREATE); - } - - //! Retrieves or creates the database that stores the open OLM sessions between our device - //! and the given curve25519 key which represents another device. - //! - //! Each entry is a map from the session_id to the pickled representation of the session. - lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key) - { - return lmdb::dbi::open( - txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE); + // Clean up leftover invites. + removeInvite(txn, room.first); } - - QString getDisplayName(const mtx::events::StateEvent &event) - { - if (!event.content.display_name.empty()) - return QString::fromStdString(event.content.display_name); - - return QString::fromStdString(event.state_key); - } - - std::optional verificationCache(const std::string &user_id, - lmdb::txn &txn); - VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn); - std::optional userKeys_(const std::string &user_id, lmdb::txn &txn); - - void setNextBatchToken(lmdb::txn &txn, const std::string &token); - - lmdb::env env_; - lmdb::dbi syncStateDb_; - lmdb::dbi roomsDb_; - lmdb::dbi spacesChildrenDb_, spacesParentsDb_; - lmdb::dbi invitesDb_; - lmdb::dbi readReceiptsDb_; - lmdb::dbi notificationsDb_; - - lmdb::dbi devicesDb_; - lmdb::dbi deviceKeysDb_; - - lmdb::dbi inboundMegolmSessionDb_; - lmdb::dbi outboundMegolmSessionDb_; - lmdb::dbi megolmSessionDataDb_; - - lmdb::dbi encryptedRooms_; - - QString localUserId_; - QString cacheDirectory_; - - std::string pickle_secret_; - - VerificationStorage verification_storage; - - bool databaseReady_ = false; + } + + void updateSpaces(lmdb::txn &txn, + const std::set &spaces_with_updates, + std::set rooms_with_updates); + + lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn) + { + return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); + } + + lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE); + } + + lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open( + txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY); + } + + // inverse of EventOrderDb + lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE); + } + + lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE); + } + + lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open( + txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY); + } + + lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open( + txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY); + } + + lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open( + txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT); + } + + lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE); + } + + lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE); + } + + lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE); + } + + lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id) + { + auto db = lmdb::dbi::open( + txn, std::string(room_id + "/state_by_key").c_str(), MDB_CREATE | MDB_DUPSORT); + lmdb::dbi_set_dupsort(txn, db, compare_state_key); + return db; + } + + lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE); + } + + lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE); + } + + lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE); + } + + lmdb::dbi getPresenceDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "presence", MDB_CREATE); } + + lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); } + + lmdb::dbi getVerificationDb(lmdb::txn &txn) + { + return lmdb::dbi::open(txn, "verified", MDB_CREATE); + } + + //! Retrieves or creates the database that stores the open OLM sessions between our device + //! and the given curve25519 key which represents another device. + //! + //! Each entry is a map from the session_id to the pickled representation of the session. + lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key) + { + return lmdb::dbi::open( + txn, std::string("olm_sessions.v2/" + curve25519_key).c_str(), MDB_CREATE); + } + + QString getDisplayName(const mtx::events::StateEvent &event) + { + if (!event.content.display_name.empty()) + return QString::fromStdString(event.content.display_name); + + return QString::fromStdString(event.state_key); + } + + std::optional verificationCache(const std::string &user_id, lmdb::txn &txn); + VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn); + std::optional userKeys_(const std::string &user_id, lmdb::txn &txn); + + void setNextBatchToken(lmdb::txn &txn, const std::string &token); + + lmdb::env env_; + lmdb::dbi syncStateDb_; + lmdb::dbi roomsDb_; + lmdb::dbi spacesChildrenDb_, spacesParentsDb_; + lmdb::dbi invitesDb_; + lmdb::dbi readReceiptsDb_; + lmdb::dbi notificationsDb_; + + lmdb::dbi devicesDb_; + lmdb::dbi deviceKeysDb_; + + lmdb::dbi inboundMegolmSessionDb_; + lmdb::dbi outboundMegolmSessionDb_; + lmdb::dbi megolmSessionDataDb_; + + lmdb::dbi encryptedRooms_; + + QString localUserId_; + QString cacheDirectory_; + + std::string pickle_secret_; + + VerificationStorage verification_storage; + + bool databaseReady_ = false; }; namespace cache { diff --git a/src/CallDevices.cpp b/src/CallDevices.cpp index 825d2f72..be185470 100644 --- a/src/CallDevices.cpp +++ b/src/CallDevices.cpp @@ -27,20 +27,20 @@ namespace { struct AudioSource { - std::string name; - GstDevice *device; + std::string name; + GstDevice *device; }; struct VideoSource { - struct Caps - { - std::string resolution; - std::vector frameRates; - }; - std::string name; - GstDevice *device; - std::vector caps; + struct Caps + { + std::string resolution; + std::vector frameRates; + }; + std::string name; + GstDevice *device; + std::vector caps; }; std::vector audioSources_; @@ -50,315 +50,304 @@ using FrameRate = std::pair; std::optional getFrameRate(const GValue *value) { - if (GST_VALUE_HOLDS_FRACTION(value)) { - gint num = gst_value_get_fraction_numerator(value); - gint den = gst_value_get_fraction_denominator(value); - return FrameRate{num, den}; - } - return std::nullopt; + if (GST_VALUE_HOLDS_FRACTION(value)) { + gint num = gst_value_get_fraction_numerator(value); + gint den = gst_value_get_fraction_denominator(value); + return FrameRate{num, den}; + } + return std::nullopt; } void addFrameRate(std::vector &rates, const FrameRate &rate) { - constexpr double minimumFrameRate = 15.0; - if (static_cast(rate.first) / rate.second >= minimumFrameRate) - rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second)); + constexpr double minimumFrameRate = 15.0; + if (static_cast(rate.first) / rate.second >= minimumFrameRate) + rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second)); } void setDefaultDevice(bool isVideo) { - auto settings = ChatPage::instance()->userSettings(); - if (isVideo && settings->camera().isEmpty()) { - const VideoSource &camera = videoSources_.front(); - settings->setCamera(QString::fromStdString(camera.name)); - settings->setCameraResolution( - QString::fromStdString(camera.caps.front().resolution)); - settings->setCameraFrameRate( - QString::fromStdString(camera.caps.front().frameRates.front())); - } else if (!isVideo && settings->microphone().isEmpty()) { - settings->setMicrophone(QString::fromStdString(audioSources_.front().name)); - } + auto settings = ChatPage::instance()->userSettings(); + if (isVideo && settings->camera().isEmpty()) { + const VideoSource &camera = videoSources_.front(); + settings->setCamera(QString::fromStdString(camera.name)); + settings->setCameraResolution(QString::fromStdString(camera.caps.front().resolution)); + settings->setCameraFrameRate( + QString::fromStdString(camera.caps.front().frameRates.front())); + } else if (!isVideo && settings->microphone().isEmpty()) { + settings->setMicrophone(QString::fromStdString(audioSources_.front().name)); + } } void addDevice(GstDevice *device) { - if (!device) - return; - - gchar *name = gst_device_get_display_name(device); - gchar *type = gst_device_get_device_class(device); - bool isVideo = !std::strncmp(type, "Video", 5); - g_free(type); - nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name); - if (!isVideo) { - audioSources_.push_back({name, device}); - g_free(name); - setDefaultDevice(false); - return; - } - - GstCaps *gstcaps = gst_device_get_caps(device); - if (!gstcaps) { - nhlog::ui()->debug("WebRTC: unable to get caps for {}", name); - g_free(name); - return; - } + if (!device) + return; + + gchar *name = gst_device_get_display_name(device); + gchar *type = gst_device_get_device_class(device); + bool isVideo = !std::strncmp(type, "Video", 5); + g_free(type); + nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name); + if (!isVideo) { + audioSources_.push_back({name, device}); + g_free(name); + setDefaultDevice(false); + return; + } - VideoSource source{name, device, {}}; + GstCaps *gstcaps = gst_device_get_caps(device); + if (!gstcaps) { + nhlog::ui()->debug("WebRTC: unable to get caps for {}", name); g_free(name); - guint nCaps = gst_caps_get_size(gstcaps); - for (guint i = 0; i < nCaps; ++i) { - GstStructure *structure = gst_caps_get_structure(gstcaps, i); - const gchar *struct_name = gst_structure_get_name(structure); - if (!std::strcmp(struct_name, "video/x-raw")) { - gint widthpx, heightpx; - if (gst_structure_get(structure, - "width", - G_TYPE_INT, - &widthpx, - "height", - G_TYPE_INT, - &heightpx, - nullptr)) { - VideoSource::Caps caps; - caps.resolution = - std::to_string(widthpx) + "x" + std::to_string(heightpx); - const GValue *value = - gst_structure_get_value(structure, "framerate"); - if (auto fr = getFrameRate(value); fr) - addFrameRate(caps.frameRates, *fr); - else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) { - addFrameRate( - caps.frameRates, - *getFrameRate(gst_value_get_fraction_range_min(value))); - addFrameRate( - caps.frameRates, - *getFrameRate(gst_value_get_fraction_range_max(value))); - } else if (GST_VALUE_HOLDS_LIST(value)) { - guint nRates = gst_value_list_get_size(value); - for (guint j = 0; j < nRates; ++j) { - const GValue *rate = - gst_value_list_get_value(value, j); - if (auto frate = getFrameRate(rate); frate) - addFrameRate(caps.frameRates, *frate); - } - } - if (!caps.frameRates.empty()) - source.caps.push_back(std::move(caps)); - } + return; + } + + VideoSource source{name, device, {}}; + g_free(name); + guint nCaps = gst_caps_get_size(gstcaps); + for (guint i = 0; i < nCaps; ++i) { + GstStructure *structure = gst_caps_get_structure(gstcaps, i); + const gchar *struct_name = gst_structure_get_name(structure); + if (!std::strcmp(struct_name, "video/x-raw")) { + gint widthpx, heightpx; + if (gst_structure_get(structure, + "width", + G_TYPE_INT, + &widthpx, + "height", + G_TYPE_INT, + &heightpx, + nullptr)) { + VideoSource::Caps caps; + caps.resolution = std::to_string(widthpx) + "x" + std::to_string(heightpx); + const GValue *value = gst_structure_get_value(structure, "framerate"); + if (auto fr = getFrameRate(value); fr) + addFrameRate(caps.frameRates, *fr); + else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) { + addFrameRate(caps.frameRates, + *getFrameRate(gst_value_get_fraction_range_min(value))); + addFrameRate(caps.frameRates, + *getFrameRate(gst_value_get_fraction_range_max(value))); + } else if (GST_VALUE_HOLDS_LIST(value)) { + guint nRates = gst_value_list_get_size(value); + for (guint j = 0; j < nRates; ++j) { + const GValue *rate = gst_value_list_get_value(value, j); + if (auto frate = getFrameRate(rate); frate) + addFrameRate(caps.frameRates, *frate); + } } + if (!caps.frameRates.empty()) + source.caps.push_back(std::move(caps)); + } } - gst_caps_unref(gstcaps); - videoSources_.push_back(std::move(source)); - setDefaultDevice(true); + } + gst_caps_unref(gstcaps); + videoSources_.push_back(std::move(source)); + setDefaultDevice(true); } template bool removeDevice(T &sources, GstDevice *device, bool changed) { - if (auto it = std::find_if(sources.begin(), - sources.end(), - [device](const auto &s) { return s.device == device; }); - it != sources.end()) { - nhlog::ui()->debug(std::string("WebRTC: device ") + - (changed ? "changed: " : "removed: ") + "{}", - it->name); - gst_object_unref(device); - sources.erase(it); - return true; - } - return false; + if (auto it = std::find_if( + sources.begin(), sources.end(), [device](const auto &s) { return s.device == device; }); + it != sources.end()) { + nhlog::ui()->debug( + std::string("WebRTC: device ") + (changed ? "changed: " : "removed: ") + "{}", it->name); + gst_object_unref(device); + sources.erase(it); + return true; + } + return false; } void removeDevice(GstDevice *device, bool changed) { - if (device) { - if (removeDevice(audioSources_, device, changed) || - removeDevice(videoSources_, device, changed)) - return; - } + if (device) { + if (removeDevice(audioSources_, device, changed) || + removeDevice(videoSources_, device, changed)) + return; + } } gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data G_GNUC_UNUSED) { - switch (GST_MESSAGE_TYPE(msg)) { - case GST_MESSAGE_DEVICE_ADDED: { - GstDevice *device; - gst_message_parse_device_added(msg, &device); - addDevice(device); - emit CallDevices::instance().devicesChanged(); - break; - } - case GST_MESSAGE_DEVICE_REMOVED: { - GstDevice *device; - gst_message_parse_device_removed(msg, &device); - removeDevice(device, false); - emit CallDevices::instance().devicesChanged(); - break; - } - case GST_MESSAGE_DEVICE_CHANGED: { - GstDevice *device; - GstDevice *oldDevice; - gst_message_parse_device_changed(msg, &device, &oldDevice); - removeDevice(oldDevice, true); - addDevice(device); - break; - } - default: - break; - } - return TRUE; + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_DEVICE_ADDED: { + GstDevice *device; + gst_message_parse_device_added(msg, &device); + addDevice(device); + emit CallDevices::instance().devicesChanged(); + break; + } + case GST_MESSAGE_DEVICE_REMOVED: { + GstDevice *device; + gst_message_parse_device_removed(msg, &device); + removeDevice(device, false); + emit CallDevices::instance().devicesChanged(); + break; + } + case GST_MESSAGE_DEVICE_CHANGED: { + GstDevice *device; + GstDevice *oldDevice; + gst_message_parse_device_changed(msg, &device, &oldDevice); + removeDevice(oldDevice, true); + addDevice(device); + break; + } + default: + break; + } + return TRUE; } template std::vector deviceNames(T &sources, const std::string &defaultDevice) { - std::vector ret; - ret.reserve(sources.size()); - for (const auto &s : sources) - ret.push_back(s.name); + std::vector ret; + ret.reserve(sources.size()); + for (const auto &s : sources) + ret.push_back(s.name); - // move default device to top of the list - if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end()) - std::swap(ret.front(), *it); + // move default device to top of the list + if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end()) + std::swap(ret.front(), *it); - return ret; + return ret; } std::optional getVideoSource(const std::string &cameraName) { - if (auto it = std::find_if(videoSources_.cbegin(), - videoSources_.cend(), - [&cameraName](const auto &s) { return s.name == cameraName; }); - it != videoSources_.cend()) { - return *it; - } - return std::nullopt; + if (auto it = std::find_if(videoSources_.cbegin(), + videoSources_.cend(), + [&cameraName](const auto &s) { return s.name == cameraName; }); + it != videoSources_.cend()) { + return *it; + } + return std::nullopt; } std::pair tokenise(std::string_view str, char delim) { - std::pair ret; - ret.first = std::atoi(str.data()); - auto pos = str.find_first_of(delim); - ret.second = std::atoi(str.data() + pos + 1); - return ret; + std::pair ret; + ret.first = std::atoi(str.data()); + auto pos = str.find_first_of(delim); + ret.second = std::atoi(str.data() + pos + 1); + return ret; } } void CallDevices::init() { - static GstDeviceMonitor *monitor = nullptr; - if (!monitor) { - monitor = gst_device_monitor_new(); - GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); - gst_device_monitor_add_filter(monitor, "Audio/Source", caps); - gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps); - gst_caps_unref(caps); - caps = gst_caps_new_empty_simple("video/x-raw"); - gst_device_monitor_add_filter(monitor, "Video/Source", caps); - gst_device_monitor_add_filter(monitor, "Video/Duplex", caps); - gst_caps_unref(caps); - - GstBus *bus = gst_device_monitor_get_bus(monitor); - gst_bus_add_watch(bus, newBusMessage, nullptr); - gst_object_unref(bus); - if (!gst_device_monitor_start(monitor)) { - nhlog::ui()->error("WebRTC: failed to start device monitor"); - return; - } + static GstDeviceMonitor *monitor = nullptr; + if (!monitor) { + monitor = gst_device_monitor_new(); + GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw"); + gst_device_monitor_add_filter(monitor, "Audio/Source", caps); + gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps); + gst_caps_unref(caps); + caps = gst_caps_new_empty_simple("video/x-raw"); + gst_device_monitor_add_filter(monitor, "Video/Source", caps); + gst_device_monitor_add_filter(monitor, "Video/Duplex", caps); + gst_caps_unref(caps); + + GstBus *bus = gst_device_monitor_get_bus(monitor); + gst_bus_add_watch(bus, newBusMessage, nullptr); + gst_object_unref(bus); + if (!gst_device_monitor_start(monitor)) { + nhlog::ui()->error("WebRTC: failed to start device monitor"); + return; } + } } bool CallDevices::haveMic() const { - return !audioSources_.empty(); + return !audioSources_.empty(); } bool CallDevices::haveCamera() const { - return !videoSources_.empty(); + return !videoSources_.empty(); } std::vector CallDevices::names(bool isVideo, const std::string &defaultDevice) const { - return isVideo ? deviceNames(videoSources_, defaultDevice) - : deviceNames(audioSources_, defaultDevice); + return isVideo ? deviceNames(videoSources_, defaultDevice) + : deviceNames(audioSources_, defaultDevice); } std::vector CallDevices::resolutions(const std::string &cameraName) const { - std::vector ret; - if (auto s = getVideoSource(cameraName); s) { - ret.reserve(s->caps.size()); - for (const auto &c : s->caps) - ret.push_back(c.resolution); - } - return ret; + std::vector ret; + if (auto s = getVideoSource(cameraName); s) { + ret.reserve(s->caps.size()); + for (const auto &c : s->caps) + ret.push_back(c.resolution); + } + return ret; } std::vector CallDevices::frameRates(const std::string &cameraName, const std::string &resolution) const { - if (auto s = getVideoSource(cameraName); s) { - if (auto it = - std::find_if(s->caps.cbegin(), + if (auto s = getVideoSource(cameraName); s) { + if (auto it = std::find_if(s->caps.cbegin(), s->caps.cend(), [&](const auto &c) { return c.resolution == resolution; }); - it != s->caps.cend()) - return it->frameRates; - } - return {}; + it != s->caps.cend()) + return it->frameRates; + } + return {}; } GstDevice * CallDevices::audioDevice() const { - std::string name = ChatPage::instance()->userSettings()->microphone().toStdString(); - if (auto it = std::find_if(audioSources_.cbegin(), - audioSources_.cend(), - [&name](const auto &s) { return s.name == name; }); - it != audioSources_.cend()) { - nhlog::ui()->debug("WebRTC: microphone: {}", name); - return it->device; - } else { - nhlog::ui()->error("WebRTC: unknown microphone: {}", name); - return nullptr; - } + std::string name = ChatPage::instance()->userSettings()->microphone().toStdString(); + if (auto it = std::find_if(audioSources_.cbegin(), + audioSources_.cend(), + [&name](const auto &s) { return s.name == name; }); + it != audioSources_.cend()) { + nhlog::ui()->debug("WebRTC: microphone: {}", name); + return it->device; + } else { + nhlog::ui()->error("WebRTC: unknown microphone: {}", name); + return nullptr; + } } GstDevice * CallDevices::videoDevice(std::pair &resolution, std::pair &frameRate) const { - auto settings = ChatPage::instance()->userSettings(); - std::string name = settings->camera().toStdString(); - if (auto s = getVideoSource(name); s) { - nhlog::ui()->debug("WebRTC: camera: {}", name); - resolution = tokenise(settings->cameraResolution().toStdString(), 'x'); - frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/'); - nhlog::ui()->debug( - "WebRTC: camera resolution: {}x{}", resolution.first, resolution.second); - nhlog::ui()->debug( - "WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second); - return s->device; - } else { - nhlog::ui()->error("WebRTC: unknown camera: {}", name); - return nullptr; - } + auto settings = ChatPage::instance()->userSettings(); + std::string name = settings->camera().toStdString(); + if (auto s = getVideoSource(name); s) { + nhlog::ui()->debug("WebRTC: camera: {}", name); + resolution = tokenise(settings->cameraResolution().toStdString(), 'x'); + frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/'); + nhlog::ui()->debug("WebRTC: camera resolution: {}x{}", resolution.first, resolution.second); + nhlog::ui()->debug("WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second); + return s->device; + } else { + nhlog::ui()->error("WebRTC: unknown camera: {}", name); + return nullptr; + } } #else @@ -366,31 +355,31 @@ CallDevices::videoDevice(std::pair &resolution, std::pair &f bool CallDevices::haveMic() const { - return false; + return false; } bool CallDevices::haveCamera() const { - return false; + return false; } std::vector CallDevices::names(bool, const std::string &) const { - return {}; + return {}; } std::vector CallDevices::resolutions(const std::string &) const { - return {}; + return {}; } std::vector CallDevices::frameRates(const std::string &, const std::string &) const { - return {}; + return {}; } #endif diff --git a/src/CallDevices.h b/src/CallDevices.h index 69325f97..d30ce644 100644 --- a/src/CallDevices.h +++ b/src/CallDevices.h @@ -14,35 +14,34 @@ typedef struct _GstDevice GstDevice; class CallDevices : public QObject { - Q_OBJECT + Q_OBJECT public: - static CallDevices &instance() - { - static CallDevices instance; - return instance; - } - - bool haveMic() const; - bool haveCamera() const; - std::vector names(bool isVideo, const std::string &defaultDevice) const; - std::vector resolutions(const std::string &cameraName) const; - std::vector frameRates(const std::string &cameraName, - const std::string &resolution) const; + static CallDevices &instance() + { + static CallDevices instance; + return instance; + } + + bool haveMic() const; + bool haveCamera() const; + std::vector names(bool isVideo, const std::string &defaultDevice) const; + std::vector resolutions(const std::string &cameraName) const; + std::vector frameRates(const std::string &cameraName, + const std::string &resolution) const; signals: - void devicesChanged(); + void devicesChanged(); private: - CallDevices(); + CallDevices(); - friend class WebRTCSession; - void init(); - GstDevice *audioDevice() const; - GstDevice *videoDevice(std::pair &resolution, - std::pair &frameRate) const; + friend class WebRTCSession; + void init(); + GstDevice *audioDevice() const; + GstDevice *videoDevice(std::pair &resolution, std::pair &frameRate) const; public: - CallDevices(CallDevices const &) = delete; - void operator=(CallDevices const &) = delete; + CallDevices(CallDevices const &) = delete; + void operator=(CallDevices const &) = delete; }; diff --git a/src/CallManager.cpp b/src/CallManager.cpp index 601c9d6b..0f701b0d 100644 --- a/src/CallManager.cpp +++ b/src/CallManager.cpp @@ -54,206 +54,197 @@ CallManager::CallManager(QObject *parent) , session_(WebRTCSession::instance()) , turnServerTimer_(this) { - qRegisterMetaType>(); - qRegisterMetaType(); - qRegisterMetaType(); - - connect( - &session_, - &WebRTCSession::offerCreated, - this, - [this](const std::string &sdp, const std::vector &candidates) { - nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_); - emit newMessage(roomid_, CallInvite{callid_, sdp, "0", timeoutms_}); - emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); - std::string callid(callid_); - QTimer::singleShot(timeoutms_, this, [this, callid]() { - if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) { - hangUp(CallHangUp::Reason::InviteTimeOut); - emit ChatPage::instance()->showNotification( - "The remote side failed to pick up."); - } - }); + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + + connect( + &session_, + &WebRTCSession::offerCreated, + this, + [this](const std::string &sdp, const std::vector &candidates) { + nhlog::ui()->debug("WebRTC: call id: {} - sending offer", callid_); + emit newMessage(roomid_, CallInvite{callid_, sdp, "0", timeoutms_}); + emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); + std::string callid(callid_); + QTimer::singleShot(timeoutms_, this, [this, callid]() { + if (session_.state() == webrtc::State::OFFERSENT && callid == callid_) { + hangUp(CallHangUp::Reason::InviteTimeOut); + emit ChatPage::instance()->showNotification("The remote side failed to pick up."); + } }); + }); + + connect( + &session_, + &WebRTCSession::answerCreated, + this, + [this](const std::string &sdp, const std::vector &candidates) { + nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_); + emit newMessage(roomid_, CallAnswer{callid_, sdp, "0"}); + emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); + }); + + connect(&session_, + &WebRTCSession::newICECandidate, + this, + [this](const CallCandidates::Candidate &candidate) { + nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_); + emit newMessage(roomid_, CallCandidates{callid_, {candidate}, "0"}); + }); + + connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer); + + connect( + this, &CallManager::turnServerRetrieved, this, [this](const mtx::responses::TurnServer &res) { + nhlog::net()->info("TURN server(s) retrieved from homeserver:"); + nhlog::net()->info("username: {}", res.username); + nhlog::net()->info("ttl: {} seconds", res.ttl); + for (const auto &u : res.uris) + nhlog::net()->info("uri: {}", u); + + // Request new credentials close to expiry + // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 + turnURIs_ = getTurnURIs(res); + uint32_t ttl = std::max(res.ttl, UINT32_C(3600)); + if (res.ttl < 3600) + nhlog::net()->warn("Setting ttl to 1 hour"); + turnServerTimer_.setInterval(ttl * 1000 * 0.9); + }); + + connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) { + switch (state) { + case webrtc::State::DISCONNECTED: + playRingtone(QUrl("qrc:/media/media/callend.ogg"), false); + clear(); + break; + case webrtc::State::ICEFAILED: { + QString error("Call connection failed."); + if (turnURIs_.empty()) + error += " Your homeserver has no configured TURN server."; + emit ChatPage::instance()->showNotification(error); + hangUp(CallHangUp::Reason::ICEFailed); + break; + } + default: + break; + } + emit newCallState(); + }); - connect( - &session_, - &WebRTCSession::answerCreated, - this, - [this](const std::string &sdp, const std::vector &candidates) { - nhlog::ui()->debug("WebRTC: call id: {} - sending answer", callid_); - emit newMessage(roomid_, CallAnswer{callid_, sdp, "0"}); - emit newMessage(roomid_, CallCandidates{callid_, candidates, "0"}); - }); + connect( + &CallDevices::instance(), &CallDevices::devicesChanged, this, &CallManager::devicesChanged); - connect(&session_, - &WebRTCSession::newICECandidate, - this, - [this](const CallCandidates::Candidate &candidate) { - nhlog::ui()->debug("WebRTC: call id: {} - sending ice candidate", callid_); - emit newMessage(roomid_, CallCandidates{callid_, {candidate}, "0"}); - }); - - connect(&turnServerTimer_, &QTimer::timeout, this, &CallManager::retrieveTurnServer); - - connect(this, - &CallManager::turnServerRetrieved, - this, - [this](const mtx::responses::TurnServer &res) { - nhlog::net()->info("TURN server(s) retrieved from homeserver:"); - nhlog::net()->info("username: {}", res.username); - nhlog::net()->info("ttl: {} seconds", res.ttl); - for (const auto &u : res.uris) - nhlog::net()->info("uri: {}", u); - - // Request new credentials close to expiry - // See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - turnURIs_ = getTurnURIs(res); - uint32_t ttl = std::max(res.ttl, UINT32_C(3600)); - if (res.ttl < 3600) - nhlog::net()->warn("Setting ttl to 1 hour"); - turnServerTimer_.setInterval(ttl * 1000 * 0.9); - }); - - connect(&session_, &WebRTCSession::stateChanged, this, [this](webrtc::State state) { - switch (state) { - case webrtc::State::DISCONNECTED: - playRingtone(QUrl("qrc:/media/media/callend.ogg"), false); - clear(); - break; - case webrtc::State::ICEFAILED: { - QString error("Call connection failed."); - if (turnURIs_.empty()) - error += " Your homeserver has no configured TURN server."; - emit ChatPage::instance()->showNotification(error); - hangUp(CallHangUp::Reason::ICEFailed); - break; - } + connect( + &player_, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status) { + if (status == QMediaPlayer::LoadedMedia) + player_.play(); + }); + + connect(&player_, + QOverload::of(&QMediaPlayer::error), + [this](QMediaPlayer::Error error) { + stopRingtone(); + switch (error) { + case QMediaPlayer::FormatError: + case QMediaPlayer::ResourceError: + nhlog::ui()->error("WebRTC: valid ringtone file not found"); + break; + case QMediaPlayer::AccessDeniedError: + nhlog::ui()->error("WebRTC: access to ringtone file denied"); + break; default: - break; + nhlog::ui()->error("WebRTC: unable to play ringtone"); + break; } - emit newCallState(); - }); - - connect(&CallDevices::instance(), - &CallDevices::devicesChanged, - this, - &CallManager::devicesChanged); - - connect(&player_, - &QMediaPlayer::mediaStatusChanged, - this, - [this](QMediaPlayer::MediaStatus status) { - if (status == QMediaPlayer::LoadedMedia) - player_.play(); - }); - - connect(&player_, - QOverload::of(&QMediaPlayer::error), - [this](QMediaPlayer::Error error) { - stopRingtone(); - switch (error) { - case QMediaPlayer::FormatError: - case QMediaPlayer::ResourceError: - nhlog::ui()->error("WebRTC: valid ringtone file not found"); - break; - case QMediaPlayer::AccessDeniedError: - nhlog::ui()->error("WebRTC: access to ringtone file denied"); - break; - default: - nhlog::ui()->error("WebRTC: unable to play ringtone"); - break; - } - }); + }); } void CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int windowIndex) { - if (isOnCall()) - return; - if (callType == CallType::SCREEN) { - if (!screenShareSupported()) - return; - if (windows_.empty() || windowIndex >= windows_.size()) { - nhlog::ui()->error("WebRTC: window index out of range"); - return; - } - } - - auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); - if (roomInfo.member_count != 2) { - emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms."); - return; - } - - std::string errorMessage; - if (!session_.havePlugins(false, &errorMessage) || - ((callType == CallType::VIDEO || callType == CallType::SCREEN) && - !session_.havePlugins(true, &errorMessage))) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - return; - } - - callType_ = callType; - roomid_ = roomid; - session_.setTurnServers(turnURIs_); - generateCallID(); - std::string strCallType = callType_ == CallType::VOICE - ? "voice" - : (callType_ == CallType::VIDEO ? "video" : "screen"); - nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType); - std::vector members(cache::getMembers(roomid.toStdString())); - const RoomMember &callee = - members.front().user_id == utils::localUser() ? members.back() : members.front(); - callParty_ = callee.user_id; - callPartyDisplayName_ = - callee.display_name.isEmpty() ? callee.user_id : callee.display_name; - callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); - emit newInviteState(); - playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true); - if (!session_.createOffer( - callType, callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) { - emit ChatPage::instance()->showNotification("Problem setting up call."); - endCall(); + if (isOnCall()) + return; + if (callType == CallType::SCREEN) { + if (!screenShareSupported()) + return; + if (windows_.empty() || windowIndex >= windows_.size()) { + nhlog::ui()->error("WebRTC: window index out of range"); + return; } + } + + auto roomInfo = cache::singleRoomInfo(roomid.toStdString()); + if (roomInfo.member_count != 2) { + emit ChatPage::instance()->showNotification("Calls are limited to 1:1 rooms."); + return; + } + + std::string errorMessage; + if (!session_.havePlugins(false, &errorMessage) || + ((callType == CallType::VIDEO || callType == CallType::SCREEN) && + !session_.havePlugins(true, &errorMessage))) { + emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); + return; + } + + callType_ = callType; + roomid_ = roomid; + session_.setTurnServers(turnURIs_); + generateCallID(); + std::string strCallType = + callType_ == CallType::VOICE ? "voice" : (callType_ == CallType::VIDEO ? "video" : "screen"); + nhlog::ui()->debug("WebRTC: call id: {} - creating {} invite", callid_, strCallType); + std::vector members(cache::getMembers(roomid.toStdString())); + const RoomMember &callee = + members.front().user_id == utils::localUser() ? members.back() : members.front(); + callParty_ = callee.user_id; + callPartyDisplayName_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + emit newInviteState(); + playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true); + if (!session_.createOffer(callType, + callType == CallType::SCREEN ? windows_[windowIndex].second : 0)) { + emit ChatPage::instance()->showNotification("Problem setting up call."); + endCall(); + } } namespace { std::string callHangUpReasonString(CallHangUp::Reason reason) { - switch (reason) { - case CallHangUp::Reason::ICEFailed: - return "ICE failed"; - case CallHangUp::Reason::InviteTimeOut: - return "Invite time out"; - default: - return "User"; - } + switch (reason) { + case CallHangUp::Reason::ICEFailed: + return "ICE failed"; + case CallHangUp::Reason::InviteTimeOut: + return "Invite time out"; + default: + return "User"; + } } } void CallManager::hangUp(CallHangUp::Reason reason) { - if (!callid_.empty()) { - nhlog::ui()->debug( - "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason)); - emit newMessage(roomid_, CallHangUp{callid_, "0", reason}); - endCall(); - } + if (!callid_.empty()) { + nhlog::ui()->debug( + "WebRTC: call id: {} - hanging up ({})", callid_, callHangUpReasonString(reason)); + emit newMessage(roomid_, CallHangUp{callid_, "0", reason}); + endCall(); + } } void CallManager::syncEvent(const mtx::events::collections::TimelineEvents &event) { #ifdef GSTREAMER_AVAILABLE - if (handleEvent(event) || handleEvent(event) || - handleEvent(event) || handleEvent(event)) - return; + if (handleEvent(event) || handleEvent(event) || + handleEvent(event) || handleEvent(event)) + return; #else - (void)event; + (void)event; #endif } @@ -261,325 +252,321 @@ template bool CallManager::handleEvent(const mtx::events::collections::TimelineEvents &event) { - if (std::holds_alternative>(event)) { - handleEvent(std::get>(event)); - return true; - } - return false; + if (std::holds_alternative>(event)) { + handleEvent(std::get>(event)); + return true; + } + return false; } void CallManager::handleEvent(const RoomEvent &callInviteEvent) { - const char video[] = "m=video"; - const std::string &sdp = callInviteEvent.content.sdp; - bool isVideo = std::search(sdp.cbegin(), - sdp.cend(), - std::cbegin(video), - std::cend(video) - 1, - [](unsigned char c1, unsigned char c2) { - return std::tolower(c1) == std::tolower(c2); - }) != sdp.cend(); - - nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}", - callInviteEvent.content.call_id, - (isVideo ? "video" : "voice"), - callInviteEvent.sender); - - if (callInviteEvent.content.call_id.empty()) - return; - - auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id); - if (isOnCall() || roomInfo.member_count != 2) { - emit newMessage(QString::fromStdString(callInviteEvent.room_id), - CallHangUp{callInviteEvent.content.call_id, - "0", - CallHangUp::Reason::InviteTimeOut}); - return; - } - - const QString &ringtone = ChatPage::instance()->userSettings()->ringtone(); - if (ringtone != "Mute") - playRingtone(ringtone == "Default" ? QUrl("qrc:/media/media/ring.ogg") - : QUrl::fromLocalFile(ringtone), - true); - roomid_ = QString::fromStdString(callInviteEvent.room_id); - callid_ = callInviteEvent.content.call_id; - remoteICECandidates_.clear(); - - std::vector members(cache::getMembers(callInviteEvent.room_id)); - const RoomMember &caller = - members.front().user_id == utils::localUser() ? members.back() : members.front(); - callParty_ = caller.user_id; - callPartyDisplayName_ = - caller.display_name.isEmpty() ? caller.user_id : caller.display_name; - callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); - - haveCallInvite_ = true; - callType_ = isVideo ? CallType::VIDEO : CallType::VOICE; - inviteSDP_ = callInviteEvent.content.sdp; - emit newInviteState(); + const char video[] = "m=video"; + const std::string &sdp = callInviteEvent.content.sdp; + bool isVideo = std::search(sdp.cbegin(), + sdp.cend(), + std::cbegin(video), + std::cend(video) - 1, + [](unsigned char c1, unsigned char c2) { + return std::tolower(c1) == std::tolower(c2); + }) != sdp.cend(); + + nhlog::ui()->debug("WebRTC: call id: {} - incoming {} CallInvite from {}", + callInviteEvent.content.call_id, + (isVideo ? "video" : "voice"), + callInviteEvent.sender); + + if (callInviteEvent.content.call_id.empty()) + return; + + auto roomInfo = cache::singleRoomInfo(callInviteEvent.room_id); + if (isOnCall() || roomInfo.member_count != 2) { + emit newMessage( + QString::fromStdString(callInviteEvent.room_id), + CallHangUp{callInviteEvent.content.call_id, "0", CallHangUp::Reason::InviteTimeOut}); + return; + } + + const QString &ringtone = ChatPage::instance()->userSettings()->ringtone(); + if (ringtone != "Mute") + playRingtone(ringtone == "Default" ? QUrl("qrc:/media/media/ring.ogg") + : QUrl::fromLocalFile(ringtone), + true); + roomid_ = QString::fromStdString(callInviteEvent.room_id); + callid_ = callInviteEvent.content.call_id; + remoteICECandidates_.clear(); + + std::vector members(cache::getMembers(callInviteEvent.room_id)); + const RoomMember &caller = + members.front().user_id == utils::localUser() ? members.back() : members.front(); + callParty_ = caller.user_id; + callPartyDisplayName_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name; + callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url); + + haveCallInvite_ = true; + callType_ = isVideo ? CallType::VIDEO : CallType::VOICE; + inviteSDP_ = callInviteEvent.content.sdp; + emit newInviteState(); } void CallManager::acceptInvite() { - if (!haveCallInvite_) - return; - - stopRingtone(); - std::string errorMessage; - if (!session_.havePlugins(false, &errorMessage) || - (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) { - emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); - hangUp(); - return; - } - - session_.setTurnServers(turnURIs_); - if (!session_.acceptOffer(inviteSDP_)) { - emit ChatPage::instance()->showNotification("Problem setting up call."); - hangUp(); - return; - } - session_.acceptICECandidates(remoteICECandidates_); - remoteICECandidates_.clear(); - haveCallInvite_ = false; - emit newInviteState(); + if (!haveCallInvite_) + return; + + stopRingtone(); + std::string errorMessage; + if (!session_.havePlugins(false, &errorMessage) || + (callType_ == CallType::VIDEO && !session_.havePlugins(true, &errorMessage))) { + emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); + hangUp(); + return; + } + + session_.setTurnServers(turnURIs_); + if (!session_.acceptOffer(inviteSDP_)) { + emit ChatPage::instance()->showNotification("Problem setting up call."); + hangUp(); + return; + } + session_.acceptICECandidates(remoteICECandidates_); + remoteICECandidates_.clear(); + haveCallInvite_ = false; + emit newInviteState(); } void CallManager::handleEvent(const RoomEvent &callCandidatesEvent) { - if (callCandidatesEvent.sender == utils::localUser().toStdString()) - return; - - nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}", - callCandidatesEvent.content.call_id, - callCandidatesEvent.sender); - - if (callid_ == callCandidatesEvent.content.call_id) { - if (isOnCall()) - session_.acceptICECandidates(callCandidatesEvent.content.candidates); - else { - // CallInvite has been received and we're awaiting localUser to accept or - // reject the call - for (const auto &c : callCandidatesEvent.content.candidates) - remoteICECandidates_.push_back(c); - } + if (callCandidatesEvent.sender == utils::localUser().toStdString()) + return; + + nhlog::ui()->debug("WebRTC: call id: {} - incoming CallCandidates from {}", + callCandidatesEvent.content.call_id, + callCandidatesEvent.sender); + + if (callid_ == callCandidatesEvent.content.call_id) { + if (isOnCall()) + session_.acceptICECandidates(callCandidatesEvent.content.candidates); + else { + // CallInvite has been received and we're awaiting localUser to accept or + // reject the call + for (const auto &c : callCandidatesEvent.content.candidates) + remoteICECandidates_.push_back(c); } + } } void CallManager::handleEvent(const RoomEvent &callAnswerEvent) { - nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}", - callAnswerEvent.content.call_id, - callAnswerEvent.sender); - - if (callAnswerEvent.sender == utils::localUser().toStdString() && - callid_ == callAnswerEvent.content.call_id) { - if (!isOnCall()) { - emit ChatPage::instance()->showNotification( - "Call answered on another device."); - stopRingtone(); - haveCallInvite_ = false; - emit newInviteState(); - } - return; + nhlog::ui()->debug("WebRTC: call id: {} - incoming CallAnswer from {}", + callAnswerEvent.content.call_id, + callAnswerEvent.sender); + + if (callAnswerEvent.sender == utils::localUser().toStdString() && + callid_ == callAnswerEvent.content.call_id) { + if (!isOnCall()) { + emit ChatPage::instance()->showNotification("Call answered on another device."); + stopRingtone(); + haveCallInvite_ = false; + emit newInviteState(); } + return; + } - if (isOnCall() && callid_ == callAnswerEvent.content.call_id) { - stopRingtone(); - if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) { - emit ChatPage::instance()->showNotification("Problem setting up call."); - hangUp(); - } + if (isOnCall() && callid_ == callAnswerEvent.content.call_id) { + stopRingtone(); + if (!session_.acceptAnswer(callAnswerEvent.content.sdp)) { + emit ChatPage::instance()->showNotification("Problem setting up call."); + hangUp(); } + } } void CallManager::handleEvent(const RoomEvent &callHangUpEvent) { - nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}", - callHangUpEvent.content.call_id, - callHangUpReasonString(callHangUpEvent.content.reason), - callHangUpEvent.sender); + nhlog::ui()->debug("WebRTC: call id: {} - incoming CallHangUp ({}) from {}", + callHangUpEvent.content.call_id, + callHangUpReasonString(callHangUpEvent.content.reason), + callHangUpEvent.sender); - if (callid_ == callHangUpEvent.content.call_id) - endCall(); + if (callid_ == callHangUpEvent.content.call_id) + endCall(); } void CallManager::toggleMicMute() { - session_.toggleMicMute(); - emit micMuteChanged(); + session_.toggleMicMute(); + emit micMuteChanged(); } bool CallManager::callsSupported() { #ifdef GSTREAMER_AVAILABLE - return true; + return true; #else - return false; + return false; #endif } bool CallManager::screenShareSupported() { - return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY"); + return std::getenv("DISPLAY") && !std::getenv("WAYLAND_DISPLAY"); } QStringList CallManager::devices(bool isVideo) const { - QStringList ret; - const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera() - : ChatPage::instance()->userSettings()->microphone(); - std::vector devices = - CallDevices::instance().names(isVideo, defaultDevice.toStdString()); - ret.reserve(devices.size()); - std::transform(devices.cbegin(), - devices.cend(), - std::back_inserter(ret), - [](const auto &d) { return QString::fromStdString(d); }); - - return ret; + QStringList ret; + const QString &defaultDevice = isVideo ? ChatPage::instance()->userSettings()->camera() + : ChatPage::instance()->userSettings()->microphone(); + std::vector devices = + CallDevices::instance().names(isVideo, defaultDevice.toStdString()); + ret.reserve(devices.size()); + std::transform(devices.cbegin(), devices.cend(), std::back_inserter(ret), [](const auto &d) { + return QString::fromStdString(d); + }); + + return ret; } void CallManager::generateCallID() { - using namespace std::chrono; - uint64_t ms = duration_cast(system_clock::now().time_since_epoch()).count(); - callid_ = "c" + std::to_string(ms); + using namespace std::chrono; + uint64_t ms = duration_cast(system_clock::now().time_since_epoch()).count(); + callid_ = "c" + std::to_string(ms); } void CallManager::clear() { - roomid_.clear(); - callParty_.clear(); - callPartyDisplayName_.clear(); - callPartyAvatarUrl_.clear(); - callid_.clear(); - callType_ = CallType::VOICE; - haveCallInvite_ = false; - emit newInviteState(); - inviteSDP_.clear(); - remoteICECandidates_.clear(); + roomid_.clear(); + callParty_.clear(); + callPartyDisplayName_.clear(); + callPartyAvatarUrl_.clear(); + callid_.clear(); + callType_ = CallType::VOICE; + haveCallInvite_ = false; + emit newInviteState(); + inviteSDP_.clear(); + remoteICECandidates_.clear(); } void CallManager::endCall() { - stopRingtone(); - session_.end(); - clear(); + stopRingtone(); + session_.end(); + clear(); } void CallManager::refreshTurnServer() { - turnURIs_.clear(); - turnServerTimer_.start(2000); + turnURIs_.clear(); + turnServerTimer_.start(2000); } void CallManager::retrieveTurnServer() { - http::client()->get_turn_server( - [this](const mtx::responses::TurnServer &res, mtx::http::RequestErr err) { - if (err) { - turnServerTimer_.setInterval(5000); - return; - } - emit turnServerRetrieved(res); - }); + http::client()->get_turn_server( + [this](const mtx::responses::TurnServer &res, mtx::http::RequestErr err) { + if (err) { + turnServerTimer_.setInterval(5000); + return; + } + emit turnServerRetrieved(res); + }); } void CallManager::playRingtone(const QUrl &ringtone, bool repeat) { - static QMediaPlaylist playlist; - playlist.clear(); - playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop - : QMediaPlaylist::CurrentItemOnce); - playlist.addMedia(ringtone); - player_.setVolume(100); - player_.setPlaylist(&playlist); + static QMediaPlaylist playlist; + playlist.clear(); + playlist.setPlaybackMode(repeat ? QMediaPlaylist::CurrentItemInLoop + : QMediaPlaylist::CurrentItemOnce); + playlist.addMedia(ringtone); + player_.setVolume(100); + player_.setPlaylist(&playlist); } void CallManager::stopRingtone() { - player_.setPlaylist(nullptr); + player_.setPlaylist(nullptr); } QStringList CallManager::windowList() { - windows_.clear(); - windows_.push_back({tr("Entire screen"), 0}); + windows_.clear(); + windows_.push_back({tr("Entire screen"), 0}); #ifdef XCB_AVAILABLE - std::unique_ptr> connection( - xcb_connect(nullptr, nullptr), [](xcb_connection_t *c) { xcb_disconnect(c); }); - if (xcb_connection_has_error(connection.get())) { - nhlog::ui()->error("Failed to connect to X server"); - return {}; - } - - xcb_ewmh_connection_t ewmh; - if (!xcb_ewmh_init_atoms_replies( - &ewmh, xcb_ewmh_init_atoms(connection.get(), &ewmh), nullptr)) { - nhlog::ui()->error("Failed to connect to EWMH server"); - return {}; + std::unique_ptr> connection( + xcb_connect(nullptr, nullptr), [](xcb_connection_t *c) { xcb_disconnect(c); }); + if (xcb_connection_has_error(connection.get())) { + nhlog::ui()->error("Failed to connect to X server"); + return {}; + } + + xcb_ewmh_connection_t ewmh; + if (!xcb_ewmh_init_atoms_replies( + &ewmh, xcb_ewmh_init_atoms(connection.get(), &ewmh), nullptr)) { + nhlog::ui()->error("Failed to connect to EWMH server"); + return {}; + } + std::unique_ptr> + ewmhconnection(&ewmh, [](xcb_ewmh_connection_t *c) { xcb_ewmh_connection_wipe(c); }); + + for (int i = 0; i < ewmh.nb_screens; i++) { + xcb_ewmh_get_windows_reply_t clients; + if (!xcb_ewmh_get_client_list_reply( + &ewmh, xcb_ewmh_get_client_list(&ewmh, i), &clients, nullptr)) { + nhlog::ui()->error("Failed to request window list"); + return {}; } - std::unique_ptr> - ewmhconnection(&ewmh, [](xcb_ewmh_connection_t *c) { xcb_ewmh_connection_wipe(c); }); - - for (int i = 0; i < ewmh.nb_screens; i++) { - xcb_ewmh_get_windows_reply_t clients; - if (!xcb_ewmh_get_client_list_reply( - &ewmh, xcb_ewmh_get_client_list(&ewmh, i), &clients, nullptr)) { - nhlog::ui()->error("Failed to request window list"); - return {}; - } - for (uint32_t w = 0; w < clients.windows_len; w++) { - xcb_window_t window = clients.windows[w]; + for (uint32_t w = 0; w < clients.windows_len; w++) { + xcb_window_t window = clients.windows[w]; - std::string name; - xcb_ewmh_get_utf8_strings_reply_t data; - auto getName = [](xcb_ewmh_get_utf8_strings_reply_t *r) { - std::string name(r->strings, r->strings_len); - xcb_ewmh_get_utf8_strings_reply_wipe(r); - return name; - }; + std::string name; + xcb_ewmh_get_utf8_strings_reply_t data; + auto getName = [](xcb_ewmh_get_utf8_strings_reply_t *r) { + std::string name(r->strings, r->strings_len); + xcb_ewmh_get_utf8_strings_reply_wipe(r); + return name; + }; - xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&ewmh, window); - if (xcb_ewmh_get_wm_name_reply(&ewmh, cookie, &data, nullptr)) - name = getName(&data); + xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name(&ewmh, window); + if (xcb_ewmh_get_wm_name_reply(&ewmh, cookie, &data, nullptr)) + name = getName(&data); - cookie = xcb_ewmh_get_wm_visible_name(&ewmh, window); - if (xcb_ewmh_get_wm_visible_name_reply(&ewmh, cookie, &data, nullptr)) - name = getName(&data); + cookie = xcb_ewmh_get_wm_visible_name(&ewmh, window); + if (xcb_ewmh_get_wm_visible_name_reply(&ewmh, cookie, &data, nullptr)) + name = getName(&data); - windows_.push_back({QString::fromStdString(name), window}); - } - xcb_ewmh_get_windows_reply_wipe(&clients); + windows_.push_back({QString::fromStdString(name), window}); } + xcb_ewmh_get_windows_reply_wipe(&clients); + } #endif - QStringList ret; - ret.reserve(windows_.size()); - for (const auto &w : windows_) - ret.append(w.first); + QStringList ret; + ret.reserve(windows_.size()); + for (const auto &w : windows_) + ret.append(w.first); - return ret; + return ret; } #ifdef GSTREAMER_AVAILABLE @@ -591,22 +578,22 @@ unsigned int busWatchId_ = 0; gboolean newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer G_GNUC_UNUSED) { - switch (GST_MESSAGE_TYPE(msg)) { - case GST_MESSAGE_EOS: - if (pipe_) { - gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL); - gst_object_unref(pipe_); - pipe_ = nullptr; - } - if (busWatchId_) { - g_source_remove(busWatchId_); - busWatchId_ = 0; - } - break; - default: - break; + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + if (pipe_) { + gst_element_set_state(GST_ELEMENT(pipe_), GST_STATE_NULL); + gst_object_unref(pipe_); + pipe_ = nullptr; + } + if (busWatchId_) { + g_source_remove(busWatchId_); + busWatchId_ = 0; } - return TRUE; + break; + default: + break; + } + return TRUE; } } #endif @@ -615,50 +602,50 @@ void CallManager::previewWindow(unsigned int index) const { #ifdef GSTREAMER_AVAILABLE - if (windows_.empty() || index >= windows_.size() || !gst_is_initialized()) - return; - - GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr); - if (!ximagesrc) { - nhlog::ui()->error("Failed to create ximagesrc"); - return; - } - GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr); - GstElement *videoscale = gst_element_factory_make("videoscale", nullptr); - GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr); - GstElement *ximagesink = gst_element_factory_make("ximagesink", nullptr); - - g_object_set(ximagesrc, "use-damage", FALSE, nullptr); - g_object_set(ximagesrc, "show-pointer", FALSE, nullptr); - g_object_set(ximagesrc, "xid", windows_[index].second, nullptr); - - GstCaps *caps = gst_caps_new_simple( - "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, nullptr); - g_object_set(capsfilter, "caps", caps, nullptr); - gst_caps_unref(caps); - - pipe_ = gst_pipeline_new(nullptr); - gst_bin_add_many( - GST_BIN(pipe_), ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr); - if (!gst_element_link_many( - ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr)) { - nhlog::ui()->error("Failed to link preview window elements"); - gst_object_unref(pipe_); - pipe_ = nullptr; - return; - } - if (gst_element_set_state(pipe_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { - nhlog::ui()->error("Unable to start preview pipeline"); - gst_object_unref(pipe_); - pipe_ = nullptr; - return; - } - - GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_)); - busWatchId_ = gst_bus_add_watch(bus, newBusMessage, nullptr); - gst_object_unref(bus); + if (windows_.empty() || index >= windows_.size() || !gst_is_initialized()) + return; + + GstElement *ximagesrc = gst_element_factory_make("ximagesrc", nullptr); + if (!ximagesrc) { + nhlog::ui()->error("Failed to create ximagesrc"); + return; + } + GstElement *videoconvert = gst_element_factory_make("videoconvert", nullptr); + GstElement *videoscale = gst_element_factory_make("videoscale", nullptr); + GstElement *capsfilter = gst_element_factory_make("capsfilter", nullptr); + GstElement *ximagesink = gst_element_factory_make("ximagesink", nullptr); + + g_object_set(ximagesrc, "use-damage", FALSE, nullptr); + g_object_set(ximagesrc, "show-pointer", FALSE, nullptr); + g_object_set(ximagesrc, "xid", windows_[index].second, nullptr); + + GstCaps *caps = gst_caps_new_simple( + "video/x-raw", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, nullptr); + g_object_set(capsfilter, "caps", caps, nullptr); + gst_caps_unref(caps); + + pipe_ = gst_pipeline_new(nullptr); + gst_bin_add_many( + GST_BIN(pipe_), ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr); + if (!gst_element_link_many( + ximagesrc, videoconvert, videoscale, capsfilter, ximagesink, nullptr)) { + nhlog::ui()->error("Failed to link preview window elements"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return; + } + if (gst_element_set_state(pipe_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + nhlog::ui()->error("Unable to start preview pipeline"); + gst_object_unref(pipe_); + pipe_ = nullptr; + return; + } + + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe_)); + busWatchId_ = gst_bus_add_watch(bus, newBusMessage, nullptr); + gst_object_unref(bus); #else - (void)index; + (void)index; #endif } @@ -666,29 +653,28 @@ namespace { std::vector getTurnURIs(const mtx::responses::TurnServer &turnServer) { - // gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp) - // where username and password are percent-encoded - std::vector ret; - for (const auto &uri : turnServer.uris) { - if (auto c = uri.find(':'); c == std::string::npos) { - nhlog::ui()->error("Invalid TURN server uri: {}", uri); - continue; - } else { - std::string scheme = std::string(uri, 0, c); - if (scheme != "turn" && scheme != "turns") { - nhlog::ui()->error("Invalid TURN server uri: {}", uri); - continue; - } - - QString encodedUri = - QString::fromStdString(scheme) + "://" + - QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) + - ":" + - QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) + - "@" + QString::fromStdString(std::string(uri, ++c)); - ret.push_back(encodedUri.toStdString()); - } + // gstreamer expects: turn(s)://username:password@host:port?transport=udp(tcp) + // where username and password are percent-encoded + std::vector ret; + for (const auto &uri : turnServer.uris) { + if (auto c = uri.find(':'); c == std::string::npos) { + nhlog::ui()->error("Invalid TURN server uri: {}", uri); + continue; + } else { + std::string scheme = std::string(uri, 0, c); + if (scheme != "turn" && scheme != "turns") { + nhlog::ui()->error("Invalid TURN server uri: {}", uri); + continue; + } + + QString encodedUri = + QString::fromStdString(scheme) + "://" + + QUrl::toPercentEncoding(QString::fromStdString(turnServer.username)) + ":" + + QUrl::toPercentEncoding(QString::fromStdString(turnServer.password)) + "@" + + QString::fromStdString(std::string(uri, ++c)); + ret.push_back(encodedUri.toStdString()); } - return ret; + } + return ret; } } diff --git a/src/CallManager.h b/src/CallManager.h index 407b8366..22f31814 100644 --- a/src/CallManager.h +++ b/src/CallManager.h @@ -26,93 +26,92 @@ class QUrl; class CallManager : public QObject { - Q_OBJECT - Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) - Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) - Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState) - Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) - Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) - Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState) - Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState) - Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) - Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState) - Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged) - Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged) - Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) - Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT) + Q_OBJECT + Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState) + Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState) + Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState) + Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState) + Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState) + Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState) + Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState) + Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged) + Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState) + Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged) + Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged) + Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT) + Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT) public: - CallManager(QObject *); + CallManager(QObject *); - bool haveCallInvite() const { return haveCallInvite_; } - bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } - webrtc::CallType callType() const { return callType_; } - webrtc::State callState() const { return session_.state(); } - QString callParty() const { return callParty_; } - QString callPartyDisplayName() const { return callPartyDisplayName_; } - QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } - bool isMicMuted() const { return session_.isMicMuted(); } - bool haveLocalPiP() const { return session_.haveLocalPiP(); } - QStringList mics() const { return devices(false); } - QStringList cameras() const { return devices(true); } - void refreshTurnServer(); + bool haveCallInvite() const { return haveCallInvite_; } + bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; } + webrtc::CallType callType() const { return callType_; } + webrtc::State callState() const { return session_.state(); } + QString callParty() const { return callParty_; } + QString callPartyDisplayName() const { return callPartyDisplayName_; } + QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; } + bool isMicMuted() const { return session_.isMicMuted(); } + bool haveLocalPiP() const { return session_.haveLocalPiP(); } + QStringList mics() const { return devices(false); } + QStringList cameras() const { return devices(true); } + void refreshTurnServer(); - static bool callsSupported(); - static bool screenShareSupported(); + static bool callsSupported(); + static bool screenShareSupported(); public slots: - void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0); - void syncEvent(const mtx::events::collections::TimelineEvents &event); - void toggleMicMute(); - void toggleLocalPiP() { session_.toggleLocalPiP(); } - void acceptInvite(); - void hangUp( - mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); - QStringList windowList(); - void previewWindow(unsigned int windowIndex) const; + void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0); + void syncEvent(const mtx::events::collections::TimelineEvents &event); + void toggleMicMute(); + void toggleLocalPiP() { session_.toggleLocalPiP(); } + void acceptInvite(); + void hangUp(mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User); + QStringList windowList(); + void previewWindow(unsigned int windowIndex) const; signals: - void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &); - void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); - void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); - void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); - void newInviteState(); - void newCallState(); - void micMuteChanged(); - void devicesChanged(); - void turnServerRetrieved(const mtx::responses::TurnServer &); + void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &); + void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &); + void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &); + void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &); + void newInviteState(); + void newCallState(); + void micMuteChanged(); + void devicesChanged(); + void turnServerRetrieved(const mtx::responses::TurnServer &); private slots: - void retrieveTurnServer(); + void retrieveTurnServer(); private: - WebRTCSession &session_; - QString roomid_; - QString callParty_; - QString callPartyDisplayName_; - QString callPartyAvatarUrl_; - std::string callid_; - const uint32_t timeoutms_ = 120000; - webrtc::CallType callType_ = webrtc::CallType::VOICE; - bool haveCallInvite_ = false; - std::string inviteSDP_; - std::vector remoteICECandidates_; - std::vector turnURIs_; - QTimer turnServerTimer_; - QMediaPlayer player_; - std::vector> windows_; + WebRTCSession &session_; + QString roomid_; + QString callParty_; + QString callPartyDisplayName_; + QString callPartyAvatarUrl_; + std::string callid_; + const uint32_t timeoutms_ = 120000; + webrtc::CallType callType_ = webrtc::CallType::VOICE; + bool haveCallInvite_ = false; + std::string inviteSDP_; + std::vector remoteICECandidates_; + std::vector turnURIs_; + QTimer turnServerTimer_; + QMediaPlayer player_; + std::vector> windows_; - template - bool handleEvent(const mtx::events::collections::TimelineEvents &event); - void handleEvent(const mtx::events::RoomEvent &); - void handleEvent(const mtx::events::RoomEvent &); - void handleEvent(const mtx::events::RoomEvent &); - void handleEvent(const mtx::events::RoomEvent &); - void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo); - void generateCallID(); - QStringList devices(bool isVideo) const; - void clear(); - void endCall(); - void playRingtone(const QUrl &ringtone, bool repeat); - void stopRingtone(); + template + bool handleEvent(const mtx::events::collections::TimelineEvents &event); + void handleEvent(const mtx::events::RoomEvent &); + void handleEvent(const mtx::events::RoomEvent &); + void handleEvent(const mtx::events::RoomEvent &); + void handleEvent(const mtx::events::RoomEvent &); + void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo); + void generateCallID(); + QStringList devices(bool isVideo) const; + void clear(); + void endCall(); + void playRingtone(const QUrl &ringtone, bool repeat); + void stopRingtone(); }; diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 3887f8b8..673f39ee 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -50,660 +50,641 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) , notificationsManager(this) , callManager_(new CallManager(this)) { - setObjectName("chatPage"); - - instance_ = this; - - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(0); - - view_manager_ = new TimelineViewManager(callManager_, this); - - topLayout_->addWidget(view_manager_->getWidget()); - - connect(this, - &ChatPage::downloadedSecrets, - this, - &ChatPage::decryptDownloadedSecrets, - Qt::QueuedConnection); - - connect(this, &ChatPage::connectionLost, this, [this]() { - nhlog::net()->info("connectivity lost"); - isConnected_ = false; - http::client()->shutdown(); - }); - connect(this, &ChatPage::connectionRestored, this, [this]() { - nhlog::net()->info("trying to re-connect"); - isConnected_ = true; - - // Drop all pending connections. - http::client()->shutdown(); - trySync(); - }); - - connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL); - connect(&connectivityTimer_, &QTimer::timeout, this, [=]() { - if (http::client()->access_token().empty()) { - connectivityTimer_.stop(); - return; - } + setObjectName("chatPage"); - http::client()->versions( - [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { - if (err) { - emit connectionLost(); - return; - } + instance_ = this; - if (!isConnected_) - emit connectionRestored(); - }); - }); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); - connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); + topLayout_ = new QHBoxLayout(this); + topLayout_->setSpacing(0); + topLayout_->setMargin(0); - connect( - view_manager_, - &TimelineViewManager::inviteUsers, - this, - [this](QString roomId, QStringList users) { - for (int ii = 0; ii < users.size(); ++ii) { - QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() { - const auto user = users.at(ii); - - http::client()->invite_user( - roomId.toStdString(), - user.toStdString(), - [this, user](const mtx::responses::RoomInvite &, - mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to invite user: %1").arg(user)); - return; - } - - emit showNotification(tr("Invited user: %1").arg(user)); - }); - }); - } - }); + view_manager_ = new TimelineViewManager(callManager_, this); + + topLayout_->addWidget(view_manager_->getWidget()); + + connect(this, + &ChatPage::downloadedSecrets, + this, + &ChatPage::decryptDownloadedSecrets, + Qt::QueuedConnection); + + connect(this, &ChatPage::connectionLost, this, [this]() { + nhlog::net()->info("connectivity lost"); + isConnected_ = false; + http::client()->shutdown(); + }); + connect(this, &ChatPage::connectionRestored, this, [this]() { + nhlog::net()->info("trying to re-connect"); + isConnected_ = true; - connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); - connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); - connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); - connect(this, - &ChatPage::highlightedNotifsRetrieved, - this, - [](const mtx::responses::Notifications ¬if) { - try { - cache::saveTimelineMentions(notif); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to save mentions: {}", e.what()); + // Drop all pending connections. + http::client()->shutdown(); + trySync(); + }); + + connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL); + connect(&connectivityTimer_, &QTimer::timeout, this, [=]() { + if (http::client()->access_token().empty()) { + connectivityTimer_.stop(); + return; + } + + http::client()->versions( + [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { + if (err) { + emit connectionLost(); + return; + } + + if (!isConnected_) + emit connectionRestored(); + }); + }); + + connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); + + connect( + view_manager_, + &TimelineViewManager::inviteUsers, + this, + [this](QString roomId, QStringList users) { + for (int ii = 0; ii < users.size(); ++ii) { + QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() { + const auto user = users.at(ii); + + http::client()->invite_user( + roomId.toStdString(), + user.toStdString(), + [this, user](const mtx::responses::RoomInvite &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to invite user: %1").arg(user)); + return; } - }); - - connect(¬ificationsManager, - &NotificationsManager::notificationClicked, - this, - [this](const QString &roomid, const QString &eventid) { - Q_UNUSED(eventid) - view_manager_->rooms()->setCurrentRoom(roomid); - activateWindow(); - }); - connect(¬ificationsManager, - &NotificationsManager::sendNotificationReply, - this, - [this](const QString &roomid, const QString &eventid, const QString &body) { - view_manager_->rooms()->setCurrentRoom(roomid); - view_manager_->queueReply(roomid, eventid, body); - activateWindow(); - }); - - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { - // ensure the qml context is shutdown before we destroy all other singletons - // Otherwise Qml will try to access the room list or settings, after they have been - // destroyed - topLayout_->removeWidget(view_manager_->getWidget()); - delete view_manager_->getWidget(); - }); - - connect( - this, - &ChatPage::initializeViews, - view_manager_, - [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); }, - Qt::QueuedConnection); - connect(this, - &ChatPage::initializeEmptyViews, - view_manager_, - &TimelineViewManager::initializeRoomlist); - connect( - this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); - connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { - view_manager_->sync(rooms); - - static unsigned int prevNotificationCount = 0; - unsigned int notificationCount = 0; - for (const auto &room : rooms.join) { - notificationCount += room.second.unread_notifications.notification_count; - } - // HACK: If we had less notifications last time we checked, send an alert if the - // user wanted one. Technically, this may cause an alert to be missed if new ones - // come in while you are reading old ones. Since the window is almost certainly open - // in this edge case, that's probably a non-issue. - // TODO: Replace this once we have proper pushrules support. This is a horrible hack - if (prevNotificationCount < notificationCount) { - if (userSettings_->hasAlertOnNotification()) - QApplication::alert(this); + emit showNotification(tr("Invited user: %1").arg(user)); + }); + }); + } + }); + + connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); + connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); + connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); + connect(this, + &ChatPage::highlightedNotifsRetrieved, + this, + [](const mtx::responses::Notifications ¬if) { + try { + cache::saveTimelineMentions(notif); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to save mentions: {}", e.what()); } - prevNotificationCount = notificationCount; - - // No need to check amounts for this section, as this function internally checks for - // duplicates. - if (notificationCount && userSettings_->hasNotifications()) - http::client()->notifications( - 5, - "", - "", - [this](const mtx::responses::Notifications &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to retrieve notifications: {} ({})", - err->matrix_error.error, - static_cast(err->status_code)); - return; - } - - emit notificationsRetrieved(std::move(res)); - }); - }); - - connect( - this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); - connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); - connect( - this, - &ChatPage::tryDelayedSyncCb, - this, - [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, - Qt::QueuedConnection); - - connect(this, - &ChatPage::newSyncResponse, - this, - &ChatPage::handleSyncResponse, - Qt::QueuedConnection); + }); + + connect(¬ificationsManager, + &NotificationsManager::notificationClicked, + this, + [this](const QString &roomid, const QString &eventid) { + Q_UNUSED(eventid) + view_manager_->rooms()->setCurrentRoom(roomid); + activateWindow(); + }); + connect(¬ificationsManager, + &NotificationsManager::sendNotificationReply, + this, + [this](const QString &roomid, const QString &eventid, const QString &body) { + view_manager_->rooms()->setCurrentRoom(roomid); + view_manager_->queueReply(roomid, eventid, body); + activateWindow(); + }); + + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() { + // ensure the qml context is shutdown before we destroy all other singletons + // Otherwise Qml will try to access the room list or settings, after they have been + // destroyed + topLayout_->removeWidget(view_manager_->getWidget()); + delete view_manager_->getWidget(); + }); + + connect( + this, + &ChatPage::initializeViews, + view_manager_, + [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); }, + Qt::QueuedConnection); + connect(this, + &ChatPage::initializeEmptyViews, + view_manager_, + &TimelineViewManager::initializeRoomlist); + connect( + this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); + connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { + view_manager_->sync(rooms); + + static unsigned int prevNotificationCount = 0; + unsigned int notificationCount = 0; + for (const auto &room : rooms.join) { + notificationCount += room.second.unread_notifications.notification_count; + } - connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage); + // HACK: If we had less notifications last time we checked, send an alert if the + // user wanted one. Technically, this may cause an alert to be missed if new ones + // come in while you are reading old ones. Since the window is almost certainly open + // in this edge case, that's probably a non-issue. + // TODO: Replace this once we have proper pushrules support. This is a horrible hack + if (prevNotificationCount < notificationCount) { + if (userSettings_->hasAlertOnNotification()) + QApplication::alert(this); + } + prevNotificationCount = notificationCount; + + // No need to check amounts for this section, as this function internally checks for + // duplicates. + if (notificationCount && userSettings_->hasNotifications()) + http::client()->notifications( + 5, + "", + "", + [this](const mtx::responses::Notifications &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve notifications: {} ({})", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } - connectCallMessage(); - connectCallMessage(); - connectCallMessage(); - connectCallMessage(); + emit notificationsRetrieved(std::move(res)); + }); + }); + + connect( + this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); + connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); + connect( + this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); + + connect( + this, &ChatPage::newSyncResponse, this, &ChatPage::handleSyncResponse, Qt::QueuedConnection); + + connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage); + + connectCallMessage(); + connectCallMessage(); + connectCallMessage(); + connectCallMessage(); } void ChatPage::logout() { - resetUI(); - deleteConfigs(); + resetUI(); + deleteConfigs(); - emit closing(); - connectivityTimer_.stop(); + emit closing(); + connectivityTimer_.stop(); } void ChatPage::dropToLoginPage(const QString &msg) { - nhlog::ui()->info("dropping to the login page: {}", msg.toStdString()); + nhlog::ui()->info("dropping to the login page: {}", msg.toStdString()); - http::client()->shutdown(); - connectivityTimer_.stop(); + http::client()->shutdown(); + connectivityTimer_.stop(); - resetUI(); - deleteConfigs(); + resetUI(); + deleteConfigs(); - emit showLoginPage(msg); + emit showLoginPage(msg); } void ChatPage::resetUI() { - view_manager_->clearAll(); + view_manager_->clearAll(); - emit unreadMessages(0); + emit unreadMessages(0); } void ChatPage::deleteConfigs() { - auto settings = UserSettings::instance()->qsettings(); - - if (UserSettings::instance()->profile() != "") { - settings->beginGroup("profile"); - settings->beginGroup(UserSettings::instance()->profile()); - } - settings->beginGroup("auth"); - settings->remove(""); - settings->endGroup(); // auth - - http::client()->shutdown(); - cache::deleteData(); + auto settings = UserSettings::instance()->qsettings(); + + if (UserSettings::instance()->profile() != "") { + settings->beginGroup("profile"); + settings->beginGroup(UserSettings::instance()->profile()); + } + settings->beginGroup("auth"); + settings->remove(""); + settings->endGroup(); // auth + + http::client()->shutdown(); + cache::deleteData(); } void ChatPage::bootstrap(QString userid, QString homeserver, QString token) { - using namespace mtx::identifiers; + using namespace mtx::identifiers; - try { - http::client()->set_user(parse(userid.toStdString())); - } catch (const std::invalid_argument &) { - nhlog::ui()->critical("bootstrapped with invalid user_id: {}", - userid.toStdString()); - } + try { + http::client()->set_user(parse(userid.toStdString())); + } catch (const std::invalid_argument &) { + nhlog::ui()->critical("bootstrapped with invalid user_id: {}", userid.toStdString()); + } - http::client()->set_server(homeserver.toStdString()); - http::client()->set_access_token(token.toStdString()); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); + http::client()->set_server(homeserver.toStdString()); + http::client()->set_access_token(token.toStdString()); + http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); - // The Olm client needs the user_id & device_id that will be included - // in the generated payloads & keys. - olm::client()->set_user_id(http::client()->user_id().to_string()); - olm::client()->set_device_id(http::client()->device_id()); + // The Olm client needs the user_id & device_id that will be included + // in the generated payloads & keys. + olm::client()->set_user_id(http::client()->user_id().to_string()); + olm::client()->set_device_id(http::client()->device_id()); - try { - cache::init(userid); - - connect(cache::client(), - &Cache::newReadReceipts, - view_manager_, - &TimelineViewManager::updateReadReceipts); - - connect(cache::client(), - &Cache::removeNotification, - ¬ificationsManager, - &NotificationsManager::removeNotification); - - const bool isInitialized = cache::isInitialized(); - const auto cacheVersion = cache::formatVersion(); - - callManager_->refreshTurnServer(); - - if (!isInitialized) { - cache::setCurrentFormat(); - } else { - if (cacheVersion == cache::CacheVersion::Current) { - loadStateFromCache(); - return; - } else if (cacheVersion == cache::CacheVersion::Older) { - if (!cache::runMigrations()) { - QMessageBox::critical( - this, + try { + cache::init(userid); + + connect(cache::client(), + &Cache::newReadReceipts, + view_manager_, + &TimelineViewManager::updateReadReceipts); + + connect(cache::client(), + &Cache::removeNotification, + ¬ificationsManager, + &NotificationsManager::removeNotification); + + const bool isInitialized = cache::isInitialized(); + const auto cacheVersion = cache::formatVersion(); + + callManager_->refreshTurnServer(); + + if (!isInitialized) { + cache::setCurrentFormat(); + } else { + if (cacheVersion == cache::CacheVersion::Current) { + loadStateFromCache(); + return; + } else if (cacheVersion == cache::CacheVersion::Older) { + if (!cache::runMigrations()) { + QMessageBox::critical(this, tr("Cache migration failed!"), tr("Migrating the cache to the current version failed. " "This can have different reasons. Please open an " "issue and try to use an older version in the mean " "time. Alternatively you can try deleting the cache " "manually.")); - QCoreApplication::quit(); - } - loadStateFromCache(); - return; - } else if (cacheVersion == cache::CacheVersion::Newer) { - QMessageBox::critical( - this, - tr("Incompatible cache version"), - tr("The cache on your disk is newer than this version of Nheko " - "supports. Please update or clear your cache.")); - QCoreApplication::quit(); - return; - } + QCoreApplication::quit(); } - - } catch (const lmdb::error &e) { - nhlog::db()->critical("failure during boot: {}", e.what()); - cache::deleteData(); - nhlog::net()->info("falling back to initial sync"); - } - - try { - // It's the first time syncing with this device - // There isn't a saved olm account to restore. - nhlog::crypto()->info("creating new olm account"); - olm::client()->create_new_account(); - cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save olm account {}", e.what()); - emit dropToLoginPageCb(QString::fromStdString(e.what())); + loadStateFromCache(); return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to create new olm account {}", e.what()); - emit dropToLoginPageCb(QString::fromStdString(e.what())); + } else if (cacheVersion == cache::CacheVersion::Newer) { + QMessageBox::critical( + this, + tr("Incompatible cache version"), + tr("The cache on your disk is newer than this version of Nheko " + "supports. Please update or clear your cache.")); + QCoreApplication::quit(); return; + } } - getProfileInfo(); - getBackupVersion(); - tryInitialSync(); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failure during boot: {}", e.what()); + cache::deleteData(); + nhlog::net()->info("falling back to initial sync"); + } + + try { + // It's the first time syncing with this device + // There isn't a saved olm account to restore. + nhlog::crypto()->info("creating new olm account"); + olm::client()->create_new_account(); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save olm account {}", e.what()); + emit dropToLoginPageCb(QString::fromStdString(e.what())); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create new olm account {}", e.what()); + emit dropToLoginPageCb(QString::fromStdString(e.what())); + return; + } + + getProfileInfo(); + getBackupVersion(); + tryInitialSync(); } void ChatPage::loadStateFromCache() { - nhlog::db()->info("restoring state from cache"); - - try { - olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret()); - - emit initializeEmptyViews(); - emit initializeMentions(cache::getTimelineMentions()); - - cache::calculateRoomReadStatus(); - - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to restore olm account: {}", e.what()); - emit dropToLoginPageCb(tr("Failed to restore OLM account. Please login again.")); - return; - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to restore cache: {}", e.what()); - emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); - return; - } catch (const json::exception &e) { - nhlog::db()->critical("failed to parse cache data: {}", e.what()); - emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); - return; - } catch (const std::exception &e) { - nhlog::db()->critical("failed to load cache data: {}", e.what()); - emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); - return; - } - - nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519); - nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519); - - getProfileInfo(); - getBackupVersion(); - verifyOneTimeKeyCountAfterStartup(); - - emit contentLoaded(); - - // Start receiving events. - emit trySyncCb(); + nhlog::db()->info("restoring state from cache"); + + try { + olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret()); + + emit initializeEmptyViews(); + emit initializeMentions(cache::getTimelineMentions()); + + cache::calculateRoomReadStatus(); + + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to restore olm account: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore OLM account. Please login again.")); + return; + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to restore cache: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); + return; + } catch (const json::exception &e) { + nhlog::db()->critical("failed to parse cache data: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); + return; + } catch (const std::exception &e) { + nhlog::db()->critical("failed to load cache data: {}", e.what()); + emit dropToLoginPageCb(tr("Failed to restore save data. Please login again.")); + return; + } + + nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519); + nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519); + + getProfileInfo(); + getBackupVersion(); + verifyOneTimeKeyCountAfterStartup(); + + emit contentLoaded(); + + // Start receiving events. + emit trySyncCb(); } void ChatPage::removeRoom(const QString &room_id) { - try { - cache::removeRoom(room_id); - cache::removeInvite(room_id.toStdString()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failure while removing room: {}", e.what()); - // TODO: Notify the user. - } + try { + cache::removeRoom(room_id); + cache::removeInvite(room_id.toStdString()); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failure while removing room: {}", e.what()); + // TODO: Notify the user. + } } void ChatPage::sendNotifications(const mtx::responses::Notifications &res) { - for (const auto &item : res.notifications) { - const auto event_id = mtx::accessors::event_id(item.event); - - try { - if (item.read) { - cache::removeReadNotification(event_id); - continue; - } - - if (!cache::isNotificationSent(event_id)) { - const auto room_id = QString::fromStdString(item.room_id); - - // We should only sent one notification per event. - cache::markSentNotification(event_id); - - // Don't send a notification when the current room is opened. - if (isRoomActive(room_id)) - continue; - - if (userSettings_->hasDesktopNotifications()) { - auto info = cache::singleRoomInfo(item.room_id); + for (const auto &item : res.notifications) { + const auto event_id = mtx::accessors::event_id(item.event); - AvatarProvider::resolve( - QString::fromStdString(info.avatar_url), - 96, - this, - [this, item](QPixmap image) { - notificationsManager.postNotification( - item, image.toImage()); - }); - } - } - } catch (const lmdb::error &e) { - nhlog::db()->warn("error while sending notification: {}", e.what()); + try { + if (item.read) { + cache::removeReadNotification(event_id); + continue; + } + + if (!cache::isNotificationSent(event_id)) { + const auto room_id = QString::fromStdString(item.room_id); + + // We should only sent one notification per event. + cache::markSentNotification(event_id); + + // Don't send a notification when the current room is opened. + if (isRoomActive(room_id)) + continue; + + if (userSettings_->hasDesktopNotifications()) { + auto info = cache::singleRoomInfo(item.room_id); + + AvatarProvider::resolve(QString::fromStdString(info.avatar_url), + 96, + this, + [this, item](QPixmap image) { + notificationsManager.postNotification( + item, image.toImage()); + }); } + } + } catch (const lmdb::error &e) { + nhlog::db()->warn("error while sending notification: {}", e.what()); } + } } void ChatPage::tryInitialSync() { - nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519); - nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519); + nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519); + nhlog::crypto()->info("curve25519: {}", olm::client()->identity_keys().curve25519); - // Upload one time keys for the device. - nhlog::crypto()->info("generating one time keys"); - olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS); + // Upload one time keys for the device. + nhlog::crypto()->info("generating one time keys"); + olm::client()->generate_one_time_keys(MAX_ONETIME_KEYS); - http::client()->upload_keys( - olm::client()->create_upload_keys_request(), - [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) { - if (err) { - const int status_code = static_cast(err->status_code); + http::client()->upload_keys( + olm::client()->create_upload_keys_request(), + [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) { + if (err) { + const int status_code = static_cast(err->status_code); - if (status_code == 404) { - nhlog::net()->warn( - "skipping key uploading. server doesn't provide /keys/upload"); - return startInitialSync(); - } + if (status_code == 404) { + nhlog::net()->warn("skipping key uploading. server doesn't provide /keys/upload"); + return startInitialSync(); + } - nhlog::crypto()->critical("failed to upload one time keys: {} {}", - err->matrix_error.error, - status_code); + nhlog::crypto()->critical( + "failed to upload one time keys: {} {}", err->matrix_error.error, status_code); - QString errorMsg(tr("Failed to setup encryption keys. Server response: " - "%1 %2. Please try again later.") - .arg(QString::fromStdString(err->matrix_error.error)) - .arg(status_code)); + QString errorMsg(tr("Failed to setup encryption keys. Server response: " + "%1 %2. Please try again later.") + .arg(QString::fromStdString(err->matrix_error.error)) + .arg(status_code)); - emit dropToLoginPageCb(errorMsg); - return; - } + emit dropToLoginPageCb(errorMsg); + return; + } - olm::mark_keys_as_published(); + olm::mark_keys_as_published(); - for (const auto &entry : res.one_time_key_counts) - nhlog::net()->info( - "uploaded {} {} one-time keys", entry.second, entry.first); + for (const auto &entry : res.one_time_key_counts) + nhlog::net()->info("uploaded {} {} one-time keys", entry.second, entry.first); - startInitialSync(); - }); + startInitialSync(); + }); } void ChatPage::startInitialSync() { - nhlog::net()->info("trying initial sync"); - - mtx::http::SyncOpts opts; - opts.timeout = 0; - opts.set_presence = currentPresence(); - - http::client()->sync( - opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) { - // TODO: Initial Sync should include mentions as well... + nhlog::net()->info("trying initial sync"); + + mtx::http::SyncOpts opts; + opts.timeout = 0; + opts.set_presence = currentPresence(); + + http::client()->sync(opts, [this](const mtx::responses::Sync &res, mtx::http::RequestErr err) { + // TODO: Initial Sync should include mentions as well... + + if (err) { + const auto error = QString::fromStdString(err->matrix_error.error); + const auto msg = tr("Please try to login again: %1").arg(error); + const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); + const int status_code = static_cast(err->status_code); + + nhlog::net()->error("initial sync error: {} {} {} {}", + err->parse_error, + status_code, + err->error_code, + err_code); + + // non http related errors + if (status_code <= 0 || status_code >= 600) { + startInitialSync(); + return; + } - if (err) { - const auto error = QString::fromStdString(err->matrix_error.error); - const auto msg = tr("Please try to login again: %1").arg(error); - const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); - const int status_code = static_cast(err->status_code); - - nhlog::net()->error("initial sync error: {} {} {} {}", - err->parse_error, - status_code, - err->error_code, - err_code); - - // non http related errors - if (status_code <= 0 || status_code >= 600) { - startInitialSync(); - return; - } - - switch (status_code) { - case 502: - case 504: - case 524: { - startInitialSync(); - return; - } - default: { - emit dropToLoginPageCb(msg); - return; - } - } - } + switch (status_code) { + case 502: + case 504: + case 524: { + startInitialSync(); + return; + } + default: { + emit dropToLoginPageCb(msg); + return; + } + } + } - nhlog::net()->info("initial sync completed"); + nhlog::net()->info("initial sync completed"); - try { - cache::client()->saveState(res); + try { + cache::client()->saveState(res); - olm::handle_to_device_messages(res.to_device.events); + olm::handle_to_device_messages(res.to_device.events); - emit initializeViews(std::move(res.rooms)); - emit initializeMentions(cache::getTimelineMentions()); + emit initializeViews(std::move(res.rooms)); + emit initializeMentions(cache::getTimelineMentions()); - cache::calculateRoomReadStatus(); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to save state after initial sync: {}", - e.what()); - startInitialSync(); - return; - } + cache::calculateRoomReadStatus(); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to save state after initial sync: {}", e.what()); + startInitialSync(); + return; + } - emit trySyncCb(); - emit contentLoaded(); - }); + emit trySyncCb(); + emit contentLoaded(); + }); } void ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token) { - try { - if (prev_batch_token != cache::nextBatchToken()) { - nhlog::net()->warn("Duplicate sync, dropping"); - return; - } - } catch (const lmdb::error &e) { - nhlog::db()->warn("Logged out in the mean time, dropping sync"); + try { + if (prev_batch_token != cache::nextBatchToken()) { + nhlog::net()->warn("Duplicate sync, dropping"); + return; } + } catch (const lmdb::error &e) { + nhlog::db()->warn("Logged out in the mean time, dropping sync"); + } - nhlog::net()->debug("sync completed: {}", res.next_batch); + nhlog::net()->debug("sync completed: {}", res.next_batch); - // Ensure that we have enough one-time keys available. - ensureOneTimeKeyCount(res.device_one_time_keys_count); + // Ensure that we have enough one-time keys available. + ensureOneTimeKeyCount(res.device_one_time_keys_count); - // TODO: fine grained error handling - try { - cache::client()->saveState(res); - olm::handle_to_device_messages(res.to_device.events); + // TODO: fine grained error handling + try { + cache::client()->saveState(res); + olm::handle_to_device_messages(res.to_device.events); - auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); + auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); - emit syncUI(res.rooms); + emit syncUI(res.rooms); - // if we process a lot of syncs (1 every 200ms), this means we clean the - // db every 100s - static int syncCounter = 0; - if (syncCounter++ >= 500) { - cache::deleteOldData(); - syncCounter = 0; - } - } catch (const lmdb::map_full_error &e) { - nhlog::db()->error("lmdb is full: {}", e.what()); - cache::deleteOldData(); - } catch (const lmdb::error &e) { - nhlog::db()->error("saving sync response: {}", e.what()); + // if we process a lot of syncs (1 every 200ms), this means we clean the + // db every 100s + static int syncCounter = 0; + if (syncCounter++ >= 500) { + cache::deleteOldData(); + syncCounter = 0; } - - emit trySyncCb(); + } catch (const lmdb::map_full_error &e) { + nhlog::db()->error("lmdb is full: {}", e.what()); + cache::deleteOldData(); + } catch (const lmdb::error &e) { + nhlog::db()->error("saving sync response: {}", e.what()); + } + + emit trySyncCb(); } void ChatPage::trySync() { - mtx::http::SyncOpts opts; - opts.set_presence = currentPresence(); - - if (!connectivityTimer_.isActive()) - connectivityTimer_.start(); - - try { - opts.since = cache::nextBatchToken(); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to retrieve next batch token: {}", e.what()); - return; - } - - http::client()->sync( - opts, - [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) { - if (err) { - const auto error = QString::fromStdString(err->matrix_error.error); - const auto msg = tr("Please try to login again: %1").arg(error); - const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); - const int status_code = static_cast(err->status_code); - - if ((http::is_logged_in() && - (err->matrix_error.errcode == - mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || - err->matrix_error.errcode == - mtx::errors::ErrorCode::M_MISSING_TOKEN)) || - !http::is_logged_in()) { - emit dropToLoginPageCb(msg); - return; - } - - nhlog::net()->error("sync error: {} {} {} {}", - err->parse_error, - status_code, - err->error_code, - err_code); - emit tryDelayedSyncCb(); - return; - } - - emit newSyncResponse(res, since); - }); + mtx::http::SyncOpts opts; + opts.set_presence = currentPresence(); + + if (!connectivityTimer_.isActive()) + connectivityTimer_.start(); + + try { + opts.since = cache::nextBatchToken(); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to retrieve next batch token: {}", e.what()); + return; + } + + http::client()->sync( + opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) { + if (err) { + const auto error = QString::fromStdString(err->matrix_error.error); + const auto msg = tr("Please try to login again: %1").arg(error); + const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); + const int status_code = static_cast(err->status_code); + + if ((http::is_logged_in() && + (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || + err->matrix_error.errcode == mtx::errors::ErrorCode::M_MISSING_TOKEN)) || + !http::is_logged_in()) { + emit dropToLoginPageCb(msg); + return; + } + + nhlog::net()->error("sync error: {} {} {} {}", + err->parse_error, + status_code, + err->error_code, + err_code); + emit tryDelayedSyncCb(); + return; + } + + emit newSyncResponse(res, since); + }); } void ChatPage::joinRoom(const QString &room) { - const auto room_id = room.toStdString(); - joinRoomVia(room_id, {}, false); + const auto room_id = room.toStdString(); + joinRoomVia(room_id, {}, false); } void @@ -711,692 +692,662 @@ ChatPage::joinRoomVia(const std::string &room_id, const std::vector &via, bool promptForConfirmation) { - if (promptForConfirmation && - QMessageBox::Yes != - QMessageBox::question( - this, - tr("Confirm join"), - tr("Do you really want to join %1?").arg(QString::fromStdString(room_id)))) - return; - - http::client()->join_room( - room_id, via, [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to join room: %1") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } - - emit tr("You joined the room"); - - // We remove any invites with the same room_id. - try { - cache::removeInvite(room_id); - } catch (const lmdb::error &e) { - emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); - } - - view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id)); - }); + if (promptForConfirmation && + QMessageBox::Yes != + QMessageBox::question( + this, + tr("Confirm join"), + tr("Do you really want to join %1?").arg(QString::fromStdString(room_id)))) + return; + + http::client()->join_room( + room_id, via, [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) { + if (err) { + emit showNotification( + tr("Failed to join room: %1").arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + emit tr("You joined the room"); + + // We remove any invites with the same room_id. + try { + cache::removeInvite(room_id); + } catch (const lmdb::error &e) { + emit showNotification(tr("Failed to remove invite: %1").arg(e.what())); + } + + view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id)); + }); } void ChatPage::createRoom(const mtx::requests::CreateRoom &req) { - http::client()->create_room( - req, [this](const mtx::responses::CreateRoom &res, mtx::http::RequestErr err) { - if (err) { - const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); - const auto error = err->matrix_error.error; - const int status_code = static_cast(err->status_code); - - nhlog::net()->warn( - "failed to create room: {} {} ({})", error, err_code, status_code); - - emit showNotification( - tr("Room creation failed: %1").arg(QString::fromStdString(error))); - return; - } - - QString newRoomId = QString::fromStdString(res.room_id.to_string()); - emit showNotification(tr("Room %1 created.").arg(newRoomId)); - emit newRoom(newRoomId); - emit changeToRoom(newRoomId); - }); + http::client()->create_room( + req, [this](const mtx::responses::CreateRoom &res, mtx::http::RequestErr err) { + if (err) { + const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); + const auto error = err->matrix_error.error; + const int status_code = static_cast(err->status_code); + + nhlog::net()->warn("failed to create room: {} {} ({})", error, err_code, status_code); + + emit showNotification( + tr("Room creation failed: %1").arg(QString::fromStdString(error))); + return; + } + + QString newRoomId = QString::fromStdString(res.room_id.to_string()); + emit showNotification(tr("Room %1 created.").arg(newRoomId)); + emit newRoom(newRoomId); + emit changeToRoom(newRoomId); + }); } void ChatPage::leaveRoom(const QString &room_id) { - http::client()->leave_room( - room_id.toStdString(), - [this, room_id](const mtx::responses::Empty &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to leave room: %1") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } - - emit leftRoom(room_id); - }); + http::client()->leave_room( + room_id.toStdString(), + [this, room_id](const mtx::responses::Empty &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to leave room: %1") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + emit leftRoom(room_id); + }); } void ChatPage::changeRoom(const QString &room_id) { - view_manager_->rooms()->setCurrentRoom(room_id); + view_manager_->rooms()->setCurrentRoom(room_id); } void ChatPage::inviteUser(QString userid, QString reason) { - auto room = currentRoom(); - - if (QMessageBox::question(this, - tr("Confirm invite"), - tr("Do you really want to invite %1 (%2)?") - .arg(cache::displayName(room, userid)) - .arg(userid)) != QMessageBox::Yes) - return; - - http::client()->invite_user( - room.toStdString(), - userid.toStdString(), - [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to invite %1 to %2: %3") - .arg(userid) - .arg(room) - .arg(QString::fromStdString(err->matrix_error.error))); - } else - emit showNotification(tr("Invited user: %1").arg(userid)); - }, - reason.trimmed().toStdString()); + auto room = currentRoom(); + + if (QMessageBox::question(this, + tr("Confirm invite"), + tr("Do you really want to invite %1 (%2)?") + .arg(cache::displayName(room, userid)) + .arg(userid)) != QMessageBox::Yes) + return; + + http::client()->invite_user( + room.toStdString(), + userid.toStdString(), + [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to invite %1 to %2: %3") + .arg(userid) + .arg(room) + .arg(QString::fromStdString(err->matrix_error.error))); + } else + emit showNotification(tr("Invited user: %1").arg(userid)); + }, + reason.trimmed().toStdString()); } void ChatPage::kickUser(QString userid, QString reason) { - auto room = currentRoom(); - - if (QMessageBox::question(this, - tr("Confirm kick"), - tr("Do you really want to kick %1 (%2)?") - .arg(cache::displayName(room, userid)) - .arg(userid)) != QMessageBox::Yes) - return; - - http::client()->kick_user( - room.toStdString(), - userid.toStdString(), - [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to kick %1 from %2: %3") - .arg(userid) - .arg(room) - .arg(QString::fromStdString(err->matrix_error.error))); - } else - emit showNotification(tr("Kicked user: %1").arg(userid)); - }, - reason.trimmed().toStdString()); + auto room = currentRoom(); + + if (QMessageBox::question(this, + tr("Confirm kick"), + tr("Do you really want to kick %1 (%2)?") + .arg(cache::displayName(room, userid)) + .arg(userid)) != QMessageBox::Yes) + return; + + http::client()->kick_user( + room.toStdString(), + userid.toStdString(), + [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to kick %1 from %2: %3") + .arg(userid) + .arg(room) + .arg(QString::fromStdString(err->matrix_error.error))); + } else + emit showNotification(tr("Kicked user: %1").arg(userid)); + }, + reason.trimmed().toStdString()); } void ChatPage::banUser(QString userid, QString reason) { - auto room = currentRoom(); - - if (QMessageBox::question(this, - tr("Confirm ban"), - tr("Do you really want to ban %1 (%2)?") - .arg(cache::displayName(room, userid)) - .arg(userid)) != QMessageBox::Yes) - return; - - http::client()->ban_user( - room.toStdString(), - userid.toStdString(), - [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to ban %1 in %2: %3") - .arg(userid) - .arg(room) - .arg(QString::fromStdString(err->matrix_error.error))); - } else - emit showNotification(tr("Banned user: %1").arg(userid)); - }, - reason.trimmed().toStdString()); + auto room = currentRoom(); + + if (QMessageBox::question(this, + tr("Confirm ban"), + tr("Do you really want to ban %1 (%2)?") + .arg(cache::displayName(room, userid)) + .arg(userid)) != QMessageBox::Yes) + return; + + http::client()->ban_user( + room.toStdString(), + userid.toStdString(), + [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to ban %1 in %2: %3") + .arg(userid) + .arg(room) + .arg(QString::fromStdString(err->matrix_error.error))); + } else + emit showNotification(tr("Banned user: %1").arg(userid)); + }, + reason.trimmed().toStdString()); } void ChatPage::unbanUser(QString userid, QString reason) { - auto room = currentRoom(); - - if (QMessageBox::question(this, - tr("Confirm unban"), - tr("Do you really want to unban %1 (%2)?") - .arg(cache::displayName(room, userid)) - .arg(userid)) != QMessageBox::Yes) - return; - - http::client()->unban_user( - room.toStdString(), - userid.toStdString(), - [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to unban %1 in %2: %3") - .arg(userid) - .arg(room) - .arg(QString::fromStdString(err->matrix_error.error))); - } else - emit showNotification(tr("Unbanned user: %1").arg(userid)); - }, - reason.trimmed().toStdString()); + auto room = currentRoom(); + + if (QMessageBox::question(this, + tr("Confirm unban"), + tr("Do you really want to unban %1 (%2)?") + .arg(cache::displayName(room, userid)) + .arg(userid)) != QMessageBox::Yes) + return; + + http::client()->unban_user( + room.toStdString(), + userid.toStdString(), + [this, userid, room](const mtx::responses::Empty &, mtx::http::RequestErr err) { + if (err) { + emit showNotification(tr("Failed to unban %1 in %2: %3") + .arg(userid) + .arg(room) + .arg(QString::fromStdString(err->matrix_error.error))); + } else + emit showNotification(tr("Unbanned user: %1").arg(userid)); + }, + reason.trimmed().toStdString()); } void ChatPage::receivedSessionKey(const std::string &room_id, const std::string &session_id) { - view_manager_->receivedSessionKey(room_id, session_id); + view_manager_->receivedSessionKey(room_id, session_id); } QString ChatPage::status() const { - return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString())); + return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString())); } void ChatPage::setStatus(const QString &status) { - http::client()->put_presence_status( - currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to set presence status_msg: {}", - err->matrix_error.error); - } - }); + http::client()->put_presence_status( + currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to set presence status_msg: {}", err->matrix_error.error); + } + }); } mtx::presence::PresenceState ChatPage::currentPresence() const { - switch (userSettings_->presence()) { - case UserSettings::Presence::Online: - return mtx::presence::online; - case UserSettings::Presence::Unavailable: - return mtx::presence::unavailable; - case UserSettings::Presence::Offline: - return mtx::presence::offline; - default: - return mtx::presence::online; - } + switch (userSettings_->presence()) { + case UserSettings::Presence::Online: + return mtx::presence::online; + case UserSettings::Presence::Unavailable: + return mtx::presence::unavailable; + case UserSettings::Presence::Offline: + return mtx::presence::offline; + default: + return mtx::presence::online; + } } void ChatPage::verifyOneTimeKeyCountAfterStartup() { - http::client()->upload_keys( - olm::client()->create_upload_keys_request(), - [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) { - if (err) { - nhlog::crypto()->warn("failed to update one-time keys: {} {} {}", - err->matrix_error.error, - static_cast(err->status_code), - static_cast(err->error_code)); - - if (err->status_code < 400 || err->status_code >= 500) - return; - } - - std::map key_counts; - auto count = 0; - if (auto c = res.one_time_key_counts.find(mtx::crypto::SIGNED_CURVE25519); - c == res.one_time_key_counts.end()) { - key_counts[mtx::crypto::SIGNED_CURVE25519] = 0; - } else { - key_counts[mtx::crypto::SIGNED_CURVE25519] = c->second; - count = c->second; - } - - nhlog::crypto()->info( - "Fetched server key count {} {}", count, mtx::crypto::SIGNED_CURVE25519); - - ensureOneTimeKeyCount(key_counts); - }); + http::client()->upload_keys( + olm::client()->create_upload_keys_request(), + [this](const mtx::responses::UploadKeys &res, mtx::http::RequestErr err) { + if (err) { + nhlog::crypto()->warn("failed to update one-time keys: {} {} {}", + err->matrix_error.error, + static_cast(err->status_code), + static_cast(err->error_code)); + + if (err->status_code < 400 || err->status_code >= 500) + return; + } + + std::map key_counts; + auto count = 0; + if (auto c = res.one_time_key_counts.find(mtx::crypto::SIGNED_CURVE25519); + c == res.one_time_key_counts.end()) { + key_counts[mtx::crypto::SIGNED_CURVE25519] = 0; + } else { + key_counts[mtx::crypto::SIGNED_CURVE25519] = c->second; + count = c->second; + } + + nhlog::crypto()->info( + "Fetched server key count {} {}", count, mtx::crypto::SIGNED_CURVE25519); + + ensureOneTimeKeyCount(key_counts); + }); } void ChatPage::ensureOneTimeKeyCount(const std::map &counts) { - if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); count != counts.end()) { - nhlog::crypto()->debug( - "Updated server key count {} {}", count->second, mtx::crypto::SIGNED_CURVE25519); - - if (count->second < MAX_ONETIME_KEYS) { - const int nkeys = MAX_ONETIME_KEYS - count->second; - - nhlog::crypto()->info( - "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519); - olm::client()->generate_one_time_keys(nkeys); - - http::client()->upload_keys( - olm::client()->create_upload_keys_request(), - [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { - if (err) { - nhlog::crypto()->warn( - "failed to update one-time keys: {} {} {}", + if (auto count = counts.find(mtx::crypto::SIGNED_CURVE25519); count != counts.end()) { + nhlog::crypto()->debug( + "Updated server key count {} {}", count->second, mtx::crypto::SIGNED_CURVE25519); + + if (count->second < MAX_ONETIME_KEYS) { + const int nkeys = MAX_ONETIME_KEYS - count->second; + + nhlog::crypto()->info("uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519); + olm::client()->generate_one_time_keys(nkeys); + + http::client()->upload_keys( + olm::client()->create_upload_keys_request(), + [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { + if (err) { + nhlog::crypto()->warn("failed to update one-time keys: {} {} {}", err->matrix_error.error, static_cast(err->status_code), static_cast(err->error_code)); - if (err->status_code < 400 || err->status_code >= 500) - return; - } - - // mark as published anyway, otherwise we may end up in a loop. - olm::mark_keys_as_published(); - }); - } else if (count->second > 2 * MAX_ONETIME_KEYS) { - nhlog::crypto()->warn("too many one-time keys, deleting 1"); - mtx::requests::ClaimKeys req; - req.one_time_keys[http::client()->user_id().to_string()] - [http::client()->device_id()] = - std::string(mtx::crypto::SIGNED_CURVE25519); - http::client()->claim_keys( - req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) { - if (err) - nhlog::crypto()->warn( - "failed to clear 1 one-time key: {} {} {}", + if (err->status_code < 400 || err->status_code >= 500) + return; + } + + // mark as published anyway, otherwise we may end up in a loop. + olm::mark_keys_as_published(); + }); + } else if (count->second > 2 * MAX_ONETIME_KEYS) { + nhlog::crypto()->warn("too many one-time keys, deleting 1"); + mtx::requests::ClaimKeys req; + req.one_time_keys[http::client()->user_id().to_string()][http::client()->device_id()] = + std::string(mtx::crypto::SIGNED_CURVE25519); + http::client()->claim_keys( + req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) { + if (err) + nhlog::crypto()->warn("failed to clear 1 one-time key: {} {} {}", err->matrix_error.error, static_cast(err->status_code), static_cast(err->error_code)); - else - nhlog::crypto()->info("cleared 1 one-time key"); - }); - } + else + nhlog::crypto()->info("cleared 1 one-time key"); + }); } + } } void ChatPage::getProfileInfo() { - const auto userid = utils::localUser().toStdString(); + const auto userid = utils::localUser().toStdString(); - http::client()->get_profile( - userid, [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve own profile info"); - return; - } + http::client()->get_profile( + userid, [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve own profile info"); + return; + } - emit setUserDisplayName(QString::fromStdString(res.display_name)); + emit setUserDisplayName(QString::fromStdString(res.display_name)); - emit setUserAvatar(QString::fromStdString(res.avatar_url)); - }); + emit setUserAvatar(QString::fromStdString(res.avatar_url)); + }); } void ChatPage::getBackupVersion() { - if (!UserSettings::instance()->useOnlineKeyBackup()) { - nhlog::crypto()->info("Online key backup disabled."); - return; - } - - http::client()->backup_version( - [this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("Failed to retrieve backup version"); - if (err->status_code == 404) - cache::client()->deleteBackupVersion(); - return; + if (!UserSettings::instance()->useOnlineKeyBackup()) { + nhlog::crypto()->info("Online key backup disabled."); + return; + } + + http::client()->backup_version( + [this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("Failed to retrieve backup version"); + if (err->status_code == 404) + cache::client()->deleteBackupVersion(); + return; + } + + // switch to UI thread for secrets stuff + QTimer::singleShot(0, this, [res] { + auto auth_data = nlohmann::json::parse(res.auth_data); + + if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") { + auto key = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!key) { + nhlog::crypto()->info("No key for online key backup."); + cache::client()->deleteBackupVersion(); + return; } - // switch to UI thread for secrets stuff - QTimer::singleShot(0, this, [res] { - auto auth_data = nlohmann::json::parse(res.auth_data); - - if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") { - auto key = - cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); - if (!key) { - nhlog::crypto()->info("No key for online key backup."); - cache::client()->deleteBackupVersion(); - return; - } - - using namespace mtx::crypto; - auto pubkey = CURVE25519_public_key_from_private( - to_binary_buf(base642bin(*key))); - - if (auth_data["public_key"].get() != pubkey) { - nhlog::crypto()->info( - "Our backup key {} does not match the one " + using namespace mtx::crypto; + auto pubkey = CURVE25519_public_key_from_private(to_binary_buf(base642bin(*key))); + + if (auth_data["public_key"].get() != pubkey) { + nhlog::crypto()->info("Our backup key {} does not match the one " "used in the online backup {}", pubkey, auth_data["public_key"]); - cache::client()->deleteBackupVersion(); - return; - } - - nhlog::crypto()->info("Using online key backup."); - OnlineBackupVersion data{}; - data.algorithm = res.algorithm; - data.version = res.version; - cache::client()->saveBackupVersion(data); - } else { - nhlog::crypto()->info("Unsupported key backup algorithm: {}", - res.algorithm); - cache::client()->deleteBackupVersion(); - } - }); + cache::client()->deleteBackupVersion(); + return; + } + + nhlog::crypto()->info("Using online key backup."); + OnlineBackupVersion data{}; + data.algorithm = res.algorithm; + data.version = res.version; + cache::client()->saveBackupVersion(data); + } else { + nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm); + cache::client()->deleteBackupVersion(); + } }); + }); } void ChatPage::initiateLogout() { - http::client()->logout([this](const mtx::responses::Logout &, mtx::http::RequestErr err) { - if (err) { - // TODO: handle special errors - emit contentLoaded(); - nhlog::net()->warn("failed to logout: {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } + http::client()->logout([this](const mtx::responses::Logout &, mtx::http::RequestErr err) { + if (err) { + // TODO: handle special errors + emit contentLoaded(); + nhlog::net()->warn("failed to logout: {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error); + return; + } - emit loggedOut(); - }); + emit loggedOut(); + }); - emit showOverlayProgressBar(); + emit showOverlayProgressBar(); } template void ChatPage::connectCallMessage() { - connect(callManager_, - qOverload(&CallManager::newMessage), - view_manager_, - qOverload(&TimelineViewManager::queueCallMessage)); + connect(callManager_, + qOverload(&CallManager::newMessage), + view_manager_, + qOverload(&TimelineViewManager::queueCallMessage)); } void ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, const SecretsToDecrypt &secrets) { - QString text = QInputDialog::getText( - ChatPage::instance(), - QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"), - keyDesc.name.empty() - ? QCoreApplication::translate( - "CrossSigningSecrets", - "Enter your recovery key or passphrase to decrypt your secrets:") - : QCoreApplication::translate( - "CrossSigningSecrets", - "Enter your recovery key or passphrase called %1 to decrypt your secrets:") - .arg(QString::fromStdString(keyDesc.name)), - QLineEdit::Password); - - if (text.isEmpty()) - return; - - auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc); - - if (!decryptionKey && keyDesc.passphrase) { - try { - decryptionKey = - mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to derive secret key from passphrase: {}", - e.what()); - } - } - - if (!decryptionKey) { - QMessageBox::information( - ChatPage::instance(), - QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"), - QCoreApplication::translate("CrossSigningSecrets", - "Failed to decrypt secrets with the " - "provided recovery key or passphrase")); - return; + QString text = QInputDialog::getText( + ChatPage::instance(), + QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"), + keyDesc.name.empty() + ? QCoreApplication::translate( + "CrossSigningSecrets", "Enter your recovery key or passphrase to decrypt your secrets:") + : QCoreApplication::translate( + "CrossSigningSecrets", + "Enter your recovery key or passphrase called %1 to decrypt your secrets:") + .arg(QString::fromStdString(keyDesc.name)), + QLineEdit::Password); + + if (text.isEmpty()) + return; + + auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc); + + if (!decryptionKey && keyDesc.passphrase) { + try { + decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to derive secret key from passphrase: {}", e.what()); } + } - for (const auto &[secretName, encryptedSecret] : secrets) { - auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName); - if (!decrypted.empty()) - cache::storeSecret(secretName, decrypted); - } + if (!decryptionKey) { + QMessageBox::information( + ChatPage::instance(), + QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"), + QCoreApplication::translate("CrossSigningSecrets", + "Failed to decrypt secrets with the " + "provided recovery key or passphrase")); + return; + } + + for (const auto &[secretName, encryptedSecret] : secrets) { + auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName); + if (!decrypted.empty()) + cache::storeSecret(secretName, decrypted); + } } void ChatPage::startChat(QString userid) { - auto joined_rooms = cache::joinedRooms(); - auto room_infos = cache::getRoomInfo(joined_rooms); - - for (std::string room_id : joined_rooms) { - if (room_infos[QString::fromStdString(room_id)].member_count == 2) { - auto room_members = cache::roomMembers(room_id); - if (std::find(room_members.begin(), - room_members.end(), - (userid).toStdString()) != room_members.end()) { - view_manager_->rooms()->setCurrentRoom( - QString::fromStdString(room_id)); - return; - } - } - } - - if (QMessageBox::Yes != - QMessageBox::question( - this, - tr("Confirm invite"), - tr("Do you really want to start a private chat with %1?").arg(userid))) + auto joined_rooms = cache::joinedRooms(); + auto room_infos = cache::getRoomInfo(joined_rooms); + + for (std::string room_id : joined_rooms) { + if (room_infos[QString::fromStdString(room_id)].member_count == 2) { + auto room_members = cache::roomMembers(room_id); + if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) != + room_members.end()) { + view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id)); return; - - mtx::requests::CreateRoom req; - req.preset = mtx::requests::Preset::PrivateChat; - req.visibility = mtx::common::RoomVisibility::Private; - if (utils::localUser() != userid) { - req.invite = {userid.toStdString()}; - req.is_direct = true; + } } - emit ChatPage::instance()->createRoom(req); + } + + if (QMessageBox::Yes != + QMessageBox::question( + this, + tr("Confirm invite"), + tr("Do you really want to start a private chat with %1?").arg(userid))) + return; + + mtx::requests::CreateRoom req; + req.preset = mtx::requests::Preset::PrivateChat; + req.visibility = mtx::common::RoomVisibility::Private; + if (utils::localUser() != userid) { + req.invite = {userid.toStdString()}; + req.is_direct = true; + } + emit ChatPage::instance()->createRoom(req); } static QString mxidFromSegments(QStringRef sigil, QStringRef mxid) { - if (mxid.isEmpty()) - return ""; - - auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8()); - - if (sigil == "u") { - return "@" + mxid_; - } else if (sigil == "roomid") { - return "!" + mxid_; - } else if (sigil == "r") { - return "#" + mxid_; - //} else if (sigil == "group") { - // return "+" + mxid_; - } else { - return ""; - } + if (mxid.isEmpty()) + return ""; + + auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8()); + + if (sigil == "u") { + return "@" + mxid_; + } else if (sigil == "roomid") { + return "!" + mxid_; + } else if (sigil == "r") { + return "#" + mxid_; + //} else if (sigil == "group") { + // return "+" + mxid_; + } else { + return ""; + } } bool ChatPage::handleMatrixUri(const QByteArray &uri) { - nhlog::ui()->info("Received uri! {}", uri.toStdString()); - QUrl uri_{QString::fromUtf8(uri)}; - - // Convert matrix.to URIs to proper format - if (uri_.scheme() == "https" && uri_.host() == "matrix.to") { - QString p = uri_.fragment(QUrl::FullyEncoded); - if (p.startsWith("/")) - p.remove(0, 1); - - auto temp = p.split("?"); - QString query; - if (temp.size() >= 2) - query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); - - temp = temp.first().split("/"); - auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); - QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); - if (!identifier.isEmpty()) { - if (identifier.startsWith("@")) { - QByteArray newUri = - "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!query.isEmpty()) - newUri.append("?" + query.toUtf8()); - return handleMatrixUri(QUrl::fromEncoded(newUri)); - } else if (identifier.startsWith("#")) { - QByteArray newUri = - "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!eventId.isEmpty()) - newUri.append( - "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - newUri.append("?" + query.toUtf8()); - return handleMatrixUri(QUrl::fromEncoded(newUri)); - } else if (identifier.startsWith("!")) { - QByteArray newUri = "matrix:roomid/" + QUrl::toPercentEncoding( - identifier.remove(0, 1)); - if (!eventId.isEmpty()) - newUri.append( - "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - newUri.append("?" + query.toUtf8()); - return handleMatrixUri(QUrl::fromEncoded(newUri)); - } - } + nhlog::ui()->info("Received uri! {}", uri.toStdString()); + QUrl uri_{QString::fromUtf8(uri)}; + + // Convert matrix.to URIs to proper format + if (uri_.scheme() == "https" && uri_.host() == "matrix.to") { + QString p = uri_.fragment(QUrl::FullyEncoded); + if (p.startsWith("/")) + p.remove(0, 1); + + auto temp = p.split("?"); + QString query; + if (temp.size() >= 2) + query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); + + temp = temp.first().split("/"); + auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); + QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); + if (!identifier.isEmpty()) { + if (identifier.startsWith("@")) { + QByteArray newUri = "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } else if (identifier.startsWith("#")) { + QByteArray newUri = "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!eventId.isEmpty()) + newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } else if (identifier.startsWith("!")) { + QByteArray newUri = + "matrix:roomid/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!eventId.isEmpty()) + newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } } + } - // non-matrix URIs are not handled by us, return false - if (uri_.scheme() != "matrix") - return false; - - auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded); - if (tempPath.startsWith('/')) - tempPath.remove(0, 1); - auto segments = tempPath.splitRef('/'); - - if (segments.size() != 2 && segments.size() != 4) - return false; - - auto sigil1 = segments[0]; - auto mxid1 = mxidFromSegments(sigil1, segments[1]); - if (mxid1.isEmpty()) - return false; - - QString mxid2; - if (segments.size() == 4 && segments[2] == "e") { - if (segments[3].isEmpty()) - return false; - else - mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8()); - } + // non-matrix URIs are not handled by us, return false + if (uri_.scheme() != "matrix") + return false; - std::vector vias; - QString action; + auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded); + if (tempPath.startsWith('/')) + tempPath.remove(0, 1); + auto segments = tempPath.splitRef('/'); - for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) { - nhlog::ui()->info("item: {}", item.toStdString()); + if (segments.size() != 2 && segments.size() != 4) + return false; - if (item.startsWith("action=")) { - action = item.remove("action="); - } else if (item.startsWith("via=")) { - vias.push_back( - QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString()); - } + auto sigil1 = segments[0]; + auto mxid1 = mxidFromSegments(sigil1, segments[1]); + if (mxid1.isEmpty()) + return false; + + QString mxid2; + if (segments.size() == 4 && segments[2] == "e") { + if (segments[3].isEmpty()) + return false; + else + mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8()); + } + + std::vector vias; + QString action; + + for (QString item : uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&')) { + nhlog::ui()->info("item: {}", item.toStdString()); + + if (item.startsWith("action=")) { + action = item.remove("action="); + } else if (item.startsWith("via=")) { + vias.push_back(QUrl::fromPercentEncoding(item.remove("via=").toUtf8()).toStdString()); } + } - if (sigil1 == "u") { - if (action.isEmpty()) { - auto t = view_manager_->rooms()->currentRoom(); - if (t && - cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) { - t->openUserProfile(mxid1); - return true; - } - emit view_manager_->openGlobalUserProfile(mxid1); - } else if (action == "chat") { - this->startChat(mxid1); - } + if (sigil1 == "u") { + if (action.isEmpty()) { + auto t = view_manager_->rooms()->currentRoom(); + if (t && cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) { + t->openUserProfile(mxid1); return true; - } else if (sigil1 == "roomid") { - auto joined_rooms = cache::joinedRooms(); - auto targetRoomId = mxid1.toStdString(); - - for (auto roomid : joined_rooms) { - if (roomid == targetRoomId) { - view_manager_->rooms()->setCurrentRoom(mxid1); - if (!mxid2.isEmpty()) - view_manager_->showEvent(mxid1, mxid2); - return true; - } - } + } + emit view_manager_->openGlobalUserProfile(mxid1); + } else if (action == "chat") { + this->startChat(mxid1); + } + return true; + } else if (sigil1 == "roomid") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomId = mxid1.toStdString(); - if (action == "join" || action.isEmpty()) { - joinRoomVia(targetRoomId, vias); - return true; - } - return false; - } else if (sigil1 == "r") { - auto joined_rooms = cache::joinedRooms(); - auto targetRoomAlias = mxid1.toStdString(); - - for (auto roomid : joined_rooms) { - auto aliases = cache::client()->getRoomAliases(roomid); - if (aliases) { - if (aliases->alias == targetRoomAlias) { - view_manager_->rooms()->setCurrentRoom( - QString::fromStdString(roomid)); - if (!mxid2.isEmpty()) - view_manager_->showEvent( - QString::fromStdString(roomid), mxid2); - return true; - } - } - } + for (auto roomid : joined_rooms) { + if (roomid == targetRoomId) { + view_manager_->rooms()->setCurrentRoom(mxid1); + if (!mxid2.isEmpty()) + view_manager_->showEvent(mxid1, mxid2); + return true; + } + } - if (action == "join" || action.isEmpty()) { - joinRoomVia(mxid1.toStdString(), vias); - return true; + if (action == "join" || action.isEmpty()) { + joinRoomVia(targetRoomId, vias); + return true; + } + return false; + } else if (sigil1 == "r") { + auto joined_rooms = cache::joinedRooms(); + auto targetRoomAlias = mxid1.toStdString(); + + for (auto roomid : joined_rooms) { + auto aliases = cache::client()->getRoomAliases(roomid); + if (aliases) { + if (aliases->alias == targetRoomAlias) { + view_manager_->rooms()->setCurrentRoom(QString::fromStdString(roomid)); + if (!mxid2.isEmpty()) + view_manager_->showEvent(QString::fromStdString(roomid), mxid2); + return true; } - return false; + } + } + + if (action == "join" || action.isEmpty()) { + joinRoomVia(mxid1.toStdString(), vias); + return true; } return false; + } + return false; } bool ChatPage::handleMatrixUri(const QUrl &uri) { - return handleMatrixUri( - uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); + return handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); } bool ChatPage::isRoomActive(const QString &room_id) { - return isActiveWindow() && currentRoom() == room_id; + return isActiveWindow() && currentRoom() == room_id; } QString ChatPage::currentRoom() const { - if (view_manager_->rooms()->currentRoom()) - return view_manager_->rooms()->currentRoom()->roomId(); - else - return ""; + if (view_manager_->rooms()->currentRoom()) + return view_manager_->rooms()->currentRoom()->roomId(); + else + return ""; } diff --git a/src/ChatPage.h b/src/ChatPage.h index 66e4c6ab..8f3dc53e 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -52,186 +52,181 @@ using SecretsToDecrypt = std::map userSettings, QWidget *parent = nullptr); + ChatPage(QSharedPointer userSettings, QWidget *parent = nullptr); - // Initialize all the components of the UI. - void bootstrap(QString userid, QString homeserver, QString token); + // Initialize all the components of the UI. + void bootstrap(QString userid, QString homeserver, QString token); - static ChatPage *instance() { return instance_; } + static ChatPage *instance() { return instance_; } - QSharedPointer userSettings() { return userSettings_; } - CallManager *callManager() { return callManager_; } - TimelineViewManager *timelineManager() { return view_manager_; } - void deleteConfigs(); + QSharedPointer userSettings() { return userSettings_; } + CallManager *callManager() { return callManager_; } + TimelineViewManager *timelineManager() { return view_manager_; } + void deleteConfigs(); - void initiateLogout(); + void initiateLogout(); - QString status() const; - void setStatus(const QString &status); + QString status() const; + void setStatus(const QString &status); - mtx::presence::PresenceState currentPresence() const; + mtx::presence::PresenceState currentPresence() const; - // TODO(Nico): Get rid of this! - QString currentRoom() const; + // TODO(Nico): Get rid of this! + QString currentRoom() const; public slots: - bool handleMatrixUri(const QByteArray &uri); - bool handleMatrixUri(const QUrl &uri); - - void startChat(QString userid); - void leaveRoom(const QString &room_id); - void createRoom(const mtx::requests::CreateRoom &req); - void joinRoom(const QString &room); - void joinRoomVia(const std::string &room_id, - const std::vector &via, - bool promptForConfirmation = true); - - void inviteUser(QString userid, QString reason); - void kickUser(QString userid, QString reason); - void banUser(QString userid, QString reason); - void unbanUser(QString userid, QString reason); - - void receivedSessionKey(const std::string &room_id, const std::string &session_id); - void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, - const SecretsToDecrypt &secrets); + bool handleMatrixUri(const QByteArray &uri); + bool handleMatrixUri(const QUrl &uri); + + void startChat(QString userid); + void leaveRoom(const QString &room_id); + void createRoom(const mtx::requests::CreateRoom &req); + void joinRoom(const QString &room); + void joinRoomVia(const std::string &room_id, + const std::vector &via, + bool promptForConfirmation = true); + + void inviteUser(QString userid, QString reason); + void kickUser(QString userid, QString reason); + void banUser(QString userid, QString reason); + void unbanUser(QString userid, QString reason); + + void receivedSessionKey(const std::string &room_id, const std::string &session_id); + void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, + const SecretsToDecrypt &secrets); signals: - void connectionLost(); - void connectionRestored(); - - void notificationsRetrieved(const mtx::responses::Notifications &); - void highlightedNotifsRetrieved(const mtx::responses::Notifications &, - const QPoint widgetPos); - - void contentLoaded(); - void closing(); - void changeWindowTitle(const int); - void unreadMessages(int count); - void showNotification(const QString &msg); - void showLoginPage(const QString &msg); - void showUserSettingsPage(); - void showOverlayProgressBar(); - - void ownProfileOk(); - void setUserDisplayName(const QString &name); - void setUserAvatar(const QString &avatar); - void loggedOut(); - - void trySyncCb(); - void tryDelayedSyncCb(); - void tryInitialSyncCb(); - void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token); - void leftRoom(const QString &room_id); - void newRoom(const QString &room_id); - void changeToRoom(const QString &room_id); - - void initializeViews(const mtx::responses::Rooms &rooms); - void initializeEmptyViews(); - void initializeMentions(const QMap ¬ifs); - void syncUI(const mtx::responses::Rooms &rooms); - void dropToLoginPageCb(const QString &msg); - - void notifyMessage(const QString &roomid, - const QString &eventid, - const QString &roomname, - const QString &sender, - const QString &message, - const QImage &icon); - - void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state); - void themeChanged(); - void decryptSidebarChanged(); - void chatFocusChanged(const bool focused); - - //! Signals for device verificaiton - void receivedDeviceVerificationAccept( - const mtx::events::msg::KeyVerificationAccept &message); - void receivedDeviceVerificationRequest( - const mtx::events::msg::KeyVerificationRequest &message, - std::string sender); - void receivedRoomDeviceVerificationRequest( - const mtx::events::RoomEvent &message, - TimelineModel *model); - void receivedDeviceVerificationCancel( - const mtx::events::msg::KeyVerificationCancel &message); - void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message); - void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message); - void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message, - std::string sender); - void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message); - void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message); - - void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, - const SecretsToDecrypt &secrets); + void connectionLost(); + void connectionRestored(); + + void notificationsRetrieved(const mtx::responses::Notifications &); + void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos); + + void contentLoaded(); + void closing(); + void changeWindowTitle(const int); + void unreadMessages(int count); + void showNotification(const QString &msg); + void showLoginPage(const QString &msg); + void showUserSettingsPage(); + void showOverlayProgressBar(); + + void ownProfileOk(); + void setUserDisplayName(const QString &name); + void setUserAvatar(const QString &avatar); + void loggedOut(); + + void trySyncCb(); + void tryDelayedSyncCb(); + void tryInitialSyncCb(); + void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token); + void leftRoom(const QString &room_id); + void newRoom(const QString &room_id); + void changeToRoom(const QString &room_id); + + void initializeViews(const mtx::responses::Rooms &rooms); + void initializeEmptyViews(); + void initializeMentions(const QMap ¬ifs); + void syncUI(const mtx::responses::Rooms &rooms); + void dropToLoginPageCb(const QString &msg); + + void notifyMessage(const QString &roomid, + const QString &eventid, + const QString &roomname, + const QString &sender, + const QString &message, + const QImage &icon); + + void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state); + void themeChanged(); + void decryptSidebarChanged(); + void chatFocusChanged(const bool focused); + + //! Signals for device verificaiton + void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message); + void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &message, + std::string sender); + void receivedRoomDeviceVerificationRequest( + const mtx::events::RoomEvent &message, + TimelineModel *model); + void receivedDeviceVerificationCancel(const mtx::events::msg::KeyVerificationCancel &message); + void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message); + void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message); + void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message, + std::string sender); + void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message); + void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message); + + void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, + const SecretsToDecrypt &secrets); private slots: - void logout(); - void removeRoom(const QString &room_id); - void changeRoom(const QString &room_id); - void dropToLoginPage(const QString &msg); + void logout(); + void removeRoom(const QString &room_id); + void changeRoom(const QString &room_id); + void dropToLoginPage(const QString &msg); - void handleSyncResponse(const mtx::responses::Sync &res, - const std::string &prev_batch_token); + void handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token); private: - static ChatPage *instance_; + static ChatPage *instance_; - void startInitialSync(); - void tryInitialSync(); - void trySync(); - void verifyOneTimeKeyCountAfterStartup(); - void ensureOneTimeKeyCount(const std::map &counts); - void getProfileInfo(); - void getBackupVersion(); + void startInitialSync(); + void tryInitialSync(); + void trySync(); + void verifyOneTimeKeyCountAfterStartup(); + void ensureOneTimeKeyCount(const std::map &counts); + void getProfileInfo(); + void getBackupVersion(); - //! Check if the given room is currently open. - bool isRoomActive(const QString &room_id); + //! Check if the given room is currently open. + bool isRoomActive(const QString &room_id); - using UserID = QString; - using Membership = mtx::events::StateEvent; - using Memberships = std::map; + using UserID = QString; + using Membership = mtx::events::StateEvent; + using Memberships = std::map; - void loadStateFromCache(); - void resetUI(); + void loadStateFromCache(); + void resetUI(); - template - Memberships getMemberships(const std::vector &events) const; + template + Memberships getMemberships(const std::vector &events) const; - //! Send desktop notification for the received messages. - void sendNotifications(const mtx::responses::Notifications &); + //! Send desktop notification for the received messages. + void sendNotifications(const mtx::responses::Notifications &); - template - void connectCallMessage(); + template + void connectCallMessage(); - QHBoxLayout *topLayout_; + QHBoxLayout *topLayout_; - TimelineViewManager *view_manager_; + TimelineViewManager *view_manager_; - QTimer connectivityTimer_; - std::atomic_bool isConnected_; + QTimer connectivityTimer_; + std::atomic_bool isConnected_; - // Global user settings. - QSharedPointer userSettings_; + // Global user settings. + QSharedPointer userSettings_; - NotificationsManager notificationsManager; - CallManager *callManager_; + NotificationsManager notificationsManager; + CallManager *callManager_; }; template std::map> ChatPage::getMemberships(const std::vector &collection) const { - std::map> memberships; + std::map> memberships; - using Member = mtx::events::StateEvent; + using Member = mtx::events::StateEvent; - for (const auto &event : collection) { - if (auto member = std::get_if(event)) { - memberships.emplace(member->state_key, *member); - } + for (const auto &event : collection) { + if (auto member = std::get_if(event)) { + memberships.emplace(member->state_key, *member); } + } - return memberships; + return memberships; } diff --git a/src/Clipboard.cpp b/src/Clipboard.cpp index d4d5bab7..93d913be 100644 --- a/src/Clipboard.cpp +++ b/src/Clipboard.cpp @@ -10,18 +10,17 @@ Clipboard::Clipboard(QObject *parent) : QObject(parent) { - connect( - QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged); + connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged); } void Clipboard::setText(QString text) { - QGuiApplication::clipboard()->setText(text); + QGuiApplication::clipboard()->setText(text); } QString Clipboard::text() const { - return QGuiApplication::clipboard()->text(); + return QGuiApplication::clipboard()->text(); } diff --git a/src/Clipboard.h b/src/Clipboard.h index fa74da22..1a6584ca 100644 --- a/src/Clipboard.h +++ b/src/Clipboard.h @@ -9,14 +9,14 @@ class Clipboard : public QObject { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) public: - Clipboard(QObject *parent = nullptr); + Clipboard(QObject *parent = nullptr); - QString text() const; - void setText(QString text_); + QString text() const; + void setText(QString text_); signals: - void textChanged(); + void textChanged(); }; diff --git a/src/ColorImageProvider.cpp b/src/ColorImageProvider.cpp index 41fd5d8f..9c371c8c 100644 --- a/src/ColorImageProvider.cpp +++ b/src/ColorImageProvider.cpp @@ -9,23 +9,23 @@ QPixmap ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &) { - auto args = id.split('?'); + auto args = id.split('?'); - QPixmap source(args[0]); + QPixmap source(args[0]); - if (size) - *size = QSize(source.width(), source.height()); + if (size) + *size = QSize(source.width(), source.height()); - if (args.size() < 2) - return source; + if (args.size() < 2) + return source; - QColor color(args[1]); + QColor color(args[1]); - QPixmap colorized = source; - QPainter painter(&colorized); - painter.setCompositionMode(QPainter::CompositionMode_SourceIn); - painter.fillRect(colorized.rect(), color); - painter.end(); + QPixmap colorized = source; + QPainter painter(&colorized); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + painter.fillRect(colorized.rect(), color); + painter.end(); - return colorized; + return colorized; } diff --git a/src/ColorImageProvider.h b/src/ColorImageProvider.h index 9ae8c85e..f2997e0a 100644 --- a/src/ColorImageProvider.h +++ b/src/ColorImageProvider.h @@ -7,9 +7,9 @@ class ColorImageProvider : public QQuickImageProvider { public: - ColorImageProvider() - : QQuickImageProvider(QQuickImageProvider::Pixmap) - {} + ColorImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) + {} - QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; }; diff --git a/src/CombinedImagePackModel.cpp b/src/CombinedImagePackModel.cpp index 341a34ec..9a52f810 100644 --- a/src/CombinedImagePackModel.cpp +++ b/src/CombinedImagePackModel.cpp @@ -13,65 +13,65 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, : QAbstractListModel(parent) , room_id(roomId) { - auto packs = cache::client()->getImagePacks(room_id, stickers); + auto packs = cache::client()->getImagePacks(room_id, stickers); - for (const auto &pack : packs) { - QString packname = - pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : ""; + for (const auto &pack : packs) { + QString packname = + pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : ""; - for (const auto &img : pack.pack.images) { - ImageDesc i{}; - i.shortcode = QString::fromStdString(img.first); - i.packname = packname; - i.image = img.second; - images.push_back(std::move(i)); - } + for (const auto &img : pack.pack.images) { + ImageDesc i{}; + i.shortcode = QString::fromStdString(img.first); + i.packname = packname; + i.image = img.second; + images.push_back(std::move(i)); } + } } int CombinedImagePackModel::rowCount(const QModelIndex &) const { - return (int)images.size(); + return (int)images.size(); } QHash CombinedImagePackModel::roleNames() const { - return { - {CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::Url, "url"}, - {Roles::ShortCode, "shortcode"}, - {Roles::Body, "body"}, - {Roles::PackName, "packname"}, - {Roles::OriginalRow, "originalRow"}, - }; + return { + {CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::Url, "url"}, + {Roles::ShortCode, "shortcode"}, + {Roles::Body, "body"}, + {Roles::PackName, "packname"}, + {Roles::OriginalRow, "originalRow"}, + }; } QVariant CombinedImagePackModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: - return QString::fromStdString(images[index.row()].image.url); - case Roles::Url: - return QString::fromStdString(images[index.row()].image.url); - case CompletionModel::SearchRole: - case Roles::ShortCode: - return images[index.row()].shortcode; - case CompletionModel::SearchRole2: - case Roles::Body: - return QString::fromStdString(images[index.row()].image.body); - case Roles::PackName: - return images[index.row()].packname; - case Roles::OriginalRow: - return index.row(); - default: - return {}; - } + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + return QString::fromStdString(images[index.row()].image.url); + case Roles::Url: + return QString::fromStdString(images[index.row()].image.url); + case CompletionModel::SearchRole: + case Roles::ShortCode: + return images[index.row()].shortcode; + case CompletionModel::SearchRole2: + case Roles::Body: + return QString::fromStdString(images[index.row()].image.body); + case Roles::PackName: + return images[index.row()].packname; + case Roles::OriginalRow: + return index.row(); + default: + return {}; } - return {}; + } + return {}; } diff --git a/src/CombinedImagePackModel.h b/src/CombinedImagePackModel.h index f0f69799..ec49b325 100644 --- a/src/CombinedImagePackModel.h +++ b/src/CombinedImagePackModel.h @@ -10,39 +10,39 @@ class CombinedImagePackModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum Roles - { - Url = Qt::UserRole, - ShortCode, - Body, - PackName, - OriginalRow, - }; - - CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - - mtx::events::msc2545::PackImage imageAt(int row) - { - if (row < 0 || static_cast(row) >= images.size()) - return {}; - return images.at(static_cast(row)).image; - } + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + PackName, + OriginalRow, + }; + + CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + mtx::events::msc2545::PackImage imageAt(int row) + { + if (row < 0 || static_cast(row) >= images.size()) + return {}; + return images.at(static_cast(row)).image; + } private: - std::string room_id; + std::string room_id; - struct ImageDesc - { - QString shortcode; - QString packname; + struct ImageDesc + { + QString shortcode; + QString packname; - mtx::events::msc2545::PackImage image; - }; + mtx::events::msc2545::PackImage image; + }; - std::vector images; + std::vector images; }; diff --git a/src/CompletionModelRoles.h b/src/CompletionModelRoles.h index 8505e761..9a735d60 100644 --- a/src/CompletionModelRoles.h +++ b/src/CompletionModelRoles.h @@ -12,8 +12,8 @@ namespace CompletionModel { // Start at Qt::UserRole * 2 to prevent clashes enum Roles { - CompletionRole = Qt::UserRole * 2, // The string to replace the active completion - SearchRole, // String completer uses for search - SearchRole2, // Secondary string completer uses for search + CompletionRole = Qt::UserRole * 2, // The string to replace the active completion + SearchRole, // String completer uses for search + SearchRole2, // Secondary string completer uses for search }; } diff --git a/src/CompletionProxyModel.cpp b/src/CompletionProxyModel.cpp index e68944c7..454f54b7 100644 --- a/src/CompletionProxyModel.cpp +++ b/src/CompletionProxyModel.cpp @@ -18,154 +18,154 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model, , maxMistakes_(max_mistakes) , max_completions_(max_completions) { - setSourceModel(model); - QChar splitPoints(' '); - - // insert all the full texts - for (int i = 0; i < sourceModel()->rowCount(); i++) { - if (static_cast(i) < max_completions_) - mapping.push_back(i); - - auto string1 = sourceModel() - ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole) - .toString() - .toLower(); - if (!string1.isEmpty()) - trie_.insert(string1.toUcs4(), i); - - auto string2 = sourceModel() - ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2) - .toString() - .toLower(); - if (!string2.isEmpty()) - trie_.insert(string2.toUcs4(), i); + setSourceModel(model); + QChar splitPoints(' '); + + // insert all the full texts + for (int i = 0; i < sourceModel()->rowCount(); i++) { + if (static_cast(i) < max_completions_) + mapping.push_back(i); + + auto string1 = sourceModel() + ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole) + .toString() + .toLower(); + if (!string1.isEmpty()) + trie_.insert(string1.toUcs4(), i); + + auto string2 = sourceModel() + ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2) + .toString() + .toLower(); + if (!string2.isEmpty()) + trie_.insert(string2.toUcs4(), i); + } + + // insert the partial matches + for (int i = 0; i < sourceModel()->rowCount(); i++) { + auto string1 = sourceModel() + ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole) + .toString() + .toLower(); + + for (const auto &e : string1.splitRef(splitPoints)) { + if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14 + trie_.insert(e.toUcs4(), i); } - // insert the partial matches - for (int i = 0; i < sourceModel()->rowCount(); i++) { - auto string1 = sourceModel() - ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole) - .toString() - .toLower(); - - for (const auto &e : string1.splitRef(splitPoints)) { - if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14 - trie_.insert(e.toUcs4(), i); - } - - auto string2 = sourceModel() - ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2) - .toString() - .toLower(); - - if (!string2.isEmpty()) { - for (const auto &e : string2.splitRef(splitPoints)) { - if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14 - trie_.insert(e.toUcs4(), i); - } - } - } + auto string2 = sourceModel() + ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2) + .toString() + .toLower(); - connect( - this, - &CompletionProxyModel::newSearchString, - this, - [this](QString s) { - s.remove(":"); - s.remove("@"); - searchString_ = s.toLower(); - invalidate(); - }, - Qt::QueuedConnection); + if (!string2.isEmpty()) { + for (const auto &e : string2.splitRef(splitPoints)) { + if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14 + trie_.insert(e.toUcs4(), i); + } + } + } + + connect( + this, + &CompletionProxyModel::newSearchString, + this, + [this](QString s) { + s.remove(":"); + s.remove("@"); + searchString_ = s.toLower(); + invalidate(); + }, + Qt::QueuedConnection); } void CompletionProxyModel::invalidate() { - auto key = searchString_.toUcs4(); - beginResetModel(); - if (!key.empty()) // return default model data, if no search string - mapping = trie_.search(key, max_completions_, maxMistakes_); - endResetModel(); + auto key = searchString_.toUcs4(); + beginResetModel(); + if (!key.empty()) // return default model data, if no search string + mapping = trie_.search(key, max_completions_, maxMistakes_); + endResetModel(); } QHash CompletionProxyModel::roleNames() const { - return this->sourceModel()->roleNames(); + return this->sourceModel()->roleNames(); } int CompletionProxyModel::rowCount(const QModelIndex &) const { - if (searchString_.isEmpty()) - return std::min(static_cast(std::min(max_completions_, - std::numeric_limits::max())), - sourceModel()->rowCount()); - else - return (int)mapping.size(); + if (searchString_.isEmpty()) + return std::min( + static_cast(std::min(max_completions_, std::numeric_limits::max())), + sourceModel()->rowCount()); + else + return (int)mapping.size(); } QModelIndex CompletionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { - // return default model data, if no search string - if (searchString_.isEmpty()) { - return index(sourceIndex.row(), 0); - } - - for (int i = 0; i < (int)mapping.size(); i++) { - if (mapping[i] == sourceIndex.row()) { - return index(i, 0); - } + // return default model data, if no search string + if (searchString_.isEmpty()) { + return index(sourceIndex.row(), 0); + } + + for (int i = 0; i < (int)mapping.size(); i++) { + if (mapping[i] == sourceIndex.row()) { + return index(i, 0); } - return QModelIndex(); + } + return QModelIndex(); } QModelIndex CompletionProxyModel::mapToSource(const QModelIndex &proxyIndex) const { - auto row = proxyIndex.row(); + auto row = proxyIndex.row(); - // return default model data, if no search string - if (searchString_.isEmpty()) { - return index(row, 0); - } + // return default model data, if no search string + if (searchString_.isEmpty()) { + return index(row, 0); + } - if (row < 0 || row >= (int)mapping.size()) - return QModelIndex(); + if (row < 0 || row >= (int)mapping.size()) + return QModelIndex(); - return sourceModel()->index(mapping[row], 0); + return sourceModel()->index(mapping[row], 0); } QModelIndex CompletionProxyModel::index(int row, int column, const QModelIndex &) const { - return createIndex(row, column); + return createIndex(row, column); } QModelIndex CompletionProxyModel::parent(const QModelIndex &) const { - return QModelIndex{}; + return QModelIndex{}; } int CompletionProxyModel::columnCount(const QModelIndex &) const { - return sourceModel()->columnCount(); + return sourceModel()->columnCount(); } QVariant CompletionProxyModel::completionAt(int i) const { - if (i >= 0 && i < rowCount()) - return data(index(i, 0), CompletionModel::CompletionRole); - else - return {}; + if (i >= 0 && i < rowCount()) + return data(index(i, 0), CompletionModel::CompletionRole); + else + return {}; } void CompletionProxyModel::setSearchString(QString s) { - emit newSearchString(s); + emit newSearchString(s); } diff --git a/src/CompletionProxyModel.h b/src/CompletionProxyModel.h index d85d9343..c6331a2d 100644 --- a/src/CompletionProxyModel.h +++ b/src/CompletionProxyModel.h @@ -11,179 +11,176 @@ template struct trie { - std::vector values; - std::map next; - - void insert(const QVector &keys, const Value &v) - { - auto t = this; - for (const auto k : keys) { - t = &t->next[k]; - } - - t->values.push_back(v); + std::vector values; + std::map next; + + void insert(const QVector &keys, const Value &v) + { + auto t = this; + for (const auto k : keys) { + t = &t->next[k]; } - std::vector valuesAndSubvalues(size_t limit = -1) const - { - std::vector ret; - if (limit < 200) - ret.reserve(limit); - - for (const auto &v : values) { - if (ret.size() >= limit) - return ret; - else - ret.push_back(v); - } + t->values.push_back(v); + } - for (const auto &[k, t] : next) { - (void)k; - if (ret.size() >= limit) - return ret; - else { - auto temp = t.valuesAndSubvalues(limit - ret.size()); - for (auto &&v : temp) { - if (ret.size() >= limit) - return ret; - - if (std::find(ret.begin(), ret.end(), v) == ret.end()) { - ret.push_back(std::move(v)); - } - } - } - } + std::vector valuesAndSubvalues(size_t limit = -1) const + { + std::vector ret; + if (limit < 200) + ret.reserve(limit); + for (const auto &v : values) { + if (ret.size() >= limit) return ret; + else + ret.push_back(v); } - std::vector search(const QVector &keys, //< TODO(Nico): replace this with a span - size_t result_count_limit, - size_t max_edit_distance_ = 2) const - { - std::vector ret; - if (!result_count_limit) + for (const auto &[k, t] : next) { + (void)k; + if (ret.size() >= limit) + return ret; + else { + auto temp = t.valuesAndSubvalues(limit - ret.size()); + for (auto &&v : temp) { + if (ret.size() >= limit) return ret; - if (keys.isEmpty()) - return valuesAndSubvalues(result_count_limit); + if (std::find(ret.begin(), ret.end(), v) == ret.end()) { + ret.push_back(std::move(v)); + } + } + } + } - auto append = [&ret, result_count_limit](std::vector &&in) { - for (auto &&v : in) { - if (ret.size() >= result_count_limit) - return; + return ret; + } - if (std::find(ret.begin(), ret.end(), v) == ret.end()) { - ret.push_back(std::move(v)); - } - } - }; - - auto limit = [&ret, result_count_limit] { - return std::min(result_count_limit, (result_count_limit - ret.size()) * 2); - }; - - // Try first exact matches, then with maximum errors - for (size_t max_edit_distance = 0; - max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit; - max_edit_distance += 1) { - if (max_edit_distance && ret.size() < result_count_limit) { - max_edit_distance -= 1; - - // swap chars case - if (keys.size() >= 2) { - auto t = this; - for (int i = 1; i >= 0; i--) { - if (auto e = t->next.find(keys[i]); - e != t->next.end()) { - t = &e->second; - } else { - t = nullptr; - break; - } - } - - if (t) { - append(t->search( - keys.mid(2), limit(), max_edit_distance)); - } - } - - // insert case - for (const auto &[k, t] : this->next) { - if (k == keys[0]) - continue; - if (ret.size() >= limit()) - break; - - // insert - append(t.search(keys, limit(), max_edit_distance)); - } - - // delete character case - append(this->search(keys.mid(1), limit(), max_edit_distance)); - - // substitute case - for (const auto &[k, t] : this->next) { - if (k == keys[0]) - continue; - if (ret.size() >= limit()) - break; - - // substitute - append(t.search(keys.mid(1), limit(), max_edit_distance)); - } - - max_edit_distance += 1; - } + std::vector search(const QVector &keys, //< TODO(Nico): replace this with a span + size_t result_count_limit, + size_t max_edit_distance_ = 2) const + { + std::vector ret; + if (!result_count_limit) + return ret; + + if (keys.isEmpty()) + return valuesAndSubvalues(result_count_limit); + + auto append = [&ret, result_count_limit](std::vector &&in) { + for (auto &&v : in) { + if (ret.size() >= result_count_limit) + return; - if (auto e = this->next.find(keys[0]); e != this->next.end()) { - append(e->second.search(keys.mid(1), limit(), max_edit_distance)); + if (std::find(ret.begin(), ret.end(), v) == ret.end()) { + ret.push_back(std::move(v)); + } + } + }; + + auto limit = [&ret, result_count_limit] { + return std::min(result_count_limit, (result_count_limit - ret.size()) * 2); + }; + + // Try first exact matches, then with maximum errors + for (size_t max_edit_distance = 0; + max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit; + max_edit_distance += 1) { + if (max_edit_distance && ret.size() < result_count_limit) { + max_edit_distance -= 1; + + // swap chars case + if (keys.size() >= 2) { + auto t = this; + for (int i = 1; i >= 0; i--) { + if (auto e = t->next.find(keys[i]); e != t->next.end()) { + t = &e->second; + } else { + t = nullptr; + break; } + } + + if (t) { + append(t->search(keys.mid(2), limit(), max_edit_distance)); + } } - return ret; + // insert case + for (const auto &[k, t] : this->next) { + if (k == keys[0]) + continue; + if (ret.size() >= limit()) + break; + + // insert + append(t.search(keys, limit(), max_edit_distance)); + } + + // delete character case + append(this->search(keys.mid(1), limit(), max_edit_distance)); + + // substitute case + for (const auto &[k, t] : this->next) { + if (k == keys[0]) + continue; + if (ret.size() >= limit()) + break; + + // substitute + append(t.search(keys.mid(1), limit(), max_edit_distance)); + } + + max_edit_distance += 1; + } + + if (auto e = this->next.find(keys[0]); e != this->next.end()) { + append(e->second.search(keys.mid(1), limit(), max_edit_distance)); + } } + + return ret; + } }; class CompletionProxyModel : public QAbstractProxyModel { - Q_OBJECT - Q_PROPERTY( - QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString) + Q_OBJECT + Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString) public: - CompletionProxyModel(QAbstractItemModel *model, - int max_mistakes = 2, - size_t max_completions = 7, - QObject *parent = nullptr); + CompletionProxyModel(QAbstractItemModel *model, + int max_mistakes = 2, + size_t max_completions = 7, + QObject *parent = nullptr); - void invalidate(); + void invalidate(); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &) const override; - QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; - QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; - QModelIndex index(int row, - int column, - const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &) const override; + QModelIndex index(int row, + int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &) const override; public slots: - QVariant completionAt(int i) const; + QVariant completionAt(int i) const; - void setSearchString(QString s); - QString searchString() const { return searchString_; } + void setSearchString(QString s); + QString searchString() const { return searchString_; } signals: - void newSearchString(QString); + void newSearchString(QString); private: - QString searchString_; - trie trie_; - std::vector mapping; - int maxMistakes_; - size_t max_completions_; + QString searchString_; + trie trie_; + std::vector mapping; + int maxMistakes_; + size_t max_completions_; }; diff --git a/src/DeviceVerificationFlow.cpp b/src/DeviceVerificationFlow.cpp index 1760ea9a..d4e27022 100644 --- a/src/DeviceVerificationFlow.cpp +++ b/src/DeviceVerificationFlow.cpp @@ -38,685 +38,653 @@ DeviceVerificationFlow::DeviceVerificationFlow(QObject *, , deviceId(deviceId_) , model_(model) { - timeout = new QTimer(this); - timeout->setSingleShot(true); - this->sas = olm::client()->sas_init(); - this->isMacVerified = false; - - auto user_id = userID.toStdString(); - this->toClient = mtx::identifiers::parse(user_id); - cache::client()->query_keys( - user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - mtx::errors::to_string(err->matrix_error.errcode), - static_cast(err->status_code)); - return; - } - - if (!this->deviceId.isEmpty() && - (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } - - this->their_keys = res; - }); - - cache::client()->query_keys( - http::client()->user_id().to_string(), - [this](const UserKeyCache &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - mtx::errors::to_string(err->matrix_error.errcode), - static_cast(err->status_code)); - return; - } - - if (res.master_keys.keys.empty()) - return; - - if (auto status = - cache::verificationStatus(http::client()->user_id().to_string()); - status && status->user_verified == crypto::Trust::Verified) - this->our_trusted_master_key = res.master_keys.keys.begin()->second; - }); - - if (model) { - connect(this->model_, - &TimelineModel::updateFlowEventId, - this, - [this](std::string event_id_) { - this->relation.rel_type = mtx::common::RelationType::Reference; - this->relation.event_id = event_id_; - this->transaction_id = event_id_; - }); - } - - connect(timeout, &QTimer::timeout, this, [this]() { - nhlog::crypto()->info("verification: timeout"); - if (state_ != Success && state_ != Failed) - this->cancelVerification(DeviceVerificationFlow::Error::Timeout); - }); - - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationStart, - this, - &DeviceVerificationFlow::handleStartMessage); - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationAccept, - this, - [this](const mtx::events::msg::KeyVerificationAccept &msg) { - nhlog::crypto()->info("verification: received accept"); - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") && - (msg.hash == "sha256") && - (msg.message_authentication_code == "hkdf-hmac-sha256")) { - this->commitment = msg.commitment; - if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.short_authentication_string.end()) { - this->method = mtx::events::msg::SASMethods::Emoji; - } else { - this->method = mtx::events::msg::SASMethods::Decimal; - } - this->mac_method = msg.message_authentication_code; - this->sendVerificationKey(); - } else { - this->cancelVerification( - DeviceVerificationFlow::Error::UnknownMethod); - } - }); - - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationCancel, - this, - [this](const mtx::events::msg::KeyVerificationCancel &msg) { - nhlog::crypto()->info("verification: received cancel"); - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - error_ = User; - emit errorChanged(); - setState(Failed); - }); - - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationKey, - this, - [this](const mtx::events::msg::KeyVerificationKey &msg) { - nhlog::crypto()->info("verification: received key"); - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - - if (sender) { - if (state_ != WaitingForOtherToAccept) { - this->cancelVerification(OutOfOrder); - return; - } - } else { - if (state_ != WaitingForKeys) { - this->cancelVerification(OutOfOrder); - return; - } - } - - this->sas->set_their_key(msg.key); - std::string info; - if (this->sender == true) { - info = "MATRIX_KEY_VERIFICATION_SAS|" + - http::client()->user_id().to_string() + "|" + - http::client()->device_id() + "|" + this->sas->public_key() + - "|" + this->toClient.to_string() + "|" + - this->deviceId.toStdString() + "|" + msg.key + "|" + - this->transaction_id; - } else { - info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() + - "|" + this->deviceId.toStdString() + "|" + msg.key + "|" + - http::client()->user_id().to_string() + "|" + - http::client()->device_id() + "|" + this->sas->public_key() + - "|" + this->transaction_id; - } - - nhlog::ui()->info("Info is: '{}'", info); - - if (this->sender == false) { - this->sendVerificationKey(); - } else { - if (this->commitment != - mtx::crypto::bin2base64_unpadded( - mtx::crypto::sha256(msg.key + this->canonical_json.dump()))) { - this->cancelVerification( - DeviceVerificationFlow::Error::MismatchedCommitment); - return; - } - } - - if (this->method == mtx::events::msg::SASMethods::Emoji) { - this->sasList = this->sas->generate_bytes_emoji(info); - setState(CompareEmoji); - } else if (this->method == mtx::events::msg::SASMethods::Decimal) { - this->sasList = this->sas->generate_bytes_decimal(info); - setState(CompareNumber); - } - }); - + timeout = new QTimer(this); + timeout->setSingleShot(true); + this->sas = olm::client()->sas_init(); + this->isMacVerified = false; + + auto user_id = userID.toStdString(); + this->toClient = mtx::identifiers::parse(user_id); + cache::client()->query_keys( + user_id, [user_id, this](const UserKeyCache &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + return; + } + + if (!this->deviceId.isEmpty() && + (res.device_keys.find(deviceId.toStdString()) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } + + this->their_keys = res; + }); + + cache::client()->query_keys( + http::client()->user_id().to_string(), + [this](const UserKeyCache &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + return; + } + + if (res.master_keys.keys.empty()) + return; + + if (auto status = cache::verificationStatus(http::client()->user_id().to_string()); + status && status->user_verified == crypto::Trust::Verified) + this->our_trusted_master_key = res.master_keys.keys.begin()->second; + }); + + if (model) { connect( - ChatPage::instance(), - &ChatPage::receivedDeviceVerificationMac, - this, - [this](const mtx::events::msg::KeyVerificationMac &msg) { - nhlog::crypto()->info("verification: received mac"); - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - - std::map key_list; - std::string key_string; + this->model_, &TimelineModel::updateFlowEventId, this, [this](std::string event_id_) { + this->relation.rel_type = mtx::common::RelationType::Reference; + this->relation.event_id = event_id_; + this->transaction_id = event_id_; + }); + } + + connect(timeout, &QTimer::timeout, this, [this]() { + nhlog::crypto()->info("verification: timeout"); + if (state_ != Success && state_ != Failed) + this->cancelVerification(DeviceVerificationFlow::Error::Timeout); + }); + + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationStart, + this, + &DeviceVerificationFlow::handleStartMessage); + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationAccept, + this, + [this](const mtx::events::msg::KeyVerificationAccept &msg) { + nhlog::crypto()->info("verification: received accept"); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + if ((msg.key_agreement_protocol == "curve25519-hkdf-sha256") && + (msg.hash == "sha256") && + (msg.message_authentication_code == "hkdf-hmac-sha256")) { + this->commitment = msg.commitment; + if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.short_authentication_string.end()) { + this->method = mtx::events::msg::SASMethods::Emoji; + } else { + this->method = mtx::events::msg::SASMethods::Decimal; + } + this->mac_method = msg.message_authentication_code; + this->sendVerificationKey(); + } else { + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + } + }); + + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationCancel, + this, + [this](const mtx::events::msg::KeyVerificationCancel &msg) { + nhlog::crypto()->info("verification: received cancel"); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + error_ = User; + emit errorChanged(); + setState(Failed); + }); + + connect( + ChatPage::instance(), + &ChatPage::receivedDeviceVerificationKey, + this, + [this](const mtx::events::msg::KeyVerificationKey &msg) { + nhlog::crypto()->info("verification: received key"); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + + if (sender) { + if (state_ != WaitingForOtherToAccept) { + this->cancelVerification(OutOfOrder); + return; + } + } else { + if (state_ != WaitingForKeys) { + this->cancelVerification(OutOfOrder); + return; + } + } + + this->sas->set_their_key(msg.key); + std::string info; + if (this->sender == true) { + info = "MATRIX_KEY_VERIFICATION_SAS|" + http::client()->user_id().to_string() + "|" + + http::client()->device_id() + "|" + this->sas->public_key() + "|" + + this->toClient.to_string() + "|" + this->deviceId.toStdString() + "|" + + msg.key + "|" + this->transaction_id; + } else { + info = "MATRIX_KEY_VERIFICATION_SAS|" + this->toClient.to_string() + "|" + + this->deviceId.toStdString() + "|" + msg.key + "|" + + http::client()->user_id().to_string() + "|" + http::client()->device_id() + + "|" + this->sas->public_key() + "|" + this->transaction_id; + } + + nhlog::ui()->info("Info is: '{}'", info); + + if (this->sender == false) { + this->sendVerificationKey(); + } else { + if (this->commitment != mtx::crypto::bin2base64_unpadded(mtx::crypto::sha256( + msg.key + this->canonical_json.dump()))) { + this->cancelVerification(DeviceVerificationFlow::Error::MismatchedCommitment); + return; + } + } + + if (this->method == mtx::events::msg::SASMethods::Emoji) { + this->sasList = this->sas->generate_bytes_emoji(info); + setState(CompareEmoji); + } else if (this->method == mtx::events::msg::SASMethods::Decimal) { + this->sasList = this->sas->generate_bytes_decimal(info); + setState(CompareNumber); + } + }); + + connect( + ChatPage::instance(), + &ChatPage::receivedDeviceVerificationMac, + this, + [this](const mtx::events::msg::KeyVerificationMac &msg) { + nhlog::crypto()->info("verification: received mac"); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + + std::map key_list; + std::string key_string; + for (const auto &mac : msg.mac) { + for (const auto &[deviceid, key] : their_keys.device_keys) { + (void)deviceid; + if (key.keys.count(mac.first)) + key_list[mac.first] = key.keys.at(mac.first); + } + + if (their_keys.master_keys.keys.count(mac.first)) + key_list[mac.first] = their_keys.master_keys.keys[mac.first]; + if (their_keys.user_signing_keys.keys.count(mac.first)) + key_list[mac.first] = their_keys.user_signing_keys.keys[mac.first]; + if (their_keys.self_signing_keys.keys.count(mac.first)) + key_list[mac.first] = their_keys.self_signing_keys.keys[mac.first]; + } + auto macs = key_verification_mac(sas.get(), + toClient, + this->deviceId.toStdString(), + http::client()->user_id(), + http::client()->device_id(), + this->transaction_id, + key_list); + + for (const auto &[key, mac] : macs.mac) { + if (mac != msg.mac.at(key)) { + this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); + return; + } + } + + if (msg.keys == macs.keys) { + mtx::requests::KeySignaturesUpload req; + if (utils::localUser().toStdString() == this->toClient.to_string()) { + // self verification, sign master key with device key, if we + // verified it for (const auto &mac : msg.mac) { - for (const auto &[deviceid, key] : their_keys.device_keys) { - (void)deviceid; - if (key.keys.count(mac.first)) - key_list[mac.first] = key.keys.at(mac.first); + if (their_keys.master_keys.keys.count(mac.first)) { + json j = their_keys.master_keys; + j.erase("signatures"); + j.erase("unsigned"); + mtx::crypto::CrossSigningKeys master_key = j; + master_key.signatures[utils::localUser().toStdString()] + ["ed25519:" + http::client()->device_id()] = + olm::client()->sign_message(j.dump()); + req.signatures[utils::localUser().toStdString()] + [master_key.keys.at(mac.first)] = master_key; + } else if (mac.first == "ed25519:" + this->deviceId.toStdString()) { + // Sign their device key with self signing key + + auto device_id = this->deviceId.toStdString(); + + if (their_keys.device_keys.count(device_id)) { + json j = their_keys.device_keys.at(device_id); + j.erase("signatures"); + j.erase("unsigned"); + + auto secret = cache::secret( + mtx::secret_storage::secrets::cross_signing_self_signing); + if (!secret) + continue; + auto ssk = mtx::crypto::PkSigning::from_seed(*secret); + + mtx::crypto::DeviceKeys dev = j; + dev.signatures[utils::localUser().toStdString()] + ["ed25519:" + ssk.public_key()] = ssk.sign(j.dump()); + + req.signatures[utils::localUser().toStdString()][device_id] = dev; } - - if (their_keys.master_keys.keys.count(mac.first)) - key_list[mac.first] = their_keys.master_keys.keys[mac.first]; - if (their_keys.user_signing_keys.keys.count(mac.first)) - key_list[mac.first] = - their_keys.user_signing_keys.keys[mac.first]; - if (their_keys.self_signing_keys.keys.count(mac.first)) - key_list[mac.first] = - their_keys.self_signing_keys.keys[mac.first]; + } } - auto macs = key_verification_mac(sas.get(), - toClient, - this->deviceId.toStdString(), - http::client()->user_id(), - http::client()->device_id(), - this->transaction_id, - key_list); - - for (const auto &[key, mac] : macs.mac) { - if (mac != msg.mac.at(key)) { - this->cancelVerification( - DeviceVerificationFlow::Error::KeyMismatch); - return; - } + } else { + // Sign their master key with user signing key + for (const auto &mac : msg.mac) { + if (their_keys.master_keys.keys.count(mac.first)) { + json j = their_keys.master_keys; + j.erase("signatures"); + j.erase("unsigned"); + + auto secret = + cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing); + if (!secret) + continue; + auto usk = mtx::crypto::PkSigning::from_seed(*secret); + + mtx::crypto::CrossSigningKeys master_key = j; + master_key.signatures[utils::localUser().toStdString()] + ["ed25519:" + usk.public_key()] = usk.sign(j.dump()); + + req.signatures[toClient.to_string()][master_key.keys.at(mac.first)] = + master_key; + } } + } + + if (!req.signatures.empty()) { + http::client()->keys_signatures_upload( + req, + [](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("failed to upload signatures: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast(err->status_code)); + } - if (msg.keys == macs.keys) { - mtx::requests::KeySignaturesUpload req; - if (utils::localUser().toStdString() == this->toClient.to_string()) { - // self verification, sign master key with device key, if we - // verified it - for (const auto &mac : msg.mac) { - if (their_keys.master_keys.keys.count(mac.first)) { - json j = their_keys.master_keys; - j.erase("signatures"); - j.erase("unsigned"); - mtx::crypto::CrossSigningKeys master_key = j; - master_key - .signatures[utils::localUser().toStdString()] - ["ed25519:" + - http::client()->device_id()] = - olm::client()->sign_message(j.dump()); - req.signatures[utils::localUser().toStdString()] - [master_key.keys.at(mac.first)] = - master_key; - } else if (mac.first == - "ed25519:" + this->deviceId.toStdString()) { - // Sign their device key with self signing key - - auto device_id = this->deviceId.toStdString(); - - if (their_keys.device_keys.count(device_id)) { - json j = - their_keys.device_keys.at(device_id); - j.erase("signatures"); - j.erase("unsigned"); - - auto secret = cache::secret( - mtx::secret_storage::secrets:: - cross_signing_self_signing); - if (!secret) - continue; - auto ssk = - mtx::crypto::PkSigning::from_seed( - *secret); - - mtx::crypto::DeviceKeys dev = j; - dev.signatures - [utils::localUser().toStdString()] - ["ed25519:" + ssk.public_key()] = - ssk.sign(j.dump()); - - req.signatures[utils::localUser() - .toStdString()] - [device_id] = dev; - } - } - } - } else { - // Sign their master key with user signing key - for (const auto &mac : msg.mac) { - if (their_keys.master_keys.keys.count(mac.first)) { - json j = their_keys.master_keys; - j.erase("signatures"); - j.erase("unsigned"); - - auto secret = - cache::secret(mtx::secret_storage::secrets:: - cross_signing_user_signing); - if (!secret) - continue; - auto usk = - mtx::crypto::PkSigning::from_seed(*secret); - - mtx::crypto::CrossSigningKeys master_key = j; - master_key - .signatures[utils::localUser().toStdString()] - ["ed25519:" + usk.public_key()] = - usk.sign(j.dump()); - - req.signatures[toClient.to_string()] - [master_key.keys.at(mac.first)] = - master_key; - } - } - } - - if (!req.signatures.empty()) { - http::client()->keys_signatures_upload( - req, - [](const mtx::responses::KeySignaturesUpload &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "failed to upload signatures: {},{}", - mtx::errors::to_string( - err->matrix_error.errcode), - static_cast(err->status_code)); - } - - for (const auto &[user_id, tmp] : res.errors) - for (const auto &[key_id, e] : tmp) - nhlog::net()->error( - "signature error for user {} and key " - "id {}: {}, {}", - user_id, - key_id, - mtx::errors::to_string(e.errcode), - e.error); - }); - } - - this->isMacVerified = true; - this->acceptDevice(); - } else { - this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); - } - }); + for (const auto &[user_id, tmp] : res.errors) + for (const auto &[key_id, e] : tmp) + nhlog::net()->error("signature error for user {} and key " + "id {}: {}, {}", + user_id, + key_id, + mtx::errors::to_string(e.errcode), + e.error); + }); + } + + this->isMacVerified = true; + this->acceptDevice(); + } else { + this->cancelVerification(DeviceVerificationFlow::Error::KeyMismatch); + } + }); + + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationReady, + this, + [this](const mtx::events::msg::KeyVerificationReady &msg) { + nhlog::crypto()->info("verification: received ready"); + if (!sender) { + if (msg.from_device != http::client()->device_id()) { + error_ = User; + emit errorChanged(); + setState(Failed); + } - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationReady, - this, - [this](const mtx::events::msg::KeyVerificationReady &msg) { - nhlog::crypto()->info("verification: received ready"); - if (!sender) { - if (msg.from_device != http::client()->device_id()) { - error_ = User; - emit errorChanged(); - setState(Failed); - } - - return; - } + return; + } - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - else { - this->deviceId = QString::fromStdString(msg.from_device); - } - } - this->startVerificationRequest(); - }); - - connect(ChatPage::instance(), - &ChatPage::receivedDeviceVerificationDone, - this, - [this](const mtx::events::msg::KeyVerificationDone &msg) { - nhlog::crypto()->info("verification: receoved done"); - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - nhlog::ui()->info("Flow done on other side"); - }); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + else { + this->deviceId = QString::fromStdString(msg.from_device); + } + } + this->startVerificationRequest(); + }); + + connect(ChatPage::instance(), + &ChatPage::receivedDeviceVerificationDone, + this, + [this](const mtx::events::msg::KeyVerificationDone &msg) { + nhlog::crypto()->info("verification: receoved done"); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + nhlog::ui()->info("Flow done on other side"); + }); - timeout->start(TIMEOUT); + timeout->start(TIMEOUT); } QString DeviceVerificationFlow::state() { + switch (state_) { + case PromptStartVerification: + return "PromptStartVerification"; + case CompareEmoji: + return "CompareEmoji"; + case CompareNumber: + return "CompareNumber"; + case WaitingForKeys: + return "WaitingForKeys"; + case WaitingForOtherToAccept: + return "WaitingForOtherToAccept"; + case WaitingForMac: + return "WaitingForMac"; + case Success: + return "Success"; + case Failed: + return "Failed"; + default: + return ""; + } +} + +void +DeviceVerificationFlow::next() +{ + if (sender) { switch (state_) { case PromptStartVerification: - return "PromptStartVerification"; + sendVerificationRequest(); + break; case CompareEmoji: - return "CompareEmoji"; case CompareNumber: - return "CompareNumber"; + sendVerificationMac(); + break; case WaitingForKeys: - return "WaitingForKeys"; case WaitingForOtherToAccept: - return "WaitingForOtherToAccept"; case WaitingForMac: - return "WaitingForMac"; case Success: - return "Success"; case Failed: - return "Failed"; - default: - return ""; + nhlog::db()->error("verification: Invalid state transition!"); + break; } -} - -void -DeviceVerificationFlow::next() -{ - if (sender) { - switch (state_) { - case PromptStartVerification: - sendVerificationRequest(); - break; - case CompareEmoji: - case CompareNumber: - sendVerificationMac(); - break; - case WaitingForKeys: - case WaitingForOtherToAccept: - case WaitingForMac: - case Success: - case Failed: - nhlog::db()->error("verification: Invalid state transition!"); - break; - } - } else { - switch (state_) { - case PromptStartVerification: - if (canonical_json.is_null()) - sendVerificationReady(); - else // legacy path without request and ready - acceptVerificationRequest(); - break; - case CompareEmoji: - [[fallthrough]]; - case CompareNumber: - sendVerificationMac(); - break; - case WaitingForKeys: - case WaitingForOtherToAccept: - case WaitingForMac: - case Success: - case Failed: - nhlog::db()->error("verification: Invalid state transition!"); - break; - } + } else { + switch (state_) { + case PromptStartVerification: + if (canonical_json.is_null()) + sendVerificationReady(); + else // legacy path without request and ready + acceptVerificationRequest(); + break; + case CompareEmoji: + [[fallthrough]]; + case CompareNumber: + sendVerificationMac(); + break; + case WaitingForKeys: + case WaitingForOtherToAccept: + case WaitingForMac: + case Success: + case Failed: + nhlog::db()->error("verification: Invalid state transition!"); + break; } + } } QString DeviceVerificationFlow::getUserId() { - return QString::fromStdString(this->toClient.to_string()); + return QString::fromStdString(this->toClient.to_string()); } QString DeviceVerificationFlow::getDeviceId() { - return this->deviceId; + return this->deviceId; } bool DeviceVerificationFlow::getSender() { - return this->sender; + return this->sender; } std::vector DeviceVerificationFlow::getSasList() { - return this->sasList; + return this->sasList; } bool DeviceVerificationFlow::isSelfVerification() const { - return this->toClient.to_string() == http::client()->user_id().to_string(); + return this->toClient.to_string() == http::client()->user_id().to_string(); } void DeviceVerificationFlow::setEventId(std::string event_id_) { - this->relation.rel_type = mtx::common::RelationType::Reference; - this->relation.event_id = event_id_; - this->transaction_id = event_id_; + this->relation.rel_type = mtx::common::RelationType::Reference; + this->relation.event_id = event_id_; + this->transaction_id = event_id_; } void DeviceVerificationFlow::handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string) { - if (msg.transaction_id.has_value()) { - if (msg.transaction_id.value() != this->transaction_id) - return; - } else if (msg.relations.references()) { - if (msg.relations.references() != this->relation.event_id) - return; - } - if ((std::find(msg.key_agreement_protocols.begin(), - msg.key_agreement_protocols.end(), - "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) && - (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) && - (std::find(msg.message_authentication_codes.begin(), - msg.message_authentication_codes.end(), - "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) { - if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Emoji) != - msg.short_authentication_string.end()) { - this->method = mtx::events::msg::SASMethods::Emoji; - } else if (std::find(msg.short_authentication_string.begin(), - msg.short_authentication_string.end(), - mtx::events::msg::SASMethods::Decimal) != - msg.short_authentication_string.end()) { - this->method = mtx::events::msg::SASMethods::Decimal; - } else { - this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); - return; - } - if (!sender) - this->canonical_json = nlohmann::json(msg); - else { - if (utils::localUser().toStdString() < this->toClient.to_string()) { - this->canonical_json = nlohmann::json(msg); - } - } - - if (state_ != PromptStartVerification) - this->acceptVerificationRequest(); + if (msg.transaction_id.has_value()) { + if (msg.transaction_id.value() != this->transaction_id) + return; + } else if (msg.relations.references()) { + if (msg.relations.references() != this->relation.event_id) + return; + } + if ((std::find(msg.key_agreement_protocols.begin(), + msg.key_agreement_protocols.end(), + "curve25519-hkdf-sha256") != msg.key_agreement_protocols.end()) && + (std::find(msg.hashes.begin(), msg.hashes.end(), "sha256") != msg.hashes.end()) && + (std::find(msg.message_authentication_codes.begin(), + msg.message_authentication_codes.end(), + "hkdf-hmac-sha256") != msg.message_authentication_codes.end())) { + if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Emoji) != + msg.short_authentication_string.end()) { + this->method = mtx::events::msg::SASMethods::Emoji; + } else if (std::find(msg.short_authentication_string.begin(), + msg.short_authentication_string.end(), + mtx::events::msg::SASMethods::Decimal) != + msg.short_authentication_string.end()) { + this->method = mtx::events::msg::SASMethods::Decimal; } else { - this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + return; + } + if (!sender) + this->canonical_json = nlohmann::json(msg); + else { + if (utils::localUser().toStdString() < this->toClient.to_string()) { + this->canonical_json = nlohmann::json(msg); + } } + + if (state_ != PromptStartVerification) + this->acceptVerificationRequest(); + } else { + this->cancelVerification(DeviceVerificationFlow::Error::UnknownMethod); + } } //! accepts a verification void DeviceVerificationFlow::acceptVerificationRequest() { - mtx::events::msg::KeyVerificationAccept req; - - req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocol = "curve25519-hkdf-sha256"; - req.hash = "sha256"; - req.message_authentication_code = "hkdf-hmac-sha256"; - if (this->method == mtx::events::msg::SASMethods::Emoji) - req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji}; - else if (this->method == mtx::events::msg::SASMethods::Decimal) - req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal}; - req.commitment = mtx::crypto::bin2base64_unpadded( - mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); - - send(req); - setState(WaitingForKeys); + mtx::events::msg::KeyVerificationAccept req; + + req.method = mtx::events::msg::VerificationMethods::SASv1; + req.key_agreement_protocol = "curve25519-hkdf-sha256"; + req.hash = "sha256"; + req.message_authentication_code = "hkdf-hmac-sha256"; + if (this->method == mtx::events::msg::SASMethods::Emoji) + req.short_authentication_string = {mtx::events::msg::SASMethods::Emoji}; + else if (this->method == mtx::events::msg::SASMethods::Decimal) + req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal}; + req.commitment = mtx::crypto::bin2base64_unpadded( + mtx::crypto::sha256(this->sas->public_key() + this->canonical_json.dump())); + + send(req); + setState(WaitingForKeys); } //! responds verification request void DeviceVerificationFlow::sendVerificationReady() { - mtx::events::msg::KeyVerificationReady req; + mtx::events::msg::KeyVerificationReady req; - req.from_device = http::client()->device_id(); - req.methods = {mtx::events::msg::VerificationMethods::SASv1}; + req.from_device = http::client()->device_id(); + req.methods = {mtx::events::msg::VerificationMethods::SASv1}; - send(req); - setState(WaitingForKeys); + send(req); + setState(WaitingForKeys); } //! accepts a verification void DeviceVerificationFlow::sendVerificationDone() { - mtx::events::msg::KeyVerificationDone req; + mtx::events::msg::KeyVerificationDone req; - send(req); + send(req); } //! starts the verification flow void DeviceVerificationFlow::startVerificationRequest() { - mtx::events::msg::KeyVerificationStart req; - - req.from_device = http::client()->device_id(); - req.method = mtx::events::msg::VerificationMethods::SASv1; - req.key_agreement_protocols = {"curve25519-hkdf-sha256"}; - req.hashes = {"sha256"}; - req.message_authentication_codes = {"hkdf-hmac-sha256"}; - req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, - mtx::events::msg::SASMethods::Emoji}; - - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - req.transaction_id = this->transaction_id; - this->canonical_json = nlohmann::json(req); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.relations.relations.push_back(this->relation); - // Set synthesized to surpress the nheko relation extensions - req.relations.synthesized = true; - this->canonical_json = nlohmann::json(req); - } - send(req); - setState(WaitingForOtherToAccept); + mtx::events::msg::KeyVerificationStart req; + + req.from_device = http::client()->device_id(); + req.method = mtx::events::msg::VerificationMethods::SASv1; + req.key_agreement_protocols = {"curve25519-hkdf-sha256"}; + req.hashes = {"sha256"}; + req.message_authentication_codes = {"hkdf-hmac-sha256"}; + req.short_authentication_string = {mtx::events::msg::SASMethods::Decimal, + mtx::events::msg::SASMethods::Emoji}; + + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + req.transaction_id = this->transaction_id; + this->canonical_json = nlohmann::json(req); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { + req.relations.relations.push_back(this->relation); + // Set synthesized to surpress the nheko relation extensions + req.relations.synthesized = true; + this->canonical_json = nlohmann::json(req); + } + send(req); + setState(WaitingForOtherToAccept); } //! sends a verification request void DeviceVerificationFlow::sendVerificationRequest() { - mtx::events::msg::KeyVerificationRequest req; + mtx::events::msg::KeyVerificationRequest req; - req.from_device = http::client()->device_id(); - req.methods = {mtx::events::msg::VerificationMethods::SASv1}; + req.from_device = http::client()->device_id(); + req.methods = {mtx::events::msg::VerificationMethods::SASv1}; - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - QDateTime currentTime = QDateTime::currentDateTimeUtc(); + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + QDateTime currentTime = QDateTime::currentDateTimeUtc(); - req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); + req.timestamp = (uint64_t)currentTime.toMSecsSinceEpoch(); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - req.to = this->toClient.to_string(); - req.msgtype = "m.key.verification.request"; - req.body = "User is requesting to verify keys with you. However, your client does " - "not support this method, so you will need to use the legacy method of " - "key verification."; - } + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { + req.to = this->toClient.to_string(); + req.msgtype = "m.key.verification.request"; + req.body = "User is requesting to verify keys with you. However, your client does " + "not support this method, so you will need to use the legacy method of " + "key verification."; + } - send(req); - setState(WaitingForOtherToAccept); + send(req); + setState(WaitingForOtherToAccept); } //! cancels a verification flow void DeviceVerificationFlow::cancelVerification(DeviceVerificationFlow::Error error_code) { - if (state_ == State::Success || state_ == State::Failed) - return; - - mtx::events::msg::KeyVerificationCancel req; - - if (error_code == DeviceVerificationFlow::Error::UnknownMethod) { - req.code = "m.unknown_method"; - req.reason = "unknown method received"; - } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) { - req.code = "m.mismatched_commitment"; - req.reason = "commitment didn't match"; - } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) { - req.code = "m.mismatched_sas"; - req.reason = "sas didn't match"; - } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) { - req.code = "m.key_match"; - req.reason = "keys did not match"; - } else if (error_code == DeviceVerificationFlow::Error::Timeout) { - req.code = "m.timeout"; - req.reason = "timed out"; - } else if (error_code == DeviceVerificationFlow::Error::User) { - req.code = "m.user"; - req.reason = "user cancelled the verification"; - } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) { - req.code = "m.unexpected_message"; - req.reason = "received messages out of order"; - } - - this->error_ = error_code; - emit errorChanged(); - this->setState(Failed); - - send(req); + if (state_ == State::Success || state_ == State::Failed) + return; + + mtx::events::msg::KeyVerificationCancel req; + + if (error_code == DeviceVerificationFlow::Error::UnknownMethod) { + req.code = "m.unknown_method"; + req.reason = "unknown method received"; + } else if (error_code == DeviceVerificationFlow::Error::MismatchedCommitment) { + req.code = "m.mismatched_commitment"; + req.reason = "commitment didn't match"; + } else if (error_code == DeviceVerificationFlow::Error::MismatchedSAS) { + req.code = "m.mismatched_sas"; + req.reason = "sas didn't match"; + } else if (error_code == DeviceVerificationFlow::Error::KeyMismatch) { + req.code = "m.key_match"; + req.reason = "keys did not match"; + } else if (error_code == DeviceVerificationFlow::Error::Timeout) { + req.code = "m.timeout"; + req.reason = "timed out"; + } else if (error_code == DeviceVerificationFlow::Error::User) { + req.code = "m.user"; + req.reason = "user cancelled the verification"; + } else if (error_code == DeviceVerificationFlow::Error::OutOfOrder) { + req.code = "m.unexpected_message"; + req.reason = "received messages out of order"; + } + + this->error_ = error_code; + emit errorChanged(); + this->setState(Failed); + + send(req); } //! sends the verification key void DeviceVerificationFlow::sendVerificationKey() { - mtx::events::msg::KeyVerificationKey req; + mtx::events::msg::KeyVerificationKey req; - req.key = this->sas->public_key(); + req.key = this->sas->public_key(); - send(req); + send(req); } mtx::events::msg::KeyVerificationMac @@ -728,79 +696,78 @@ key_verification_mac(mtx::crypto::SAS *sas, const std::string &transactionId, std::map keys) { - mtx::events::msg::KeyVerificationMac req; + mtx::events::msg::KeyVerificationMac req; - std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice + - receiver.to_string() + receiverDevice + transactionId; + std::string info = "MATRIX_KEY_VERIFICATION_MAC" + sender.to_string() + senderDevice + + receiver.to_string() + receiverDevice + transactionId; - std::string key_list; - bool first = true; - for (const auto &[key_id, key] : keys) { - req.mac[key_id] = sas->calculate_mac(key, info + key_id); + std::string key_list; + bool first = true; + for (const auto &[key_id, key] : keys) { + req.mac[key_id] = sas->calculate_mac(key, info + key_id); - if (!first) - key_list += ","; - key_list += key_id; - first = false; - } + if (!first) + key_list += ","; + key_list += key_id; + first = false; + } - req.keys = sas->calculate_mac(key_list, info + "KEY_IDS"); + req.keys = sas->calculate_mac(key_list, info + "KEY_IDS"); - return req; + return req; } //! sends the mac of the keys void DeviceVerificationFlow::sendVerificationMac() { - std::map key_list; - key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; + std::map key_list; + key_list["ed25519:" + http::client()->device_id()] = olm::client()->identity_keys().ed25519; - // send our master key, if we trust it - if (!this->our_trusted_master_key.empty()) - key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key; + // send our master key, if we trust it + if (!this->our_trusted_master_key.empty()) + key_list["ed25519:" + our_trusted_master_key] = our_trusted_master_key; - mtx::events::msg::KeyVerificationMac req = - key_verification_mac(sas.get(), - http::client()->user_id(), - http::client()->device_id(), - this->toClient, - this->deviceId.toStdString(), - this->transaction_id, - key_list); + mtx::events::msg::KeyVerificationMac req = key_verification_mac(sas.get(), + http::client()->user_id(), + http::client()->device_id(), + this->toClient, + this->deviceId.toStdString(), + this->transaction_id, + key_list); - send(req); + send(req); - setState(WaitingForMac); - acceptDevice(); + setState(WaitingForMac); + acceptDevice(); } //! Completes the verification flow void DeviceVerificationFlow::acceptDevice() { - if (!isMacVerified) { - setState(WaitingForMac); - } else if (state_ == WaitingForMac) { - cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString()); - this->sendVerificationDone(); - setState(Success); - - // Request secrets. We should probably check somehow, if a device knowns about the - // secrets. - if (utils::localUser().toStdString() == this->toClient.to_string() && - (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) || - !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) { - olm::request_cross_signing_keys(); - } + if (!isMacVerified) { + setState(WaitingForMac); + } else if (state_ == WaitingForMac) { + cache::markDeviceVerified(this->toClient.to_string(), this->deviceId.toStdString()); + this->sendVerificationDone(); + setState(Success); + + // Request secrets. We should probably check somehow, if a device knowns about the + // secrets. + if (utils::localUser().toStdString() == this->toClient.to_string() && + (!cache::secret(mtx::secret_storage::secrets::cross_signing_self_signing) || + !cache::secret(mtx::secret_storage::secrets::cross_signing_user_signing))) { + olm::request_cross_signing_keys(); } + } } void DeviceVerificationFlow::unverify() { - cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString()); + cache::markDeviceUnverified(this->toClient.to_string(), this->deviceId.toStdString()); - emit refreshProfile(); + emit refreshProfile(); } QSharedPointer @@ -810,22 +777,22 @@ DeviceVerificationFlow::NewInRoomVerification(QObject *parent_, QString other_user_, QString event_id_) { - QSharedPointer flow( - new DeviceVerificationFlow(parent_, - Type::RoomMsg, - timelineModel_, - other_user_, - QString::fromStdString(msg.from_device))); - - flow->setEventId(event_id_.toStdString()); - - if (std::find(msg.methods.begin(), - msg.methods.end(), - mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { - flow->cancelVerification(UnknownMethod); - } - - return flow; + QSharedPointer flow( + new DeviceVerificationFlow(parent_, + Type::RoomMsg, + timelineModel_, + other_user_, + QString::fromStdString(msg.from_device))); + + flow->setEventId(event_id_.toStdString()); + + if (std::find(msg.methods.begin(), + msg.methods.end(), + mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { + flow->cancelVerification(UnknownMethod); + } + + return flow; } QSharedPointer DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, @@ -833,17 +800,17 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString other_user_, QString txn_id_) { - QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); - flow->transaction_id = txn_id_.toStdString(); - - if (std::find(msg.methods.begin(), - msg.methods.end(), - mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { - flow->cancelVerification(UnknownMethod); - } + QSharedPointer flow(new DeviceVerificationFlow( + parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + flow->transaction_id = txn_id_.toStdString(); + + if (std::find(msg.methods.begin(), + msg.methods.end(), + mtx::events::msg::VerificationMethods::SASv1) == msg.methods.end()) { + flow->cancelVerification(UnknownMethod); + } - return flow; + return flow; } QSharedPointer DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, @@ -851,32 +818,32 @@ DeviceVerificationFlow::NewToDeviceVerification(QObject *parent_, QString other_user_, QString txn_id_) { - QSharedPointer flow(new DeviceVerificationFlow( - parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); - flow->transaction_id = txn_id_.toStdString(); + QSharedPointer flow(new DeviceVerificationFlow( + parent_, Type::ToDevice, nullptr, other_user_, QString::fromStdString(msg.from_device))); + flow->transaction_id = txn_id_.toStdString(); - flow->handleStartMessage(msg, ""); + flow->handleStartMessage(msg, ""); - return flow; + return flow; } QSharedPointer DeviceVerificationFlow::InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid) { - QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); - flow->sender = true; - return flow; + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::RoomMsg, timelineModel_, userid, "")); + flow->sender = true; + return flow; } QSharedPointer DeviceVerificationFlow::InitiateDeviceVerification(QObject *parent_, QString userid, QString device) { - QSharedPointer flow( - new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); + QSharedPointer flow( + new DeviceVerificationFlow(parent_, Type::ToDevice, nullptr, userid, device)); - flow->sender = true; - flow->transaction_id = http::client()->generate_txn_id(); + flow->sender = true; + flow->transaction_id = http::client()->generate_txn_id(); - return flow; + return flow; } diff --git a/src/DeviceVerificationFlow.h b/src/DeviceVerificationFlow.h index 4685a450..f71fa337 100644 --- a/src/DeviceVerificationFlow.h +++ b/src/DeviceVerificationFlow.h @@ -60,192 +60,189 @@ using sas_ptr = std::unique_ptr; // clang-format on class DeviceVerificationFlow : public QObject { - Q_OBJECT - Q_PROPERTY(QString state READ state NOTIFY stateChanged) - Q_PROPERTY(Error error READ error NOTIFY errorChanged) - Q_PROPERTY(QString userId READ getUserId CONSTANT) - Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) - Q_PROPERTY(bool sender READ getSender CONSTANT) - Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) - Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) - Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) + Q_OBJECT + Q_PROPERTY(QString state READ state NOTIFY stateChanged) + Q_PROPERTY(Error error READ error NOTIFY errorChanged) + Q_PROPERTY(QString userId READ getUserId CONSTANT) + Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) + Q_PROPERTY(bool sender READ getSender CONSTANT) + Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) + Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT) + Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT) public: - enum State - { - PromptStartVerification, - WaitingForOtherToAccept, - WaitingForKeys, - CompareEmoji, - CompareNumber, - WaitingForMac, - Success, - Failed, - }; - Q_ENUM(State) - - enum Type - { - ToDevice, - RoomMsg - }; - - enum Error - { - UnknownMethod, - MismatchedCommitment, - MismatchedSAS, - KeyMismatch, - Timeout, - User, - OutOfOrder, - }; - Q_ENUM(Error) - - static QSharedPointer NewInRoomVerification( - QObject *parent_, - TimelineModel *timelineModel_, - const mtx::events::msg::KeyVerificationRequest &msg, - QString other_user_, - QString event_id_); - static QSharedPointer NewToDeviceVerification( - QObject *parent_, - const mtx::events::msg::KeyVerificationRequest &msg, - QString other_user_, - QString txn_id_); - static QSharedPointer NewToDeviceVerification( - QObject *parent_, - const mtx::events::msg::KeyVerificationStart &msg, - QString other_user_, - QString txn_id_); - static QSharedPointer - InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); - static QSharedPointer InitiateDeviceVerification(QObject *parent, - QString userid, - QString device); - - // getters - QString state(); - Error error() { return error_; } - QString getUserId(); - QString getDeviceId(); - bool getSender(); - std::vector getSasList(); - QString transactionId() { return QString::fromStdString(this->transaction_id); } - // setters - void setDeviceId(QString deviceID); - void setEventId(std::string event_id); - bool isDeviceVerification() const - { - return this->type == DeviceVerificationFlow::Type::ToDevice; - } - bool isSelfVerification() const; - - void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); + enum State + { + PromptStartVerification, + WaitingForOtherToAccept, + WaitingForKeys, + CompareEmoji, + CompareNumber, + WaitingForMac, + Success, + Failed, + }; + Q_ENUM(State) + + enum Type + { + ToDevice, + RoomMsg + }; + + enum Error + { + UnknownMethod, + MismatchedCommitment, + MismatchedSAS, + KeyMismatch, + Timeout, + User, + OutOfOrder, + }; + Q_ENUM(Error) + + static QSharedPointer NewInRoomVerification( + QObject *parent_, + TimelineModel *timelineModel_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString event_id_); + static QSharedPointer NewToDeviceVerification( + QObject *parent_, + const mtx::events::msg::KeyVerificationRequest &msg, + QString other_user_, + QString txn_id_); + static QSharedPointer NewToDeviceVerification( + QObject *parent_, + const mtx::events::msg::KeyVerificationStart &msg, + QString other_user_, + QString txn_id_); + static QSharedPointer + InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); + static QSharedPointer InitiateDeviceVerification(QObject *parent, + QString userid, + QString device); + + // getters + QString state(); + Error error() { return error_; } + QString getUserId(); + QString getDeviceId(); + bool getSender(); + std::vector getSasList(); + QString transactionId() { return QString::fromStdString(this->transaction_id); } + // setters + void setDeviceId(QString deviceID); + void setEventId(std::string event_id); + bool isDeviceVerification() const + { + return this->type == DeviceVerificationFlow::Type::ToDevice; + } + bool isSelfVerification() const; + + void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); public slots: - //! unverifies a device - void unverify(); - //! Continues the flow - void next(); - //! Cancel the flow - void cancel() { cancelVerification(User); } + //! unverifies a device + void unverify(); + //! Continues the flow + void next(); + //! Cancel the flow + void cancel() { cancelVerification(User); } signals: - void refreshProfile(); - void stateChanged(); - void errorChanged(); + void refreshProfile(); + void stateChanged(); + void errorChanged(); private: - DeviceVerificationFlow(QObject *, - DeviceVerificationFlow::Type flow_type, - TimelineModel *model, - QString userID, - QString deviceId_); - void setState(State state) - { - if (state != state_) { - state_ = state; - emit stateChanged(); - } + DeviceVerificationFlow(QObject *, + DeviceVerificationFlow::Type flow_type, + TimelineModel *model, + QString userID, + QString deviceId_); + void setState(State state) + { + if (state != state_) { + state_ = state; + emit stateChanged(); } - - void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string); - //! sends a verification request - void sendVerificationRequest(); - //! accepts a verification request - void sendVerificationReady(); - //! completes the verification flow(); - void sendVerificationDone(); - //! accepts a verification - void acceptVerificationRequest(); - //! starts the verification flow - void startVerificationRequest(); - //! cancels a verification flow - void cancelVerification(DeviceVerificationFlow::Error error_code); - //! sends the verification key - void sendVerificationKey(); - //! sends the mac of the keys - void sendVerificationMac(); - //! Completes the verification flow - void acceptDevice(); - - std::string transaction_id; - - bool sender; - Type type; - mtx::identifiers::User toClient; - QString deviceId; - - // public part of our master key, when trusted or empty - std::string our_trusted_master_key; - - mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; - QTimer *timeout = nullptr; - sas_ptr sas; - std::string mac_method; - std::string commitment; - nlohmann::json canonical_json; - - std::vector sasList; - UserKeyCache their_keys; - TimelineModel *model_; - mtx::common::Relation relation; - - State state_ = PromptStartVerification; - Error error_ = UnknownMethod; - - bool isMacVerified = false; - - template - void send(T msg) - { - if (this->type == DeviceVerificationFlow::Type::ToDevice) { - mtx::requests::ToDeviceMessages body; - msg.transaction_id = this->transaction_id; - body[this->toClient][deviceId.toStdString()] = msg; - - http::client()->send_to_device( - this->transaction_id, body, [](mtx::http::RequestErr err) { - if (err) - nhlog::net()->warn( - "failed to send verification to_device message: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - }); - } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { - if constexpr (!std::is_same_v) { - msg.relations.relations.push_back(this->relation); - // Set synthesized to surpress the nheko relation extensions - msg.relations.synthesized = true; - } - (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type); - } - - nhlog::net()->debug( - "Sent verification step: {} in state: {}", - mtx::events::to_string(mtx::events::to_device_content_to_type), - state().toStdString()); + } + + void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string); + //! sends a verification request + void sendVerificationRequest(); + //! accepts a verification request + void sendVerificationReady(); + //! completes the verification flow(); + void sendVerificationDone(); + //! accepts a verification + void acceptVerificationRequest(); + //! starts the verification flow + void startVerificationRequest(); + //! cancels a verification flow + void cancelVerification(DeviceVerificationFlow::Error error_code); + //! sends the verification key + void sendVerificationKey(); + //! sends the mac of the keys + void sendVerificationMac(); + //! Completes the verification flow + void acceptDevice(); + + std::string transaction_id; + + bool sender; + Type type; + mtx::identifiers::User toClient; + QString deviceId; + + // public part of our master key, when trusted or empty + std::string our_trusted_master_key; + + mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; + QTimer *timeout = nullptr; + sas_ptr sas; + std::string mac_method; + std::string commitment; + nlohmann::json canonical_json; + + std::vector sasList; + UserKeyCache their_keys; + TimelineModel *model_; + mtx::common::Relation relation; + + State state_ = PromptStartVerification; + Error error_ = UnknownMethod; + + bool isMacVerified = false; + + template + void send(T msg) + { + if (this->type == DeviceVerificationFlow::Type::ToDevice) { + mtx::requests::ToDeviceMessages body; + msg.transaction_id = this->transaction_id; + body[this->toClient][deviceId.toStdString()] = msg; + + http::client()->send_to_device( + this->transaction_id, body, [](mtx::http::RequestErr err) { + if (err) + nhlog::net()->warn("failed to send verification to_device message: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + }); + } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { + if constexpr (!std::is_same_v) { + msg.relations.relations.push_back(this->relation); + // Set synthesized to surpress the nheko relation extensions + msg.relations.synthesized = true; + } + (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type); } + + nhlog::net()->debug("Sent verification step: {} in state: {}", + mtx::events::to_string(mtx::events::to_device_content_to_type), + state().toStdString()); + } }; diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 362bf4e9..d794a384 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -16,463 +16,460 @@ using is_detected = typename nheko::detail::detector - bool operator()(const mtx::events::StateEvent &) - { - return true; - } - template - bool operator()(const mtx::events::Event &) - { - return false; - } + template + bool operator()(const mtx::events::StateEvent &) + { + return true; + } + template + bool operator()(const mtx::events::Event &) + { + return false; + } }; struct EventMsgType { - template - using msgtype_t = decltype(E::msgtype); - template - mtx::events::MessageType operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - if constexpr (std::is_same_v, - std::remove_cv_t>) - return mtx::events::getMessageType(e.content.msgtype.value()); - else if constexpr (std::is_same_v< - std::string, - std::remove_cv_t>) - return mtx::events::getMessageType(e.content.msgtype); - } - return mtx::events::MessageType::Unknown; + template + using msgtype_t = decltype(E::msgtype); + template + mtx::events::MessageType operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + if constexpr (std::is_same_v, + std::remove_cv_t>) + return mtx::events::getMessageType(e.content.msgtype.value()); + else if constexpr (std::is_same_v>) + return mtx::events::getMessageType(e.content.msgtype); } + return mtx::events::MessageType::Unknown; + } }; struct EventRoomName { - template - std::string operator()(const T &e) - { - if constexpr (std::is_same_v, T>) - return e.content.name; - return ""; - } + template + std::string operator()(const T &e) + { + if constexpr (std::is_same_v, T>) + return e.content.name; + return ""; + } }; struct EventRoomTopic { - template - std::string operator()(const T &e) - { - if constexpr (std::is_same_v, T>) - return e.content.topic; - return ""; - } + template + std::string operator()(const T &e) + { + if constexpr (std::is_same_v, T>) + return e.content.topic; + return ""; + } }; struct CallType { - template - std::string operator()(const T &e) - { - if constexpr (std::is_same_v, - T>) { - const char video[] = "m=video"; - const std::string &sdp = e.content.sdp; - return std::search(sdp.cbegin(), - sdp.cend(), - std::cbegin(video), - std::cend(video) - 1, - [](unsigned char c1, unsigned char c2) { - return std::tolower(c1) == std::tolower(c2); - }) != sdp.cend() - ? "video" - : "voice"; - } - return std::string(); + template + std::string operator()(const T &e) + { + if constexpr (std::is_same_v, T>) { + const char video[] = "m=video"; + const std::string &sdp = e.content.sdp; + return std::search(sdp.cbegin(), + sdp.cend(), + std::cbegin(video), + std::cend(video) - 1, + [](unsigned char c1, unsigned char c2) { + return std::tolower(c1) == std::tolower(c2); + }) != sdp.cend() + ? "video" + : "voice"; } + return std::string(); + } }; struct EventBody { - template - using body_t = decltype(C::body); - template - std::string operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - if constexpr (std::is_same_v, - std::remove_cv_t>) - return e.content.body ? e.content.body.value() : ""; - else if constexpr (std::is_same_v< - std::string, - std::remove_cv_t>) - return e.content.body; - } - return ""; + template + using body_t = decltype(C::body); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + if constexpr (std::is_same_v, + std::remove_cv_t>) + return e.content.body ? e.content.body.value() : ""; + else if constexpr (std::is_same_v>) + return e.content.body; } + return ""; + } }; struct EventFormattedBody { - template - using formatted_body_t = decltype(C::formatted_body); - template - std::string operator()(const mtx::events::RoomEvent &e) - { - if constexpr (is_detected::value) { - if (e.content.format == "org.matrix.custom.html") - return e.content.formatted_body; - } - return ""; + template + using formatted_body_t = decltype(C::formatted_body); + template + std::string operator()(const mtx::events::RoomEvent &e) + { + if constexpr (is_detected::value) { + if (e.content.format == "org.matrix.custom.html") + return e.content.formatted_body; } + return ""; + } }; struct EventFile { - template - using file_t = decltype(Content::file); - template - std::optional operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) - return e.content.file; - return std::nullopt; - } + template + using file_t = decltype(Content::file); + template + std::optional operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) + return e.content.file; + return std::nullopt; + } }; struct EventUrl { - template - using url_t = decltype(Content::url); - template - std::string operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - if (auto file = EventFile{}(e)) - return file->url; - return e.content.url; - } - return ""; + template + using url_t = decltype(Content::url); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + if (auto file = EventFile{}(e)) + return file->url; + return e.content.url; } + return ""; + } }; struct EventThumbnailUrl { - template - using thumbnail_url_t = decltype(Content::info.thumbnail_url); - template - std::string operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.info.thumbnail_url; - } - return ""; + template + using thumbnail_url_t = decltype(Content::info.thumbnail_url); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.info.thumbnail_url; } + return ""; + } }; struct EventBlurhash { - template - using blurhash_t = decltype(Content::info.blurhash); - template - std::string operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.info.blurhash; - } - return ""; + template + using blurhash_t = decltype(Content::info.blurhash); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.info.blurhash; } + return ""; + } }; struct EventFilename { - template - std::string operator()(const mtx::events::Event &) - { - return ""; - } - std::string operator()(const mtx::events::RoomEvent &e) - { - // body may be the original filename - return e.content.body; - } - std::string operator()(const mtx::events::RoomEvent &e) - { - // body may be the original filename - return e.content.body; - } - std::string operator()(const mtx::events::RoomEvent &e) - { - // body may be the original filename - return e.content.body; - } - std::string operator()(const mtx::events::RoomEvent &e) - { - // body may be the original filename - if (!e.content.filename.empty()) - return e.content.filename; - return e.content.body; - } + template + std::string operator()(const mtx::events::Event &) + { + return ""; + } + std::string operator()(const mtx::events::RoomEvent &e) + { + // body may be the original filename + return e.content.body; + } + std::string operator()(const mtx::events::RoomEvent &e) + { + // body may be the original filename + return e.content.body; + } + std::string operator()(const mtx::events::RoomEvent &e) + { + // body may be the original filename + return e.content.body; + } + std::string operator()(const mtx::events::RoomEvent &e) + { + // body may be the original filename + if (!e.content.filename.empty()) + return e.content.filename; + return e.content.body; + } }; struct EventMimeType { - template - using mimetype_t = decltype(Content::info.mimetype); - template - std::string operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.info.mimetype; - } - return ""; + template + using mimetype_t = decltype(Content::info.mimetype); + template + std::string operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.info.mimetype; } + return ""; + } }; struct EventFilesize { - template - using filesize_t = decltype(Content::info.size); - template - int64_t operator()(const mtx::events::RoomEvent &e) - { - if constexpr (is_detected::value) { - return e.content.info.size; - } - return 0; + template + using filesize_t = decltype(Content::info.size); + template + int64_t operator()(const mtx::events::RoomEvent &e) + { + if constexpr (is_detected::value) { + return e.content.info.size; } + return 0; + } }; struct EventRelations { - template - using related_ev_id_t = decltype(Content::relations); - template - mtx::common::Relations operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.relations; - } - return {}; + template + using related_ev_id_t = decltype(Content::relations); + template + mtx::common::Relations operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.relations; } + return {}; + } }; struct SetEventRelations { - mtx::common::Relations new_relations; - template - using related_ev_id_t = decltype(Content::relations); - template - void operator()(mtx::events::Event &e) - { - if constexpr (is_detected::value) { - e.content.relations = std::move(new_relations); - } + mtx::common::Relations new_relations; + template + using related_ev_id_t = decltype(Content::relations); + template + void operator()(mtx::events::Event &e) + { + if constexpr (is_detected::value) { + e.content.relations = std::move(new_relations); } + } }; struct EventTransactionId { - template - std::string operator()(const mtx::events::RoomEvent &e) - { - return e.unsigned_data.transaction_id; - } - template - std::string operator()(const mtx::events::Event &e) - { - return e.unsigned_data.transaction_id; - } + template + std::string operator()(const mtx::events::RoomEvent &e) + { + return e.unsigned_data.transaction_id; + } + template + std::string operator()(const mtx::events::Event &e) + { + return e.unsigned_data.transaction_id; + } }; struct EventMediaHeight { - template - using h_t = decltype(Content::info.h); - template - uint64_t operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.info.h; - } - return -1; + template + using h_t = decltype(Content::info.h); + template + uint64_t operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.info.h; } + return -1; + } }; struct EventMediaWidth { - template - using w_t = decltype(Content::info.w); - template - uint64_t operator()(const mtx::events::Event &e) - { - if constexpr (is_detected::value) { - return e.content.info.w; - } - return -1; + template + using w_t = decltype(Content::info.w); + template + uint64_t operator()(const mtx::events::Event &e) + { + if constexpr (is_detected::value) { + return e.content.info.w; } + return -1; + } }; template double eventPropHeight(const mtx::events::RoomEvent &e) { - auto w = eventWidth(e); - if (w == 0) - w = 1; + auto w = eventWidth(e); + if (w == 0) + w = 1; - double prop = eventHeight(e) / (double)w; + double prop = eventHeight(e) / (double)w; - return prop > 0 ? prop : 1.; + return prop > 0 ? prop : 1.; } } std::string mtx::accessors::event_id(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto e) { return e.event_id; }, event); + return std::visit([](const auto e) { return e.event_id; }, event); } std::string mtx::accessors::room_id(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto e) { return e.room_id; }, event); + return std::visit([](const auto e) { return e.room_id; }, event); } std::string mtx::accessors::sender(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto e) { return e.sender; }, event); + return std::visit([](const auto e) { return e.sender; }, event); } QDateTime mtx::accessors::origin_server_ts(const mtx::events::collections::TimelineEvents &event) { - return QDateTime::fromMSecsSinceEpoch( - std::visit([](const auto e) { return e.origin_server_ts; }, event)); + return QDateTime::fromMSecsSinceEpoch( + std::visit([](const auto e) { return e.origin_server_ts; }, event)); } std::string mtx::accessors::filename(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFilename{}, event); + return std::visit(EventFilename{}, event); } mtx::events::MessageType mtx::accessors::msg_type(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMsgType{}, event); + return std::visit(EventMsgType{}, event); } std::string mtx::accessors::room_name(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventRoomName{}, event); + return std::visit(EventRoomName{}, event); } std::string mtx::accessors::room_topic(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventRoomTopic{}, event); + return std::visit(EventRoomTopic{}, event); } std::string mtx::accessors::call_type(const mtx::events::collections::TimelineEvents &event) { - return std::visit(CallType{}, event); + return std::visit(CallType{}, event); } std::string mtx::accessors::body(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventBody{}, event); + return std::visit(EventBody{}, event); } std::string mtx::accessors::formatted_body(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFormattedBody{}, event); + return std::visit(EventFormattedBody{}, event); } QString mtx::accessors::formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event) { - auto formatted = formatted_body(event); - if (!formatted.empty()) - return QString::fromStdString(formatted); - else - return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "
"); + auto formatted = formatted_body(event); + if (!formatted.empty()) + return QString::fromStdString(formatted); + else + return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "
"); } std::optional mtx::accessors::file(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFile{}, event); + return std::visit(EventFile{}, event); } std::string mtx::accessors::url(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventUrl{}, event); + return std::visit(EventUrl{}, event); } std::string mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventThumbnailUrl{}, event); + return std::visit(EventThumbnailUrl{}, event); } std::string mtx::accessors::blurhash(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventBlurhash{}, event); + return std::visit(EventBlurhash{}, event); } std::string mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMimeType{}, event); + return std::visit(EventMimeType{}, event); } mtx::common::Relations mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventRelations{}, event); + return std::visit(EventRelations{}, event); } void mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations) { - std::visit(SetEventRelations{std::move(relations)}, event); + std::visit(SetEventRelations{std::move(relations)}, event); } std::string mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventTransactionId{}, event); + return std::visit(EventTransactionId{}, event); } int64_t mtx::accessors::filesize(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventFilesize{}, event); + return std::visit(EventFilesize{}, event); } uint64_t mtx::accessors::media_height(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMediaHeight{}, event); + return std::visit(EventMediaHeight{}, event); } uint64_t mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &event) { - return std::visit(EventMediaWidth{}, event); + return std::visit(EventMediaWidth{}, event); } nlohmann::json mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event) { - return std::visit([](const auto &e) { return nlohmann::json(e); }, event); + return std::visit([](const auto &e) { return nlohmann::json(e); }, event); } bool mtx::accessors::is_state_event(const mtx::events::collections::TimelineEvents &event) { - return std::visit(IsStateEvent{}, event); + return std::visit(IsStateEvent{}, event); } diff --git a/src/EventAccessors.h b/src/EventAccessors.h index a58c7de0..c6b8e854 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -14,24 +14,24 @@ namespace nheko { struct nonesuch { - ~nonesuch() = delete; - nonesuch(nonesuch const &) = delete; - void operator=(nonesuch const &) = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const &) = delete; + void operator=(nonesuch const &) = delete; }; namespace detail { template class Op, class... Args> struct detector { - using value_t = std::false_type; - using type = Default; + using value_t = std::false_type; + using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { - using value_t = std::true_type; - using type = Op; + using value_t = std::true_type; + using type = Op; }; } // namespace detail diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp index 6392de22..39e46f01 100644 --- a/src/ImagePackListModel.cpp +++ b/src/ImagePackListModel.cpp @@ -13,82 +13,81 @@ ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *paren : QAbstractListModel(parent) , room_id(roomId) { - auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); + auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); - for (const auto &pack : packs_) { - packs.push_back( - QSharedPointer(new SingleImagePackModel(pack))); - } + for (const auto &pack : packs_) { + packs.push_back(QSharedPointer(new SingleImagePackModel(pack))); + } } int ImagePackListModel::rowCount(const QModelIndex &) const { - return (int)packs.size(); + return (int)packs.size(); } QHash ImagePackListModel::roleNames() const { - return { - {Roles::DisplayName, "displayName"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::FromAccountData, "fromAccountData"}, - {Roles::FromCurrentRoom, "fromCurrentRoom"}, - {Roles::StateKey, "statekey"}, - {Roles::RoomId, "roomid"}, - }; + return { + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::FromAccountData, "fromAccountData"}, + {Roles::FromCurrentRoom, "fromCurrentRoom"}, + {Roles::StateKey, "statekey"}, + {Roles::RoomId, "roomid"}, + }; } QVariant ImagePackListModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &pack = packs.at(index.row()); - switch (role) { - case Roles::DisplayName: - return pack->packname(); - case Roles::AvatarUrl: - return pack->avatarUrl(); - case Roles::FromAccountData: - return pack->roomid().isEmpty(); - case Roles::FromCurrentRoom: - return pack->roomid().toStdString() == this->room_id; - case Roles::StateKey: - return pack->statekey(); - case Roles::RoomId: - return pack->roomid(); - default: - return {}; - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &pack = packs.at(index.row()); + switch (role) { + case Roles::DisplayName: + return pack->packname(); + case Roles::AvatarUrl: + return pack->avatarUrl(); + case Roles::FromAccountData: + return pack->roomid().isEmpty(); + case Roles::FromCurrentRoom: + return pack->roomid().toStdString() == this->room_id; + case Roles::StateKey: + return pack->statekey(); + case Roles::RoomId: + return pack->roomid(); + default: + return {}; } - return {}; + } + return {}; } SingleImagePackModel * ImagePackListModel::packAt(int row) { - if (row < 0 || static_cast(row) >= packs.size()) - return {}; - auto e = packs.at(row).get(); - QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); - return e; + if (row < 0 || static_cast(row) >= packs.size()) + return {}; + auto e = packs.at(row).get(); + QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); + return e; } SingleImagePackModel * ImagePackListModel::newPack(bool inRoom) { - ImagePackInfo info{}; - if (inRoom) - info.source_room = room_id; - return new SingleImagePackModel(info); + ImagePackInfo info{}; + if (inRoom) + info.source_room = room_id; + return new SingleImagePackModel(info); } bool ImagePackListModel::containsAccountPack() const { - for (const auto &p : packs) - if (p->roomid().isEmpty()) - return true; - return false; + for (const auto &p : packs) + if (p->roomid().isEmpty()) + return true; + return false; } diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h index 2aa5abb2..0b39729a 100644 --- a/src/ImagePackListModel.h +++ b/src/ImagePackListModel.h @@ -11,31 +11,31 @@ class SingleImagePackModel; class ImagePackListModel : public QAbstractListModel { - Q_OBJECT - Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) + Q_OBJECT + Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) public: - enum Roles - { - DisplayName = Qt::UserRole, - AvatarUrl, - FromAccountData, - FromCurrentRoom, - StateKey, - RoomId, - }; - - ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - - Q_INVOKABLE SingleImagePackModel *packAt(int row); - Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); - - bool containsAccountPack() const; + enum Roles + { + DisplayName = Qt::UserRole, + AvatarUrl, + FromAccountData, + FromCurrentRoom, + StateKey, + RoomId, + }; + + ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE SingleImagePackModel *packAt(int row); + Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); + + bool containsAccountPack() const; private: - std::string room_id; + std::string room_id; - std::vector> packs; + std::vector> packs; }; diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 27b2116f..e045581a 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -16,69 +16,68 @@ InviteesModel::InviteesModel(QObject *parent) void InviteesModel::addUser(QString mxid) { - beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); + beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); - auto invitee = new Invitee{mxid, this}; - auto indexOfInvitee = invitees_.count(); - connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { - emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); - }); + auto invitee = new Invitee{mxid, this}; + auto indexOfInvitee = invitees_.count(); + connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { + emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); + }); - invitees_.push_back(invitee); + invitees_.push_back(invitee); - endInsertRows(); - emit countChanged(); + endInsertRows(); + emit countChanged(); } QHash InviteesModel::roleNames() const { - return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; } QVariant InviteesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) - return {}; + if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) + return {}; - switch (role) { - case Mxid: - return invitees_[index.row()]->mxid_; - case DisplayName: - return invitees_[index.row()]->displayName_; - case AvatarUrl: - return invitees_[index.row()]->avatarUrl_; - default: - return {}; - } + switch (role) { + case Mxid: + return invitees_[index.row()]->mxid_; + case DisplayName: + return invitees_[index.row()]->displayName_; + case AvatarUrl: + return invitees_[index.row()]->avatarUrl_; + default: + return {}; + } } QStringList InviteesModel::mxids() { - QStringList mxidList; - for (int i = 0; i < invitees_.length(); ++i) - mxidList.push_back(invitees_[i]->mxid_); - return mxidList; + QStringList mxidList; + for (int i = 0; i < invitees_.length(); ++i) + mxidList.push_back(invitees_[i]->mxid_); + return mxidList; } Invitee::Invitee(const QString &mxid, QObject *parent) : QObject{parent} , mxid_{mxid} { - http::client()->get_profile( - mxid_.toStdString(), - [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve profile info"); - emit userInfoLoaded(); - return; - } + http::client()->get_profile( + mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve profile info"); + emit userInfoLoaded(); + return; + } - displayName_ = QString::fromStdString(res.display_name); - avatarUrl_ = QString::fromStdString(res.avatar_url); + displayName_ = QString::fromStdString(res.display_name); + avatarUrl_ = QString::fromStdString(res.avatar_url); - emit userInfoLoaded(); - }); + emit userInfoLoaded(); + }); } diff --git a/src/InviteesModel.h b/src/InviteesModel.h index a4e19ebb..fd64116b 100644 --- a/src/InviteesModel.h +++ b/src/InviteesModel.h @@ -10,54 +10,54 @@ class Invitee : public QObject { - Q_OBJECT + Q_OBJECT public: - Invitee(const QString &mxid, QObject *parent = nullptr); + Invitee(const QString &mxid, QObject *parent = nullptr); signals: - void userInfoLoaded(); + void userInfoLoaded(); private: - const QString mxid_; - QString displayName_; - QString avatarUrl_; + const QString mxid_; + QString displayName_; + QString avatarUrl_; - friend class InviteesModel; + friend class InviteesModel; }; class InviteesModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - }; + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; - InviteesModel(QObject *parent = nullptr); + InviteesModel(QObject *parent = nullptr); - Q_INVOKABLE void addUser(QString mxid); + Q_INVOKABLE void addUser(QString mxid); - QHash roleNames() const override; - int rowCount(const QModelIndex & = QModelIndex()) const override - { - return (int)invitees_.size(); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QStringList mxids(); + QHash roleNames() const override; + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return (int)invitees_.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QStringList mxids(); signals: - void accept(); - void countChanged(); + void accept(); + void countChanged(); private: - QVector invitees_; + QVector invitees_; }; #endif // INVITEESMODEL_H diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp index 23b601fc..e2828286 100644 --- a/src/JdenticonProvider.cpp +++ b/src/JdenticonProvider.cpp @@ -22,20 +22,20 @@ static QPixmap clipRadius(QPixmap img, double radius) { - QPixmap out(img.size()); - out.fill(Qt::transparent); + QPixmap out(img.size()); + out.fill(Qt::transparent); - QPainter painter(&out); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - QPainterPath ppath; - ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); - painter.setClipPath(ppath); - painter.drawPixmap(img.rect(), img); + painter.setClipPath(ppath); + painter.drawPixmap(img.rect(), img); - return out; + return out; } JdenticonResponse::JdenticonResponse(const QString &key, @@ -49,64 +49,64 @@ JdenticonResponse::JdenticonResponse(const QString &key, , m_pixmap{m_requestedSize} , jdenticonInterface_{Jdenticon::getJdenticonInterface()} { - setAutoDelete(false); + setAutoDelete(false); } void JdenticonResponse::run() { - m_pixmap.fill(Qt::transparent); - - QPainter painter; - painter.begin(&m_pixmap); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - - try { - QSvgRenderer renderer{ - jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()}; - renderer.render(&painter); - } catch (std::exception &e) { - nhlog::ui()->error( - "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString()); - } + m_pixmap.fill(Qt::transparent); + + QPainter painter; + painter.begin(&m_pixmap); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + try { + QSvgRenderer renderer{ + jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()}; + renderer.render(&painter); + } catch (std::exception &e) { + nhlog::ui()->error( + "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString()); + } - painter.end(); + painter.end(); - m_pixmap = clipRadius(m_pixmap, m_radius); + m_pixmap = clipRadius(m_pixmap, m_radius); - emit finished(); + emit finished(); } namespace Jdenticon { JdenticonInterface * getJdenticonInterface() { - static JdenticonInterface *interface = nullptr; - static bool interfaceExists{true}; - - if (interface == nullptr && interfaceExists) { - QDir pluginsDir(qApp->applicationDirPath()); - - bool plugins = pluginsDir.cd("plugins"); - if (plugins) { - for (const QString &fileName : pluginsDir.entryList(QDir::Files)) { - QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = pluginLoader.instance(); - if (plugin) { - interface = qobject_cast(plugin); - if (interface) { - nhlog::ui()->info("Loaded jdenticon plugin."); - break; - } - } - } - } else { - nhlog::ui()->info("jdenticon plugin not found."); - interfaceExists = false; + static JdenticonInterface *interface = nullptr; + static bool interfaceExists{true}; + + if (interface == nullptr && interfaceExists) { + QDir pluginsDir(qApp->applicationDirPath()); + + bool plugins = pluginsDir.cd("plugins"); + if (plugins) { + for (const QString &fileName : pluginsDir.entryList(QDir::Files)) { + QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = pluginLoader.instance(); + if (plugin) { + interface = qobject_cast(plugin); + if (interface) { + nhlog::ui()->info("Loaded jdenticon plugin."); + break; + } } + } + } else { + nhlog::ui()->info("jdenticon plugin not found."); + interfaceExists = false; } + } - return interface; + return interface; } } diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h index bcda29c8..f4ef6d10 100644 --- a/src/JdenticonProvider.h +++ b/src/JdenticonProvider.h @@ -23,59 +23,58 @@ class JdenticonResponse , public QRunnable { public: - JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize); + JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize); - QQuickTextureFactory *textureFactory() const override - { - return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage()); - } + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage()); + } - void run() override; + void run() override; - QString m_key; - bool m_crop; - double m_radius; - QSize m_requestedSize; - QPixmap m_pixmap; - JdenticonInterface *jdenticonInterface_ = nullptr; + QString m_key; + bool m_crop; + double m_radius; + QSize m_requestedSize; + QPixmap m_pixmap; + JdenticonInterface *jdenticonInterface_ = nullptr; }; class JdenticonProvider : public QObject , public QQuickAsyncImageProvider { - Q_OBJECT + Q_OBJECT public: - static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; } + static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; } public slots: - QQuickImageResponse *requestImageResponse(const QString &id, - const QSize &requestedSize) override - { - auto id_ = id; - bool crop = true; - double radius = 0; - - auto queryStart = id.lastIndexOf('?'); - if (queryStart != -1) { - id_ = id.left(queryStart); - auto query = id.midRef(queryStart + 1); - auto queryBits = query.split('&'); - - for (auto b : queryBits) { - if (b.startsWith("radius=")) { - radius = b.mid(7).toDouble(); - } - } + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override + { + auto id_ = id; + bool crop = true; + double radius = 0; + + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); + + for (auto b : queryBits) { + if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); } - - JdenticonResponse *response = - new JdenticonResponse(id_, crop, radius, requestedSize); - pool.start(response); - return response; + } } + JdenticonResponse *response = new JdenticonResponse(id_, crop, radius, requestedSize); + pool.start(response); + return response; + } + private: - QThreadPool pool; + QThreadPool pool; }; diff --git a/src/Logging.cpp b/src/Logging.cpp index 67bcaf7a..a18a1cee 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -25,35 +25,35 @@ constexpr auto MAX_LOG_FILES = 3; void qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - std::string localMsg = msg.toStdString(); - const char *file = context.file ? context.file : ""; - const char *function = context.function ? context.function : ""; - - if ( - // The default style has the point size set. If you use pixel size anywhere, you get - // that warning, which is useless, since sometimes you need the pixel size to match the - // text to the size of the outer element for example. This is done in the avatar and - // without that you get one warning for every Avatar displayed, which is stupid! - msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size."))) - return; - - switch (type) { - case QtDebugMsg: - nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtInfoMsg: - nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtWarningMsg: - nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtCriticalMsg: - nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - case QtFatalMsg: - nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); - break; - } + std::string localMsg = msg.toStdString(); + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + + if ( + // The default style has the point size set. If you use pixel size anywhere, you get + // that warning, which is useless, since sometimes you need the pixel size to match the + // text to the size of the outer element for example. This is done in the avatar and + // without that you get one warning for every Avatar displayed, which is stupid! + msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size."))) + return; + + switch (type) { + case QtDebugMsg: + nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtInfoMsg: + nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtWarningMsg: + nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtCriticalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + case QtFatalMsg: + nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function); + break; + } } } @@ -63,60 +63,59 @@ bool enable_debug_log_from_commandline = false; void init(const std::string &file_path) { - auto file_sink = std::make_shared( - file_path, MAX_FILE_SIZE, MAX_LOG_FILES); - - auto console_sink = std::make_shared(); - - std::vector sinks; - sinks.push_back(file_sink); - sinks.push_back(console_sink); - - net_logger = std::make_shared("net", std::begin(sinks), std::end(sinks)); - ui_logger = std::make_shared("ui", std::begin(sinks), std::end(sinks)); - db_logger = std::make_shared("db", std::begin(sinks), std::end(sinks)); - crypto_logger = - std::make_shared("crypto", std::begin(sinks), std::end(sinks)); - qml_logger = std::make_shared("qml", std::begin(sinks), std::end(sinks)); - - if (nheko::enable_debug_log || enable_debug_log_from_commandline) { - db_logger->set_level(spdlog::level::trace); - ui_logger->set_level(spdlog::level::trace); - crypto_logger->set_level(spdlog::level::trace); - net_logger->set_level(spdlog::level::trace); - qml_logger->set_level(spdlog::level::trace); - } - - qInstallMessageHandler(qmlMessageHandler); + auto file_sink = std::make_shared( + file_path, MAX_FILE_SIZE, MAX_LOG_FILES); + + auto console_sink = std::make_shared(); + + std::vector sinks; + sinks.push_back(file_sink); + sinks.push_back(console_sink); + + net_logger = std::make_shared("net", std::begin(sinks), std::end(sinks)); + ui_logger = std::make_shared("ui", std::begin(sinks), std::end(sinks)); + db_logger = std::make_shared("db", std::begin(sinks), std::end(sinks)); + crypto_logger = std::make_shared("crypto", std::begin(sinks), std::end(sinks)); + qml_logger = std::make_shared("qml", std::begin(sinks), std::end(sinks)); + + if (nheko::enable_debug_log || enable_debug_log_from_commandline) { + db_logger->set_level(spdlog::level::trace); + ui_logger->set_level(spdlog::level::trace); + crypto_logger->set_level(spdlog::level::trace); + net_logger->set_level(spdlog::level::trace); + qml_logger->set_level(spdlog::level::trace); + } + + qInstallMessageHandler(qmlMessageHandler); } std::shared_ptr ui() { - return ui_logger; + return ui_logger; } std::shared_ptr net() { - return net_logger; + return net_logger; } std::shared_ptr db() { - return db_logger; + return db_logger; } std::shared_ptr crypto() { - return crypto_logger; + return crypto_logger; } std::shared_ptr qml() { - return qml_logger; + return qml_logger; } } diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index f53d81ba..64e9c865 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -34,477 +34,468 @@ LoginPage::LoginPage(QWidget *parent) : QWidget(parent) , inferredServerAddress_() { - qRegisterMetaType("LoginPage::LoginMethod"); + qRegisterMetaType("LoginPage::LoginMethod"); - top_layout_ = new QVBoxLayout(); + top_layout_ = new QVBoxLayout(); + + top_bar_layout_ = new QHBoxLayout(); + top_bar_layout_->setSpacing(0); + top_bar_layout_->setMargin(0); + + back_button_ = new FlatButton(this); + back_button_->setMinimumSize(QSize(30, 30)); - top_bar_layout_ = new QHBoxLayout(); - top_bar_layout_->setSpacing(0); - top_bar_layout_->setMargin(0); - - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); - - top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - top_bar_layout_->addStretch(1); - - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); - - QIcon logo; - logo.addFile(":/logos/login.png"); - - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); - - logo_layout_ = new QHBoxLayout(); - logo_layout_->setContentsMargins(0, 0, 0, 20); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 200)); - - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 30); - form_widget_->setLayout(form_layout_); - - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); - - matrixid_input_ = new TextField(this); - matrixid_input_->setLabel(tr("Matrix ID")); - matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}")); - matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); - matrixid_input_->setToolTip( - tr("Your login name. A mxid should start with @ followed by the user id. After the user " - "id you need to include your server name after a :.\nYou can also put your homeserver " - "address there, if your server doesn't support .well-known lookup.\nExample: " - "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " - "field to enter the server manually.")); - - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(40); - spinner_->setFixedWidth(40); - spinner_->hide(); - - errorIcon_ = new QLabel(this); - errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); - errorIcon_->hide(); - - matrixidLayout_ = new QHBoxLayout(); - matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); - - QFont font; - - error_matrixid_label_ = new QLabel(this); - error_matrixid_label_->setFont(font); - error_matrixid_label_->setWordWrap(true); - - password_input_ = new TextField(this); - password_input_->setLabel(tr("Password")); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Your password.")); - - deviceName_ = new TextField(this); - deviceName_->setLabel(tr("Device name")); - deviceName_->setToolTip( - tr("A name for this device, which will be shown to others, when verifying your devices. " - "If none is provided a default is used.")); - - serverInput_ = new TextField(this); - serverInput_->setLabel(tr("Homeserver address")); - serverInput_->setPlaceholderText(tr("server.my:8787")); - serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " - "client API.\nExample: https://server.my:8787")); - serverInput_->hide(); - - serverLayout_ = new QHBoxLayout(); - serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); - - form_layout_->addLayout(matrixidLayout_); - form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter); - form_layout_->addWidget(password_input_); - form_layout_->addWidget(deviceName_, Qt::AlignHCenter); - form_layout_->addLayout(serverLayout_); - - error_matrixid_label_->hide(); - - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(20); - button_layout_->setContentsMargins(0, 0, 0, 30); - - login_button_ = new RaisedButton(tr("LOGIN"), this); - login_button_->setMinimumSize(150, 65); - login_button_->setFontSize(20); - login_button_->setCornerRadius(3); - - sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); - sso_login_button_->setMinimumSize(150, 65); - sso_login_button_->setFontSize(20); - sso_login_button_->setCornerRadius(3); - sso_login_button_->setVisible(false); - - button_layout_->addStretch(1); - button_layout_->addWidget(login_button_); - button_layout_->addWidget(sso_login_button_); - button_layout_->addStretch(1); - - error_label_ = new QLabel(this); - error_label_->setFont(font); - error_label_->setWordWrap(true); - - top_layout_->addLayout(top_bar_layout_); - top_layout_->addStretch(1); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); - - setLayout(top_layout_); - - connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection); - connect( - this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); - - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); - }); - connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { - onLoginButtonClicked(LoginMethod::SSO); - }); - connect(this, - &LoginPage::showErrorMessage, - this, - static_cast(&LoginPage::showError), - Qt::QueuedConnection); - connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); - connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); - connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); + top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + top_bar_layout_->addStretch(1); + + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + + back_button_->setIcon(icon); + back_button_->setIconSize(QSize(32, 32)); + + QIcon logo; + logo.addFile(":/logos/login.png"); + + logo_ = new QLabel(this); + logo_->setPixmap(logo.pixmap(128)); + + logo_layout_ = new QHBoxLayout(); + logo_layout_->setContentsMargins(0, 0, 0, 20); + logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); + + form_wrapper_ = new QHBoxLayout(); + form_widget_ = new QWidget(); + form_widget_->setMinimumSize(QSize(350, 200)); + + form_layout_ = new QVBoxLayout(); + form_layout_->setSpacing(20); + form_layout_->setContentsMargins(0, 0, 0, 30); + form_widget_->setLayout(form_layout_); + + form_wrapper_->addStretch(1); + form_wrapper_->addWidget(form_widget_); + form_wrapper_->addStretch(1); + + matrixid_input_ = new TextField(this); + matrixid_input_->setLabel(tr("Matrix ID")); + matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}")); + matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org")); + matrixid_input_->setToolTip( + tr("Your login name. A mxid should start with @ followed by the user id. After the user " + "id you need to include your server name after a :.\nYou can also put your homeserver " + "address there, if your server doesn't support .well-known lookup.\nExample: " + "@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a " + "field to enter the server manually.")); + + spinner_ = new LoadingIndicator(this); + spinner_->setFixedHeight(40); + spinner_->setFixedWidth(40); + spinner_->hide(); + + errorIcon_ = new QLabel(this); + errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png")); + errorIcon_->hide(); + + matrixidLayout_ = new QHBoxLayout(); + matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter); + + QFont font; + + error_matrixid_label_ = new QLabel(this); + error_matrixid_label_->setFont(font); + error_matrixid_label_->setWordWrap(true); + + password_input_ = new TextField(this); + password_input_->setLabel(tr("Password")); + password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip(tr("Your password.")); + + deviceName_ = new TextField(this); + deviceName_->setLabel(tr("Device name")); + deviceName_->setToolTip( + tr("A name for this device, which will be shown to others, when verifying your devices. " + "If none is provided a default is used.")); + + serverInput_ = new TextField(this); + serverInput_->setLabel(tr("Homeserver address")); + serverInput_->setPlaceholderText(tr("server.my:8787")); + serverInput_->setToolTip(tr("The address that can be used to contact you homeservers " + "client API.\nExample: https://server.my:8787")); + serverInput_->hide(); + + serverLayout_ = new QHBoxLayout(); + serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter); + + form_layout_->addLayout(matrixidLayout_); + form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter); + form_layout_->addWidget(password_input_); + form_layout_->addWidget(deviceName_, Qt::AlignHCenter); + form_layout_->addLayout(serverLayout_); + + error_matrixid_label_->hide(); + + button_layout_ = new QHBoxLayout(); + button_layout_->setSpacing(20); + button_layout_->setContentsMargins(0, 0, 0, 30); + + login_button_ = new RaisedButton(tr("LOGIN"), this); + login_button_->setMinimumSize(150, 65); + login_button_->setFontSize(20); + login_button_->setCornerRadius(3); + + sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); + sso_login_button_->setMinimumSize(150, 65); + sso_login_button_->setFontSize(20); + sso_login_button_->setCornerRadius(3); + sso_login_button_->setVisible(false); + + button_layout_->addStretch(1); + button_layout_->addWidget(login_button_); + button_layout_->addWidget(sso_login_button_); + button_layout_->addStretch(1); + + error_label_ = new QLabel(this); + error_label_->setFont(font); + error_label_->setWordWrap(true); + + top_layout_->addLayout(top_bar_layout_); + top_layout_->addStretch(1); + top_layout_->addLayout(logo_layout_); + top_layout_->addLayout(form_wrapper_); + top_layout_->addStretch(1); + top_layout_->addLayout(button_layout_); + top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); + top_layout_->addStretch(1); + + setLayout(top_layout_); + + connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection); + connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); + + connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + connect(login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); + }); + connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(LoginMethod::SSO); + }); + connect(this, + &LoginPage::showErrorMessage, + this, + static_cast(&LoginPage::showError), + Qt::QueuedConnection); + connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click())); + connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered())); + connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); } void LoginPage::showError(const QString &msg) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight((int)qCeil(width / 200.0) * height); - error_label_->setText(msg); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + error_label_->setFixedHeight((int)qCeil(width / 200.0) * height); + error_label_->setText(msg); } void LoginPage::showError(QLabel *label, const QString &msg) { - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + label->setFixedHeight((int)qCeil(width / 200.0) * height); + label->setText(msg); } void LoginPage::onMatrixIdEntered() { - error_label_->setText(""); + error_label_->setText(""); - User user; + User user; - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; + if (!matrixid_input_->isValid()) { + error_matrixid_label_->show(); + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } else { + error_matrixid_label_->setText(""); + error_matrixid_label_->hide(); + } + + try { + user = parse(matrixid_input_->text().toStdString()); + } catch (const std::exception &) { + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } + + QString homeServer = QString::fromStdString(user.hostname()); + if (homeServer != inferredServerAddress_) { + serverInput_->hide(); + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + if (serverInput_->isVisible()) { + matrixidLayout_->removeWidget(spinner_); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } else { - error_matrixid_label_->setText(""); - error_matrixid_label_->hide(); + serverLayout_->removeWidget(spinner_); + matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } - try { - user = parse(matrixid_input_->text().toStdString()); - } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } + inferredServerAddress_ = homeServer; + serverInput_->setText(homeServer); - QString homeServer = QString::fromStdString(user.hostname()); - if (homeServer != inferredServerAddress_) { - serverInput_->hide(); - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - if (serverInput_->isVisible()) { - matrixidLayout_->removeWidget(spinner_); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } else { - serverLayout_->removeWidget(spinner_); - matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); - } - - inferredServerAddress_ = homeServer; - serverInput_->setText(homeServer); - - http::client()->set_server(user.hostname()); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); - - http::client()->well_known([this](const mtx::responses::WellKnown &res, - mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - nhlog::net()->info("Autodiscovery: No .well-known."); - checkHomeserverVersion(); - return; - } - - if (!err->parse_error.empty()) { - emit versionErrorCb( - tr("Autodiscovery failed. Received malformed response.")); - nhlog::net()->error( - "Autodiscovery failed. Received malformed response."); - return; - } - - emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); - nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known. {} {}", - err->status_code, - err->error_code); - return; - } - - nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + - "'"); - http::client()->set_server(res.homeserver.base_url); - checkHomeserverVersion(); - }); - } + http::client()->set_server(user.hostname()); + http::client()->verify_certificates( + !UserSettings::instance()->disableCertificateValidation()); + + http::client()->well_known( + [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + nhlog::net()->info("Autodiscovery: No .well-known."); + checkHomeserverVersion(); + return; + } + + if (!err->parse_error.empty()) { + emit versionErrorCb(tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error("Autodiscovery failed. Received malformed response."); + return; + } + + emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known. {} {}", + err->status_code, + err->error_code); + return; + } + + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); + http::client()->set_server(res.homeserver.base_url); + checkHomeserverVersion(); + }); + } } void LoginPage::checkHomeserverVersion() { - http::client()->versions( - [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - emit versionErrorCb(tr("The required endpoints were not found. " - "Possibly not a Matrix server.")); - return; - } - - if (!err->parse_error.empty()) { - emit versionErrorCb(tr("Received malformed response. Make sure " - "the homeserver domain is valid.")); - return; - } - - emit versionErrorCb(tr( - "An unknown error occured. Make sure the homeserver domain is valid.")); - return; - } + http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + emit versionErrorCb(tr("The required endpoints were not found. " + "Possibly not a Matrix server.")); + return; + } - http::client()->get_login( - [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { - if (err || flows.flows.empty()) - emit versionOkCb(true, false); - - bool ssoSupported_ = false; - bool passwordSupported_ = false; - for (const auto &flow : flows.flows) { - if (flow.type == mtx::user_interactive::auth_types::sso) { - ssoSupported_ = true; - } else if (flow.type == - mtx::user_interactive::auth_types::password) { - passwordSupported_ = true; - } - } - emit versionOkCb(passwordSupported_, ssoSupported_); - }); + if (!err->parse_error.empty()) { + emit versionErrorCb(tr("Received malformed response. Make sure " + "the homeserver domain is valid.")); + return; + } + + emit versionErrorCb( + tr("An unknown error occured. Make sure the homeserver domain is valid.")); + return; + } + + http::client()->get_login( + [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { + if (err || flows.flows.empty()) + emit versionOkCb(true, false); + + bool ssoSupported_ = false; + bool passwordSupported_ = false; + for (const auto &flow : flows.flows) { + if (flow.type == mtx::user_interactive::auth_types::sso) { + ssoSupported_ = true; + } else if (flow.type == mtx::user_interactive::auth_types::password) { + passwordSupported_ = true; + } + } + emit versionOkCb(passwordSupported_, ssoSupported_); }); + }); } void LoginPage::onServerAddressEntered() { - error_label_->setText(""); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); - http::client()->set_server(serverInput_->text().toStdString()); - checkHomeserverVersion(); - - serverLayout_->removeWidget(errorIcon_); - errorIcon_->hide(); - serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); - spinner_->start(); + error_label_->setText(""); + http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation()); + http::client()->set_server(serverInput_->text().toStdString()); + checkHomeserverVersion(); + + serverLayout_->removeWidget(errorIcon_); + errorIcon_->hide(); + serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight); + spinner_->start(); } void LoginPage::versionError(const QString &error) { - showError(error_label_, error); - serverInput_->show(); - - spinner_->stop(); - serverLayout_->removeWidget(spinner_); - serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); - errorIcon_->show(); - matrixidLayout_->removeWidget(spinner_); + showError(error_label_, error); + serverInput_->show(); + + spinner_->stop(); + serverLayout_->removeWidget(spinner_); + serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight); + errorIcon_->show(); + matrixidLayout_->removeWidget(spinner_); } void LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_) { - passwordSupported = passwordSupported_; - ssoSupported = ssoSupported_; + passwordSupported = passwordSupported_; + ssoSupported = ssoSupported_; - serverLayout_->removeWidget(spinner_); - matrixidLayout_->removeWidget(spinner_); - spinner_->stop(); + serverLayout_->removeWidget(spinner_); + matrixidLayout_->removeWidget(spinner_); + spinner_->stop(); - sso_login_button_->setVisible(ssoSupported); - login_button_->setVisible(passwordSupported); + sso_login_button_->setVisible(ssoSupported); + login_button_->setVisible(passwordSupported); - if (serverInput_->isVisible()) - serverInput_->hide(); + if (serverInput_->isVisible()) + serverInput_->hide(); } void LoginPage::onLoginButtonClicked(LoginMethod loginMethod) { - error_label_->setText(""); - User user; + error_label_->setText(""); + User user; + + if (!matrixid_input_->isValid()) { + error_matrixid_label_->show(); + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } else { + error_matrixid_label_->setText(""); + error_matrixid_label_->hide(); + } + + try { + user = parse(matrixid_input_->text().toStdString()); + } catch (const std::exception &) { + showError(error_matrixid_label_, + tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); + return; + } + + if (loginMethod == LoginMethod::Password) { + if (password_input_->text().isEmpty()) + return showError(error_label_, tr("Empty password")); + + http::client()->login( + user.localpart(), + password_input_->text().toStdString(), + deviceName_->text().trimmed().isEmpty() ? initialDeviceName() + : deviceName_->text().toStdString(), + [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + auto error = err->matrix_error.error; + if (error.empty()) + error = err->parse_error; + + showErrorMessage(error_label_, QString::fromStdString(error)); + emit errorOccurred(); + return; + } + + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } + + emit loginOk(res); + }); + } else { + auto sso = new SSOHandler(); + connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { + mtx::requests::Login req{}; + req.token = token; + req.type = mtx::user_interactive::auth_types::token; + req.device_id = deviceName_->text().trimmed().isEmpty() + ? initialDeviceName() + : deviceName_->text().toStdString(); + http::client()->login( + req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { + if (err) { + showErrorMessage(error_label_, + QString::fromStdString(err->matrix_error.error)); + emit errorOccurred(); + return; + } - if (!matrixid_input_->isValid()) { - error_matrixid_label_->show(); - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } else { - error_matrixid_label_->setText(""); - error_matrixid_label_->hide(); - } + if (res.well_known) { + http::client()->set_server(res.well_known->homeserver.base_url); + nhlog::net()->info("Login requested to user server: " + + res.well_known->homeserver.base_url); + } - try { - user = parse(matrixid_input_->text().toStdString()); - } catch (const std::exception &) { - showError(error_matrixid_label_, - tr("You have entered an invalid Matrix ID e.g @joe:matrix.org")); - return; - } + emit loginOk(res); + }); + sso->deleteLater(); + }); + connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { + showErrorMessage(error_label_, tr("SSO login failed")); + emit errorOccurred(); + sso->deleteLater(); + }); - if (loginMethod == LoginMethod::Password) { - if (password_input_->text().isEmpty()) - return showError(error_label_, tr("Empty password")); - - http::client()->login( - user.localpart(), - password_input_->text().toStdString(), - deviceName_->text().trimmed().isEmpty() ? initialDeviceName() - : deviceName_->text().toStdString(), - [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - auto error = err->matrix_error.error; - if (error.empty()) - error = err->parse_error; - - showErrorMessage(error_label_, QString::fromStdString(error)); - emit errorOccurred(); - return; - } - - if (res.well_known) { - http::client()->set_server(res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } - - emit loginOk(res); - }); - } else { - auto sso = new SSOHandler(); - connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) { - mtx::requests::Login req{}; - req.token = token; - req.type = mtx::user_interactive::auth_types::token; - req.device_id = deviceName_->text().trimmed().isEmpty() - ? initialDeviceName() - : deviceName_->text().toStdString(); - http::client()->login( - req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { - if (err) { - showErrorMessage( - error_label_, - QString::fromStdString(err->matrix_error.error)); - emit errorOccurred(); - return; - } - - if (res.well_known) { - http::client()->set_server( - res.well_known->homeserver.base_url); - nhlog::net()->info("Login requested to user server: " + - res.well_known->homeserver.base_url); - } - - emit loginOk(res); - }); - sso->deleteLater(); - }); - connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { - showErrorMessage(error_label_, tr("SSO login failed")); - emit errorOccurred(); - sso->deleteLater(); - }); - - QDesktopServices::openUrl( - QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); - } + QDesktopServices::openUrl( + QString::fromStdString(http::client()->login_sso_redirect(sso->url()))); + } - emit loggingIn(); + emit loggingIn(); } void LoginPage::reset() { - matrixid_input_->clear(); - password_input_->clear(); - password_input_->show(); - serverInput_->clear(); - - spinner_->stop(); - errorIcon_->hide(); - serverLayout_->removeWidget(spinner_); - serverLayout_->removeWidget(errorIcon_); - matrixidLayout_->removeWidget(spinner_); - - inferredServerAddress_.clear(); + matrixid_input_->clear(); + password_input_->clear(); + password_input_->show(); + serverInput_->clear(); + + spinner_->stop(); + errorIcon_->hide(); + serverLayout_->removeWidget(spinner_); + serverLayout_->removeWidget(errorIcon_); + matrixidLayout_->removeWidget(spinner_); + + inferredServerAddress_.clear(); } void LoginPage::onBackButtonClicked() { - emit backButtonClicked(); + emit backButtonClicked(); } void LoginPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } diff --git a/src/LoginPage.h b/src/LoginPage.h index 2e1eb9b9..01dd27e1 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -24,101 +24,101 @@ struct Login; class LoginPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - enum class LoginMethod - { - Password, - SSO, - }; + enum class LoginMethod + { + Password, + SSO, + }; - LoginPage(QWidget *parent = nullptr); + LoginPage(QWidget *parent = nullptr); - void reset(); + void reset(); signals: - void backButtonClicked(); - void loggingIn(); - void errorOccurred(); + void backButtonClicked(); + void loggingIn(); + void errorOccurred(); - //! Used to trigger the corresponding slot outside of the main thread. - void versionErrorCb(const QString &err); - void versionOkCb(bool passwordSupported, bool ssoSupported); + //! Used to trigger the corresponding slot outside of the main thread. + void versionErrorCb(const QString &err); + void versionOkCb(bool passwordSupported, bool ssoSupported); - void loginOk(const mtx::responses::Login &res); - void showErrorMessage(QLabel *label, const QString &msg); + void loginOk(const mtx::responses::Login &res); + void showErrorMessage(QLabel *label, const QString &msg); protected: - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent *event) override; public slots: - // Displays errors produced during the login. - void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); + // Displays errors produced during the login. + void showError(const QString &msg); + void showError(QLabel *label, const QString &msg); private slots: - // Callback for the back button. - void onBackButtonClicked(); + // Callback for the back button. + void onBackButtonClicked(); - // Callback for the login button. - void onLoginButtonClicked(LoginMethod loginMethod); + // Callback for the login button. + void onLoginButtonClicked(LoginMethod loginMethod); - // Callback for probing the server found in the mxid - void onMatrixIdEntered(); + // Callback for probing the server found in the mxid + void onMatrixIdEntered(); - // Callback for probing the manually entered server - void onServerAddressEntered(); + // Callback for probing the manually entered server + void onServerAddressEntered(); - // Callback for errors produced during server probing - void versionError(const QString &error_message); - // Callback for successful server probing - void versionOk(bool passwordSupported, bool ssoSupported); + // Callback for errors produced during server probing + void versionError(const QString &error_message); + // Callback for successful server probing + void versionOk(bool passwordSupported, bool ssoSupported); private: - void checkHomeserverVersion(); - std::string initialDeviceName() - { + void checkHomeserverVersion(); + std::string initialDeviceName() + { #if defined(Q_OS_MAC) - return "Nheko on macOS"; + return "Nheko on macOS"; #elif defined(Q_OS_LINUX) - return "Nheko on Linux"; + return "Nheko on Linux"; #elif defined(Q_OS_WIN) - return "Nheko on Windows"; + return "Nheko on Windows"; #elif defined(Q_OS_FREEBSD) - return "Nheko on FreeBSD"; + return "Nheko on FreeBSD"; #else - return "Nheko"; + return "Nheko"; #endif - } + } - QVBoxLayout *top_layout_; + QVBoxLayout *top_layout_; - QHBoxLayout *top_bar_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; + QHBoxLayout *top_bar_layout_; + QHBoxLayout *logo_layout_; + QHBoxLayout *button_layout_; - QLabel *logo_; - QLabel *error_label_; - QLabel *error_matrixid_label_; + QLabel *logo_; + QLabel *error_label_; + QLabel *error_matrixid_label_; - QHBoxLayout *serverLayout_; - QHBoxLayout *matrixidLayout_; - LoadingIndicator *spinner_; - QLabel *errorIcon_; - QString inferredServerAddress_; + QHBoxLayout *serverLayout_; + QHBoxLayout *matrixidLayout_; + LoadingIndicator *spinner_; + QLabel *errorIcon_; + QString inferredServerAddress_; - FlatButton *back_button_; - RaisedButton *login_button_, *sso_login_button_; + FlatButton *back_button_; + RaisedButton *login_button_, *sso_login_button_; - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; + QWidget *form_widget_; + QHBoxLayout *form_wrapper_; + QVBoxLayout *form_layout_; - TextField *matrixid_input_; - TextField *password_input_; - TextField *deviceName_; - TextField *serverInput_; - bool passwordSupported = true; - bool ssoSupported = false; + TextField *matrixid_input_; + TextField *password_input_; + TextField *deviceName_; + TextField *serverInput_; + bool passwordSupported = true; + bool ssoSupported = false; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index b423304f..bc53b906 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -43,415 +43,407 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , userSettings_{UserSettings::instance()} { - instance_ = this; - - setWindowTitle(0); - setObjectName("MainWindow"); - - modal_ = new OverlayModal(this); - - restoreWindowSize(); - - QFont font; - font.setStyleStrategy(QFont::PreferAntialias); - setFont(font); - - trayIcon_ = new TrayIcon(":/logos/nheko.svg", this); - - welcome_page_ = new WelcomePage(this); - login_page_ = new LoginPage(this); - register_page_ = new RegisterPage(this); - chat_page_ = new ChatPage(userSettings_, this); - userSettingsPage_ = new UserSettingsPage(userSettings_, this); - - // Initialize sliding widget manager. - pageStack_ = new QStackedWidget(this); - pageStack_->addWidget(welcome_page_); - pageStack_->addWidget(login_page_); - pageStack_->addWidget(register_page_); - pageStack_->addWidget(chat_page_); - pageStack_->addWidget(userSettingsPage_); - - setCentralWidget(pageStack_); - - connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); - connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); - - connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); - connect( - register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar); - connect( - login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); - connect(register_page_, &RegisterPage::errorOccurred, this, [this]() { - removeOverlayProgressBar(); - }); - connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); - - connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); - connect( - chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); - connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); - connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); - connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { - login_page_->showError(msg); - showLoginPage(); - }); - - connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() { - pageStack_->setCurrentWidget(chat_page_); - }); - - connect( - userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); - connect( - userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); - connect(trayIcon_, - SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - - connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); - - connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); - - connect( - chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); - - connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { - http::client()->set_user(res.user_id); - showChatPage(); - }); - - connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); - - QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); - connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); - - trayIcon_->setVisible(userSettings_->tray()); - - // load cache on event loop - QTimer::singleShot(0, this, [this] { - if (hasActiveUser()) { - QString token = userSettings_->accessToken(); - QString home_server = userSettings_->homeserver(); - QString user_id = userSettings_->userId(); - QString device_id = userSettings_->deviceId(); - - http::client()->set_access_token(token.toStdString()); - http::client()->set_server(home_server.toStdString()); - http::client()->set_device_id(device_id.toStdString()); - - try { - using namespace mtx::identifiers; - http::client()->set_user(parse(user_id.toStdString())); - } catch (const std::invalid_argument &) { - nhlog::ui()->critical("bootstrapped with invalid user_id: {}", - user_id.toStdString()); - } - - showChatPage(); - } - }); + instance_ = this; + + setWindowTitle(0); + setObjectName("MainWindow"); + + modal_ = new OverlayModal(this); + + restoreWindowSize(); + + QFont font; + font.setStyleStrategy(QFont::PreferAntialias); + setFont(font); + + trayIcon_ = new TrayIcon(":/logos/nheko.svg", this); + + welcome_page_ = new WelcomePage(this); + login_page_ = new LoginPage(this); + register_page_ = new RegisterPage(this); + chat_page_ = new ChatPage(userSettings_, this); + userSettingsPage_ = new UserSettingsPage(userSettings_, this); + + // Initialize sliding widget manager. + pageStack_ = new QStackedWidget(this); + pageStack_->addWidget(welcome_page_); + pageStack_->addWidget(login_page_); + pageStack_->addWidget(register_page_); + pageStack_->addWidget(chat_page_); + pageStack_->addWidget(userSettingsPage_); + + setCentralWidget(pageStack_); + + connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage())); + connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage())); + + connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar); + connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar); + connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); + connect( + register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); }); + connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage())); + + connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage); + connect( + chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar); + connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle); + connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int))); + connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) { + login_page_->showError(msg); + showLoginPage(); + }); + + connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() { + pageStack_->setCurrentWidget(chat_page_); + }); + + connect(userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool))); + connect( + userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged); + connect(trayIcon_, + SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, + SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + + connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); + + connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged); + + connect(chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); + + connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) { + http::client()->set_user(res.user_id); + showChatPage(); + }); + + connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage); + + QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); + connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); + + trayIcon_->setVisible(userSettings_->tray()); + + // load cache on event loop + QTimer::singleShot(0, this, [this] { + if (hasActiveUser()) { + QString token = userSettings_->accessToken(); + QString home_server = userSettings_->homeserver(); + QString user_id = userSettings_->userId(); + QString device_id = userSettings_->deviceId(); + + http::client()->set_access_token(token.toStdString()); + http::client()->set_server(home_server.toStdString()); + http::client()->set_device_id(device_id.toStdString()); + + try { + using namespace mtx::identifiers; + http::client()->set_user(parse(user_id.toStdString())); + } catch (const std::invalid_argument &) { + nhlog::ui()->critical("bootstrapped with invalid user_id: {}", + user_id.toStdString()); + } + + showChatPage(); + } + }); } void MainWindow::setWindowTitle(int notificationCount) { - QString name = "nheko"; - - if (!userSettings_.data()->profile().isEmpty()) - name += " | " + userSettings_.data()->profile(); - if (notificationCount > 0) { - name.append(QString{" (%1)"}.arg(notificationCount)); - } - QMainWindow::setWindowTitle(name); + QString name = "nheko"; + + if (!userSettings_.data()->profile().isEmpty()) + name += " | " + userSettings_.data()->profile(); + if (notificationCount > 0) { + name.append(QString{" (%1)"}.arg(notificationCount)); + } + QMainWindow::setWindowTitle(name); } bool MainWindow::event(QEvent *event) { - auto type = event->type(); - if (type == QEvent::WindowActivate) { - emit focusChanged(true); - } else if (type == QEvent::WindowDeactivate) { - emit focusChanged(false); - } - - return QMainWindow::event(event); + auto type = event->type(); + if (type == QEvent::WindowActivate) { + emit focusChanged(true); + } else if (type == QEvent::WindowDeactivate) { + emit focusChanged(false); + } + + return QMainWindow::event(event); } void MainWindow::restoreWindowSize() { - int savedWidth = userSettings_->qsettings()->value("window/width").toInt(); - int savedheight = userSettings_->qsettings()->value("window/height").toInt(); + int savedWidth = userSettings_->qsettings()->value("window/width").toInt(); + int savedheight = userSettings_->qsettings()->value("window/height").toInt(); - nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight); + nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight); - if (savedWidth == 0 || savedheight == 0) - resize(conf::window::width, conf::window::height); - else - resize(savedWidth, savedheight); + if (savedWidth == 0 || savedheight == 0) + resize(conf::window::width, conf::window::height); + else + resize(savedWidth, savedheight); } void MainWindow::saveCurrentWindowSize() { - auto settings = userSettings_->qsettings(); - QSize current = size(); + auto settings = userSettings_->qsettings(); + QSize current = size(); - settings->setValue("window/width", current.width()); - settings->setValue("window/height", current.height()); + settings->setValue("window/width", current.width()); + settings->setValue("window/height", current.height()); } void MainWindow::removeOverlayProgressBar() { - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); - connect(timer, &QTimer::timeout, [this, timer]() { - timer->deleteLater(); + connect(timer, &QTimer::timeout, [this, timer]() { + timer->deleteLater(); - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); - if (spinner_) - spinner_->stop(); - }); + if (spinner_) + spinner_->stop(); + }); - // FIXME: Snackbar doesn't work if it's initialized in the constructor. - QTimer::singleShot(0, this, [this]() { - snackBar_ = new SnackBar(this); - connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); - }); + // FIXME: Snackbar doesn't work if it's initialized in the constructor. + QTimer::singleShot(0, this, [this]() { + snackBar_ = new SnackBar(this); + connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage); + }); - timer->start(50); + timer->start(50); } void MainWindow::showChatPage() { - auto userid = QString::fromStdString(http::client()->user_id().to_string()); - auto device_id = QString::fromStdString(http::client()->device_id()); - auto homeserver = QString::fromStdString(http::client()->server() + ":" + - std::to_string(http::client()->port())); - auto token = QString::fromStdString(http::client()->access_token()); - - userSettings_.data()->setUserId(userid); - userSettings_.data()->setAccessToken(token); - userSettings_.data()->setDeviceId(device_id); - userSettings_.data()->setHomeserver(homeserver); - - showOverlayProgressBar(); - - pageStack_->setCurrentWidget(chat_page_); - - pageStack_->removeWidget(welcome_page_); - pageStack_->removeWidget(login_page_); - pageStack_->removeWidget(register_page_); - - login_page_->reset(); - chat_page_->bootstrap(userid, homeserver, token); - connect(cache::client(), - &Cache::secretChanged, - userSettingsPage_, - &UserSettingsPage::updateSecretStatus); - emit reload(); + auto userid = QString::fromStdString(http::client()->user_id().to_string()); + auto device_id = QString::fromStdString(http::client()->device_id()); + auto homeserver = QString::fromStdString(http::client()->server() + ":" + + std::to_string(http::client()->port())); + auto token = QString::fromStdString(http::client()->access_token()); + + userSettings_.data()->setUserId(userid); + userSettings_.data()->setAccessToken(token); + userSettings_.data()->setDeviceId(device_id); + userSettings_.data()->setHomeserver(homeserver); + + showOverlayProgressBar(); + + pageStack_->setCurrentWidget(chat_page_); + + pageStack_->removeWidget(welcome_page_); + pageStack_->removeWidget(login_page_); + pageStack_->removeWidget(register_page_); + + login_page_->reset(); + chat_page_->bootstrap(userid, homeserver, token); + connect(cache::client(), + &Cache::secretChanged, + userSettingsPage_, + &UserSettingsPage::updateSecretStatus); + emit reload(); } void MainWindow::closeEvent(QCloseEvent *event) { - if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { - if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != - QMessageBox::Yes) { - event->ignore(); - return; - } + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { + if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") != + QMessageBox::Yes) { + event->ignore(); + return; } + } - if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && - userSettings_->tray()) { - event->ignore(); - hide(); - } + if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && userSettings_->tray()) { + event->ignore(); + hide(); + } } void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { - switch (reason) { - case QSystemTrayIcon::Trigger: - if (!isVisible()) { - show(); - } else { - hide(); - } - break; - default: - break; + switch (reason) { + case QSystemTrayIcon::Trigger: + if (!isVisible()) { + show(); + } else { + hide(); } + break; + default: + break; + } } bool MainWindow::hasActiveUser() { - auto settings = userSettings_->qsettings(); - QString prefix; - if (userSettings_->profile() != "") - prefix = "profile/" + userSettings_->profile() + "/"; - - return settings->contains(prefix + "auth/access_token") && - settings->contains(prefix + "auth/home_server") && - settings->contains(prefix + "auth/user_id"); + auto settings = userSettings_->qsettings(); + QString prefix; + if (userSettings_->profile() != "") + prefix = "profile/" + userSettings_->profile() + "/"; + + return settings->contains(prefix + "auth/access_token") && + settings->contains(prefix + "auth/home_server") && + settings->contains(prefix + "auth/user_id"); } void MainWindow::openLeaveRoomDialog(const QString &room_id) { - auto dialog = new dialogs::LeaveRoom(this); - connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { - chat_page_->leaveRoom(room_id); - }); + auto dialog = new dialogs::LeaveRoom(this); + connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() { + chat_page_->leaveRoom(room_id); + }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::showOverlayProgressBar() { - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(100); - spinner_->setFixedWidth(100); - spinner_->setObjectName("ChatPageLoadSpinner"); - spinner_->start(); + spinner_ = new LoadingIndicator(this); + spinner_->setFixedHeight(100); + spinner_->setFixedWidth(100); + spinner_->setObjectName("ChatPageLoadSpinner"); + spinner_->start(); - showSolidOverlayModal(spinner_); + showSolidOverlayModal(spinner_); } void MainWindow::openJoinRoomDialog(std::function callback) { - auto dialog = new dialogs::JoinRoom(this); - connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) { - if (!room.isEmpty()) - callback(room); - }); + auto dialog = new dialogs::JoinRoom(this); + connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) { + if (!room.isEmpty()) + callback(room); + }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::openCreateRoomDialog( std::function callback) { - auto dialog = new dialogs::CreateRoom(this); - connect(dialog, - &dialogs::CreateRoom::createRoom, - this, - [callback](const mtx::requests::CreateRoom &request) { callback(request); }); + auto dialog = new dialogs::CreateRoom(this); + connect(dialog, + &dialogs::CreateRoom::createRoom, + this, + [callback](const mtx::requests::CreateRoom &request) { callback(request); }); - showDialog(dialog); + showDialog(dialog); } void MainWindow::showTransparentOverlayModal(QWidget *content, QFlags flags) { - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30, 150)); - modal_->setDismissible(true); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); + modal_->setWidget(content); + modal_->setColor(QColor(30, 30, 30, 150)); + modal_->setDismissible(true); + modal_->setContentAlignment(flags); + modal_->raise(); + modal_->show(); } void MainWindow::showSolidOverlayModal(QWidget *content, QFlags flags) { - modal_->setWidget(content); - modal_->setColor(QColor(30, 30, 30)); - modal_->setDismissible(false); - modal_->setContentAlignment(flags); - modal_->raise(); - modal_->show(); + modal_->setWidget(content); + modal_->setColor(QColor(30, 30, 30)); + modal_->setDismissible(false); + modal_->setContentAlignment(flags); + modal_->raise(); + modal_->show(); } void MainWindow::openLogoutDialog() { - auto dialog = new dialogs::Logout(this); - connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { - if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { - if (QMessageBox::question( - this, "nheko", "A call is in progress. Log out?") != - QMessageBox::Yes) { - return; - } - WebRTCSession::instance().end(); - } - chat_page_->initiateLogout(); - }); - - showDialog(dialog); + auto dialog = new dialogs::Logout(this); + connect(dialog, &dialogs::Logout::loggingOut, this, [this]() { + if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) { + if (QMessageBox::question(this, "nheko", "A call is in progress. Log out?") != + QMessageBox::Yes) { + return; + } + WebRTCSession::instance().end(); + } + chat_page_->initiateLogout(); + }); + + showDialog(dialog); } bool MainWindow::hasActiveDialogs() const { - return !modal_ && modal_->isVisible(); + return !modal_ && modal_->isVisible(); } bool MainWindow::pageSupportsTray() const { - return !welcome_page_->isVisible() && !login_page_->isVisible() && - !register_page_->isVisible(); + return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible(); } void MainWindow::hideOverlay() { - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); } inline void MainWindow::showDialog(QWidget *dialog) { - utils::centerWidget(dialog, this); - dialog->raise(); - dialog->show(); + utils::centerWidget(dialog, this); + dialog->raise(); + dialog->show(); } void MainWindow::showWelcomePage() { - removeOverlayProgressBar(); - pageStack_->addWidget(welcome_page_); - pageStack_->setCurrentWidget(welcome_page_); + removeOverlayProgressBar(); + pageStack_->addWidget(welcome_page_); + pageStack_->setCurrentWidget(welcome_page_); } void MainWindow::showLoginPage() { - if (modal_) - modal_->hide(); + if (modal_) + modal_->hide(); - pageStack_->addWidget(login_page_); - pageStack_->setCurrentWidget(login_page_); + pageStack_->addWidget(login_page_); + pageStack_->setCurrentWidget(login_page_); } void MainWindow::showRegisterPage() { - pageStack_->addWidget(register_page_); - pageStack_->setCurrentWidget(register_page_); + pageStack_->addWidget(register_page_); + pageStack_->setCurrentWidget(register_page_); } void MainWindow::showUserSettingsPage() { - pageStack_->setCurrentWidget(userSettingsPage_); + pageStack_->setCurrentWidget(userSettingsPage_); } diff --git a/src/MainWindow.h b/src/MainWindow.h index d9ffb9b1..eff8fbe7 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -46,93 +46,92 @@ class ReCaptcha; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(int x READ x CONSTANT) - Q_PROPERTY(int y READ y CONSTANT) - Q_PROPERTY(int width READ width CONSTANT) - Q_PROPERTY(int height READ height CONSTANT) + Q_PROPERTY(int x READ x CONSTANT) + Q_PROPERTY(int y READ y CONSTANT) + Q_PROPERTY(int width READ width CONSTANT) + Q_PROPERTY(int height READ height CONSTANT) public: - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(QWidget *parent = nullptr); - static MainWindow *instance() { return instance_; } - void saveCurrentWindowSize(); + static MainWindow *instance() { return instance_; } + void saveCurrentWindowSize(); - void openLeaveRoomDialog(const QString &room_id); - void openInviteUsersDialog(std::function callback); - void openCreateRoomDialog( - std::function callback); - void openJoinRoomDialog(std::function callback); - void openLogoutDialog(); + void openLeaveRoomDialog(const QString &room_id); + void openInviteUsersDialog(std::function callback); + void openCreateRoomDialog( + std::function callback); + void openJoinRoomDialog(std::function callback); + void openLogoutDialog(); - void hideOverlay(); - void showSolidOverlayModal(QWidget *content, - QFlags flags = Qt::AlignCenter); - void showTransparentOverlayModal(QWidget *content, - QFlags flags = Qt::AlignTop | - Qt::AlignHCenter); + void hideOverlay(); + void showSolidOverlayModal(QWidget *content, QFlags flags = Qt::AlignCenter); + void showTransparentOverlayModal(QWidget *content, + QFlags flags = Qt::AlignTop | + Qt::AlignHCenter); protected: - void closeEvent(QCloseEvent *event) override; - bool event(QEvent *event) override; + void closeEvent(QCloseEvent *event) override; + bool event(QEvent *event) override; private slots: - //! Handle interaction with the tray icon. - void iconActivated(QSystemTrayIcon::ActivationReason reason); + //! Handle interaction with the tray icon. + void iconActivated(QSystemTrayIcon::ActivationReason reason); - //! Show the welcome page in the main window. - void showWelcomePage(); + //! Show the welcome page in the main window. + void showWelcomePage(); - //! Show the login page in the main window. - void showLoginPage(); + //! Show the login page in the main window. + void showLoginPage(); - //! Show the register page in the main window. - void showRegisterPage(); + //! Show the register page in the main window. + void showRegisterPage(); - //! Show user settings page. - void showUserSettingsPage(); + //! Show user settings page. + void showUserSettingsPage(); - //! Show the chat page and start communicating with the given access token. - void showChatPage(); + //! Show the chat page and start communicating with the given access token. + void showChatPage(); - void showOverlayProgressBar(); - void removeOverlayProgressBar(); + void showOverlayProgressBar(); + void removeOverlayProgressBar(); - virtual void setWindowTitle(int notificationCount); + virtual void setWindowTitle(int notificationCount); signals: - void focusChanged(const bool focused); - void reload(); + void focusChanged(const bool focused); + void reload(); private: - void showDialog(QWidget *dialog); - bool hasActiveUser(); - void restoreWindowSize(); - //! Check if there is an open dialog. - bool hasActiveDialogs() const; - //! Check if the current page supports the "minimize to tray" functionality. - bool pageSupportsTray() const; - - static MainWindow *instance_; - - //! The initial welcome screen. - WelcomePage *welcome_page_; - //! The login screen. - LoginPage *login_page_; - //! The register page. - RegisterPage *register_page_; - //! A stacked widget that handles the transitions between widgets. - QStackedWidget *pageStack_; - //! The main chat area. - ChatPage *chat_page_; - UserSettingsPage *userSettingsPage_; - QSharedPointer userSettings_; - //! Tray icon that shows the unread message count. - TrayIcon *trayIcon_; - //! Notifications display. - SnackBar *snackBar_ = nullptr; - //! Overlay modal used to project other widgets. - OverlayModal *modal_ = nullptr; - LoadingIndicator *spinner_ = nullptr; + void showDialog(QWidget *dialog); + bool hasActiveUser(); + void restoreWindowSize(); + //! Check if there is an open dialog. + bool hasActiveDialogs() const; + //! Check if the current page supports the "minimize to tray" functionality. + bool pageSupportsTray() const; + + static MainWindow *instance_; + + //! The initial welcome screen. + WelcomePage *welcome_page_; + //! The login screen. + LoginPage *login_page_; + //! The register page. + RegisterPage *register_page_; + //! A stacked widget that handles the transitions between widgets. + QStackedWidget *pageStack_; + //! The main chat area. + ChatPage *chat_page_; + UserSettingsPage *userSettingsPage_; + QSharedPointer userSettings_; + //! Tray icon that shows the unread message count. + TrayIcon *trayIcon_; + //! Notifications display. + SnackBar *snackBar_ = nullptr; + //! Overlay modal used to project other widgets. + OverlayModal *modal_ = nullptr; + LoadingIndicator *spinner_ = nullptr; }; diff --git a/src/MatrixClient.cpp b/src/MatrixClient.cpp index 196a9322..2ceb53a8 100644 --- a/src/MatrixClient.cpp +++ b/src/MatrixClient.cpp @@ -37,31 +37,31 @@ namespace http { mtx::http::Client * client() { - return client_.get(); + return client_.get(); } bool is_logged_in() { - return !client_->access_token().empty(); + return !client_->access_token().empty(); } void init() { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType>("std::map"); - qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>("std::map"); + qRegisterMetaType>(); } } // namespace http diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 0c0f0cdd..34730e9a 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -15,98 +15,96 @@ MemberList::MemberList(const QString &room_id, QObject *parent) : QAbstractListModel{parent} , room_id_{room_id} { - try { - info_ = cache::singleRoomInfo(room_id_.toStdString()); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve room info from cache: {}", - room_id_.toStdString()); - } + try { + info_ = cache::singleRoomInfo(room_id_.toStdString()); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", room_id_.toStdString()); + } - try { - auto members = cache::getMembers(room_id_.toStdString()); - addUsers(members); - numUsersLoaded_ = members.size(); - } catch (const lmdb::error &e) { - nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); - } + try { + auto members = cache::getMembers(room_id_.toStdString()); + addUsers(members); + numUsersLoaded_ = members.size(); + } catch (const lmdb::error &e) { + nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); + } } void MemberList::addUsers(const std::vector &members) { - beginInsertRows( - QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); + beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); - for (const auto &member : members) - m_memberList.push_back( - {member, - ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( - member.user_id)}); + for (const auto &member : members) + m_memberList.push_back( + {member, + ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( + member.user_id)}); - endInsertRows(); + endInsertRows(); } QHash MemberList::roleNames() const { - return { - {Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Trustlevel, "trustlevel"}, - }; + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Trustlevel, "trustlevel"}, + }; } QVariant MemberList::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) - return {}; + if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) + return {}; - switch (role) { - case Mxid: - return m_memberList[index.row()].first.user_id; - case DisplayName: - return m_memberList[index.row()].first.display_name; - case AvatarUrl: - return m_memberList[index.row()].second; - case Trustlevel: { - auto stat = - cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString()); + switch (role) { + case Mxid: + return m_memberList[index.row()].first.user_id; + case DisplayName: + return m_memberList[index.row()].first.display_name; + case AvatarUrl: + return m_memberList[index.row()].second; + case Trustlevel: { + auto stat = + cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString()); - if (!stat) - return crypto::Unverified; - if (stat->unverified_device_count) - return crypto::Unverified; - else - return stat->user_verified; - } - default: - return {}; - } + if (!stat) + return crypto::Unverified; + if (stat->unverified_device_count) + return crypto::Unverified; + else + return stat->user_verified; + } + default: + return {}; + } } bool MemberList::canFetchMore(const QModelIndex &) const { - const size_t numMembers = rowCount(); - if (numMembers > 1 && numMembers < info_.member_count) - return true; - else - return false; + const size_t numMembers = rowCount(); + if (numMembers > 1 && numMembers < info_.member_count) + return true; + else + return false; } void MemberList::fetchMore(const QModelIndex &) { - loadingMoreMembers_ = true; - emit loadingMoreMembersChanged(); + loadingMoreMembers_ = true; + emit loadingMoreMembersChanged(); - auto members = cache::getMembers(room_id_.toStdString(), rowCount()); - addUsers(members); - numUsersLoaded_ += members.size(); - emit numUsersLoadedChanged(); + auto members = cache::getMembers(room_id_.toStdString(), rowCount()); + addUsers(members); + numUsersLoaded_ += members.size(); + emit numUsersLoadedChanged(); - loadingMoreMembers_ = false; - emit loadingMoreMembersChanged(); + loadingMoreMembers_ = false; + emit loadingMoreMembersChanged(); } diff --git a/src/MemberList.h b/src/MemberList.h index cffcd83d..b16ac983 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -10,59 +10,59 @@ class MemberList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) - Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) - Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) - Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) + Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) + Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - Trustlevel, - }; - MemberList(const QString &room_id, QObject *parent = nullptr); + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Trustlevel, + }; + MemberList(const QString &room_id, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - Q_UNUSED(parent) - return static_cast(m_memberList.size()); - } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return static_cast(m_memberList.size()); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QString roomName() const { return QString::fromStdString(info_.name); } - int memberCount() const { return info_.member_count; } - QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } - QString roomId() const { return room_id_; } - int numUsersLoaded() const { return numUsersLoaded_; } - bool loadingMoreMembers() const { return loadingMoreMembers_; } + QString roomName() const { return QString::fromStdString(info_.name); } + int memberCount() const { return info_.member_count; } + QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } + QString roomId() const { return room_id_; } + int numUsersLoaded() const { return numUsersLoaded_; } + bool loadingMoreMembers() const { return loadingMoreMembers_; } signals: - void roomNameChanged(); - void memberCountChanged(); - void avatarUrlChanged(); - void roomIdChanged(); - void numUsersLoadedChanged(); - void loadingMoreMembersChanged(); + void roomNameChanged(); + void memberCountChanged(); + void avatarUrlChanged(); + void roomIdChanged(); + void numUsersLoadedChanged(); + void loadingMoreMembersChanged(); public slots: - void addUsers(const std::vector &users); + void addUsers(const std::vector &users); protected: - bool canFetchMore(const QModelIndex &) const override; - void fetchMore(const QModelIndex &) override; + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; private: - QVector> m_memberList; - QString room_id_; - RoomInfo info_; - int numUsersLoaded_{0}; - bool loadingMoreMembers_{false}; + QVector> m_memberList; + QString room_id_; + RoomInfo info_; + int numUsersLoaded_{0}; + bool loadingMoreMembers_{false}; }; diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 056374a9..5d0ee0be 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -24,70 +24,70 @@ QHash infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - auto id_ = id; - bool crop = true; - double radius = 0; - - auto queryStart = id.lastIndexOf('?'); - if (queryStart != -1) { - id_ = id.left(queryStart); - auto query = id.midRef(queryStart + 1); - auto queryBits = query.split('&'); - - for (auto b : queryBits) { - if (b == "scale") { - crop = false; - } else if (b.startsWith("radius=")) { - radius = b.mid(7).toDouble(); - } - } + auto id_ = id; + bool crop = true; + double radius = 0; + + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); + + for (auto b : queryBits) { + if (b == "scale") { + crop = false; + } else if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); + } } + } - MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); - pool.start(response); - return response; + MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); + pool.start(response); + return response; } void MxcImageProvider::addEncryptionInfo(mtx::crypto::EncryptedFile info) { - infos.insert(QString::fromStdString(info.url), info); + infos.insert(QString::fromStdString(info.url), info); } void MxcImageResponse::run() { - MxcImageProvider::download( - m_id, - m_requestedSize, - [this](QString, QSize, QImage image, QString) { - if (image.isNull()) { - m_error = "Failed to download image."; - } else { - m_image = image; - } - emit finished(); - }, - m_crop, - m_radius); + MxcImageProvider::download( + m_id, + m_requestedSize, + [this](QString, QSize, QImage image, QString) { + if (image.isNull()) { + m_error = "Failed to download image."; + } else { + m_image = image; + } + emit finished(); + }, + m_crop, + m_radius); } static QImage clipRadius(QImage img, double radius) { - QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); - out.fill(Qt::transparent); + QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); + out.fill(Qt::transparent); - QPainter painter(&out); - painter.setRenderHint(QPainter::Antialiasing, true); - painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); - QPainterPath ppath; - ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize); - painter.setClipPath(ppath); - painter.drawImage(img.rect(), img); + painter.setClipPath(ppath); + painter.drawImage(img.rect(), img); - return out; + return out; } void @@ -97,187 +97,165 @@ MxcImageProvider::download(const QString &id, bool crop, double radius) { - std::optional encryptionInfo; - auto temp = infos.find("mxc://" + id); - if (temp != infos.end()) - encryptionInfo = *temp; - - if (requestedSize.isValid() && !encryptionInfo) { - QString fileName = - QString("%1_%2x%3_%4_radius%5") - .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | - QByteArray::OmitTrailingEquals))) - .arg(requestedSize.width()) - .arg(requestedSize.height()) - .arg(crop ? "crop" : "scale") - .arg(radius); - QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + - "/media_cache", - fileName); - QDir().mkpath(fileInfo.absolutePath()); - - if (fileInfo.exists()) { - QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (!image.isNull()) { - image = image.scaled( - requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - if (!image.isNull()) { - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - return; - } - } + std::optional encryptionInfo; + auto temp = infos.find("mxc://" + id); + if (temp != infos.end()) + encryptionInfo = *temp; + + if (requestedSize.isValid() && !encryptionInfo) { + QString fileName = QString("%1_%2x%3_%4_radius%5") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(requestedSize.width()) + .arg(requestedSize.height()) + .arg(crop ? "crop" : "scale") + .arg(radius); + QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + "/media_cache", + fileName); + QDir().mkpath(fileInfo.absolutePath()); + + if (fileInfo.exists()) { + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (!image.isNull()) { + image = image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if (radius != 0) { + image = clipRadius(std::move(image), radius); } - mtx::http::ThumbOpts opts; - opts.mxc_url = "mxc://" + id.toStdString(); - opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; - opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; - opts.method = crop ? "crop" : "scale"; - http::client()->get_thumbnail( - opts, - [fileInfo, requestedSize, radius, then, id](const std::string &res, - mtx::http::RequestErr err) { - if (err || res.empty()) { - then(id, QSize(), {}, ""); - - return; - } - - auto data = QByteArray(res.data(), (int)res.size()); - QImage image = utils::readImage(data); - if (!image.isNull()) { - image = image.scaled( - requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - } - image.setText("mxc url", "mxc://" + id); - if (image.save(fileInfo.absoluteFilePath(), "png")) - nhlog::ui()->debug("Wrote: {}", - fileInfo.absoluteFilePath().toStdString()); - else - nhlog::ui()->debug("Failed to write: {}", - fileInfo.absoluteFilePath().toStdString()); - - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - }); - } else { - try { - QString fileName = - QString("%1_radius%2") - .arg(QString::fromUtf8(id.toUtf8().toBase64( - QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) - .arg(radius); - - QFileInfo fileInfo( - QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + - "/media_cache", - fileName); - QDir().mkpath(fileInfo.absolutePath()); - - if (fileInfo.exists()) { - if (encryptionInfo) { - QFile f(fileInfo.absoluteFilePath()); - f.open(QIODevice::ReadOnly); - - QByteArray fileData = f.readAll(); - auto tempData = - mtx::crypto::to_string(mtx::crypto::decrypt_file( - fileData.toStdString(), encryptionInfo.value())); - auto data = - QByteArray(tempData.data(), (int)tempData.size()); - QImage image = utils::readImage(data); - image.setText("mxc url", "mxc://" + id); - if (!image.isNull()) { - if (radius != 0) { - image = - clipRadius(std::move(image), radius); - } - - then(id, - requestedSize, - image, - fileInfo.absoluteFilePath()); - return; - } - } else { - QImage image = - utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (!image.isNull()) { - if (radius != 0) { - image = - clipRadius(std::move(image), radius); - } - - then(id, - requestedSize, - image, - fileInfo.absoluteFilePath()); - return; - } - } + if (!image.isNull()) { + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + } + } + + mtx::http::ThumbOpts opts; + opts.mxc_url = "mxc://" + id.toStdString(); + opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; + opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; + opts.method = crop ? "crop" : "scale"; + http::client()->get_thumbnail( + opts, + [fileInfo, requestedSize, radius, then, id](const std::string &res, + mtx::http::RequestErr err) { + if (err || res.empty()) { + then(id, QSize(), {}, ""); + + return; + } + + auto data = QByteArray(res.data(), (int)res.size()); + QImage image = utils::readImage(data); + if (!image.isNull()) { + image = + image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + } + image.setText("mxc url", "mxc://" + id); + if (image.save(fileInfo.absoluteFilePath(), "png")) + nhlog::ui()->debug("Wrote: {}", fileInfo.absoluteFilePath().toStdString()); + else + nhlog::ui()->debug("Failed to write: {}", + fileInfo.absoluteFilePath().toStdString()); + + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + }); + } else { + try { + QString fileName = QString("%1_radius%2") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(radius); + + QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + + "/media_cache", + fileName); + QDir().mkpath(fileInfo.absolutePath()); + + if (fileInfo.exists()) { + if (encryptionInfo) { + QFile f(fileInfo.absoluteFilePath()); + f.open(QIODevice::ReadOnly); + + QByteArray fileData = f.readAll(); + auto tempData = mtx::crypto::to_string( + mtx::crypto::decrypt_file(fileData.toStdString(), encryptionInfo.value())); + auto data = QByteArray(tempData.data(), (int)tempData.size()); + QImage image = utils::readImage(data); + image.setText("mxc url", "mxc://" + id); + if (!image.isNull()) { + if (radius != 0) { + image = clipRadius(std::move(image), radius); } - http::client()->download( - "mxc://" + id.toStdString(), - [fileInfo, requestedSize, then, id, radius, encryptionInfo]( - const std::string &res, - const std::string &, - const std::string &originalFilename, - mtx::http::RequestErr err) { - if (err) { - then(id, QSize(), {}, ""); - return; - } - - auto tempData = res; - QFile f(fileInfo.absoluteFilePath()); - if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { - then(id, QSize(), {}, ""); - return; - } - f.write(tempData.data(), tempData.size()); - f.close(); - - if (encryptionInfo) { - tempData = - mtx::crypto::to_string(mtx::crypto::decrypt_file( - tempData, encryptionInfo.value())); - auto data = - QByteArray(tempData.data(), (int)tempData.size()); - QImage image = utils::readImage(data); - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - image.setText("original filename", - QString::fromStdString(originalFilename)); - image.setText("mxc url", "mxc://" + id); - then( - id, requestedSize, image, fileInfo.absoluteFilePath()); - return; - } - - QImage image = - utils::readImageFromFile(fileInfo.absoluteFilePath()); - if (radius != 0) { - image = clipRadius(std::move(image), radius); - } - - image.setText("original filename", - QString::fromStdString(originalFilename)); - image.setText("mxc url", "mxc://" + id); - then(id, requestedSize, image, fileInfo.absoluteFilePath()); - }); - } catch (std::exception &e) { - nhlog::net()->error("Exception while downloading media: {}", e.what()); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + } else { + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (!image.isNull()) { + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } } + } + + http::client()->download( + "mxc://" + id.toStdString(), + [fileInfo, requestedSize, then, id, radius, encryptionInfo]( + const std::string &res, + const std::string &, + const std::string &originalFilename, + mtx::http::RequestErr err) { + if (err) { + then(id, QSize(), {}, ""); + return; + } + + auto tempData = res; + QFile f(fileInfo.absoluteFilePath()); + if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + then(id, QSize(), {}, ""); + return; + } + f.write(tempData.data(), tempData.size()); + f.close(); + + if (encryptionInfo) { + tempData = mtx::crypto::to_string( + mtx::crypto::decrypt_file(tempData, encryptionInfo.value())); + auto data = QByteArray(tempData.data(), (int)tempData.size()); + QImage image = utils::readImage(data); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + image.setText("original filename", QString::fromStdString(originalFilename)); + image.setText("mxc url", "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + return; + } + + QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + + image.setText("original filename", QString::fromStdString(originalFilename)); + image.setText("mxc url", "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); + }); + } catch (std::exception &e) { + nhlog::net()->error("Exception while downloading media: {}", e.what()); } + } } diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 6de83c0e..3cf5bbf4 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,46 +19,46 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) - : m_id(id) - , m_requestedSize(requestedSize) - , m_crop(crop) - , m_radius(radius) - { - setAutoDelete(false); - } + MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) + : m_id(id) + , m_requestedSize(requestedSize) + , m_crop(crop) + , m_radius(radius) + { + setAutoDelete(false); + } - QQuickTextureFactory *textureFactory() const override - { - return QQuickTextureFactory::textureFactoryForImage(m_image); - } - QString errorString() const override { return m_error; } + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + QString errorString() const override { return m_error; } - void run() override; + void run() override; - QString m_id, m_error; - QSize m_requestedSize; - QImage m_image; - bool m_crop; - double m_radius; + QString m_id, m_error; + QSize m_requestedSize; + QImage m_image; + bool m_crop; + double m_radius; }; class MxcImageProvider : public QObject , public QQuickAsyncImageProvider { - Q_OBJECT + Q_OBJECT public slots: - QQuickImageResponse *requestImageResponse(const QString &id, - const QSize &requestedSize) override; + QQuickImageResponse *requestImageResponse(const QString &id, + const QSize &requestedSize) override; - static void addEncryptionInfo(mtx::crypto::EncryptedFile info); - static void download(const QString &id, - const QSize &requestedSize, - std::function then, - bool crop = true, - double radius = 0); + static void addEncryptionInfo(mtx::crypto::EncryptedFile info); + static void download(const QString &id, + const QSize &requestedSize, + std::function then, + bool crop = true, + double radius = 0); private: - QThreadPool pool; + QThreadPool pool; }; diff --git a/src/Olm.cpp b/src/Olm.cpp index 72dc582f..60460b5c 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -39,433 +39,385 @@ backup_session_key(const MegolmSessionIndex &idx, void from_json(const nlohmann::json &obj, OlmMessage &msg) { - if (obj.at("type") != "m.room.encrypted") - throw std::invalid_argument("invalid type for olm message"); + if (obj.at("type") != "m.room.encrypted") + throw std::invalid_argument("invalid type for olm message"); - if (obj.at("content").at("algorithm") != OLM_ALGO) - throw std::invalid_argument("invalid algorithm for olm message"); + if (obj.at("content").at("algorithm") != OLM_ALGO) + throw std::invalid_argument("invalid algorithm for olm message"); - msg.sender = obj.at("sender"); - msg.sender_key = obj.at("content").at("sender_key"); - msg.ciphertext = obj.at("content") - .at("ciphertext") - .get>(); + msg.sender = obj.at("sender"); + msg.sender_key = obj.at("content").at("sender_key"); + msg.ciphertext = obj.at("content") + .at("ciphertext") + .get>(); } mtx::crypto::OlmClient * client() { - return client_.get(); + return client_.get(); } static void handle_secret_request(const mtx::events::DeviceEvent *e, const std::string &sender) { - using namespace mtx::events; + using namespace mtx::events; - if (e->content.action != mtx::events::msg::RequestAction::Request) - return; + if (e->content.action != mtx::events::msg::RequestAction::Request) + return; - auto local_user = http::client()->user_id(); + auto local_user = http::client()->user_id(); - if (sender != local_user.to_string()) - return; + if (sender != local_user.to_string()) + return; - auto verificationStatus = cache::verificationStatus(local_user.to_string()); + auto verificationStatus = cache::verificationStatus(local_user.to_string()); - if (!verificationStatus) - return; + if (!verificationStatus) + return; - auto deviceKeys = cache::userKeys(local_user.to_string()); - if (!deviceKeys) - return; + auto deviceKeys = cache::userKeys(local_user.to_string()); + if (!deviceKeys) + return; - if (std::find(verificationStatus->verified_devices.begin(), - verificationStatus->verified_devices.end(), - e->content.requesting_device_id) == - verificationStatus->verified_devices.end()) - return; + if (std::find(verificationStatus->verified_devices.begin(), + verificationStatus->verified_devices.end(), + e->content.requesting_device_id) == verificationStatus->verified_devices.end()) + return; - // this is a verified device - mtx::events::DeviceEvent secretSend; - secretSend.type = EventType::SecretSend; - secretSend.content.request_id = e->content.request_id; + // this is a verified device + mtx::events::DeviceEvent secretSend; + secretSend.type = EventType::SecretSend; + secretSend.content.request_id = e->content.request_id; - auto secret = cache::client()->secret(e->content.name); - if (!secret) - return; - secretSend.content.secret = secret.value(); + auto secret = cache::client()->secret(e->content.name); + if (!secret) + return; + secretSend.content.secret = secret.value(); - send_encrypted_to_device_messages( - {{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend); + send_encrypted_to_device_messages( + {{local_user.to_string(), {{e->content.requesting_device_id}}}}, secretSend); - nhlog::net()->info("Sent secret '{}' to ({},{})", - e->content.name, - local_user.to_string(), - e->content.requesting_device_id); + nhlog::net()->info("Sent secret '{}' to ({},{})", + e->content.name, + local_user.to_string(), + e->content.requesting_device_id); } void handle_to_device_messages(const std::vector &msgs) { - if (msgs.empty()) - return; - nhlog::crypto()->info("received {} to_device messages", msgs.size()); - nlohmann::json j_msg; - - for (const auto &msg : msgs) { - j_msg = std::visit([](auto &e) { return json(e); }, std::move(msg)); - if (j_msg.count("type") == 0) { - nhlog::crypto()->warn("received message with no type field: {}", - j_msg.dump(2)); - continue; - } + if (msgs.empty()) + return; + nhlog::crypto()->info("received {} to_device messages", msgs.size()); + nlohmann::json j_msg; + + for (const auto &msg : msgs) { + j_msg = std::visit([](auto &e) { return json(e); }, std::move(msg)); + if (j_msg.count("type") == 0) { + nhlog::crypto()->warn("received message with no type field: {}", j_msg.dump(2)); + continue; + } - std::string msg_type = j_msg.at("type"); - - if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) { - try { - olm::OlmMessage olm_msg = j_msg; - cache::client()->query_keys( - olm_msg.sender, - [olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) { - if (e) { - nhlog::crypto()->error( - "Failed to query user keys, dropping olm " - "message"); - return; - } - handle_olm_message(std::move(olm_msg), userKeys); - }); - } catch (const nlohmann::json::exception &e) { - nhlog::crypto()->warn( - "parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); - } catch (const std::invalid_argument &e) { - nhlog::crypto()->warn("validation error for olm message: {} {}", - e.what(), - j_msg.dump(2)); - } + std::string msg_type = j_msg.at("type"); - } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { - nhlog::crypto()->warn("handling key request event: {}", j_msg.dump(2)); - try { - mtx::events::DeviceEvent req = j_msg; - if (req.content.action == mtx::events::msg::RequestAction::Request) - handle_key_request_message(req); - else - nhlog::crypto()->warn( - "ignore key request (unhandled action): {}", + if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) { + try { + olm::OlmMessage olm_msg = j_msg; + cache::client()->query_keys( + olm_msg.sender, [olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) { + if (e) { + nhlog::crypto()->error("Failed to query user keys, dropping olm " + "message"); + return; + } + handle_olm_message(std::move(olm_msg), userKeys); + }); + } catch (const nlohmann::json::exception &e) { + nhlog::crypto()->warn( + "parsing error for olm message: {} {}", e.what(), j_msg.dump(2)); + } catch (const std::invalid_argument &e) { + nhlog::crypto()->warn( + "validation error for olm message: {} {}", e.what(), j_msg.dump(2)); + } + + } else if (msg_type == to_string(mtx::events::EventType::RoomKeyRequest)) { + nhlog::crypto()->warn("handling key request event: {}", j_msg.dump(2)); + try { + mtx::events::DeviceEvent req = j_msg; + if (req.content.action == mtx::events::msg::RequestAction::Request) + handle_key_request_message(req); + else + nhlog::crypto()->warn("ignore key request (unhandled action): {}", req.content.request_id); - } catch (const nlohmann::json::exception &e) { - nhlog::crypto()->warn( - "parsing error for key_request message: {} {}", - e.what(), - j_msg.dump(2)); - } - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationAccept(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationRequest(message.content, - message.sender); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationCancel(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationKey(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationMac(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationStart(message.content, - message.sender); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { - auto message = std::get< - mtx::events::DeviceEvent>(msg); - ChatPage::instance()->receivedDeviceVerificationReady(message.content); - } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { - auto message = - std::get>( - msg); - ChatPage::instance()->receivedDeviceVerificationDone(message.content); - } else if (auto e = - std::get_if>( - &msg)) { - handle_secret_request(e, e->sender); - } else { - nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); - } + } catch (const nlohmann::json::exception &e) { + nhlog::crypto()->warn( + "parsing error for key_request message: {} {}", e.what(), j_msg.dump(2)); + } + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationAccept)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationAccept(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationRequest)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationRequest(message.content, + message.sender); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationCancel)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationCancel(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationKey)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationKey(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationMac)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationMac(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationStart)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationStart(message.content, message.sender); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationReady)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationReady(message.content); + } else if (msg_type == to_string(mtx::events::EventType::KeyVerificationDone)) { + auto message = + std::get>(msg); + ChatPage::instance()->receivedDeviceVerificationDone(message.content); + } else if (auto e = + std::get_if>(&msg)) { + handle_secret_request(e, e->sender); + } else { + nhlog::crypto()->warn("unhandled event: {}", j_msg.dump(2)); } + } } void handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys) { - nhlog::crypto()->info("sender : {}", msg.sender); - nhlog::crypto()->info("sender_key: {}", msg.sender_key); + nhlog::crypto()->info("sender : {}", msg.sender); + nhlog::crypto()->info("sender_key: {}", msg.sender_key); - if (msg.sender_key == olm::client()->identity_keys().ed25519) { - nhlog::crypto()->warn("Ignoring olm message from ourselves!"); - return; + if (msg.sender_key == olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn("Ignoring olm message from ourselves!"); + return; + } + + const auto my_key = olm::client()->identity_keys().curve25519; + + bool failed_decryption = false; + + for (const auto &cipher : msg.ciphertext) { + // We skip messages not meant for the current device. + if (cipher.first != my_key) { + nhlog::crypto()->debug( + "Skipping message for {} since we are {}.", cipher.first, my_key); + continue; } - const auto my_key = olm::client()->identity_keys().curve25519; + const auto type = cipher.second.type; + nhlog::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE"); - bool failed_decryption = false; + auto payload = try_olm_decryption(msg.sender_key, cipher.second); - for (const auto &cipher : msg.ciphertext) { - // We skip messages not meant for the current device. - if (cipher.first != my_key) { - nhlog::crypto()->debug( - "Skipping message for {} since we are {}.", cipher.first, my_key); - continue; - } + if (payload.is_null()) { + // Check for PRE_KEY message + if (cipher.second.type == 0) { + payload = handle_pre_key_olm_message(msg.sender, msg.sender_key, cipher.second); + } else { + nhlog::crypto()->error("Undecryptable olm message!"); + failed_decryption = true; + continue; + } + } - const auto type = cipher.second.type; - nhlog::crypto()->info("type: {}", type == 0 ? "OLM_PRE_KEY" : "OLM_MESSAGE"); + if (!payload.is_null()) { + mtx::events::collections::DeviceEvents device_event; + + // Other properties are included in order to prevent an attacker from + // publishing someone else's curve25519 keys as their own and subsequently + // claiming to have sent messages which they didn't. sender must correspond + // to the user who sent the event, recipient to the local user, and + // recipient_keys to the local ed25519 key. + std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"]; + if (receiver_ed25519.empty() || + receiver_ed25519 != olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn("Decrypted event doesn't include our ed25519: {}", + payload.dump()); + return; + } + std::string receiver = payload["recipient"]; + if (receiver.empty() || receiver != http::client()->user_id().to_string()) { + nhlog::crypto()->warn("Decrypted event doesn't include our user_id: {}", + payload.dump()); + return; + } + + // Clients must confirm that the sender_key and the ed25519 field value + // under the keys property match the keys returned by /keys/query for the + // given user, and must also verify the signature of the payload. Without + // this check, a client cannot be sure that the sender device owns the + // private part of the ed25519 key it claims to have in the Olm payload. + // This is crucial when the ed25519 key corresponds to a verified device. + std::string sender_ed25519 = payload["keys"]["ed25519"]; + if (sender_ed25519.empty()) { + nhlog::crypto()->warn("Decrypted event doesn't include sender ed25519: {}", + payload.dump()); + return; + } - auto payload = try_olm_decryption(msg.sender_key, cipher.second); + bool from_their_device = false; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + auto c_key = key.keys.find("curve25519:" + device_id); + auto e_key = key.keys.find("ed25519:" + device_id); - if (payload.is_null()) { - // Check for PRE_KEY message - if (cipher.second.type == 0) { - payload = handle_pre_key_olm_message( - msg.sender, msg.sender_key, cipher.second); - } else { - nhlog::crypto()->error("Undecryptable olm message!"); - failed_decryption = true; - continue; - } + if (c_key == key.keys.end() || e_key == key.keys.end()) { + nhlog::crypto()->warn("Skipping device {} as we have no keys for it.", + device_id); + } else if (c_key->second == msg.sender_key && e_key->second == sender_ed25519) { + from_their_device = true; + break; } + } + if (!from_their_device) { + nhlog::crypto()->warn("Decrypted event isn't sent from a device " + "listed by that user! {}", + payload.dump()); + return; + } + + { + std::string msg_type = payload["type"]; + json event_array = json::array(); + event_array.push_back(payload); + + std::vector temp_events; + mtx::responses::utils::parse_device_events(event_array, temp_events); + if (temp_events.empty()) { + nhlog::crypto()->warn("Decrypted unknown event: {}", payload.dump()); + return; + } + device_event = temp_events.at(0); + } + + using namespace mtx::events; + if (auto e1 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationAccept(e1->content); + } else if (auto e2 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationRequest(e2->content, e2->sender); + } else if (auto e3 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationCancel(e3->content); + } else if (auto e4 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationKey(e4->content); + } else if (auto e5 = std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationMac(e5->content); + } else if (auto e6 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationStart(e6->content, e6->sender); + } else if (auto e7 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationReady(e7->content); + } else if (auto e8 = + std::get_if>(&device_event)) { + ChatPage::instance()->receivedDeviceVerificationDone(e8->content); + } else if (auto roomKey = std::get_if>(&device_event)) { + create_inbound_megolm_session(*roomKey, msg.sender_key, sender_ed25519); + } else if (auto forwardedRoomKey = + std::get_if>(&device_event)) { + forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back(msg.sender_key); + import_inbound_megolm_session(*forwardedRoomKey); + } else if (auto e = std::get_if>(&device_event)) { + auto local_user = http::client()->user_id(); + + if (msg.sender != local_user.to_string()) + return; + + auto secret_name = request_id_to_secret_name.find(e->content.request_id); + + if (secret_name != request_id_to_secret_name.end()) { + nhlog::crypto()->info("Received secret: {}", secret_name->second); + + mtx::events::msg::SecretRequest secretRequest{}; + secretRequest.action = mtx::events::msg::RequestAction::Cancellation; + secretRequest.requesting_device_id = http::client()->device_id(); + secretRequest.request_id = e->content.request_id; + + auto verificationStatus = cache::verificationStatus(local_user.to_string()); + + if (!verificationStatus) + return; - if (!payload.is_null()) { - mtx::events::collections::DeviceEvents device_event; - - // Other properties are included in order to prevent an attacker from - // publishing someone else's curve25519 keys as their own and subsequently - // claiming to have sent messages which they didn't. sender must correspond - // to the user who sent the event, recipient to the local user, and - // recipient_keys to the local ed25519 key. - std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"]; - if (receiver_ed25519.empty() || - receiver_ed25519 != olm::client()->identity_keys().ed25519) { - nhlog::crypto()->warn( - "Decrypted event doesn't include our ed25519: {}", - payload.dump()); - return; - } - std::string receiver = payload["recipient"]; - if (receiver.empty() || receiver != http::client()->user_id().to_string()) { - nhlog::crypto()->warn( - "Decrypted event doesn't include our user_id: {}", - payload.dump()); - return; - } - - // Clients must confirm that the sender_key and the ed25519 field value - // under the keys property match the keys returned by /keys/query for the - // given user, and must also verify the signature of the payload. Without - // this check, a client cannot be sure that the sender device owns the - // private part of the ed25519 key it claims to have in the Olm payload. - // This is crucial when the ed25519 key corresponds to a verified device. - std::string sender_ed25519 = payload["keys"]["ed25519"]; - if (sender_ed25519.empty()) { - nhlog::crypto()->warn( - "Decrypted event doesn't include sender ed25519: {}", - payload.dump()); - return; + auto deviceKeys = cache::userKeys(local_user.to_string()); + std::string sender_device_id; + if (deviceKeys) { + for (auto &[dev, key] : deviceKeys->device_keys) { + if (key.keys["curve25519:" + dev] == msg.sender_key) { + sender_device_id = dev; + break; + } } + } - bool from_their_device = false; - for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - auto c_key = key.keys.find("curve25519:" + device_id); - auto e_key = key.keys.find("ed25519:" + device_id); + std::map> + body; - if (c_key == key.keys.end() || e_key == key.keys.end()) { - nhlog::crypto()->warn( - "Skipping device {} as we have no keys for it.", - device_id); - } else if (c_key->second == msg.sender_key && - e_key->second == sender_ed25519) { - from_their_device = true; - break; - } - } - if (!from_their_device) { - nhlog::crypto()->warn("Decrypted event isn't sent from a device " - "listed by that user! {}", - payload.dump()); - return; - } + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id && dev != sender_device_id) + body[local_user][dev] = secretRequest; + } - { - std::string msg_type = payload["type"]; - json event_array = json::array(); - event_array.push_back(payload); - - std::vector temp_events; - mtx::responses::utils::parse_device_events(event_array, - temp_events); - if (temp_events.empty()) { - nhlog::crypto()->warn("Decrypted unknown event: {}", - payload.dump()); - return; - } - device_event = temp_events.at(0); - } + http::client()->send_to_device( + http::client()->generate_txn_id(), + body, + [name = secret_name->second](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to send request cancellation " + "for secrect " + "'{}'", + name); + } + }); - using namespace mtx::events; - if (auto e1 = - std::get_if>(&device_event)) { - ChatPage::instance()->receivedDeviceVerificationAccept(e1->content); - } else if (auto e2 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationRequest(e2->content, - e2->sender); - } else if (auto e3 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationCancel(e3->content); - } else if (auto e4 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationKey(e4->content); - } else if (auto e5 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationMac(e5->content); - } else if (auto e6 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationStart(e6->content, - e6->sender); - } else if (auto e7 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationReady(e7->content); - } else if (auto e8 = std::get_if>( - &device_event)) { - ChatPage::instance()->receivedDeviceVerificationDone(e8->content); - } else if (auto roomKey = - std::get_if>(&device_event)) { - create_inbound_megolm_session( - *roomKey, msg.sender_key, sender_ed25519); - } else if (auto forwardedRoomKey = - std::get_if>( - &device_event)) { - forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back( - msg.sender_key); - import_inbound_megolm_session(*forwardedRoomKey); - } else if (auto e = - std::get_if>(&device_event)) { - auto local_user = http::client()->user_id(); - - if (msg.sender != local_user.to_string()) - return; - - auto secret_name = - request_id_to_secret_name.find(e->content.request_id); - - if (secret_name != request_id_to_secret_name.end()) { - nhlog::crypto()->info("Received secret: {}", - secret_name->second); - - mtx::events::msg::SecretRequest secretRequest{}; - secretRequest.action = - mtx::events::msg::RequestAction::Cancellation; - secretRequest.requesting_device_id = - http::client()->device_id(); - secretRequest.request_id = e->content.request_id; - - auto verificationStatus = - cache::verificationStatus(local_user.to_string()); - - if (!verificationStatus) - return; - - auto deviceKeys = cache::userKeys(local_user.to_string()); - std::string sender_device_id; - if (deviceKeys) { - for (auto &[dev, key] : deviceKeys->device_keys) { - if (key.keys["curve25519:" + dev] == - msg.sender_key) { - sender_device_id = dev; - break; - } - } - } - - std::map< - mtx::identifiers::User, - std::map> - body; - - for (const auto &dev : - verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id && - dev != sender_device_id) - body[local_user][dev] = secretRequest; - } - - http::client() - ->send_to_device( - http::client()->generate_txn_id(), - body, - [name = - secret_name->second](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "Failed to send request cancellation " - "for secrect " - "'{}'", - name); - } - }); - - nhlog::crypto()->info("Storing secret {}", - secret_name->second); - cache::client()->storeSecret(secret_name->second, - e->content.secret); - - request_id_to_secret_name.erase(secret_name); - } - - } else if (auto sec_req = - std::get_if>(&device_event)) { - handle_secret_request(sec_req, msg.sender); - } + nhlog::crypto()->info("Storing secret {}", secret_name->second); + cache::client()->storeSecret(secret_name->second, e->content.secret); - return; - } else { - failed_decryption = true; + request_id_to_secret_name.erase(secret_name); } - } - if (failed_decryption) { - try { - std::map> targets; - for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - if (key.keys.at("curve25519:" + device_id) == msg.sender_key) - targets[msg.sender].push_back(device_id); - } + } else if (auto sec_req = std::get_if>(&device_event)) { + handle_secret_request(sec_req, msg.sender); + } - send_encrypted_to_device_messages( - targets, mtx::events::DeviceEvent{}, true); - nhlog::crypto()->info("Recovering from broken olm channel with {}:{}", - msg.sender, - msg.sender_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", - e.what()); - } + return; + } else { + failed_decryption = true; } + } + + if (failed_decryption) { + try { + std::map> targets; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + if (key.keys.at("curve25519:" + device_id) == msg.sender_key) + targets[msg.sender].push_back(device_id); + } + + send_encrypted_to_device_messages( + targets, mtx::events::DeviceEvent{}, true); + nhlog::crypto()->info( + "Recovering from broken olm channel with {}:{}", msg.sender, msg.sender_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", e.what()); + } + } } nlohmann::json @@ -473,320 +425,291 @@ handle_pre_key_olm_message(const std::string &sender, const std::string &sender_key, const mtx::events::msg::OlmCipherContent &content) { - nhlog::crypto()->info("opening olm session with {}", sender); + nhlog::crypto()->info("opening olm session with {}", sender); - mtx::crypto::OlmSessionPtr inbound_session = nullptr; - try { - inbound_session = - olm::client()->create_inbound_session_from(sender_key, content.body); + mtx::crypto::OlmSessionPtr inbound_session = nullptr; + try { + inbound_session = olm::client()->create_inbound_session_from(sender_key, content.body); - // We also remove the one time key used to establish that - // session so we'll have to update our copy of the account object. - cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to create inbound session with {}: {}", sender, e.what()); - return {}; - } + // We also remove the one time key used to establish that + // session so we'll have to update our copy of the account object. + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create inbound session with {}: {}", sender, e.what()); + return {}; + } - if (!mtx::crypto::matches_inbound_session_from( - inbound_session.get(), sender_key, content.body)) { - nhlog::crypto()->warn("inbound olm session doesn't match sender's key ({})", - sender); - return {}; - } + if (!mtx::crypto::matches_inbound_session_from( + inbound_session.get(), sender_key, content.body)) { + nhlog::crypto()->warn("inbound olm session doesn't match sender's key ({})", sender); + return {}; + } - mtx::crypto::BinaryBuf output; - try { - output = - olm::client()->decrypt_message(inbound_session.get(), content.type, content.body); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to decrypt olm message {}: {}", content.body, e.what()); - return {}; - } + mtx::crypto::BinaryBuf output; + try { + output = olm::client()->decrypt_message(inbound_session.get(), content.type, content.body); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to decrypt olm message {}: {}", content.body, e.what()); + return {}; + } - auto plaintext = json::parse(std::string((char *)output.data(), output.size())); - nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); + auto plaintext = json::parse(std::string((char *)output.data(), output.size())); + nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2)); - try { - nhlog::crypto()->debug("New olm session: {}", - mtx::crypto::session_id(inbound_session.get())); - cache::saveOlmSession( - sender_key, std::move(inbound_session), QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->warn( - "failed to save inbound olm session from {}: {}", sender, e.what()); - } + try { + nhlog::crypto()->debug("New olm session: {}", + mtx::crypto::session_id(inbound_session.get())); + cache::saveOlmSession( + sender_key, std::move(inbound_session), QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("failed to save inbound olm session from {}: {}", sender, e.what()); + } - return plaintext; + return plaintext; } mtx::events::msg::Encrypted encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body) { - using namespace mtx::events; - using namespace mtx::identifiers; - - auto own_user_id = http::client()->user_id().to_string(); - - auto members = cache::client()->getMembersWithKeys( - room_id, UserSettings::instance()->onlyShareKeysWithVerifiedUsers()); - - std::map> sendSessionTo; - mtx::crypto::OutboundGroupSessionPtr session = nullptr; - GroupSessionData group_session_data; - - if (cache::outboundMegolmSessionExists(room_id)) { - auto res = cache::getOutboundMegolmSession(room_id); - auto encryptionSettings = cache::client()->roomEncryptionSettings(room_id); - mtx::events::state::Encryption defaultSettings; - - // rotate if we crossed the limits for this key - if (res.data.message_index < - encryptionSettings.value_or(defaultSettings).rotation_period_msgs && - (QDateTime::currentMSecsSinceEpoch() - res.data.timestamp) < - encryptionSettings.value_or(defaultSettings).rotation_period_ms) { - auto member_it = members.begin(); - auto session_member_it = res.data.currently.keys.begin(); - auto session_member_it_end = res.data.currently.keys.end(); - - while (member_it != members.end() || - session_member_it != session_member_it_end) { - if (member_it == members.end()) { - // a member left, purge session! - nhlog::crypto()->debug( - "Rotating megolm session because of left member"); - break; - } - - if (session_member_it == session_member_it_end) { - // share with all remaining members - while (member_it != members.end()) { - sendSessionTo[member_it->first] = {}; - - if (member_it->second) - for (const auto &dev : - member_it->second->device_keys) - if (member_it->first != - own_user_id || - dev.first != device_id) - sendSessionTo[member_it - ->first] - .push_back(dev.first); - - ++member_it; - } - - session = std::move(res.session); - break; - } - - if (member_it->first > session_member_it->first) { - // a member left, purge session - nhlog::crypto()->debug( - "Rotating megolm session because of left member"); - break; - } else if (member_it->first < session_member_it->first) { - // new member, send them the session at this index - sendSessionTo[member_it->first] = {}; - - if (member_it->second) { - for (const auto &dev : - member_it->second->device_keys) - if (member_it->first != own_user_id || - dev.first != device_id) - sendSessionTo[member_it->first] - .push_back(dev.first); - } - - ++member_it; - } else { - // compare devices - bool device_removed = false; - for (const auto &dev : - session_member_it->second.deviceids) { - if (!member_it->second || - !member_it->second->device_keys.count( - dev.first)) { - device_removed = true; - break; - } - } - - if (device_removed) { - // device removed, rotate session! - nhlog::crypto()->debug( - "Rotating megolm session because of removed " - "device of {}", - member_it->first); - break; - } - - // check for new devices to share with - if (member_it->second) - for (const auto &dev : - member_it->second->device_keys) - if (!session_member_it->second.deviceids - .count(dev.first) && - (member_it->first != own_user_id || - dev.first != device_id)) - sendSessionTo[member_it->first] - .push_back(dev.first); - - ++member_it; - ++session_member_it; - if (member_it == members.end() && - session_member_it == session_member_it_end) { - // all devices match or are newly added - session = std::move(res.session); - } - } - } + using namespace mtx::events; + using namespace mtx::identifiers; + + auto own_user_id = http::client()->user_id().to_string(); + + auto members = cache::client()->getMembersWithKeys( + room_id, UserSettings::instance()->onlyShareKeysWithVerifiedUsers()); + + std::map> sendSessionTo; + mtx::crypto::OutboundGroupSessionPtr session = nullptr; + GroupSessionData group_session_data; + + if (cache::outboundMegolmSessionExists(room_id)) { + auto res = cache::getOutboundMegolmSession(room_id); + auto encryptionSettings = cache::client()->roomEncryptionSettings(room_id); + mtx::events::state::Encryption defaultSettings; + + // rotate if we crossed the limits for this key + if (res.data.message_index < + encryptionSettings.value_or(defaultSettings).rotation_period_msgs && + (QDateTime::currentMSecsSinceEpoch() - res.data.timestamp) < + encryptionSettings.value_or(defaultSettings).rotation_period_ms) { + auto member_it = members.begin(); + auto session_member_it = res.data.currently.keys.begin(); + auto session_member_it_end = res.data.currently.keys.end(); + + while (member_it != members.end() || session_member_it != session_member_it_end) { + if (member_it == members.end()) { + // a member left, purge session! + nhlog::crypto()->debug("Rotating megolm session because of left member"); + break; } - group_session_data = std::move(res.data); - } + if (session_member_it == session_member_it_end) { + // share with all remaining members + while (member_it != members.end()) { + sendSessionTo[member_it->first] = {}; - if (!session) { - nhlog::ui()->debug("creating new outbound megolm session"); - - // Create a new outbound megolm session. - session = olm::client()->init_outbound_group_session(); - const auto session_id = mtx::crypto::session_id(session.get()); - const auto session_key = mtx::crypto::session_key(session.get()); - - // Saving the new megolm session. - GroupSessionData session_data{}; - session_data.message_index = 0; - session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); - session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519; - - sendSessionTo.clear(); - - for (const auto &[user, devices] : members) { - sendSessionTo[user] = {}; - session_data.currently.keys[user] = {}; - if (devices) { - for (const auto &[device_id_, key] : devices->device_keys) { - (void)key; - if (device_id != device_id_ || user != own_user_id) { - sendSessionTo[user].push_back(device_id_); - session_data.currently.keys[user] - .deviceids[device_id_] = 0; - } - } + if (member_it->second) + for (const auto &dev : member_it->second->device_keys) + if (member_it->first != own_user_id || dev.first != device_id) + sendSessionTo[member_it->first].push_back(dev.first); + + ++member_it; + } + + session = std::move(res.session); + break; + } + + if (member_it->first > session_member_it->first) { + // a member left, purge session + nhlog::crypto()->debug("Rotating megolm session because of left member"); + break; + } else if (member_it->first < session_member_it->first) { + // new member, send them the session at this index + sendSessionTo[member_it->first] = {}; + + if (member_it->second) { + for (const auto &dev : member_it->second->device_keys) + if (member_it->first != own_user_id || dev.first != device_id) + sendSessionTo[member_it->first].push_back(dev.first); + } + + ++member_it; + } else { + // compare devices + bool device_removed = false; + for (const auto &dev : session_member_it->second.deviceids) { + if (!member_it->second || + !member_it->second->device_keys.count(dev.first)) { + device_removed = true; + break; } + } + + if (device_removed) { + // device removed, rotate session! + nhlog::crypto()->debug("Rotating megolm session because of removed " + "device of {}", + member_it->first); + break; + } + + // check for new devices to share with + if (member_it->second) + for (const auto &dev : member_it->second->device_keys) + if (!session_member_it->second.deviceids.count(dev.first) && + (member_it->first != own_user_id || dev.first != device_id)) + sendSessionTo[member_it->first].push_back(dev.first); + + ++member_it; + ++session_member_it; + if (member_it == members.end() && session_member_it == session_member_it_end) { + // all devices match or are newly added + session = std::move(res.session); + } } + } + } - { - MegolmSessionIndex index; - index.room_id = room_id; - index.session_id = session_id; - index.sender_key = olm::client()->identity_keys().curve25519; - auto megolm_session = - olm::client()->init_inbound_group_session(session_key); - backup_session_key(index, session_data, megolm_session); - cache::saveInboundMegolmSession( - index, std::move(megolm_session), session_data); + group_session_data = std::move(res.data); + } + + if (!session) { + nhlog::ui()->debug("creating new outbound megolm session"); + + // Create a new outbound megolm session. + session = olm::client()->init_outbound_group_session(); + const auto session_id = mtx::crypto::session_id(session.get()); + const auto session_key = mtx::crypto::session_key(session.get()); + + // Saving the new megolm session. + GroupSessionData session_data{}; + session_data.message_index = 0; + session_data.timestamp = QDateTime::currentMSecsSinceEpoch(); + session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519; + + sendSessionTo.clear(); + + for (const auto &[user, devices] : members) { + sendSessionTo[user] = {}; + session_data.currently.keys[user] = {}; + if (devices) { + for (const auto &[device_id_, key] : devices->device_keys) { + (void)key; + if (device_id != device_id_ || user != own_user_id) { + sendSessionTo[user].push_back(device_id_); + session_data.currently.keys[user].deviceids[device_id_] = 0; + } } + } + } - cache::saveOutboundMegolmSession(room_id, session_data, session); - group_session_data = std::move(session_data); + { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = session_id; + index.sender_key = olm::client()->identity_keys().curve25519; + auto megolm_session = olm::client()->init_inbound_group_session(session_key); + backup_session_key(index, session_data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), session_data); } - mtx::events::DeviceEvent megolm_payload{}; - megolm_payload.content.algorithm = MEGOLM_ALGO; - megolm_payload.content.room_id = room_id; - megolm_payload.content.session_id = mtx::crypto::session_id(session.get()); - megolm_payload.content.session_key = mtx::crypto::session_key(session.get()); - megolm_payload.type = mtx::events::EventType::RoomKey; - - if (!sendSessionTo.empty()) - olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload); - - // relations shouldn't be encrypted... - mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); - - auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); - - // Prepare the m.room.encrypted event. - msg::Encrypted data; - data.ciphertext = std::string((char *)payload.data(), payload.size()); - data.sender_key = olm::client()->identity_keys().curve25519; - data.session_id = mtx::crypto::session_id(session.get()); - data.device_id = device_id; - data.algorithm = MEGOLM_ALGO; - data.relations = relations; - - group_session_data.message_index = olm_outbound_group_session_message_index(session.get()); - nhlog::crypto()->debug("next message_index {}", group_session_data.message_index); - - // update current set of members for the session with the new members and that message_index - for (const auto &[user, devices] : sendSessionTo) { - if (!group_session_data.currently.keys.count(user)) - group_session_data.currently.keys[user] = {}; - - for (const auto &device_id_ : devices) { - if (!group_session_data.currently.keys[user].deviceids.count(device_id_)) - group_session_data.currently.keys[user].deviceids[device_id_] = - group_session_data.message_index; - } + cache::saveOutboundMegolmSession(room_id, session_data, session); + group_session_data = std::move(session_data); + } + + mtx::events::DeviceEvent megolm_payload{}; + megolm_payload.content.algorithm = MEGOLM_ALGO; + megolm_payload.content.room_id = room_id; + megolm_payload.content.session_id = mtx::crypto::session_id(session.get()); + megolm_payload.content.session_key = mtx::crypto::session_key(session.get()); + megolm_payload.type = mtx::events::EventType::RoomKey; + + if (!sendSessionTo.empty()) + olm::send_encrypted_to_device_messages(sendSessionTo, megolm_payload); + + // relations shouldn't be encrypted... + mtx::common::Relations relations = mtx::common::parse_relations(body["content"]); + + auto payload = olm::client()->encrypt_group_message(session.get(), body.dump()); + + // Prepare the m.room.encrypted event. + msg::Encrypted data; + data.ciphertext = std::string((char *)payload.data(), payload.size()); + data.sender_key = olm::client()->identity_keys().curve25519; + data.session_id = mtx::crypto::session_id(session.get()); + data.device_id = device_id; + data.algorithm = MEGOLM_ALGO; + data.relations = relations; + + group_session_data.message_index = olm_outbound_group_session_message_index(session.get()); + nhlog::crypto()->debug("next message_index {}", group_session_data.message_index); + + // update current set of members for the session with the new members and that message_index + for (const auto &[user, devices] : sendSessionTo) { + if (!group_session_data.currently.keys.count(user)) + group_session_data.currently.keys[user] = {}; + + for (const auto &device_id_ : devices) { + if (!group_session_data.currently.keys[user].deviceids.count(device_id_)) + group_session_data.currently.keys[user].deviceids[device_id_] = + group_session_data.message_index; } + } - // We need to re-pickle the session after we send a message to save the new message_index. - cache::updateOutboundMegolmSession(room_id, group_session_data, session); + // We need to re-pickle the session after we send a message to save the new message_index. + cache::updateOutboundMegolmSession(room_id, group_session_data, session); - return data; + return data; } nlohmann::json try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCipherContent &msg) { - auto session_ids = cache::getOlmSessions(sender_key); + auto session_ids = cache::getOlmSessions(sender_key); - nhlog::crypto()->info("attempt to decrypt message with {} known session_ids", - session_ids.size()); + nhlog::crypto()->info("attempt to decrypt message with {} known session_ids", + session_ids.size()); - for (const auto &id : session_ids) { - auto session = cache::getOlmSession(sender_key, id); + for (const auto &id : session_ids) { + auto session = cache::getOlmSession(sender_key, id); - if (!session) { - nhlog::crypto()->warn("Unknown olm session: {}:{}", sender_key, id); - continue; - } + if (!session) { + nhlog::crypto()->warn("Unknown olm session: {}:{}", sender_key, id); + continue; + } - mtx::crypto::BinaryBuf text; + mtx::crypto::BinaryBuf text; - try { - text = olm::client()->decrypt_message(session->get(), msg.type, msg.body); - nhlog::crypto()->debug("Updated olm session: {}", - mtx::crypto::session_id(session->get())); - cache::saveOlmSession( - id, std::move(session.value()), QDateTime::currentMSecsSinceEpoch()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}", - msg.type, - sender_key, - id, - e.what()); - continue; - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save session: {}", e.what()); - return {}; - } + try { + text = olm::client()->decrypt_message(session->get(), msg.type, msg.body); + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session->get())); + cache::saveOlmSession( + id, std::move(session.value()), QDateTime::currentMSecsSinceEpoch()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}", + msg.type, + sender_key, + id, + e.what()); + continue; + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save session: {}", e.what()); + return {}; + } - try { - return json::parse(std::string_view((char *)text.data(), text.size())); - } catch (const json::exception &e) { - nhlog::crypto()->critical( - "failed to parse the decrypted session msg: {} {}", - e.what(), - std::string_view((char *)text.data(), text.size())); - } + try { + return json::parse(std::string_view((char *)text.data(), text.size())); + } catch (const json::exception &e) { + nhlog::crypto()->critical("failed to parse the decrypted session msg: {} {}", + e.what(), + std::string_view((char *)text.data(), text.size())); } + } - return {}; + return {}; } void @@ -794,75 +717,73 @@ create_inbound_megolm_session(const mtx::events::DeviceEventinit_inbound_group_session(roomKey.content.session_key); - backup_session_key(index, data, megolm_session); - cache::saveInboundMegolmSession(index, std::move(megolm_session), data); - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); - return; - } - - nhlog::crypto()->info( - "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); - - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + MegolmSessionIndex index; + index.room_id = roomKey.content.room_id; + index.session_id = roomKey.content.session_id; + index.sender_key = sender_key; + + try { + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = {sender_key}; + data.sender_claimed_ed25519_key = sender_ed25519; + + auto megolm_session = + olm::client()->init_inbound_group_session(roomKey.content.session_key); + backup_session_key(index, data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to create inbound megolm session: {}", e.what()); + return; + } + + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); + + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } void import_inbound_megolm_session( const mtx::events::DeviceEvent &roomKey) { - MegolmSessionIndex index; - index.room_id = roomKey.content.room_id; - index.session_id = roomKey.content.session_id; - index.sender_key = roomKey.content.sender_key; - - try { - auto megolm_session = - olm::client()->import_inbound_group_session(roomKey.content.session_key); - - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = - roomKey.content.forwarding_curve25519_key_chain; - data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; - // may have come from online key backup, so we can't trust it... - data.trusted = false; - // if we got it forwarded from the sender, assume it is trusted. They may still have - // used key backup, but it is unlikely. - if (roomKey.content.forwarding_curve25519_key_chain.size() == 1 && - roomKey.content.forwarding_curve25519_key_chain.back() == - roomKey.content.sender_key) { - data.trusted = true; - } - - backup_session_key(index, data, megolm_session); - cache::saveInboundMegolmSession(index, std::move(megolm_session), data); - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); - return; + MegolmSessionIndex index; + index.room_id = roomKey.content.room_id; + index.session_id = roomKey.content.session_id; + index.sender_key = roomKey.content.sender_key; + + try { + auto megolm_session = + olm::client()->import_inbound_group_session(roomKey.content.session_key); + + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = roomKey.content.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key; + // may have come from online key backup, so we can't trust it... + data.trusted = false; + // if we got it forwarded from the sender, assume it is trusted. They may still have + // used key backup, but it is unlikely. + if (roomKey.content.forwarding_curve25519_key_chain.size() == 1 && + roomKey.content.forwarding_curve25519_key_chain.back() == roomKey.content.sender_key) { + data.trusted = true; } - nhlog::crypto()->info( - "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); + backup_session_key(index, data, megolm_session); + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); + return; + } + + nhlog::crypto()->info( + "established inbound megolm session ({}, {})", roomKey.content.room_id, roomKey.sender); - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); } void @@ -870,165 +791,156 @@ backup_session_key(const MegolmSessionIndex &idx, const GroupSessionData &data, mtx::crypto::InboundGroupSessionPtr &session) { - try { - if (!UserSettings::instance()->useOnlineKeyBackup()) { - // Online key backup disabled - return; - } - - auto backupVersion = cache::client()->backupVersion(); - if (!backupVersion) { - // no trusted OKB - return; - } - - using namespace mtx::crypto; - - auto decryptedSecret = - cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); - if (!decryptedSecret) { - // no backup key available - return; - } - auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); - - auto public_key = - mtx::crypto::CURVE25519_public_key_from_private(sessionDecryptionKey); - - mtx::responses::backup::SessionData sessionData; - sessionData.algorithm = mtx::crypto::MEGOLM_ALGO; - sessionData.forwarding_curve25519_key_chain = data.forwarding_curve25519_key_chain; - sessionData.sender_claimed_keys["ed25519"] = data.sender_claimed_ed25519_key; - sessionData.sender_key = idx.sender_key; - sessionData.session_key = mtx::crypto::export_session(session.get(), -1); - - auto encrypt_session = mtx::crypto::encrypt_session(sessionData, public_key); - - mtx::responses::backup::SessionBackup bk; - bk.first_message_index = olm_inbound_group_session_first_known_index(session.get()); - bk.forwarded_count = data.forwarding_curve25519_key_chain.size(); - bk.is_verified = false; - bk.session_data = std::move(encrypt_session); - - http::client()->put_room_keys( - backupVersion->version, - idx.room_id, - idx.session_id, - bk, - [idx](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to backup session key ({}:{}): {} ({})", - idx.room_id, - idx.session_id, - err->matrix_error.error, - static_cast(err->status_code)); - } else { - nhlog::crypto()->debug( - "backed up session key ({}:{})", idx.room_id, idx.session_id); - } - }); - } catch (std::exception &e) { - nhlog::net()->warn("failed to backup session key: {}", e.what()); - } -} - -void -mark_keys_as_published() -{ - olm::client()->mark_keys_as_published(); - cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); -} - -void -lookup_keybackup(const std::string room, const std::string session_id) -{ + try { if (!UserSettings::instance()->useOnlineKeyBackup()) { - // Online key backup disabled - return; + // Online key backup disabled + return; } auto backupVersion = cache::client()->backupVersion(); if (!backupVersion) { - // no trusted OKB - return; + // no trusted OKB + return; } using namespace mtx::crypto; auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); if (!decryptedSecret) { - // no backup key available - return; + // no backup key available + return; } auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); - http::client()->room_keys( + auto public_key = mtx::crypto::CURVE25519_public_key_from_private(sessionDecryptionKey); + + mtx::responses::backup::SessionData sessionData; + sessionData.algorithm = mtx::crypto::MEGOLM_ALGO; + sessionData.forwarding_curve25519_key_chain = data.forwarding_curve25519_key_chain; + sessionData.sender_claimed_keys["ed25519"] = data.sender_claimed_ed25519_key; + sessionData.sender_key = idx.sender_key; + sessionData.session_key = mtx::crypto::export_session(session.get(), -1); + + auto encrypt_session = mtx::crypto::encrypt_session(sessionData, public_key); + + mtx::responses::backup::SessionBackup bk; + bk.first_message_index = olm_inbound_group_session_first_known_index(session.get()); + bk.forwarded_count = data.forwarding_curve25519_key_chain.size(); + bk.is_verified = false; + bk.session_data = std::move(encrypt_session); + + http::client()->put_room_keys( backupVersion->version, - room, - session_id, - [room, session_id, sessionDecryptionKey](const mtx::responses::backup::SessionBackup &bk, - mtx::http::RequestErr err) { - if (err) { - if (err->status_code != 404) - nhlog::crypto()->error( - "Failed to dowload key {}:{}: {} - {}", - room, - session_id, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } - try { - auto session = decrypt_session(bk.session_data, sessionDecryptionKey); - - if (session.algorithm != mtx::crypto::MEGOLM_ALGO) - // don't know this algorithm - return; - - MegolmSessionIndex index; - index.room_id = room; - index.session_id = session_id; - index.sender_key = session.sender_key; - - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = - session.forwarding_curve25519_key_chain; - data.sender_claimed_ed25519_key = session.sender_claimed_keys["ed25519"]; - // online key backup can't be trusted, because anyone can upload to it. - data.trusted = false; - - auto megolm_session = - olm::client()->import_inbound_group_session(session.session_key); - - if (!cache::inboundMegolmSessionExists(index) || - olm_inbound_group_session_first_known_index(megolm_session.get()) < - olm_inbound_group_session_first_known_index( - cache::getInboundMegolmSession(index).get())) { - cache::saveInboundMegolmSession( - index, std::move(megolm_session), data); - - nhlog::crypto()->info("imported inbound megolm session " - "from key backup ({}, {})", - room, - session_id); - - // call on UI thread - QTimer::singleShot(0, ChatPage::instance(), [index] { - ChatPage::instance()->receivedSessionKey( - index.room_id, index.session_id); - }); - } - } catch (const lmdb::error &e) { - nhlog::crypto()->critical("failed to save inbound megolm session: {}", - e.what()); - return; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to import inbound megolm session: {}", - e.what()); - return; - } + idx.room_id, + idx.session_id, + bk, + [idx](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to backup session key ({}:{}): {} ({})", + idx.room_id, + idx.session_id, + err->matrix_error.error, + static_cast(err->status_code)); + } else { + nhlog::crypto()->debug( + "backed up session key ({}:{})", idx.room_id, idx.session_id); + } }); + } catch (std::exception &e) { + nhlog::net()->warn("failed to backup session key: {}", e.what()); + } +} + +void +mark_keys_as_published() +{ + olm::client()->mark_keys_as_published(); + cache::saveOlmAccount(olm::client()->save(cache::client()->pickleSecret())); +} + +void +lookup_keybackup(const std::string room, const std::string session_id) +{ + if (!UserSettings::instance()->useOnlineKeyBackup()) { + // Online key backup disabled + return; + } + + auto backupVersion = cache::client()->backupVersion(); + if (!backupVersion) { + // no trusted OKB + return; + } + + using namespace mtx::crypto; + + auto decryptedSecret = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1); + if (!decryptedSecret) { + // no backup key available + return; + } + auto sessionDecryptionKey = to_binary_buf(base642bin(*decryptedSecret)); + + http::client()->room_keys( + backupVersion->version, + room, + session_id, + [room, session_id, sessionDecryptionKey](const mtx::responses::backup::SessionBackup &bk, + mtx::http::RequestErr err) { + if (err) { + if (err->status_code != 404) + nhlog::crypto()->error("Failed to dowload key {}:{}: {} - {}", + room, + session_id, + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error); + return; + } + try { + auto session = decrypt_session(bk.session_data, sessionDecryptionKey); + + if (session.algorithm != mtx::crypto::MEGOLM_ALGO) + // don't know this algorithm + return; + + MegolmSessionIndex index; + index.room_id = room; + index.session_id = session_id; + index.sender_key = session.sender_key; + + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = session.forwarding_curve25519_key_chain; + data.sender_claimed_ed25519_key = session.sender_claimed_keys["ed25519"]; + // online key backup can't be trusted, because anyone can upload to it. + data.trusted = false; + + auto megolm_session = + olm::client()->import_inbound_group_session(session.session_key); + + if (!cache::inboundMegolmSessionExists(index) || + olm_inbound_group_session_first_known_index(megolm_session.get()) < + olm_inbound_group_session_first_known_index( + cache::getInboundMegolmSession(index).get())) { + cache::saveInboundMegolmSession(index, std::move(megolm_session), data); + + nhlog::crypto()->info("imported inbound megolm session " + "from key backup ({}, {})", + room, + session_id); + + // call on UI thread + QTimer::singleShot(0, ChatPage::instance(), [index] { + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + }); + } + } catch (const lmdb::error &e) { + nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what()); + return; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to import inbound megolm session: {}", e.what()); + return; + } + }); } void @@ -1036,158 +948,152 @@ send_key_request_for(mtx::events::EncryptedEvent e, const std::string &request_id, bool cancel) { - using namespace mtx::events; - - nhlog::crypto()->debug("sending key request: sender_key {}, session_id {}", - e.content.sender_key, - e.content.session_id); - - mtx::events::msg::KeyRequest request; - request.action = cancel ? mtx::events::msg::RequestAction::Cancellation - : mtx::events::msg::RequestAction::Request; - - request.algorithm = MEGOLM_ALGO; - request.room_id = e.room_id; - request.sender_key = e.content.sender_key; - request.session_id = e.content.session_id; - request.request_id = request_id; - request.requesting_device_id = http::client()->device_id(); - - nhlog::crypto()->debug("m.room_key_request: {}", json(request).dump(2)); - - std::map> body; - body[mtx::identifiers::parse(e.sender)][e.content.device_id] = - request; - body[http::client()->user_id()]["*"] = request; - - http::client()->send_to_device( - http::client()->generate_txn_id(), body, [e](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } - - nhlog::net()->info("m.room_key_request sent to {}:{} and your own devices", - e.sender, - e.content.device_id); - }); - - // http::client()->room_keys + using namespace mtx::events; + + nhlog::crypto()->debug("sending key request: sender_key {}, session_id {}", + e.content.sender_key, + e.content.session_id); + + mtx::events::msg::KeyRequest request; + request.action = cancel ? mtx::events::msg::RequestAction::Cancellation + : mtx::events::msg::RequestAction::Request; + + request.algorithm = MEGOLM_ALGO; + request.room_id = e.room_id; + request.sender_key = e.content.sender_key; + request.session_id = e.content.session_id; + request.request_id = request_id; + request.requesting_device_id = http::client()->device_id(); + + nhlog::crypto()->debug("m.room_key_request: {}", json(request).dump(2)); + + std::map> body; + body[mtx::identifiers::parse(e.sender)][e.content.device_id] = request; + body[http::client()->user_id()]["*"] = request; + + http::client()->send_to_device( + http::client()->generate_txn_id(), body, [e](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } + + nhlog::net()->info( + "m.room_key_request sent to {}:{} and your own devices", e.sender, e.content.device_id); + }); + + // http::client()->room_keys } void handle_key_request_message(const mtx::events::DeviceEvent &req) { - if (req.content.algorithm != MEGOLM_ALGO) { - nhlog::crypto()->debug("ignoring key request {} with invalid algorithm: {}", - req.content.request_id, - req.content.algorithm); - return; - } - - // Check if we were the sender of the session being requested (unless it is actually us - // requesting the session). - if (req.sender != http::client()->user_id().to_string() && - req.content.sender_key != olm::client()->identity_keys().curve25519) { - nhlog::crypto()->debug( - "ignoring key request {} because we did not create the requested session: " - "\nrequested({}) ours({})", - req.content.request_id, - req.content.sender_key, - olm::client()->identity_keys().curve25519); - return; - } - - // Check that the requested session_id and the one we have saved match. - MegolmSessionIndex index{}; - index.room_id = req.content.room_id; - index.session_id = req.content.session_id; - index.sender_key = req.content.sender_key; - - // Check if we have the keys for the requested session. - auto sessionData = cache::getMegolmSessionData(index); - if (!sessionData) { - nhlog::crypto()->warn("requested session not found in room: {}", - req.content.room_id); - return; - } - - const auto session = cache::getInboundMegolmSession(index); - if (!session) { - nhlog::crypto()->warn("No session with id {} in db", req.content.session_id); - return; - } - - if (!cache::isRoomMember(req.sender, req.content.room_id)) { - nhlog::crypto()->warn( - "user {} that requested the session key is not member of the room {}", - req.sender, - req.content.room_id); - return; - } - - // check if device is verified - auto verificationStatus = cache::verificationStatus(req.sender); - bool verifiedDevice = false; - if (verificationStatus && - // Share keys, if the option to share with trusted users is enabled or with yourself - (ChatPage::instance()->userSettings()->shareKeysWithTrustedUsers() || - req.sender == http::client()->user_id().to_string())) { - for (const auto &dev : verificationStatus->verified_devices) { - if (dev == req.content.requesting_device_id) { - verifiedDevice = true; - nhlog::crypto()->debug("Verified device: {}", dev); - break; - } - } - } - - bool shouldSeeKeys = false; - uint64_t minimumIndex = -1; - if (sessionData->currently.keys.count(req.sender)) { - if (sessionData->currently.keys.at(req.sender) - .deviceids.count(req.content.requesting_device_id)) { - shouldSeeKeys = true; - minimumIndex = sessionData->currently.keys.at(req.sender) - .deviceids.at(req.content.requesting_device_id); - } + if (req.content.algorithm != MEGOLM_ALGO) { + nhlog::crypto()->debug("ignoring key request {} with invalid algorithm: {}", + req.content.request_id, + req.content.algorithm); + return; + } + + // Check if we were the sender of the session being requested (unless it is actually us + // requesting the session). + if (req.sender != http::client()->user_id().to_string() && + req.content.sender_key != olm::client()->identity_keys().curve25519) { + nhlog::crypto()->debug( + "ignoring key request {} because we did not create the requested session: " + "\nrequested({}) ours({})", + req.content.request_id, + req.content.sender_key, + olm::client()->identity_keys().curve25519); + return; + } + + // Check that the requested session_id and the one we have saved match. + MegolmSessionIndex index{}; + index.room_id = req.content.room_id; + index.session_id = req.content.session_id; + index.sender_key = req.content.sender_key; + + // Check if we have the keys for the requested session. + auto sessionData = cache::getMegolmSessionData(index); + if (!sessionData) { + nhlog::crypto()->warn("requested session not found in room: {}", req.content.room_id); + return; + } + + const auto session = cache::getInboundMegolmSession(index); + if (!session) { + nhlog::crypto()->warn("No session with id {} in db", req.content.session_id); + return; + } + + if (!cache::isRoomMember(req.sender, req.content.room_id)) { + nhlog::crypto()->warn("user {} that requested the session key is not member of the room {}", + req.sender, + req.content.room_id); + return; + } + + // check if device is verified + auto verificationStatus = cache::verificationStatus(req.sender); + bool verifiedDevice = false; + if (verificationStatus && + // Share keys, if the option to share with trusted users is enabled or with yourself + (ChatPage::instance()->userSettings()->shareKeysWithTrustedUsers() || + req.sender == http::client()->user_id().to_string())) { + for (const auto &dev : verificationStatus->verified_devices) { + if (dev == req.content.requesting_device_id) { + verifiedDevice = true; + nhlog::crypto()->debug("Verified device: {}", dev); + break; + } } - - if (!verifiedDevice && !shouldSeeKeys) { - nhlog::crypto()->debug("ignoring key request for room {}", req.content.room_id); - return; - } - - if (verifiedDevice) { - // share the minimum index we have - minimumIndex = -1; - } - - try { - auto session_key = mtx::crypto::export_session(session.get(), minimumIndex); - - // - // Prepare the m.room_key event. - // - mtx::events::msg::ForwardedRoomKey forward_key{}; - forward_key.algorithm = MEGOLM_ALGO; - forward_key.room_id = index.room_id; - forward_key.session_id = index.session_id; - forward_key.session_key = session_key; - forward_key.sender_key = index.sender_key; - - // TODO(Nico): Figure out if this is correct - forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key; - forward_key.forwarding_curve25519_key_chain = - sessionData->forwarding_curve25519_key_chain; - - send_megolm_key_to_device( - req.sender, req.content.requesting_device_id, forward_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to forward session key: {}", e.what()); + } + + bool shouldSeeKeys = false; + uint64_t minimumIndex = -1; + if (sessionData->currently.keys.count(req.sender)) { + if (sessionData->currently.keys.at(req.sender) + .deviceids.count(req.content.requesting_device_id)) { + shouldSeeKeys = true; + minimumIndex = sessionData->currently.keys.at(req.sender) + .deviceids.at(req.content.requesting_device_id); } + } + + if (!verifiedDevice && !shouldSeeKeys) { + nhlog::crypto()->debug("ignoring key request for room {}", req.content.room_id); + return; + } + + if (verifiedDevice) { + // share the minimum index we have + minimumIndex = -1; + } + + try { + auto session_key = mtx::crypto::export_session(session.get(), minimumIndex); + + // + // Prepare the m.room_key event. + // + mtx::events::msg::ForwardedRoomKey forward_key{}; + forward_key.algorithm = MEGOLM_ALGO; + forward_key.room_id = index.room_id; + forward_key.session_id = index.session_id; + forward_key.session_key = session_key; + forward_key.sender_key = index.sender_key; + + // TODO(Nico): Figure out if this is correct + forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key; + forward_key.forwarding_curve25519_key_chain = sessionData->forwarding_curve25519_key_chain; + + send_megolm_key_to_device(req.sender, req.content.requesting_device_id, forward_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to forward session key: {}", e.what()); + } } void @@ -1195,14 +1101,14 @@ send_megolm_key_to_device(const std::string &user_id, const std::string &device_id, const mtx::events::msg::ForwardedRoomKey &payload) { - mtx::events::DeviceEvent room_key; - room_key.content = payload; - room_key.type = mtx::events::EventType::ForwardedRoomKey; - - std::map> targets; - targets[user_id] = {device_id}; - send_encrypted_to_device_messages(targets, room_key); - nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id); + mtx::events::DeviceEvent room_key; + room_key.content = payload; + room_key.type = mtx::events::EventType::ForwardedRoomKey; + + std::map> targets; + targets[user_id] = {device_id}; + send_encrypted_to_device_messages(targets, room_key); + nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id); } DecryptionResult @@ -1210,79 +1116,74 @@ decryptEvent(const MegolmSessionIndex &index, const mtx::events::EncryptedEvent &event, bool dont_write_db) { - try { - if (!cache::client()->inboundMegolmSessionExists(index)) { - return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt}; - } - } catch (const lmdb::error &e) { - return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; - } - - // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors - - std::string msg_str; - try { - auto session = cache::client()->getInboundMegolmSession(index); - auto sessionData = - cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); - - auto res = - olm::client()->decrypt_group_message(session.get(), event.content.ciphertext); - msg_str = std::string((char *)res.data.data(), res.data.size()); - - if (!event.event_id.empty() && event.event_id[0] == '$') { - auto oldIdx = sessionData.indices.find(res.message_index); - if (oldIdx != sessionData.indices.end()) { - if (oldIdx->second != event.event_id) - return {DecryptionErrorCode::ReplayAttack, - std::nullopt, - std::nullopt}; - } else if (!dont_write_db) { - sessionData.indices[res.message_index] = event.event_id; - cache::client()->saveInboundMegolmSession( - index, std::move(session), sessionData); - } - } - } catch (const lmdb::error &e) { - return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; - } catch (const mtx::crypto::olm_exception &e) { - if (e.error_code() == mtx::crypto::OlmErrorCode::UNKNOWN_MESSAGE_INDEX) - return {DecryptionErrorCode::MissingSessionIndex, e.what(), std::nullopt}; - return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt}; + try { + if (!cache::client()->inboundMegolmSessionExists(index)) { + return {DecryptionErrorCode::MissingSession, std::nullopt, std::nullopt}; } - - try { - // Add missing fields for the event. - json body = json::parse(msg_str); - body["event_id"] = event.event_id; - body["sender"] = event.sender; - body["origin_server_ts"] = event.origin_server_ts; - body["unsigned"] = event.unsigned_data; - - // relations are unencrypted in content... - mtx::common::add_relations(body["content"], event.content.relations); - - mtx::events::collections::TimelineEvent te; - mtx::events::collections::from_json(body, te); - - return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)}; - } catch (std::exception &e) { - return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; + } catch (const lmdb::error &e) { + return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; + } + + // TODO: Lookup index,event_id,origin_server_ts tuple for replay attack errors + + std::string msg_str; + try { + auto session = cache::client()->getInboundMegolmSession(index); + auto sessionData = + cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); + + auto res = olm::client()->decrypt_group_message(session.get(), event.content.ciphertext); + msg_str = std::string((char *)res.data.data(), res.data.size()); + + if (!event.event_id.empty() && event.event_id[0] == '$') { + auto oldIdx = sessionData.indices.find(res.message_index); + if (oldIdx != sessionData.indices.end()) { + if (oldIdx->second != event.event_id) + return {DecryptionErrorCode::ReplayAttack, std::nullopt, std::nullopt}; + } else if (!dont_write_db) { + sessionData.indices[res.message_index] = event.event_id; + cache::client()->saveInboundMegolmSession(index, std::move(session), sessionData); + } } + } catch (const lmdb::error &e) { + return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; + } catch (const mtx::crypto::olm_exception &e) { + if (e.error_code() == mtx::crypto::OlmErrorCode::UNKNOWN_MESSAGE_INDEX) + return {DecryptionErrorCode::MissingSessionIndex, e.what(), std::nullopt}; + return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt}; + } + + try { + // Add missing fields for the event. + json body = json::parse(msg_str); + body["event_id"] = event.event_id; + body["sender"] = event.sender; + body["origin_server_ts"] = event.origin_server_ts; + body["unsigned"] = event.unsigned_data; + + // relations are unencrypted in content... + mtx::common::add_relations(body["content"], event.content.relations); + + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(body, te); + + return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)}; + } catch (std::exception &e) { + return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; + } } crypto::Trust calculate_trust(const std::string &user_id, const MegolmSessionIndex &index) { - auto status = cache::client()->verificationStatus(user_id); - auto megolmData = cache::client()->getMegolmSessionData(index); - crypto::Trust trustlevel = crypto::Trust::Unverified; + auto status = cache::client()->verificationStatus(user_id); + auto megolmData = cache::client()->getMegolmSessionData(index); + crypto::Trust trustlevel = crypto::Trust::Unverified; - if (megolmData && megolmData->trusted && - status.verified_device_keys.count(index.sender_key)) - trustlevel = status.verified_device_keys.at(index.sender_key); + if (megolmData && megolmData->trusted && status.verified_device_keys.count(index.sender_key)) + trustlevel = status.verified_device_keys.at(index.sender_key); - return trustlevel; + return trustlevel; } //! Send encrypted to device messages, targets is a map from userid to device ids or {} for all @@ -1292,397 +1193,352 @@ send_encrypted_to_device_messages(const std::map, qint64> rateLimit; + static QMap, qint64> rateLimit; - nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); + nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event); - std::map> keysToQuery; - mtx::requests::ClaimKeys claims; - std::map> - messages; - std::map> pks; + std::map> keysToQuery; + mtx::requests::ClaimKeys claims; + std::map> + messages; + std::map> pks; - auto our_curve = olm::client()->identity_keys().curve25519; + auto our_curve = olm::client()->identity_keys().curve25519; - for (const auto &[user, devices] : targets) { - auto deviceKeys = cache::client()->userKeys(user); + for (const auto &[user, devices] : targets) { + auto deviceKeys = cache::client()->userKeys(user); - // no keys for user, query them - if (!deviceKeys) { - keysToQuery[user] = devices; - continue; + // no keys for user, query them + if (!deviceKeys) { + keysToQuery[user] = devices; + continue; + } + + auto deviceTargets = devices; + if (devices.empty()) { + deviceTargets.clear(); + for (const auto &[device, keys] : deviceKeys->device_keys) { + (void)keys; + deviceTargets.push_back(device); + } + } + + for (const auto &device : deviceTargets) { + if (!deviceKeys->device_keys.count(device)) { + keysToQuery[user] = {}; + break; + } + + auto d = deviceKeys->device_keys.at(device); + + if (!d.keys.count("curve25519:" + device) || !d.keys.count("ed25519:" + device)) { + nhlog::crypto()->warn("Skipping device {} since it has no keys!", device); + continue; + } + + auto device_curve = d.keys.at("curve25519:" + device); + if (device_curve == our_curve) { + nhlog::crypto()->warn("Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + + auto session = cache::getLatestOlmSession(device_curve); + if (!session || force_new_session) { + auto currentTime = QDateTime::currentSecsSinceEpoch(); + if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < currentTime) { + claims.one_time_keys[user][device] = mtx::crypto::SIGNED_CURVE25519; + pks[user][device].ed25519 = d.keys.at("ed25519:" + device); + pks[user][device].curve25519 = d.keys.at("curve25519:" + device); + + rateLimit.insert(QPair(user, device), currentTime); + } else { + nhlog::crypto()->warn("Not creating new session with {}:{} " + "because of rate limit", + user, + device); } + continue; + } + + messages[mtx::identifiers::parse(user)][device] = + olm::client() + ->create_olm_encrypted_content(session->get(), + ev_json, + UserId(user), + d.keys.at("ed25519:" + device), + device_curve) + .get(); + + try { + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session->get())); + cache::saveOlmSession(d.keys.at("curve25519:" + device), + std::move(*session), + QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to pickle outbound olm session: {}", e.what()); + } + } + } + + if (!messages.empty()) + http::client()->send_to_device( + http::client()->generate_txn_id(), messages, [](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } + }); - auto deviceTargets = devices; - if (devices.empty()) { - deviceTargets.clear(); - for (const auto &[device, keys] : deviceKeys->device_keys) { - (void)keys; - deviceTargets.push_back(device); - } + auto BindPks = [ev_json](decltype(pks) pks_temp) { + return [pks = pks_temp, ev_json](const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr) { + std::map> + messages; + for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { + nhlog::net()->debug("claimed keys for {}", user_id); + if (retrieved_devices.size() == 0) { + nhlog::net()->debug("no one-time keys found for user_id: {}", user_id); + continue; } - for (const auto &device : deviceTargets) { - if (!deviceKeys->device_keys.count(device)) { - keysToQuery[user] = {}; - break; - } + for (const auto &rd : retrieved_devices) { + const auto device_id = rd.first; - auto d = deviceKeys->device_keys.at(device); + nhlog::net()->debug("{} : \n {}", device_id, rd.second.dump(2)); - if (!d.keys.count("curve25519:" + device) || - !d.keys.count("ed25519:" + device)) { - nhlog::crypto()->warn("Skipping device {} since it has no keys!", - device); - continue; - } + if (rd.second.empty() || !rd.second.begin()->contains("key")) { + nhlog::net()->warn("Skipping device {} as it has no key.", device_id); + continue; + } - auto device_curve = d.keys.at("curve25519:" + device); - if (device_curve == our_curve) { - nhlog::crypto()->warn("Skipping our own device, since sending " - "ourselves olm messages makes no sense."); - continue; - } + auto otk = rd.second.begin()->at("key"); - auto session = cache::getLatestOlmSession(device_curve); - if (!session || force_new_session) { - auto currentTime = QDateTime::currentSecsSinceEpoch(); - if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < - currentTime) { - claims.one_time_keys[user][device] = - mtx::crypto::SIGNED_CURVE25519; - pks[user][device].ed25519 = d.keys.at("ed25519:" + device); - pks[user][device].curve25519 = - d.keys.at("curve25519:" + device); - - rateLimit.insert(QPair(user, device), currentTime); - } else { - nhlog::crypto()->warn("Not creating new session with {}:{} " - "because of rate limit", - user, - device); - } - continue; - } + auto sign_key = pks.at(user_id).at(device_id).ed25519; + auto id_key = pks.at(user_id).at(device_id).curve25519; + + // Verify signature + { + auto signedKey = *rd.second.begin(); + std::string signature = + signedKey["signatures"][user_id].value("ed25519:" + device_id, ""); - messages[mtx::identifiers::parse(user)][device] = - olm::client() - ->create_olm_encrypted_content(session->get(), - ev_json, - UserId(user), - d.keys.at("ed25519:" + device), - device_curve) - .get(); - - try { - nhlog::crypto()->debug("Updated olm session: {}", - mtx::crypto::session_id(session->get())); - cache::saveOlmSession(d.keys.at("curve25519:" + device), - std::move(*session), - QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to save outbound olm session: {}", - e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to pickle outbound olm session: {}", e.what()); + if (signature.empty() || !mtx::crypto::ed25519_verify_signature( + sign_key, signedKey, signature)) { + nhlog::net()->warn("Skipping device {} as its one time key " + "has an invalid signature.", + device_id); + continue; } + } + + auto session = olm::client()->create_outbound_session(id_key, otk); + + messages[mtx::identifiers::parse(user_id)][device_id] = + olm::client() + ->create_olm_encrypted_content( + session.get(), ev_json, UserId(user_id), sign_key, id_key) + .get(); + + try { + nhlog::crypto()->debug("Updated olm session: {}", + mtx::crypto::session_id(session.get())); + cache::saveOlmSession( + id_key, std::move(session), QDateTime::currentMSecsSinceEpoch()); + } catch (const lmdb::error &e) { + nhlog::db()->critical("failed to save outbound olm session: {}", e.what()); + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->critical("failed to pickle outbound olm session: {}", + e.what()); + } } - } + nhlog::net()->info("send_to_device: {}", user_id); + } - if (!messages.empty()) + if (!messages.empty()) http::client()->send_to_device( http::client()->generate_txn_id(), messages, [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } + if (err) { + nhlog::net()->warn("failed to send " + "send_to_device " + "message: {}", + err->matrix_error.error); + } }); - - auto BindPks = [ev_json](decltype(pks) pks_temp) { - return [pks = pks_temp, ev_json](const mtx::responses::ClaimKeys &res, - mtx::http::RequestErr) { - std::map> - messages; - for (const auto &[user_id, retrieved_devices] : res.one_time_keys) { - nhlog::net()->debug("claimed keys for {}", user_id); - if (retrieved_devices.size() == 0) { - nhlog::net()->debug( - "no one-time keys found for user_id: {}", user_id); - continue; - } - - for (const auto &rd : retrieved_devices) { - const auto device_id = rd.first; - - nhlog::net()->debug( - "{} : \n {}", device_id, rd.second.dump(2)); - - if (rd.second.empty() || - !rd.second.begin()->contains("key")) { - nhlog::net()->warn( - "Skipping device {} as it has no key.", - device_id); - continue; - } - - auto otk = rd.second.begin()->at("key"); - - auto sign_key = pks.at(user_id).at(device_id).ed25519; - auto id_key = pks.at(user_id).at(device_id).curve25519; - - // Verify signature - { - auto signedKey = *rd.second.begin(); - std::string signature = - signedKey["signatures"][user_id].value( - "ed25519:" + device_id, ""); - - if (signature.empty() || - !mtx::crypto::ed25519_verify_signature( - sign_key, signedKey, signature)) { - nhlog::net()->warn( - "Skipping device {} as its one time key " - "has an invalid signature.", - device_id); - continue; - } - } - - auto session = - olm::client()->create_outbound_session(id_key, otk); - - messages[mtx::identifiers::parse( - user_id)][device_id] = - olm::client() - ->create_olm_encrypted_content(session.get(), - ev_json, - UserId(user_id), - sign_key, - id_key) - .get(); - - try { - nhlog::crypto()->debug( - "Updated olm session: {}", - mtx::crypto::session_id(session.get())); - cache::saveOlmSession( - id_key, - std::move(session), - QDateTime::currentMSecsSinceEpoch()); - } catch (const lmdb::error &e) { - nhlog::db()->critical( - "failed to save outbound olm session: {}", - e.what()); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical( - "failed to pickle outbound olm session: {}", - e.what()); - } - } - nhlog::net()->info("send_to_device: {}", user_id); - } - - if (!messages.empty()) - http::client()->send_to_device( - http::client()->generate_txn_id(), - messages, - [](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send " - "send_to_device " - "message: {}", - err->matrix_error.error); - } - }); - }; }; - - if (!claims.one_time_keys.empty()) - http::client()->claim_keys(claims, BindPks(pks)); - - if (!keysToQuery.empty()) { - mtx::requests::QueryKeys req; - req.device_keys = keysToQuery; - http::client()->query_keys( - req, - [ev_json, BindPks, our_curve](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {} {}", - err->matrix_error.error, - static_cast(err->status_code)); - return; - } - - nhlog::net()->info("queried keys"); - - cache::client()->updateUserKeys(cache::nextBatchToken(), res); - - mtx::requests::ClaimKeys claim_keys; - - std::map> deviceKeys; - - for (const auto &user : res.device_keys) { - for (const auto &dev : user.second) { - const auto user_id = ::UserId(dev.second.user_id); - const auto device_id = DeviceId(dev.second.device_id); - - if (user_id.get() == - http::client()->user_id().to_string() && - device_id.get() == http::client()->device_id()) - continue; - - const auto device_keys = dev.second.keys; - const auto curveKey = "curve25519:" + device_id.get(); - const auto edKey = "ed25519:" + device_id.get(); - - if ((device_keys.find(curveKey) == device_keys.end()) || - (device_keys.find(edKey) == device_keys.end())) { - nhlog::net()->debug( - "ignoring malformed keys for device {}", - device_id.get()); - continue; - } - - DevicePublicKeys pks; - pks.ed25519 = device_keys.at(edKey); - pks.curve25519 = device_keys.at(curveKey); - - if (pks.curve25519 == our_curve) { - nhlog::crypto()->warn( - "Skipping our own device, since sending " - "ourselves olm messages makes no sense."); - continue; - } - - try { - if (!mtx::crypto::verify_identity_signature( - dev.second, device_id, user_id)) { - nhlog::crypto()->warn( - "failed to verify identity keys: {}", - json(dev.second).dump(2)); - continue; - } - } catch (const json::exception &e) { - nhlog::crypto()->warn( - "failed to parse device key json: {}", - e.what()); - continue; - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->warn( - "failed to verify device key json: {}", - e.what()); - continue; - } - - auto currentTime = QDateTime::currentSecsSinceEpoch(); - if (rateLimit.value(QPair(user.first, device_id.get())) + - 60 * 60 * 10 < - currentTime) { - deviceKeys[user_id].emplace(device_id, pks); - claim_keys.one_time_keys[user.first][device_id] = - mtx::crypto::SIGNED_CURVE25519; - - rateLimit.insert( - QPair(user.first, device_id.get()), - currentTime); - } else { - nhlog::crypto()->warn( - "Not creating new session with {}:{} " - "because of rate limit", - user.first, - device_id.get()); - continue; - } - - nhlog::net()->info("{}", device_id.get()); - nhlog::net()->info(" curve25519 {}", pks.curve25519); - nhlog::net()->info(" ed25519 {}", pks.ed25519); - } + }; + + if (!claims.one_time_keys.empty()) + http::client()->claim_keys(claims, BindPks(pks)); + + if (!keysToQuery.empty()) { + mtx::requests::QueryKeys req; + req.device_keys = keysToQuery; + http::client()->query_keys( + req, + [ev_json, BindPks, our_curve](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast(err->status_code)); + return; + } + + nhlog::net()->info("queried keys"); + + cache::client()->updateUserKeys(cache::nextBatchToken(), res); + + mtx::requests::ClaimKeys claim_keys; + + std::map> deviceKeys; + + for (const auto &user : res.device_keys) { + for (const auto &dev : user.second) { + const auto user_id = ::UserId(dev.second.user_id); + const auto device_id = DeviceId(dev.second.device_id); + + if (user_id.get() == http::client()->user_id().to_string() && + device_id.get() == http::client()->device_id()) + continue; + + const auto device_keys = dev.second.keys; + const auto curveKey = "curve25519:" + device_id.get(); + const auto edKey = "ed25519:" + device_id.get(); + + if ((device_keys.find(curveKey) == device_keys.end()) || + (device_keys.find(edKey) == device_keys.end())) { + nhlog::net()->debug("ignoring malformed keys for device {}", + device_id.get()); + continue; + } + + DevicePublicKeys pks; + pks.ed25519 = device_keys.at(edKey); + pks.curve25519 = device_keys.at(curveKey); + + if (pks.curve25519 == our_curve) { + nhlog::crypto()->warn("Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + + try { + if (!mtx::crypto::verify_identity_signature( + dev.second, device_id, user_id)) { + nhlog::crypto()->warn("failed to verify identity keys: {}", + json(dev.second).dump(2)); + continue; } + } catch (const json::exception &e) { + nhlog::crypto()->warn("failed to parse device key json: {}", e.what()); + continue; + } catch (const mtx::crypto::olm_exception &e) { + nhlog::crypto()->warn("failed to verify device key json: {}", e.what()); + continue; + } + + auto currentTime = QDateTime::currentSecsSinceEpoch(); + if (rateLimit.value(QPair(user.first, device_id.get())) + 60 * 60 * 10 < + currentTime) { + deviceKeys[user_id].emplace(device_id, pks); + claim_keys.one_time_keys[user.first][device_id] = + mtx::crypto::SIGNED_CURVE25519; + + rateLimit.insert(QPair(user.first, device_id.get()), currentTime); + } else { + nhlog::crypto()->warn("Not creating new session with {}:{} " + "because of rate limit", + user.first, + device_id.get()); + continue; + } + + nhlog::net()->info("{}", device_id.get()); + nhlog::net()->info(" curve25519 {}", pks.curve25519); + nhlog::net()->info(" ed25519 {}", pks.ed25519); + } + } - if (!claim_keys.one_time_keys.empty()) - http::client()->claim_keys(claim_keys, BindPks(deviceKeys)); - }); - } + if (!claim_keys.one_time_keys.empty()) + http::client()->claim_keys(claim_keys, BindPks(deviceKeys)); + }); + } } void request_cross_signing_keys() { - mtx::events::msg::SecretRequest secretRequest{}; - secretRequest.action = mtx::events::msg::RequestAction::Request; - secretRequest.requesting_device_id = http::client()->device_id(); + mtx::events::msg::SecretRequest secretRequest{}; + secretRequest.action = mtx::events::msg::RequestAction::Request; + secretRequest.requesting_device_id = http::client()->device_id(); - auto local_user = http::client()->user_id(); + auto local_user = http::client()->user_id(); - auto verificationStatus = cache::verificationStatus(local_user.to_string()); + auto verificationStatus = cache::verificationStatus(local_user.to_string()); - if (!verificationStatus) - return; + if (!verificationStatus) + return; - auto request = [&](std::string secretName) { - secretRequest.name = secretName; - secretRequest.request_id = "ss." + http::client()->generate_txn_id(); + auto request = [&](std::string secretName) { + secretRequest.name = secretName; + secretRequest.request_id = "ss." + http::client()->generate_txn_id(); - request_id_to_secret_name[secretRequest.request_id] = secretRequest.name; + request_id_to_secret_name[secretRequest.request_id] = secretRequest.name; - std::map> - body; + std::map> + body; - for (const auto &dev : verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id) - body[local_user][dev] = secretRequest; - } + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id) + body[local_user][dev] = secretRequest; + } + + http::client()->send_to_device( + http::client()->generate_txn_id(), + body, + [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to send request for secrect '{}'", secretName); + // Cancel request on UI thread + QTimer::singleShot(1, cache::client(), [request_id]() { + request_id_to_secret_name.erase(request_id); + }); + return; + } + }); + for (const auto &dev : verificationStatus->verified_devices) { + if (dev != secretRequest.requesting_device_id) + body[local_user][dev].action = mtx::events::msg::RequestAction::Cancellation; + } + + // timeout after 15 min + QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() { + if (request_id_to_secret_name.count(secretRequest.request_id)) { + request_id_to_secret_name.erase(secretRequest.request_id); http::client()->send_to_device( http::client()->generate_txn_id(), body, - [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error("Failed to send request for secrect '{}'", - secretName); - // Cancel request on UI thread - QTimer::singleShot(1, cache::client(), [request_id]() { - request_id_to_secret_name.erase(request_id); - }); - return; - } + [secretRequest](mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to cancel request for secrect '{}'", + secretRequest.name); + return; + } }); + } + }); + }; - for (const auto &dev : verificationStatus->verified_devices) { - if (dev != secretRequest.requesting_device_id) - body[local_user][dev].action = - mtx::events::msg::RequestAction::Cancellation; - } - - // timeout after 15 min - QTimer::singleShot(15 * 60 * 1000, [secretRequest, body]() { - if (request_id_to_secret_name.count(secretRequest.request_id)) { - request_id_to_secret_name.erase(secretRequest.request_id); - http::client()->send_to_device( - http::client()->generate_txn_id(), - body, - [secretRequest](mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error( - "Failed to cancel request for secrect '{}'", - secretRequest.name); - return; - } - }); - } - }); - }; - - request(mtx::secret_storage::secrets::cross_signing_self_signing); - request(mtx::secret_storage::secrets::cross_signing_user_signing); - request(mtx::secret_storage::secrets::megolm_backup_v1); + request(mtx::secret_storage::secrets::cross_signing_self_signing); + request(mtx::secret_storage::secrets::cross_signing_user_signing); + request(mtx::secret_storage::secrets::megolm_backup_v1); } namespace { @@ -1690,67 +1546,63 @@ void unlock_secrets(const std::string &key, const std::map &secrets) { - http::client()->secret_storage_key( - key, - [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->error("Failed to download secret storage key"); - return; - } - - emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets); - }); + http::client()->secret_storage_key( + key, + [secrets](mtx::secret_storage::AesHmacSha2KeyDescription keyDesc, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->error("Failed to download secret storage key"); + return; + } + + emit ChatPage::instance()->downloadedSecrets(keyDesc, secrets); + }); } } void download_cross_signing_keys() { - using namespace mtx::secret_storage; - http::client()->secret_storage_secret( - secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) { - std::optional backup_key; - if (!err) - backup_key = secret; - - http::client()->secret_storage_secret( - secrets::cross_signing_self_signing, - [backup_key](Secret secret, mtx::http::RequestErr err) { - std::optional self_signing_key; - if (!err) - self_signing_key = secret; - - http::client()->secret_storage_secret( - secrets::cross_signing_user_signing, - [backup_key, self_signing_key](Secret secret, - mtx::http::RequestErr err) { - std::optional user_signing_key; - if (!err) - user_signing_key = secret; - - std::map> - secrets; - - if (backup_key && !backup_key->encrypted.empty()) - secrets[backup_key->encrypted.begin()->first] - [secrets::megolm_backup_v1] = - backup_key->encrypted.begin()->second; - if (self_signing_key && !self_signing_key->encrypted.empty()) - secrets[self_signing_key->encrypted.begin()->first] - [secrets::cross_signing_self_signing] = - self_signing_key->encrypted.begin()->second; - if (user_signing_key && !user_signing_key->encrypted.empty()) - secrets[user_signing_key->encrypted.begin()->first] - [secrets::cross_signing_user_signing] = - user_signing_key->encrypted.begin()->second; - - for (const auto &[key, secrets] : secrets) - unlock_secrets(key, secrets); - }); - }); - }); + using namespace mtx::secret_storage; + http::client()->secret_storage_secret( + secrets::megolm_backup_v1, [](Secret secret, mtx::http::RequestErr err) { + std::optional backup_key; + if (!err) + backup_key = secret; + + http::client()->secret_storage_secret( + secrets::cross_signing_self_signing, + [backup_key](Secret secret, mtx::http::RequestErr err) { + std::optional self_signing_key; + if (!err) + self_signing_key = secret; + + http::client()->secret_storage_secret( + secrets::cross_signing_user_signing, + [backup_key, self_signing_key](Secret secret, mtx::http::RequestErr err) { + std::optional user_signing_key; + if (!err) + user_signing_key = secret; + + std::map> + secrets; + + if (backup_key && !backup_key->encrypted.empty()) + secrets[backup_key->encrypted.begin()->first][secrets::megolm_backup_v1] = + backup_key->encrypted.begin()->second; + if (self_signing_key && !self_signing_key->encrypted.empty()) + secrets[self_signing_key->encrypted.begin()->first] + [secrets::cross_signing_self_signing] = + self_signing_key->encrypted.begin()->second; + if (user_signing_key && !user_signing_key->encrypted.empty()) + secrets[user_signing_key->encrypted.begin()->first] + [secrets::cross_signing_user_signing] = + user_signing_key->encrypted.begin()->second; + + for (const auto &[key, secrets] : secrets) + unlock_secrets(key, secrets); + }); + }); + }); } } // namespace olm diff --git a/src/Olm.h b/src/Olm.h index eb60ae3a..44e2b8ed 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -18,32 +18,32 @@ Q_NAMESPACE enum DecryptionErrorCode { - NoError, - MissingSession, // Session was not found, retrieve from backup or request from other devices - // and try again - MissingSessionIndex, // Session was found, but it does not reach back enough to this index, - // retrieve from backup or request from other devices and try again - DbError, // DB read failed - DecryptionFailed, // libolm error - ParsingFailed, // Failed to parse the actual event - ReplayAttack, // Megolm index reused + NoError, + MissingSession, // Session was not found, retrieve from backup or request from other devices + // and try again + MissingSessionIndex, // Session was found, but it does not reach back enough to this index, + // retrieve from backup or request from other devices and try again + DbError, // DB read failed + DecryptionFailed, // libolm error + ParsingFailed, // Failed to parse the actual event + ReplayAttack, // Megolm index reused }; Q_ENUM_NS(DecryptionErrorCode) struct DecryptionResult { - DecryptionErrorCode error; - std::optional error_message; - std::optional event; + DecryptionErrorCode error; + std::optional error_message; + std::optional event; }; struct OlmMessage { - std::string sender_key; - std::string sender; + std::string sender_key; + std::string sender; - using RecipientKey = std::string; - std::map ciphertext; + using RecipientKey = std::string; + std::map ciphertext; }; void diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 25262c59..ff93f7d8 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -16,116 +16,115 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject , event_id_{event_id} , room_id_{room_id} { - try { - addUsers(cache::readReceipts(event_id_, room_id_)); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id_.toStdString(), - room_id_.toStdString()); - - return; - } + try { + addUsers(cache::readReceipts(event_id_, room_id_)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id_.toStdString(), + room_id_.toStdString()); + + return; + } - connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); + connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); } void ReadReceiptsModel::update() { - try { - addUsers(cache::readReceipts(event_id_, room_id_)); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id_.toStdString(), - room_id_.toStdString()); - - return; - } + try { + addUsers(cache::readReceipts(event_id_, room_id_)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id_.toStdString(), + room_id_.toStdString()); + + return; + } } QHash ReadReceiptsModel::roleNames() const { - // Note: RawTimestamp is purposely not included here - return { - {Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Timestamp, "timestamp"}, - }; + // Note: RawTimestamp is purposely not included here + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Timestamp, "timestamp"}, + }; } QVariant ReadReceiptsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) - return {}; - - switch (role) { - case Mxid: - return readReceipts_[index.row()].first; - case DisplayName: - return cache::displayName(room_id_, readReceipts_[index.row()].first); - case AvatarUrl: - return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); - case Timestamp: - return dateFormat(readReceipts_[index.row()].second); - case RawTimestamp: - return readReceipts_[index.row()].second; - default: - return {}; - } + if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return readReceipts_[index.row()].first; + case DisplayName: + return cache::displayName(room_id_, readReceipts_[index.row()].first); + case AvatarUrl: + return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); + case Timestamp: + return dateFormat(readReceipts_[index.row()].second); + case RawTimestamp: + return readReceipts_[index.row()].second; + default: + return {}; + } } void ReadReceiptsModel::addUsers( const std::multimap> &users) { - auto newReceipts = users.size() - readReceipts_.size(); - - if (newReceipts > 0) { - beginInsertRows( - QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); + auto newReceipts = users.size() - readReceipts_.size(); - for (const auto &user : users) { - QPair item = { - QString::fromStdString(user.second), - QDateTime::fromMSecsSinceEpoch(user.first)}; - if (!readReceipts_.contains(item)) - readReceipts_.push_back(item); - } + if (newReceipts > 0) { + beginInsertRows( + QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); - endInsertRows(); + for (const auto &user : users) { + QPair item = {QString::fromStdString(user.second), + QDateTime::fromMSecsSinceEpoch(user.first)}; + if (!readReceipts_.contains(item)) + readReceipts_.push_back(item); } + + endInsertRows(); + } } QString ReadReceiptsModel::dateFormat(const QDateTime &then) const { - auto now = QDateTime::currentDateTime(); - auto days = then.daysTo(now); - - if (days == 0) - return QLocale::system().toString(then.time(), QLocale::ShortFormat); - else if (days < 2) - return tr("Yesterday, %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - else if (days < 7) - //: %1 is the name of the current day, %2 is the time the read receipt was read. The - //: result may look like this: Monday, 7:15 - return QString("%1, %2") - .arg(then.toString("dddd")) - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + auto now = QDateTime::currentDateTime(); + auto days = then.daysTo(now); + if (days == 0) return QLocale::system().toString(then.time(), QLocale::ShortFormat); + else if (days < 2) + return tr("Yesterday, %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + else if (days < 7) + //: %1 is the name of the current day, %2 is the time the read receipt was read. The + //: result may look like this: Monday, 7:15 + return QString("%1, %2") + .arg(then.toString("dddd")) + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + + return QLocale::system().toString(then.time(), QLocale::ShortFormat); } ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent) : QSortFilterProxyModel{parent} , model_{event_id, room_id, this} { - setSourceModel(&model_); - setSortRole(ReadReceiptsModel::RawTimestamp); - sort(0, Qt::DescendingOrder); - setDynamicSortFilter(true); + setSourceModel(&model_); + setSortRole(ReadReceiptsModel::RawTimestamp); + sort(0, Qt::DescendingOrder); + setDynamicSortFilter(true); } diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 3b45716c..7487fff8 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -13,61 +13,61 @@ class ReadReceiptsModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum Roles - { - Mxid, - DisplayName, - AvatarUrl, - Timestamp, - RawTimestamp, - }; - - explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); - - QString eventId() const { return event_id_; } - QString roomId() const { return room_id_; } - - QHash roleNames() const override; - int rowCount(const QModelIndex &parent) const override - { - Q_UNUSED(parent) - return readReceipts_.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Timestamp, + RawTimestamp, + }; + + explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); + + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent) const override + { + Q_UNUSED(parent) + return readReceipts_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; public slots: - void addUsers(const std::multimap> &users); - void update(); + void addUsers(const std::multimap> &users); + void update(); private: - QString dateFormat(const QDateTime &then) const; + QString dateFormat(const QDateTime &then) const; - QString event_id_; - QString room_id_; - QVector> readReceipts_; + QString event_id_; + QString room_id_; + QVector> readReceipts_; }; class ReadReceiptsProxy : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(QString eventId READ eventId CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) + Q_PROPERTY(QString eventId READ eventId CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) public: - explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); + explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); - QString eventId() const { return event_id_; } - QString roomId() const { return room_id_; } + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } private: - QString event_id_; - QString room_id_; + QString event_id_; + QString room_id_; - ReadReceiptsModel model_; + ReadReceiptsModel model_; }; #endif // READRECEIPTSMODEL_H diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index fb6a1b97..0204a307 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -33,496 +33,483 @@ Q_DECLARE_METATYPE(mtx::user_interactive::Auth) RegisterPage::RegisterPage(QWidget *parent) : QWidget(parent) { - qRegisterMetaType(); - qRegisterMetaType(); - top_layout_ = new QVBoxLayout(); - - back_layout_ = new QHBoxLayout(); - back_layout_->setSpacing(0); - back_layout_->setContentsMargins(5, 5, -1, -1); - - back_button_ = new FlatButton(this); - back_button_->setMinimumSize(QSize(30, 30)); - - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - - back_button_->setIcon(icon); - back_button_->setIconSize(QSize(32, 32)); - - back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); - back_layout_->addStretch(1); - - QIcon logo; - logo.addFile(":/logos/register.png"); - - logo_ = new QLabel(this); - logo_->setPixmap(logo.pixmap(128)); - - logo_layout_ = new QHBoxLayout(); - logo_layout_->setMargin(0); - logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); - - form_wrapper_ = new QHBoxLayout(); - form_widget_ = new QWidget(); - form_widget_->setMinimumSize(QSize(350, 300)); - - form_layout_ = new QVBoxLayout(); - form_layout_->setSpacing(20); - form_layout_->setContentsMargins(0, 0, 0, 40); - form_widget_->setLayout(form_layout_); - - form_wrapper_->addStretch(1); - form_wrapper_->addWidget(form_widget_); - form_wrapper_->addStretch(1); - - username_input_ = new TextField(); - username_input_->setLabel(tr("Username")); - username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+")); - username_input_->setToolTip(tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); - - password_input_ = new TextField(); - password_input_->setLabel(tr("Password")); - password_input_->setRegexp(QRegularExpression("^.{8,}$")); - password_input_->setEchoMode(QLineEdit::Password); - password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " - "for password strength may depend on your server.")); - - password_confirmation_ = new TextField(); - password_confirmation_->setLabel(tr("Password confirmation")); - password_confirmation_->setEchoMode(QLineEdit::Password); - - server_input_ = new TextField(); - server_input_->setLabel(tr("Homeserver")); - server_input_->setRegexp(QRegularExpression(".+")); - server_input_->setToolTip( - tr("A server that allows registration. Since matrix is decentralized, you need to first " - "find a server you can register on or host your own.")); - - error_username_label_ = new QLabel(this); - error_username_label_->setWordWrap(true); - error_username_label_->hide(); - - error_password_label_ = new QLabel(this); - error_password_label_->setWordWrap(true); - error_password_label_->hide(); - - error_password_confirmation_label_ = new QLabel(this); - error_password_confirmation_label_->setWordWrap(true); - error_password_confirmation_label_->hide(); - - error_server_label_ = new QLabel(this); - error_server_label_->setWordWrap(true); - error_server_label_->hide(); - - form_layout_->addWidget(username_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_username_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_label_, Qt::AlignHCenter); - form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); - form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter); - form_layout_->addWidget(server_input_, Qt::AlignHCenter); - form_layout_->addWidget(error_server_label_, Qt::AlignHCenter); - - button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(0); - button_layout_->setMargin(0); - - error_label_ = new QLabel(this); - error_label_->setWordWrap(true); - - register_button_ = new RaisedButton(tr("REGISTER"), this); - register_button_->setMinimumSize(350, 65); - register_button_->setFontSize(conf::btn::fontSize); - register_button_->setCornerRadius(conf::btn::cornerRadius); - - button_layout_->addStretch(1); - button_layout_->addWidget(register_button_); - button_layout_->addStretch(1); - - top_layout_->addLayout(back_layout_); - top_layout_->addLayout(logo_layout_); - top_layout_->addLayout(form_wrapper_); - top_layout_->addStretch(1); - top_layout_->addLayout(button_layout_); - top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); - top_layout_->addStretch(1); - setLayout(top_layout_); - - connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); - - connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); - connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); - connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_confirmation_, - &TextField::editingFinished, - this, - &RegisterPage::checkPasswordConfirmation); - connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); - - connect( - this, - &RegisterPage::serverError, - this, - [this](const QString &msg) { - server_input_->setValid(false); - showError(error_server_label_, msg); - }, - Qt::QueuedConnection); - - connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); - connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); - connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); - connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); - connect( - this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); + qRegisterMetaType(); + qRegisterMetaType(); + top_layout_ = new QVBoxLayout(); + + back_layout_ = new QHBoxLayout(); + back_layout_->setSpacing(0); + back_layout_->setContentsMargins(5, 5, -1, -1); + + back_button_ = new FlatButton(this); + back_button_->setMinimumSize(QSize(30, 30)); + + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + + back_button_->setIcon(icon); + back_button_->setIconSize(QSize(32, 32)); + + back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter); + back_layout_->addStretch(1); + + QIcon logo; + logo.addFile(":/logos/register.png"); + + logo_ = new QLabel(this); + logo_->setPixmap(logo.pixmap(128)); + + logo_layout_ = new QHBoxLayout(); + logo_layout_->setMargin(0); + logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter); + + form_wrapper_ = new QHBoxLayout(); + form_widget_ = new QWidget(); + form_widget_->setMinimumSize(QSize(350, 300)); + + form_layout_ = new QVBoxLayout(); + form_layout_->setSpacing(20); + form_layout_->setContentsMargins(0, 0, 0, 40); + form_widget_->setLayout(form_layout_); + + form_wrapper_->addStretch(1); + form_wrapper_->addWidget(form_widget_); + form_wrapper_->addStretch(1); + + username_input_ = new TextField(); + username_input_->setLabel(tr("Username")); + username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+")); + username_input_->setToolTip(tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); + + password_input_ = new TextField(); + password_input_->setLabel(tr("Password")); + password_input_->setRegexp(QRegularExpression("^.{8,}$")); + password_input_->setEchoMode(QLineEdit::Password); + password_input_->setToolTip(tr("Please choose a secure password. The exact requirements " + "for password strength may depend on your server.")); + + password_confirmation_ = new TextField(); + password_confirmation_->setLabel(tr("Password confirmation")); + password_confirmation_->setEchoMode(QLineEdit::Password); + + server_input_ = new TextField(); + server_input_->setLabel(tr("Homeserver")); + server_input_->setRegexp(QRegularExpression(".+")); + server_input_->setToolTip( + tr("A server that allows registration. Since matrix is decentralized, you need to first " + "find a server you can register on or host your own.")); + + error_username_label_ = new QLabel(this); + error_username_label_->setWordWrap(true); + error_username_label_->hide(); + + error_password_label_ = new QLabel(this); + error_password_label_->setWordWrap(true); + error_password_label_->hide(); + + error_password_confirmation_label_ = new QLabel(this); + error_password_confirmation_label_->setWordWrap(true); + error_password_confirmation_label_->hide(); + + error_server_label_ = new QLabel(this); + error_server_label_->setWordWrap(true); + error_server_label_->hide(); + + form_layout_->addWidget(username_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_username_label_, Qt::AlignHCenter); + form_layout_->addWidget(password_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_password_label_, Qt::AlignHCenter); + form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); + form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter); + form_layout_->addWidget(server_input_, Qt::AlignHCenter); + form_layout_->addWidget(error_server_label_, Qt::AlignHCenter); + + button_layout_ = new QHBoxLayout(); + button_layout_->setSpacing(0); + button_layout_->setMargin(0); + + error_label_ = new QLabel(this); + error_label_->setWordWrap(true); + + register_button_ = new RaisedButton(tr("REGISTER"), this); + register_button_->setMinimumSize(350, 65); + register_button_->setFontSize(conf::btn::fontSize); + register_button_->setCornerRadius(conf::btn::cornerRadius); + + button_layout_->addStretch(1); + button_layout_->addWidget(register_button_); + button_layout_->addStretch(1); + + top_layout_->addLayout(back_layout_); + top_layout_->addLayout(logo_layout_); + top_layout_->addLayout(form_wrapper_); + top_layout_->addStretch(1); + top_layout_->addLayout(button_layout_); + top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); + top_layout_->addStretch(1); + setLayout(top_layout_); + + connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); + connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); + + connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); + connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); + connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(password_confirmation_, + &TextField::editingFinished, + this, + &RegisterPage::checkPasswordConfirmation); + connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); + connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); + + connect( + this, + &RegisterPage::serverError, + this, + [this](const QString &msg) { + server_input_->setValid(false); + showError(error_server_label_, msg); + }, + Qt::QueuedConnection); + + connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); + connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); + connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); + connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); + connect(this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); } void RegisterPage::onBackButtonClicked() { - emit backButtonClicked(); + emit backButtonClicked(); } void RegisterPage::showError(const QString &msg) { - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - error_label_->setFixedHeight(qCeil(width / 200.0) * height); - error_label_->setText(msg); + emit errorOccurred(); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + error_label_->setFixedHeight(qCeil(width / 200.0) * height); + error_label_->setText(msg); } void RegisterPage::showError(QLabel *label, const QString &msg) { - emit errorOccurred(); - auto rect = QFontMetrics(font()).boundingRect(msg); - int width = rect.width(); - int height = rect.height(); - label->setFixedHeight((int)qCeil(width / 200.0) * height); - label->setText(msg); - label->show(); + emit errorOccurred(); + auto rect = QFontMetrics(font()).boundingRect(msg); + int width = rect.width(); + int height = rect.height(); + label->setFixedHeight((int)qCeil(width / 200.0) * height); + label->setText(msg); + label->show(); } bool RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg) { - if (t_field->isValid()) { - label->hide(); - return true; - } else { - showError(label, msg); - return false; - } + if (t_field->isValid()) { + label->hide(); + return true; + } else { + showError(label, msg); + return false; + } } bool RegisterPage::checkUsername() { - return checkOneField(error_username_label_, - username_input_, - tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /.")); + return checkOneField(error_username_label_, + username_input_, + tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); } bool RegisterPage::checkPassword() { - return checkOneField( - error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); + return checkOneField( + error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); } bool RegisterPage::checkPasswordConfirmation() { - if (password_input_->text() == password_confirmation_->text()) { - error_password_confirmation_label_->hide(); - password_confirmation_->setValid(true); - return true; - } else { - showError(error_password_confirmation_label_, tr("Passwords don't match")); - password_confirmation_->setValid(false); - return false; - } + if (password_input_->text() == password_confirmation_->text()) { + error_password_confirmation_label_->hide(); + password_confirmation_->setValid(true); + return true; + } else { + showError(error_password_confirmation_label_, tr("Passwords don't match")); + password_confirmation_->setValid(false); + return false; + } } bool RegisterPage::checkServer() { - // This doesn't check that the server is reachable, - // just that the input is not obviously wrong. - return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); + // This doesn't check that the server is reachable, + // just that the input is not obviously wrong. + return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); } void RegisterPage::onRegisterButtonClicked() { - if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { - auto server = server_input_->text().toStdString(); - - http::client()->set_server(server); - http::client()->verify_certificates( - !UserSettings::instance()->disableCertificateValidation()); - - // This starts a chain of `emit`s which ends up doing the - // registration. Signals are used rather than normal function - // calls so that the dialogs used in UIA work correctly. - // - // The sequence of events looks something like this: - // - // dowellKnownLookup - // v - // doVersionsCheck - // v - // doRegistration - // v - // doUIA <-----------------+ - // v | More auth required - // doRegistrationWithAuth -+ - // | Success - // v - // registering - - emit wellKnownLookup(); - - emit registering(); - } + if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { + auto server = server_input_->text().toStdString(); + + http::client()->set_server(server); + http::client()->verify_certificates( + !UserSettings::instance()->disableCertificateValidation()); + + // This starts a chain of `emit`s which ends up doing the + // registration. Signals are used rather than normal function + // calls so that the dialogs used in UIA work correctly. + // + // The sequence of events looks something like this: + // + // dowellKnownLookup + // v + // doVersionsCheck + // v + // doRegistration + // v + // doUIA <-----------------+ + // v | More auth required + // doRegistrationWithAuth -+ + // | Success + // v + // registering + + emit wellKnownLookup(); + + emit registering(); + } } void RegisterPage::doWellKnownLookup() { - http::client()->well_known( - [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - nhlog::net()->info("Autodiscovery: No .well-known."); - // Check that the homeserver can be reached - emit versionsCheck(); - return; - } - - if (!err->parse_error.empty()) { - emit serverError( - tr("Autodiscovery failed. Received malformed response.")); - nhlog::net()->error( - "Autodiscovery failed. Received malformed response."); - return; - } - - emit serverError(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); - nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known. {} {}", - err->status_code, - err->error_code); - return; - } - - nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); - http::client()->set_server(res.homeserver.base_url); + http::client()->well_known( + [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + nhlog::net()->info("Autodiscovery: No .well-known."); // Check that the homeserver can be reached emit versionsCheck(); - }); + return; + } + + if (!err->parse_error.empty()) { + emit serverError(tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error("Autodiscovery failed. Received malformed response."); + return; + } + + emit serverError(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known. {} {}", + err->status_code, + err->error_code); + return; + } + + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); + http::client()->set_server(res.homeserver.base_url); + // Check that the homeserver can be reached + emit versionsCheck(); + }); } void RegisterPage::doVersionsCheck() { - // Make a request to /_matrix/client/versions to check the address - // given is a Matrix homeserver. - http::client()->versions( - [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - emit serverError( - tr("The required endpoints were not found. Possibly " - "not a Matrix server.")); - return; - } - - if (!err->parse_error.empty()) { - emit serverError( - tr("Received malformed response. Make sure the homeserver " - "domain is valid.")); - return; - } - - emit serverError(tr("An unknown error occured. Make sure the " - "homeserver domain is valid.")); - return; - } - - // Attempt registration without an `auth` dict - emit registration(); - }); + // Make a request to /_matrix/client/versions to check the address + // given is a Matrix homeserver. + http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + emit serverError(tr("The required endpoints were not found. Possibly " + "not a Matrix server.")); + return; + } + + if (!err->parse_error.empty()) { + emit serverError(tr("Received malformed response. Make sure the homeserver " + "domain is valid.")); + return; + } + + emit serverError(tr("An unknown error occured. Make sure the " + "homeserver domain is valid.")); + return; + } + + // Attempt registration without an `auth` dict + emit registration(); + }); } void RegisterPage::doRegistration() { - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, registrationCb()); - } + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, registrationCb()); + } } void RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth) { - // These inputs should still be alright, but check just in case - if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - http::client()->registration(username, password, auth, registrationCb()); - } + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, auth, registrationCb()); + } } mtx::http::Callback RegisterPage::registrationCb() { - // Return a function to be used as the callback when an attempt at - // registration is made. - return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { - if (!err) { - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); - emit registerOk(); - return; - } - - // The server requires registration flows. - if (err->status_code == 401) { - if (err->matrix_error.unauthorized.flows.empty()) { - nhlog::net()->warn("failed to retrieve registration flows: " - "status_code({}), matrix_error({}) ", - static_cast(err->status_code), - err->matrix_error.error); - showError(QString::fromStdString(err->matrix_error.error)); - return; - } - - // Attempt to complete a UIA stage - emit UIA(err->matrix_error.unauthorized); - return; - } - - nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", - static_cast(err->status_code), - err->matrix_error.error); + // Return a function to be used as the callback when an attempt at + // registration is made. + return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { + if (!err) { + http::client()->set_user(res.user_id); + http::client()->set_access_token(res.access_token); + emit registerOk(); + return; + } + // The server requires registration flows. + if (err->status_code == 401) { + if (err->matrix_error.unauthorized.flows.empty()) { + nhlog::net()->warn("failed to retrieve registration flows: " + "status_code({}), matrix_error({}) ", + static_cast(err->status_code), + err->matrix_error.error); showError(QString::fromStdString(err->matrix_error.error)); - }; + return; + } + + // Attempt to complete a UIA stage + emit UIA(err->matrix_error.unauthorized); + return; + } + + nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", + static_cast(err->status_code), + err->matrix_error.error); + + showError(QString::fromStdString(err->matrix_error.error)); + }; } void RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) { - auto completed_stages = unauthorized.completed; - auto flows = unauthorized.flows; - auto session = - unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; - - nhlog::ui()->info("Completed stages: {}", completed_stages.size()); - - if (!completed_stages.empty()) { - // Get rid of all flows which don't start with the sequence of - // stages that have already been completed. - flows.erase( - std::remove_if(flows.begin(), - flows.end(), - [completed_stages](auto flow) { - if (completed_stages.size() > flow.stages.size()) - return true; - for (size_t f = 0; f < completed_stages.size(); f++) - if (completed_stages[f] != flow.stages[f]) - return true; - return false; - }), - flows.end()); - } + auto completed_stages = unauthorized.completed; + auto flows = unauthorized.flows; + auto session = + unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; + + nhlog::ui()->info("Completed stages: {}", completed_stages.size()); + + if (!completed_stages.empty()) { + // Get rid of all flows which don't start with the sequence of + // stages that have already been completed. + flows.erase(std::remove_if(flows.begin(), + flows.end(), + [completed_stages](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); + } + + if (flows.empty()) { + nhlog::ui()->error("No available registration flows!"); + showError(tr("No supported registration flows!")); + return; + } + + auto current_stage = flows.front().stages.at(completed_stages.size()); + + if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); - if (flows.empty()) { - nhlog::ui()->error("No available registration flows!"); - showError(tr("No supported registration flows!")); - return; - } + connect( + captchaDialog, &dialogs::ReCaptcha::confirmation, this, [this, session, captchaDialog]() { + captchaDialog->close(); + captchaDialog->deleteLater(); + doRegistrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); + }); - auto current_stage = flows.front().stages.at(completed_stages.size()); - - if (current_stage == mtx::user_interactive::auth_types::recaptcha) { - auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); - - connect(captchaDialog, - &dialogs::ReCaptcha::confirmation, - this, - [this, session, captchaDialog]() { - captchaDialog->close(); - captchaDialog->deleteLater(); - doRegistrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); - - connect( - captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); - - QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); - - } else if (current_stage == mtx::user_interactive::auth_types::dummy) { - doRegistrationWithAuth( - mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); - - } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { - bool ok; - QString token = - QInputDialog::getText(this, - tr("Registration token"), - tr("Please enter a valid registration token."), - QLineEdit::Normal, - QString(), - &ok); - - if (ok) { - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, - mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); - } else { - emit errorOccurred(); - } - } else { - // use fallback - auto dialog = new dialogs::FallbackAuth( - QString::fromStdString(current_stage), QString::fromStdString(session), this); + connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); - connect( - dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { - dialog->close(); - dialog->deleteLater(); - emit registrationWithAuth(mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); + QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); - connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + doRegistrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); - dialog->show(); + } else if (current_stage == mtx::user_interactive::auth_types::registration_token) { + bool ok; + QString token = QInputDialog::getText(this, + tr("Registration token"), + tr("Please enter a valid registration token."), + QLineEdit::Normal, + QString(), + &ok); + + if (ok) { + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}}); + } else { + emit errorOccurred(); } + } else { + // use fallback + auto dialog = new dialogs::FallbackAuth( + QString::fromStdString(current_stage), QString::fromStdString(session), this); + + connect(dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { + dialog->close(); + dialog->deleteLater(); + emit registrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}}); + }); + + connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); + + dialog->show(); + } } void RegisterPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 42ea00cb..b88808f9 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -21,76 +21,76 @@ class QHBoxLayout; class RegisterPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - RegisterPage(QWidget *parent = nullptr); + RegisterPage(QWidget *parent = nullptr); protected: - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent *event) override; signals: - void backButtonClicked(); - void errorOccurred(); + void backButtonClicked(); + void errorOccurred(); - //! Used to trigger the corresponding slot outside of the main thread. - void serverError(const QString &err); + //! Used to trigger the corresponding slot outside of the main thread. + void serverError(const QString &err); - void wellKnownLookup(); - void versionsCheck(); - void registration(); - void UIA(const mtx::user_interactive::Unauthorized &unauthorized); - void registrationWithAuth(const mtx::user_interactive::Auth &auth); + void wellKnownLookup(); + void versionsCheck(); + void registration(); + void UIA(const mtx::user_interactive::Unauthorized &unauthorized); + void registrationWithAuth(const mtx::user_interactive::Auth &auth); - void registering(); - void registerOk(); + void registering(); + void registerOk(); private slots: - void onBackButtonClicked(); - void onRegisterButtonClicked(); - - // function for showing different errors - void showError(const QString &msg); - void showError(QLabel *label, const QString &msg); - - bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); - bool checkUsername(); - bool checkPassword(); - bool checkPasswordConfirmation(); - bool checkServer(); - - void doWellKnownLookup(); - void doVersionsCheck(); - void doRegistration(); - void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); - void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); - mtx::http::Callback registrationCb(); + void onBackButtonClicked(); + void onRegisterButtonClicked(); + + // function for showing different errors + void showError(const QString &msg); + void showError(QLabel *label, const QString &msg); + + bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); + bool checkUsername(); + bool checkPassword(); + bool checkPasswordConfirmation(); + bool checkServer(); + + void doWellKnownLookup(); + void doVersionsCheck(); + void doRegistration(); + void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); + void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); + mtx::http::Callback registrationCb(); private: - QVBoxLayout *top_layout_; - - QHBoxLayout *back_layout_; - QHBoxLayout *logo_layout_; - QHBoxLayout *button_layout_; - - QLabel *logo_; - QLabel *error_label_; - QLabel *error_username_label_; - QLabel *error_password_label_; - QLabel *error_password_confirmation_label_; - QLabel *error_server_label_; - QLabel *error_registration_token_label_; - - FlatButton *back_button_; - RaisedButton *register_button_; - - QWidget *form_widget_; - QHBoxLayout *form_wrapper_; - QVBoxLayout *form_layout_; - - TextField *username_input_; - TextField *password_input_; - TextField *password_confirmation_; - TextField *server_input_; - TextField *registration_token_input_; + QVBoxLayout *top_layout_; + + QHBoxLayout *back_layout_; + QHBoxLayout *logo_layout_; + QHBoxLayout *button_layout_; + + QLabel *logo_; + QLabel *error_label_; + QLabel *error_username_label_; + QLabel *error_password_label_; + QLabel *error_password_confirmation_label_; + QLabel *error_server_label_; + QLabel *error_registration_token_label_; + + FlatButton *back_button_; + RaisedButton *register_button_; + + QWidget *form_widget_; + QHBoxLayout *form_wrapper_; + QVBoxLayout *form_layout_; + + TextField *username_input_; + TextField *password_input_; + TextField *password_confirmation_; + TextField *server_input_; + TextField *registration_token_input_; }; diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp index cfa2b623..707571d6 100644 --- a/src/RoomDirectoryModel.cpp +++ b/src/RoomDirectoryModel.cpp @@ -12,207 +12,205 @@ RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &serve : QAbstractListModel(parent) , server_(server) { - connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { - auto roomid_ = roomid.toStdString(); - - int i = 0; - for (const auto &room : publicRoomsData_) { - if (room.room_id == roomid_) { - emit dataChanged(index(i), index(i), {Roles::CanJoin}); - break; - } - i++; - } - }); - - connect(this, - &RoomDirectoryModel::fetchedRoomsBatch, - this, - &RoomDirectoryModel::displayRooms, - Qt::QueuedConnection); + connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) { + auto roomid_ = roomid.toStdString(); + + int i = 0; + for (const auto &room : publicRoomsData_) { + if (room.room_id == roomid_) { + emit dataChanged(index(i), index(i), {Roles::CanJoin}); + break; + } + i++; + } + }); + + connect(this, + &RoomDirectoryModel::fetchedRoomsBatch, + this, + &RoomDirectoryModel::displayRooms, + Qt::QueuedConnection); } QHash RoomDirectoryModel::roleNames() const { - return { - {Roles::Name, "name"}, - {Roles::Id, "roomid"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::Topic, "topic"}, - {Roles::MemberCount, "numMembers"}, - {Roles::Previewable, "canPreview"}, - {Roles::CanJoin, "canJoin"}, - }; + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"}, + {Roles::CanJoin, "canJoin"}, + }; } void RoomDirectoryModel::resetDisplayedData() { - beginResetModel(); + beginResetModel(); - prevBatch_ = ""; - nextBatch_ = ""; - canFetchMore_ = true; + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; - publicRoomsData_.clear(); + publicRoomsData_.clear(); - endResetModel(); + endResetModel(); } void RoomDirectoryModel::setMatrixServer(const QString &s) { - server_ = s.toStdString(); + server_ = s.toStdString(); - nhlog::ui()->debug("Received matrix server: {}", server_); + nhlog::ui()->debug("Received matrix server: {}", server_); - resetDisplayedData(); + resetDisplayedData(); } void RoomDirectoryModel::setSearchTerm(const QString &f) { - userSearchString_ = f.toStdString(); + userSearchString_ = f.toStdString(); - nhlog::ui()->debug("Received user query: {}", userSearchString_); + nhlog::ui()->debug("Received user query: {}", userSearchString_); - resetDisplayedData(); + resetDisplayedData(); } bool RoomDirectoryModel::canJoinRoom(const QString &room) const { - return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); + return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty(); } std::vector RoomDirectoryModel::getViasForRoom(const std::vector &aliases) { - std::vector vias; - - vias.reserve(aliases.size()); - - std::transform(aliases.begin(), - aliases.end(), - std::back_inserter(vias), - [](const auto &alias) { return alias.substr(alias.find(":") + 1); }); - - // When joining a room hosted on a homeserver other than the one the - // account has been registered on, the room's server has to be explicitly - // specified in the "server_name=..." URL parameter of the Matrix Join Room - // request. For more details consult the specs: - // https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias - if (!server_.empty()) { - vias.push_back(server_); - } + std::vector vias; + + vias.reserve(aliases.size()); + + std::transform(aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) { + return alias.substr(alias.find(":") + 1); + }); - return vias; + // When joining a room hosted on a homeserver other than the one the + // account has been registered on, the room's server has to be explicitly + // specified in the "server_name=..." URL parameter of the Matrix Join Room + // request. For more details consult the specs: + // https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias + if (!server_.empty()) { + vias.push_back(server_); + } + + return vias; } void RoomDirectoryModel::joinRoom(const int &index) { - if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { - const auto &chunk = publicRoomsData_[index]; - nhlog::ui()->debug("'Joining room {}", chunk.room_id); - ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); - } + if (index >= 0 && static_cast(index) < publicRoomsData_.size()) { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } } QVariant RoomDirectoryModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &room_chunk = publicRoomsData_[index.row()]; - switch (role) { - case Roles::Name: - return QString::fromStdString(room_chunk.name); - case Roles::Id: - return QString::fromStdString(room_chunk.room_id); - case Roles::AvatarUrl: - return QString::fromStdString(room_chunk.avatar_url); - case Roles::Topic: - return QString::fromStdString(room_chunk.topic); - case Roles::MemberCount: - return QVariant::fromValue(room_chunk.num_joined_members); - case Roles::Previewable: - return QVariant::fromValue(room_chunk.world_readable); - case Roles::CanJoin: - return canJoinRoom(QString::fromStdString(room_chunk.room_id)); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + case Roles::CanJoin: + return canJoinRoom(QString::fromStdString(room_chunk.room_id)); } - return {}; + } + return {}; } void RoomDirectoryModel::fetchMore(const QModelIndex &) { - if (!canFetchMore_) - return; - - nhlog::net()->debug("Fetching more rooms from mtxclient..."); - - mtx::requests::PublicRooms req; - req.limit = limit_; - req.since = prevBatch_; - req.filter.generic_search_term = userSearchString_; - // req.third_party_instance_id = third_party_instance_id; - auto requested_server = server_; - - reachedEndOfPagination_ = false; - emit reachedEndOfPaginationChanged(); - - loadingMoreRooms_ = true; - emit loadingMoreRoomsChanged(); - - http::client()->post_public_rooms( - req, - [requested_server, this, req](const mtx::responses::PublicRooms &res, - mtx::http::RequestErr err) { - loadingMoreRooms_ = false; - emit loadingMoreRoomsChanged(); - - if (err) { - nhlog::net()->error( - "Failed to retrieve rooms from mtxclient - {} - {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error, - err->parse_error); - } else if (req.filter.generic_search_term == this->userSearchString_ && - req.since == this->prevBatch_ && requested_server == this->server_) { - nhlog::net()->debug("signalling chunk to GUI thread"); - emit fetchedRoomsBatch(res.chunk, res.next_batch); - } - }, - requested_server); + if (!canFetchMore_) + return; + + nhlog::net()->debug("Fetching more rooms from mtxclient..."); + + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; + + reachedEndOfPagination_ = false; + emit reachedEndOfPaginationChanged(); + + loadingMoreRooms_ = true; + emit loadingMoreRoomsChanged(); + + http::client()->post_public_rooms( + req, + [requested_server, this, req](const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) { + loadingMoreRooms_ = false; + emit loadingMoreRoomsChanged(); + + if (err) { + nhlog::net()->error("Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if (req.filter.generic_search_term == this->userSearchString_ && + req.since == this->prevBatch_ && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.next_batch); + } + }, + requested_server); } void RoomDirectoryModel::displayRooms(std::vector fetched_rooms, const std::string &next_batch) { - nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); - - if (fetched_rooms.empty()) { - nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); - return; - } - - beginInsertRows(QModelIndex(), - static_cast(publicRoomsData_.size()), - static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); - this->publicRoomsData_.insert( - this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); - endInsertRows(); - - if (next_batch.empty()) { - canFetchMore_ = false; - reachedEndOfPagination_ = true; - emit reachedEndOfPaginationChanged(); - } + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); + + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } + + beginInsertRows(QModelIndex(), + static_cast(publicRoomsData_.size()), + static_cast(publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert( + this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); + + if (next_batch.empty()) { + canFetchMore_ = false; + reachedEndOfPagination_ = true; + emit reachedEndOfPaginationChanged(); + } - prevBatch_ = next_batch; + prevBatch_ = next_batch; - nhlog::ui()->debug("Finished loading rooms"); + nhlog::ui()->debug("Finished loading rooms"); } diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h index 80c04612..4699474b 100644 --- a/src/RoomDirectoryModel.h +++ b/src/RoomDirectoryModel.h @@ -25,74 +25,74 @@ struct PublicRooms; class RoomDirectoryModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT - Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) - Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY - reachedEndOfPaginationChanged) + Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) + Q_PROPERTY( + bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged) public: - explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); + explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = ""); - enum Roles - { - Name = Qt::UserRole, - Id, - AvatarUrl, - Topic, - MemberCount, - Previewable, - CanJoin, - }; - QHash roleNames() const override; + enum Roles + { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable, + CanJoin, + }; + QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; - inline int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return static_cast(publicRoomsData_.size()); - } + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return static_cast(publicRoomsData_.size()); + } - bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } - bool loadingMoreRooms() const { return loadingMoreRooms_; } + bool loadingMoreRooms() const { return loadingMoreRooms_; } - bool reachedEndOfPagination() const { return reachedEndOfPagination_; } + bool reachedEndOfPagination() const { return reachedEndOfPagination_; } - void fetchMore(const QModelIndex &) override; + void fetchMore(const QModelIndex &) override; - Q_INVOKABLE void joinRoom(const int &index = -1); + Q_INVOKABLE void joinRoom(const int &index = -1); signals: - void fetchedRoomsBatch(std::vector rooms, - const std::string &next_batch); - void loadingMoreRoomsChanged(); - void reachedEndOfPaginationChanged(); + void fetchedRoomsBatch(std::vector rooms, + const std::string &next_batch); + void loadingMoreRoomsChanged(); + void reachedEndOfPaginationChanged(); public slots: - void setMatrixServer(const QString &s = ""); - void setSearchTerm(const QString &f); + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); private slots: - void displayRooms(std::vector rooms, - const std::string &next_batch); + void displayRooms(std::vector rooms, + const std::string &next_batch); private: - bool canJoinRoom(const QString &room) const; + bool canJoinRoom(const QString &room) const; - static constexpr size_t limit_ = 50; + static constexpr size_t limit_ = 50; - std::string server_; - std::string userSearchString_; - std::string prevBatch_; - std::string nextBatch_; - bool canFetchMore_{true}; - bool loadingMoreRooms_{false}; - bool reachedEndOfPagination_{false}; - std::vector publicRoomsData_; + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_{true}; + bool loadingMoreRooms_{false}; + bool reachedEndOfPagination_{false}; + std::vector publicRoomsData_; - std::vector getViasForRoom(const std::vector &room); - void resetDisplayedData(); + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); }; diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index 656a0deb..8c05b7bb 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -14,71 +14,67 @@ RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent) : QAbstractListModel(parent) , showOnlyRoomWithAliases_(showOnlyRoomWithAliases) { - std::vector rooms_ = cache::joinedRooms(); - roomInfos = cache::getRoomInfo(rooms_); - if (!showOnlyRoomWithAliases_) { - roomids.reserve(rooms_.size()); - roomAliases.reserve(rooms_.size()); - } + std::vector rooms_ = cache::joinedRooms(); + roomInfos = cache::getRoomInfo(rooms_); + if (!showOnlyRoomWithAliases_) { + roomids.reserve(rooms_.size()); + roomAliases.reserve(rooms_.size()); + } - for (const auto &r : rooms_) { - auto roomAliasesList = cache::client()->getRoomAliases(r); + for (const auto &r : rooms_) { + auto roomAliasesList = cache::client()->getRoomAliases(r); - if (showOnlyRoomWithAliases_) { - if (roomAliasesList && !roomAliasesList->alias.empty()) { - roomids.push_back(QString::fromStdString(r)); - roomAliases.push_back( - QString::fromStdString(roomAliasesList->alias)); - } - } else { - roomids.push_back(QString::fromStdString(r)); - roomAliases.push_back( - roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : ""); - } + if (showOnlyRoomWithAliases_) { + if (roomAliasesList && !roomAliasesList->alias.empty()) { + roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back(QString::fromStdString(roomAliasesList->alias)); + } + } else { + roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back(roomAliasesList ? QString::fromStdString(roomAliasesList->alias) + : ""); } + } } QHash RoomsModel::roleNames() const { - return {{CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::RoomAlias, "roomAlias"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::RoomID, "roomid"}, - {Roles::RoomName, "roomName"}}; + return {{CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::RoomAlias, "roomAlias"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::RoomID, "roomid"}, + {Roles::RoomName, "roomName"}}; } QVariant RoomsModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: { - if (UserSettings::instance()->markdown()) { - QString percentEncoding = - QUrl::toPercentEncoding(roomAliases[index.row()]); - return QString("[%1](https://matrix.to/#/%2)") - .arg(roomAliases[index.row()], percentEncoding); - } else { - return roomAliases[index.row()]; - } - } - case CompletionModel::SearchRole: - case Qt::DisplayRole: - case Roles::RoomAlias: - return roomAliases[index.row()].toHtmlEscaped(); - case CompletionModel::SearchRole2: - case Roles::RoomName: - return QString::fromStdString(roomInfos.at(roomids[index.row()]).name) - .toHtmlEscaped(); - case Roles::AvatarUrl: - return QString::fromStdString( - roomInfos.at(roomids[index.row()]).avatar_url); - case Roles::RoomID: - return roomids[index.row()].toHtmlEscaped(); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: { + if (UserSettings::instance()->markdown()) { + QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]); + return QString("[%1](https://matrix.to/#/%2)") + .arg(roomAliases[index.row()], percentEncoding); + } else { + return roomAliases[index.row()]; + } + } + case CompletionModel::SearchRole: + case Qt::DisplayRole: + case Roles::RoomAlias: + return roomAliases[index.row()].toHtmlEscaped(); + case CompletionModel::SearchRole2: + case Roles::RoomName: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).name).toHtmlEscaped(); + case Roles::AvatarUrl: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).avatar_url); + case Roles::RoomID: + return roomids[index.row()].toHtmlEscaped(); } - return {}; + } + return {}; } diff --git a/src/RoomsModel.h b/src/RoomsModel.h index 255f207c..b6e29974 100644 --- a/src/RoomsModel.h +++ b/src/RoomsModel.h @@ -12,26 +12,26 @@ class RoomsModel : public QAbstractListModel { public: - enum Roles - { - AvatarUrl = Qt::UserRole, - RoomAlias, - RoomID, - RoomName, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + RoomAlias, + RoomID, + RoomName, + }; - RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return (int)roomids.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomids.size(); + } + QVariant data(const QModelIndex &index, int role) const override; private: - std::vector roomids; - std::vector roomAliases; - std::map roomInfos; - bool showOnlyRoomWithAliases_; + std::vector roomids; + std::vector roomAliases; + std::map roomInfos; + bool showOnlyRoomWithAliases_; }; diff --git a/src/SSOHandler.cpp b/src/SSOHandler.cpp index 8fd0828c..a6f7ba11 100644 --- a/src/SSOHandler.cpp +++ b/src/SSOHandler.cpp @@ -12,46 +12,46 @@ SSOHandler::SSOHandler(QObject *) { - QTimer::singleShot(120000, this, &SSOHandler::ssoFailed); - - using namespace httplib; - - svr.set_logger([](const Request &req, const Response &res) { - nhlog::net()->info("req: {}, res: {}", req.path, res.status); - }); - - svr.Get("/sso", [this](const Request &req, Response &res) { - if (req.has_param("loginToken")) { - auto val = req.get_param_value("loginToken"); - res.set_content("SSO success", "text/plain"); - emit ssoSuccess(val); - } else { - res.set_content("Missing loginToken for SSO login!", "text/plain"); - emit ssoFailed(); - } - }); - - std::thread t([this]() { - this->port = svr.bind_to_any_port("localhost"); - svr.listen_after_bind(); - }); - t.detach(); - - while (!svr.is_running()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + QTimer::singleShot(120000, this, &SSOHandler::ssoFailed); + + using namespace httplib; + + svr.set_logger([](const Request &req, const Response &res) { + nhlog::net()->info("req: {}, res: {}", req.path, res.status); + }); + + svr.Get("/sso", [this](const Request &req, Response &res) { + if (req.has_param("loginToken")) { + auto val = req.get_param_value("loginToken"); + res.set_content("SSO success", "text/plain"); + emit ssoSuccess(val); + } else { + res.set_content("Missing loginToken for SSO login!", "text/plain"); + emit ssoFailed(); } + }); + + std::thread t([this]() { + this->port = svr.bind_to_any_port("localhost"); + svr.listen_after_bind(); + }); + t.detach(); + + while (!svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } } SSOHandler::~SSOHandler() { - svr.stop(); - while (svr.is_running()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } + svr.stop(); + while (svr.is_running()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } } std::string SSOHandler::url() const { - return "http://localhost:" + std::to_string(port) + "/sso"; + return "http://localhost:" + std::to_string(port) + "/sso"; } diff --git a/src/SSOHandler.h b/src/SSOHandler.h index bd0d424d..ab652a06 100644 --- a/src/SSOHandler.h +++ b/src/SSOHandler.h @@ -9,20 +9,20 @@ class SSOHandler : public QObject { - Q_OBJECT + Q_OBJECT public: - SSOHandler(QObject *parent = nullptr); + SSOHandler(QObject *parent = nullptr); - ~SSOHandler(); + ~SSOHandler(); - std::string url() const; + std::string url() const; signals: - void ssoSuccess(std::string token); - void ssoFailed(); + void ssoSuccess(std::string token); + void ssoFailed(); private: - httplib::Server svr; - int port = 0; + httplib::Server svr; + int port = 0; }; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 6d0f0ad9..978a0480 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -24,344 +24,336 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { - [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); + [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); - if (!pack.pack) - pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; + if (!pack.pack) + pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; - for (const auto &e : pack.images) - shortcodes.push_back(e.first); + for (const auto &e : pack.images) + shortcodes.push_back(e.first); - connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); + connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); } int SingleImagePackModel::rowCount(const QModelIndex &) const { - return (int)shortcodes.size(); + return (int)shortcodes.size(); } QHash SingleImagePackModel::roleNames() const { - return { - {Roles::Url, "url"}, - {Roles::ShortCode, "shortCode"}, - {Roles::Body, "body"}, - {Roles::IsEmote, "isEmote"}, - {Roles::IsSticker, "isSticker"}, - }; + return { + {Roles::Url, "url"}, + {Roles::ShortCode, "shortCode"}, + {Roles::Body, "body"}, + {Roles::IsEmote, "isEmote"}, + {Roles::IsSticker, "isSticker"}, + }; } QVariant SingleImagePackModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - const auto &img = pack.images.at(shortcodes.at(index.row())); - switch (role) { - case Url: - return QString::fromStdString(img.url); - case ShortCode: - return QString::fromStdString(shortcodes.at(index.row())); - case Body: - return QString::fromStdString(img.body); - case IsEmote: - return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); - case IsSticker: - return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); - default: - return {}; - } + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case Url: + return QString::fromStdString(img.url); + case ShortCode: + return QString::fromStdString(shortcodes.at(index.row())); + case Body: + return QString::fromStdString(img.body); + case IsEmote: + return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + case IsSticker: + return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + default: + return {}; } - return {}; + } + return {}; } bool SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role) { - using mtx::events::msc2545::PackUsage; - - if (hasIndex(index.row(), index.column(), index.parent())) { - auto &img = pack.images.at(shortcodes.at(index.row())); - switch (role) { - case ShortCode: { - auto newCode = value.toString().toStdString(); - - // otherwise we delete this by accident - if (pack.images.count(newCode)) - return false; - - auto tmp = img; - auto oldCode = shortcodes.at(index.row()); - pack.images.erase(oldCode); - shortcodes[index.row()] = newCode; - pack.images.insert({newCode, tmp}); - - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); - return true; - } - case Body: - img.body = value.toString().toStdString(); - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::Body}); - return true; - case IsEmote: { - bool isEmote = value.toBool(); - bool isSticker = - img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); - - img.usage.set(PackUsage::Emoji, isEmote); - img.usage.set(PackUsage::Sticker, isSticker); - - if (img.usage == pack.pack->usage) - img.usage.reset(); - - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); - - return true; - } - case IsSticker: { - bool isEmote = - img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); - bool isSticker = value.toBool(); - - img.usage.set(PackUsage::Emoji, isEmote); - img.usage.set(PackUsage::Sticker, isSticker); - - if (img.usage == pack.pack->usage) - img.usage.reset(); - - emit dataChanged( - this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); - - return true; - } - } + using mtx::events::msc2545::PackUsage; + + if (hasIndex(index.row(), index.column(), index.parent())) { + auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case ShortCode: { + auto newCode = value.toString().toStdString(); + + // otherwise we delete this by accident + if (pack.images.count(newCode)) + return false; + + auto tmp = img; + auto oldCode = shortcodes.at(index.row()); + pack.images.erase(oldCode); + shortcodes[index.row()] = newCode; + pack.images.insert({newCode, tmp}); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); + return true; } - return false; + case Body: + img.body = value.toString().toStdString(); + emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::Body}); + return true; + case IsEmote: { + bool isEmote = value.toBool(); + bool isSticker = img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); + + return true; + } + case IsSticker: { + bool isEmote = img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + bool isSticker = value.toBool(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); + + return true; + } + } + } + return false; } bool SingleImagePackModel::isGloballyEnabled() const { - if (auto roomPacks = - cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { - if (auto tmp = std::get_if< - mtx::events::EphemeralEvent>( - &*roomPacks)) { - if (tmp->content.rooms.count(roomid_) && - tmp->content.rooms.at(roomid_).count(statekey_)) - return true; - } + if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = + std::get_if>( + &*roomPacks)) { + if (tmp->content.rooms.count(roomid_) && + tmp->content.rooms.at(roomid_).count(statekey_)) + return true; } - return false; + } + return false; } void SingleImagePackModel::setGloballyEnabled(bool enabled) { - mtx::events::msc2545::ImagePackRooms content{}; - if (auto roomPacks = - cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { - if (auto tmp = std::get_if< - mtx::events::EphemeralEvent>( - &*roomPacks)) { - content = tmp->content; - } + mtx::events::msc2545::ImagePackRooms content{}; + if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = + std::get_if>( + &*roomPacks)) { + content = tmp->content; } + } - if (enabled) - content.rooms[roomid_][statekey_] = {}; - else - content.rooms[roomid_].erase(statekey_); + if (enabled) + content.rooms[roomid_][statekey_] = {}; + else + content.rooms[roomid_].erase(statekey_); - http::client()->put_account_data(content, [](mtx::http::RequestErr) { - // emit this->globallyEnabledChanged(); - }); + http::client()->put_account_data(content, [](mtx::http::RequestErr) { + // emit this->globallyEnabledChanged(); + }); } bool SingleImagePackModel::canEdit() const { - if (roomid_.empty()) - return true; - else - return Permissions(QString::fromStdString(roomid_)) - .canChange(qml_mtx_events::ImagePackInRoom); + if (roomid_.empty()) + return true; + else + return Permissions(QString::fromStdString(roomid_)) + .canChange(qml_mtx_events::ImagePackInRoom); } void SingleImagePackModel::setPackname(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->display_name) { - this->pack.pack->display_name = val_; - emit packnameChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->display_name) { + this->pack.pack->display_name = val_; + emit packnameChanged(); + } } void SingleImagePackModel::setAttribution(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->attribution) { - this->pack.pack->attribution = val_; - emit attributionChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->attribution) { + this->pack.pack->attribution = val_; + emit attributionChanged(); + } } void SingleImagePackModel::setAvatarUrl(QString val) { - auto val_ = val.toStdString(); - if (val_ != this->pack.pack->avatar_url) { - this->pack.pack->avatar_url = val_; - emit avatarUrlChanged(); - } + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->avatar_url) { + this->pack.pack->avatar_url = val_; + emit avatarUrlChanged(); + } } void SingleImagePackModel::setStatekey(QString val) { - auto val_ = val.toStdString(); - if (val_ != statekey_) { - statekey_ = val_; - emit statekeyChanged(); - } + auto val_ = val.toStdString(); + if (val_ != statekey_) { + statekey_ = val_; + emit statekeyChanged(); + } } void SingleImagePackModel::setIsStickerPack(bool val) { - using mtx::events::msc2545::PackUsage; - if (val != pack.pack->is_sticker()) { - pack.pack->usage.set(PackUsage::Sticker, val); - emit isStickerPackChanged(); - } + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_sticker()) { + pack.pack->usage.set(PackUsage::Sticker, val); + emit isStickerPackChanged(); + } } void SingleImagePackModel::setIsEmotePack(bool val) { - using mtx::events::msc2545::PackUsage; - if (val != pack.pack->is_emoji()) { - pack.pack->usage.set(PackUsage::Emoji, val); - emit isEmotePackChanged(); - } + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_emoji()) { + pack.pack->usage.set(PackUsage::Emoji, val); + emit isEmotePackChanged(); + } } void SingleImagePackModel::save() { - if (roomid_.empty()) { - http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to update image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - }); - } else { - if (old_statekey_ != statekey_) { - http::client()->send_state_event( - roomid_, - to_string(mtx::events::EventType::ImagePackInRoom), - old_statekey_, - nlohmann::json::object(), - [](const mtx::responses::EventId &, mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to delete old image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - }); - } - - http::client()->send_state_event( - roomid_, - statekey_, - pack, - [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { - if (e) - ChatPage::instance()->showNotification( - tr("Failed to update image pack: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - - nhlog::net()->info("Uploaded image pack: %1", statekey_); - }); + if (roomid_.empty()) { + http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } else { + if (old_statekey_ != statekey_) { + http::client()->send_state_event( + roomid_, + to_string(mtx::events::EventType::ImagePackInRoom), + old_statekey_, + nlohmann::json::object(), + [](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to delete old image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + }); } + + http::client()->send_state_event( + roomid_, + statekey_, + pack, + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + + nhlog::net()->info("Uploaded image pack: %1", statekey_); + }); + } } void SingleImagePackModel::addStickers(QList files) { - for (const auto &f : files) { - auto file = QFile(f.toLocalFile()); - if (!file.open(QFile::ReadOnly)) { - ChatPage::instance()->showNotification( - tr("Failed to open image: %1").arg(f.toLocalFile())); - return; - } - - auto bytes = file.readAll(); - auto img = utils::readImage(bytes); - - mtx::common::ImageInfo info{}; - - auto sz = img.size() / 2; - if (sz.width() > 512 || sz.height() > 512) { - sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); - } else if (img.height() < 128 && img.width() < 128) { - sz = img.size(); - } - - info.h = sz.height(); - info.w = sz.width(); - info.size = bytes.size(); - info.mimetype = - QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(); - - auto filename = f.fileName().toStdString(); - http::client()->upload( - bytes.toStdString(), - QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), - filename, - [this, filename, info](const mtx::responses::ContentURI &uri, - mtx::http::RequestErr e) { - if (e) { - ChatPage::instance()->showNotification( - tr("Failed to upload image: %1") - .arg(QString::fromStdString(e->matrix_error.error))); - return; - } - - emit addImage(uri.content_uri, filename, info); - }); + for (const auto &f : files) { + auto file = QFile(f.toLocalFile()); + if (!file.open(QFile::ReadOnly)) { + ChatPage::instance()->showNotification( + tr("Failed to open image: %1").arg(f.toLocalFile())); + return; } + + auto bytes = file.readAll(); + auto img = utils::readImage(bytes); + + mtx::common::ImageInfo info{}; + + auto sz = img.size() / 2; + if (sz.width() > 512 || sz.height() > 512) { + sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); + } else if (img.height() < 128 && img.width() < 128) { + sz = img.size(); + } + + info.h = sz.height(); + info.w = sz.width(); + info.size = bytes.size(); + info.mimetype = QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(); + + auto filename = f.fileName().toStdString(); + http::client()->upload( + bytes.toStdString(), + QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), + filename, + [this, filename, info](const mtx::responses::ContentURI &uri, mtx::http::RequestErr e) { + if (e) { + ChatPage::instance()->showNotification( + tr("Failed to upload image: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + + emit addImage(uri.content_uri, filename, info); + }); + } } void SingleImagePackModel::remove(int idx) { - if (idx < (int)shortcodes.size() && idx >= 0) { - beginRemoveRows(QModelIndex(), idx, idx); - auto s = shortcodes.at(idx); - shortcodes.erase(shortcodes.begin() + idx); - pack.images.erase(s); - endRemoveRows(); - } + if (idx < (int)shortcodes.size() && idx >= 0) { + beginRemoveRows(QModelIndex(), idx, idx); + auto s = shortcodes.at(idx); + shortcodes.erase(shortcodes.begin() + idx); + pack.images.erase(s); + endRemoveRows(); + } } void SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info) { - mtx::events::msc2545::PackImage img{}; - img.url = uri; - img.info = info; - beginInsertRows( - QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); + mtx::events::msc2545::PackImage img{}; + img.url = uri; + img.info = info; + beginInsertRows( + QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); - pack.images[filename] = img; - shortcodes.push_back(filename); + pack.images[filename] = img; + shortcodes.push_back(filename); - endInsertRows(); + endInsertRows(); } diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index 60138d36..cd8b0547 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -14,81 +14,78 @@ class SingleImagePackModel : public QAbstractListModel { - Q_OBJECT - - Q_PROPERTY(QString roomid READ roomid CONSTANT) - Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) - Q_PROPERTY( - QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) - Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY( - bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) - Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) - Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY - globallyEnabledChanged) - Q_PROPERTY(bool canEdit READ canEdit CONSTANT) + Q_OBJECT + + Q_PROPERTY(QString roomid READ roomid CONSTANT) + Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) + Q_PROPERTY(QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) + Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY( + bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) + Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) + Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY + globallyEnabledChanged) + Q_PROPERTY(bool canEdit READ canEdit CONSTANT) public: - enum Roles - { - Url = Qt::UserRole, - ShortCode, - Body, - IsEmote, - IsSticker, - }; - Q_ENUM(Roles); - - SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, - const QVariant &value, - int role = Qt::EditRole) override; - - QString roomid() const { return QString::fromStdString(roomid_); } - QString statekey() const { return QString::fromStdString(statekey_); } - QString packname() const { return QString::fromStdString(pack.pack->display_name); } - QString attribution() const { return QString::fromStdString(pack.pack->attribution); } - QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } - bool isStickerPack() const { return pack.pack->is_sticker(); } - bool isEmotePack() const { return pack.pack->is_emoji(); } - - bool isGloballyEnabled() const; - bool canEdit() const; - void setGloballyEnabled(bool enabled); - - void setPackname(QString val); - void setAttribution(QString val); - void setAvatarUrl(QString val); - void setStatekey(QString val); - void setIsStickerPack(bool val); - void setIsEmotePack(bool val); - - Q_INVOKABLE void save(); - Q_INVOKABLE void addStickers(QList files); - Q_INVOKABLE void remove(int index); + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + IsEmote, + IsSticker, + }; + Q_ENUM(Roles); + + SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + QString roomid() const { return QString::fromStdString(roomid_); } + QString statekey() const { return QString::fromStdString(statekey_); } + QString packname() const { return QString::fromStdString(pack.pack->display_name); } + QString attribution() const { return QString::fromStdString(pack.pack->attribution); } + QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } + bool isStickerPack() const { return pack.pack->is_sticker(); } + bool isEmotePack() const { return pack.pack->is_emoji(); } + + bool isGloballyEnabled() const; + bool canEdit() const; + void setGloballyEnabled(bool enabled); + + void setPackname(QString val); + void setAttribution(QString val); + void setAvatarUrl(QString val); + void setStatekey(QString val); + void setIsStickerPack(bool val); + void setIsEmotePack(bool val); + + Q_INVOKABLE void save(); + Q_INVOKABLE void addStickers(QList files); + Q_INVOKABLE void remove(int index); signals: - void globallyEnabledChanged(); - void statekeyChanged(); - void attributionChanged(); - void packnameChanged(); - void avatarUrlChanged(); - void isEmotePackChanged(); - void isStickerPackChanged(); + void globallyEnabledChanged(); + void statekeyChanged(); + void attributionChanged(); + void packnameChanged(); + void avatarUrlChanged(); + void isEmotePackChanged(); + void isStickerPackChanged(); - void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); + void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); private slots: - void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); + void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); private: - std::string roomid_; - std::string statekey_, old_statekey_; + std::string roomid_; + std::string statekey_, old_statekey_; - mtx::events::msc2545::ImagePack pack; - std::vector shortcodes; + mtx::events::msc2545::ImagePack pack; + std::vector shortcodes; }; diff --git a/src/TrayIcon.cpp b/src/TrayIcon.cpp index db0130c8..98a1d242 100644 --- a/src/TrayIcon.cpp +++ b/src/TrayIcon.cpp @@ -19,7 +19,7 @@ MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename) : QIconEngine() { - icon_ = QIcon(filename); + icon_ = QIcon(filename); } void @@ -28,95 +28,95 @@ MsgCountComposedIcon::paint(QPainter *painter, QIcon::Mode mode, QIcon::State state) { - painter->setRenderHint(QPainter::TextAntialiasing); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - painter->setRenderHint(QPainter::Antialiasing); - - icon_.paint(painter, rect, Qt::AlignCenter, mode, state); - - if (msgCount <= 0) - return; - - QColor backgroundColor("red"); - QColor textColor("white"); - - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(backgroundColor); - - QFont f; - f.setPointSizeF(8); - f.setWeight(QFont::Thin); - - painter->setBrush(brush); - painter->setPen(Qt::NoPen); - painter->setFont(f); - - QRectF bubble(rect.width() - BubbleDiameter, - rect.height() - BubbleDiameter, - BubbleDiameter, - BubbleDiameter); - painter->drawEllipse(bubble); - painter->setPen(QPen(textColor)); - painter->setBrush(Qt::NoBrush); - painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount)); + painter->setRenderHint(QPainter::TextAntialiasing); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setRenderHint(QPainter::Antialiasing); + + icon_.paint(painter, rect, Qt::AlignCenter, mode, state); + + if (msgCount <= 0) + return; + + QColor backgroundColor("red"); + QColor textColor("white"); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor); + + QFont f; + f.setPointSizeF(8); + f.setWeight(QFont::Thin); + + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->setFont(f); + + QRectF bubble(rect.width() - BubbleDiameter, + rect.height() - BubbleDiameter, + BubbleDiameter, + BubbleDiameter); + painter->drawEllipse(bubble); + painter->setPen(QPen(textColor)); + painter->setBrush(Qt::NoBrush); + painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount)); } QIconEngine * MsgCountComposedIcon::clone() const { - return new MsgCountComposedIcon(*this); + return new MsgCountComposedIcon(*this); } QList MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const { - Q_UNUSED(mode); - Q_UNUSED(state); - QList sizes; - sizes.append(QSize(24, 24)); - sizes.append(QSize(32, 32)); - sizes.append(QSize(48, 48)); - sizes.append(QSize(64, 64)); - sizes.append(QSize(128, 128)); - sizes.append(QSize(256, 256)); - return sizes; + Q_UNUSED(mode); + Q_UNUSED(state); + QList sizes; + sizes.append(QSize(24, 24)); + sizes.append(QSize(32, 32)); + sizes.append(QSize(48, 48)); + sizes.append(QSize(64, 64)); + sizes.append(QSize(128, 128)); + sizes.append(QSize(256, 256)); + return sizes; } QPixmap MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - QImage img(size, QImage::Format_ARGB32); - img.fill(qRgba(0, 0, 0, 0)); - QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion); - { - QPainter painter(&result); - paint(&painter, QRect(QPoint(0, 0), size), mode, state); - } - return result; + QImage img(size, QImage::Format_ARGB32); + img.fill(qRgba(0, 0, 0, 0)); + QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion); + { + QPainter painter(&result); + paint(&painter, QRect(QPoint(0, 0), size), mode, state); + } + return result; } TrayIcon::TrayIcon(const QString &filename, QWidget *parent) : QSystemTrayIcon(parent) { #if defined(Q_OS_MAC) || defined(Q_OS_WIN) - setIcon(QIcon(filename)); + setIcon(QIcon(filename)); #else - icon_ = new MsgCountComposedIcon(filename); - setIcon(QIcon(icon_)); + icon_ = new MsgCountComposedIcon(filename); + setIcon(QIcon(icon_)); #endif - QMenu *menu = new QMenu(parent); - setContextMenu(menu); + QMenu *menu = new QMenu(parent); + setContextMenu(menu); - viewAction_ = new QAction(tr("Show"), this); - quitAction_ = new QAction(tr("Quit"), this); + viewAction_ = new QAction(tr("Show"), this); + quitAction_ = new QAction(tr("Quit"), this); - connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show())); - connect(quitAction_, &QAction::triggered, this, QApplication::quit); + connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show())); + connect(quitAction_, &QAction::triggered, this, QApplication::quit); - menu->addAction(viewAction_); - menu->addAction(quitAction_); + menu->addAction(viewAction_); + menu->addAction(quitAction_); } void @@ -127,25 +127,25 @@ TrayIcon::setUnreadCount(int count) // currently, to avoid writing obj-c code, ignore deprecated warnings on the badge functions #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - auto labelText = count == 0 ? "" : QString::number(count); + auto labelText = count == 0 ? "" : QString::number(count); - if (labelText == QtMac::badgeLabelText()) - return; + if (labelText == QtMac::badgeLabelText()) + return; - QtMac::setBadgeLabelText(labelText); + QtMac::setBadgeLabelText(labelText); #pragma clang diagnostic pop #elif defined(Q_OS_WIN) // FIXME: Find a way to use Windows apis for the badge counter (if any). #else - if (count == icon_->msgCount) - return; + if (count == icon_->msgCount) + return; - // Custom drawing on Linux. - MsgCountComposedIcon *tmp = static_cast(icon_->clone()); - tmp->msgCount = count; + // Custom drawing on Linux. + MsgCountComposedIcon *tmp = static_cast(icon_->clone()); + tmp->msgCount = count; - setIcon(QIcon(tmp)); + setIcon(QIcon(tmp)); - icon_ = tmp; + icon_ = tmp; #endif } diff --git a/src/TrayIcon.h b/src/TrayIcon.h index 10dfafc5..1ce7fb0b 100644 --- a/src/TrayIcon.h +++ b/src/TrayIcon.h @@ -16,33 +16,33 @@ class QPainter; class MsgCountComposedIcon : public QIconEngine { public: - MsgCountComposedIcon(const QString &filename); + MsgCountComposedIcon(const QString &filename); - void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; - QIconEngine *clone() const override; - QList availableSizes(QIcon::Mode mode, QIcon::State state) const override; - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + QIconEngine *clone() const override; + QList availableSizes(QIcon::Mode mode, QIcon::State state) const override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - int msgCount = 0; + int msgCount = 0; private: - const int BubbleDiameter = 17; + const int BubbleDiameter = 17; - QIcon icon_; + QIcon icon_; }; class TrayIcon : public QSystemTrayIcon { - Q_OBJECT + Q_OBJECT public: - TrayIcon(const QString &filename, QWidget *parent); + TrayIcon(const QString &filename, QWidget *parent); public slots: - void setUnreadCount(int count); + void setUnreadCount(int count); private: - QAction *viewAction_; - QAction *quitAction_; + QAction *viewAction_; + QAction *quitAction_; - MsgCountComposedIcon *icon_; + MsgCountComposedIcon *icon_; }; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 7b01b0b8..cc1f8206 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -41,1499 +41,1484 @@ QSharedPointer UserSettings::instance_; UserSettings::UserSettings() { - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { - instance_.clear(); - }); + connect( + QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); }); } QSharedPointer UserSettings::instance() { - return instance_; + return instance_; } void UserSettings::initialize(std::optional profile) { - instance_.reset(new UserSettings()); - instance_->load(profile); + instance_.reset(new UserSettings()); + instance_->load(profile); } void UserSettings::load(std::optional profile) { - tray_ = settings.value("user/window/tray", false).toBool(); - startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); - - roomListWidth_ = settings.value("user/sidebar/room_list_width", -1).toInt(); - communityListWidth_ = settings.value("user/sidebar/community_list_width", -1).toInt(); - - hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); - hasAlertOnNotification_ = settings.value("user/alert_on_notification", false).toBool(); - groupView_ = settings.value("user/group_view", true).toBool(); - hiddenTags_ = settings.value("user/hidden_tags", QStringList{}).toStringList(); - buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool(); - timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt(); - messageHoverHighlight_ = - settings.value("user/timeline/message_hover_highlight", false).toBool(); - enlargeEmojiOnlyMessages_ = - settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); - markdown_ = settings.value("user/markdown_enabled", true).toBool(); - animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool(); - typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); - sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); - readReceipts_ = settings.value("user/read_receipts", true).toBool(); - theme_ = settings.value("user/theme", defaultTheme_).toString(); - font_ = settings.value("user/font_family", "default").toString(); - avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); - useIdenticon_ = settings.value("user/use_identicon", true).toBool(); - decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); - privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); - privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); - mobileMode_ = settings.value("user/mobile_mode", false).toBool(); - emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); - baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); - auto tempPresence = settings.value("user/presence", "").toString().toStdString(); - auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); - if (presenceValue < 0) - presenceValue = 0; - presence_ = static_cast(presenceValue); - ringtone_ = settings.value("user/ringtone", "Default").toString(); - microphone_ = settings.value("user/microphone", QString()).toString(); - camera_ = settings.value("user/camera", QString()).toString(); - cameraResolution_ = settings.value("user/camera_resolution", QString()).toString(); - cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString(); - screenShareFrameRate_ = settings.value("user/screen_share_frame_rate", 5).toInt(); - screenSharePiP_ = settings.value("user/screen_share_pip", true).toBool(); - screenShareRemoteVideo_ = settings.value("user/screen_share_remote_video", false).toBool(); - screenShareHideCursor_ = settings.value("user/screen_share_hide_cursor", false).toBool(); - useStunServer_ = settings.value("user/use_stun_server", false).toBool(); - - if (profile) // set to "" if it's the default to maintain compatibility - profile_ = (*profile == "default") ? "" : *profile; - else - profile_ = settings.value("user/currentProfile", "").toString(); - - QString prefix = - (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; - accessToken_ = settings.value(prefix + "auth/access_token", "").toString(); - homeserver_ = settings.value(prefix + "auth/home_server", "").toString(); - userId_ = settings.value(prefix + "auth/user_id", "").toString(); - deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); - - shareKeysWithTrustedUsers_ = - settings.value(prefix + "user/automatically_share_keys_with_trusted_users", false) - .toBool(); - onlyShareKeysWithVerifiedUsers_ = - settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); - useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", false).toBool(); - - disableCertificateValidation_ = - settings.value("disable_certificate_validation", false).toBool(); - - applyTheme(); + tray_ = settings.value("user/window/tray", false).toBool(); + startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); + + roomListWidth_ = settings.value("user/sidebar/room_list_width", -1).toInt(); + communityListWidth_ = settings.value("user/sidebar/community_list_width", -1).toInt(); + + hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); + hasAlertOnNotification_ = settings.value("user/alert_on_notification", false).toBool(); + groupView_ = settings.value("user/group_view", true).toBool(); + hiddenTags_ = settings.value("user/hidden_tags", QStringList{}).toStringList(); + buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool(); + timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt(); + messageHoverHighlight_ = + settings.value("user/timeline/message_hover_highlight", false).toBool(); + enlargeEmojiOnlyMessages_ = + settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); + markdown_ = settings.value("user/markdown_enabled", true).toBool(); + animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool(); + typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); + sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); + readReceipts_ = settings.value("user/read_receipts", true).toBool(); + theme_ = settings.value("user/theme", defaultTheme_).toString(); + font_ = settings.value("user/font_family", "default").toString(); + avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); + useIdenticon_ = settings.value("user/use_identicon", true).toBool(); + decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); + privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); + privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); + mobileMode_ = settings.value("user/mobile_mode", false).toBool(); + emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); + baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); + auto tempPresence = settings.value("user/presence", "").toString().toStdString(); + auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); + if (presenceValue < 0) + presenceValue = 0; + presence_ = static_cast(presenceValue); + ringtone_ = settings.value("user/ringtone", "Default").toString(); + microphone_ = settings.value("user/microphone", QString()).toString(); + camera_ = settings.value("user/camera", QString()).toString(); + cameraResolution_ = settings.value("user/camera_resolution", QString()).toString(); + cameraFrameRate_ = settings.value("user/camera_frame_rate", QString()).toString(); + screenShareFrameRate_ = settings.value("user/screen_share_frame_rate", 5).toInt(); + screenSharePiP_ = settings.value("user/screen_share_pip", true).toBool(); + screenShareRemoteVideo_ = settings.value("user/screen_share_remote_video", false).toBool(); + screenShareHideCursor_ = settings.value("user/screen_share_hide_cursor", false).toBool(); + useStunServer_ = settings.value("user/use_stun_server", false).toBool(); + + if (profile) // set to "" if it's the default to maintain compatibility + profile_ = (*profile == "default") ? "" : *profile; + else + profile_ = settings.value("user/currentProfile", "").toString(); + + QString prefix = (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; + accessToken_ = settings.value(prefix + "auth/access_token", "").toString(); + homeserver_ = settings.value(prefix + "auth/home_server", "").toString(); + userId_ = settings.value(prefix + "auth/user_id", "").toString(); + deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); + + shareKeysWithTrustedUsers_ = + settings.value(prefix + "user/automatically_share_keys_with_trusted_users", false).toBool(); + onlyShareKeysWithVerifiedUsers_ = + settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); + useOnlineKeyBackup_ = settings.value(prefix + "user/online_key_backup", false).toBool(); + + disableCertificateValidation_ = + settings.value("disable_certificate_validation", false).toBool(); + + applyTheme(); } void UserSettings::setMessageHoverHighlight(bool state) { - if (state == messageHoverHighlight_) - return; - messageHoverHighlight_ = state; - emit messageHoverHighlightChanged(state); - save(); + if (state == messageHoverHighlight_) + return; + messageHoverHighlight_ = state; + emit messageHoverHighlightChanged(state); + save(); } void UserSettings::setEnlargeEmojiOnlyMessages(bool state) { - if (state == enlargeEmojiOnlyMessages_) - return; - enlargeEmojiOnlyMessages_ = state; - emit enlargeEmojiOnlyMessagesChanged(state); - save(); + if (state == enlargeEmojiOnlyMessages_) + return; + enlargeEmojiOnlyMessages_ = state; + emit enlargeEmojiOnlyMessagesChanged(state); + save(); } void UserSettings::setTray(bool state) { - if (state == tray_) - return; - tray_ = state; - emit trayChanged(state); - save(); + if (state == tray_) + return; + tray_ = state; + emit trayChanged(state); + save(); } void UserSettings::setStartInTray(bool state) { - if (state == startInTray_) - return; - startInTray_ = state; - emit startInTrayChanged(state); - save(); + if (state == startInTray_) + return; + startInTray_ = state; + emit startInTrayChanged(state); + save(); } void UserSettings::setMobileMode(bool state) { - if (state == mobileMode_) - return; - mobileMode_ = state; - emit mobileModeChanged(state); - save(); + if (state == mobileMode_) + return; + mobileMode_ = state; + emit mobileModeChanged(state); + save(); } void UserSettings::setGroupView(bool state) { - if (groupView_ == state) - return; + if (groupView_ == state) + return; - groupView_ = state; - emit groupViewStateChanged(state); - save(); + groupView_ = state; + emit groupViewStateChanged(state); + save(); } void UserSettings::setHiddenTags(QStringList hiddenTags) { - hiddenTags_ = hiddenTags; - save(); + hiddenTags_ = hiddenTags; + save(); } void UserSettings::setMarkdown(bool state) { - if (state == markdown_) - return; - markdown_ = state; - emit markdownChanged(state); - save(); + if (state == markdown_) + return; + markdown_ = state; + emit markdownChanged(state); + save(); } void UserSettings::setAnimateImagesOnHover(bool state) { - if (state == animateImagesOnHover_) - return; - animateImagesOnHover_ = state; - emit animateImagesOnHoverChanged(state); - save(); + if (state == animateImagesOnHover_) + return; + animateImagesOnHover_ = state; + emit animateImagesOnHoverChanged(state); + save(); } void UserSettings::setReadReceipts(bool state) { - if (state == readReceipts_) - return; - readReceipts_ = state; - emit readReceiptsChanged(state); - save(); + if (state == readReceipts_) + return; + readReceipts_ = state; + emit readReceiptsChanged(state); + save(); } void UserSettings::setTypingNotifications(bool state) { - if (state == typingNotifications_) - return; - typingNotifications_ = state; - emit typingNotificationsChanged(state); - save(); + if (state == typingNotifications_) + return; + typingNotifications_ = state; + emit typingNotificationsChanged(state); + save(); } void UserSettings::setSortByImportance(bool state) { - if (state == sortByImportance_) - return; - sortByImportance_ = state; - emit roomSortingChanged(state); - save(); + if (state == sortByImportance_) + return; + sortByImportance_ = state; + emit roomSortingChanged(state); + save(); } void UserSettings::setButtonsInTimeline(bool state) { - if (state == buttonsInTimeline_) - return; - buttonsInTimeline_ = state; - emit buttonInTimelineChanged(state); - save(); + if (state == buttonsInTimeline_) + return; + buttonsInTimeline_ = state; + emit buttonInTimelineChanged(state); + save(); } void UserSettings::setTimelineMaxWidth(int state) { - if (state == timelineMaxWidth_) - return; - timelineMaxWidth_ = state; - emit timelineMaxWidthChanged(state); - save(); + if (state == timelineMaxWidth_) + return; + timelineMaxWidth_ = state; + emit timelineMaxWidthChanged(state); + save(); } void UserSettings::setCommunityListWidth(int state) { - if (state == communityListWidth_) - return; - communityListWidth_ = state; - emit communityListWidthChanged(state); - save(); + if (state == communityListWidth_) + return; + communityListWidth_ = state; + emit communityListWidthChanged(state); + save(); } void UserSettings::setRoomListWidth(int state) { - if (state == roomListWidth_) - return; - roomListWidth_ = state; - emit roomListWidthChanged(state); - save(); + if (state == roomListWidth_) + return; + roomListWidth_ = state; + emit roomListWidthChanged(state); + save(); } void UserSettings::setDesktopNotifications(bool state) { - if (state == hasDesktopNotifications_) - return; - hasDesktopNotifications_ = state; - emit desktopNotificationsChanged(state); - save(); + if (state == hasDesktopNotifications_) + return; + hasDesktopNotifications_ = state; + emit desktopNotificationsChanged(state); + save(); } void UserSettings::setAlertOnNotification(bool state) { - if (state == hasAlertOnNotification_) - return; - hasAlertOnNotification_ = state; - emit alertOnNotificationChanged(state); - save(); + if (state == hasAlertOnNotification_) + return; + hasAlertOnNotification_ = state; + emit alertOnNotificationChanged(state); + save(); } void UserSettings::setAvatarCircles(bool state) { - if (state == avatarCircles_) - return; - avatarCircles_ = state; - emit avatarCirclesChanged(state); - save(); + if (state == avatarCircles_) + return; + avatarCircles_ = state; + emit avatarCirclesChanged(state); + save(); } void UserSettings::setDecryptSidebar(bool state) { - if (state == decryptSidebar_) - return; - decryptSidebar_ = state; - emit decryptSidebarChanged(state); - save(); + if (state == decryptSidebar_) + return; + decryptSidebar_ = state; + emit decryptSidebarChanged(state); + save(); } void UserSettings::setPrivacyScreen(bool state) { - if (state == privacyScreen_) { - return; - } - privacyScreen_ = state; - emit privacyScreenChanged(state); - save(); + if (state == privacyScreen_) { + return; + } + privacyScreen_ = state; + emit privacyScreenChanged(state); + save(); } void UserSettings::setPrivacyScreenTimeout(int state) { - if (state == privacyScreenTimeout_) { - return; - } - privacyScreenTimeout_ = state; - emit privacyScreenTimeoutChanged(state); - save(); + if (state == privacyScreenTimeout_) { + return; + } + privacyScreenTimeout_ = state; + emit privacyScreenTimeoutChanged(state); + save(); } void UserSettings::setFontSize(double size) { - if (size == baseFontSize_) - return; - baseFontSize_ = size; - emit fontSizeChanged(size); - save(); + if (size == baseFontSize_) + return; + baseFontSize_ = size; + emit fontSizeChanged(size); + save(); } void UserSettings::setFontFamily(QString family) { - if (family == font_) - return; - font_ = family; - emit fontChanged(family); - save(); + if (family == font_) + return; + font_ = family; + emit fontChanged(family); + save(); } void UserSettings::setEmojiFontFamily(QString family) { - if (family == emojiFont_) - return; + if (family == emojiFont_) + return; - if (family == tr("Default")) { - emojiFont_ = "default"; - } else { - emojiFont_ = family; - } + if (family == tr("Default")) { + emojiFont_ = "default"; + } else { + emojiFont_ = family; + } - emit emojiFontChanged(family); - save(); + emit emojiFontChanged(family); + save(); } void UserSettings::setPresence(Presence state) { - if (state == presence_) - return; - presence_ = state; - emit presenceChanged(state); - save(); + if (state == presence_) + return; + presence_ = state; + emit presenceChanged(state); + save(); } void UserSettings::setTheme(QString theme) { - if (theme == theme_) - return; - theme_ = theme; - save(); - applyTheme(); - emit themeChanged(theme); + if (theme == theme_) + return; + theme_ = theme; + save(); + applyTheme(); + emit themeChanged(theme); } void UserSettings::setUseStunServer(bool useStunServer) { - if (useStunServer == useStunServer_) - return; - useStunServer_ = useStunServer; - emit useStunServerChanged(useStunServer); - save(); + if (useStunServer == useStunServer_) + return; + useStunServer_ = useStunServer; + emit useStunServerChanged(useStunServer); + save(); } void UserSettings::setOnlyShareKeysWithVerifiedUsers(bool shareKeys) { - if (shareKeys == onlyShareKeysWithVerifiedUsers_) - return; + if (shareKeys == onlyShareKeysWithVerifiedUsers_) + return; - onlyShareKeysWithVerifiedUsers_ = shareKeys; - emit onlyShareKeysWithVerifiedUsersChanged(shareKeys); - save(); + onlyShareKeysWithVerifiedUsers_ = shareKeys; + emit onlyShareKeysWithVerifiedUsersChanged(shareKeys); + save(); } void UserSettings::setShareKeysWithTrustedUsers(bool shareKeys) { - if (shareKeys == shareKeysWithTrustedUsers_) - return; + if (shareKeys == shareKeysWithTrustedUsers_) + return; - shareKeysWithTrustedUsers_ = shareKeys; - emit shareKeysWithTrustedUsersChanged(shareKeys); - save(); + shareKeysWithTrustedUsers_ = shareKeys; + emit shareKeysWithTrustedUsersChanged(shareKeys); + save(); } void UserSettings::setUseOnlineKeyBackup(bool useBackup) { - if (useBackup == useOnlineKeyBackup_) - return; + if (useBackup == useOnlineKeyBackup_) + return; - useOnlineKeyBackup_ = useBackup; - emit useOnlineKeyBackupChanged(useBackup); - save(); + useOnlineKeyBackup_ = useBackup; + emit useOnlineKeyBackupChanged(useBackup); + save(); } void UserSettings::setRingtone(QString ringtone) { - if (ringtone == ringtone_) - return; - ringtone_ = ringtone; - emit ringtoneChanged(ringtone); - save(); + if (ringtone == ringtone_) + return; + ringtone_ = ringtone; + emit ringtoneChanged(ringtone); + save(); } void UserSettings::setMicrophone(QString microphone) { - if (microphone == microphone_) - return; - microphone_ = microphone; - emit microphoneChanged(microphone); - save(); + if (microphone == microphone_) + return; + microphone_ = microphone; + emit microphoneChanged(microphone); + save(); } void UserSettings::setCamera(QString camera) { - if (camera == camera_) - return; - camera_ = camera; - emit cameraChanged(camera); - save(); + if (camera == camera_) + return; + camera_ = camera; + emit cameraChanged(camera); + save(); } void UserSettings::setCameraResolution(QString resolution) { - if (resolution == cameraResolution_) - return; - cameraResolution_ = resolution; - emit cameraResolutionChanged(resolution); - save(); + if (resolution == cameraResolution_) + return; + cameraResolution_ = resolution; + emit cameraResolutionChanged(resolution); + save(); } void UserSettings::setCameraFrameRate(QString frameRate) { - if (frameRate == cameraFrameRate_) - return; - cameraFrameRate_ = frameRate; - emit cameraFrameRateChanged(frameRate); - save(); + if (frameRate == cameraFrameRate_) + return; + cameraFrameRate_ = frameRate; + emit cameraFrameRateChanged(frameRate); + save(); } void UserSettings::setScreenShareFrameRate(int frameRate) { - if (frameRate == screenShareFrameRate_) - return; - screenShareFrameRate_ = frameRate; - emit screenShareFrameRateChanged(frameRate); - save(); + if (frameRate == screenShareFrameRate_) + return; + screenShareFrameRate_ = frameRate; + emit screenShareFrameRateChanged(frameRate); + save(); } void UserSettings::setScreenSharePiP(bool state) { - if (state == screenSharePiP_) - return; - screenSharePiP_ = state; - emit screenSharePiPChanged(state); - save(); + if (state == screenSharePiP_) + return; + screenSharePiP_ = state; + emit screenSharePiPChanged(state); + save(); } void UserSettings::setScreenShareRemoteVideo(bool state) { - if (state == screenShareRemoteVideo_) - return; - screenShareRemoteVideo_ = state; - emit screenShareRemoteVideoChanged(state); - save(); + if (state == screenShareRemoteVideo_) + return; + screenShareRemoteVideo_ = state; + emit screenShareRemoteVideoChanged(state); + save(); } void UserSettings::setScreenShareHideCursor(bool state) { - if (state == screenShareHideCursor_) - return; - screenShareHideCursor_ = state; - emit screenShareHideCursorChanged(state); - save(); + if (state == screenShareHideCursor_) + return; + screenShareHideCursor_ = state; + emit screenShareHideCursorChanged(state); + save(); } void UserSettings::setProfile(QString profile) { - if (profile == profile_) - return; - profile_ = profile; - emit profileChanged(profile_); - save(); + if (profile == profile_) + return; + profile_ = profile; + emit profileChanged(profile_); + save(); } void UserSettings::setUserId(QString userId) { - if (userId == userId_) - return; - userId_ = userId; - emit userIdChanged(userId_); - save(); + if (userId == userId_) + return; + userId_ = userId; + emit userIdChanged(userId_); + save(); } void UserSettings::setAccessToken(QString accessToken) { - if (accessToken == accessToken_) - return; - accessToken_ = accessToken; - emit accessTokenChanged(accessToken_); - save(); + if (accessToken == accessToken_) + return; + accessToken_ = accessToken; + emit accessTokenChanged(accessToken_); + save(); } void UserSettings::setDeviceId(QString deviceId) { - if (deviceId == deviceId_) - return; - deviceId_ = deviceId; - emit deviceIdChanged(deviceId_); - save(); + if (deviceId == deviceId_) + return; + deviceId_ = deviceId; + emit deviceIdChanged(deviceId_); + save(); } void UserSettings::setHomeserver(QString homeserver) { - if (homeserver == homeserver_) - return; - homeserver_ = homeserver; - emit homeserverChanged(homeserver_); - save(); + if (homeserver == homeserver_) + return; + homeserver_ = homeserver; + emit homeserverChanged(homeserver_); + save(); } void UserSettings::setDisableCertificateValidation(bool disabled) { - if (disabled == disableCertificateValidation_) - return; - disableCertificateValidation_ = disabled; - http::client()->verify_certificates(!disabled); - emit disableCertificateValidationChanged(disabled); + if (disabled == disableCertificateValidation_) + return; + disableCertificateValidation_ = disabled; + http::client()->verify_certificates(!disabled); + emit disableCertificateValidationChanged(disabled); } void UserSettings::setUseIdenticon(bool state) { - if (state == useIdenticon_) - return; - useIdenticon_ = state; - emit useIdenticonChanged(useIdenticon_); - save(); + if (state == useIdenticon_) + return; + useIdenticon_ = state; + emit useIdenticonChanged(useIdenticon_); + save(); } void UserSettings::applyTheme() { - QFile stylefile; + QFile stylefile; - if (this->theme() == "light") { - stylefile.setFileName(":/styles/styles/nheko.qss"); - } else if (this->theme() == "dark") { - stylefile.setFileName(":/styles/styles/nheko-dark.qss"); - } else { - stylefile.setFileName(":/styles/styles/system.qss"); - } - QApplication::setPalette(Theme::paletteFromTheme(this->theme().toStdString())); + if (this->theme() == "light") { + stylefile.setFileName(":/styles/styles/nheko.qss"); + } else if (this->theme() == "dark") { + stylefile.setFileName(":/styles/styles/nheko-dark.qss"); + } else { + stylefile.setFileName(":/styles/styles/system.qss"); + } + QApplication::setPalette(Theme::paletteFromTheme(this->theme().toStdString())); - stylefile.open(QFile::ReadOnly); - QString stylesheet = QString(stylefile.readAll()); + stylefile.open(QFile::ReadOnly); + QString stylesheet = QString(stylefile.readAll()); - qobject_cast(QApplication::instance())->setStyleSheet(stylesheet); + qobject_cast(QApplication::instance())->setStyleSheet(stylesheet); } void UserSettings::save() { - settings.beginGroup("user"); - - settings.beginGroup("window"); - settings.setValue("tray", tray_); - settings.setValue("start_in_tray", startInTray_); - settings.endGroup(); // window - - settings.beginGroup("sidebar"); - settings.setValue("community_list_width", communityListWidth_); - settings.setValue("room_list_width", roomListWidth_); - settings.endGroup(); // window - - settings.beginGroup("timeline"); - settings.setValue("buttons", buttonsInTimeline_); - settings.setValue("message_hover_highlight", messageHoverHighlight_); - settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_); - settings.setValue("max_width", timelineMaxWidth_); - settings.endGroup(); // timeline - - settings.setValue("avatar_circles", avatarCircles_); - settings.setValue("decrypt_sidebar", decryptSidebar_); - settings.setValue("privacy_screen", privacyScreen_); - settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); - settings.setValue("mobile_mode", mobileMode_); - settings.setValue("font_size", baseFontSize_); - settings.setValue("typing_notifications", typingNotifications_); - settings.setValue("sort_by_unread", sortByImportance_); - settings.setValue("minor_events", sortByImportance_); - settings.setValue("read_receipts", readReceipts_); - settings.setValue("group_view", groupView_); - settings.setValue("hidden_tags", hiddenTags_); - settings.setValue("markdown_enabled", markdown_); - settings.setValue("animate_images_on_hover", animateImagesOnHover_); - settings.setValue("desktop_notifications", hasDesktopNotifications_); - settings.setValue("alert_on_notification", hasAlertOnNotification_); - settings.setValue("theme", theme()); - settings.setValue("font_family", font_); - settings.setValue("emoji_font_family", emojiFont_); - settings.setValue("presence", - QString::fromUtf8(QMetaEnum::fromType().valueToKey( - static_cast(presence_)))); - settings.setValue("ringtone", ringtone_); - settings.setValue("microphone", microphone_); - settings.setValue("camera", camera_); - settings.setValue("camera_resolution", cameraResolution_); - settings.setValue("camera_frame_rate", cameraFrameRate_); - settings.setValue("screen_share_frame_rate", screenShareFrameRate_); - settings.setValue("screen_share_pip", screenSharePiP_); - settings.setValue("screen_share_remote_video", screenShareRemoteVideo_); - settings.setValue("screen_share_hide_cursor", screenShareHideCursor_); - settings.setValue("use_stun_server", useStunServer_); - settings.setValue("currentProfile", profile_); - settings.setValue("use_identicon", useIdenticon_); - - settings.endGroup(); // user - - QString prefix = - (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; - settings.setValue(prefix + "auth/access_token", accessToken_); - settings.setValue(prefix + "auth/home_server", homeserver_); - settings.setValue(prefix + "auth/user_id", userId_); - settings.setValue(prefix + "auth/device_id", deviceId_); - - settings.setValue(prefix + "user/automatically_share_keys_with_trusted_users", - shareKeysWithTrustedUsers_); - settings.setValue(prefix + "user/only_share_keys_with_verified_users", - onlyShareKeysWithVerifiedUsers_); - settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); - - settings.setValue("disable_certificate_validation", disableCertificateValidation_); - - settings.sync(); + settings.beginGroup("user"); + + settings.beginGroup("window"); + settings.setValue("tray", tray_); + settings.setValue("start_in_tray", startInTray_); + settings.endGroup(); // window + + settings.beginGroup("sidebar"); + settings.setValue("community_list_width", communityListWidth_); + settings.setValue("room_list_width", roomListWidth_); + settings.endGroup(); // window + + settings.beginGroup("timeline"); + settings.setValue("buttons", buttonsInTimeline_); + settings.setValue("message_hover_highlight", messageHoverHighlight_); + settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_); + settings.setValue("max_width", timelineMaxWidth_); + settings.endGroup(); // timeline + + settings.setValue("avatar_circles", avatarCircles_); + settings.setValue("decrypt_sidebar", decryptSidebar_); + settings.setValue("privacy_screen", privacyScreen_); + settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); + settings.setValue("mobile_mode", mobileMode_); + settings.setValue("font_size", baseFontSize_); + settings.setValue("typing_notifications", typingNotifications_); + settings.setValue("sort_by_unread", sortByImportance_); + settings.setValue("minor_events", sortByImportance_); + settings.setValue("read_receipts", readReceipts_); + settings.setValue("group_view", groupView_); + settings.setValue("hidden_tags", hiddenTags_); + settings.setValue("markdown_enabled", markdown_); + settings.setValue("animate_images_on_hover", animateImagesOnHover_); + settings.setValue("desktop_notifications", hasDesktopNotifications_); + settings.setValue("alert_on_notification", hasAlertOnNotification_); + settings.setValue("theme", theme()); + settings.setValue("font_family", font_); + settings.setValue("emoji_font_family", emojiFont_); + settings.setValue( + "presence", + QString::fromUtf8(QMetaEnum::fromType().valueToKey(static_cast(presence_)))); + settings.setValue("ringtone", ringtone_); + settings.setValue("microphone", microphone_); + settings.setValue("camera", camera_); + settings.setValue("camera_resolution", cameraResolution_); + settings.setValue("camera_frame_rate", cameraFrameRate_); + settings.setValue("screen_share_frame_rate", screenShareFrameRate_); + settings.setValue("screen_share_pip", screenSharePiP_); + settings.setValue("screen_share_remote_video", screenShareRemoteVideo_); + settings.setValue("screen_share_hide_cursor", screenShareHideCursor_); + settings.setValue("use_stun_server", useStunServer_); + settings.setValue("currentProfile", profile_); + settings.setValue("use_identicon", useIdenticon_); + + settings.endGroup(); // user + + QString prefix = (profile_ != "" && profile_ != "default") ? "profile/" + profile_ + "/" : ""; + settings.setValue(prefix + "auth/access_token", accessToken_); + settings.setValue(prefix + "auth/home_server", homeserver_); + settings.setValue(prefix + "auth/user_id", userId_); + settings.setValue(prefix + "auth/device_id", deviceId_); + + settings.setValue(prefix + "user/automatically_share_keys_with_trusted_users", + shareKeysWithTrustedUsers_); + settings.setValue(prefix + "user/only_share_keys_with_verified_users", + onlyShareKeysWithVerifiedUsers_); + settings.setValue(prefix + "user/online_key_backup", useOnlineKeyBackup_); + + settings.setValue("disable_certificate_validation", disableCertificateValidation_); + + settings.sync(); } HorizontalLine::HorizontalLine(QWidget *parent) : QFrame{parent} { - setFrameShape(QFrame::HLine); - setFrameShadow(QFrame::Sunken); + setFrameShape(QFrame::HLine); + setFrameShadow(QFrame::Sunken); } UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidget *parent) : QWidget{parent} , settings_{settings} { - topLayout_ = new QVBoxLayout{this}; - - QIcon icon; - icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); - - auto backBtn_ = new FlatButton{this}; - backBtn_->setMinimumSize(QSize(24, 24)); - backBtn_->setIcon(icon); - backBtn_->setIconSize(QSize(24, 24)); - - QFont font; - font.setPointSizeF(font.pointSizeF() * 1.1); - - auto versionInfo = new QLabel(QString("%1 | %2").arg(nheko::version).arg(nheko::build_os)); - if (QCoreApplication::applicationName() != "nheko") - versionInfo->setText(versionInfo->text() + " | " + - tr("profile: %1").arg(QCoreApplication::applicationName())); - versionInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); - - topBarLayout_ = new QHBoxLayout; - topBarLayout_->setSpacing(0); - topBarLayout_->setMargin(0); - topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); - topBarLayout_->addStretch(1); - - formLayout_ = new QFormLayout; - - formLayout_->setLabelAlignment(Qt::AlignLeft); - formLayout_->setFormAlignment(Qt::AlignRight); - formLayout_->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - formLayout_->setRowWrapPolicy(QFormLayout::WrapLongRows); - formLayout_->setHorizontalSpacing(0); - - auto general_ = new QLabel{tr("GENERAL"), this}; - general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); - general_->setFont(font); - - trayToggle_ = new Toggle{this}; - startInTrayToggle_ = new Toggle{this}; - avatarCircles_ = new Toggle{this}; - useIdenticon_ = new Toggle{this}; - decryptSidebar_ = new Toggle(this); - privacyScreen_ = new Toggle{this}; - onlyShareKeysWithVerifiedUsers_ = new Toggle(this); - shareKeysWithTrustedUsers_ = new Toggle(this); - useOnlineKeyBackup_ = new Toggle(this); - groupViewToggle_ = new Toggle{this}; - timelineButtonsToggle_ = new Toggle{this}; - typingNotifications_ = new Toggle{this}; - messageHoverHighlight_ = new Toggle{this}; - enlargeEmojiOnlyMessages_ = new Toggle{this}; - sortByImportance_ = new Toggle{this}; - readReceipts_ = new Toggle{this}; - markdown_ = new Toggle{this}; - animateImagesOnHover_ = new Toggle{this}; - desktopNotifications_ = new Toggle{this}; - alertOnNotification_ = new Toggle{this}; - useStunServer_ = new Toggle{this}; - mobileMode_ = new Toggle{this}; - scaleFactorCombo_ = new QComboBox{this}; - fontSizeCombo_ = new QComboBox{this}; - fontSelectionCombo_ = new QFontComboBox{this}; - emojiFontSelectionCombo_ = new QComboBox{this}; - ringtoneCombo_ = new QComboBox{this}; - microphoneCombo_ = new QComboBox{this}; - cameraCombo_ = new QComboBox{this}; - cameraResolutionCombo_ = new QComboBox{this}; - cameraFrameRateCombo_ = new QComboBox{this}; - timelineMaxWidthSpin_ = new QSpinBox{this}; - privacyScreenTimeout_ = new QSpinBox{this}; - - trayToggle_->setChecked(settings_->tray()); - startInTrayToggle_->setChecked(settings_->startInTray()); - avatarCircles_->setChecked(settings_->avatarCircles()); - useIdenticon_->setChecked(settings_->useIdenticon()); - decryptSidebar_->setChecked(settings_->decryptSidebar()); - privacyScreen_->setChecked(settings_->privacyScreen()); - onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); - shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); - useOnlineKeyBackup_->setChecked(settings_->useOnlineKeyBackup()); - groupViewToggle_->setChecked(settings_->groupView()); - timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); - typingNotifications_->setChecked(settings_->typingNotifications()); - messageHoverHighlight_->setChecked(settings_->messageHoverHighlight()); - enlargeEmojiOnlyMessages_->setChecked(settings_->enlargeEmojiOnlyMessages()); - sortByImportance_->setChecked(settings_->sortByImportance()); - readReceipts_->setChecked(settings_->readReceipts()); - markdown_->setChecked(settings_->markdown()); - animateImagesOnHover_->setChecked(settings_->animateImagesOnHover()); - desktopNotifications_->setChecked(settings_->hasDesktopNotifications()); - alertOnNotification_->setChecked(settings_->hasAlertOnNotification()); - useStunServer_->setChecked(settings_->useStunServer()); - mobileMode_->setChecked(settings_->mobileMode()); - - if (!settings_->tray()) { - startInTrayToggle_->setState(false); - startInTrayToggle_->setDisabled(true); - } - - if (!settings_->privacyScreen()) { - privacyScreenTimeout_->setDisabled(true); - } - - avatarCircles_->setFixedSize(64, 48); - - auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; - uiLabel_->setFixedHeight(uiLabel_->minimumHeight() + LayoutTopMargin); - uiLabel_->setAlignment(Qt::AlignBottom); - uiLabel_->setFont(font); - - for (double option = 1; option <= 3; option += 0.25) - scaleFactorCombo_->addItem(QString::number(option)); - for (double option = 6; option <= 24; option += 0.5) - fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option))); - - QFontDatabase fontDb; - - // TODO: Is there a way to limit to just emojis, rather than - // all emoji fonts? - auto emojiFamilies = fontDb.families(QFontDatabase::Symbol); - emojiFontSelectionCombo_->addItem(tr("Default")); - for (const auto &family : emojiFamilies) { - emojiFontSelectionCombo_->addItem(family); - } - - QString currentFont = settings_->font(); - if (currentFont != "default" || currentFont != "") { - fontSelectionCombo_->setCurrentIndex(fontSelectionCombo_->findText(currentFont)); + topLayout_ = new QVBoxLayout{this}; + + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + + auto backBtn_ = new FlatButton{this}; + backBtn_->setMinimumSize(QSize(24, 24)); + backBtn_->setIcon(icon); + backBtn_->setIconSize(QSize(24, 24)); + + QFont font; + font.setPointSizeF(font.pointSizeF() * 1.1); + + auto versionInfo = new QLabel(QString("%1 | %2").arg(nheko::version).arg(nheko::build_os)); + if (QCoreApplication::applicationName() != "nheko") + versionInfo->setText(versionInfo->text() + " | " + + tr("profile: %1").arg(QCoreApplication::applicationName())); + versionInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); + + topBarLayout_ = new QHBoxLayout; + topBarLayout_->setSpacing(0); + topBarLayout_->setMargin(0); + topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); + topBarLayout_->addStretch(1); + + formLayout_ = new QFormLayout; + + formLayout_->setLabelAlignment(Qt::AlignLeft); + formLayout_->setFormAlignment(Qt::AlignRight); + formLayout_->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + formLayout_->setRowWrapPolicy(QFormLayout::WrapLongRows); + formLayout_->setHorizontalSpacing(0); + + auto general_ = new QLabel{tr("GENERAL"), this}; + general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + general_->setFont(font); + + trayToggle_ = new Toggle{this}; + startInTrayToggle_ = new Toggle{this}; + avatarCircles_ = new Toggle{this}; + useIdenticon_ = new Toggle{this}; + decryptSidebar_ = new Toggle(this); + privacyScreen_ = new Toggle{this}; + onlyShareKeysWithVerifiedUsers_ = new Toggle(this); + shareKeysWithTrustedUsers_ = new Toggle(this); + useOnlineKeyBackup_ = new Toggle(this); + groupViewToggle_ = new Toggle{this}; + timelineButtonsToggle_ = new Toggle{this}; + typingNotifications_ = new Toggle{this}; + messageHoverHighlight_ = new Toggle{this}; + enlargeEmojiOnlyMessages_ = new Toggle{this}; + sortByImportance_ = new Toggle{this}; + readReceipts_ = new Toggle{this}; + markdown_ = new Toggle{this}; + animateImagesOnHover_ = new Toggle{this}; + desktopNotifications_ = new Toggle{this}; + alertOnNotification_ = new Toggle{this}; + useStunServer_ = new Toggle{this}; + mobileMode_ = new Toggle{this}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QFontComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; + ringtoneCombo_ = new QComboBox{this}; + microphoneCombo_ = new QComboBox{this}; + cameraCombo_ = new QComboBox{this}; + cameraResolutionCombo_ = new QComboBox{this}; + cameraFrameRateCombo_ = new QComboBox{this}; + timelineMaxWidthSpin_ = new QSpinBox{this}; + privacyScreenTimeout_ = new QSpinBox{this}; + + trayToggle_->setChecked(settings_->tray()); + startInTrayToggle_->setChecked(settings_->startInTray()); + avatarCircles_->setChecked(settings_->avatarCircles()); + useIdenticon_->setChecked(settings_->useIdenticon()); + decryptSidebar_->setChecked(settings_->decryptSidebar()); + privacyScreen_->setChecked(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); + shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setChecked(settings_->useOnlineKeyBackup()); + groupViewToggle_->setChecked(settings_->groupView()); + timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); + typingNotifications_->setChecked(settings_->typingNotifications()); + messageHoverHighlight_->setChecked(settings_->messageHoverHighlight()); + enlargeEmojiOnlyMessages_->setChecked(settings_->enlargeEmojiOnlyMessages()); + sortByImportance_->setChecked(settings_->sortByImportance()); + readReceipts_->setChecked(settings_->readReceipts()); + markdown_->setChecked(settings_->markdown()); + animateImagesOnHover_->setChecked(settings_->animateImagesOnHover()); + desktopNotifications_->setChecked(settings_->hasDesktopNotifications()); + alertOnNotification_->setChecked(settings_->hasAlertOnNotification()); + useStunServer_->setChecked(settings_->useStunServer()); + mobileMode_->setChecked(settings_->mobileMode()); + + if (!settings_->tray()) { + startInTrayToggle_->setState(false); + startInTrayToggle_->setDisabled(true); + } + + if (!settings_->privacyScreen()) { + privacyScreenTimeout_->setDisabled(true); + } + + avatarCircles_->setFixedSize(64, 48); + + auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; + uiLabel_->setFixedHeight(uiLabel_->minimumHeight() + LayoutTopMargin); + uiLabel_->setAlignment(Qt::AlignBottom); + uiLabel_->setFont(font); + + for (double option = 1; option <= 3; option += 0.25) + scaleFactorCombo_->addItem(QString::number(option)); + for (double option = 6; option <= 24; option += 0.5) + fontSizeCombo_->addItem(QString("%1 ").arg(QString::number(option))); + + QFontDatabase fontDb; + + // TODO: Is there a way to limit to just emojis, rather than + // all emoji fonts? + auto emojiFamilies = fontDb.families(QFontDatabase::Symbol); + emojiFontSelectionCombo_->addItem(tr("Default")); + for (const auto &family : emojiFamilies) { + emojiFontSelectionCombo_->addItem(family); + } + + QString currentFont = settings_->font(); + if (currentFont != "default" || currentFont != "") { + fontSelectionCombo_->setCurrentIndex(fontSelectionCombo_->findText(currentFont)); + } + + emojiFontSelectionCombo_->setCurrentIndex( + emojiFontSelectionCombo_->findText(settings_->emojiFont())); + + themeCombo_ = new QComboBox{this}; + themeCombo_->addItem("Light"); + themeCombo_->addItem("Dark"); + themeCombo_->addItem("System"); + + QString themeStr = settings_->theme(); + themeStr.replace(0, 1, themeStr[0].toUpper()); + int themeIndex = themeCombo_->findText(themeStr); + themeCombo_->setCurrentIndex(themeIndex); + + timelineMaxWidthSpin_->setMinimum(0); + timelineMaxWidthSpin_->setMaximum(100'000'000); + timelineMaxWidthSpin_->setSingleStep(10); + + privacyScreenTimeout_->setMinimum(0); + privacyScreenTimeout_->setMaximum(3600); + privacyScreenTimeout_->setSingleStep(10); + + auto callsLabel = new QLabel{tr("CALLS"), this}; + callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); + callsLabel->setAlignment(Qt::AlignBottom); + callsLabel->setFont(font); + + auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; + encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); + encryptionLabel_->setAlignment(Qt::AlignBottom); + encryptionLabel_->setFont(font); + + QFont monospaceFont; + monospaceFont.setFamily("Monospace"); + monospaceFont.setStyleHint(QFont::Monospace); + monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9); + + deviceIdValue_ = new QLabel{this}; + deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); + deviceIdValue_->setFont(monospaceFont); + + deviceFingerprintValue_ = new QLabel{this}; + deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); + deviceFingerprintValue_->setFont(monospaceFont); + + deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X'))); + + backupSecretCached = new QLabel{this}; + masterSecretCached = new QLabel{this}; + selfSigningSecretCached = new QLabel{this}; + userSigningSecretCached = new QLabel{this}; + backupSecretCached->setFont(monospaceFont); + masterSecretCached->setFont(monospaceFont); + selfSigningSecretCached->setFont(monospaceFont); + userSigningSecretCached->setFont(monospaceFont); + + auto sessionKeysLabel = new QLabel{tr("Session Keys"), this}; + sessionKeysLabel->setFont(font); + sessionKeysLabel->setMargin(OptionMargin); + + auto sessionKeysImportBtn = new QPushButton{tr("IMPORT"), this}; + auto sessionKeysExportBtn = new QPushButton{tr("EXPORT"), this}; + + auto sessionKeysLayout = new QHBoxLayout; + sessionKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); + sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); + sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); + + auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this}; + crossSigningKeysLabel->setFont(font); + crossSigningKeysLabel->setMargin(OptionMargin); + + auto crossSigningRequestBtn = new QPushButton{tr("REQUEST"), this}; + auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this}; + + auto crossSigningKeysLayout = new QHBoxLayout; + crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); + crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight); + crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight); + + auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") { + auto label = new QLabel{labelText, this}; + label->setFont(font); + label->setMargin(OptionMargin); + + if (!tooltipText.isEmpty()) { + label->setToolTip(tooltipText); } - emojiFontSelectionCombo_->setCurrentIndex( - emojiFontSelectionCombo_->findText(settings_->emojiFont())); - - themeCombo_ = new QComboBox{this}; - themeCombo_->addItem("Light"); - themeCombo_->addItem("Dark"); - themeCombo_->addItem("System"); - - QString themeStr = settings_->theme(); - themeStr.replace(0, 1, themeStr[0].toUpper()); - int themeIndex = themeCombo_->findText(themeStr); - themeCombo_->setCurrentIndex(themeIndex); - - timelineMaxWidthSpin_->setMinimum(0); - timelineMaxWidthSpin_->setMaximum(100'000'000); - timelineMaxWidthSpin_->setSingleStep(10); - - privacyScreenTimeout_->setMinimum(0); - privacyScreenTimeout_->setMaximum(3600); - privacyScreenTimeout_->setSingleStep(10); - - auto callsLabel = new QLabel{tr("CALLS"), this}; - callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); - callsLabel->setAlignment(Qt::AlignBottom); - callsLabel->setFont(font); - - auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; - encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); - encryptionLabel_->setAlignment(Qt::AlignBottom); - encryptionLabel_->setFont(font); - - QFont monospaceFont; - monospaceFont.setFamily("Monospace"); - monospaceFont.setStyleHint(QFont::Monospace); - monospaceFont.setPointSizeF(monospaceFont.pointSizeF() * 0.9); - - deviceIdValue_ = new QLabel{this}; - deviceIdValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); - deviceIdValue_->setFont(monospaceFont); - - deviceFingerprintValue_ = new QLabel{this}; - deviceFingerprintValue_->setTextInteractionFlags(Qt::TextSelectableByMouse); - deviceFingerprintValue_->setFont(monospaceFont); - - deviceFingerprintValue_->setText(utils::humanReadableFingerprint(QString(44, 'X'))); - - backupSecretCached = new QLabel{this}; - masterSecretCached = new QLabel{this}; - selfSigningSecretCached = new QLabel{this}; - userSigningSecretCached = new QLabel{this}; - backupSecretCached->setFont(monospaceFont); - masterSecretCached->setFont(monospaceFont); - selfSigningSecretCached->setFont(monospaceFont); - userSigningSecretCached->setFont(monospaceFont); - - auto sessionKeysLabel = new QLabel{tr("Session Keys"), this}; - sessionKeysLabel->setFont(font); - sessionKeysLabel->setMargin(OptionMargin); - - auto sessionKeysImportBtn = new QPushButton{tr("IMPORT"), this}; - auto sessionKeysExportBtn = new QPushButton{tr("EXPORT"), this}; - - auto sessionKeysLayout = new QHBoxLayout; - sessionKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); - sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); - sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); - - auto crossSigningKeysLabel = new QLabel{tr("Cross Signing Keys"), this}; - crossSigningKeysLabel->setFont(font); - crossSigningKeysLabel->setMargin(OptionMargin); - - auto crossSigningRequestBtn = new QPushButton{tr("REQUEST"), this}; - auto crossSigningDownloadBtn = new QPushButton{tr("DOWNLOAD"), this}; - - auto crossSigningKeysLayout = new QHBoxLayout; - crossSigningKeysLayout->addWidget(new QLabel{"", this}, 1, Qt::AlignRight); - crossSigningKeysLayout->addWidget(crossSigningRequestBtn, 0, Qt::AlignRight); - crossSigningKeysLayout->addWidget(crossSigningDownloadBtn, 0, Qt::AlignRight); - - auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") { - auto label = new QLabel{labelText, this}; - label->setFont(font); - label->setMargin(OptionMargin); - - if (!tooltipText.isEmpty()) { - label->setToolTip(tooltipText); - } - - auto layout = new QHBoxLayout; - layout->addWidget(field, 0, Qt::AlignRight); - - formLayout_->addRow(label, layout); - }; - - formLayout_->addRow(general_); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap( - tr("Minimize to tray"), - trayToggle_, - tr("Keep the application running in the background after closing the client window.")); - boxWrap(tr("Start in tray"), - startInTrayToggle_, - tr("Start the application in the background without showing the client window.")); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Circular Avatars"), - avatarCircles_, - tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); - boxWrap(tr("Use identicons"), - useIdenticon_, - tr("Display an identicon instead of a letter when a user has not set an avatar.")); - boxWrap(tr("Group's sidebar"), - groupViewToggle_, - tr("Show a column containing groups and tags next to the room list.")); - boxWrap(tr("Decrypt messages in sidebar"), - decryptSidebar_, - tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " - "encrypted chats.")); - boxWrap(tr("Privacy Screen"), - privacyScreen_, - tr("When the window loses focus, the timeline will\nbe blurred.")); - boxWrap( - tr("Privacy screen timeout (in seconds [0 - 3600])"), - privacyScreenTimeout_, - tr("Set timeout (in seconds) for how long after window loses\nfocus before the screen" - " will be blurred.\nSet to 0 to blur immediately after focus loss. Max value of 1 " - "hour (3600 seconds)")); - boxWrap(tr("Show buttons in timeline"), - timelineButtonsToggle_, - tr("Show buttons to quickly reply, react or access additional options next to each " - "message.")); - boxWrap(tr("Limit width of timeline"), - timelineMaxWidthSpin_, - tr("Set the max width of messages in the timeline (in pixels). This can help " - "readability on wide screen, when Nheko is maximised")); - boxWrap(tr("Typing notifications"), - typingNotifications_, - tr("Show who is typing in a room.\nThis will also enable or disable sending typing " - "notifications to others.")); - boxWrap( - tr("Sort rooms by unreads"), - sortByImportance_, - tr( - "Display rooms with new messages first.\nIf this is off, the list of rooms will only " - "be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which " - "have active notifications (the small circle with a number in it) will be sorted on " - "top. Rooms, that you have muted, will still be sorted by timestamp, since you don't " - "seem to consider them as important as the other rooms.")); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Read receipts"), - readReceipts_, - tr("Show if your message was read.\nStatus is displayed next to timestamps.")); - boxWrap( - tr("Send messages as Markdown"), - markdown_, - tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " - "text.")); - boxWrap(tr("Play animated images only on hover"), - animateImagesOnHover_, - tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.")); - boxWrap(tr("Desktop notifications"), - desktopNotifications_, - tr("Notify about received message when the client is not currently focused.")); - boxWrap(tr("Alert on notification"), - alertOnNotification_, - tr("Show an alert when a message is received.\nThis usually causes the application " - "icon in the task bar to animate in some fashion.")); - boxWrap(tr("Highlight message on hover"), - messageHoverHighlight_, - tr("Change the background color of messages when you hover over them.")); - boxWrap(tr("Large Emoji in timeline"), - enlargeEmojiOnlyMessages_, - tr("Make font size larger if messages with only a few emojis are displayed.")); - formLayout_->addRow(uiLabel_); - formLayout_->addRow(new HorizontalLine{this}); - - boxWrap(tr("Touchscreen mode"), - mobileMode_, - tr("Will prevent text selection in the timeline to make touch scrolling easier.")); + auto layout = new QHBoxLayout; + layout->addWidget(field, 0, Qt::AlignRight); + + formLayout_->addRow(label, layout); + }; + + formLayout_->addRow(general_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Minimize to tray"), + trayToggle_, + tr("Keep the application running in the background after closing the client window.")); + boxWrap(tr("Start in tray"), + startInTrayToggle_, + tr("Start the application in the background without showing the client window.")); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Circular Avatars"), + avatarCircles_, + tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); + boxWrap(tr("Use identicons"), + useIdenticon_, + tr("Display an identicon instead of a letter when a user has not set an avatar.")); + boxWrap(tr("Group's sidebar"), + groupViewToggle_, + tr("Show a column containing groups and tags next to the room list.")); + boxWrap(tr("Decrypt messages in sidebar"), + decryptSidebar_, + tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " + "encrypted chats.")); + boxWrap(tr("Privacy Screen"), + privacyScreen_, + tr("When the window loses focus, the timeline will\nbe blurred.")); + boxWrap(tr("Privacy screen timeout (in seconds [0 - 3600])"), + privacyScreenTimeout_, + tr("Set timeout (in seconds) for how long after window loses\nfocus before the screen" + " will be blurred.\nSet to 0 to blur immediately after focus loss. Max value of 1 " + "hour (3600 seconds)")); + boxWrap(tr("Show buttons in timeline"), + timelineButtonsToggle_, + tr("Show buttons to quickly reply, react or access additional options next to each " + "message.")); + boxWrap(tr("Limit width of timeline"), + timelineMaxWidthSpin_, + tr("Set the max width of messages in the timeline (in pixels). This can help " + "readability on wide screen, when Nheko is maximised")); + boxWrap(tr("Typing notifications"), + typingNotifications_, + tr("Show who is typing in a room.\nThis will also enable or disable sending typing " + "notifications to others.")); + boxWrap( + tr("Sort rooms by unreads"), + sortByImportance_, + tr("Display rooms with new messages first.\nIf this is off, the list of rooms will only " + "be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which " + "have active notifications (the small circle with a number in it) will be sorted on " + "top. Rooms, that you have muted, will still be sorted by timestamp, since you don't " + "seem to consider them as important as the other rooms.")); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Read receipts"), + readReceipts_, + tr("Show if your message was read.\nStatus is displayed next to timestamps.")); + boxWrap(tr("Send messages as Markdown"), + markdown_, + tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " + "text.")); + boxWrap(tr("Play animated images only on hover"), + animateImagesOnHover_, + tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.")); + boxWrap(tr("Desktop notifications"), + desktopNotifications_, + tr("Notify about received message when the client is not currently focused.")); + boxWrap(tr("Alert on notification"), + alertOnNotification_, + tr("Show an alert when a message is received.\nThis usually causes the application " + "icon in the task bar to animate in some fashion.")); + boxWrap(tr("Highlight message on hover"), + messageHoverHighlight_, + tr("Change the background color of messages when you hover over them.")); + boxWrap(tr("Large Emoji in timeline"), + enlargeEmojiOnlyMessages_, + tr("Make font size larger if messages with only a few emojis are displayed.")); + formLayout_->addRow(uiLabel_); + formLayout_->addRow(new HorizontalLine{this}); + + boxWrap(tr("Touchscreen mode"), + mobileMode_, + tr("Will prevent text selection in the timeline to make touch scrolling easier.")); #if !defined(Q_OS_MAC) - boxWrap(tr("Scale factor"), - scaleFactorCombo_, - tr("Change the scale factor of the whole user interface.")); + boxWrap(tr("Scale factor"), + scaleFactorCombo_, + tr("Change the scale factor of the whole user interface.")); #else - scaleFactorCombo_->hide(); + scaleFactorCombo_->hide(); #endif - boxWrap(tr("Font size"), fontSizeCombo_); - boxWrap(tr("Font Family"), fontSelectionCombo_); + boxWrap(tr("Font size"), fontSizeCombo_); + boxWrap(tr("Font Family"), fontSelectionCombo_); #if !defined(Q_OS_MAC) - boxWrap(tr("Emoji Font Family"), emojiFontSelectionCombo_); + boxWrap(tr("Emoji Font Family"), emojiFontSelectionCombo_); #else - emojiFontSelectionCombo_->hide(); + emojiFontSelectionCombo_->hide(); #endif - boxWrap(tr("Theme"), themeCombo_); - - formLayout_->addRow(callsLabel); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Ringtone"), - ringtoneCombo_, - tr("Set the notification sound to play when a call invite arrives")); - boxWrap(tr("Microphone"), microphoneCombo_); - boxWrap(tr("Camera"), cameraCombo_); - boxWrap(tr("Camera resolution"), cameraResolutionCombo_); - boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_); - - ringtoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - ringtoneCombo_->addItem("Mute"); - ringtoneCombo_->addItem("Default"); - ringtoneCombo_->addItem("Other..."); - const QString &ringtone = settings_->ringtone(); - if (!ringtone.isEmpty() && ringtone != "Mute" && ringtone != "Default") - ringtoneCombo_->addItem(ringtone); - microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); - - boxWrap(tr("Allow fallback call assist server"), - useStunServer_, - tr("Will use turn.matrix.org as assist when your home server does not offer one.")); - - formLayout_->addRow(encryptionLabel_); - formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Device ID"), deviceIdValue_); - boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); - boxWrap(tr("Send encrypted messages to verified users only"), - onlyShareKeysWithVerifiedUsers_, - tr("Requires a user to be verified to send encrypted messages to them. This " - "improves safety but makes E2EE more tedious.")); - boxWrap(tr("Share keys with verified users and devices"), - shareKeysWithTrustedUsers_, - tr("Automatically replies to key requests from other users, if they are verified, " - "even if that device shouldn't have access to those keys otherwise.")); - boxWrap(tr("Online Key Backup"), - useOnlineKeyBackup_, - tr("Download message encryption keys from and upload to the encrypted online key " - "backup.")); - formLayout_->addRow(new HorizontalLine{this}); - formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); - formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); - - boxWrap(tr("Master signing key"), - masterSecretCached, - tr("Your most important key. You don't need to have it cached, since not caching " - "it makes it less likely it can be stolen and it is only needed to rotate your " - "other signing keys.")); - boxWrap(tr("User signing key"), - userSigningSecretCached, - tr("The key to verify other users. If it is cached, verifying a user will verify " - "all their devices.")); - boxWrap( - tr("Self signing key"), - selfSigningSecretCached, - tr("The key to verify your own devices. If it is cached, verifying one of your devices " - "will mark it verified for all your other devices and for users, that have verified " - "you.")); - boxWrap(tr("Backup key"), - backupSecretCached, - tr("The key to decrypt online key backups. If it is cached, you can enable online " - "key backup to store encryption keys securely encrypted on the server.")); - updateSecretStatus(); - - auto scrollArea_ = new QScrollArea{this}; - scrollArea_->setFrameShape(QFrame::NoFrame); - scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - scrollArea_->setWidgetResizable(true); - scrollArea_->setAlignment(Qt::AlignTop | Qt::AlignVCenter); - - QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); - - auto spacingAroundForm = new QHBoxLayout; - spacingAroundForm->addStretch(1); - spacingAroundForm->addLayout(formLayout_, 0); - spacingAroundForm->addStretch(1); - - auto scrollAreaContents_ = new QWidget{this}; - scrollAreaContents_->setObjectName("UserSettingScrollWidget"); - scrollAreaContents_->setLayout(spacingAroundForm); - - scrollArea_->setWidget(scrollAreaContents_); - topLayout_->addLayout(topBarLayout_); - topLayout_->addWidget(scrollArea_, Qt::AlignTop); - topLayout_->addStretch(1); - topLayout_->addWidget(versionInfo); - - connect(themeCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &text) { - settings_->setTheme(text.toLower()); - emit themeChanged(); - }); - connect(scaleFactorCombo_, - static_cast(&QComboBox::currentTextChanged), - [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); - connect(fontSizeCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); - connect(fontSelectionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); - connect(emojiFontSelectionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); - - connect(ringtoneCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &ringtone) { - if (ringtone == "Other...") { - QString homeFolder = - QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - auto filepath = QFileDialog::getOpenFileName( - this, tr("Select a file"), homeFolder, tr("All Files (*)")); - if (!filepath.isEmpty()) { - const auto &oldSetting = settings_->ringtone(); - if (oldSetting != "Mute" && oldSetting != "Default") - ringtoneCombo_->removeItem( - ringtoneCombo_->findText(oldSetting)); - settings_->setRingtone(filepath); - ringtoneCombo_->addItem(filepath); - ringtoneCombo_->setCurrentText(filepath); - } else { - ringtoneCombo_->setCurrentText(settings_->ringtone()); - } - } else if (ringtone == "Mute" || ringtone == "Default") { - const auto &oldSetting = settings_->ringtone(); - if (oldSetting != "Mute" && oldSetting != "Default") - ringtoneCombo_->removeItem( - ringtoneCombo_->findText(oldSetting)); - settings_->setRingtone(ringtone); - } - }); - - connect(microphoneCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString µphone) { settings_->setMicrophone(microphone); }); - - connect(cameraCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &camera) { - settings_->setCamera(camera); - std::vector resolutions = - CallDevices::instance().resolutions(camera.toStdString()); - cameraResolutionCombo_->clear(); - for (const auto &resolution : resolutions) - cameraResolutionCombo_->addItem(QString::fromStdString(resolution)); - }); - - connect(cameraResolutionCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &resolution) { - settings_->setCameraResolution(resolution); - std::vector frameRates = CallDevices::instance().frameRates( - settings_->camera().toStdString(), resolution.toStdString()); - cameraFrameRateCombo_->clear(); - for (const auto &frameRate : frameRates) - cameraFrameRateCombo_->addItem(QString::fromStdString(frameRate)); - }); - - connect(cameraFrameRateCombo_, - static_cast(&QComboBox::currentTextChanged), - [this](const QString &frameRate) { settings_->setCameraFrameRate(frameRate); }); - - connect(trayToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setTray(enabled); - if (enabled) { - startInTrayToggle_->setChecked(false); - startInTrayToggle_->setEnabled(true); - startInTrayToggle_->setState(false); - settings_->setStartInTray(false); - } else { - startInTrayToggle_->setChecked(false); - startInTrayToggle_->setState(false); - startInTrayToggle_->setDisabled(true); - settings_->setStartInTray(false); + boxWrap(tr("Theme"), themeCombo_); + + formLayout_->addRow(callsLabel); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Ringtone"), + ringtoneCombo_, + tr("Set the notification sound to play when a call invite arrives")); + boxWrap(tr("Microphone"), microphoneCombo_); + boxWrap(tr("Camera"), cameraCombo_); + boxWrap(tr("Camera resolution"), cameraResolutionCombo_); + boxWrap(tr("Camera frame rate"), cameraFrameRateCombo_); + + ringtoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + ringtoneCombo_->addItem("Mute"); + ringtoneCombo_->addItem("Default"); + ringtoneCombo_->addItem("Other..."); + const QString &ringtone = settings_->ringtone(); + if (!ringtone.isEmpty() && ringtone != "Mute" && ringtone != "Default") + ringtoneCombo_->addItem(ringtone); + microphoneCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraResolutionCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + cameraFrameRateCombo_->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + boxWrap(tr("Allow fallback call assist server"), + useStunServer_, + tr("Will use turn.matrix.org as assist when your home server does not offer one.")); + + formLayout_->addRow(encryptionLabel_); + formLayout_->addRow(new HorizontalLine{this}); + boxWrap(tr("Device ID"), deviceIdValue_); + boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); + boxWrap(tr("Send encrypted messages to verified users only"), + onlyShareKeysWithVerifiedUsers_, + tr("Requires a user to be verified to send encrypted messages to them. This " + "improves safety but makes E2EE more tedious.")); + boxWrap(tr("Share keys with verified users and devices"), + shareKeysWithTrustedUsers_, + tr("Automatically replies to key requests from other users, if they are verified, " + "even if that device shouldn't have access to those keys otherwise.")); + boxWrap(tr("Online Key Backup"), + useOnlineKeyBackup_, + tr("Download message encryption keys from and upload to the encrypted online key " + "backup.")); + formLayout_->addRow(new HorizontalLine{this}); + formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); + formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); + + boxWrap(tr("Master signing key"), + masterSecretCached, + tr("Your most important key. You don't need to have it cached, since not caching " + "it makes it less likely it can be stolen and it is only needed to rotate your " + "other signing keys.")); + boxWrap(tr("User signing key"), + userSigningSecretCached, + tr("The key to verify other users. If it is cached, verifying a user will verify " + "all their devices.")); + boxWrap(tr("Self signing key"), + selfSigningSecretCached, + tr("The key to verify your own devices. If it is cached, verifying one of your devices " + "will mark it verified for all your other devices and for users, that have verified " + "you.")); + boxWrap(tr("Backup key"), + backupSecretCached, + tr("The key to decrypt online key backups. If it is cached, you can enable online " + "key backup to store encryption keys securely encrypted on the server.")); + updateSecretStatus(); + + auto scrollArea_ = new QScrollArea{this}; + scrollArea_->setFrameShape(QFrame::NoFrame); + scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + scrollArea_->setWidgetResizable(true); + scrollArea_->setAlignment(Qt::AlignTop | Qt::AlignVCenter); + + QScroller::grabGesture(scrollArea_, QScroller::TouchGesture); + + auto spacingAroundForm = new QHBoxLayout; + spacingAroundForm->addStretch(1); + spacingAroundForm->addLayout(formLayout_, 0); + spacingAroundForm->addStretch(1); + + auto scrollAreaContents_ = new QWidget{this}; + scrollAreaContents_->setObjectName("UserSettingScrollWidget"); + scrollAreaContents_->setLayout(spacingAroundForm); + + scrollArea_->setWidget(scrollAreaContents_); + topLayout_->addLayout(topBarLayout_); + topLayout_->addWidget(scrollArea_, Qt::AlignTop); + topLayout_->addStretch(1); + topLayout_->addWidget(versionInfo); + + connect(themeCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &text) { + settings_->setTheme(text.toLower()); + emit themeChanged(); + }); + connect(scaleFactorCombo_, + static_cast(&QComboBox::currentTextChanged), + [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); + connect(fontSizeCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); + connect(fontSelectionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); + connect(emojiFontSelectionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); + + connect(ringtoneCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &ringtone) { + if (ringtone == "Other...") { + QString homeFolder = + QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + auto filepath = QFileDialog::getOpenFileName( + this, tr("Select a file"), homeFolder, tr("All Files (*)")); + if (!filepath.isEmpty()) { + const auto &oldSetting = settings_->ringtone(); + if (oldSetting != "Mute" && oldSetting != "Default") + ringtoneCombo_->removeItem(ringtoneCombo_->findText(oldSetting)); + settings_->setRingtone(filepath); + ringtoneCombo_->addItem(filepath); + ringtoneCombo_->setCurrentText(filepath); + } else { + ringtoneCombo_->setCurrentText(settings_->ringtone()); + } + } else if (ringtone == "Mute" || ringtone == "Default") { + const auto &oldSetting = settings_->ringtone(); + if (oldSetting != "Mute" && oldSetting != "Default") + ringtoneCombo_->removeItem(ringtoneCombo_->findText(oldSetting)); + settings_->setRingtone(ringtone); } - emit trayOptionChanged(enabled); - }); - - connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setStartInTray(enabled); - }); - - connect(mobileMode_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMobileMode(enabled); - }); + }); + + connect(microphoneCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString µphone) { settings_->setMicrophone(microphone); }); + + connect(cameraCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &camera) { + settings_->setCamera(camera); + std::vector resolutions = + CallDevices::instance().resolutions(camera.toStdString()); + cameraResolutionCombo_->clear(); + for (const auto &resolution : resolutions) + cameraResolutionCombo_->addItem(QString::fromStdString(resolution)); + }); + + connect(cameraResolutionCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &resolution) { + settings_->setCameraResolution(resolution); + std::vector frameRates = CallDevices::instance().frameRates( + settings_->camera().toStdString(), resolution.toStdString()); + cameraFrameRateCombo_->clear(); + for (const auto &frameRate : frameRates) + cameraFrameRateCombo_->addItem(QString::fromStdString(frameRate)); + }); + + connect(cameraFrameRateCombo_, + static_cast(&QComboBox::currentTextChanged), + [this](const QString &frameRate) { settings_->setCameraFrameRate(frameRate); }); + + connect(trayToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setTray(enabled); + if (enabled) { + startInTrayToggle_->setChecked(false); + startInTrayToggle_->setEnabled(true); + startInTrayToggle_->setState(false); + settings_->setStartInTray(false); + } else { + startInTrayToggle_->setChecked(false); + startInTrayToggle_->setState(false); + startInTrayToggle_->setDisabled(true); + settings_->setStartInTray(false); + } + emit trayOptionChanged(enabled); + }); - connect(groupViewToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setGroupView(enabled); - }); + connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setStartInTray(enabled); + }); - connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setDecryptSidebar(enabled); - }); + connect(mobileMode_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setMobileMode(enabled); + }); - connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setPrivacyScreen(enabled); - if (enabled) { - privacyScreenTimeout_->setEnabled(true); - } else { - privacyScreenTimeout_->setDisabled(true); - } - }); + connect(groupViewToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setGroupView(enabled); + }); - connect(onlyShareKeysWithVerifiedUsers_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setOnlyShareKeysWithVerifiedUsers(enabled); - }); + connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setDecryptSidebar(enabled); + }); - connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setShareKeysWithTrustedUsers(enabled); - }); + connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setPrivacyScreen(enabled); + if (enabled) { + privacyScreenTimeout_->setEnabled(true); + } else { + privacyScreenTimeout_->setDisabled(true); + } + }); + + connect(onlyShareKeysWithVerifiedUsers_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setOnlyShareKeysWithVerifiedUsers(enabled); + }); + + connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setShareKeysWithTrustedUsers(enabled); + }); + + connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { + if (enabled) { + if (QMessageBox::question( + this, + tr("Enable online key backup"), + tr("The Nheko authors recommend not enabling online key backup until " + "symmetric online key backup is available. Enable anyway?")) != + QMessageBox::StandardButton::Yes) { + useOnlineKeyBackup_->setState(false); + return; + } + } + settings_->setUseOnlineKeyBackup(enabled); + }); - connect(useOnlineKeyBackup_, &Toggle::toggled, this, [this](bool enabled) { - if (enabled) { - if (QMessageBox::question( - this, - tr("Enable online key backup"), - tr("The Nheko authors recommend not enabling online key backup until " - "symmetric online key backup is available. Enable anyway?")) != - QMessageBox::StandardButton::Yes) { - useOnlineKeyBackup_->setState(false); - return; - } - } - settings_->setUseOnlineKeyBackup(enabled); - }); + connect(avatarCircles_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAvatarCircles(enabled); + }); - connect(avatarCircles_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAvatarCircles(enabled); + if (JdenticonProvider::isAvailable()) + connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseIdenticon(enabled); }); + else + useIdenticon_->setDisabled(true); - if (JdenticonProvider::isAvailable()) - connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setUseIdenticon(enabled); - }); - else - useIdenticon_->setDisabled(true); + connect( + markdown_, &Toggle::toggled, this, [this](bool enabled) { settings_->setMarkdown(enabled); }); - connect(markdown_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMarkdown(enabled); - }); + connect(animateImagesOnHover_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAnimateImagesOnHover(enabled); + }); - connect(animateImagesOnHover_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAnimateImagesOnHover(enabled); - }); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setTypingNotifications(enabled); + }); - connect(typingNotifications_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setTypingNotifications(enabled); - }); + connect(sortByImportance_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setSortByImportance(enabled); + }); - connect(sortByImportance_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setSortByImportance(enabled); - }); + connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setButtonsInTimeline(enabled); + }); - connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setButtonsInTimeline(enabled); - }); + connect(readReceipts_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setReadReceipts(enabled); + }); - connect(readReceipts_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setReadReceipts(enabled); - }); + connect(desktopNotifications_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setDesktopNotifications(enabled); + }); - connect(desktopNotifications_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setDesktopNotifications(enabled); - }); - - connect(alertOnNotification_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setAlertOnNotification(enabled); - }); + connect(alertOnNotification_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setAlertOnNotification(enabled); + }); - connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setMessageHoverHighlight(enabled); - }); + connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setMessageHoverHighlight(enabled); + }); - connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setEnlargeEmojiOnlyMessages(enabled); - }); + connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setEnlargeEmojiOnlyMessages(enabled); + }); - connect(useStunServer_, &Toggle::toggled, this, [this](bool enabled) { - settings_->setUseStunServer(enabled); - }); + connect(useStunServer_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseStunServer(enabled); + }); - connect(timelineMaxWidthSpin_, - qOverload(&QSpinBox::valueChanged), - this, - [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); + connect(timelineMaxWidthSpin_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); - connect(privacyScreenTimeout_, - qOverload(&QSpinBox::valueChanged), - this, - [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); }); + connect(privacyScreenTimeout_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); }); - connect( - sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); + connect( + sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); - connect( - sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); + connect( + sessionKeysExportBtn, &QPushButton::clicked, this, &UserSettingsPage::exportSessionKeys); - connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() { - olm::request_cross_signing_keys(); - }); + connect(crossSigningRequestBtn, &QPushButton::clicked, this, []() { + olm::request_cross_signing_keys(); + }); - connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() { - olm::download_cross_signing_keys(); - }); + connect(crossSigningDownloadBtn, &QPushButton::clicked, this, []() { + olm::download_cross_signing_keys(); + }); - connect(backBtn_, &QPushButton::clicked, this, [this]() { - settings_->save(); - emit moveBack(); - }); + connect(backBtn_, &QPushButton::clicked, this, [this]() { + settings_->save(); + emit moveBack(); + }); } void UserSettingsPage::showEvent(QShowEvent *) { - // FIXME macOS doesn't show the full option unless a space is added. - utils::restoreCombobox(fontSizeCombo_, QString::number(settings_->fontSize()) + " "); - utils::restoreCombobox(scaleFactorCombo_, QString::number(utils::scaleFactor())); - utils::restoreCombobox(themeCombo_, settings_->theme()); - utils::restoreCombobox(ringtoneCombo_, settings_->ringtone()); - - trayToggle_->setState(settings_->tray()); - startInTrayToggle_->setState(settings_->startInTray()); - groupViewToggle_->setState(settings_->groupView()); - decryptSidebar_->setState(settings_->decryptSidebar()); - privacyScreen_->setState(settings_->privacyScreen()); - onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); - shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); - useOnlineKeyBackup_->setState(settings_->useOnlineKeyBackup()); - avatarCircles_->setState(settings_->avatarCircles()); - typingNotifications_->setState(settings_->typingNotifications()); - sortByImportance_->setState(settings_->sortByImportance()); - timelineButtonsToggle_->setState(settings_->buttonsInTimeline()); - mobileMode_->setState(settings_->mobileMode()); - readReceipts_->setState(settings_->readReceipts()); - markdown_->setState(settings_->markdown()); - desktopNotifications_->setState(settings_->hasDesktopNotifications()); - alertOnNotification_->setState(settings_->hasAlertOnNotification()); - messageHoverHighlight_->setState(settings_->messageHoverHighlight()); - enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages()); - deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); - timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); - privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout()); - - auto mics = CallDevices::instance().names(false, settings_->microphone().toStdString()); - microphoneCombo_->clear(); - for (const auto &m : mics) - microphoneCombo_->addItem(QString::fromStdString(m)); - - auto cameraResolution = settings_->cameraResolution(); - auto cameraFrameRate = settings_->cameraFrameRate(); - - auto cameras = CallDevices::instance().names(true, settings_->camera().toStdString()); - cameraCombo_->clear(); - for (const auto &c : cameras) - cameraCombo_->addItem(QString::fromStdString(c)); - - utils::restoreCombobox(cameraResolutionCombo_, cameraResolution); - utils::restoreCombobox(cameraFrameRateCombo_, cameraFrameRate); - - useStunServer_->setState(settings_->useStunServer()); - - deviceFingerprintValue_->setText( - utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); + // FIXME macOS doesn't show the full option unless a space is added. + utils::restoreCombobox(fontSizeCombo_, QString::number(settings_->fontSize()) + " "); + utils::restoreCombobox(scaleFactorCombo_, QString::number(utils::scaleFactor())); + utils::restoreCombobox(themeCombo_, settings_->theme()); + utils::restoreCombobox(ringtoneCombo_, settings_->ringtone()); + + trayToggle_->setState(settings_->tray()); + startInTrayToggle_->setState(settings_->startInTray()); + groupViewToggle_->setState(settings_->groupView()); + decryptSidebar_->setState(settings_->decryptSidebar()); + privacyScreen_->setState(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); + shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); + useOnlineKeyBackup_->setState(settings_->useOnlineKeyBackup()); + avatarCircles_->setState(settings_->avatarCircles()); + typingNotifications_->setState(settings_->typingNotifications()); + sortByImportance_->setState(settings_->sortByImportance()); + timelineButtonsToggle_->setState(settings_->buttonsInTimeline()); + mobileMode_->setState(settings_->mobileMode()); + readReceipts_->setState(settings_->readReceipts()); + markdown_->setState(settings_->markdown()); + desktopNotifications_->setState(settings_->hasDesktopNotifications()); + alertOnNotification_->setState(settings_->hasAlertOnNotification()); + messageHoverHighlight_->setState(settings_->messageHoverHighlight()); + enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages()); + deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); + timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); + privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout()); + + auto mics = CallDevices::instance().names(false, settings_->microphone().toStdString()); + microphoneCombo_->clear(); + for (const auto &m : mics) + microphoneCombo_->addItem(QString::fromStdString(m)); + + auto cameraResolution = settings_->cameraResolution(); + auto cameraFrameRate = settings_->cameraFrameRate(); + + auto cameras = CallDevices::instance().names(true, settings_->camera().toStdString()); + cameraCombo_->clear(); + for (const auto &c : cameras) + cameraCombo_->addItem(QString::fromStdString(c)); + + utils::restoreCombobox(cameraResolutionCombo_, cameraResolution); + utils::restoreCombobox(cameraFrameRateCombo_, cameraFrameRate); + + useStunServer_->setState(settings_->useStunServer()); + + deviceFingerprintValue_->setText( + utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); } void UserSettingsPage::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } void UserSettingsPage::importSessionKeys() { - const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const QString fileName = - QFileDialog::getOpenFileName(this, tr("Open Sessions File"), homeFolder, ""); - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, tr("Error"), file.errorString()); - return; - } - - auto bin = file.peek(file.size()); - auto payload = std::string(bin.data(), bin.size()); - - bool ok; - auto password = QInputDialog::getText(this, - tr("File Password"), - tr("Enter the passphrase to decrypt the file:"), - QLineEdit::Password, - "", - &ok); - if (!ok) - return; - - if (password.isEmpty()) { - QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); - return; - } - - try { - auto sessions = - mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); - cache::importSessionKeys(std::move(sessions)); - } catch (const std::exception &e) { - QMessageBox::warning(this, tr("Error"), e.what()); - } + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = + QFileDialog::getOpenFileName(this, tr("Open Sessions File"), homeFolder, ""); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(this, tr("Error"), file.errorString()); + return; + } + + auto bin = file.peek(file.size()); + auto payload = std::string(bin.data(), bin.size()); + + bool ok; + auto password = QInputDialog::getText(this, + tr("File Password"), + tr("Enter the passphrase to decrypt the file:"), + QLineEdit::Password, + "", + &ok); + if (!ok) + return; + + if (password.isEmpty()) { + QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); + return; + } + + try { + auto sessions = mtx::crypto::decrypt_exported_sessions(payload, password.toStdString()); + cache::importSessionKeys(std::move(sessions)); + } catch (const std::exception &e) { + QMessageBox::warning(this, tr("Error"), e.what()); + } } void UserSettingsPage::exportSessionKeys() { - // Open password dialog. - bool ok; - auto password = QInputDialog::getText(this, - tr("File Password"), - tr("Enter passphrase to encrypt your session keys:"), - QLineEdit::Password, - "", - &ok); - if (!ok) - return; - - if (password.isEmpty()) { - QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); - return; - } - - // Open file dialog to save the file. - const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - const QString fileName = - QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", ""); - - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, tr("Error"), file.errorString()); - return; - } - - // Export sessions & save to file. - try { - auto encrypted_blob = mtx::crypto::encrypt_exported_sessions( - cache::exportSessionKeys(), password.toStdString()); - - QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob)); - - QString prefix("-----BEGIN MEGOLM SESSION DATA-----"); - QString suffix("-----END MEGOLM SESSION DATA-----"); - QString newline("\n"); - QTextStream out(&file); - out << prefix << newline << b64 << newline << suffix << newline; - file.close(); - } catch (const std::exception &e) { - QMessageBox::warning(this, tr("Error"), e.what()); - } + // Open password dialog. + bool ok; + auto password = QInputDialog::getText(this, + tr("File Password"), + tr("Enter passphrase to encrypt your session keys:"), + QLineEdit::Password, + "", + &ok); + if (!ok) + return; + + if (password.isEmpty()) { + QMessageBox::warning(this, tr("Error"), tr("The password cannot be empty")); + return; + } + + // Open file dialog to save the file. + const QString homeFolder = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + const QString fileName = + QFileDialog::getSaveFileName(this, tr("File to save the exported session keys"), "", ""); + + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("Error"), file.errorString()); + return; + } + + // Export sessions & save to file. + try { + auto encrypted_blob = mtx::crypto::encrypt_exported_sessions(cache::exportSessionKeys(), + password.toStdString()); + + QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob)); + + QString prefix("-----BEGIN MEGOLM SESSION DATA-----"); + QString suffix("-----END MEGOLM SESSION DATA-----"); + QString newline("\n"); + QTextStream out(&file); + out << prefix << newline << b64 << newline << suffix << newline; + file.close(); + } catch (const std::exception &e) { + QMessageBox::warning(this, tr("Error"), e.what()); + } } void UserSettingsPage::updateSecretStatus() { - QString ok = "QLabel { color : #00cc66; }"; - QString notSoOk = "QLabel { color : #ff9933; }"; - - auto updateLabel = [&ok, ¬SoOk](QLabel *label, const std::string &secretName) { - if (cache::secret(secretName)) { - label->setStyleSheet(ok); - label->setText(tr("CACHED")); - } else { - if (secretName == mtx::secret_storage::secrets::cross_signing_master) - label->setStyleSheet(ok); - else - label->setStyleSheet(notSoOk); - label->setText(tr("NOT CACHED")); - } - }; - - updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master); - updateLabel(userSigningSecretCached, - mtx::secret_storage::secrets::cross_signing_user_signing); - updateLabel(selfSigningSecretCached, - mtx::secret_storage::secrets::cross_signing_self_signing); - updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1); + QString ok = "QLabel { color : #00cc66; }"; + QString notSoOk = "QLabel { color : #ff9933; }"; + + auto updateLabel = [&ok, ¬SoOk](QLabel *label, const std::string &secretName) { + if (cache::secret(secretName)) { + label->setStyleSheet(ok); + label->setText(tr("CACHED")); + } else { + if (secretName == mtx::secret_storage::secrets::cross_signing_master) + label->setStyleSheet(ok); + else + label->setStyleSheet(notSoOk); + label->setText(tr("NOT CACHED")); + } + }; + + updateLabel(masterSecretCached, mtx::secret_storage::secrets::cross_signing_master); + updateLabel(userSigningSecretCached, mtx::secret_storage::secrets::cross_signing_user_signing); + updateLabel(selfSigningSecretCached, mtx::secret_storage::secrets::cross_signing_self_signing); + updateLabel(backupSecretCached, mtx::secret_storage::secrets::megolm_backup_v1); } diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index bcd9439b..31e28db2 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -30,402 +30,395 @@ constexpr int LayoutBottomMargin = LayoutTopMargin; class UserSettings : public QObject { - Q_OBJECT - - Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) - Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE - setMessageHoverHighlight NOTIFY messageHoverHighlightChanged) - Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE - setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged) - Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged) - Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) - Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) - Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) - Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover - NOTIFY animateImagesOnHoverChanged) - Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications - NOTIFY typingNotificationsChanged) - Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY - roomSortingChanged) - Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY - buttonInTimelineChanged) - Q_PROPERTY( - bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged) - Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE - setDesktopNotifications NOTIFY desktopNotificationsChanged) - Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification - NOTIFY alertOnNotificationChanged) - Q_PROPERTY( - bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) - Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY - decryptSidebarChanged) - Q_PROPERTY( - bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) - Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout - NOTIFY privacyScreenTimeoutChanged) - Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY - timelineMaxWidthChanged) - Q_PROPERTY( - int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged) - Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY - communityListWidthChanged) - Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged) - Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) - Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) - Q_PROPERTY( - QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) - Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged) - Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged) - Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged) - Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged) - Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY - cameraResolutionChanged) - Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY - cameraFrameRateChanged) - Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate - NOTIFY screenShareFrameRateChanged) - Q_PROPERTY(bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY - screenSharePiPChanged) - Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE - setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged) - Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE - setScreenShareHideCursor NOTIFY screenShareHideCursorChanged) - Q_PROPERTY( - bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) - Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE - setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) - Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE - setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) - Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup - NOTIFY useOnlineKeyBackupChanged) - Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) - Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY( - QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) - Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) - Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) - Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE - setDisableCertificateValidation NOTIFY disableCertificateValidationChanged) - Q_PROPERTY( - bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged) - - UserSettings(); + Q_OBJECT + + Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) + Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight + NOTIFY messageHoverHighlightChanged) + Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE + setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged) + Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged) + Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) + Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) + Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) + Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover + NOTIFY animateImagesOnHoverChanged) + Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY + typingNotificationsChanged) + Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY + roomSortingChanged) + Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY + buttonInTimelineChanged) + Q_PROPERTY(bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged) + Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE setDesktopNotifications + NOTIFY desktopNotificationsChanged) + Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification + NOTIFY alertOnNotificationChanged) + Q_PROPERTY( + bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) + Q_PROPERTY( + bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) + Q_PROPERTY( + bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged) + Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout + NOTIFY privacyScreenTimeoutChanged) + Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY + timelineMaxWidthChanged) + Q_PROPERTY( + int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged) + Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY + communityListWidthChanged) + Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged) + Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) + Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) + Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged) + Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged) + Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged) + Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged) + Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY + cameraResolutionChanged) + Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY + cameraFrameRateChanged) + Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate + NOTIFY screenShareFrameRateChanged) + Q_PROPERTY( + bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY screenSharePiPChanged) + Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE + setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged) + Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE setScreenShareHideCursor + NOTIFY screenShareHideCursorChanged) + Q_PROPERTY( + bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) + Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE + setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) + Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE + setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) + Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup NOTIFY + useOnlineKeyBackupChanged) + Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) + Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) + Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE + setDisableCertificateValidation NOTIFY disableCertificateValidationChanged) + Q_PROPERTY(bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged) + + UserSettings(); public: - static QSharedPointer instance(); - static void initialize(std::optional profile); - - QSettings *qsettings() { return &settings; } - - enum class Presence - { - AutomaticPresence, - Online, - Unavailable, - Offline, - }; - Q_ENUM(Presence) - - void save(); - void load(std::optional profile); - void applyTheme(); - void setTheme(QString theme); - void setMessageHoverHighlight(bool state); - void setEnlargeEmojiOnlyMessages(bool state); - void setTray(bool state); - void setStartInTray(bool state); - void setMobileMode(bool mode); - void setFontSize(double size); - void setFontFamily(QString family); - void setEmojiFontFamily(QString family); - void setGroupView(bool state); - void setMarkdown(bool state); - void setAnimateImagesOnHover(bool state); - void setReadReceipts(bool state); - void setTypingNotifications(bool state); - void setSortByImportance(bool state); - void setButtonsInTimeline(bool state); - void setTimelineMaxWidth(int state); - void setCommunityListWidth(int state); - void setRoomListWidth(int state); - void setDesktopNotifications(bool state); - void setAlertOnNotification(bool state); - void setAvatarCircles(bool state); - void setDecryptSidebar(bool state); - void setPrivacyScreen(bool state); - void setPrivacyScreenTimeout(int state); - void setPresence(Presence state); - void setRingtone(QString ringtone); - void setMicrophone(QString microphone); - void setCamera(QString camera); - void setCameraResolution(QString resolution); - void setCameraFrameRate(QString frameRate); - void setScreenShareFrameRate(int frameRate); - void setScreenSharePiP(bool state); - void setScreenShareRemoteVideo(bool state); - void setScreenShareHideCursor(bool state); - void setUseStunServer(bool state); - void setOnlyShareKeysWithVerifiedUsers(bool state); - void setShareKeysWithTrustedUsers(bool state); - void setUseOnlineKeyBackup(bool state); - void setProfile(QString profile); - void setUserId(QString userId); - void setAccessToken(QString accessToken); - void setDeviceId(QString deviceId); - void setHomeserver(QString homeserver); - void setDisableCertificateValidation(bool disabled); - void setHiddenTags(QStringList hiddenTags); - void setUseIdenticon(bool state); - - QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } - bool messageHoverHighlight() const { return messageHoverHighlight_; } - bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; } - bool tray() const { return tray_; } - bool startInTray() const { return startInTray_; } - bool groupView() const { return groupView_; } - bool avatarCircles() const { return avatarCircles_; } - bool decryptSidebar() const { return decryptSidebar_; } - bool privacyScreen() const { return privacyScreen_; } - int privacyScreenTimeout() const { return privacyScreenTimeout_; } - bool markdown() const { return markdown_; } - bool animateImagesOnHover() const { return animateImagesOnHover_; } - bool typingNotifications() const { return typingNotifications_; } - bool sortByImportance() const { return sortByImportance_; } - bool buttonsInTimeline() const { return buttonsInTimeline_; } - bool mobileMode() const { return mobileMode_; } - bool readReceipts() const { return readReceipts_; } - bool hasDesktopNotifications() const { return hasDesktopNotifications_; } - bool hasAlertOnNotification() const { return hasAlertOnNotification_; } - bool hasNotifications() const - { - return hasDesktopNotifications() || hasAlertOnNotification(); + static QSharedPointer instance(); + static void initialize(std::optional profile); + + QSettings *qsettings() { return &settings; } + + enum class Presence + { + AutomaticPresence, + Online, + Unavailable, + Offline, + }; + Q_ENUM(Presence) + + void save(); + void load(std::optional profile); + void applyTheme(); + void setTheme(QString theme); + void setMessageHoverHighlight(bool state); + void setEnlargeEmojiOnlyMessages(bool state); + void setTray(bool state); + void setStartInTray(bool state); + void setMobileMode(bool mode); + void setFontSize(double size); + void setFontFamily(QString family); + void setEmojiFontFamily(QString family); + void setGroupView(bool state); + void setMarkdown(bool state); + void setAnimateImagesOnHover(bool state); + void setReadReceipts(bool state); + void setTypingNotifications(bool state); + void setSortByImportance(bool state); + void setButtonsInTimeline(bool state); + void setTimelineMaxWidth(int state); + void setCommunityListWidth(int state); + void setRoomListWidth(int state); + void setDesktopNotifications(bool state); + void setAlertOnNotification(bool state); + void setAvatarCircles(bool state); + void setDecryptSidebar(bool state); + void setPrivacyScreen(bool state); + void setPrivacyScreenTimeout(int state); + void setPresence(Presence state); + void setRingtone(QString ringtone); + void setMicrophone(QString microphone); + void setCamera(QString camera); + void setCameraResolution(QString resolution); + void setCameraFrameRate(QString frameRate); + void setScreenShareFrameRate(int frameRate); + void setScreenSharePiP(bool state); + void setScreenShareRemoteVideo(bool state); + void setScreenShareHideCursor(bool state); + void setUseStunServer(bool state); + void setOnlyShareKeysWithVerifiedUsers(bool state); + void setShareKeysWithTrustedUsers(bool state); + void setUseOnlineKeyBackup(bool state); + void setProfile(QString profile); + void setUserId(QString userId); + void setAccessToken(QString accessToken); + void setDeviceId(QString deviceId); + void setHomeserver(QString homeserver); + void setDisableCertificateValidation(bool disabled); + void setHiddenTags(QStringList hiddenTags); + void setUseIdenticon(bool state); + + QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } + bool messageHoverHighlight() const { return messageHoverHighlight_; } + bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; } + bool tray() const { return tray_; } + bool startInTray() const { return startInTray_; } + bool groupView() const { return groupView_; } + bool avatarCircles() const { return avatarCircles_; } + bool decryptSidebar() const { return decryptSidebar_; } + bool privacyScreen() const { return privacyScreen_; } + int privacyScreenTimeout() const { return privacyScreenTimeout_; } + bool markdown() const { return markdown_; } + bool animateImagesOnHover() const { return animateImagesOnHover_; } + bool typingNotifications() const { return typingNotifications_; } + bool sortByImportance() const { return sortByImportance_; } + bool buttonsInTimeline() const { return buttonsInTimeline_; } + bool mobileMode() const { return mobileMode_; } + bool readReceipts() const { return readReceipts_; } + bool hasDesktopNotifications() const { return hasDesktopNotifications_; } + bool hasAlertOnNotification() const { return hasAlertOnNotification_; } + bool hasNotifications() const { return hasDesktopNotifications() || hasAlertOnNotification(); } + int timelineMaxWidth() const { return timelineMaxWidth_; } + int communityListWidth() const { return communityListWidth_; } + int roomListWidth() const { return roomListWidth_; } + double fontSize() const { return baseFontSize_; } + QString font() const { return font_; } + QString emojiFont() const + { + if (emojiFont_ == "Default") { + return tr("Default"); } - int timelineMaxWidth() const { return timelineMaxWidth_; } - int communityListWidth() const { return communityListWidth_; } - int roomListWidth() const { return roomListWidth_; } - double fontSize() const { return baseFontSize_; } - QString font() const { return font_; } - QString emojiFont() const - { - if (emojiFont_ == "Default") { - return tr("Default"); - } - - return emojiFont_; - } - Presence presence() const { return presence_; } - QString ringtone() const { return ringtone_; } - QString microphone() const { return microphone_; } - QString camera() const { return camera_; } - QString cameraResolution() const { return cameraResolution_; } - QString cameraFrameRate() const { return cameraFrameRate_; } - int screenShareFrameRate() const { return screenShareFrameRate_; } - bool screenSharePiP() const { return screenSharePiP_; } - bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; } - bool screenShareHideCursor() const { return screenShareHideCursor_; } - bool useStunServer() const { return useStunServer_; } - bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } - bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } - bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; } - QString profile() const { return profile_; } - QString userId() const { return userId_; } - QString accessToken() const { return accessToken_; } - QString deviceId() const { return deviceId_; } - QString homeserver() const { return homeserver_; } - bool disableCertificateValidation() const { return disableCertificateValidation_; } - QStringList hiddenTags() const { return hiddenTags_; } - bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); } + + return emojiFont_; + } + Presence presence() const { return presence_; } + QString ringtone() const { return ringtone_; } + QString microphone() const { return microphone_; } + QString camera() const { return camera_; } + QString cameraResolution() const { return cameraResolution_; } + QString cameraFrameRate() const { return cameraFrameRate_; } + int screenShareFrameRate() const { return screenShareFrameRate_; } + bool screenSharePiP() const { return screenSharePiP_; } + bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; } + bool screenShareHideCursor() const { return screenShareHideCursor_; } + bool useStunServer() const { return useStunServer_; } + bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } + bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } + bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; } + QString profile() const { return profile_; } + QString userId() const { return userId_; } + QString accessToken() const { return accessToken_; } + QString deviceId() const { return deviceId_; } + QString homeserver() const { return homeserver_; } + bool disableCertificateValidation() const { return disableCertificateValidation_; } + QStringList hiddenTags() const { return hiddenTags_; } + bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); } signals: - void groupViewStateChanged(bool state); - void roomSortingChanged(bool state); - void themeChanged(QString state); - void messageHoverHighlightChanged(bool state); - void enlargeEmojiOnlyMessagesChanged(bool state); - void trayChanged(bool state); - void startInTrayChanged(bool state); - void markdownChanged(bool state); - void animateImagesOnHoverChanged(bool state); - void typingNotificationsChanged(bool state); - void buttonInTimelineChanged(bool state); - void readReceiptsChanged(bool state); - void desktopNotificationsChanged(bool state); - void alertOnNotificationChanged(bool state); - void avatarCirclesChanged(bool state); - void decryptSidebarChanged(bool state); - void privacyScreenChanged(bool state); - void privacyScreenTimeoutChanged(int state); - void timelineMaxWidthChanged(int state); - void roomListWidthChanged(int state); - void communityListWidthChanged(int state); - void mobileModeChanged(bool mode); - void fontSizeChanged(double state); - void fontChanged(QString state); - void emojiFontChanged(QString state); - void presenceChanged(Presence state); - void ringtoneChanged(QString ringtone); - void microphoneChanged(QString microphone); - void cameraChanged(QString camera); - void cameraResolutionChanged(QString resolution); - void cameraFrameRateChanged(QString frameRate); - void screenShareFrameRateChanged(int frameRate); - void screenSharePiPChanged(bool state); - void screenShareRemoteVideoChanged(bool state); - void screenShareHideCursorChanged(bool state); - void useStunServerChanged(bool state); - void onlyShareKeysWithVerifiedUsersChanged(bool state); - void shareKeysWithTrustedUsersChanged(bool state); - void useOnlineKeyBackupChanged(bool state); - void profileChanged(QString profile); - void userIdChanged(QString userId); - void accessTokenChanged(QString accessToken); - void deviceIdChanged(QString deviceId); - void homeserverChanged(QString homeserver); - void disableCertificateValidationChanged(bool disabled); - void useIdenticonChanged(bool state); + void groupViewStateChanged(bool state); + void roomSortingChanged(bool state); + void themeChanged(QString state); + void messageHoverHighlightChanged(bool state); + void enlargeEmojiOnlyMessagesChanged(bool state); + void trayChanged(bool state); + void startInTrayChanged(bool state); + void markdownChanged(bool state); + void animateImagesOnHoverChanged(bool state); + void typingNotificationsChanged(bool state); + void buttonInTimelineChanged(bool state); + void readReceiptsChanged(bool state); + void desktopNotificationsChanged(bool state); + void alertOnNotificationChanged(bool state); + void avatarCirclesChanged(bool state); + void decryptSidebarChanged(bool state); + void privacyScreenChanged(bool state); + void privacyScreenTimeoutChanged(int state); + void timelineMaxWidthChanged(int state); + void roomListWidthChanged(int state); + void communityListWidthChanged(int state); + void mobileModeChanged(bool mode); + void fontSizeChanged(double state); + void fontChanged(QString state); + void emojiFontChanged(QString state); + void presenceChanged(Presence state); + void ringtoneChanged(QString ringtone); + void microphoneChanged(QString microphone); + void cameraChanged(QString camera); + void cameraResolutionChanged(QString resolution); + void cameraFrameRateChanged(QString frameRate); + void screenShareFrameRateChanged(int frameRate); + void screenSharePiPChanged(bool state); + void screenShareRemoteVideoChanged(bool state); + void screenShareHideCursorChanged(bool state); + void useStunServerChanged(bool state); + void onlyShareKeysWithVerifiedUsersChanged(bool state); + void shareKeysWithTrustedUsersChanged(bool state); + void useOnlineKeyBackupChanged(bool state); + void profileChanged(QString profile); + void userIdChanged(QString userId); + void accessTokenChanged(QString accessToken); + void deviceIdChanged(QString deviceId); + void homeserverChanged(QString homeserver); + void disableCertificateValidationChanged(bool disabled); + void useIdenticonChanged(bool state); private: - // Default to system theme if QT_QPA_PLATFORMTHEME var is set. - QString defaultTheme_ = - QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() - ? "light" - : "system"; - QString theme_; - bool messageHoverHighlight_; - bool enlargeEmojiOnlyMessages_; - bool tray_; - bool startInTray_; - bool groupView_; - bool markdown_; - bool animateImagesOnHover_; - bool typingNotifications_; - bool sortByImportance_; - bool buttonsInTimeline_; - bool readReceipts_; - bool hasDesktopNotifications_; - bool hasAlertOnNotification_; - bool avatarCircles_; - bool decryptSidebar_; - bool privacyScreen_; - int privacyScreenTimeout_; - bool shareKeysWithTrustedUsers_; - bool onlyShareKeysWithVerifiedUsers_; - bool useOnlineKeyBackup_; - bool mobileMode_; - int timelineMaxWidth_; - int roomListWidth_; - int communityListWidth_; - double baseFontSize_; - QString font_; - QString emojiFont_; - Presence presence_; - QString ringtone_; - QString microphone_; - QString camera_; - QString cameraResolution_; - QString cameraFrameRate_; - int screenShareFrameRate_; - bool screenSharePiP_; - bool screenShareRemoteVideo_; - bool screenShareHideCursor_; - bool useStunServer_; - bool disableCertificateValidation_ = false; - QString profile_; - QString userId_; - QString accessToken_; - QString deviceId_; - QString homeserver_; - QStringList hiddenTags_; - bool useIdenticon_; - - QSettings settings; - - static QSharedPointer instance_; + // Default to system theme if QT_QPA_PLATFORMTHEME var is set. + QString defaultTheme_ = + QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() + ? "light" + : "system"; + QString theme_; + bool messageHoverHighlight_; + bool enlargeEmojiOnlyMessages_; + bool tray_; + bool startInTray_; + bool groupView_; + bool markdown_; + bool animateImagesOnHover_; + bool typingNotifications_; + bool sortByImportance_; + bool buttonsInTimeline_; + bool readReceipts_; + bool hasDesktopNotifications_; + bool hasAlertOnNotification_; + bool avatarCircles_; + bool decryptSidebar_; + bool privacyScreen_; + int privacyScreenTimeout_; + bool shareKeysWithTrustedUsers_; + bool onlyShareKeysWithVerifiedUsers_; + bool useOnlineKeyBackup_; + bool mobileMode_; + int timelineMaxWidth_; + int roomListWidth_; + int communityListWidth_; + double baseFontSize_; + QString font_; + QString emojiFont_; + Presence presence_; + QString ringtone_; + QString microphone_; + QString camera_; + QString cameraResolution_; + QString cameraFrameRate_; + int screenShareFrameRate_; + bool screenSharePiP_; + bool screenShareRemoteVideo_; + bool screenShareHideCursor_; + bool useStunServer_; + bool disableCertificateValidation_ = false; + QString profile_; + QString userId_; + QString accessToken_; + QString deviceId_; + QString homeserver_; + QStringList hiddenTags_; + bool useIdenticon_; + + QSettings settings; + + static QSharedPointer instance_; }; class HorizontalLine : public QFrame { - Q_OBJECT + Q_OBJECT public: - HorizontalLine(QWidget *parent = nullptr); + HorizontalLine(QWidget *parent = nullptr); }; class UserSettingsPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - UserSettingsPage(QSharedPointer settings, QWidget *parent = nullptr); + UserSettingsPage(QSharedPointer settings, QWidget *parent = nullptr); protected: - void showEvent(QShowEvent *event) override; - void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + void paintEvent(QPaintEvent *event) override; signals: - void moveBack(); - void trayOptionChanged(bool value); - void themeChanged(); - void decryptSidebarChanged(); + void moveBack(); + void trayOptionChanged(bool value); + void themeChanged(); + void decryptSidebarChanged(); public slots: - void updateSecretStatus(); + void updateSecretStatus(); private slots: - void importSessionKeys(); - void exportSessionKeys(); + void importSessionKeys(); + void exportSessionKeys(); private: - // Layouts - QVBoxLayout *topLayout_; - QHBoxLayout *topBarLayout_; - QFormLayout *formLayout_; - - // Shared settings object. - QSharedPointer settings_; - - Toggle *trayToggle_; - Toggle *startInTrayToggle_; - Toggle *groupViewToggle_; - Toggle *timelineButtonsToggle_; - Toggle *typingNotifications_; - Toggle *messageHoverHighlight_; - Toggle *enlargeEmojiOnlyMessages_; - Toggle *sortByImportance_; - Toggle *readReceipts_; - Toggle *markdown_; - Toggle *animateImagesOnHover_; - Toggle *desktopNotifications_; - Toggle *alertOnNotification_; - Toggle *avatarCircles_; - Toggle *useIdenticon_; - Toggle *useStunServer_; - Toggle *decryptSidebar_; - Toggle *privacyScreen_; - QSpinBox *privacyScreenTimeout_; - Toggle *shareKeysWithTrustedUsers_; - Toggle *onlyShareKeysWithVerifiedUsers_; - Toggle *useOnlineKeyBackup_; - Toggle *mobileMode_; - QLabel *deviceFingerprintValue_; - QLabel *deviceIdValue_; - QLabel *backupSecretCached; - QLabel *masterSecretCached; - QLabel *selfSigningSecretCached; - QLabel *userSigningSecretCached; - - QComboBox *themeCombo_; - QComboBox *scaleFactorCombo_; - QComboBox *fontSizeCombo_; - QFontComboBox *fontSelectionCombo_; - QComboBox *emojiFontSelectionCombo_; - QComboBox *ringtoneCombo_; - QComboBox *microphoneCombo_; - QComboBox *cameraCombo_; - QComboBox *cameraResolutionCombo_; - QComboBox *cameraFrameRateCombo_; - - QSpinBox *timelineMaxWidthSpin_; - - int sideMargin_ = 0; + // Layouts + QVBoxLayout *topLayout_; + QHBoxLayout *topBarLayout_; + QFormLayout *formLayout_; + + // Shared settings object. + QSharedPointer settings_; + + Toggle *trayToggle_; + Toggle *startInTrayToggle_; + Toggle *groupViewToggle_; + Toggle *timelineButtonsToggle_; + Toggle *typingNotifications_; + Toggle *messageHoverHighlight_; + Toggle *enlargeEmojiOnlyMessages_; + Toggle *sortByImportance_; + Toggle *readReceipts_; + Toggle *markdown_; + Toggle *animateImagesOnHover_; + Toggle *desktopNotifications_; + Toggle *alertOnNotification_; + Toggle *avatarCircles_; + Toggle *useIdenticon_; + Toggle *useStunServer_; + Toggle *decryptSidebar_; + Toggle *privacyScreen_; + QSpinBox *privacyScreenTimeout_; + Toggle *shareKeysWithTrustedUsers_; + Toggle *onlyShareKeysWithVerifiedUsers_; + Toggle *useOnlineKeyBackup_; + Toggle *mobileMode_; + QLabel *deviceFingerprintValue_; + QLabel *deviceIdValue_; + QLabel *backupSecretCached; + QLabel *masterSecretCached; + QLabel *selfSigningSecretCached; + QLabel *userSigningSecretCached; + + QComboBox *themeCombo_; + QComboBox *scaleFactorCombo_; + QComboBox *fontSizeCombo_; + QFontComboBox *fontSelectionCombo_; + QComboBox *emojiFontSelectionCombo_; + QComboBox *ringtoneCombo_; + QComboBox *microphoneCombo_; + QComboBox *cameraCombo_; + QComboBox *cameraResolutionCombo_; + QComboBox *cameraFrameRateCombo_; + + QSpinBox *timelineMaxWidthSpin_; + + int sideMargin_ = 0; }; diff --git a/src/UsersModel.cpp b/src/UsersModel.cpp index 13b05f0e..f82353cc 100644 --- a/src/UsersModel.cpp +++ b/src/UsersModel.cpp @@ -14,51 +14,51 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent) : QAbstractListModel(parent) , room_id(roomId) { - roomMembers_ = cache::roomMembers(roomId); - for (const auto &m : roomMembers_) { - displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m))); - userids.push_back(QString::fromStdString(m)); - } + roomMembers_ = cache::roomMembers(roomId); + for (const auto &m : roomMembers_) { + displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m))); + userids.push_back(QString::fromStdString(m)); + } } QHash UsersModel::roleNames() const { - return { - {CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::DisplayName, "displayName"}, - {Roles::AvatarUrl, "avatarUrl"}, - {Roles::UserID, "userid"}, - }; + return { + {CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::UserID, "userid"}, + }; } QVariant UsersModel::data(const QModelIndex &index, int role) const { - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: - if (UserSettings::instance()->markdown()) - return QString("[%1](https://matrix.to/#/%2)") - .arg(displayNames[index.row()].toHtmlEscaped()) - .arg(QString(QUrl::toPercentEncoding(userids[index.row()]))); - else - return displayNames[index.row()]; - case CompletionModel::SearchRole: - return displayNames[index.row()]; - case Qt::DisplayRole: - case Roles::DisplayName: - return displayNames[index.row()].toHtmlEscaped(); - case CompletionModel::SearchRole2: - return userids[index.row()]; - case Roles::AvatarUrl: - return cache::avatarUrl(QString::fromStdString(room_id), - QString::fromStdString(roomMembers_[index.row()])); - case Roles::UserID: - return userids[index.row()].toHtmlEscaped(); - } + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + if (UserSettings::instance()->markdown()) + return QString("[%1](https://matrix.to/#/%2)") + .arg(displayNames[index.row()].toHtmlEscaped()) + .arg(QString(QUrl::toPercentEncoding(userids[index.row()]))); + else + return displayNames[index.row()]; + case CompletionModel::SearchRole: + return displayNames[index.row()]; + case Qt::DisplayRole: + case Roles::DisplayName: + return displayNames[index.row()].toHtmlEscaped(); + case CompletionModel::SearchRole2: + return userids[index.row()]; + case Roles::AvatarUrl: + return cache::avatarUrl(QString::fromStdString(room_id), + QString::fromStdString(roomMembers_[index.row()])); + case Roles::UserID: + return userids[index.row()].toHtmlEscaped(); } - return {}; + } + return {}; } diff --git a/src/UsersModel.h b/src/UsersModel.h index 5bc94b0f..e719a8bd 100644 --- a/src/UsersModel.h +++ b/src/UsersModel.h @@ -9,25 +9,25 @@ class UsersModel : public QAbstractListModel { public: - enum Roles - { - AvatarUrl = Qt::UserRole, - DisplayName, - UserID, - }; + enum Roles + { + AvatarUrl = Qt::UserRole, + DisplayName, + UserID, + }; - UsersModel(const std::string &roomId, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override - { - (void)parent; - return (int)roomMembers_.size(); - } - QVariant data(const QModelIndex &index, int role) const override; + UsersModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomMembers_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; private: - std::string room_id; - std::vector roomMembers_; - std::vector displayNames; - std::vector userids; + std::string room_id; + std::vector roomMembers_; + std::vector displayNames; + std::vector userids; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f524c6c..b0fb01b1 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -38,155 +38,154 @@ template static DescInfo createDescriptionInfo(const Event &event, const QString &localUser, const QString &displayName) { - const auto msg = std::get(event); - const auto sender = QString::fromStdString(msg.sender); + const auto msg = std::get(event); + const auto sender = QString::fromStdString(msg.sender); - const auto username = displayName; - const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); - auto body = utils::event_body(event).trimmed(); - if (mtx::accessors::relations(event).reply_to()) - body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); + const auto username = displayName; + const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts); + auto body = utils::event_body(event).trimmed(); + if (mtx::accessors::relations(event).reply_to()) + body = QString::fromStdString(utils::stripReplyFromBody(body.toStdString())); - return DescInfo{QString::fromStdString(msg.event_id), - sender, - utils::messageDescription(username, body, sender == localUser), - utils::descriptiveTime(ts), - msg.origin_server_ts, - ts}; + return DescInfo{QString::fromStdString(msg.event_id), + sender, + utils::messageDescription(username, body, sender == localUser), + utils::descriptiveTime(ts), + msg.origin_server_ts, + ts}; } std::string utils::stripReplyFromBody(const std::string &bodyi) { - QString body = QString::fromStdString(bodyi); - QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); - while (body.startsWith(">")) - body.remove(plainQuote); - if (body.startsWith("\n")) - body.remove(0, 1); + QString body = QString::fromStdString(bodyi); + QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption); + while (body.startsWith(">")) + body.remove(plainQuote); + if (body.startsWith("\n")) + body.remove(0, 1); - body.replace("@room", QString::fromUtf8("@\u2060room")); - return body.toStdString(); + body.replace("@room", QString::fromUtf8("@\u2060room")); + return body.toStdString(); } std::string utils::stripReplyFromFormattedBody(const std::string &formatted_bodyi) { - QString formatted_body = QString::fromStdString(formatted_bodyi); - formatted_body.remove(QRegularExpression(".*", - QRegularExpression::DotMatchesEverythingOption)); - formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); - return formatted_body.toStdString(); + QString formatted_body = QString::fromStdString(formatted_bodyi); + formatted_body.remove(QRegularExpression(".*", + QRegularExpression::DotMatchesEverythingOption)); + formatted_body.replace("@room", QString::fromUtf8("@\u2060room")); + return formatted_body.toStdString(); } RelatedInfo utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_) { - RelatedInfo related = {}; - related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); - related.related_event = std::move(id); - related.type = mtx::accessors::msg_type(event); + RelatedInfo related = {}; + related.quoted_user = QString::fromStdString(mtx::accessors::sender(event)); + related.related_event = std::move(id); + related.type = mtx::accessors::msg_type(event); - // get body, strip reply fallback, then transform the event to text, if it is a media event - // etc - related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); - related.quoted_body = - QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); - related.quoted_body = utils::getQuoteBody(related); + // get body, strip reply fallback, then transform the event to text, if it is a media event + // etc + related.quoted_body = QString::fromStdString(mtx::accessors::body(event)); + related.quoted_body = + QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString())); + related.quoted_body = utils::getQuoteBody(related); - // get quoted body and strip reply fallback - related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); - related.quoted_formatted_body = QString::fromStdString( - stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); - related.room = room_id_; + // get quoted body and strip reply fallback + related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event); + related.quoted_formatted_body = QString::fromStdString( + stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString())); + related.room = room_id_; - return related; + return related; } QString utils::localUser() { - return QString::fromStdString(http::client()->user_id().to_string()); + return QString::fromStdString(http::client()->user_id().to_string()); } bool utils::codepointIsEmoji(uint code) { - // TODO: Be more precise here. - return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || - (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; + // TODO: Be more precise here. + return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || + (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; } QString utils::replaceEmoji(const QString &body) { - QString fmtBody; - fmtBody.reserve(body.size()); - - QVector utf32_string = body.toUcs4(); - - bool insideFontBlock = false; - for (auto &code : utf32_string) { - if (utils::codepointIsEmoji(code)) { - if (!insideFontBlock) { - fmtBody += QStringLiteral("emojiFont() % - QStringLiteral("\">"); - insideFontBlock = true; - } - - } else { - if (insideFontBlock) { - fmtBody += QStringLiteral(""); - insideFontBlock = false; - } - } - if (QChar::requiresSurrogates(code)) { - QChar emoji[] = {static_cast(QChar::highSurrogate(code)), - static_cast(QChar::lowSurrogate(code))}; - fmtBody.append(emoji, 2); - } else { - fmtBody.append(QChar(static_cast(code))); - } - } - if (insideFontBlock) { + QString fmtBody; + fmtBody.reserve(body.size()); + + QVector utf32_string = body.toUcs4(); + + bool insideFontBlock = false; + for (auto &code : utf32_string) { + if (utils::codepointIsEmoji(code)) { + if (!insideFontBlock) { + fmtBody += QStringLiteral("emojiFont() % + QStringLiteral("\">"); + insideFontBlock = true; + } + + } else { + if (insideFontBlock) { fmtBody += QStringLiteral(""); + insideFontBlock = false; + } } + if (QChar::requiresSurrogates(code)) { + QChar emoji[] = {static_cast(QChar::highSurrogate(code)), + static_cast(QChar::lowSurrogate(code))}; + fmtBody.append(emoji, 2); + } else { + fmtBody.append(QChar(static_cast(code))); + } + } + if (insideFontBlock) { + fmtBody += QStringLiteral(""); + } - return fmtBody; + return fmtBody; } void utils::setScaleFactor(float factor) { - if (factor < 1 || factor > 3) - return; + if (factor < 1 || factor > 3) + return; - QSettings settings; - settings.setValue("settings/scale_factor", factor); + QSettings settings; + settings.setValue("settings/scale_factor", factor); } float utils::scaleFactor() { - QSettings settings; - return settings.value("settings/scale_factor", -1).toFloat(); + QSettings settings; + return settings.value("settings/scale_factor", -1).toFloat(); } QString utils::descriptiveTime(const QDateTime &then) { - const auto now = QDateTime::currentDateTime(); - const auto days = then.daysTo(now); + const auto now = QDateTime::currentDateTime(); + const auto days = then.daysTo(now); - if (days == 0) - return QLocale::system().toString(then.time(), QLocale::ShortFormat); - else if (days < 2) - return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); - else if (days < 7) - return then.toString("dddd"); + if (days == 0) + return QLocale::system().toString(then.time(), QLocale::ShortFormat); + else if (days < 2) + return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); + else if (days < 7) + return then.toString("dddd"); - return QLocale::system().toString(then.date(), QLocale::ShortFormat); + return QLocale::system().toString(then.date(), QLocale::ShortFormat); } DescInfo @@ -194,630 +193,622 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUser, const QString &displayName) { - using Audio = mtx::events::RoomEvent; - using Emote = mtx::events::RoomEvent; - using File = mtx::events::RoomEvent; - using Image = mtx::events::RoomEvent; - using Notice = mtx::events::RoomEvent; - using Text = mtx::events::RoomEvent; - using Video = mtx::events::RoomEvent; - using CallInvite = mtx::events::RoomEvent; - using CallAnswer = mtx::events::RoomEvent; - using CallHangUp = mtx::events::RoomEvent; - using Encrypted = mtx::events::EncryptedEvent; - - if (std::holds_alternative